技術

ローカル話者ダイアライゼーション: 100%プライベートなミーティングノート作成ツールをどう構築したか

OpenWhisprは、音声がデバイスの外に出ないオープンソースのミーティングアシスタントです。すべての音声フィンガープリント、話者ラベル、埋め込みは、あなた自身のマシン上で処理・保存されます。

OpenWhispr

OpenWhispr

エンジニアリング

2026年4月15日
目次

ローカル話者ダイアライゼーションとは、会議録音の中で誰がいつ話したかをラベル付けする処理を、音声、埋め込み、文字起こしを外部サーバーへ送信せず、すべてユーザーのデバイス上で実行することです。 会議の文字起こしには「誰が何を言ったのか」という問題があります。Zoomはテキストの壁を返します。多くのAIノート作成ツール — Otter、Fireflies、Read、Granola — は、会議音声を自社サーバーにアップロードし、そこで話者モデルを走らせ、ラベル付きセグメントを返すことで解決します。あなたの声も、同僚の声も、他社のデータベース内のベクトルになります。私たちはそれを望まず、ユーザーも望んでいません。OpenWhisprは逆の前提で作られたオープンソースのミーティングノート作成ツールです。音声はデバイスの外に出ず、話者フィンガープリントはディスク上のローカルSQLiteファイルに保存され、あなたが明示的に選ばない限り会議に関する情報はアップロードされません。この記事では、その約束のうちダイアライゼーション側を技術的に分解します。それが何であり、どのモデルを選び、どう組み合わせ、各ステップでデータの1バイト1バイトがどこに存在するのかを説明します。

最終更新: 2026年4月15日。実装の詳細は、2026年4月14日にOpenWhisprデスクトップアプリへマージされた話者ダイアライゼーション機能に基づいています。以下の技術的主張はすべて一次資料に基づくか、オープンソースリポジトリで直接確認できます。

ファクトチェック・スナップショット(二重情報源)

  • 話者ダイアライゼーションは「誰がいつ話したか」に答えます。 NIST Rich Transcriptionやpyannote.audioの参照パイプラインのようなコミュニティベンチマークで評価されます。 NIST RT evaluation · pyannote.audio paper (Bredin et al., 2020)
  • 私たちは3D-Speakerプロジェクトのpyannote-segmentation-3.0とCAM++を使用しています。 どちらもオープンソースで、ONNXへ移植でき、sherpa-onnxのモデルリリースから直接ダウンロードされます。 pyannote-segmentation-3.0 model card · CAM++ / 3D-Speaker paper (Wang et al., 2023)
  • すべての推論はネイティブONNXバイナリであるsherpa-onnxを通じて実行されます — PythonランタイムもPyTorchもGPUも不要です。 バイナリと依存関係はビルド時にバージョン固定しています。 GitHubのsherpa-onnx · sherpa-onnx releases
  • 音声フィンガープリントは512次元のfloat32ベクトルで、ローカルSQLite BLOBとして保存されます。 話者1人あたり約2KBです。デバイスの外に出ることはありません。 SQLite datatype docs · ONNX Runtime
  • ダイアライゼーション中に、音声、埋め込み、文字起こしは一切デバイス外へ送信されません。 この機能全体で発生する唯一のネットワーク通信は、初回起動時の一度きりのモデルダウンロードです。オープンソースリポジトリで自分で確認できます。 OpenWhisprプライバシーポリシー · GitHubのOpenWhispr

話者ダイアライゼーションの正体

話者ダイアライゼーションは、録音の中で誰がいつ話したかをラベル付けする処理です。何を言ったかではありません。 文字起こしは「何を」に答え、ダイアライゼーションは「誰が」に答えます。この2つは補完関係にあります。優れたミーティングノート作成ツールには両方が必要であり、両者を連携させることは片方だけを作るより難しい作業です。

