この記事の対象読者
- Pythonの基本文法(関数、クラス、pip)を理解している方
- 音声認識(Speech-to-Text)に興味がある方
- Whisperは使ったことあるけど、日本語特化モデルを試したい方
- ローカル環境で音声認識を動かしたい方
この記事で得られること
- ReazonSpeechの概念と、なぜ日本語認識で最強なのかの理解
- 3種類のモデル(NeMo / K2 / ESPnet)の違いと選び方
- コピペで即動く音声認識コード
- 35,000時間のコーパスへのアクセス方法
この記事で扱わないこと
- Pythonの環境構築方法(pyenvやvenvの使い方)
- 深層学習の基礎理論(Transformer、Attention機構など)
- 音声認識モデルの学習・ファインチューニング
1. ReazonSpeechとの出会い
「日本語の音声認識、なんでこんなに認識精度悪いんだ...」
Whisperを使って議事録の文字起こしをしていたとき、そう感じたことはありませんか?
私は何度もありました。英語だと驚くほど正確なのに、日本語になると突然「空耳アワー」が始まる。固有名詞は壊滅的。句読点もめちゃくちゃ。
そんな中で見つけたのが ReazonSpeech です。
初めて動かしたとき、正直びっくりしました。「え、句読点ちゃんと入るじゃん」「固有名詞もかなり正確...」と。しかもWhisper Large-v2並みの精度で、Whisper Tinyより速い。
ReazonSpeechは、世界最大のオープン日本語音声コーパスを構築するプロジェクトです。株式会社Reazon Holdingsのヒューマンインタラクション研究所が開発・公開しています。
人間で例えるなら、Whisperが「53カ国語を話せる通訳」だとすれば、ReazonSpeechは「日本語だけに人生を捧げた国語の先生」。専門特化の強みが光ります。
ここまでで、ReazonSpeechがどんなプロジェクトか、なんとなくイメージできたでしょうか。次は、この技術を支える基盤について見ていきましょう。
2. 前提知識の確認
本題に入る前に、この記事で使う用語を整理しておきます。
2.1 ASR(Automatic Speech Recognition)とは
ASRは「自動音声認識」の略称です。音声をテキストに変換する技術のことで、「Speech-to-Text」とも呼ばれます。SiriやGoogle音声入力の裏側で動いている技術ですね。
2.2 CER(Character Error Rate)とは
音声認識の精度を測る指標です。認識結果と正解テキストを比較して、どれだけ文字単位で間違えたかを表します。値が低いほど高精度。日本語のように単語区切りが曖昧な言語では、WER(Word Error Rate)よりCERがよく使われます。
2.3 RTF(Real Time Factor)とは
処理速度を表す指標です。「1秒の音声を処理するのに何秒かかるか」を示します。RTFが0.5なら、1秒の音声を0.5秒で処理できる(リアルタイムより速い)ということ。RTFが1.0未満ならリアルタイム処理が可能です。
2.4 コーパス(Corpus)とは
機械学習モデルを訓練するための大規模データセットのことです。音声認識の場合、「音声ファイル + その書き起こしテキスト」のペアが大量に必要になります。ReazonSpeechは35,000時間分のコーパスを公開しています。
これらの用語が押さえられたら、次に進みましょう。
3. ReazonSpeechが生まれた背景
3.1 日本語音声認識の課題
日本語の音声認識は、英語と比べて難しいとされています。その理由は以下の通りです。
| 課題 | 説明 |
|---|---|
| 文字種の多さ | ひらがな、カタカナ、漢字、アルファベットが混在 |
| 単語境界の曖昧さ | 英語のようにスペースで区切られない |
| 同音異義語の多さ | 「橋」「端」「箸」など、文脈判断が必要 |
| 学習データの不足 | 英語に比べて大規模コーパスが少なかった |
特に最後の「学習データ不足」は深刻でした。高品質な日本語音声コーパスは、CSJ(日本語話し言葉コーパス)の約600時間程度が主流。英語圏の数万時間規模と比べると圧倒的に少なかったのです。
3.2 ReazonSpeechの登場
2023年1月、Reazon Holdingsが19,000時間の日本語音声コーパスを公開しました。これは当時、オープンな日本語音声コーパスとして世界最大規模でした。
さらに2024年2月のv2.0で35,000時間に拡大。英語圏の大規模データセット(LibriSpeechの約1,000時間、Common Voiceの数千時間)と比較しても遜色のないスケールに達しています。
背景がわかったところで、抽象的な概念から順に、具体的な仕組みを見ていきましょう。
4. ReazonSpeechの基本概念
4.1 3つのモデルバリエーション
ReazonSpeechは、用途に応じて3種類のモデルを提供しています。
| モデル | パラメータ数 | 特徴 | 依存ライブラリ |
|---|---|---|---|
| NeMo版 | 619M | 最高精度、長時間音声対応 | NVIDIA NeMo |
| K2版 | 159M | 高速・軽量、ONNX形式 | sherpa-onnx |
| ESPnet版 | 120M | 研究用途向け | ESPnet |
初心者には K2版 がおすすめです。軽量で高速、しかもONNX形式なのでGPUがなくてもCPUで動きます。
4.2 アーキテクチャの秘密
v2.0以降のReazonSpeechが高速なのは、Fast Conformer と Zipformer という最新アーキテクチャを採用しているからです。
従来のConformerは、Transformerの注意機構(Attention)が入力長の2乗に比例する計算量を要しました。これが長時間音声で遅くなる原因でした。
Fast Conformerは、Longformerスタイルの線形注意機構を採用。計算量を線形に抑えることで、長時間音声もシングルパスで高速処理できます。
4.3 Whisperとの比較
公式ベンチマークによると、ReazonSpeech v2.0の性能は以下の通りです。
| 比較軸 | ReazonSpeech v2.0 (NeMo) | Whisper Large-v2 |
|---|---|---|
| 精度 (JSUT-BASIC5000) | 同等 | 同等 |
| 速度 | Whisper Tiny並み | 遅い |
| パラメータ数 | 619M | 1.55B |
| 日本語特化 | Yes | No (多言語) |
同じ精度なら2.5倍速い。これがReazonSpeechの強みです。
基本概念が理解できたところで、これらの抽象的な概念を具体的なコードで実装していきましょう。
5. 実際に使ってみよう
5.1 環境構築
まず、Python仮想環境を作成します。
# 仮想環境の作成
python3 -m venv reazonspeech-env
source reazonspeech-env/bin/activate # Windowsは: reazonspeech-env\Scripts\activate
# pipのアップグレード
pip install --upgrade pip wheel
次に、使用するモデルに応じてパッケージをインストールします。
# K2版(推奨・軽量で高速)
git clone https://github.com/reazon-research/ReazonSpeech
pip install ReazonSpeech/pkg/k2-asr
# NeMo版(最高精度・要GPU)
pip install ReazonSpeech/pkg/nemo-asr
# ESPnet版(研究用途)
pip install ReazonSpeech/pkg/espnet-asr
5.2 設定ファイルの準備
以下の3種類の設定ファイルを用意しています。用途に応じて選択してください。
開発環境用(config.yaml)
# config.yaml - 開発環境用(このままコピーして使える)
# ReazonSpeech音声認識設定
model:
# K2版を使用(軽量・高速)
type: "k2"
device: "cpu" # GPU使用時は "cuda"
audio:
# 入力音声の設定
sample_rate: 16000 # ReazonSpeechは16kHz固定
max_duration: 30 # K2版の上限は約30秒
output:
# 出力設定
format: "text" # "text" or "json"
timestamps: false # タイムスタンプ出力
save_dir: "./results"
logging:
level: "DEBUG"
file: "./logs/dev.log"
本番環境用(config.production.yaml)
# config.production.yaml - 本番環境用(このままコピーして使える)
# ReazonSpeech音声認識設定 - Production
model:
# NeMo版を使用(最高精度)
type: "nemo"
device: "cuda"
audio:
sample_rate: 16000
max_duration: 3600 # NeMo版は長時間対応
output:
format: "json"
timestamps: true
save_dir: "/var/data/transcripts"
logging:
level: "INFO"
file: "/var/log/reazonspeech/app.log"
performance:
batch_size: 8
num_workers: 4
テスト環境用(config.test.yaml)
# config.test.yaml - テスト/CI用(このままコピーして使える)
# ReazonSpeech音声認識設定 - Test
model:
type: "k2"
device: "cpu" # CI環境はCPU前提
audio:
sample_rate: 16000
max_duration: 10 # テスト用に短い音声
output:
format: "json"
timestamps: false
save_dir: "./test_results"
logging:
level: "WARNING"
file: null # テストではログファイル不要
test:
sample_audio: "./fixtures/test_audio.wav"
expected_text: "これはテスト用の音声です"
5.3 基本的な使い方(K2版)
"""
ReazonSpeech K2版 音声認識サンプル
使い方: python transcribe_k2.py audio.wav
必要なパッケージ: pip install ReazonSpeech/pkg/k2-asr
"""
import sys
from reazonspeech.k2.asr import load_model, transcribe, audio_from_path
def main():
"""メイン処理"""
# 引数チェック
if len(sys.argv) < 2:
print("使い方: python transcribe_k2.py <音声ファイル>")
print("例: python transcribe_k2.py speech.wav")
sys.exit(1)
audio_path = sys.argv[1]
print(f"音声ファイル: {audio_path}")
# モデルのロード(初回はダウンロードに時間がかかる)
print("モデルをロード中...")
model = load_model()
print("モデルロード完了")
# 音声ファイルの読み込み
print("音声を読み込み中...")
audio = audio_from_path(audio_path)
# 音声認識の実行
print("認識中...")
result = transcribe(model, audio)
# 結果の表示
print("\n" + "=" * 50)
print("認識結果:")
print("=" * 50)
print(result.text)
print("=" * 50)
return result.text
if __name__ == "__main__":
main()
5.4 タイムスタンプ付き認識
"""
ReazonSpeech K2版 タイムスタンプ付き音声認識
字幕ファイル生成などに便利
"""
from reazonspeech.k2.asr import load_model, transcribe, audio_from_path
def transcribe_with_timestamps(audio_path: str) -> list:
"""
タイムスタンプ付きで音声認識を実行
Args:
audio_path: 音声ファイルのパス
Returns:
list: [(開始秒, トークン), ...] のリスト
"""
model = load_model()
audio = audio_from_path(audio_path)
result = transcribe(model, audio)
timestamps = []
for subword in result.subwords:
timestamps.append({
"start": subword.seconds,
"token": subword.token
})
return timestamps
def format_as_srt(timestamps: list) -> str:
"""SRT字幕形式に変換"""
srt_lines = []
# 文字をチャンク化(10文字ごと)
chunk_size = 10
current_chunk = []
chunk_start = 0.0
for i, item in enumerate(timestamps):
if not current_chunk:
chunk_start = item["start"]
current_chunk.append(item["token"])
if len(current_chunk) >= chunk_size or i == len(timestamps) - 1:
chunk_end = item["start"] + 0.5
text = "".join(current_chunk)
# SRT形式で出力
idx = len(srt_lines) // 4 + 1
start_str = format_time(chunk_start)
end_str = format_time(chunk_end)
srt_lines.append(str(idx))
srt_lines.append(f"{start_str} --> {end_str}")
srt_lines.append(text)
srt_lines.append("")
current_chunk = []
return "\n".join(srt_lines)
def format_time(seconds: float) -> str:
"""秒数をSRT形式の時刻に変換"""
hours = int(seconds // 3600)
minutes = int((seconds % 3600) // 60)
secs = int(seconds % 60)
millis = int((seconds % 1) * 1000)
return f"{hours:02d}:{minutes:02d}:{secs:02d},{millis:03d}"
if __name__ == "__main__":
import sys
if len(sys.argv) < 2:
print("使い方: python transcribe_timestamps.py <音声ファイル>")
sys.exit(1)
timestamps = transcribe_with_timestamps(sys.argv[1])
print("=== タイムスタンプ一覧 ===")
for item in timestamps[:20]: # 最初の20件
print(f"{item['start']:.2f}秒: {item['token']}")
print("\n=== SRT形式 ===")
srt = format_as_srt(timestamps)
print(srt)
5.5 実行結果
上記のコードを実行すると、以下のような出力が得られます。
$ python transcribe_k2.py sample.wav
音声ファイル: sample.wav
モデルをロード中...
モデルロード完了
音声を読み込み中...
認識中...
==================================================
認識結果:
==================================================
気象庁は、雪や路面の凍結による交通への影響、暴風雪や高波に警戒するとともに、雪崩や屋根からの落雪にも十分注意するよう呼びかけています。
==================================================
タイムスタンプ付き版の出力:
$ python transcribe_timestamps.py sample.wav
=== タイムスタンプ一覧 ===
0.00秒: 気
1.04秒: 象
1.20秒: 庁
1.44秒: は
1.96秒: 雪
2.16秒: や
...
=== SRT形式 ===
1
00:00:00,000 --> 00:01:44,500
気象庁は、雪や路
2
00:01:96,000 --> 00:03:44,500
面の凍結による交通
...
5.6 よくあるエラーと対処法
| エラー | 原因 | 対処法 |
|---|---|---|
ModuleNotFoundError: No module named 'sherpa_onnx' |
K2版の依存ライブラリ未インストール |
pip install sherpa-onnx を実行 |
RuntimeError: Audio too long |
K2版の30秒制限超過 | 音声を分割するか、NeMo版を使用 |
FileNotFoundError: [Errno 2] No such file |
音声ファイルパスが間違い | パスを確認、絶対パスで指定 |
ValueError: Sample rate must be 16000 |
サンプルレートが16kHzでない | ffmpegで変換: ffmpeg -i input.mp3 -ar 16000 output.wav
|
CUDA out of memory |
GPUメモリ不足(NeMo版) | バッチサイズを小さくするか、CPU使用に切り替え |
基本的な使い方をマスターしたので、次は応用例を見ていきましょう。
6. ユースケース別ガイド
6.1 ユースケース1: 議事録の自動作成
- 想定読者: 会議の文字起こしを自動化したいビジネスパーソン
- 推奨構成: NeMo版(長時間音声対応)+ 話者分離
- サンプルコード:
"""
議事録自動作成スクリプト
長時間の会議音声を文字起こしし、テキストファイルに保存
"""
import os
from datetime import datetime
from reazonspeech.nemo.asr import load_model, transcribe, audio_from_path
def create_meeting_minutes(audio_path: str, output_dir: str = "./minutes") -> str:
"""
会議音声から議事録を作成
Args:
audio_path: 会議音声ファイルのパス
output_dir: 出力ディレクトリ
Returns:
str: 保存したファイルのパス
"""
# 出力ディレクトリの作成
os.makedirs(output_dir, exist_ok=True)
# モデルのロード
print("モデルをロード中...")
model = load_model()
# 音声の読み込みと認識
print(f"音声ファイルを処理中: {audio_path}")
audio = audio_from_path(audio_path)
result = transcribe(model, audio)
# 議事録ファイルの作成
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
output_path = os.path.join(output_dir, f"minutes_{timestamp}.txt")
with open(output_path, "w", encoding="utf-8") as f:
f.write(f"議事録\n")
f.write(f"作成日時: {datetime.now().strftime('%Y年%m月%d日 %H:%M')}\n")
f.write(f"元ファイル: {os.path.basename(audio_path)}\n")
f.write("=" * 50 + "\n\n")
f.write(result.text)
print(f"議事録を保存しました: {output_path}")
return output_path
if __name__ == "__main__":
import sys
if len(sys.argv) < 2:
print("使い方: python meeting_minutes.py <会議音声ファイル>")
sys.exit(1)
create_meeting_minutes(sys.argv[1])
6.2 ユースケース2: リアルタイム字幕生成
- 想定読者: 配信者、動画制作者
- 推奨構成: K2版(高速処理)+ VAD(音声区間検出)
- サンプルコード:
"""
リアルタイム風字幕生成
短い音声チャンクを連続処理して字幕を生成
"""
import wave
import numpy as np
from reazonspeech.k2.asr import load_model, transcribe
def generate_subtitles(audio_path: str, chunk_seconds: float = 5.0) -> list:
"""
音声を分割して字幕を生成
Args:
audio_path: 音声ファイルのパス
chunk_seconds: チャンクの長さ(秒)
Returns:
list: 字幕データのリスト
"""
model = load_model()
subtitles = []
# WAVファイルを読み込み
with wave.open(audio_path, 'rb') as wf:
sample_rate = wf.getframerate()
n_channels = wf.getnchannels()
total_frames = wf.getnframes()
chunk_frames = int(chunk_seconds * sample_rate)
current_time = 0.0
while True:
frames = wf.readframes(chunk_frames)
if not frames:
break
# numpy配列に変換
audio_data = np.frombuffer(frames, dtype=np.int16)
if n_channels == 2:
audio_data = audio_data[::2] # ステレオ→モノラル
# float32に正規化
audio_float = audio_data.astype(np.float32) / 32768.0
# 認識実行
result = transcribe(model, audio_float)
if result.text.strip():
subtitles.append({
"start": current_time,
"end": current_time + chunk_seconds,
"text": result.text
})
current_time += chunk_seconds
return subtitles
if __name__ == "__main__":
import sys
if len(sys.argv) < 2:
print("使い方: python realtime_subtitle.py <音声ファイル>")
sys.exit(1)
subtitles = generate_subtitles(sys.argv[1])
for sub in subtitles:
print(f"[{sub['start']:.1f}s - {sub['end']:.1f}s] {sub['text']}")
6.3 ユースケース3: 音声検索システム
- 想定読者: 大量の音声アーカイブを検索可能にしたい開発者
- 推奨構成: K2版 + 全文検索エンジン(Elasticsearch等)
- サンプルコード:
"""
音声ファイルのインデックス作成
認識結果をJSONで保存し、検索可能に
"""
import os
import json
import hashlib
from pathlib import Path
from reazonspeech.k2.asr import load_model, transcribe, audio_from_path
def index_audio_files(audio_dir: str, index_path: str = "audio_index.json") -> dict:
"""
ディレクトリ内の音声ファイルをインデックス化
Args:
audio_dir: 音声ファイルが格納されたディレクトリ
index_path: インデックスの保存先
Returns:
dict: インデックスデータ
"""
model = load_model()
index = {"files": [], "version": "1.0"}
audio_extensions = {".wav", ".mp3", ".flac", ".m4a"}
audio_files = [
f for f in Path(audio_dir).rglob("*")
if f.suffix.lower() in audio_extensions
]
print(f"処理対象: {len(audio_files)} ファイル")
for i, audio_file in enumerate(audio_files, 1):
print(f"[{i}/{len(audio_files)}] {audio_file.name}")
try:
audio = audio_from_path(str(audio_file))
result = transcribe(model, audio)
# ファイルのハッシュ値を計算(重複検出用)
file_hash = hashlib.md5(
audio_file.read_bytes()
).hexdigest()
index["files"].append({
"path": str(audio_file),
"hash": file_hash,
"transcript": result.text,
"duration": len(audio) / 16000 # 秒数
})
except Exception as e:
print(f" エラー: {e}")
continue
# インデックスを保存
with open(index_path, "w", encoding="utf-8") as f:
json.dump(index, f, ensure_ascii=False, indent=2)
print(f"\nインデックスを保存: {index_path}")
return index
def search_audio(query: str, index_path: str = "audio_index.json") -> list:
"""
インデックスからキーワード検索
Args:
query: 検索クエリ
index_path: インデックスファイルのパス
Returns:
list: マッチしたファイルのリスト
"""
with open(index_path, "r", encoding="utf-8") as f:
index = json.load(f)
results = []
for file_info in index["files"]:
if query in file_info["transcript"]:
results.append({
"path": file_info["path"],
"transcript": file_info["transcript"],
"match_context": extract_context(
file_info["transcript"], query
)
})
return results
def extract_context(text: str, query: str, context_len: int = 30) -> str:
"""検索語の周辺テキストを抽出"""
idx = text.find(query)
if idx == -1:
return ""
start = max(0, idx - context_len)
end = min(len(text), idx + len(query) + context_len)
return f"...{text[start:end]}..."
if __name__ == "__main__":
import sys
if len(sys.argv) < 2:
print("使い方:")
print(" インデックス作成: python audio_search.py index <音声ディレクトリ>")
print(" 検索: python audio_search.py search <検索語>")
sys.exit(1)
command = sys.argv[1]
if command == "index" and len(sys.argv) >= 3:
index_audio_files(sys.argv[2])
elif command == "search" and len(sys.argv) >= 3:
query = sys.argv[2]
results = search_audio(query)
print(f"検索結果: {len(results)} 件")
for r in results:
print(f"\n📁 {r['path']}")
print(f" {r['match_context']}")
else:
print("引数が不正です")
ユースケースが把握できたところで、この記事を読んだ後の学習パスを確認しましょう。
7. 学習ロードマップ
この記事を読んだ後、次のステップとして以下をおすすめします。
初級者向け(まずはここから)
-
公式クイックスタートを試す
- URL: https://research.reazon.jp/projects/ReazonSpeech/quickstart.html
- K2版で短い音声を文字起こし
-
Google Colabで動かす
-
サンプル音声でベンチマーク
- 自分の録音した音声で精度を確認
- Whisperと比較してみる
中級者向け(実践に進む)
-
HowToガイドを読む
- URL: https://research.reazon.jp/projects/ReazonSpeech/howto.html
- コーパスへのアクセス方法、NeMo版の使い方
-
VAD(音声区間検出)と組み合わせる
- Sileroなどの VADで無音区間をスキップ
- 長時間音声の効率的な処理
-
APIサーバー化する
- FastAPIやFlaskでREST API化
- Dockerでコンテナ化
上級者向け(さらに深く)
-
コーパスでファインチューニング
- URL: https://huggingface.co/datasets/reazon-research/reazonspeech
- 35,000時間のコーパスにアクセス
-
カスタムモデルの学習
- k2-fsa/icefallの学習レシピを参照
- 特定ドメイン向けにチューニング
-
ストリーミングASRの実装
- リアルタイム音声認識の実現
- WebSocket + 逐次認識
8. まとめ
この記事では、ReazonSpeechについて以下を解説しました。
- ReazonSpeechとは: 世界最大のオープン日本語音声コーパスを構築するプロジェクト
- なぜ強いのか: 35,000時間のコーパス + 最新アーキテクチャ(Fast Conformer / Zipformer)
- 3つのモデル: NeMo版(最高精度)、K2版(高速・軽量)、ESPnet版(研究向け)
- 実践コード: 基本的な文字起こしからタイムスタンプ、議事録作成まで
私の所感
正直なところ、ReazonSpeechを使い始めてから「日本語の音声認識ってこんなに進化してたのか」と驚きました。
Whisperが多言語対応の汎用性で勝負しているのに対し、ReazonSpeechは日本語一点集中の職人技。どちらが良いかは用途次第ですが、日本語だけを扱うなら、ReazonSpeechを第一選択肢として検討する価値は十分あります。
特にK2版の「軽量なのに高精度」は、ローカル環境で手軽に試せるという点で大きなアドバンテージ。GPU必須だったWhisper Largeと同等の精度が、CPUで動くというのは革命的です。
Apache 2.0ライセンスで商用利用も可能。ぜひ皆さんも試してみてください。