ReazonSpeech v2, whisper-large v3, nue-asrを比較してみた
今年2024年の2月14日に、日本語音声の文字起こしエンジンReazonSpeechのv2がリリースされました。NVIDIAのNemoを採用し、学習データセットも強化され、Fast Conformerという手法により高速化されたそうです。強そう。
同じく今年の1月に、transformersが4.73になり、OpenAIによる文字起こしエンジンwhisperが、transfomersでBatched state-of-the-art long-form transcriptionに対応したそうです。LLM勢が慣れたtransformersのpipelineを使えるは嬉しい。
昨年2023年の12月には、rinna社からGPT-NeoXを利用した文字起こしエンジンのnue-asrが登場しています。nueは鵺(妖怪の ぬえ)から名付けられたそうです。
いずれもApache 2.0またはMITライセンスで提供されており、商用利用含めて無料で利用出来ます。
この2ヵ月ちょっとで一気に進展があったので、比較してみることにしました。
比較材料
普段使いを想定し、私がいつもオンラインミーティングに使っている普通の会議用マイクで、しゃべりのプロでも何でもない中年男性(私)がボソボソと吹き込んだ音声をテスト材料とします。文章も、この世に存在したことがない脈絡の無い文章のほうがテストしがいがあるだろうと思い、土星で発達した猫の文明というお題でChatGPTに生成してもらった文章を読み上げました。
音声の長さは2分35秒です。
先にざっとしたまとめ
jiwerというpythonのライブラリを使い、あくまでも簡易的にワードエラーレート(WER)を出してみました。比較対象として、クラウドサービスであるLINE CLOVA Noteによる文字起こしのエラーレートも出しました。
ReazonSpeech v2 WER: 0.05472636815920398
whisper large v3 WER: 0.06633499170812604
nue-asr WER: 0.19402985074626866
line clova note WER: 0.05638474295190713
2分35秒の音声をローカルPC(RTX4090環境)でテキスト化するのにかかった時間は以下です。モデルのロード時間は含まず処理時間のみです。
ReazonSpeech v2: 10.606995105743408 秒
whisper large v3: 6.07532000541687 秒
nue-asr: 10.26275110244751 秒
ReazonSpeech v2、かなりいい!
以下に、各エンジンをPtyhonで動かしたときのサンプルコードを載せます。
ReazonSpeech v2
以下のクイックスタートガイドの通りに進めます。
注意点としては、ffmpegとCythonをインストールしておく必要があることでしょうか。
# Pythonのvenv環境作成
python3 -m venv venv
source venv/bin/activate
# ffmpegとCythonをインストール
sudo apt install ffmpeg
pip install Cython
# インストール
git clone https://github.com/reazon-research/ReazonSpeech
pip install ReazonSpeech/pkg/nemo-asr
動かし方は、HowToガイドにある以下のPythonコードで動きます。speech-001.wavのところを文字起こししたい音声ファイルにしてください。
from reazonspeech.nemo.asr import load_model, transcribe, audio_from_path
# 実行時にHugging Faceからモデルを取得します (2.3GB)
model = load_model(device='cuda')
# ローカルの音声ファイルを読み込む
audio = audio_from_path('speech-001.wav')
# 音声認識を適用する
ret = transcribe(model, audio)
print(ret.text)
Whisper large v3をtransformersで
whisperをtransformersで動かすのは、サンプルコードの通りにやりました。
import torch
from transformers import AutoModelForSpeechSeq2Seq, AutoProcessor, pipeline
from datasets import load_dataset
device = "cuda:0" if torch.cuda.is_available() else "cpu"
torch_dtype = torch.float16 if torch.cuda.is_available() else torch.float32
model_id = "openai/whisper-large-v3"
model = AutoModelForSpeechSeq2Seq.from_pretrained(
model_id, torch_dtype=torch_dtype, low_cpu_mem_usage=True, use_safetensors=True,
)
model.to(device)
processor = AutoProcessor.from_pretrained(model_id)
pipe = pipeline(
"automatic-speech-recognition",
model=model,
tokenizer=processor.tokenizer,
feature_extractor=processor.feature_extractor,
max_new_tokens=128,
chunk_length_s=30,
batch_size=16,
return_timestamps=True,
torch_dtype=torch_dtype,
device=device,
)
result = pipe("土星猫文明.wav", generate_kwargs={"language": "japanese"})
print(result["text"])
ちなみに、flash attention 2を使ったり、タイムスタンプを付けたりと、オプションも豊富なようです。
nue-asr
nue-asrは、2分35秒であっても長すぎて入力できず、30秒ごとに区切ってもなお「The input audio is 30.0 sec, but such long audio inputs may degrade recognition accuracy. It is recommended to split the audio into shorter segments.」と怒られたので、10秒ごとに区切って渡すことにしました。pydubというpythonで音声ファイルを扱えるライブラリを使っています。
どうしても区切り目で認識精度が低下するので、nue-asrの見かけ上のワードエラーレートが高くなってしまった原因だと感じています。発話の切れ目でファイルを切るなどの工夫できれば、一気にエラーレートは下がりそうです。
pip install pydub
pip install git+https://github.com/rinnakk/nue-asr.git
で必要なライブラリをインストールしておきます。
import nue_asr
from pydub import AudioSegment
import os
model = nue_asr.load_model("rinna/nue-asr")
tokenizer = nue_asr.load_tokenizer("rinna/nue-asr")
# 音声ファイルを読み込む
audio = AudioSegment.from_wav("土星猫文明.wav")
# 10秒ごとに分割
chunks = [audio[i:i+10000] for i in range(0, len(audio), 10000)]
# 分割した音声ファイルを保存
for i, chunk in enumerate(chunks):
chunk.export(f"chunk{i}.wav", format="wav")
# 分割した音声ファイルを順次処理
results = []
for i in range(len(chunks)):
result = nue_asr.transcribe(model, tokenizer, f"chunk{i}.wav")
results.append(result.text)
# 得られた文字を結合
combined_text = ' '.join(results)
# 結果をテキストファイルに保存
with open("combined_result.txt", "w", encoding="utf-8") as f:
f.write(combined_text)
# 分割した音声ファイルを削除
for i in range(len(chunks)):
os.remove(f"chunk{i}.wav")
print(combined_text)
比較の方法(超簡易的)
文字起こしの比較は、ワードエラーレートというものを使うことが多いようです。jiwerというワードエラーレートを計算してくれるライブラリがあったので、こちらを使うことにしました。
jiwerは外国で開発されたものなので、単語区切りはスペースがあることを前提としています。そこで、janomeで形態素解析を行い、単語間にスペースを入れています。
また、句読点や改行は削除する前処理を入れています。
import unicodedata
import re
from jiwer import wer
from janome.tokenizer import Tokenizer
def preprocess_text(text):
# 句読点を除去し、全角文字を半角に統一する
text = unicodedata.normalize("NFKC", text)
# 句読点、空白、改行を除去
text = re.sub(r'[\s。、,.]', '', text)
return text
# 日本語テキストの前処理と単語分割
def preprocess_and_tokenize_japanese(text):
tokenizer = Tokenizer()
tokens = tokenizer.tokenize(text)
words = [token.surface for token in tokens]
return ' '.join(words)
# 元の台本テキストファイルを読み込む
with open("original_text.txt", "r", encoding="utf-8") as f:
original_text = f.read()
original_text = preprocess_text(original_text)
original_text = preprocess_and_tokenize_japanese(original_text)
# nueの文字起こしテキストファイルを読み込む
with open("nue_result.txt", "r", encoding="utf-8") as f:
nue_reslut = f.read()
nue_reslut = preprocess_text(nue_reslut)
nue_reslut = preprocess_and_tokenize_japanese(nue_reslut)
# whisperの文字起こしテキストファイルを読み込む
with open("whisper_result.txt", "r", encoding="utf-8") as f:
whisper_result = f.read()
whisper_result = preprocess_text(whisper_result)
whisper_result = preprocess_and_tokenize_japanese(whisper_result)
# reazonspeechの文字起こしテキストファイルを読み込む
with open("reazon_result.txt", "r", encoding="utf-8") as f:
reazon_result = f.read()
reazon_result = preprocess_text(reazon_result)
reazon_result = preprocess_and_tokenize_japanese(reazon_result)
# LINE Clova Noteの文字起こしテキストファイルを読み込む
with open("lineclovanote.txt", "r", encoding="utf-8") as f:
lineclovanote = f.read()
lineclovanote = preprocess_text(lineclovanote)
lineclovanote = preprocess_and_tokenize_japanese(lineclovanote)
print(f"original: {original_text[:300]}")
print(f"whisper: {whisper_result[:300]}")
print(f"nue: {nue_reslut[:300]}")
print(f"reazon: {reazon_result[:300]}")
print(f"lineclovanote: {lineclovanote[:300]}")
whisper_wer = wer(original_text, whisper_result)
nue_wer = wer(original_text, nue_reslut)
reazon_wer = wer(original_text, reazon_result)
lineclovanote_wer = wer(original_text, lineclovanote)
print(f"whisper WER: {whisper_wer}")
print(f"nue WER: {nue_wer}")
print(f"reazon WER: {reazon_wer}")
print(f"lineclovanote WER: {lineclovanote_wer}")
これにより出したものが、この記事の最初のほうに入れたワードエラーレート(WER)です。私はこの辺は完全に素人なので、やり方が間違っていたら大変申し訳ありません。あくまでも簡易的な比較というコトで…。
長時間だとどうなのか?
試しに手元にある2時間13分の私的な音声ファイルを上記スクリプトのまま渡してみたところ、whiper large v3は普通に完走しました。そもそもpipelineのところで chunk_length_s=30, と30秒ごとに切断するようにしているので、長時間のファイルでも変わらないのでしょう。
ReazonSpeech v2の場合、長いファイルをそのまま読み込んだようなので、24GBのVRAMから溢れ、共有メモリも利用する状況になりました。なので、とてもとても時間がかかりました…。
Nue-asrは2分35秒のファイルも細切れにしないといけなかったので、テストしていません。
実際に文字起こしされたテキストデータ
以下に、文字起こしの結果を貼ります。それぞれの傾向がわかるかもしれません。
元の台本
ReazonSpeech v2
whisper large v3
Nue-asr
LINE Clova Note
ReazonSpeech v2は精度が高いものの、句読点がなくて読みにくいかもしれません。Nue-asrは句読点を入れてくれたりするので読みやすいものの、もともと「だ・である調」だったもの一部「です・ます調」になっていたりと、GPT-NeoXによる「生成」が入っているような気がします。whisper-large-v3はその中間という感じでしょうか。
上記の結果だけを見るとwhisperが使いやすそうですが、whisperはノイズの多い環境やフランクなフリートークでは謎のハルシネーションが多発する体感もあります。無音のはずのところで「チャンネル登録よろしくお願いします」などと出てきたこともありました。YouTubeで学習しているのか…??
なので、ReazonSpeechにはかなり期待しています。さまざまなユースケースで使ってみて、その結果も見ていきたいです。
この記事が気に入ったらサポートをしてみませんか?