日本語に特化した高精度な音声認識 ReazonSpeech を使って、会議音声を書き起こししてみた

2023.01.31

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんちには。

データアナリティクス事業本部 機械学習チームの中村です。

本記事では、株式会社レアゾン・ホールディングスが2023年1月18日に公開したReazonSpeechを使ってみたいと思います。

ReazonSpeechはプロダクト群の総称で、日本語に特化した超高精度なモデルと、その学習に使用されたコーパス、コーパス作成のためのツールなどが公開されています。

詳細は下記リンクを参照ください。

モデルの方は、ライセンスがApache-2.0となっており商用利用も可能な形となっています。

コーパスはテレビなどの音声となりますので、CDLA-Sharing-1.0 (ただし利用目的は著作権法30条の4に定める情報解析に限る)とされています。

今回はこのモデルをColab上で動かして、以前投稿したWhisperなどと比較してみたいと思います。

実行手順は下記の以前の記事も参考にされてください。

実行環境

Google Colab環境で実行します。ハードウェアのスペックは以下の通りです。

今回は低めのスペックで実施します。

  • GPU : なし
  • メモリ : 12GB (標準)

主要なソフトウェアのバージョンです。

  • Python : 3.8.10
  • transformers : 4.26.0
  • espnet : 0.10.3
  • openai-whisper : 20230124

使ってみた

データの準備

以前のWhisper記事で使用した音源を使用します。

(社内勉強会の私が発話しているMeet録画機能のデータです)

以下で音声を30秒分切り出します。サンプリングレートはライブラリ側で吸収しないため、この時点で16,000Hzに変換します。

(Whisperはライブラリ側でレート変換を行い、内部的には16,000Hzデータとして扱っている点は同じです)

!ffmpeg -i "{元の動画データ}" -ss 610 -to 640 -ac 1 -ar 16000 "clean.wav"

コマンドの説明は以下です。

  • -ss 610 -to 640 : 610secから640secを切り出し
  • -ac 1 : 出力する音声データ(a)のチャンネル数(c)をモノラル(1)に
  • -ar 16000 : 出力する音声データ(a)のサンプリングレート(r)を16000に

切り取られた音源は以下です。

雑音を重畳した音声も作成しておきます。こちらも前回同様の手順で実施します。

!ffmpeg -i "{ノイズデータの元ファイル}" -ss 0 -to 30 -ar 16000 "noise.wav"

このノイズ音源を8倍マシにしてミックスします。

!ffmpeg -i "clean.wav" -i "noise.wav" -filter_complex \
    "[0:a]volume=1.0,channelsplit=channel_layout=mono[a1];\
     [1:a]volume=8.0,channelsplit=channel_layout=mono[a2];\
     [a1][a2]amerge=inputs=2" \
    -ac 1 "snmix_snr_high.wav"

作成した音源は以下の通りです。

ReazonSpeechモデルの準備と動かし方

以下に公式のColabサンプルがあります。これを参考に音声ファイルを認識させます。

必要なモジュールの準備は以下です。

!pip install -q espnet==0.10.3
!pip install -q espnet_model_zoo
import warnings
warnings.filterwarnings('ignore')

その後、公開済みのモデルを取得します。

import librosa
import torch
from espnet2.bin.asr_inference import Speech2Text

device = "cuda" if torch.cuda.is_available() else "cpu"
beam_size = 5

reazonspeech = Speech2Text.from_pretrained(
    "reazon-research/reazonspeech-espnet-v1",
    beam_size=beam_size,
    batch_size=0,
    device=device
)

認識は以下のコードで実行できます。

speech, sample_rate = librosa.load("clean.wav", sr=16000)
result = reazonspeech(speech)
print(result[0][0])

この結果については後程比較します。

処理時間の計測

%%timeitを使うことで処理時間の計測が可能です。ロードは考慮したくないため以下のような形で計測します。

%%timeit
reazonspeech(speech)

この処理時間についても後程比較します。

Whisper側の準備と動かし方

Whisperはlarge-v2, large-v1, medium, baseを今回試しました。

コード例はlarge-v2のケースで記載していますが、他のモデルサイズも同様の手順で可能です。

Whisperは以下でセットアップします。

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

その後、公開済みのモデルを取得します。

import whisper
whisper_large = whisper.load_model("large") # large-v2がロードされる

注意点としてlargeを指定すると、自動的にlarge-v2となりますので、以前のバージョンを使用したい場合はlarge-v1を明示的に指定する必要があります。

コード的には以下が該当します。

以下の動画によればlarge-v2は英語以外の性能が大きく向上しているとのことでした。

Whisperによる認識は以下のコードで実行できます。

