はじめに
これまで、Ollamaを使ったローカルLLMを扱ってきた
ローカルでSpeech to Textが実現できれば、完全にローカルで動く対話システムに大きく近づくと考えた
そこで、本記事ではwhisperによる文字起こしを行うpythonプログラムを紹介する
https://github.com/openai/whisper
このコードはClaude3.5 sonnetに書いてもらったものに手直しを加えたものである
結論、今回はMacbookのCPUでモデルサイズ”small”のwhisperを動かすことができた
それ以上のサイズのモデルについては未検証
また、MacのGPUであるMPSを利用することができないか試したが失敗した
実行環境
- Macbook Air
- チップ:Apple M3
- メモリ:16GB
- macOS:Sonoma 14.6.1
- Python 3.12.7
Whisper導入の手順
pyenv+venvを用いた仮想環境で行なっている(必ず仮想環境でなければいけないというわけではなさそうなのでお好みで)
ffmpegのインストールが必要らしいが、もともと入っていたので気にしなかった
もし必要だと言われたらbrew installでインストール可能
$ brew install ffmpeg
ライブラリのインストール
# Whisperのインストール
pip3 install openai-whisper
# 音声入力用ライブラリ
pip3 install pyaudio numpy torch
ソースコード
import whisper
import pyaudio
import numpy as np
import threading
import queue
import time
import torch
class WhisperTranscriber:
def __init__(self, model_size='base', language='japanese'):
"""
Whisper音声認識クラス
"""
self.model = whisper.load_model(model_size)
for param in self.model.parameters():
print(f"モデルのデバイス: {param.device}")
break # 最初のパラメータだけ確認すれば十分
# 音声入力設定
self.pyaudio = pyaudio.PyAudio()
self.sample_rate = 16000
self.chunk_duration = 3
self.chunks_per_inference = int(self.sample_rate * self.chunk_duration)
# スレッディング用キュー
self.audio_queue = queue.Queue()
self.stop_event = threading.Event()
self.language = language
def transcribe_audio(self):
"""
リアルタイム音声文字起こし
"""
if self.model is None:
print("モデルの初期化に失敗しました")
return
audio_data = []
while not self.stop_event.is_set():
try:
chunk = self.audio_queue.get(timeout=1)
audio_data.extend(chunk)
if len(audio_data) >= self.chunks_per_inference:
# テンソル変換
audio_np = np.array(audio_data[:self.chunks_per_inference])
# 推論を実行
result = self.model.transcribe(
audio_np,
language=self.language,
fp16=False
)
print(f"文字起こし結果: {result['text']}")
audio_data = audio_data[self.chunks_per_inference:]
except queue.Empty:
continue
except Exception as e:
print(f"推論中にエラー: {e}")
def audio_callback(self, in_data, frame_count, time_info, status):
"""
音声データ処理用コールバック
"""
audio_chunk = np.frombuffer(in_data, dtype=np.float32)
self.audio_queue.put(audio_chunk)
return (None, pyaudio.paContinue)
def start_recording(self):
"""
音声録音開始
"""
self.stream = self.pyaudio.open(
format=pyaudio.paFloat32,
channels=1,
rate=self.sample_rate,
input=True,
frames_per_buffer=self.chunks_per_inference,
stream_callback=self.audio_callback
)
print("音声録音を開始")
def run(self):
"""
音声認識プロセス実行
"""
self.start_recording()
transcribe_thread = threading.Thread(target=self.transcribe_audio)
transcribe_thread.start()
try:
transcribe_thread.join()
except KeyboardInterrupt:
print("\n音声認識を終了")
self.stop_event.set()
self.stream.stop_stream()
self.stream.close()
self.pyaudio.terminate()
def main():
transcriber = WhisperTranscriber(
model_size='base',
language='japanese'
)
transcriber.run()
if __name__ == "__main__":
main()
初回実行時はモデルのダウンロードが行われる
実行結果例
色々と文章が表示されるかもしれないが、とりあえず動く
モデルのデバイス: cpu
文字起こし結果: こんにちは
文字起こし結果:
3秒ごとに認識結果を返してくれる
ノイズを拾って”ご視聴ありがとうございました”という結果を返すことがある
暫定的な措置ではあるが、マイクの入力閾値を上げてやればマシになる
モデルについて
公式ドキュメントに書いてある→https://github.com/openai/whisper
表によると、tiny~turboまで種類があるそうだ
今回の環境ではbaseとsmallが動くことを確認できた
推論速度の違いはあまり感じなかった(数値で測ったわけではなく、あくまでも体感)
音声データがもっと長ければ差は大きくなるだろう
精度はもちろんsmallのほうがいい気がする
ただし、smallでもはっきりと発音しないとすぐに間違うので、実用にはかなり微妙(個人の感想です)
MPSを使おうとした
MacのGPU(MPS)を使用することができるか試してみた
というか、はじめにClaudeが書いたコードではMPSを使うように指定した結果、エラーが出て動かなかった
mpsが使用可能かどうかは以下のプログラムで確認可能
import torch
print(torch.mps.is_available())
Trueと表示されたらmpsが使用できるという意味
whisperでmpsを使用するにはwhisper.load_model()メソッドの引数でdevice=”mps”と指定する
実行結果(の一部)
NotImplementedError: Could not run 'aten::_sparse_coo_tensor_with_dims_and_tensors' with arguments from the 'SparseMPS' backend.
少ししか調べていないが、whisperの問題というよりもpytorchの問題ぽい
macOSやライブラリのバージョン等を調べたり、同様の問題が報告されていないかを調べたりする必要があるのだろうが、今回はここまでで断念
おわりに
今後やってみたいこと
- small以上のモデルでもこのノートパソコンで動くかどうか
- 今回のソースコードの中身について解読
- MPSを使用するための方法の調査
- 報告されているissueを読む
- whisperから派生したモデルのほうを試す
コメントを残す