🎙️

Wav2Vec2による日本語音声認識を試してみる

2022/08/03に公開

0. はじめに

0-1. 3行でこの記事をまとめると

  • Transformerという言語モデルがDeep Learning界隈で猛威を奮っているよ
  • 音声認識にもTransformerが使われ始めているよ
  • 日本語×Transformerの音声認識の記事が少ないから書いてみたよ

0-2. 必要な前提知識や物

  • 大学初等相当の数学をベースとする深層学習の知識
  • プログラミング言語Python及び深層学習フレームワークPytorchとその周辺に関する知識と開発環境
  • 深層学習モデルのTransformerに対する興味
  • VRAMが6GB(?)以上内蔵されているNVIDIA製GPUを搭載したPCまたはそれと同等以上のGoogle Colaboratoryなどのクラウド環境

1. 序論

Deep Learningが注目されるようになってから10年近く経ちました。当初は画像認識のために作られていたニューラルネットワークモデルは応用に応用を重ねられ、自然言語処理をはじめ多くの分野で従来の手法を大きく上回る精度を叩き出すことに成功しています。その中でも更に2017年に発表された論文、「Attention Is All You Need」はDeep Leaning界にさらなる震撼をもたらしました。論文中で提案されたTransformerと呼ばれるモデルは言語モデルの新たなマイルストーンとも呼べる存在になりました。 
Transformerは画像認識にも応用され、更には2022年8月現在、文章画像の生成モデルにおいても存在感を放つようになりました。 
Transformerの音声認識の分野において有名なものがWav2Vec 2.0(以下Wav2Vec2)です。本稿ではHugging Face Transformersに実装されているWav2Vec2を用いて、日本語の音声認識についての実験を行います。理論については最小限に抑え、実装の紹介を重視しています。

2. 関連記事や書籍

2-1. 音声認識について

Pythonを用いた音声認識については高島遼一先生の「Pythonで学ぶ音声認識 機械学習実践シリーズ」を一読しておくと本稿の理解が早まると思います。この手の本の中では理解しやすい本だと思います。Pythonの知識が前提となりますが、音声認識の歴史をたどり最後に深層学習による音声認識に挑むという内容の本です。不満だった点はTransformerによる音声認識については言及のみになっている点です(だから本稿を書いたという点もあります)。また本稿ではこの本の内容を引用することがあります。

2-2. Wav2Vec2について

本稿執筆時点(2022年8月)ではWav2Vec2について日本語で紹介されているブログはほとんどありませんでした。具体的には以下の2件です。

また、コードも公開されていません。まだまだ日本語音声認識にTransformerを使用するという事例は国内ではほとんど浸透していないように見えます。

3. Wav2Vec2の特徴

すでに何回か出現し、本稿の核となるワードですが「Wav2Vec2」とは何でしょうか。Wav2Vec2はFacebook Research(現 Meta Research)によって発表された音声認識の手法です。詳細は先に挙げた技術ブログなどに譲るとして、特に注目すべき特徴があります。

  • 従来手法(RNN-Based)のようにメルフィルタバンク特徴量などに変換せず生の信号を入力とする。
  • CNN Encoderで潜在表現に変換しTransformer Encoderでコンテクストを得るというRNNフリーなモデル。

1つ目も2つ目もPython音声認識本を読んでいれば直感に反するものです。特に1つ目については音声処理に関する特徴量抽出などの知識は全く必要としないということになります。2つ目は並列処理による学習が難しいというRNN系の弱点を克服しています。Wav2Vec2は上記のような特徴を持つEnd-to-Endなモデルであるというところは抑えておくと良いと思います。

4. 実装と実験

すべての実装はここに置きました。Jupyter Notebookを使用しています。実装を読めばおしまいですが、順に説明していきます。本稿ではJSUTコーパスを用いて音声認識を行います。

実行環境:

  • CPU:corei9-10900K
  • RAM:64GB
  • GPU:GeForce RTX 3090
  • OS:Ubuntu 20.04.2 LTS

Jupyter Notebookを起動し、00_prepare.ipynbを開いて実行してください。JSUTコーパスがダウンロードされ、サンプリング周波数を16000Hzにリサンプルする処理が行われます。コードについてはPython音声認識本をほぼ丸パクリしました。「No module named xxx」とエラーが表示されたときは、「xxx」をインストールする必要があります。pipで入るものもあればaptで入れるものもあるので適宜検索してください。

