こんにちは.AIチームの邊土名です.
本記事はAI Shift Advent Calendar 2022の17日目の記事です.
今回は,音声認識誤りを軽減させることを目的として,BERTを用いて音声認識 (Automatic Speech Recognition; ASR) 結果のN-Bestをリランキングすることを試みました.
はじめに
AI Messenger Voicebot のような音声対話システムにおいては,音声認識誤りが切っても切り離せない大きな課題となっています.弊社でも音声認識誤りに対処するための研究開発を進めており,このテックブログでもたびたびトピックに挙げています.
具体的な音声認識誤りの例として,システム (Bot) が「個数をおっしゃってください」と発話した後,ユーザーが「3個」と発話したのにも関わらず「ハンコ」と誤認識されることがあります.特に難しいのが同音異義語で,たとえば「9時」が「久慈」に誤認識されるケースが挙げられます.以前の発話内容を考慮すればよしなに認識できそうな気もしますが,一般的な音声認識システムは現時点での発話しか入力しないため対処が難しいという問題があります.
しかし,音声認識結果のN-Bestを見てみると正しい認識結果が含まれていることがあります.実際,上記で挙げた「ハンコ」「久慈」のケースでも,N-Bestの上位に「3個」「9時」がそれぞれ含まれていることが確認できました.N-Bestの中から正しい音声認識候補を取得する方法については,大規模言語モデルを用いて直前のBot発話と関連度の高い音声認識候補を取ればうまくいくような気がします.
そこで,本稿では,BERTを用いてASR出力のN-Bestをリランキングし,最も関連度が高かった候補を最終的な音声認識結果として出力することで,音声認識誤りを軽減させることができないか検証していきたいと思います.
データセット作成
今回は評価用データセットとして, Megagon Labs が公開している「宿泊施設探索対話コーパス」を使用します.こちらのコーパスは,架空の宿予約サービス上でオペレーター(Agent)とカスタマー(User)が日本語でテキストチャットしているという想定のもと集められた対話データで,210対話分のデータが含まれています.
この対話コーパスから,オペレーターとカスタマーの発話ペアを作成します.オペレーターのテキストを「直前のBot発話」とみなすことで,Bot発話の文脈を考慮してユーザー発話の音声認識結果をリランキングする実験が行えるようになります.以下のような対話ペアを1874件作成しました.
{
"agent": "さようでございますか。それでは、駐車場を無料でご利用できるホテルをお探しします。立地ですが、観光地をまわりやすい場所はいかがでしょうか?",
"user": "はい、観光地をまわりやすい場所にあるといいですね。ただ1番の目的は出雲大社なので、そこまでアクセスがよければ助かります。"
}
次に,カスタマー発話のASR N-Bestを取得します.まず,pyopenjtalkのText-to-Speech(TTS)機能を用いて,テキストから音声に変換します.
import numpy as np
from scipy.io import wavfile
import pyopenjtalk
# Text-to-Speech
audio, sr = pyopenjtalk.tts(text)
wavfile.write('path/to/audio_file.wav', sr, audio.astype(np.int16))
最後に,Google Cloud Speech-to-Text (Google STT) を用いて音声認識させます.取得するASR仮説候補の数ですが,今回は最大10件,すなわち10-Bestとします.ここで,音声品質を電話と近づけるためにサンプリングレートを8kHzに落としています.
import io
import librosa
import soundfile
from google.cloud import speech
# Google Cloud Speech-to-Text の設定
account_json = 'path/to/gcp_credential_file'
client = speech.SpeechClient.from_service_account_json(account_json)
config = speech.RecognitionConfig(
encoding=speech.RecognitionConfig.AudioEncoding.LINEAR16,
sample_rate_hertz=8000,
max_alternatives=10, # 最大10件のASR仮説候補を出す(10-Best)
model='default',
use_enhanced=False,
language_code="ja-JP",
)
with io.BytesIO() as fio:
# 音声データを8kHzにダウンサンプリングして読み込む
audio, _ = librosa.load('path/to/audio_file.wav', sr=8000)
soundfile.write(fio, audio, samplerate=8000, subtype='PCM_16', format='WAV')
fio.seek(0)
# 音声認識
response = client.recognize(
config=config,
audio=speech.RecognitionAudio(content=fio.read()))
asr_nbest = []
for result in response.results:
# ASRのN-bestを保存
for alt in result.alternatives:
asr_nbest.append(alt.transcript)
最終的に,以下のような音声認識結果が取得できます(10-Bestの中に「出雲大社」が含まれていない点がかなり気になりますが……)
タイ観光地が流れやすい場所になるといいですねただ一番の目的はいつもと一緒なのでそこまでアクセスが良ければ助かります
タイ観光地が流れやすい場所にあるといいですねただ一番の目的はいつもと一緒なのでそこまでアクセスが良ければ助かります
はい観光地を流れやすい場所にあるといいですねただ一番の目的はいつもと一緒なのでそこまでアクセスが良ければ助かります
はい観光地を流れやすい場所になるといいですねただ一番の目的はいつもと一緒なのでそこまでアクセスが良ければ助かります
タイ観光地を流れやすい場所にあるといいですねただ一番の目的はいつもと一緒なのでそこまでアクセスが良ければ助かります
タイ観光地を流れやすい場所になるといいですねただ一番の目的はいつもと一緒なのでそこまでアクセスが良ければ助かります
はい観光地が流れやすい場所になるといいですねただ一番の目的はいつもと一緒なのでそこまでアクセスが良ければ助かります
はい観光地が流れやすい場所にあるといいですねただ一番の目的はいつもと一緒なのでそこまでアクセスが良ければ助かります
はい観光地を流れやすい場所にやるといいですねただ一番の目的はいつもと一緒なのでそこまでアクセスが良ければ助かります
タイ観光地が流れやすい場所にやるといいですねただ一番の目的はいつもと一緒なのでそこまでアクセスが良ければ助かります
BERTによるASR N-Bestのリランキング
今回の実験では,BERTの Next Sentence Prediction (NSP) のアプローチでASR N-Best仮説をリランキングしたいと思います.
NSPは,2つの文を入力した際に2文目が1文目の続きかどうかを予測するタスクで,BERTの事前学習でも採用されています.入出力は以下のようになります.
Input: [CLS] the man went to [MASK] store [SEP] he bought a gallon [MASK] milk [SEP] Output: Label (IsNext or NotNext)
なお,NSPを事前学習で行うことの有効性に関しては以前から疑問が呈されており,後継のRoBERTaなどのモデルでは使われなくなってしまいました……
そんなNSPですが,直前のBot発話を1文目,ユーザ発話のN-Best候補の1つを2文目としてNSPを行うことで,どのN-Best候補が文脈的に尤もらしいのかをN-Best候補間で定量的に比較することができるのではないかと考えました.すなわち,入力は以下のようになります.
[CLS](オペレーターのテキスト/直前のBot発話)[SEP](ユーザ発話のN-Best:1)[SEP] [CLS](オペレーターのテキスト/直前のBot発話)[SEP](ユーザ発話のN-Best:2)[SEP] [CLS](オペレーターのテキスト/直前のBot発話)[SEP](ユーザ発話のN-Best:3)[SEP] …
それでは早速実装していきたいと思います.BERTモデルは東北大学のbert-base-japanese-whole-word-maskingを使用し,BertForNextSentencePredictionでPretrainモデルを読み込みます.
from transformers import BertForNextSentencePrediction, BertJapaneseTokenizer
tokenizer = BertJapaneseTokenizer.from_pretrained('cl-tohoku/bert-base-japanese-whole-word-masking')
model = BertForNextSentencePrediction.from_pretrained('cl-tohoku/bert-base-japanese-whole-word-masking')
model.eval()
BERTの出力値(logits)を降順にソートした際のindexをリランキング結果とします.
import torch
# context (str): オペレーターのテキスト
# nbest (List[str]): カスタマー発話のASR N-Best
input_texts = zip([context] * len(nbest), nbest)
inputs = tokenizer.batch_encode_plus(input_texts, return_tensors='pt', padding=True)
with torch.no_grad():
output = model(**inputs, labels=torch.LongTensor([1]*len(nbest)))
ranking = output.logits[:, 0].argsort(descending=True).tolist()
評価
今回は評価指標として Word Error Rate (WER) , Character Error Rate (CER) を採用します.WERとCERの計算には JiWER を使用しました.また,前処理として,音声認識結果には含まれにくいであろう句読点・疑問符・感嘆符は削除しました.以下に評価結果を示します.
WER [%] | CER [%] | |
Oracle | 7.3 | 9.3 |
Random | 16.8 | 16.4 |
ASR 1-Best | 12.5 | 13.4 |
BERT NSP | 15.6 | 15.6 |
ここで,OracleはN-Best中からWER/CERが最小となる候補を選択した際の性能,すなわち性能の上限値を示しています.RandomはN-Best中からランダムにASR結果を選択した際の性能,ASR 1-Bestは一般的な音声認識システムと同様にN-Best中で最も確信度が高かった候補をそのまま出力した際の性能を示しています.
WERとCERの結果を見る限りでは,残念ながらBERT NSPによるリランキング性能は単にASR出力の1-Bestを取ったときよりも低いという結果になりました.
ただし,N-Bestの中に正解データ(カスタマーのテキスト)を含め正解データを識別できるのかを検証してみると, 57.0 % の精度 (Accuracy) で識別できていることが確認できました.このことから,Fine-tuningしていないBERTでも直前の文脈を考慮して正しいASR仮説を選択できる能力はある程度有していると考えられます.
Accuracy [%] | |
Random | 12.7 |
BERT NSP | 57.0 |
また,今回の実験ではBERT NSPのリランキングはうまくいかなかったのですが,ASR 1-BestとOracleを比べると1-Bestの方のエラー率が4〜5ポイント程度高いことから,何かしらの手段でN-Best候補をリランキングすることは有効だといえるでしょう.
おわりに
本稿では,BERTを用いたASRのN-Best仮説をリランキングする実験を行いました.残念ながらWER,CERで評価すると通常の1-Bestより劣るという結果になってしまいましたが,Oracleの結果を見る限りリランキングすること自体は有効であると考えられます.
今後はリランキング手法の改善およびリランキングが後続タスク (Slot-filling, Intent Classification) に与える影響も含めて調査していきたいと思います.
ここまで読んでいただきありがとうございました!
明日はDevチームの由利より「SQLBoilerでMulti Schemaへのアクセスを試してみる」が公開予定です.こちらの記事もぜひご覧いただければと思います.