前回、WhisperのモデルをONNXにする方法について記述した。
Whisperのモデルは、単体では音声認識はできず、音声をメルスペクトログラムにする前処理と、トークンをデコードして文字列にする後処理が必要になる。
今回は、前処理と後処理をC#で実装する方法について記述する。
音声を16kHzにリサンプリングする
Whisperでは、ffmpeg-pythonを使用してリサンプリングしている。
.NETでffmpegを扱う良いライブラリがなかったので、.NETのオーディオライブラリのNAudioを使用して、音声をリサンプリングした。
using (var reader = new AudioFileReader("a.wav")) { var resampler = new WdlResamplingSampleProvider(reader, SAMPLE_RATE).ToMono(); resampler.Read(audio, 0, audio.Length); }
対数メルスペクトログラムに変換する
メルスペクトログラムは、音声をフーリエ変換したスペクトルを2乗した、パワースペクトルにメル尺度を適用したものである。
WhisperのPython実装では、フーリエ変換にPyTorchのtorch.stftを使用しているが、C#では別の方法で実装が必要である。
NAudioにもFFTのメソッドがあるが、フレームのサンプル数が2のべき乗という制約がある。Whisperは1フレームが400サンプルのため使用できない。
そのため、.NETの数値ライブラリのMathNet.Numericsを使用した。
窓関数
Whisperでは、窓関数には、ハン窓が使用されている。
MathNet.Numericsの、Window.Hannで実装した。
パディング
時刻0をフレームの中心にして、スライディングウィンドウで、フレームを切り出す際に、両端でパディングが必要になる。
Whisperでは、パディングの方法には、reflectが使用されている。
ライブラリがないため、スクラッチで実装した。
フーリエ変換
MathNet.NumericsのFourier.Forwardを使用して実装した。
サンプル数でスケーリングは行わない。
Fourier.Forward(frame, FourierOptions.NoScaling);
メル尺度の適用
メル尺度の係数は、whisper/assets/mel_filters.npzに保存されている。
C#で読み取れるように、PythonのNumpyでRawデータにしてバイナリファイルにしておき、それを読み込むようにした。
対数変換
対数にした後に、スケールの変換を行っているので、Whisperの実装と同じに実装した。
logSpec = logSpec.PointwiseMaximum(1e-10).PointwiseLog10(); logSpec = logSpec.PointwiseMaximum(logSpec.Enumerate().Max() - 8.0); logSpec = (logSpec + 4.0) / 4.0;
前処理は、以上である。
推論
ONNXRuntimeを使用して、推論を行う。
推論の箇所は、言語の自動判定と、メインループの2か所にある。
メインループでは、推論結果に対して、いくつか特定のトークンを抑止するためのマスク処理がある。
Pythonの実装をそのまま移植して実装した。
推論結果をトークン列にする
推論結果は、トークンIDごとの確率として出力されるので、確率が最大となるトークンIDを見つけて、トークン列とする。
トークン列は、そのままでは可読可能な文字列ではないため、デコード処理が必要である。
トークンのデコード
Whisperでは、文字列のトークン化に、バイトレベルBPEという方法が使われている。
その処理には、transformersライブラリのGPT2TokenizerFastが使用されている。
デコード処理の内部は、Rustで実装されているため、Rustのコードを読み解いてC#で同様の処理を実装した。
処理は単純で、
- UTF8の各バイトを1文字に変換するルールに従った対応表
- トークンIDから対応表のキー列となる文字列に変換する対応表
の2つの対応表を使って、トークン列からUFT8のバイト列に変換を行う。
1.の対応表は、Rustの実装をC#に移植した。
2.の対応表は、whisper/assets/multilingual/vocab.jsonに保存されているjsonを使う。
以上で、音声からテキストに変換できる。
テスト
C#で実装した処理で、音声ファイルをテキストに変換できるか確認した。
「これはテストです。」とマイクで録音した音声ファイルを使って、言語が正しく認識されて、日本語テキストに変換できることを確認した。
>dotnet run a.wav ja これはテストです。
Whisperのtestsにあるjfk.flacでも試した。
>dotnet run jfk.flac en And so my fellow Americans ask not what your country can do for you, ask what you can do for your country.