Whisperを独自データでファインチューニング - コーパス作成編(Google Colab)

# はじめに
最近は音声モデルを使ってGPTと組み合わせるなどして、自然に会話できるようなボットを使ったような製品、例えば、Cotomoのようなものも出てきて、音声で会話するというシーンが今後増えるようなことなど予想されます。

https://cotomo.ai/

今回は一番肝の、音声からの文字起こし部分をチューニングしてみたいと思います。（STTですね。）

音声モデルはまだ発展途上であり、例えば幼児の声をうまく聞き取れなかったりする問題はあるかなと思います。
なぜこのようなことが起きるかと言うと、それはそのモデルに、幼児の音声が入っていないからですね。

# 今回作るもの
今回は、Whisperをファインチューニングする前のデータセット（音声コーパス）を作りたいと思います。
音声コーパスとは、簡単に言うと大量の音声ファイルにとその音声ファイルに紐づく正解ラベルが入ったCSVなどが固まっているものですね。音声モデルをチューニングするために必要なデータセットになります。


# 今回作る音声コーパスのファイル構造
今回は下記のような形で、音声コーパスを作成します。
train.csvには、clipsの中の音声ファイルの正解ラベルが入っています。
```mermaid
graph TD
    A[ja_voice_finetuning] --> B[train.csv]
    A --> C[clips]
    C --> D[file1.mp3]
    C --> E[file2.mp3]
    C --> F[file3.mp3]
```

# データセットの作成
まず、このデータセットの作成には、GoogleColabを用います。データセットの作成くらいは無料版でも問題ないですが、ファインチューニング時、38GBほどVRAMを使用しますので、個人的にはPROまたはPRO+を契約されておくことをおすすめします。

## 音声ファイルの準備
まずは、音声ファイルを準備します。
今回はオリジナルの音声ファイルが準備できませんでしたので、Mozillaが提供している音声ファイルを借ります。オリジナルデータがある方は、以下は無視してください。

まずは、下記から音声コーパスのZIPをダウンロードしてください。
Common Voice Corpus 17.0をダウンロードします。

https://commonvoice.mozilla.org/ja/datasets

ダウンロードしたフォルダを解凍し下記のフォルダに移動します。
```
解凍フォルダ\cv-corpus-17.0-2024-03-15\ja\clips
```

そしたら、下記のようにmp3の音声ファイルが多数あることを確認できると思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/418662/24641912-5b14-a42e-d0e3-8e18d7e2caf4.png)

この中の数十個だけ使います。

GoogleDriveに`ja_voice_finetuning`というフォルダを作り、その中に`clips`フォルダを作成して、その中に音声ファイルを入れてください。　※（独自の音声ファイルをお持ちの方は、そのままclipsフォルダの中に入れてください。）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/418662/be568029-b2c6-b1ad-0bc0-6837dcfc98ed.png)


## GoogleColabでの実装
下記で、まずはGoogleDriveに接続してください。※学習データなどを永続化させたいため。
```python
from google.colab import drive
drive.mount('/content/drive')
```

音声に発話していない空白時間を除去するために、VAD(Voice Activity Detection)音声区間検出を使います。
今回はそのVADの中でも性能良いと評判のsilero-vadを使います。

https://github.com/snakers4/silero-vad

下記で必要なものをインポートします。
```python
!pip install -q torchaudio

SAMPLING_RATE = 16000

import torch
torch.set_num_threads(1)

from IPython.display import Audio
from pprint import pprint
```

下記でVADのモデルを取得します。
```python
USE_ONNX = False # change this to True if you want to test onnx model
if USE_ONNX:
    !pip install -q onnxruntime

model, utils = torch.hub.load(repo_or_dir='snakers4/silero-vad',
                              model='silero_vad',
                              force_reload=True,
                              onnx=USE_ONNX)

(get_speech_timestamps,
 save_audio,
 read_audio,
 VADIterator,
 collect_chunks) = utils
```

ここまでで、一旦Silero-vadの準備ができました。
ここから、音声ファイルの下準備をします。音声ファイルで発話していない部分を除去し、つなぎ合わせて発話しているだけの音声ファイルを作成します。

directoryは音声ファイルの準備で行った下処理を行います。
out_directoryは下処理が終わった音声ファイルが格納されています。

