panco’s blog

興味が沸いたことを書く

軽い気持ちで音声データをテキスト化したら全然うまくいかず頓挫している件

進捗が悪く、一度もブログ化できていなかったトピックを記事にする。

「進捗が悪い」の裏には「仕事が忙しくてそもそも時間が取れていない」「トライアンドエラー祭りで滞る」「なんかやる気出ない」といった状況が絡んでくるが、2023 年 11 ~ 12 月はすべてが該当した。
最後にこの件で Github に Push したのが 2023 年 11 月上旬ということを知り、戦慄している 1 月下旬の深夜 2 時。(しかも first commit が最初で最後だったという・・・戦慄極まりない)

震えていても何も始まらないので、今回やりたかったこと、実施したこと、何がうまくいかなくて断念したのかをこの記事で整理しておく。

執筆時点では失敗に終わっており、他に取り組まねばならない趣味の開発があるため一旦切り上げる。興味はまだあるため、諸々落ち着いたら自分でこのブログを読み返して再チャレンジしたい。おそらく GW の時期になりそう。

前置きが長くなったが、本題に入る。

やりたかったこと

  • 国内外の競馬実況で頻繁に使われる単語を抽出し、何かしら特徴があるかを分析する
  • 国内外の競馬実況の周波数を可視化し、何かしら特徴があるかを分析する

単なる好奇心から気になったことであり、1 ~ 2 日あればできそうだと思っていた。しかし、やってみたら全然できなかった。すでに分析している人がいるのかもしれないが(いないか)、結果より過程を楽しみたいので調べていない。。

執筆時点の状況

国内外の競馬実況で頻繁に使われる単語を抽出し、何かしら特徴があるかを分析する

→ 紆余曲折を経て挫折(この記事のトピック。詳細は後述する)

国内外の競馬実況の周波数を可視化し、何かしら特徴があるかを分析する

→ 未着手(上述の単語抽出で詰まったため余力なし)

実施したこと

1.競馬実況音声データ取得

5 か国のマイルの G1 レースを対象にした。レース映像から適当に wav を作成して音声データとした。5 か国 × 3 レースで計 15 レースをサンプルとした。

2.音声データをテキスト化(ここで詰まった)

1 で用意した wav ファイルをテキスト化して、形態素解析なんかをする想定だった。しかし、想定外なことに音声データのテキスト化に苦戦してしまい、断念している。
音声データのテキスト化にあたり、SpeechRecognition というライブラリを使った。

GitHub - Uberi/speech_recognition: Speech recognition module for Python, supporting several engines and APIs, online and offline.

SpeechRecognition は音声認識に関する様々なエンジン・API に対応しているが、導入が簡単そうだった Google Speech Recognition で音声認識・テキスト化に取り組んだ。簡単そうという判断がすでに間違っていたのかもしれないが、現時点では何が最適解かわからずにいる。(マイクの音声をリアルタイムでテキスト化する用途で SpeechRecognition を使っている記事は何個か見つけたが、wav ファイルをテキスト化した事例は比較的少なかった。)

次に実装内容や詰まった点を記載する。

実行環境

実装 ver.1.0

まず、サンプルコードを見つつ実装したものがこちら。
サンプルコードは以下 URL を参照。

speech_recognition/examples/audio_transcribe.py at master · Uberi/speech_recognition · GitHub

import speech_recognition as sr

r = sr.Recognizer()

# ファイルパス設定
audio_file = 'horseracing-commentary/data/wav/02_DoncasterMile.wav'

# wav ファイル読込
with sr.AudioFile(audio_file) as source:
    audio_data = r.record(source)

# recognize speech using Google Speech Recognition
try:
    # 音声データをテキストに変換
    text_data = r.recognize_google(audio_data, language='en-US')

    # テキストファイルのファイル名を作成('wav' を 'txt' に変換)
    file_name = audio_file.replace('wav', 'txt')
    # テキストファイルに書き込み
    f = open(file_name, 'w')
    f.write(text_data)
    f.close()

# Raises a speech_recognition.UnknownValueError exception if the speech is unintelligible.
except sr.UnknownValueError:
    print('Google Speech Recognition could not understand audio')
# Raises a speech_recognition.RequestError exception if the speech recognition operation failed, if the key isn't valid, or if there is no internet connection.
except sr.RequestError as e:
    print('Could not request results from Google Speech Recognition service; {0}'.format(e))

OSError 発生(解決済み)

まあ動くでしょうと思い「実装 ver.1.0」を実行すると思わぬエラーが発生。

OSError: FLAC conversion utility not available - consider installing the FLAC command line application by running `apt-get install flac` or your operating system's equivalent

なんそれ!と思いつつググったら、stackoverflow が教えてくれた。

python - FLAC conversion utility not available - consider installing the FLAC command line application on Spyder/Windows 10 - Stack Overflow

