はじめに
こんにちは、AIチームの杉山です。
本記事はAI Shift Advent Calendar 2022の13日目の記事です。
前回の私の記事では音声音素埋め込みを用いてエンティティ解決を行う論文を紹介しましたが、日本語に適用する際には少し考えなければいけない点があるため、今回はその問題とそれに対する一つの案を紹介したいと思います。
日本語テキストからの音素の取得
前回の記事では音声認識結果から音素を取得し、その音素列に対する埋め込みベクトルを求めることで検索精度の向上をおこなっていました。日本語テキストに対してはOpenJTalkなどを用いることで音素列を取得することができますが、今回はそのPythonラッパーであるpyopenjtalkを使って進めます。
pip install pyopenjtalk
pyopenjtalkでは、g2p(Grapheme-to-phoeneme)メソッドで読み仮名および音素列を取得することができます
import pyopenjtalk
# 読み仮名での出力
pyopenjtalk.g2p("株式会社AI Shiftの杉山です。", kana=True)
# 'カブシキガイシャAI\u3000シフトノスギヤマデス。'
# 音素列での出力
pyopenjtalk.g2p("株式会社AI Shiftの杉山です。")
# 'k a b u sh I k i g a i sh a e i a i pau sh i f U t o n o s u g i y a m a d e s U'
空白はそれぞれ特殊文字として出力されましたが、読み仮名と音素列がそれぞれ出力されていることが確認できます。
しかし日本語においては、文字列としては同じ表記ですが異なる読み方を持つような語句(同形異音語)が存在します。(例:十分[ジュップン・ジューブン], 河野[カワノ・コウノ])
このような語句を発話した音声に対して音声認識を行い、上記の方法で読み仮名を出力しようとするとどちらの読みだったのかがわからなくなってしまうという問題があります。
# ジュップンと発話して音声認識結果が「十分」になったと想定
pyopenjtalk.g2p('十分', kana=True)
# 'ジューブン'
# カワノと発話して音声認識結果が「河野」になったと想定
pyopenjtalk.g2p('河野', kana=True)
# 'コーノ'
前者のジュップンとジューブンでは意味が変わってしまいますし、後者では音声認識結果をもとに明示的確認で問い返そうとすると齟齬が起きてしまいます。
音声対話の例
システム「お名前を教えてください」
ユーザー「河野(カワノ)です」
システム「コーノさんでよろしいですか?」
ユーザー「いえ、違います。河野(カワノ)です」
システム「コーノさんでよろしいですか?」
ユーザー「もういいです!」
前者の例では文脈を考慮することで自然言語処理的なアプローチによりどちらの語句(かな)であるか推定できるかもしれませんが、後者に関しては後から推定することは困難です。
そのような問題に対し、親会社のサイバーエージェントの研究組織であるAI Labと連携してかな漢字混じりではなく高精度にかな表記で出力するような音声認識モデルの開発も進めていますが、ここではGoogleの音声認識モデルを使っている場合を想定して読みを推定する方法を考えてみます。
音声認識結果の取得
Googleの音声認識モデルは高精度でかな漢字混じりの認識結果を取得できますが、意図的にかなだけの結果を取得することはできません。
以下は手元の音声ファイルに対してGoogleの音声認識APIを適用するコードと、「コーノ」と読み上げた音声での結果です。
from google.cloud import speech as gs
gs_client = gs.SpeechClient()
speech_file = "YOUR_AUDIO_FILE"
with io.open(speech_file, "rb") as audio_file:
content = audio_file.read()
audio = gs.RecognitionAudio(content=content)
config = gs.RecognitionConfig(
encoding=gs.RecognitionConfig.AudioEncoding.LINEAR16,
sample_rate_hertz=16000,
language_code="ja-JP",
)
response = gs_client.recognize(config=config, audio=audio)
for result in response.results:
print(f"{result=}")
# 結果
alternatives {
transcript: "河野"
confidence: 0.940834165
}
単語単位での確信度を出力するオプションを有効にすると、副次的に読み方の候補を出力してくれますが、複数ある場合にはどちらが正しそうか、という情報は出力されません。
# 同様のコードは割愛
config = gs.RecognitionConfig(
encoding=gs.RecognitionConfig.AudioEncoding.LINEAR16,
sample_rate_hertz=16000,
language_code="ja-JP",
enable_word_confidence=True,
)
response = gs_client.recognize(config=config, audio=audio)
for result in response.results:
print(f"{result=}")
# 「コーノ」と読み上げた音声での認識結果
alternatives {
transcript: "河野"
confidence: 0.940834165
words {
word: "河野|カワノ,コーノ"
confidence: 0.922872782
}
}
# 「カワノ」と読み上げた音声での認識結果
alternatives {
transcript: "河野"
confidence: 0.885048866
words {
word: "河野|カワノ,コーノ" # どちらの音声でも読み方候補の順序は変わらず
confidence: 0.826526582
}
}
音声認識結果からの読みの推定
このように、音声認識結果1つだけでは読みがどちらであったかを区別することができないため、複数の結果を用いることを考えます。今はスコアTop1の結果だけを見ていますが、それぞれの認識候補は読みの近いものが出力されていると考えられるため、いわゆるN-bestの結果も見ることにします。max_alternativesオプションにN-bestのNを指定することでスコアTopNまでの認識候補を得ることができます。(今回の例ではN=5)
# 同様のコードは割愛
config = gs.RecognitionConfig(
encoding=gs.RecognitionConfig.AudioEncoding.LINEAR16,
sample_rate_hertz=16000,
language_code="ja-JP",
enable_word_confidence=True,
max_alternatives=5,
)
response = gs_client.recognize(config=config, audio=audio)
for result in response.results:
for alt in result.alternatives:
print(f"{alt.transcript=}")
# 「コーノ」と読み上げた音声での認識候補上位5件
alt.transcript='河野'
alt.transcript='こうの'
alt.transcript='この'
alt.transcript='今日の'
alt.transcript='紅の'
# 「カワノ」と読み上げた音声での認識候補上位5件
alt.transcript='河野'
alt.transcript='川野'
alt.transcript='川の'
alt.transcript='かわの'
alt.transcript='革の'
今回の例ではかなだけの結果も含まれていましたが、それが無く、かな漢字混じりの結果だけだったとしても各候補に対してMeCabなどで読みを取得し、多数決を取るなどすれば本来の読み方を推定することができそうです。
import MeCab
tagger = MeCab.Tagger()
tagger.parse('河野')
# 河野\tコーノ\tコウノ\tコウノ\t名詞-固有名詞-人名-姓\t\t\t1\nEOS\n'
# MeCabでN-bestの結果を取得する場合(N=3)
tagger.parseNBest(3, '河野').split('\n')
# ['河野\tコーノ\tコウノ\tコウノ\t名詞-固有名詞-人名-姓\t\t\t1', 'EOS',
# '河野\tカワノ\tカワノ\tカワノ\t名詞-固有名詞-人名-姓\t\t\t1', 'EOS',
# '河野\tコーノ\tコウノ\tコウノ\t名詞-固有名詞-地名-一般\t\t\t1', 'EOS', '']
「コーノ」の例であれば「紅の」(クレナイノ、コーノ)が同じ読み方を持ちますし、「今日の」(キョーノ、コンニチノ)は編集距離などで見てカワノよりコーノの方が近いため河野=コーノであることが推測できます。
また、「カワノ」の例では全ての候補が「カワノ」の読みを持つため河野=カワノであると考えられます。
おわりに
今回の記事では、音声認識結果が一意に読みが定まらないような同形異音語であった場合に、N-bestの結果を用いることで本来の発話内容を推定する方法を紹介しました。これにより、読み情報を用いた音声QA検索や、前回私の記事で紹介したような音素埋め込みなどが、日本語においても適切に適用できることが期待されます。
ここまで読んでいただきありがとうございました。明日はAIチームの友松より今日明日で開催中の第13回対話システムシンポジウムの参加報告記事が出る予定です。こちらもご覧いただけると幸いです。