ダイアライゼーションが本当に難しいのには、いくつかのしぶとい理由があります。同じ通話で2人の声が重なることがあります。背景ノイズが混じります。「うん」「はい」のような短い発話には話者情報がほとんどありません。会議の途中で新しい話者が現れることもあります。さらに文字起こしと違い、ダイアライゼーションは全体を見た判断が必要です。セグメント7をセグメント2と比較せずにラベル付けすることはできません。この全体文脈が必要だからこそ、多くの商用ソリューションは録音全体をサーバー側でダイアライズし、ラベル付き文字起こしを送り返します。

文字起こしだけでは足りません。10人の会議で45分間のクロストークがあり、それが区別のないテキストの壁として出力されたら読めません。人々が本当に欲しい成果物である会議メモには話者の帰属が必要です。アクション項目、意思決定、約束はすべて人に紐づくからです。「Aliceが金曜までに移行を出荷する」はメモです。「金曜までに移行を出荷する」はノイズです。

ダイアライゼーションは話者認識とは違います

ダイアライゼーションは「この3つのチャンクはSpeaker A、この2つはSpeaker B」と言います。Speaker Aが誰かは知りません。話者認識は、音声フィンガープリントを保存済みプロファイルと照合してSpeaker Aに名前を付けます。OpenWhisprは両方を行います。ダイアライゼーションは毎回の会議で実行され、認識は誰かを名前でラベル付けした後に働きます。どちらもローカルに留まります。

4段階のローカルパイプライン

OpenWhisprの話者ダイアライゼーションパイプラインは、音声活動検出、セグメンテーション、話者埋め込み、クラスタリングの4段階で構成され、その後に話者セグメントと文字起こしを突き合わせるマージ処理が続きます。 各段階は独立したONNXモデルまたはネイティブバイナリです。PythonランタイムもPyTorchもCUDAも不要です。モデルのディスク上の合計サイズは約45MBです。

ローカルダイアライゼーションパイプライン

オーディオキャプチャマイク + システム、16kHz
VADSilero、2MB
セグメンテーションpyannote 3.0
埋め込みCAM++、512-dim
クラスタリングAgglomerative、0.5
ラベルSQLite でプロファイルを照合する

ローカルに留まる理由:

すべてのステージは、Python やクラウドではなく、単一の sherpa-onnx バイナリを介してデバイス上で実行されます。

ONNX モデルのうち ~45MB が一度ダウンロードされ、その後 ~/.cache/openwhispr/diarization-models/. にキャッシュされました

埋め込みは、ローカル ディスク上に SQLite BLOBs として保存されている 512-dim float32 ベクトルです。

音声キャプチャは最初からデュアルストリームです。マイクは定義上「あなた」なので、自分の声をダイアライズしようとして計算を使うことはありません。すべてのマイクセグメントは、音声照合ではなくソースによってあなたとラベル付けされます。システム音声(通話上の他の全員)だけが実際に話者分離を必要とするため、作業量はすぐに半分になります。

音声がキャプチャされると、4段階が順番に実行されます。VADは無音を落とし、高価な埋め込み段階が空のフレームに計算を使わないようにします。セグメンテーションは連続した発話を単一話者のチャンクへ切ります。埋め込みモデルは各チャンクを512次元の音声フィンガープリントへ変換します。凝集型クラスタリングは似たフィンガープリントをまとめ、最終的な話者IDの集合を生成します。最後のパスで、それらのIDをタイムスタンプの重なりに基づいて文字起こしへ戻し、すべての単語に話者タグを付けます。

全体はアプリから起動された単一の子プロセス内で動きます。サーバーも、待ち受けポートも、Electron外部へのIPCもありません。それは次を読めば確認できます: src/helpers/diarization.js オープンソースリポジトリ内の該当コードです。

ステージ1: 音声活動検出(Silero)