result = whisper_large.transcribe("clean.wav", language="ja")
print(result["text"])

この結果については後程比較します。

処理時間の計測

計測用のコードは以下を使いました。

実行環境のメモリがシビアな場合でメモリ関連で問題が発生する場合は、ランタイムの再起動が必要かもしれません。

%%timeit
model.transcribe("clean.wav", language="ja")

この処理時間についても後程比較します。

処理時間の比較

それぞれのモデルで30秒のデータを処理した場合の結果です。

種類 30秒データの処理時間
ReazonSpeech 1min 28s
Whisper large-v2 7min 24s
Whisper large-v1 2min 33s
Whisper medium 1min 26s
Whisper base 22.7s

そもそもWhisper側が全体的に前回の検証時よりも高速になっているようです。

ReazonSpeechはWhisper largeよりパラメータ数が大幅に少ないということで、その通りにかなり高速に終わることが分かりました。 今回の実行環境では、ReazonSpeechはWhisper medium相当の速度で処理が可能なようです。

また今回の実行環境で実時間で終わらせるためには、Whisper baseを選択する必要がありそうです。

認識結果の比較(clean)

次に認識結果を比較します。以下が雑音無しの場合の認識結果の比較です。

種類 テキスト
正解のテキスト 今あるんですけれども このボックスの位置 縦横どこら辺にあってその大きさがどれくらいかっていうのを推定する問題にまず一つなってますと でそれを検出 ボックスを検出したうえで通常のクラス分類ですね そういったこれがイスなのかどうかというのを推定するというこの2種類の問題を解く必要があるのが物体検出という風
ReazonSpeech それが今はあるんですけれどもこのボックスの位置がどこら辺にあってその大きさがどれぐらいかっていうのを推定するのがまず一つになっていますのでそれを見つけるということをしていただきたいというふうに思うんですねそういったことがあったのかどうかっていうことをするとそういうことなのかなということがあるので
Whisper large-v2 が今あるんですけれども このボックスの位置 縦横どこら辺にあってその大きさがどれぐらいかっていう のを推定する問題に まず一つなってますと それを検出 ボックスを検出した上で 通常のクラス分類ですねそういった これがイスなのかどうか ってのを推定するという この2種類の問題を解く必要があるのが物体 検出というふうになります
Whisper large-v1 が今あるんですけれども このボックス の位置 縦横どこら辺にあってこの大きさがどれぐらいかっていう のを推定する問題にまず一つなってますと それを検出 ボックスを検出した上で 通常のグラス分類ですねそういった これが椅子なのかどうか っていうのを推定するという この2種類の問題を解く必要があるの が物体検出というふうになって
Whisper medium が今あるんですけれども このボックス の位置 縦横どこら辺にあって 大きさがどれぐらいかっていうのを推定 する問題にまず一つなってますと それをボックスを検出した上で 通常のグラス分類ですね そういったこれが椅子なのかどうかっていう のを推定するという この二種類の問題を解く必要があるのが物体検出 というふうになってます
Whisper base が今あるんですけれどもこのボックスの位置 縦を行うことから辺にあってこの大きさがどれぐらいかというのを推定する問題にまず一つなってますとそれを検出 木層検出した上で通常のクラス分類ですねそういった これが椅子なのかどうかというのを推定するというこの2種類の問題を得意つうがあるのが 物体検出という Whole Companyの

最も良い結果を示したのはWhisper large-v2かなという印象です。

ReazonSpeechも前半は良い感じなのですが、後半は発話してない内容が埋められているような状態です。

後半の認識結果が不安定なのはWhisper baseも同様のようです。

印象だけだとフェアではないので、正解テキストとの距離指標を計算してみます。

(漢字と読みの話は一旦脇に置いて、表記文字レベルの比較とさせてくださいm(_ _)m)

以下で計算用のモジュールをインストールします。

!pip install python-Levenshtein

算出のためのコードは以下です。

