はじめに
私の熱い『アイの歌声を聴かせて』ファン活動、アイカツ、はじまります!
私のアイカツ(#アイの歌声を聴かせて 技術者なりのファン活動)、シオン v0.0.1に大きく近づきました🙌
— nikkie にっきー シオンv0.0.1開発中⚒ (@ftnext) 2022年2月13日
この嬉しさ、「やったな、サンダー」級!!
プログラムを起動してから音声で入力、
すると音声でオウム返ししてくれます!😆
(デモでお見せしたいのですが、どう収録すればいいのかな?) pic.twitter.com/vew5qjSLv8
この記事は、アイの歌声を聴かせてに関する一技術者なりのファン活動の一幕です。
実は私、大好きなアイうたに出てくる"ポンコツAI"シオンを実装しようとしています。
今回は音声を認識してテキストに変換する方法について、これまでに分かったことを書き留めます。
クラウドベンダーが提供するAPIを使っていましたが、音声合成と同様に、機械学習モデルを使った実装に切り替えました(入門レベルであり、「やってみた系」です)。
アイうた劇中と重なる構成でアウトプットできることに気付いたため、2回に分けてお送りします。
目次
- はじめに
- 目次
- これまでのシオン・プロジェクト
- 音声を文字に起こす
- espnet_model_zoo
- 動作確認:sayコマンドでwavファイルを作る
- Microphoneから取得した音声データを書き起こす
- ところが、「シオンが変だ!」
- 動作環境
これまでのシオン・プロジェクト
- シオン v0.0.1を3月末までに作るのを目標に進めています
- 現在のシオン v0.0.1の実装
- 人が音声で入力
- 音声を文字に起こす
- 文字列を処理(今はオウム返し)
- 処理された文字列を読み上げる
- 4の部分について、macOSの
say
コマンドを、ttslearn
の機械学習モデルを使った音声合成に更新しました
今回は2の部分をアップデートします。
音声を文字に起こす
これまでの実装:Cloud Speech-To-Text API
GoogleのCloud Speech-To-Text APIを使っていました。
https://cloud.google.com/speech-to-text
ライブラリ SpeechRecognition
を使い、「マイクで受け取った音声をCloud Speech-To-Text APIに送り、返り値を受け取る」という実装です。
import speech_recognition as sr r = sr.Recognizer() with sr.Microphone() as source: print("なにか話してください") audio = r.listen(source) recognized_text = r.recognize_google_cloud( audio, credentials_json=credentials_json, language="ja-JP" ) print(recognized_text.strip())
SpeechRecognition
の Microphone
はよくできていて、これだけのコードで プログラムに音声で入力 できます。
音声データはCloud Speech-To-Text APIに送られ、書き起こしたテキストが手に入ります。
なお、Cloud Speech-To-Text APIを使うための初期設定やcredentials_json
の扱いは、ここでは省略しています。
これで最初に作る分には大成功1なのですが、シオンはスタンドアローン(通信できない)なので、APIを使わない方法を探す必要がありました。
そこで今回、機械学習モデルを使う方法に切り替えたわけです。
機械学習モデルを使った現在の実装
以下の学習済みモデルを利用しています。
https://huggingface.co/espnet/kan-bayashi_csj_asr_train_asr_transformer_raw_char_sp_valid.acc.ave
これをロードし、マイクで受け取った音声データを渡して、書き起こしたテキストを得ます。
順を追って、構成要素を紹介します。
espnet_model_zoo
ESPnetについては、kan-bayashiさん(ESPnetの開発者であり、上記のモデルの作成者)のチュートリアルが分かりやすいです。
また、同様の学習済みモデルをロードして使う例は、レトリバさんのブログにまとまっていました。
from espnet2.bin.asr_inference import Speech2Text speech2text = Speech2Text.from_pretrained( "kan-bayashi/csj_asr_train_asr_transformer_raw_char_sp_valid.acc.ave" )
動作確認:sayコマンドでwavファイルを作る
レトリバさんのブログにある「親譲りの無鉄砲で子供の時から損ばかりしている」(『坊っちゃん』ですね)の音声ファイルは、macOSのsayコマンドで作りました2。
$ say 親譲りの無鉄砲で子供の時から損ばかりしている -o sample.wav --data-format=LEF32@16000
@
以降はサンプリングレートです(man say
参照)。
今回のモデルは16000のデータで訓練されています3。
SoundFile
というライブラリを使って、wavファイルをNumPy arrayとして読み込みます。
学習済みモデルに渡すと、テキストが書き起こされます。
import soundfile speech_array, sampling_rate = soundfile.read("sample.wav") nbests = speech2text(speech_array) text, tokens, *_ = nbests[0] print(text)
Microphone
から取得した音声データを書き起こす
wavファイルの音声データを書き起こす方法は分かりました。
speech_recognition.Microphone
から取得した音声データもNumPy arrayにして学習済みモデルに渡せばよさそうですね。
Microphone
をlisten
した返り値は、AudioData
型です4。
AudioData
インスタンスはget_raw_data
メソッドで、音声データをbytes型で得られます5。
これらをつなぎ合わせたのが以下のコード:
import numpy as np import speech_recognition as sr from espnet2.bin.asr_inference import Speech2Text speech2text = Speech2Text.from_pretrained( "kan-bayashi/csj_asr_train_asr_transformer_raw_char_sp_valid.acc.ave" ) r = sr.Recognizer() with sr.Microphone(sample_rate=16_000) as source: print("なにか話してください") audio_data = r.listen(source) frame_bytes = audio_data.get_raw_data() speech_array = np.frombuffer(frame_bytes, dtype=np.int16) nbests = speech2text(speech_array) text, tokens, *_ = nbests[0] print(text)
np.frombuffer
はbytesをarrayに変えます。
https://numpy.org/doc/stable/reference/generated/numpy.frombuffer.html
ところが、「シオンが変だ!」
ワクワクしながらスクリプトを実行し、マイクから「こんにちは」と入力します。
『坊っちゃん』の一節が書き起こせたのですから、「こんにちは」の書き起こしは余裕なはず!
ところが、現れたのは
'で'
🙄
「シオンが変だ!」
これは実装にバグがあったからなのですが、原因と対処は後編でお送りします(2022/02/19 後編へのリンクを追加しました)。
動作環境
- macOS
- CPU環境で動かしています
- Python 3.9.4
- SpeechRecognition 3.8.1
- espnet-model-zoo 0.1.7
- espnet 0.10.6
- torch 1.10.2
- SoundFile 0.10.3.post1
-
↩sayコマンドを実行するスクリプトに、音声を入力できるように機能追加して動かせました!
— nikkie にっきー (@ftnext) 2022年1月9日
入力されたテキストをそのまま返す実装なので、「こんにちは」って言ったら「こんにちは」って言います。
動いた瞬間はプログラミングやってて今までで一番嬉しかったかもしれません #アイの歌声を聴かせて https://t.co/BSLf3YrcoV -
fs=16k ref:https://zenodo.org/record/4037458/↩
-
https://github.com/Uberi/speech_recognition/blob/3.8.1/reference/library-reference.rst#recognizer_instancelistensource-audiosource-timeout-unionfloat-none–none-phrase_time_limit-unionfloat-none–none-snowboy_configuration-uniontuplestr-iterablestr-none–none—audiodata↩
-
https://github.com/Uberi/speech_recognition/blob/3.8.1/reference/library-reference.rst#audiodata_instanceget_raw_dataconvert_rate-unionint-none–none-convert_width-unionint-none–none—bytes↩