音声活動検出(VAD)は、高価な段階を走らせる前に発話と無音を分ける軽量なフィルターです。 無音に対して話者埋め込みモデルを走らせるのはCPUの無駄であり、ゴミのようなベクトルを生成します。VADが残りのパイプラインを高速に保ちます。

私たちは Silero VADを使っています。2MBのONNXモデルで、MITライセンス、実質的にオープンソース業界の標準です。Sileroは現在の32ミリ秒ウィンドウに発話が含まれる確率をフレームごとに返します。最新CPUでのコストはごく小さく、32ミリ秒チャンクあたり約0.1ミリ秒、つまり1コアの約3分の1パーセントです。

ライブパイプラインは、通話中のシステム音声に対してSileroを継続的に実行します。実際に出荷しているしきい値は、きれいなベンチマークではなく実際の会議音声に合わせて調整されています:

  • ウィンドウサイズ: 512サンプル(16kHzで32ms)
  • 発話しきい値: 0.15 — 小さな声や遠い話者を取りこぼさないよう、意図的に積極的
  • 無音しきい値: 0.08
  • セグメント終了条件: 連続16個の無音ウィンドウ(~512ms)
  • 埋め込み用の最小セグメント: 0.8秒
  • ライブ識別の頻度: 発話が1.6秒以上蓄積された後、1秒ごと

正直なトレードオフ

0.15という積極的な発話しきい値は、遠いマイクの小さな声を拾えますが、大きなキーストロークや室内ノイズに反応することもあります。0.8秒の最小セグメントフィルターは、そうした誤検出の大半が埋め込み段階に届く前に捨てるため、下流の精度は守られます。

ステージ2: セグメンテーション(pyannote 3.0)

セグメンテーションは、連続した発話を1人の話者だけを含むチャンクへ切り分け、話者交代点と重なり領域の両方を検出します。 ここが話者境界を決める段階です。その後のすべての処理は、その境界が正しいことを前提にします。

私たちは pyannote-segmentation-3.0を使っています。これはpyannote.audioチームの現行モデルで、 sherpa-onnx プロジェクトによってONNXへエクスポートされています。Pyannoteは実質的な学術ベースラインです。pyannote.audioパイプラインは、AMIやCALLHOMEのような標準ベンチマークで12〜15%台のダイアライゼーション誤り率を達成しており、商用クラウドプロバイダーが好んで引用するのと同じ水準です。

なぜ特にONNX移植版なのか。Electronアプリ内にPyTorchを同梱するのは現実的ではないからです。ランタイムだけで約1.5GBあり、高速に動かすには実質的にGPUが必要です。ONNXエクスポートはCPU上で動く単一の6.6MBファイルで、 ONNX Runtime経由で実行できます。同じモデルで、重さはごく一部です。

sherpa-onnxのオフラインダイアライゼーションバイナリにいくつかのフラグを渡して呼び出します。最小継続時間の設定はpyannote論文の推奨デフォルトに基づいています。短い発話バーストは隣接する無音にマージされ、短い無音で連続した話者セグメントが分断されないようにします。出力は暫定話者IDが付いた開始/終了時刻ペアのリストです( speaker_0, speaker_1など)。

ステージ3: 話者埋め込み(CAM++)

話者埋め込みとは、ファイルに対するハッシュのように、声を表す固定長の数値リストです。 同じ声の2つの録音はベクトル空間で近い埋め込みになり、異なる声は遠くなります。「近さ」はコサイン類似度、つまり-1から1までの単一の数値で測ります。これがパイプライン全体の数学的な中心です。

私たちの埋め込みモデルは、AlibabaのDAMO Academyによる3D-SpeakerプロジェクトのCAM++で、 VoxCeleb 話者検証ベンチマークで学習されています。実際に同梱しているファイルは 3dspeaker_speech_campplus_sv_en_voxceleb_16k.onnxで、約28MBのONNXエクスポートとして、セグメントごとに512次元のfloat32ベクトルを生成します。論文は arXiv:2303.00332 (Wang et al., 2023)です。

