開発者ガイド

アーキテクチャの理解から自分のモデルの貢献まで——ステップバイステップの完全ガイド。

1. アーキテクチャ概要

FunASR は 3 つのコアコンセプトを中心に構築されています:コンポーネント発見のためのレジストリ、統一エントリポイントとしての AutoModel、モデルの宣言的定義としての config.yaml

ユーザーコード FunASR フレームワーク モデルハブ ───────── ──────────────── ───────── AutoModel(model="name") │ ├─→ download_model() ──────→ ModelScope / HuggingFace │ ↓ ↓ │ config.yaml を読み込み model.pt をダウンロード │ ↓ ├─→ tables.model_classes["Name"] ← @tables.register デコレータ │ ↓ ├─→ model_class(**config) ← __init__: encoder/decoder を構築 │ ↓ ├─→ load_pretrained_model() ← model.pt から重みを読み込み │ ↓ └─→ model.eval() ← 推論可能状態 generate(input="audio.wav") │ ├─ VAD なし → inference() ← 単一発話 │ ↓ │ model.inference(data_in, tokenizer, frontend, **kwargs) │ ↓ │ 返り値 [{"key", "text", "timestamp", ...}] │ └─ VAD あり → inference_with_vad() ← 長時間音声 ↓ 1. VAD:音声をセグメント分割 → [[開始ms, 終了ms], ...] 2. 長さでソート(バッチング効率化) 3. ASR:各セグメントを認識 4. タイムスタンプをマージ(VAD オフセットを加算) 5. 句読点復元(オプション) 6. 話者分離(オプション) ↓ 返り値 [{"key", "text", "timestamp", "sentence_info"}]

レジストリシステム

FunASR のすべてのコンポーネントは名前で登録されます。レジストリは設定文字列を Python クラスにマッピングするルックアップテーブルです:

レジストリ用途
model_classesASR、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 は辞書のリストでなければなりません。必須/オプションフィールド:

フィールド必須説明
keystrはいサンプル識別子
textstrはい(ASR)認識テキスト
timestamplist話者分離時[[開始ms, 終了ms], ...] 文字単位タイムスタンプ
valuelistVAD のみ[[開始ms, 終了ms], ...] 音声区間
spk_embeddingTensor話者モデルのみ形状 [N, 192]
タイムスタンプのフォーマットは重要です!モデルがタイムスタンプを辞書形式(Fun-ASR-Nano の {"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",
)

動作の仕組み:

  1. FunASR がモデルリポジトリをダウンロード(重み + 設定 + model.py)
  2. remote_code="./model.py" が動的にインポートされる
  3. そのファイル内の @tables.register デコレータがモデルクラスをレジストリに登録
  4. 通常の build_model() フローが登録されたクラスで進行

リポジトリ構成:

your-model-repo/
├── model.py              # @tables.register 付きモデルクラス
├── config.yaml           # モデルアーキテクチャ設定
├── configuration.json    # パス解決設定
├── model.pt              # 学習済み重み
└── example/test.wav      # デモ音声

参考実装:Fun-ASR-NanoSenseVoice

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_sizeinference_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. コントリビューション

コードスタイル

PR チェックリスト

ライセンス

コード:MIT ライセンス。モデルの重み:FunASR モデルライセンス(帰属表示付きで商用利用可)。