概要
OpenAI Whisperを活用した日本語歌詞のforced-alignmentの試行錯誤をしています。
通常のWhisperはフレーズレベルの音声区間検出しかできないのに対して、単語レベルの音声区間検出をできるよう改良を加えたstable-tsを試してみます。
結論としては、日本語音楽データに対してはそのまま使うのは難がありそうな精度でした。
なお本記事は流れとしてはその3の続きというよりはその1の続きに近いです。
シリーズ一覧は以下
【試行錯誤】OpenAI Whisperを活用した日本語歌詞のforced-alignment リンクまとめ
背景
Whisperでフレーズレベルよりも細かいタイムスタンプを検出したいという需要はままあるようで、本家のgithubでDiscussionがあったりします。
このDiscussionを追っていくと、「word-levelのタイムスタンプ取得をできるようにしたよ」と言っている方がいて、その方のコードがstable-tsです。
whisperのモデルを上書きするような構成らしいです。本家は英語を想定してチェックしていると思いますので、日本語歌詞に対してどの程度精度がでるかサクッと検証してみます。
環境
Google Colabでやります。
検証
stable-tsのReadMeに従うだけです。
pipでwhisperをインストールした後、プロジェクトをクローンしてフォルダに移動します。
!git clone https://github.com/jianfch/stable-ts.git
cd stable-ts
以下を実行します。ReadMeとの差分として、モデルを精度を優先してlargeに変更します。
音源はその2で作成した音源分離済みのボーカル音声を使います。
非常に簡単に実行できて、ありがたいです。
import whisper
from stable_whisper import modify_model
model = whisper.load_model('large', 'cuda')
modify_model(model)
# modified model should run just like the regular model but with additional hyperparameters and extra data in results
results = model.transcribe('../yorunikakeru_vocals.wav')
stab_segments_org = results['segments']
stab_segments_orgはdictのリストになっていて各要素の"whole_word_timestamps"というkeyに単語ごとのタイムスタンプが格納されています。
1つめと2つめの結果を見てみます。
for s in stab_segments_org[0]['whole_word_timestamps']:
print(s)
print("")
for s in stab_segments_org[1]["whole_word_timestamps"]:
print(s)
{'word': '沈', 'timestamp': 1.079999953508377}
{'word': 'む', 'timestamp': 1.75}
{'word': 'ように', 'timestamp': 1.75}
{'word': '溶', 'timestamp': 1.9799998998641968}
{'word': 'けて', 'timestamp': 2.319999933242798}
{'word': 'ゆ', 'timestamp': 2.5}
{'word': 'く', 'timestamp': 2.5}
{'word': 'ように', 'timestamp': 5.7799999713897705}
{'word': '二', 'timestamp': 6.059999942779541}
{'word': '人', 'timestamp': 6.269999980926514}
{'word': 'だけ', 'timestamp': 15.339999675750732}
{'word': 'の', 'timestamp': 15.519999980926514}
{'word': '空', 'timestamp': 15.619999408721924}
{'word': 'が', 'timestamp': 17.0}
{'word': '広', 'timestamp': 18.0}
{'word': 'が', 'timestamp': 18.329999923706055}
{'word': 'る', 'timestamp': 18.329999923706055}
{'word': '夜', 'timestamp': 22.539999961853027}
{'word': 'に', 'timestamp': 22.539999961853027}
timestampは始点(?)だけで、区間にはなっていないようです。
一見良さげな結果に見えますが、「く」と「ように」の間が3秒も空いていますが、実際はこんなには空いていません。
また「空が広がる夜に」は15秒以降に発話されたことになっていますが、このあたりはBGMの期間です。普通にsegmentを検出したときにBGMがsegmentに含まれてしまう問題が、こっちの結果にも悪影響を及ぼしていそうです。
詳細は把握していませんが、以下のコードにより、よりタイムスタンプを安定させられると言っているように読めます。
やってみます。
# or to get token timestamps that adhere more to the top prediction
from stable_whisper import stabilize_timestamps
stab_segments = stabilize_timestamps(results, top_focus=True)
同様に1つめと2つめを表示してみます。
for s in stab_segments[0]['whole_word_timestamps']:
print(s)
print("")
for s in stab_segments[1]["whole_word_timestamps"]:
print(s)
{'word': '沈', 'timestamp': 1.079999953508377}
{'word': 'む', 'timestamp': 1.75}
{'word': 'ように', 'timestamp': 1.75}
{'word': '溶', 'timestamp': 1.9799998998641968}
{'word': 'けて', 'timestamp': 2.319999933242798}
{'word': 'ゆ', 'timestamp': 2.5}
{'word': 'く', 'timestamp': 2.5}
{'word': 'ように', 'timestamp': 5.7799999713897705}
{'word': '二', 'timestamp': 6.059999942779541}
{'word': '人', 'timestamp': 6.269999980926514}
{'word': 'だけ', 'timestamp': 15.339999675750732}
{'word': 'の', 'timestamp': 15.519999980926514}
{'word': '空', 'timestamp': 15.619999408721924}
{'word': 'が', 'timestamp': 17.0}
{'word': '広', 'timestamp': 18.0}
{'word': 'が', 'timestamp': 18.329999923706055}
{'word': 'る', 'timestamp': 18.329999923706055}
{'word': '夜', 'timestamp': 22.539999961853027}
{'word': 'に', 'timestamp': 22.539999961853027}
結果はstabilize_timestampsをかけるまえと全く同様でした。
今回のデータではあまり効果がないのか、ひょっとしたら3つ目以降の要素では変わっている部分もあるのか、わかりませんが、少なくとも今回のタスクで修正されてほしいところを修正できる期待はうすそうです。
以上から、stable-tsによる区間検出は、少なくともYOASOBI「夜に駆ける」に対してはあまり正確な検出ができなさそうでした。
おわりに
whisperがBGM部分を発話区間とみなしてしまう問題は、stable-tsでも残り続けており、その結果、wordレベルのタイムスタンプはあまり精度が出ないことがわかりました。
とはいえ、今後更に改良される可能性はあるので、動向を見守りつつ、いったんはその3でやったような方向性で引き続き進もうと思います。