01_preprocess.ipynbを開いて実行してください。yamlのラベルデータを読み込み、ラベルのcsvファイルと辞書ファイルを作成します。これもほとんど書籍と同じです。ただし今回は先頭の250発話をvalidationかつtestデータとし、残りの4750発話をtrainデータとしました。設定を変えるにはコードブロック3つ目のtgtという変数に「roman」、「hira」、「char」のうちどれかを選択、入力してください。それぞれローマ字、ひらがな、文字(漢字やひらがななど)をラベルファイルとするということを意味しています。pykakasiを使用して単語ごとのローマ字、ひらがな、文字を取得するようにしています。今回は音素認識については異なる前処理が必要となるので行いません。

tgt = 'roman' #ローマ字
tgt = 'hira' #ひらがな
tgt = 'char' #文字

02_Wav2Vec2_tiny_train.ipynbを開いてください。コードブロック2つ目の変数tgtが先程選択したラベルの種類と一致している必要があります。00、01の処理が終わったら実行してみてください。コードはこちらを参考にしました。またWav2Vec2の事前学習モデルにはこちらをお借りしました。

02_Wav2Vec2_tiny_train.ipynbはGPUを使用します。CUDA Out Of Memory(OOM)などが出たらコードブロック2つ目の変数、per_device_train_batch_sizeを小さくしてみてください。ただbatch_sizeが1でもOOMするならGPUをVRAMが大きいものに変える必要があると思います。

TRAIN_ALL_WEIGHTS = True 
lr = 1e-4 
num_train_epochs = 50 
per_device_train_batch_size = 16 # ここを変更
torch.backends.cudnn.benchmark = True 
tgt = 'roman' 

他にも「worker (数字) is killed.」というエラー表示が出たら、コードブロック22個目の引数内にあるdataloader_num_workersをos.cpu_count()から小さい非負整数、特に0に変更してみてください。ただし学習時間が若干長くなることがあります。

- dataloader_num_workers=os.cpu_count(), #変更前
+ dataloader_num_workers=0, #変更後

他にもLossが0になる、nanになる場合は半精度計算をOFFにしてみてください(デフォルトではOFF)。

今回使用するモデルはtinyとあるだけあって、パラメータ数は約2300万パラメータ、Transformer Encoder Blockは6つで、Largeと呼ばれるモデルと比較して90%以上のパラメータ削減が行われています。下に単純な比較を書いておきます。

  • Tiny:Encoder Block数6、隠れ層のニューロン数384、合計パラメータ数約2300万

  • Base:Encoder Block数12、隠れ層のニューロン数768、合計パラメータ数約9400万

  • Large:Encoder Block数24、隠れ層のニューロン数1024、合計パラメータ数約3.2億

学習には筆者の環境では2時間強かかりました。何故かGPUがフル回転せずどこかにボトルネックがあるように感じました。原因がわかる方はご連絡いただけると嬉しいです…

学習が終わったら03_inference.ipynbを開いて実行してください。これもほとんど音声認識本のコードを拝借しました。tgtの設定を忘れずに。推論結果とエラーレートが出力されます。

5. 実験結果

学習結果をPython音声認識本と並べてみます。書籍にはCTCとAttentionの結果が載っています。これとWav2Vec2-tiny(W2V2-t)との結果を比較してみましょう。データセットの内容が少し異なるのでややアンフェアですが、各TER(CER)を以下に載せます。

  • 音素(ローマ字)
    • CTC:1.95%, Attention:1.71%, (W2V2-t:1.96%)
  • ひらがな
    • CTC:3.82%, Attention:4.00%, W2V2-t:3.28%
  • 文字
    • CTC:28.41%,  Attention:38.27%, W2V2-t:33.49%

「torch.backends.cudnn.benchmark = True」を使用しているため、この結果には再現性がなく誤差が発生します。

6. 考察