ref = "今あるんですけれども このボックスの位置 縦横どこら辺にあってその大きさがどれくらいかっていうのを推定する問題にまず一つなってますと でそれを検出 ボックスを検出したうえで通常のクラス分類ですね そういったこれがイスなのかどうかというのを推定するというこの2種類の問題を解く必要があるのが物体検出という風"
tars = [
    "それが今はあるんですけれどもこのボックスの位置がどこら辺にあってその大きさがどれぐらいかっていうのを推定するのがまず一つになっていますのでそれを見つけるということをしていただきたいというふうに思うんですねそういったことがあったのかどうかっていうことをするとそういうことなのかなということがあるので",
    "が今あるんですけれども このボックスの位置 縦横どこら辺にあってその大きさがどれぐらいかっていう のを推定する問題に まず一つなってますと それを検出 ボックスを検出した上で 通常のクラス分類ですねそういった これがイスなのかどうか ってのを推定するという この2種類の問題を解く必要があるのが物体 検出というふうになります",
    "が今あるんですけれども このボックス の位置 縦横どこら辺にあってこの大きさがどれぐらいかっていう のを推定する問題にまず一つなってますと それを検出 ボックスを検出した上で 通常のグラス分類ですねそういった これが椅子なのかどうか っていうのを推定するという この2種類の問題を解く必要があるの が物体検出というふうになって",
    "が今あるんですけれども このボックス の位置 縦横どこら辺にあって 大きさがどれぐらいかっていうのを推定 する問題にまず一つなってますと それをボックスを検出した上で 通常のグラス分類ですね そういったこれが椅子なのかどうかっていう のを推定するという この二種類の問題を解く必要があるのが物体検出 というふうになってます",
    "が今あるんですけれどもこのボックスの位置 縦を行うことから辺にあってこの大きさがどれぐらいかというのを推定する問題にまず一つなってますとそれを検出 木層検出した上で通常のクラス分類ですねそういった これが椅子なのかどうかというのを推定するというこの2種類の問題を得意つうがあるのが 物体検出という Whole Companyの"
]

for t in tars:
    dist = Levenshtein.distance(ref, t)
    dist2 = Levenshtein.jaro_winkler(ref, t)
    print(f"{dist}, {dist2:.02f}")

結果は以下となりました。

種類 レーベンシュタイン距離 ジャロ・ウィンクラー距離
ReazonSpeech 74 0.64
Whisper large-v2 23 0.81
Whisper large-v1 26 0.79
Whisper medium 30 0.77
Whisper base 45 0.76

おおむね主観的な印象と変わらない距離指標の結果となっています。

認識結果の比較(雑音あり)

次に雑音ありの場合の認識結果の比較です。

種類 テキスト
正解のテキスト 今あるんですけれども このボックスの位置 縦横どこら辺にあってその大きさがどれくらいかっていうのを推定する問題にまず一つなってますと でそれを検出 ボックスを検出したうえで通常のクラス分類ですね そういったこれがイスなのかどうかというのを推定するというこの2種類の問題を解く必要があるのが物体検出という風
ReazonSpeech そうなんですけれどもこのボックスの位置というのはこの辺りにあってこのあとの動きはどれぐらいかというところですけれどもまず1つですということでそれを見つけるということをしているということでこれはいつなのかどうかというのを見ていくとこの2種類になっているということです
Whisper large-v2 ボックスの位置、縦横どこら辺にあって、大きさがどれくらいかを推定する問題をまず一つ決めます。それを検出、ボックスを検出した上で、通常のグラス部に、それからこれがイスなのかどうかを推定する。2種類の読得必要があるのが、絶対決め必要という風になります。
Whisper large-v1 このボックスの位置、縦横どこら辺にあって、大きさがどれぐらいかを設定する問題、まず一つにしています。それを検出、ボックスを検出した上で、通常のグラス、それが椅子なのかどうかを全て、2種類のモノを得る必要があるのが大気道室という風に思います。
Whisper medium ボックスの位置、縦横どこら辺にあって、大きさがどれくらいかを設定する問題がまず一つあります。それを目測した上で、通常のグラス部品や椅子などの物を設定するために、2種類の物を取得する必要があるのが、物体検出というふうに思います。
Whisper base そのアンスケットの僕の位置を固定コロコラフにやって、大きさがどれぐらいかの設定する問題、まず一つの設定をしています。それを検出する、目装検出した上で、通常のブラスで、 vonできる人はお願いしながらコロコに出そうです。

雑音ありでも、最も良い結果を示したのはWhisper large-v2かなという印象です。

こちらも距離指標を計算しておきましょう。(コードは割愛)

種類 レーベンシュタイン距離 ジャロ・ウィンクラー距離
ReazonSpeech 95 0.59
Whisper large-v2 71 0.64
Whisper large-v1 74 0.63
Whisper medium 89 0.61
Whisper base 110 0.54

まとめ

いかがでしたでしょうか。一部のデータでの限定的な検証内容ではありますが、ロバスト性や認識精度を追求するのであればWhisper、より高速で高い水準の精度としてはWhisper mediumやReazonSpeechなどが選択肢になるのかなという印象を受けました。

認識精度は実際には自然発話や読み上げなどタスクに依存する話になっていくので、双方を見極める必要があると思います。

本記事がそういった比較の際の参考になれば幸いです。