【試行錯誤】OpenAI Whisperを活用した日本語歌詞のforced-alignment その４：stable-tsを試す

# 概要
OpenAI Whisperを活用した日本語歌詞のforced-alignmentの試行錯誤をしています。
通常のWhisperはフレーズレベルの音声区間検出しかできないのに対して、単語レベルの音声区間検出をできるよう改良を加えた[stable-ts](https://github.com/jianfch/stable-ts)を試してみます。
結論としては、日本語音楽データに対してはそのまま使うのは難がありそうな精度でした。

なお本記事は流れとしては[その３](https://qiita.com/shimajiroxyz/items/de213cd333e7bf846781)の続きというよりは[その１](https://qiita.com/shimajiroxyz/items/c019cfd60ba2953eb463)の続きに近いです。

シリーズ一覧は以下
[【試行錯誤】OpenAI Whisperを活用した日本語歌詞のforced-alignment リンクまとめ](https://qiita.com/shimajiroxyz/items/c91c5074fd24608ea5b1)


# 背景
Whisperでフレーズレベルよりも細かいタイムスタンプを検出したいという需要はままあるようで、[本家のgithubでDiscussion](https://github.com/openai/whisper/discussions/3)があったりします。
このDiscussionを追っていくと、「word-levelのタイムスタンプ取得をできるようにしたよ」と言っている方がいて、その方のコードが[stable-ts](https://github.com/jianfch/stable-ts)です。
whisperのモデルを上書きするような構成らしいです。本家は英語を想定してチェックしていると思いますので、日本語歌詞に対してどの程度精度がでるかサクッと検証してみます。
# 環境
Google Colabでやります。
# 検証
[stable-ts](https://github.com/jianfch/stable-ts)のReadMeに従うだけです。
pipでwhisperをインストールした後、プロジェクトをクローンしてフォルダに移動します。

```
!git clone https://github.com/jianfch/stable-ts.git
```

```
cd stable-ts
```

以下を実行します。ReadMeとの差分として、モデルを精度を優先してlargeに変更します。
音源は[その２](https://qiita.com/shimajiroxyz/items/6d253a0a6c53ca65027e)で作成した音源分離済みのボーカル音声を使います。
非常に簡単に実行できて、ありがたいです。


```Python
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に単語ごとのタイムスタンプが格納されています。
１つめと２つめの結果を見てみます。

```Python
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は始点（？）だけで、区間にはなっていないようです。
一見良さげな結果に見えますが、「く」と「ように」の間が３秒も空いていますが、実際はこんなには空いていません。
また「空が広がる夜に」は１５秒以降に発話されたことになっていますが、このあたりはBGMの期間です。普通にsegmentを検出したときにBGMがsegmentに含まれてしまう問題が、こっちの結果にも悪影響を及ぼしていそうです。

詳細は把握していませんが、以下のコードにより、よりタイムスタンプを安定させられると言っているように読めます。
やってみます。

```Python
# 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)
```

同様に１つめと２つめを表示してみます。

```Python
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をかけるまえと全く同様でした。
今回のデータではあまり効果がないのか、ひょっとしたら３つ目以降の要素では変わっている部分もあるのか、わかりませんが、少なくとも今回のタスクで修正されてほしいところを修正できる期待はうすそうです。

以上から、stable-tsによる区間検出は、少なくともYOASOBI「夜に駆ける」に対してはあまり正確な検出ができなさそうでした。

# おわりに
whisperがBGM部分を発話区間とみなしてしまう問題は、stable-tsでも残り続けており、その結果、wordレベルのタイムスタンプはあまり精度が出ないことがわかりました。
とはいえ、今後更に改良される可能性はあるので、動向を見守りつつ、いったんは[その３](https://qiita.com/shimajiroxyz/items/de213cd333e7bf846781)でやったような方向性で引き続き進もうと思います。