```python
import os
directory = '/content/drive/MyDrive/ja_voice_finetuning/clips'
out_directory = '/content/drive/MyDrive/ja_voice_finetuning/silero-clips'

# 指定されたディレクトリ内の全てのファイルをループ処理
for filename in os.listdir(directory):
    if filename.endswith(".mp3"):  # ファイルが.mp3で終わる場合
        file_path = os.path.join(directory, filename)  # ファイルのフルパスを取得
        print(f'Processing file: {file_path}')

        # read_audio とは、ファイルを読み込んで適切な形式（例: numpy array）に変換する関数です
        audio = read_audio(file_path, sampling_rate=SAMPLING_RATE)

        # get_speech_timestamps は、音声のタイムスタンプを検出する関数です
        speech_timestamps = get_speech_timestamps(audio, model, sampling_rate=SAMPLING_RATE)

        if speech_timestamps:  # タイムスタンプリストが空でない場合
            # 処理された音声を保存する関数を呼び出す
            output_path = os.path.join(out_directory, filename)
            save_audio(output_path, collect_chunks(speech_timestamps, audio), sampling_rate=SAMPLING_RATE)
        else:
            print(f"No speech detected in file: {file_path}")
```

## 正解ラベルの作成
音声ファイルを一つ一つ聞いて、正解ラベルを作っていくのはとても骨の折れる作業可と思いますので、今回は、一回Whisperに推論させて、その結果を修正する形にします。（そうでもしないと、結構辛いと思う。）

koraはなぜ使うかと言うと、このあと作るCSVにGoogleDrive上の音声ファイルへのリンクを作るためになります。
 ```python
!pip install pydub openai-whisper
!pip install kora
```

まずは、GoogleDriveへのリンクを取得する関数を定義します。
```python
from kora.xattr import get_id
def get_gdrive_link(file_path):
  fid = get_id(file_path)
  print(f"https://drive.google.com/file/d/{format(fid)}/view")
  return f"https://drive.google.com/file/d/{format(fid)}/view"
```

音声ファイルから文字起こしした内容を、CSVに書き出します。
```python
import whisper
import torch
import os
import csv  # CSVファイル出力のために必要

# 現在利用可能なデバイスを確認
device = "cuda" if torch.cuda.is_available() else "cpu"
print(f"Using device: {device}")

# Whisperモデルをロード
model = whisper.load_model("large-v3", device=device)

# 出力先のCSVファイルパスを定義
output_csv_file = '/content/drive/MyDrive/ja_voice_finetuning/train.csv'


# CSVファイルを開き、結果を書き込む
with open(output_csv_file, mode='w', newline='', encoding='utf-8') as file:
    writer = csv.writer(file)
    # CSVのヘッダーを書き込む
    writer.writerow(['url', 'path','sampling_rate', 'correct', 'whisper'])

    # 指定されたディレクトリ内の全てのファイルをループ処理
    for filename in os.listdir(out_directory):
        if filename.endswith(".mp3"):  # ファイルが.mp3で終わる場合
            file_path = os.path.join(out_directory, filename)  # ファイルのフルパスを取得
            print(f'Processing file: {file_path}')
            result = model.transcribe(file_path, language="ja")
            # 結果をCSVに書き込む（ここではurlとcontentは空とする）
            # https://drive.google.com/file/d/1ISt6Pg2eJFuHvV1Fkm2NZBvzAJ4grewq/view?usp=drive_link
            writer.writerow([get_gdrive_link(file_path), file_path, SAMPLING_RATE ,'', result["text"]])
            print(result["text"])

print(f"Processing complete. Results saved to {output_csv_file}")

```

GoogleDriveの`ja_voice_finetuning`に、train.csvができていることが確認できると思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/418662/6c5df65d-7ceb-4fc8-f3f3-87b017ad3568.png)


スプレッドシートでこれを開くと、下記のようになっていますので、whisperカラムを見ながら、correctに正しい正解ラベルを記載していってください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/418662/1f52b2e8-772c-42cf-4baa-8af678ece0ce.png)


お疲れ様です。これでデータセットの準備は完了です！
次回は、このデータセットを下にWhisperをファインチューニングします。

https://qiita.com/umaxiaotian/items/5a01537ded7783c7d0ec