OpnenAIの音声認識器「Whisper」について調べてみた

## はじめに
こんにちは。shoheiです。
[WINC(早稲田コンピュータ研究会) Advent Calendar 2022](https://adventar.org/calendars/8102) 21日目の記事です。(2日遅れました、、、すいません🙇)

OpnenAIがリリースしているWhisperという音声認識サービスについて調査したので記事にします。

## Whisperとは
Whisper([GitHub](https://github.com/openai/whisper))とは、多言語において高精度な音声認識器で翻訳や言語認識の機能も搭載しています。
特に精度がとても良く、人間レベルで音声認識ができるのです。
精度と実行時間はトレードオフの関係にあるため、Whisperには以下のモデルが用意されています。(引用：https://github.com/openai/whisper)
|Size|Parameters|English-only model|	Multilingual |model	Required VRAM	|Relative speed
|---|---|---|---|---|---|
|tiny	|39 M	|tiny.en	|tiny	|~1 GB	|~32x
|base	|74 M	|base.en	|base	|~1 GB	|~16x
|small	|244 M	|small.en	|small	|~2 GB	|~6x
|medium	|769 M	|medium.en	|medium	|~5 GB	|~2x
|large	|1550 M	|N/A	|large	|~10 GB	|1x

また、GPUを使わずにCPUのみでも早く実行できる[Whisper.cpp](https://github.com/ggerganov/whisper.cpp)も開発されています。(これについは今回は扱いません。また後で記事にしようと思います。)
今回はbaseとmediumのモデルをWhisperで試して精度と処理時間を調査してみようと思います。MacBookProのCPUとcolaboのGPUを使って試してみます。

## 試してみる
私自身が音楽が好きなので今回は歌詞の音声認識を行うことにしました。

### 使用するデータについて
インターネット上の[ボーカル素材｜著作権フリーの無料音楽素材ダウンロード](https://music-note.jp/vocal/)より、著作権フリーのボーカル素材を取得し、これらの歌詞の音声認識を行います。
上記のサイトから
- music nation(musicnation.wav)
- good love(good-love.wav)
- only words(desperate.mp3)
をダウンロードして試してみます。

|data|length(m:s)|language|
|---|---|---|
|musicnation.wav|1:26|English|
|good-love.wav|1:36|English|
|desperate.mp3|2:39|Japanese|


### PyTorch, Whisper, ffmpegのInstall
whisperを利用するには、PyTorchとffmpegが必要なのでインストールしていなければどちらもインストールしましょう。
ffmpegはインストールの際にエラーが出ることがありました。condaでinstallした場合にはエラーが起こらなかったので、condaでインストールすると良いと思います。

```
# PyTorchのインストール
conda install pytorch torchvision -c pytorch
# Whisperのインストール
pip install git+https://github.com/openai/whisper.git 
# ffmpegのインストール
conda install ffmpeg -c conda-forge
```

### Whisperによる音声認識
Whisperでは、最初に使用するモデルをロードし、その後、modelに対して、transcribeやdetect_languageといった処理を行います。利用方法はめちゃくちゃ簡単です。
```
import torch
import whisper
model = "base" ("tiny", "small", "medium" or "large")
file = "./musicnation.wav" (Whisperに与える音声ファイル)
# モデルのロード
model = whisper.load_model(model)
# 音声認識
result = model.transcribe(file)
# 言語を指定する場合はlanguageオプションにjaなどを付け足します
# result = model.transcribe(file, language="ja")
print(result["text"])
```


### コード全体
同じディレクトリにある音声ファイルの音声認識を行いlog.mdに書き出します。
```
import time
import torch
import whisper
from datetime import datetime

Models = [
    'base',
    'medium',
    ]

Files = [
    "./musicnation.wav",
    "./good-love.wav",
    "./desperate.mp3", 
]

LogFile = "./log.md"

def main():
    writeLog("# Whisper Research\n")
    writeLog(f"## Research Start {datetime.now().strftime('%Y/%m/%d %H:%M:%S')}  \n")
    writeLog(f' ** cuda available: {torch.cuda.is_available()} **\n')
    for m in Models:
        writeLog(f' ### Model : ({m}) ---\n')
        s_loadmodel = time.time()
        model = whisper.load_model(m)
        e_loadmodel = time.time()
        writeLog(f' ** Load Model ({m}) : {e_loadmodel-s_loadmodel}**\n')
        for file in Files:
            transcribe(model, file)
        writeLog(' -------- \n')


def transcribe(model, file):
    s_transcribe = time.time()
    result = model.transcribe(file, verbose=True)
    e_transcribe = time.time()
    writeLog(f'- **Transcribe ({file}) : {e_transcribe-s_transcribe}**\n')
    writeLog(f' - ({file}) : {result["text"]}\n')


def writeLog(mes):
    with open(LogFile, mode="a") as f:
        f.write(mes)

if __name__ == '__main__':
    main()

```

## 結果
### 処理時間
今回はCPUとGPUで処理時間を計測しています。
- CPU
    - MacBookPro(M1, 2020)
    - Apple Sillicon
- GPU
    - Google Colaboratory
    - NVIDIA Tesla T4

|Name|Length|base(CPU)|base(GPU)|medium(CPU)|medium(GPU)|
|---|---|---|---|---|---|
|musicnation.wav|86s|27.0s|11.7s|198s|5.1s
|good-love.wav|96s|42.5s|3.1s|245s|6.2s
|desperate.mp3|159s|51.2s|9.9s|418s|15.6s

- CPUだと時間がかかってしまいますが、GPUだと処理時間がすごい速いです。
- musicnation.wavのbase(GPU)は時間がかかってしまっていますが、どうやら最初の処理には何故か時間がかかってしまうようでした。2個目以降のファイルは大抵もう少し早く処理を行えました。

### 精度
時間ごとに認識の結果を出力できます。精度を比較するため、同じ出力は手作業で消去しました。連続して同じ出力がされる場合もあるので、音声認識の結果を利用する場合は、連続した出力を除去する処理が必要になります。
各ファイルの精度は以下のようになります。

#### musicnation.wav
```
# baseモデル
[00:00.000 --> 00:14.320]  You cannot escape my sound It's always going round and round
[00:14.320 --> 00:19.320]  The sound you hear is quite shocking It's really rockin'
[00:19.320 --> 00:26.000]  My legs fit, my biggest soul The music still here
[00:26.000 --> 00:34.000]  Here to my goal This music runnin' through my veins
[00:34.000 --> 00:41.000]  Having met the... in my brain Oh yeah!
[00:43.000 --> 01:07.000]  Oh yeah!
[01:07.000 --> 01:17.000]  The show is rockin' all the night We'll take you with us on and fly

# Mediumモデル
[00:00.000 --> 00:14.200]  You cannot escape my sound, it's always going round and round
[00:14.200 --> 00:19.200]  The sound you hear is quite shocking, it's really rocking
[00:19.200 --> 00:29.000]  My biggest fear, my biggest sorrow, but music still, will hit tomorrow
[00:29.000 --> 00:38.000]  There's music running through my veins, heavy metal in my brain
[00:38.000 --> 01:07.000]  Oh yeah, oh yeah, oh yeah
[01:07.000 --> 01:16.000]  The show is rocking all the night, we'll take you with us on a flight
```
#### good-love.wav
```
# baseモデル
[00:00.000 --> 00:15.000]  Bays go, and release the treble Turn the music up and rise the level,
[00:15.000 --> 00:21.000]  Let the bow, let you give to me I thought about it constantly and it
[00:21.000 --> 00:34.000]  It's just for free, baby boy what you've done to me, the liar, higher, burn me on fire,
[00:34.000 --> 00:58.000]  Take me, yeah, oh baby boy, yeah, oh, this thing got me feeling nothing
[00:58.000 --> 01:13.000]  Come on and up with me, they'll shout Aays go, and release the treble Turn the music up and rise the level,
[01:13.000 --> 01:42.000]  Let the bow, yeah oh, yeah oh, yeah

# Mediumモデル
[00:00.000 --> 00:15.320]  Bass go and release the treble Turn the music up and rise the level
[00:15.320 --> 00:20.840]  Let the blow that you give to me I thought about it constantly
[00:20.840 --> 00:26.320]  Get a kiss just for free Baby boy what you've done to me
[00:26.320 --> 00:36.280]  You're like a liar, higher Put me on fire, fire
[00:36.280 --> 00:41.200]  Take me, yeah, oh baby
[00:41.200 --> 01:03.640]  This pink and me feeling all day Come on and open me, we'll short it
[01:03.640 --> 01:13.880]  Bass go and release the treble Turn the music up and rise the level
[01:13.880 --> 01:35.960]  Let the blow that you give to me I thought about it constantly
```


- desperate.mp3
```
# baseモデル
[00:13.320 --> 00:16.740] 🧡
[00:16.380 --> 00:19.720] ami
[00:20.020 --> 00:23.900] に
[00:23.900 --> 00:25.140] 申し伝え
[00:25.120 --> 00:28.840] n
[00:28.840 --> 00:34.120] 絵の目のインセンのことが全て枯れ果てていく
[00:34.120 --> 00:40.440] 星のワイトひどつの答え
[00:40.440 --> 00:49.460] あぶなって涙が消える言葉は何も見つけられず
[00:59.020 --> 01:04.920] あぶなって涙が見ることは何も見つけられ
[01:04.920 --> 01:10.440] 旅にもの下に笑顔がない
[01:12.520 --> 01:17.820] あぶなって涙が消える言葉は何も見つけられ
[01:17.820 --> 01:21.920] 旅にもの下に笑顔がない
[01:21.920 --> 01:27.940] それに何も見つくことが全て枯れ果てていく
[01:27.940 --> 01:33.920] 星のワイトひどつの答え
[01:33.920 --> 01:38.240] あぶなって涙が消える言葉は何も見つけられ
[01:38.240 --> 01:40.560] 旅にもの下に笑顔がない
[01:40.560 --> 01:46.420] あぶなって涙が消える言葉は何も見つけられ
[01:46.420 --> 01:49.360] 旅にもの下に笑顔がない
[01:49.360 --> 01:54.640] あぶなって涙が消える言葉は何も見つけられ
[01:54.640 --> 01:58.520] 旅にもの下に笑顔がない
[01:58.520 --> 02:04.240] あぶなって涙が消える言葉は何も見つけられ
[02:04.240 --> 02:07.520] 旅にもの下に笑顔がない
[02:07.520 --> 02:12.640] あぶなって涙が消える言葉は何も見つけられ
[02:12.640 --> 02:18.120] 旅にもの下に笑顔がない
[02:20.120 --> 02:25.600] あぶなって涙が消える言葉は何も見つけられ
[02:25.600 --> 02:31.080] 旅にもの下に笑顔がない

# Mediumモデル
[00:00.000 --> 00:14.380] 【音楽】
[00:14.380 --> 00:19.980] 伝えられない 線の言葉 全て変え果てていく
[00:19.980 --> 00:26.280] 欲しいのは 一つの答え
[00:26.280 --> 00:31.880] 溢れて涙かける言葉 何も見つけられず
[00:31.880 --> 00:42.880] 【音楽】
[00:42.880 --> 00:59.880] 溢れて涙かける言葉 何も見つけられず
[00:59.880 --> 01:05.680] 溢れて涙かける言葉 何も見つけられず
[01:05.680 --> 01:10.480] 色の褪さりは解けない
[01:10.480 --> 01:17.280] 溢れて涙かける言葉 何も見つけられず
[01:17.280 --> 01:21.280] 色の褪さりは解けない
[01:21.280 --> 01:27.280] 伝えられない 線の言葉 全て変え果てていく
[01:27.280 --> 01:32.280] 欲しいのは 一つの答え
[01:33.280 --> 01:37.280] 溢れて涙かける言葉 何も見つけられず
[01:37.280 --> 01:41.280] 色の褪さりは解けない
[01:41.280 --> 01:45.280] 伝えられない 線の言葉 全て変え果てていく
[01:45.280 --> 01:49.280] 欲しいのは 一つの答え
[01:49.280 --> 01:53.280] 溢れて涙かける言葉 何も見つけられず
[01:53.280 --> 01:57.280] 色の褪さりは解けない
[01:57.280 --> 02:01.280] 伝えられない 線の言葉 全て変え果てていく
[02:01.280 --> 02:07.280] 伝えられない 線の言葉 全て変え果てていく
[02:07.280 --> 02:11.280] 溢れて涙かける言葉 何も見つけられず
[02:11.280 --> 02:15.280] 色の褪さりは解けない
[02:15.280 --> 02:21.280] 溢れて涙かける言葉 何も見つけられず
[02:21.280 --> 02:25.280] 色の褪さりは解けない
[02:25.280 --> 02:31.280] 溢れて涙かける言葉 何も見つけられず
[02:31.280 --> 02:55.280] 色の褪さりは解けない
```

## 考察
元データの歌詞は面倒なので書きません笑
精度確かめられませんね。データは[ここ](https://drive.google.com/drive/folders/1FbXfeo8dsOGrrzBRkyG8b8aoh82mOMTD)に置いときます。興味あれば聞いてみてください。
- [GoogleDrive](https://drive.google.com/drive/folders/1FbXfeo8dsOGrrzBRkyG8b8aoh82mOMTD)

私は全ての音声データを聞いているので(あたりまえ)、ここからは私の考察です。
- 今回は言語を指定せずに分析にかけました。**baseでもmediumでも全てのデータが正しく言語を認識出来ました。**(ここはさすがにできてもらわないと、)
- **英語の場合、baseでもよく認識ができていました。** さすがです。一方日本語だとまだまだです。しかし、処理時間はCPUでも実際のデータの半分の時間なので早いですね。
- **mediumであれば、CPUだととても時間がかかってしまいます**が、英語でも日本語でも精度よく認識できています。(毎回同じ出力で間違ってんだろ！って方、ここから実際の音声を聞いてみてください。この音源同じ言葉の繰り返しでした。選ぶのミスったかも。すいません。)
- **GPUであれば、mediumでも短時間で音声認識できます。** ちなみにlargeだと環境にもよりますが、元データと同じくらいあるいはそれ以上時間がかかります。しかし、音が重なっていても良く分析できることが多かったです。
- **言語を指定すると、もう少し処理時間が短く**できますが、認識したいファイル群が多言語の場合は難しいですね。
- AWSのEC２上で行うときは、インスタンスタイプをCファミリーにすると良いと思います。GPUをつかえるに越したことがないですが、コスパで見たらC5.2xlargeとかを使うのがいいと思います。


## warnings.warn("FP16 is not supported on CPU; using FP32 instead")
CPU実行時のこのエラーは
```
result = model.transcribe(file, verbose=True, fp16=False)
```
とfp16オプションをFalseにすると解決できます。
このオプションを入れなくても正常に動作はしました。


## 終わりに
このようにWhisperを使うと歌詞の認識まで精度良くできることがわかりました。
CPUでの実行には時間がかかってしまいますが、精度はとてもいいので楽しめると思います。

whisper.cppやリアルタイム認識、出力の分析などもやっているのでまた時間があったら記事にしようと思います。


## 参考
- https://github.com/openai/whisper
- https://openai.com/blog/whisper/
- https://music-note.jp/vocal/