明らかな代替候補である ECAPA-TDNNではなくCAM++を選んだ理由は3つあります。パラメータ数がおよそ半分で、VoxCeleb1-Oでの等誤り率が低く、CPU推論が速いことです。NVIDIA NeMoのMSDDモデルにはファーストパーティのONNXエクスポートがなく、PyTorchに大きく依存しています。Picovoice Falconは商用のシート単位ライセンスです。AppleのCoreML SpeechフレームワークはmacOS専用です。CAM++だけが、クロスプラットフォーム、ONNX、オープンライセンス、CPUで高速という制約を満たしました。

CAM++へ入力するため、私たちは独自のlog-mel filterbank特徴抽出器を src/helpers/speakerEmbeddings.jsに同梱しています。80メルバンド、25ミリ秒ウィンドウ、10ミリ秒ホップという、この種のモデルで標準的なパラメータです。特徴量はONNXモデルへ onnxruntime-node経由で流れ、Electronのメインプロセス内で実行されます。出力側からは512要素のFloat32Arrayが出てきて、それをSQLiteに2,048バイトのBLOBとして書き込みます。

照合ステップ自体は、2つのベクトル間のコサイン類似度という最も単純なものです。2つの会議セグメントが同じ話者かどうかを決める実際のコードは次のとおりです:

// src/helpers/speakerEmbeddings.js
cosineSimilarity(a, b) {
  let dot = 0, normA = 0, normB = 0;
  for (let i = 0; i < a.length; i++) {
    dot += a[i] * b[i];
    normA += a[i] * a[i];
    normB += b[i] * b[i];
  }
  const denom = Math.sqrt(normA) * Math.sqrt(normB);
  return denom === 0 ? 0 : dot / denom;
}

なぜ512次元なのか

512は、VoxCelebの話者検証リーダーボードにおいて、精度と保存コストの現在のちょうどよい落としどころです。256次元では似ているが別人の声を区別する力が落ち始めます。1024次元では保存量と計算量が増える一方で効果は逓減します。512個のfloat32 × 2KB × 10人話者の会議1000件で、ディスク上は20MBです。ローカルSQLiteファイルには十分収まります。

ステージ4: クラスタリング — 「話者は何人いるのか」問題

すべてのセグメントを埋め込むとN個のベクトルがあります。クラスタリングはそれらをK人の話者へまとめます。しかし難しいのは、たいてい事前にKが分からないことです。 営業電話なら話者は2人かもしれません。全社会議なら8人かもしれません。固定Kのクラスタリングはどちらの端でも破綻します。

私たちはコサイン類似度しきい値0.5の凝集型クラスタリングを使います。各セグメントをそれぞれ1つのクラスタとして開始し、最も類似度の高い2クラスタをマージし、しきい値を超えるペアがなくなるまで繰り返します。停止位置を決めるのは目標人数ではなくしきい値です。これにより設定なしで2人から10人まで自然にスケールします。

実際の呼び出しは src/helpers/diarization.jsにあります。sherpa-onnxバイナリをいくつかのCLIフラグ付きでspawnします:

// src/helpers/diarization.js
const args = [
  `--segmentation.pyannote-model=${segPath}`,
  `--embedding.model=${embPath}`,
  `--clustering.num-clusters=${numSpeakers}`,    // -1 = auto
  `--clustering.cluster-threshold=${threshold}`, // 0.5 default
  "--min-duration-on=0.2",
  "--min-duration-off=0.5",
  wavPath,
];

出力はプレーンテキストです。セグメントごとに1行で、形式は start_sec -- end_sec speaker_NNです。これをオブジェクト配列にパースし、文字起こしを走査して、各文字起こしセグメントを時間的重なりが最大のダイアライゼーションセグメントに対応付けます。マイク由来のセグメントは常に youとラベル付けされます。音声よりソースが常に優先です。