Wav2Vec2-tiny(2300万パラメータ)による結果は、RNN系によるCTC(660万パラメータ)やAttention(800万パラメータ)と比較して特別優れているというわけでは無いようでした。 
まずモデルを変えずに改善できそうなところを考えてみましょう。Transformerは大規模言語モデルです。例えばVision Transformer(ViT)が莫大な数の画像を事前学習することによって高い精度を獲得したように、Transformerの学習にはデータ量が必要です。今回は学習データが4750発話しかなかったのでこの発話数を増やすことができれば精度が上がるかもしれません。Python音声認識本でもAttention(Transformerの構造もAttentionが含まれている)で精度を得るためには大量のデータを必要とする旨が書かれています。今回は試しませんでしたが、逆にデータ量をこれ以上減らすと急激にエラーレートが上がるのではないでしょうか。データを増やすにはData Augmentationの追加が考えられます。波形を直接Augmentationする技法はあまりたくさんは思いつきませんが、torchaudioなどを使えばある程度できると考えています。librosaは並列処理が苦手(もしくは不可能?)なようでした。しかしCPUがボトルネックっぽいのに更に遅くなりそうな処理を入れるのは… 
次に考えられるのはモデルを変えることです。計算時間の観点から今回はtinyを採用しましたが、BaseやLargeを試してみる価値はあります。今回の実験は本来はBaseを採用する予定だったのですが、何故か最頻値崩壊(Mode Collapse)を起こし学習が進まなかったので、急遽tinyに変えました。筆者はWav2Vec2-large-XLSR-53でローマ字ラベルを試しましたが、TERが0.64%だったのでモデルの大きさは正義かもしれません。モデルを変更するにはコードブロック15個目を以下のように記述します。その他にもHugging Faceでは様々な人が様々なモデルを公開しています。気になるPretained Modelがあったら試してみるといいでしょう。

model = Wav2Vec2ForCTC.from_pretrained(
+    'facebook/wav2vec2-large-xlsr-53', # モデルの変更
-    #'charsiu/zh_w2v2_tiny_fc_10ms', # tinyモデル
    attention_dropout=0.2,
    hidden_dropout=0.2,
    feat_proj_dropout=0.2,
    mask_time_prob=0.1,
    layerdrop=0.2,
    ctc_loss_reduction="mean", 
    pad_token_id=processor.tokenizer.pad_token_id,
    diversity_loss_weight=100
)

+ model.lm_head = nn.Linear(1024, len(processor.tokenizer)) # ニューロン数を変更
model.config.vocab_size=len(processor.tokenizer)

7. 参考文献、リンク

記事筆者のGithub PageTwitterはこちら。情報が明らかに間違っている、誤植を発見したなどあればご指摘ください。単なる感想でもウェルカムです。

8. おまけ

この後では先程学習させたモデルを用いて、リアルタイム音声認識に挑戦してみるという内容と、Common Voiceというリアルデータを用いた音声認識モデルの作成に挑戦してみることにします。ソースコードは上に載せたものと同じリンクです。omakeフォルダ内のプログラムを使用します。

8-1. リアルタイム音声認識

単純に録音された音声を認識するだけでは用途が狭いです。pythonのライブラリ群を用いてリアルタイム音声認識に挑戦してみましょう。omakeフォルダの中にあるrtsr.pyに実装しています。pyaudioというライブラリが必要で、インストール方法が特殊なので検索してください。Mac OSの場合はかなり面倒なようです。 
プログラム自体に特に複雑な点はなく、色んなサイトから情報を寄せ集めたものです。while文を用いて録音した音声をモデルに推論させるということをしています。1.5秒間無音が続いたら一旦録音を終了してモデルを動かすというサイクルが行われます。認識されない場合はマイクの確認を行ってください。pyaudioが、使用したいマイクと異なるデバイスの音を拾っている可能性が高いです。音声認識に飽きたらCtrl+Cで終了してください。 
音声認識の精度について議論してみましょう。データセットとして使用した音声は1話者で十分な音質を持っています。一方でこちらが入力する音声には様々な音質や環境音の混入がある他、そもそも話者が異なるため、予測可能な音声の分布と予測したい音声の分布が異なってしまいます。その結果、testデータセットに対してTERが3%程度のものでも、実際に使用してみると精度の低さに驚くことでしょう。異なる話者や環境でも十分な認識精度を持たせるには様々な話者、様々な音質の学習データセットを用いて学習させる必要がありそうです。

8-2.  Common Voiceデータセットを用いたモデル構築

データを集めるというのも難しい問題です。本来であれば様々な話者、様々な環境のもとで録音された音声が大量に必要になります(つまり莫大な予算が必要になる)。FireFoxでおなじみのMozillaが運営するCommon Voiceプロジェクトによって提供されるデータセットはこの問題を緩和します。データセットを利用するにはメールアドレスなどいくつかの情報を提供する必要がありますが、2022年8月現在50時間を超える日本語音声データを無料で入手することができます。これを利用しない手はないでしょう。
一方でこのデータセットにはいくつかの問題点やバイアスが存在し、

  • 音声が20代、30代の男性に偏っている
  • 非ネイティブと思われる話者の片言の発声がある
  • S/N比が低過ぎて、何を言っているかわからない音声
  • ボイスチェンジャーを用いた謎の高い音声

