📚

[音声認識]whisperモデルで文字起こし

2024/07/13に公開

🎧はじめに

本ページで解説させていただくのは、
音声認識の一分野であるの文字起こし(Audio Transcription)の処理手順です。

文字起こしは、以下のような場面で活用されています💪

1. 議事録作成📝

  • 会議の内容を文字起こしすることで、議事録作成を自動化することに活用されています。
  • Nishika株式会社では、「SecureMemo / SecureMemoCloud」を開発し、議事録作成AIを提供しております。
  • 2024/8/31まで、文字起こしコンペを開催しておりますので、よろしければご参加ください!

2. 医療分野🏥

  • 医師や看護師が、患者さんとのやり取りを記録することでカルテ作成を行い、医療品質の向上に活用されています。

3. 教育分野🏫

  • 授業の内容を記録し、「周囲の盛り上がり/説明内容」から授業改善に活用されています。

文字起こしの手順

データ処理

  • データの読み込み
    • 最初に、文字起こしに必要な「音声ファイルのパス/文字起こし結果」のデータを読み込みます。
      • 複数の音声を文字起こしする場合、DataFrameとしてまとめておくと便利です。
      • 今回はの魔王魂さんご提供の音声を利用いたしました🌕
    import polars as pl
    df = pl.read_csv("df_zenn.csv")
    df
    
    df
  • Whipserモデルのロード
    • 今回文字起こしに利用する、Whisperモデルをロードします。
from transformers import WhisperProcessor, WhisperForConditionalGeneration
import torch


* データのスライス
    * Whisper等、音声認識モデルは、長時間のデータを扱うことは得意ではありません。
    * 文字起こしの「精度/速度」向上を目的に、30[sec]ごとに音声を区切ります。
        * 例:1:40の音声であれば、[(0:00,0:30)(0:30,1:00),(1:00,1:30),(1:30:1:40)]のようにスライスします。