0.5というしきい値は当てずっぽうではありません。私たち自身の評価ハーネス(scripts/meeting-diarization-eval.js)を実際の会議録音で使って調整しました。しきい値を高くするとアンダークラスタリングになり、1人の話者が2人に分割されます。低くするとオーバークラスタリングになり、似た2つの声が結合されます。0.5が私たちの収束したスイートスポットです。

ライブ・ダイアライゼーション: 話している最中にラベル付け

バッチのみのダイアライゼーションは正確ですが、通話中には見えません。ライブのみは即時ですがノイズが多くなります。OpenWhisprは両方を実行します。会議中にライブラベルを表示し、終了後にフルバッチ処理で磨き込みます。

ライブ経路は src/helpers/liveSpeakerIdentifier.jsにあります。システム音声ストリームを16kHzで取り出し、32ミリ秒フレームごとにSilero VADへ渡し、セグメントが閉じる(約512msの無音後)か、ライブ識別の頻度(発話が1.6秒以上蓄積された後1秒ごと)に達するまで発話フレームを蓄積します。トリガーされるとCAM++埋め込みを抽出し、アクティブ通話の埋め込みのインメモリマップと保存済み話者プロファイルに照合し、IPCイベント — meeting-speaker-identified— を最良の一致とともに発火します。React側はそれを受け取り、文字起こしの吹き出しをその場で更新します。

人間がUIで話者名を手動設定した後、その割り当ては ロックされます。会議後のバッチ処理は、自身の分析が異なっていても上書きできません。このロック挙動は src/utils/transcriptSpeakerState.tsにあり、重要です。最悪のUXは、ユーザーの修正を自動処理が黙って上書きしてしまうことだからです。

録音が停止すると、完全なWAVファイルに対してフルのオフラインダイアライゼーションを実行します。バッチ処理は会議全体を見られます。直近1.6秒だけではありません。そのため最終ラベルはよりきれいになります。バッチ結果はライブラベルと突き合わせられ、すべてのユーザーロックを尊重し、暫定的な推測をより正確なグループ分けに置き換えます。

ハイブリッドが単独方式より優れる理由

ライブのみは速いですが短いセグメントではノイズが多くなります。バッチのみは正確ですが会議が終わるまで無言です。ハイブリッドなら、通話中の即時フィードバックと、全体文脈を反映した最終文字起こしの両方が得られます。コストは追加の照合ステップ1つだけで、終了後にバックグラウンドで一度走るだけなので実質的に無料です。

音声プロファイル: 会議をまたいで話者を覚える

一度の会議で話者を「Alice」とラベル付けすると、OpenWhisprは以後のすべての会議で自動的に彼女をラベル付けします。あなたが何もしなくても、彼女の音声フィンガープリントがデバイスから出ることはありません。

仕組みは単純です。各話者のセントロイド埋め込みをローカルSQLiteデータベースに保存します。将来の通話で新しい埋め込みが現れると、保存済みプロファイルすべてとコサイン類似度で比較します。しきい値を超えた一致は名前を自動割り当てします。この処理はサーバーに触れません。これを支える3つのテーブルは普通のSQLiteです:

-- src/helpers/database.js
CREATE TABLE speaker_profiles (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  display_name TEXT NOT NULL,
  email TEXT,
  embedding BLOB NOT NULL,          -- 512 float32s (~2KB)
  sample_count INTEGER DEFAULT 1,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE speaker_mappings (
  note_id INTEGER NOT NULL,
  speaker_id TEXT NOT NULL,
  profile_id INTEGER,
  display_name TEXT NOT NULL,
  PRIMARY KEY (note_id, speaker_id),
  FOREIGN KEY (note_id) REFERENCES notes(id) ON DELETE CASCADE,
  FOREIGN KEY (profile_id) REFERENCES speaker_profiles(id) ON DELETE SET NULL
);

CREATE TABLE note_speaker_embeddings (
  note_id INTEGER NOT NULL,
  speaker_id TEXT NOT NULL,
  embedding BLOB NOT NULL,
  PRIMARY KEY (note_id, speaker_id),
  FOREIGN KEY (note_id) REFERENCES notes(id) ON DELETE CASCADE
);

照合は、信頼度と誤検出のバランスを取るため3段階です:

  • Cosine ≥ 0.70: 自動確定。プロンプトなしで即座にラベルが表示されます。
  • 0.55 ≤ Cosine < 0.70: 候補提示。UIに「これはAliceですか?」と確認/却下コントロールを表示し、入力を待ちます。
  • Cosine < 0.55: 匿名のまま。手動で名前を付けるまで、そのセグメントはSpeaker Nのままです。

一致を確認すると、保存済みセントロイドは生の上書きではなく移動平均で更新されます。式は単純です: new_avg = (stored_avg × sample_count + new_embedding) / (sample_count + 1)。これにより、寒い朝、違うマイク、悪いケーブルといった声のドリフトに対応し、将来の照合を壊しません。Aliceの声のサンプルをシステムが多く見ているほど、彼女のプロファイルは安定します。

新しいプロファイルは 遡及的な再ラベル付けも引き起こします。初めて話者に名前を付けると、バックグラウンドタスクが過去すべての会議の保存済み埋め込みを走査し、一致を探し、履歴の文字起こし内にある未命名話者を更新します。ユーザーがロックしたマッピングは決して触りません。結果として、Aliceを一度名付けるだけで、彼女が参加した過去すべての通話にも遡ってラベルが付きます。

Google Calendarが接続され、会議に参加者がいる場合、話者ピッカーUIには参加者の名前とメールアドレスが事前入力されます。ワンクリックで Speaker 2 alice@example.comを割り当て、その対応は永続化されます。参加者と音声プロファイルはリンクされたままなので、デバイスをまたいでも正しい名前が表示されます。

データが実際に存在する場所

音声はデバイスの外に出ません。これはマーケティング文句ではありません。コードの機械的な性質であり、OpenWhisprはオープンソースなので自分で検証できます。 各ステップで各データがどう扱われるかを正確に示します。

対象保存場所デバイス外へ出るか
会議の生音声OSの一時ディレクトリ、ダイアライゼーション後に削除いいえ — 一度も
ダイアライゼーションONNXモデル~/.cache/openwhispr/diarization-models/初回起動時に一度だけダウンロード、その後は二度となし
話者埋め込み(音声フィンガープリント)ローカルSQLite BLOBいいえ — 一度も
話者と名前のマッピングローカルSQLiteテーブルいいえ — 一度も
文字起こしテキストローカルSQLite(有効化した場合のみ任意のクラウド同期)オプトインした場合のみ

会議の生PCM音声はOSの一時ディレクトリ内のファイルに書き込まれ、ローカルでspawnされたsherpa-onnxバイナリへ渡され、ダイアライゼーション完了時に finallyブロックで削除されます。OpenWhisprのダイアライゼーションパイプラインには、音声をアップロードするためにネットワークソケットを開くコードパスはありません。コードはオープンソースなので、 src/helpers/diarization.js, src/helpers/liveSpeakerIdentifier.js src/helpers/speakerEmbeddings.js fetch, axiosまたは http をgrepして、自分で確認できます。

Electronのメインプロセスとレンダラープロセス間のIPCは、組み込みの contextBridge を使います。これは単一プロセス内のカーネルレベルのパイプであり、ネットワークソケットではありません。話者埋め込みと音声プロファイルは、ノートと同じデータベースファイル内にSQLite BLOBとして保存され、同じOSレベルのファイル権限を受けます。

唯一の例外を透明に

この機能で唯一発生するネットワーク通信は、初回起動時の一度きりのモデルダウンロードです。sherpa-onnxのGitHub releases CDNから3つのONNXファイルを取得します。私たちはこれを明示的に記載しています。「セットアップ中に1回だけ、その後はゼロ」というプライバシーの物語は、「常時クラウド推論」とはまったく違うからです。オープンソースのミーティングアシスタントとして、発見されるより先に伝えたいのです。

正直な制約

ローカルダイアライゼーションは優れていますが、完璧ではありません。本当に苦手なケースと、それぞれに対して私たちがどう対処しているかを示します。

  • 話者の重なり。 2人が同時に話す状況は、単一ラベルの帰属では本質的に情報が失われます。Pyannote 3.0は重なり領域を検出しますが、セグメントごとに1つのspeaker_idでは「両方が話している」を表現できません。そうしたセグメントは暫定としてマークし、UIで修正できるようにしています。
  • コールドスタート精度。 新しい同僚との最初の会議では、汎用のVoxCeleb学習特徴量だけを使います。一度ラベル付けし、プロファイルにいくつかのサンプルが入ると、その後の会議は目に見えて精度が上がります。
  • 短い発話。 「うん」「はい」、短い笑い声など、約0.8秒未満のものは信頼できる埋め込みを生成できません。これらのセグメントは埋め込み段階から落とし、周辺文脈からのラベル伝播へフォールバックします。
  • 遠距離または低SNR音声。 騒がしい部屋でスピーカーフォン越しに話す人の音声は、緩やかに、しかし目に見えて劣化します。自動ゲイン制御とVAD調整は役立ちますが、どれだけモデルが高度でも悪い物理条件を完全に克服することはできません。
  • CPUのみの推論。 GPUがあれば速くなりますが、私たちはあえて必須にしていません。実際には最新CPUのスループットで十分です。埋め込み1つあたり約100ミリ秒、M1 Macで45分の会議をバッチダイアライズするのに約30秒です。

私たちは評価ハーネス( (scripts/meeting-diarization-eval.js) )をオープンソースリポジトリで公開しているので、私たちが選んだ見栄えのよいデモクリップではなく、自分の録音で測定できます。マーケティング資料の光沢ある数字より、その方が誠実だと考えています。

Sherpa-ONNXを選んだ理由

必要だったのは、クロスプラットフォームで、Python不要で、CPUで高速に動き、オープンソースのダイアライゼーションランタイムでした。Sherpa-ONNXだけが4つすべてを満たしました。

要件は具体的でした。macOS(IntelとApple Siliconの両方)、Windows x64、Linux x64で、別々のコードパスなしに動く必要がありました。PyTorchを同梱することはできません。ランタイムだけで約1.5GBあり、インタラクティブなレイテンシで使うには実質的にGPUが必要だからです。CUDAを必須にすることもできません。そして、商用でもあるオープンソースのデスクトップアプリに同梱できるほど寛容なライセンスである必要がありました。

実際に評価したものは次のとおりです:

  • pyannote.audio (Python): 不採用。ElectronアプリにPyTorchを同梱するのは現実的ではありません。
  • NVIDIA NeMo MSDD: PyTorch依存で、ファーストパーティのONNXエクスポートがなく、GPU中心です。
  • Picovoice Falcon: アクティブユーザー単位の商用ライセンスに加え、初回実行時のクラウドアクティベーションがあります。
  • Apple CoreML + Speech framework: macOS専用で、クロスプラットフォームの同等性がありません。
  • sherpa-onnx: Apache 2.0、ネイティブONNXバイナリ、約35MBのモデル、spawnしてstdoutを読むインターフェイス、k2-fsaにより活発にメンテナンスされています。同じツールチェーンが、出荷済み製品の本番ASRも支えています。

正直な注意点が1つあります。sherpa-onnxのオフライン話者ダイアライゼーションバイナリは比較的新しいものです。私たちは v1.12.23にバージョン固定し、ピンを上げる前に毎回フルの評価ハーネスを実行します。これほど重要な部分には、そのトレードオフが正しいと考えています。

よくある質問

音声をクラウドへアップロードしないプライベートなミーティングノート作成ツールはありますか?
はい。OpenWhisprは無料でオープンソースのミーティングノート作成ツールで、音声はデバイスの外に出ません。文字起こしはOpenAI WhisperまたはNVIDIA Parakeetでローカル実行され、話者ダイアライゼーションはpyannote segmentationとCAM++ embeddingsでローカル実行され、音声フィンガープリントは私たちのサーバーではなく、あなた自身のマシン上のローカルSQLiteファイルに保存されます。
Otter、Fireflies、Granolaは私の会議を見ることができますか?
はい。クラウドベースのAIノート作成ツールは、文字起こしとダイアライゼーションのために会議音声を自社サーバーへアップロードします。話者ラベル、文字起こし、音声埋め込みはそのインフラ内で計算され、データベースに保存されます。音声のすべてのバイトをデバイス上に留める代替が必要なら、OpenWhisprのようなローカルファーストのツールが必要です。
話者ダイアライゼーションとは何ですか?
話者ダイアライゼーションとは、録音の中で誰がいつ話したかをラベル付けする処理です。何が話されたかに答える文字起こしとは別のものです。ダイアライズされた文字起こしでは、区別のないテキストの壁ではなく、各発話にAlice、Bob、Speaker 3のような話者ラベルが付きます。
ローカル話者ダイアライゼーションは実際に機能しますか? クラウドより劣るだけではありませんか?
機能します。OpenWhisprのパイプライン — pyannote-segmentation-3.0と3D-SpeakerプロジェクトのCAM++ embeddingsを組み合わせ、sherpa-onnx経由で実行する構成 — は、標準ベンチマーク上で商用クラウドAPIと同じ範囲のダイアライゼーション誤り率を達成します。主な正直なトレードオフは、重なり発話の扱いと、システムがまだ聞いたことのない声に対するコールドスタート精度です。
ローカルダイアライゼーションモデルはどのくらいのディスク容量を使いますか?
合計で約45MBです。pyannote segmentation(6.6MB)、CAM++話者埋め込みモデル(28MB)、Silero VAD(2MB)です。3つとも初回起動時に一度だけダウンロードされ、~/.cache/openwhispr/diarization-models/ にキャッシュされます。その後はディスクに残り、再ダウンロードされません。
OpenWhisprのローカルダイアライゼーションにGPUは必要ですか?
いいえ。すべてONNX Runtime経由でCPU上で動きます。最新のApple Siliconや最近のx86 CPUなら快適に処理できます。実際にはM1 Macで45分の会議をバッチダイアライズするのに約30秒で、通話中のライブ話者ラベルは1〜2秒以内に表示されます。
OpenWhisprは本当にオープンソースですか?
はい。ダイアライゼーションコード、文字起こしパイプライン、デスクトップアプリの残りもGitHubでオープンソースです。この記事のプライバシー主張は直接検証できます。ダイアライゼーションヘルパーでfetch、axios、httpをgrepしても、ダイアライゼーション経路にネットワーク呼び出しは見つかりません。
通話中に2人が同時に話すとどうなりますか?
Pyannote 3.0は重なり領域を明示的に検出し注釈しますが、重なり中の単一ラベル帰属は本質的に情報が失われます。私たちはそうしたセグメントを文字起こし上で暫定としてマークし、話者ラベルをクリックして修正できるようにしています。手動修正はロックされ、後の自動処理で上書きされることはありません。

プライベートなミーティングノート作成ツールを試す

OpenWhisprは、音声がデバイスの外に出ない無料のオープンソースミーティングアシスタントです。ローカルダイアライゼーション、ローカル文字起こし、ローカル音声プロファイル。macOS、Windows、Linuxで動作します。

アカウント不要 · オフライン対応 · 永遠にオープンソース