Downloads · 30 days
0
infodeliverailab/qwen3-omni-sortformer-diarization-pipeline
qwen3-omni-sortformer-diarization-pipeline is a machine learning model from infodeliverailab. Use it for the machine learning task on the model card, and read the license before you ship it in a product. It is set up for nemo. The card lists the license as apache-2.0.
日本語電話音声を対象に、文字起こしの精度は高いが話者分離が弱いLLM(Qwen3-Omni)と、 話者分離は音響的に正確だが文字起こしをしないモデル(NVIDIA Sortformer)を、 LLM(Gemma、OpenAI互換API経由)で統合し、最終的な話者ラベル付き文字起こしを作る パイプラインです。
Downloads · 30 days
0
Access
Public
Updated Jul 30, 2026
Repo size
—
Likes
0
Public
Click a slice to open those files.
.py33.8 KB · 65%
From the Hugging Face model README
日本語電話音声を対象に、**文字起こしの精度は高いが話者分離が弱いLLM(Qwen3-Omni)**と、 **話者分離は音響的に正確だが文字起こしをしないモデル(NVIDIA Sortformer)**を、 **LLM(Gemma、OpenAI互換API経由)**で統合し、最終的な話者ラベル付き文字起こしを作る パイプラインです。
このリポジトリはコードのみです。重みは含みません。 Qwen3-OmniとSortformerは それぞれのHugging Face公式リポジトリから自動でダウンロードされます。
qwen_transcribe.py Qwen3-Omniで区間ごとのJSON文字起こし(start/end/spk/text)
sortformer_diarize.py Sortformerでフレーム単位の生の話者確信度(sigmoid)を取得
acoustic_categorize.py Sortformerの確信度を5段階(超低/低/中/高/超高)にカテゴリ化し、
Qwenの各区間に対応付ける
gemma_fusion.py 上記を統合し、Gemma(OpenAI互換API)に最終判定させる
pipeline.py 上記4つをsubprocessで束ねるオーケストレータ (CLI)
server_v2.py ブラウザから音声をアップロードして実行できるUI (FastAPI)
index_v2.html server_v2.py が返すページ
qwen_transcribe.py はvLLM(transformers>=5.5.3が必要)、sortformer_diarize.py は
NeMo(nemo_toolkitがtransformers==4.57.6に固定インストールする)を使っており、
この2つは同じPython環境に共存できません(pip install nemo_toolkitが
transformersを4.57.6に強制ダウングレードし、vLLMを壊します)。そのため:
qwen_transcribe.py / acoustic_categorize.py / gemma_fusion.py / pipeline.py /
server_v2.py は system python (vLLM環境) で実行sortformer_diarize.py だけは 別のvirtualenv (NeMo環境) で実行pipeline.pyとserver_v2.pyは、正しいインタプリタでそれぞれをsubprocessとして
呼び出すことでこの制約を吸収しています。
# 1. vLLM環境 (system python)
pip install vllm==0.25.1 # transformers>=5.5.3 が入る
pip install fastapi uvicorn python-multipart librosa
# 2. NeMo環境 (別のvirtualenv、vLLM環境とは絶対に混ぜない)
python3 -m venv /root/nemo_venv
/root/nemo_venv/bin/pip install 'nemo_toolkit[asr]'
# 3. Qwen3-Omniモデル本体 (INT8推奨。bf16でも動くがVRAM 70GB以上必要)
hf download infodeliverailab/qwen-3-omni-vllm-int8 --local-dir /root/model
# (Sortformerは初回実行時にHugging Faceから自動ダウンロードされる)
GEMMA_URL / GEMMA_MODEL / GEMMA_API_KEY は環境変数で渡します
(OpenAI互換の/chat/completionsエンドポイントであれば任意のプロバイダで動きます。
開発時は bytecompute.ai の gemma-4-31B-it で検証しています):
export GEMMA_URL="https://your-openai-compatible-endpoint/v1"
export GEMMA_MODEL="your-model-name"
export GEMMA_API_KEY="your-api-key"
(gemma_fusion.pyだけがこれらを読みます。pipeline.py/server_v2.pyは
subprocessを起動する際にこの環境変数をそのまま継承させているだけです。)
python3 pipeline.py path/to/audio.wav \
--workdir /root/diar_work \
--model /root/model \
--api-key "$GEMMA_API_KEY"
--workdir以下に各段階の中間ファイルが残ります(qwen_segments.json /
sortformer_probs.npy / annotated_segments.json / final_diarized.json)。
最終結果は標準出力にも表示されます。
複数ファイルをまとめて処理する場合は、wavパスを複数並べるだけでバッチモードになります:
python3 pipeline.py a.wav b.wav c.wav \
--workdir /root/diar_work \
--model /root/model \
--api-key "$GEMMA_API_KEY"
1ファイルずつpipeline.pyを呼ぶと、Qwen3-Omni(vLLMエンジン)とSortformerを
ファイルごとに毎回ロードし直すため、torch.compileのキャッシュが効いていても
1ファイルあたり70〜90秒程度の起動オーバーヘッドが発生します(実際の生成自体は
短い音声なら数秒)。バッチモードではQwen3-OmniとSortformerをそれぞれ1回だけ
ロードし、複数ファイルを処理してから次の段階に進みます(実測: 6ファイルで
逐次実行が約9分半 → バッチモードで約110秒)。--workdirはこの場合、
ファイルごとのサブディレクトリ(ファイル名から拡張子を除いたもの)を作る
親ディレクトリとして扱われます。
各モジュールは単体でも実行できます(中間ファイルを使い回して1段階だけやり直す、 デバッグする、等の際に便利です):
python3 qwen_transcribe.py audio.wav --model /root/model --output qwen_segments.json
/root/nemo_venv/bin/python3 sortformer_diarize.py audio.wav --output sortformer_probs.npy
python3 acoustic_categorize.py qwen_segments.json sortformer_probs.npy --output annotated_segments.json
python3 gemma_fusion.py annotated_segments.json --output final_diarized.json --api-key "$GEMMA_API_KEY"
GEMMA_API_KEY=... uvicorn server_v2:app --host 127.0.0.1 --port 8090
--host 0.0.0.0にしないこと。 RunPod等のpublic proxyに認証なしで晒され、
顧客音声や有償APIキーが誰でも使える状態になります。ローカル(127.0.0.1)で
起動し、ssh -L 8090:127.0.0.1:8090 user@hostのようなSSHポートフォワード経由で
アクセスしてください。
ブラウザで開いて音声ファイルをアップロードすると、話者ラベル付きの結果が 音声再生と同期してハイライト表示されます。GPUを1台しか使わないため、 同時に1リクエストしか処理できません(2つ目のリクエストは429を返します)。
[
{"start": 0.0, "end": 4.02, "final_spk": "spk0", "text": "...", "reason": "..."},
...
]
final_spkはspk0, spk1, ... (声質クラスタベース、役職や性別に固定の意味は
ありません)。reasonはGemmaが判定根拠を一言で述べたものです。
Sortformerは離散ラベルではなく、5段階カテゴリ化した生確率を渡すこと。
model.diarize()が返すspeaker_0/speaker_1のような離散クラスタラベルを
そのままGemmaに渡すと、ほぼそのままエコーされるだけで実質何も統合されません。
model.forward()が返す生のsigmoid確信度を5段階(超低/低/中/高/超高)に
バケット化して渡すことで、初めてGemmaがQwenの推定との食い違いを検討できます。
「過検出の可能性がある」と明示的な逃げ道を与えないこと。 Sortformerの話者クラスタが誤検出(雑音・エコー等)の可能性があると伝えると、 Gemmaは本物の話者交代も含めて一律で疑い出し、精度が悪化します。 「文脈も考慮して」程度の一般的な指示にとどめる方が安定します。
話者の人数やクラスタの対応関係を事前に教えないこと。 「実は2人しかいない」「speaker_1とspeaker_2は同一人物」といった正解を プロンプトに書くと、その特定のケースでは直りますが、一般化した指示には なりません。会話全体の文脈と確信度データから推論させること。
Qwenは「区切りが変わるたびに機械的に話者を交互に切り替える」癖がある。 実際には同じ人物が話し続けている1つの発話を2つの区切りに分割した上で、 その境界でも律儀に話者を交互にしてしまうことがあります。「文脈的な一貫性を 確認せよ」という指示はこれを一部改善しますが、逆に本当に交互に話している 短い相槌(「はい」「うん」等)を同一話者に誤って統合してしまう副作用もあり、 完全な解決策ではありません(過少検知と過剰検知はトレードオフの関係にある)。
Qwenのtimestampは開始時刻のみで、実時間からズレることがある。 音声認識モデルは実際の時計を持っているわけではなく、これまで生成した テキスト量から時刻を「それらしく」推測しているだけです。特に会話後半で 実時間より早く進む傾向が観測されています。話者分離の精度を評価する際、 フレーム単位の時刻一致率で見ると本来正しい話者判定まで不正解に見えることが あるので、内容ベースでの評価も併用してください。
Sortformerの確信度をQwenの区切りに対して事後計算する設計には限界がある。 会話のテンポが速い(発話が2〜3秒間隔で交代する)と、Qwenの区切り自体が 不正確なため、その区切り窓で計算した音響確信度も汚染されます (前後の話者の声が窓の中に漏れ込み、両方の話者で確信度が「超高」に張り付く)。 Sortformer自身が検出した音響的な区間境界を基準にする設計の方が、 本来はより頑健なはずです(短い音声での検証では機能しましたが、 このリポジトリのメインパイプラインはQwen区切り基準のままです — 発展の余地)。
Qwenへの出力形式は、区切りごとのタイムスタンプタグより素直なJSON配列の方が
安定します。 [MM:SS]textのようなタグ形式で開始・終了の両方を出させようと
すると、終了時刻をテキスト生成前に「先読み」する必要が生じ、タイムラインが
実際より大きく圧縮される崩壊が起きました。{"start":..,"end":..,"spk":.., "text":..}ずつのJSONオブジェクトを求める形式にしたところ、この崩壊は
起きず、短い割り込み発言の検出率も上がりました(ただし出力に稀に同じ
キーが重複するなど、フォーマット遵守が完璧ではない点は残っています)。
音声全体を1回のforward passで渡しても、モデルの音声エンコーダ内部は
チャンク処理をしている可能性がある。 config.jsonの
thinker_config.audio_configにn_window/n_window_inferや
seconds_per_chunk=2といった値があり、これは実際にモデル内部の音声処理が
何らかの窓単位で行われることを示唆しますが、これが本当に文字起こしの
不整合(発話の途中で話者ラベルが不自然に変わる等)の原因になっているかは
未検証です。config内のフィールド名から推測しただけで、実際の
attentionメカニズムを確認したわけではないので、確定的な結論として
扱わないでください。
実際の電話音声5サンプル+参照文字起こし(別のLLMによる書き起こし、人手検証は していない)との突き合わせでは、フレーム単位の話者一致率は52%〜80%と サンプルによってばらつきがありました。内容ベースで見ると、Qwen単体より 明確に改善している区間がある一方、Qwenの機械的な交互切り替えの癖に 起因する系統的な誤りが残るケースもあります。「これで完成」という段階では なく、実験的な改善余地の多いシステムとして捉えてください。