```python
import time
start = time.time()


output_folder = "slice/"
duration_ms = 30000 #30秒ごとに音声を区切る.  「推論時間の短縮/精度向上」が狙い。

list_id = []
list_slice_id = []
list_path = []
list_slice_path = []

LEN = df.shape[0]

for i in range(LEN):
    audio_path = df[i, "path"]
    print(f"元ファイル:{audio_path}")
    audio_new, slice_ids_new = slice_mp3(audio_path, output_folder, duration_ms)

    title = df[i, "title"]
    path = df[i, "path"]

    list_id.extend([id]*len(audio_new))  #スライスの数 と同じ長さのListをExtend
    list_path.extend([path]*len(audio_new)) #スライスの数 と同じ長さのListをExtend

    list_slice_path.extend(audio_new) #スライスした音声ファイルのパス
    list_slice_id.extend(slice_ids_new) #スライスした作品のID

    # print(f"slice_ids_new:{slice_ids_new}")

    if (i+1) % 5 ==0:
        print(f"{i+1}/{LEN} 完了🦉📗\n")
        elapsed = (time.time()-start)/60.0
        print("経過時間:{:.1f}[分]🕰️\n".format(elapsed))

df_slice = pl.DataFrame({
    "ID":list_id,
    "slice_id":list_slice_id,
    "audio_path":list_path,
    "slice_path":list_slice_path,
})

モデルとプロセッサをロード:

  • モデルとプロセッサの違い
    • モデル:音声認識タスクや、そのための学習に使用
    • プロセッサ:モデルが受け取れる形式に、データを処理(processing)するのに使用
model = WhisperForConditionalGeneration.from_pretrained("openai/whisper-small")
processor = WhisperProcessor.from_pretrained("openai/whisper-small")

文字起こしの実行

  • 途中、whisperモデルが扱えるデータ形式(Pytorchテンソル)に変換します。
import librosa
start = time.time()
n_sample = 10

list_path = [] #対象音声のファイルパス
list_transcription = [] #文字起こし結果
# list_label = [] #正解ラベル


for i in range(n_sample):
# for i in range(1):

    ## 読み込み
    path = list_audio[i] #audio path(スライスされた音声)

    ## 音声データ読み込み
    audio, sr = librosa.load(path, sr=16000)
    print(f"音声データ🎧:\n{audio}")


    ##音声データを、テンソルに変換:Whisper入力用
    input_features = processor(audio, sampling_rate=sr, return_tensors="pt").input_features
    print(f"テンソル化された音声データ📚:\n{input_features}")
    print("\n\n")


    ##推論(文字起こし)の実行
    # input_features = input_features.to(device)
    predicted_ids = model.generate(input_features)
    transcription = processor.batch_decode(predicted_ids, skip_special_tokens=True)

    print(f"文字起こし:{transcription[0]}")
    # print(f"正解の文章:{label}")
    list_path.append(path)
    list_transcription.append(transcription[0])
    # list_label.append(label)


    print(f"{i+1}/{n_sample}完了⏰")
    elapsed = time.time() - start
    print("経過時間{:.1f}[秒]:".format(elapsed))
    print(f"--------------------------------------------------------------------------------------------------------\n\n")

result = pl.DataFrame({
    "path":list_path,
    "transcription":list_transcription,
})

print("推論完了🦌")

精度(CER)計算

文字単位での精度計算をします。
今回はスライスごとに文字起こしを実行したため、
文字おこし結果を、結合する処理をします。
使用ライブラリはLevenshtein
です。※文字起こし結果と、正解ラベルの近さを計算するのに使用します。

#文字起こし結果
transcription_concat = \
    result.select(pl.col("transcription").str.concat("")).item()
transcription_concat
作曲 李宗盛キラキラキラめくお星様お月様さあ魔法が街の中に降りてくるならムラムラ心の中で踊るメロディを今なら聞かせてあげてもいいよ今夜はかわいい女の子に変身するの同じ空を見てるかな駆け抜けるTwinkle twinkleブラチの球の想い運ぶの時のキラビリンス抜け出せないの恋は予測ができないワンタランこうでなくっちゃね 時め決めると答えしてほしい人は あなただけなのに願うこの瞬間 続きますようにいつもあなたと 笑っていたいよときめきラビリンス抜け出すけないの星屋の恋を掴んでシューピングスターときめき革命 今夜一晩天神森の you
#正解ラベル
df[0,"target"]
きらきらきらめくお星様お月様さぁ魔法が街の中に降りてくるララルラ心の中で踊るメロディをAh今なら聴かせてあげてもいいよ はじけてよ スパイラルハートあなたに届けたいよ今夜はときめき☆ラビリンス抜け出せないの聖夜の恋を掴んでshooting starときめき革命今夜一番 可愛い女の子に変身するのフワフワ粉雪一面を染めてゆくねぇあなたも同じ空を見てるかな駆けぬけるトゥインクルウィンクプラチナ級の想い 運ぶのときめき☆ラビリンス抜け出せないの恋は予測ができない wonderlandこうでなくっちゃねときめきメルト溶かして欲しい人はあなただけなの目が合うこの瞬間続きますようにいつもあなたと笑っていたいよときめき☆ラビリンス抜け出せないの聖夜の恋を掴んでshooting starときめき革命今夜一番可愛い女の子に変身するの
  • cer計算
import Levenshtein

def calculate_cer(reference, hypothesis):
    # Levenshtein距離を計算
    distance = Levenshtein.distance(reference, hypothesis)
    
    # 参照文字列の長さを取得
    ref_length = len(reference)
    
    # CERを計算
    cer = distance / ref_length
    
    return cer


cer = calculate_cer(df[0,"target"], transcription_concat)
print(f"Character Error Rate (CER): {cer:.4f}")
Character Error Rate (CER): 0.5429

CERは0.0~1.0 (0に近いほど性能が良い)ため、あまり良くない推論精度でした。。

以上です。

Discussion