などなど、結構カオスなデータセットであることも理解して使用する必要があります。 
何故かCommon VoiceのVersionが10だと学習の際、系列長が巨大な数になりOOMするというバグがあったので、今回はVersion9(ja_49h_2022-04-27と書かれています)を使用します。 
まずはデータセットをダウンロード、解凍したフォルダを00_prepare.ipynbと同ディレクトリに配置してください。mp3をpythonライブラリで使用するにはffmpegが必要になります。

$ sudo apt-get -y install ffmpeg

その後00_prepare.ipynbを実行してみてください。2022年8月現在ではコードが動きますが、データセットの更新によって動作しなくなる可能性があるので適宜コードを変更してください。このプログラムは実行に時間がかかるので、その間に01_preprocess.ipynbを実行してしまっても良いでしょう。00はmp3ファイルをサンプリング周波数16000Hzにリサンプルした後、wavファイルとして書き出すということを行います。01はデータセットのパスやラベルについての記述があるファイルに情報を足してcsvファイルに変換するというものです。JSUTの音声認識で使用した01_preprocess.ipynbとやっていることは似ています。今回は先頭500発話をvalidationかつtestデータセットとし、残りをtrainデータとしました。またターゲットラベルはひらがなを指定しました。

00, 01の実行が終わったら02_Wav2Vec2_XLSR_train.ipynbを実行してみてください。このノートブックとJUSTを使用して学習するノートブックとの主な違いは、Data Augmentationがマシマシになっているところです。これにより学習速度が半分くらいに落ちてしまいました。本格的に精度向上を考えるとき以外はOFFにしても良いかもしれません。学習が終わったら03_inference.ipynbで精度を確認しましょう。

学習にかかったおおよその時間とひらがなにおける精度を書いておきます。

  • Tiny, Data augmentationなし:RTX2070搭載PCで約20時間、TER:21.43%
  • Large, Data augmentationあり:RTX3090搭載PCで約90時間、TER:8.77%

Common Voiceがノイジーなデータセットであるため、データ量を増やしたにもかかわらずTestデータのTER自体はJSUTを用いたときと比べて上昇してしまいました。一方でリアルタイム音声認識に完成したモデルを使ってみると、認識精度の改善を体感できると思います。話者数や録音環境のバリエーションは増えるため、認識可能な分布が広くなり精度の向上が得られているということでしょう。
他にもJSUTを用いたモデルでCommon Voiceのデータを予測させてみたり、その逆をやってみたりしてみると、先ほどのリアルタイム音声認識の精度の低さを理解できたりするので面白いと思います。

8-3. まとめとFuture Work

近年ディープラーニングモデルの中でも特に注目されているTransformerを用いた音声認識モデルのWav2Vec2についての紹介と実装を行いました。先人たちの知恵を借りると比較的楽に実装できてしまうものです。 
今後の課題としていくつか考えられるものがあります。まず1つ目がデータセットの量と質の向上です。今回はCommon Voiceを使用しましたが、量も質もまだまだ十分ではありません。本格的に商用などに利用したい場合はデータのクレンジングとさらなるデータ収集を行うべきでしょう。2つ目はモデルの質の向上です。ハイパパラメータのチューニングやData Augmentationなどが適切かについてはあまり深く考えずにモデルを作りました。行き着く先はKaggle的な技術になるかと思われますが、限界まで性能を上げたいときには必要になるでしょう。 
挑戦してみたいこととして、単語ごとに認識させるということをやってみたいと考えています。今回は1文字ごとに認識させるというものでした。例えばT5Tokenizerのようなトークナイザを用いて単語区切り(つまり1文字以上)にして学習、認識させるとまた違った結果が得られるのではないかと思います。
身も蓋もない話ですが、単に精度の高い音声認識が欲しければGCPのSpeech-to-Textなど、Big Techが提供しているサービスを利用するほうが満足度は高いでしょう。しかしながらある程度自力で前処理やモデルの構築ができるようになっておくということは新しいアイデアの種となるということが起こりうるのではないでしょうか。筆者はそう考えています。
Wav2Vec2は2020年の論文であり、これはディープラーニングの世界においては決して新しい技術ではありません。今後も音声認識についてどのような手法が登場するのか、そしてその手法をキャッチアップしていきたいと思います。そしてこの記事をここまで読んでいただいた方も少しでもそう思っていただけるような内容であったなら、この記事を執筆した意味がありました。繰り返すようですがここまでお読みいただきありがとうございました。

Discussion