【AI Shift Advent Calendar 2022】Whisperによる音声認識のTips

こんにちは!AIチームの東です。
本記事はAI Shift Advent Calendar 2022の16日目の記事です。

先日はOpenAIが公開した汎用的な音声認識モデルWhisperの紹介記事を書きましたが、本記事ではもう少し踏み込んで、具体的な活用例についていくつか紹介したいと思います。

Whisperの活用例

基本的な利用方法

先日のTech blogでも述べた通り、Whisperを用いた音声認識はGoogle Colab等の環境でこのように簡単に実行することができます。

!pip install git+https://github.com/openai/whisper.git
import whisper

model = whisper.load_model("base")
result = model.transcribe("sample.wav")

load_model()で使用したいモデルのサイズ(今回は”base”)を指定し、transcribe()メソッドで認識させたい音声ファイルを指定するだけでresultに様々な結果(認識結果、タイムスタンプ、検出した言語情報など)が格納されます。

複雑な処理を必要とせず、気軽に高精度の音声認識を行えるためか、GitHubのDiscussionsには質問や他のツールとの統合など、世界中の方から様々な情報がやり取りされており、有益な知見を得ることができます。

今回はその中から活用例を何点かピックアップして紹介していきます。

Promptによる認識結果の制御

transcribe()にはオプションとして--initial_promptを指定することができます。前回の記事で紹介したデコーディング戦略の一つ「直前までの認識結果の条件付きの利用」では、(30秒以上の)音声を認識する際に、直前までのセグメントの認識結果をデコーダの入力に加えるというプロセスを取っているのですが、このオプションではそのテキストを直接指定することができます。

Discussionsでは認識結果がこのテキストのスタイルに寄せた結果になるという報告が寄せられており、具体的には大文字/小文字や句読点の有無を(ある程度)制御できるそうです。

実際に日本語の音声を使って、promptの効果を試してみます。

文脈情報の付与による認識結果の改善

弊社が運用しているAI Messenger Voicebotでは電話応対業務を自動化するサービスを提供しているのですが、応対シナリオの中には特定のエンティティ(商品名、名前、地名など)だけを聞く、というターンが存在します。そのような場合には、ユーザからどのような発話が来るか予め予想が立てられるため、その文脈情報を事前情報としてモデルに渡すことで認識結果の改善が見込めそうです。

今回は、名前と番地表現のそれぞれについて、電話口で私が発話した音声を使って効果を検証しました。

名前(発話内容=あづまゆうき)の認識例:

  • initial_promptなし:
{'text': 'わずもキゅうき',
'segments': [{'id': 0,
'seek': 0,
'start': 0.0,
'end': 4.5,
'text': 'わずもキゅうき',
'tokens': [50364, 9206, 18216, 4801, 15535, 824, 227, 2646, 7016, 50589],
'temperature': 1.0,
'avg_logprob': -2.158733194524592,
'compression_ratio': 0.7,
'no_speech_prob': 0.16119693219661713}],
'language': 'ja'}
  • initial_prompt=”名前は”:
{'text': 'あずまゆき',
'segments': [{'id': 0,
'seek': 0,
'start': 0.0,
'end': 30.0,
'text': 'あずまゆき',
'tokens': [50364, 3590, 18216, 2889, 34072, 7016],
'temperature': 0.0,
'avg_logprob': -0.915644645690918,
'compression_ratio': 0.625,
'no_speech_prob': 0.039263028651475906}],
'language': 'ja'}

番地表現(発話内容=2-6-14)の認識例:

  • initial_promptなし:
{'text': 'ニロ now',
'segments': [{'id': 0,
'seek': 0,
'start': 0.0,
'end': 7.34,
'text': 'ニロ now',
'tokens': [50364, 34737, 17164, 586, 50731],
'temperature': 1.0,
'avg_logprob': -4.067421595255534,
'compression_ratio': 0.5555555555555556,
'no_speech_prob': 0.21271148324012756}],
'language': 'ja'}
  • initial_prompt=”番地は10-84。番地は”:
{'text': '2-6-14。',
'segments': [{'id': 0,
'seek': 0,
'start': 0.0,
'end': 4.0,
'text': '2-6-14。',
'tokens': [50364, 17, 12, 21, 12, 7271, 1543, 50564],
'temperature': 0.0,
'avg_logprob': -0.7278143564860026,
'compression_ratio': 0.5294117647058824,
'no_speech_prob': 0.047076333314180374}],
'language': 'ja'}