According to the source code it is searching for a flac without exe extension that will not work in Windows. If that fails it looks for a file with a specific name (flac-win32.exe) in module folder. You can either try to remove the extension of the file in the System32 folder or put the file in the module folder.

上記ベストアンサーの通り、flac.exe -> flac に変更した。拡張子を外していいのかそわそわしたが、これで OSError が解消された。(自分は conda 環境の \Library\bin 配下にある flac.exe を flac に変更した。)

しかし、次のエラーが発生し、ドツボにはまる。

途中までしかテキスト化されない・・・(ほぼ頓挫)

プログラムは正常終了しているが、期待通りではない。2 分程実況し続けている実況者の声が、途中までしかテキスト化されていない。

実際の出力結果
02_DoncasterMile.txt

Wakefield the 20 for better than love this year's favorite and the guy's open now they're offering no speed by inspirational girl she'll settle last and Dillsburg won the start from Converse going forward is Forbidden Love and just focus slicing through as well then came Dallas and for my Thunderstruck and Miss Bristol Mr Brightside on the emperor River lighthouses pretty deep for let them by Lewis of indices who's improving on the rails then kissing on the inside of us from inspirational songs on the outside of ice bath and then came back to private eye from 55504 139.99 by the

文字起こしの精度が怪しい気もするがそれはさておき、どう見ても 2 分間実況し続けている人の語数にしては少なすぎるし、中途半端なところで切れてしまっている。

エラー解析の教訓だが、切り分けをせずにとりあえずググったり、思いついて怪しそうと思ったりした点から潰しにかかって自爆した。切り分けをしていないだなんて死にたくなるが、当時はこんなにはまると思っていなかったのもあり、軽い気持ちでやりすぎたのは反省。
まずはそもそも wav ファイルの読込が正常にできているのかをデバッグすべきだった、と今になって思う。

ということで、トライアンドエラーの内容を次に記載する。あれこれやったが、冒頭に書いた通り期待通りにはならなかった。

トライアンドエラー 1.無音状態が 5 秒続いたら読込終了とする

Recognizer をインスタンス化した後にコード追加。デフォルトでは 0.8 秒無音が続くと音声認識を終了させる仕様になっている。実況の場合、その程度一時的に無音となることはあり得るため、0.8 秒から 5 秒に変更した。(さすがにマイルの競馬実況で 5 秒無音だと放送事故という感覚)

r = sr.Recognizer()
# 無音の長さ(秒)を設定。5秒無音状態が続くと読込終了。
r.pause_threshold = 5

結果は「実装 ver.1.0」の出力結果と変わらず NG。

トライアンドエラー 2.無音状態の他にもいろいろ追加してみる

recognizer_instance.pause_threshold の他にもいろいろと設定変更できる(デフォルトで定義されている)属性があることを知り、とりあえず諸々調整した。しかし、結果は全く変わらなかった。音声データには実況以外に馬の走る音や歓声、雑音などが入っているため、それらが影響しているのではと疑ったが、検証した限りでは関係なかった。よって NG。

r = sr.Recognizer()
# 無音の長さ(秒)を設定。5秒無音状態が続くと読込終了。
r.pause_threshold = 5
# 周囲のノイズレベルを自動調整
r.dynamic_energy_threshold = False
# 閾値。設定値より小さい値は無音、大きい値は音声とみなされる。デフォルトは300
r.energy_threshold = 200

トライアンドエラー 3.読込の最大時間を設定

wav ファイルの読込で、duration という読込の最大時間を設定するパラメータが追加できたので実装。2 分程度のファイルに対し、3 分(180 秒)を設定。出力結果は変わらず NG。

# wav ファイル読込
with sr.AudioFile(audio_file) as source:
    audio_data = r.record(source, duration=180)

トライアンドエラー 4.サンプリングレート周りを確認

トライアンドエラー 1 ~ 3 をやった段階で、もう 12 月下旬だった。軽い好奇心から「実装 ver.1.0」を作ったのが 11 月上旬だったため、結構時間が経っておりらちが明かないため、stackoverflow に投げかけてみた。そこでもらったコメントを参考にサンプリングレート周りを確認した。
観点と確認結果は次の通り。

  • wav ファイルのサンプリングレートは?
    → 48kHz / 16bit
  • 変数 audio_data が持っているサンプリングレートは?
    → sample_rate: 48000, sample_width: 2

sampling width が怪しそうではある。stackoverflow では、sample_width に 1 or 4 を明示的に渡して検証してみては?というアドバイスをもらった。しかし、結果は変わらずお手上げモード。とても丁寧に教えてくれたので申し訳なかったが、結果 NG で頓挫した。

おわりに

消化不良過ぎるので、まじで落ち着いたらなんとかしたい。