開発者ガイド
アーキテクチャの理解から自分のモデルの貢献まで——ステップバイステップの完全ガイド。
1. アーキテクチャ概要
FunASR は 3 つのコアコンセプトを中心に構築されています:コンポーネント発見のためのレジストリ、統一エントリポイントとしての AutoModel、モデルの宣言的定義としての config.yaml。
レジストリシステム
FunASR のすべてのコンポーネントは名前で登録されます。レジストリは設定文字列を Python クラスにマッピングするルックアップテーブルです:
| レジストリ | 用途 | 例 |
|---|---|---|
model_classes | ASR、VAD、句読点、話者モデル | "Paraformer", "FsmnVADStreaming" |
encoder_classes | エンコーダアーキテクチャ | "SANMEncoder", "ConformerEncoder" |
decoder_classes | デコーダアーキテクチャ | "ParaformerSANMDecoder" |
frontend_classes | 音声特徴量抽出 | "WavFrontend", "WhisperFrontend" |
tokenizer_classes | テキストトークン化 | "SentencepiecesTokenizer" |
dataset_classes | トレーニングデータ読み込み | "AudioDataset" |
from funasr.register import tables
# 新しいモデルを登録
@tables.register("model_classes", "MyModel")
class MyModel(nn.Module):
...
# 登録済みモデルの一覧を表示
tables.print("model")
2. 開発環境セットアップ
クローンして開発モードでインストール
git clone https://github.com/modelscope/FunASR.git cd FunASR pip install -e . # 編集可能インストール pip install -e ".[train]" # トレーニング依存関係も含む
インストールの確認
python -c "from funasr import AutoModel; print('OK')"
python -c "from funasr.register import tables; tables.print('model')"
既存テストの実行
# クイックスモークテスト python tests_models/test_fsmn_vad.py python tests_models/test_paraformer.py # フルテストスイート cd tests_models && python run_all_tests.py
3. 推論の仕組み(詳細解説)
新しいモデルを追加する前に、推論のデータフローを理解することが不可欠です。model.generate(input="audio.wav") を呼び出した際の全体の流れ:
ステップ 1:入力の準備
prepare_data_iterator() が様々な入力タイプ(ファイルパス、URL、numpy 配列、bytes、リスト)を統一的な (key_list, data_list) フォーマットに正規化します。
ステップ 2:モデル推論
各モデルの inference() メソッドは以下を受け取ります:
def inference(self, data_in, data_lengths=None, key=None,
tokenizer=None, frontend=None, **kwargs):
# data_in: 音声サンプルのリスト(numpy 配列)
# tokenizer: トークン ID をテキストにデコード
# frontend: fbank 特徴量を抽出
# **kwargs: config.yaml の全パラメータ + ユーザーのランタイムパラメータ
# 返り値は必ず:(結果リスト, メタデータ辞書)
return [{"key": "id", "text": "hello", "timestamp": [...]}], {"batch_data_time": 5.5}
ステップ 3:出力フォーマット
results_list は辞書のリストでなければなりません。必須/オプションフィールド:
| フィールド | 型 | 必須 | 説明 |
|---|---|---|---|
key | str | はい | サンプル識別子 |
text | str | はい(ASR) | 認識テキスト |
timestamp | list | 話者分離時 | [[開始ms, 終了ms], ...] 文字単位タイムスタンプ |
value | list | VAD のみ | [[開始ms, 終了ms], ...] 音声区間 |
spk_embedding | Tensor | 話者モデルのみ | 形状 [N, 192] |
{"start_time": 0.5, "end_time": 0.8} のような形式)で出力する場合、FunASR の inference_with_vad が自動変換します。ただし、標準的に期待されるフォーマットは [[開始ms, 終了ms], ...](ミリ秒単位の 2 要素リスト)です。
4. 新しいモデルの追加
モデルディレクトリの作成
funasr/models/my_model/ ├── __init__.py # 空ファイル ├── model.py # メインモデルクラス ├── encoder.py # (オプション)カスタムエンコーダ └── decoder.py # (オプション)カスタムデコーダ
モデルクラスの実装
import torch.nn as nn
from funasr.register import tables
@tables.register("model_classes", "MyModel")
class MyModel(nn.Module):
def __init__(self, **kwargs):
super().__init__() # ← 必ず super().__init__() を呼ぶこと
# kwargs からネットワーク構造を構築(kwargs は config.yaml から来る)
# kwargs に含まれるもの:input_size, vocab_size, tokenizer, frontend 等
def forward(self, speech, speech_lengths, text, text_lengths, **kwargs):
"""トレーニングの順伝播。(loss, stats_dict, weight) を返す。"""
...
def inference(self, data_in, data_lengths=None, key=None,
tokenizer=None, frontend=None, **kwargs):
"""推論。(results_list, meta_data) を返す。"""
...
config.yaml の作成
# このファイルで使用するコンポーネントを定義
model: MyModel # @tables.register のキーと一致
model_conf:
hidden_size: 512
frontend: WavFrontend # 既存のフロントエンドを再利用
frontend_conf:
fs: 16000
n_mels: 80
frame_length: 25
frame_shift: 10
cmvn_file: null
tokenizer: SentencepiecesTokenizer
tokenizer_conf:
bpemodel: null
configuration.json の作成(Hub アップロード用)
{
"framework": "pytorch",
"task": "auto-speech-recognition",
"model": {"type": "funasr"},
"file_path_metas": {
"init_param": "model.pt",
"config": "config.yaml",
"tokenizer_conf": {"bpemodel": "my_tokenizer.model"},
"frontend_conf": {"cmvn_file": "am.mvn"}
}
}
このファイルは AutoModel に相対パスの解決方法を伝えます。モデルがダウンロードされると、file_path_metas 内の各パスにモデルディレクトリが前置されます。
ローカルテスト
from funasr import AutoModel # ローカルディレクトリから読み込み model = AutoModel(model="./my_model_dir") res = model.generate(input="test.wav") print(res)
5. 新しいフロントエンド / トークナイザー / データセットの追加
すべてのコンポーネントに同じレジストリパターンが適用されます。新しいフロントエンドの追加例:
from funasr.register import tables
@tables.register("frontend_classes", "MyFrontend")
class MyFrontend(nn.Module):
def __init__(self, fs=16000, **kwargs):
super().__init__()
self.fs = fs
def output_size(self):
return 80 # 特徴量の次元数
def forward(self, input, input_lengths):
# input: 生波形 (batch, samples)
# 返り値: 特徴量 (batch, frames, dim), 長さ
...
config.yaml で参照:
frontend: MyFrontend
frontend_conf:
fs: 16000
トークナイザー(tokenizer_classes)、データセット(dataset_classes)、エンコーダ(encoder_classes)なども同じパターンです。
6. スタンドアロンリポジトリモード
モデルを FunASR のソースツリー内に置く必要はありません。trust_remote_code=True を使用すると、FunASR は外部ファイルからモデルクラスを動的に読み込みます:
# ユーザーコード — 独立リポジトリからモデルを読み込む
model = AutoModel(
model="your-org/your-model", # HuggingFace/ModelScope リポジトリ
trust_remote_code=True,
remote_code="./model.py", # モデルクラス定義ファイルのパス
hub="hf",
)
動作の仕組み:
- FunASR がモデルリポジトリをダウンロード(重み + 設定 + model.py)
remote_code="./model.py"が動的にインポートされる- そのファイル内の
@tables.registerデコレータがモデルクラスをレジストリに登録 - 通常の
build_model()フローが登録されたクラスで進行
リポジトリ構成:
your-model-repo/ ├── model.py # @tables.register 付きモデルクラス ├── config.yaml # モデルアーキテクチャ設定 ├── configuration.json # パス解決設定 ├── model.pt # 学習済み重み └── example/test.wav # デモ音声
参考実装:Fun-ASR-Nano、SenseVoice
7. モデルのテスト
テストスクリプトの作成
# tests_models/test_my_model.py
import sys, time
from funasr import AutoModel
def main():
model = AutoModel(model="path/to/model", device="cpu", disable_update=True)
res = model.generate(input="test.wav")
assert res and len(res) > 0, "結果が空です"
assert "text" in res[0], "text フィールドがありません"
print("PASSED")
return 0
if __name__ == "__main__":
sys.exit(main())
VAD + 話者分離パイプラインのテスト
# モデルが話者分離をサポートする場合:
model = AutoModel(
model="path/to/model",
vad_model="fsmn-vad",
spk_model="cam++",
)
res = model.generate(input="meeting.wav", cache={})
assert "sentence_info" in res[0]
assert "spk" in res[0]["sentence_info"][0]
ストリーミング推論のテスト(該当する場合)
cache = {}
for i in range(total_chunks):
chunk = audio[i*stride:(i+1)*stride]
res = model.generate(input=chunk, cache=cache,
is_final=(i == total_chunks-1), ...)
# 検証:同じ音声で複数セッションにわたり同じ結果が得られること
8. よくある落とし穴
❌ super().__init__() の呼び忘れ
# 間違い — "object has no attribute '_state_dict_pre_hooks'" エラーの原因
class MyEncoder(nn.Module):
def __init__(self):
pass
# 正しい
class MyEncoder(nn.Module):
def __init__(self):
super().__init__()
❌ モデル内で kwargs["batch_size"] をチェック
kwargs の batch_size は inference_with_vad がセグメントバッチング用に設定した値(ミリ秒単位の大きな数値)です。実際のデータバッチサイズの判定には使わないでください。代わりに len(data_in) を使用します。
❌ 空入力・短い入力の未処理
VAD が空のセグメントを生成する可能性があります。inference() は data_in = [] を適切に処理できなければなりません。
❌ タイムスタンプフォーマットの不一致
モデルが辞書形式のタイムスタンプ({"start_time": 0.5, "end_time": 0.8})を返す場合、パイプラインが自動変換します。ただし [start_time, end_time, text](3 要素)を出力する場合は、テキストを除去してください——ダウンストリームは [開始ms, 終了ms](2 要素、ミリ秒単位)を期待しています。
❌ 他のモデルディレクトリからのインポート
# 間違い — 密結合を生む from funasr.models.paraformer.model import Paraformer # 正しい — 必要なコードを自分のディレクトリにコピーする # または config.yaml でレジストリ名を通じて参照
❌ 推論中に self.kwargs を変更する
AutoModel から渡された kwargs を変更しないでください。フレームワークは呼び出し間で状態をリセットしますが、永続的な変更はセッション間で漏洩する可能性があります。
9. コントリビューション
コードスタイル
- 既存のパターンに従う——
paraformer/model.pyを参考にする - すべてのパブリックメソッドに docstring を追加(Args と Returns を含む)
- コメントは「なぜ」のみ——「何を」の説明は明確な命名で代替
PR チェックリスト
- 新モデル:自己完結したディレクトリ、他モデルへのインポートなし
tests_models/にテストスクリプトを含めるexamples/industrial_data_pretraining/にデモを含める- 既存のテストがすべてパスすること
- ユーザー向け機能の場合、README の What's New にエントリを追加
ライセンス
コード:MIT ライセンス。モデルの重み:FunASR モデルライセンス(帰属表示付きで商用利用可)。