認識させたい対象によってどのようなpromptが適しているのかはあまり自明ではないですが、認識結果を求める表現に寄せて改善させることができました(また、今回の認識結果はbaseモデルを使った結果であり、largeモデルを使った場合はpromptがなくても正確な書き起こし結果が得られました)。

話者交代タイミングの提示

少し変わった使い方として、複数話者が話している音声に対して、話者の切り替わりを認識結果に加えることもできるようです。各発話の先頭に”- ”を加えた会話例を入れることでそのような動作になることを確認しました。また、今回の挙動はlargeモデルを使った場合に現れ、baseモデルでは動作しませんでした。なお、簡単のために入力音声にはGoogle TTSで作成した(読み上げた)音声ファイルを使用しました。

  • initial_prompt=”- はじめまして。 - こちらこそはじめまして。”:
{'text': '- 今日はいい天気ですね。- そうですね。',
'segments': [{'id': 0,
'seek': 0,
'start': 0.0,
'end': 2.0,
'text': '- 今日はいい天気ですね。',
'tokens': [12, 220, 43791, 13806, 6135, 25870, 14277, 1543],
'temperature': 0.0,
'avg_logprob': -0.31040320676915784,
'compression_ratio': 1.0185185185185186,
'no_speech_prob': 9.024541213875636e-05},
{'id': 1,
'seek': 200,
'start': 2.0,
'end': 28.0,
'text': '- そうですね。',
'tokens': [50364, 12, 36165, 14277, 1543, 51664],
'temperature': 0.0,
'avg_logprob': -0.4157192366463797,
'compression_ratio': 0.6896551724137931,
'no_speech_prob': 5.738270920119248e-05}],
'language': 'ja'}

トークン単位の音声区間検出

Whisperの学習にはサブタスクとして音声区間検出(VAD)が組み込まれており、音声のない箇所をno_speechと判定することができ、認識結果のタイムスタンプ情報を出力することができます。

しかし、WhisperがWeakly supervisedで学習されているためか、(おそらく)正確な音声区間情報が学習データに不足しており、長文の認識結果が悪化する、長い無音が入るとうまく動作しないなど様々な問題が発生しています。

そのため、タイムスタンプの出力の挙動を安定化させるロジックを組み込んだ実装が有志により公開されています。

この実装では通常のタイムスタンプ情報に加え、トークン単位のタイムスタンプが出せるようになっており、より細かい時間情報が取れそうです。

実装をpipでインストールする他は公式実装とほぼ同様に動かすことができます。

! pip install git+https://github.com/jianfch/stable-ts.git

from stable_whisper import load_model
model = load_model('large')
prompt = "株式会社AI Shiftは、サイバーエージェントの子会社として2019年8月30日に設立いたしました。"
results = model.transcribe('sample.wav', initial_prompt=prompt)

また、タイミング付きの字幕ファイルを書き出す関数が定義されていたため、以下のように字幕ファイルを生成して、簡単な動画を作成してみます。

from stable_whisper import results_to_sentence_word_ass
results_to_sentence_word_ass(results, 'audio.ass')
ffmpeg -f lavfi -i color=size=720x120:rate=25:color=black -i sample.wav -vf "subtitles=new_audio.ass:force_style='Fontsize=30'" -shortest audio_subbed.mp4

概ね正しそうなのですが、所々タイムスタンプがずれていそうな結果になりました。この辺りは音声の音質や内容によってかなり左右されそうなので、色々とチューニングが必要そうです(蛇足ですが、promptにAI Shiftを含む文章を入れたため、「AI Shift」と「シフトする」を分けて認識できるようになっています)。

おわりに

今回の記事では、OpenAIが発表した汎用的な音声認識モデルWhisperの利用法をその結果とともに紹介しました。Discussionsは活発な議論が行われおり、今後も引き続き注視するとともに弊社での活用法についても考えていきたいと思います。

明日はAIチームの邊土名よりBERTを用いたASRのN-Best仮説のリランキングについて記事が出る予定です。こちらもご覧いただけると幸いです。
最後まで読んでいただきありがとうございました!

参考リンク

https://github.com/openai/whisper/discussions/29
https://github.com/openai/whisper/discussions/108

PICK UP

TAG