前回の記事で「話者判別の詳細は別記事に譲ります」と書いたので、その回収です。うまくいっている部分と、まだ解けていない部分の両方を書きます。
TL;DR
- フォルダに録音を置くだけで、話者別の逐語録ができてチャットに届きます。依頼文は書きません。
- 文字起こしも話者判別も全部自宅のPCの中(GPUなし)。追加費用は0円です。
- 話者判別は参照音声があれば実名、なければ「話者A」として分離。人数も自動で推定します。
- 山場は素直に作ると人数を少なく見積もること。迷ったら分割する側に倒して解決しました。
- まだ完成していません。実用域は2〜4人。続きの「課題の整理」も、ローカルLLMでは壊れました。
1. なぜ作ったか
会議の記録を残すのは大事ですが、正直めんどうです。録音は簡単でも、後から聞き直して文字にするのは時間がかかります。
かといって、外部の文字起こしサービスに会議の音声をそのまま渡すのは避けたい。中身によっては、社外に出していいものではありません(この線引きについては前回の記事で詳しく書きました)。
なので、自宅のPCの中だけで完結する仕組みを作ることにしました。
2. 全体像:音声を置いてから、逐語録が届くまで
やっていることは6つです。
| # | やること | 使っているもの |
|---|---|---|
| 1 | 音声の投入を検知する | 常駐している監視プロセス |
| 2 | 文字起こし | faster-whisper(medium / CPU) |
| 3 | 話者判別 | resemblyzer + 自前のクラスタリング |
| 4 | 逐語録を組み立てる | 自作 |
| 5 | 保存して、音声を圧縮して片付ける | PyAV |
| 6 | チャットに投稿する | ブラウザ操作 |
3. 「依頼文を書かない」という設計
私のAI会社では、たいていの仕事は「Webの画面から依頼を書く」→「秘書役のAIが担当を振り分ける」という流れで動きます。でも議事録は、この流れを通しません(もう一つ、記事のネタを自動でWiki化する仕組みも同じ方式です。「毎回同じことしかしない仕事」はこちらに寄せています)。
決まったフォルダに音声ファイルを置く。それが起動の合図です。依頼文を書く工程そのものが存在しません。
会議が終わって録音ファイルができたら、それをフォルダに放り込む。やることはそれだけ。「議事録を作ってください」と打つ手間すら省きました。毎回同じことしかしない仕事に、毎回同じ依頼文を書くのは無駄だからです。
正体は「watcher」という1本の監視ループ
「フォルダに置くだけで起動する」と聞くと、議事録専用の仕組みを新しく作ったように聞こえるかもしれません。でも実態は違います。
私のAI会社にはwatcherという常駐プロセスが1本だけあります。5秒ごとにループを回して、doing/フォルダにチケットが置かれていないか見張り、あれば担当エージェントを起動する——これがAI会社の秘書業務を裏で支えている中枢です。
議事録は、このwatcherが5秒ごとに回すチェック項目の中に、「議事録フォルダも見る」という1行が混ざっているだけです。チケットの監視も、議事録フォルダの監視も、同じ1つのループの中で、同じ5秒間隔で、順番に行われています。専用の監視プロセスを別に立てているわけではありません。
つまり「議事録が特別な起動方法を持っている」のではなく、watcherという1本の仕組みが、たまたま複数の役割を同時に担っているというのが正確な言い方です。専用プロセスをもう1本立てるより、既にあるループに1つ処理を足す方が、メモリも管理の手間も増えません。
地味だけど大事な3つの工夫
素直に「フォルダを見て、ファイルがあったら処理する」と書くと、実際には壊れます。
① 書き込みの途中で掴まない
録音ファイルのコピーが終わる前に処理を始めると、中途半端なファイルを読んでしまいます。そこで、ファイルサイズが変化しなくなる(=書き込みが終わった)ことを確認してから着手します。
② 二重に処理しない
5秒ごとに見に行くので、処理に時間がかかると同じファイルを何度も拾ってしまいます。そこで、着手した瞬間に .processing/ という別のフォルダへ移動させます。フォルダから消えていれば、次の周回では見えない——ロックの仕組みをわざわざ作らなくても、移動だけで解決します。
③ 本体を止めない
文字起こしは重い処理で、15分の音声に10分以上かかります。もしこれを監視ループの中で待ってしまうと、その間チケットの処理が全部止まります。そこで、議事録の処理は完全に別プロセスとして切り離して起動し、監視ループはすぐ次の周回に進みます。
4. 話者判別:誰が喋ったかを、どう分けているか
ここが本題です。
考え方
まず、声には人ごとの特徴があるという前提に立ちます。resemblyzer というライブラリを使うと、音声の一区間を**256次元の数値の並び(=声の指紋のようなもの)**に変換できます。同じ人の声なら近い数値に、違う人なら遠い数値になります。
あとは、近いもの同士をグループにまとめれば、話者ごとに分けられる——これがクラスタリングです。
実名か、匿名か
グループ分けができても、それが「誰なのか」は分かりません。そこで:
-
議事録/話者/というフォルダに、参照音声(その人が喋っているファイル。ファイル名がそのまま人の名前になります)を置いておく - 各グループの"平均的な声の指紋"と、参照音声の指紋を似ている度合いで突き合わせる
- 十分似ていれば実名(例:「山田」)、似ている人がいなければ匿名(「話者A」「話者B」…)
参照音声を用意していない人も、ちゃんと別人として分離されるのがポイントです。「知らない人だから全部ひとまとめ」にはなりません。
何人いるかも自動で決める
参照音声を3人分置いたからといって、その会議に3人しかいないとは限りません。飛び入りの人がいるかもしれない。
なので、話者数そのものを推定しています。「2人だと仮定して分けてみる」「3人だと仮定して分けてみる」…と順に試して、一番きれいに分かれたものを採用する、という方法です。
「きれいに分かれた」の判定にはシルエット係数という指標を使います。ざっくり言うと「同じグループの中では似ていて、違うグループとは離れているか」を数値にしたものです。
5. 山場:素直に作ると「人数を少なく」見積もる
ここが今回いちばん苦労したところです。
シルエット係数が最大になる分け方を選ぶ——これは教科書どおりの素直な実装です。ところが実際に会議音声で試すと、明らかに人数を少なく判定することが頻発しました。3人で話しているのに2人と判定し、別々の人が同じグループに混ざってしまうのです。
理由を考えると腑に落ちます。**シルエット係数は「グループ数が少ない方が高くなりやすい」**性質があります。大雑把にまとめてしまえば、グループ内はまとまって見えるからです。指標に忠実に従うほど、統合しすぎる方に倒れるわけです。
議事録として困るのはどちらか。「Aさんの発言がBさんのものになる」方が、明らかに困ります。「話者A」「話者B」と分かれすぎているのは後から人が直せますが、混ざってしまったものは、元の音声を聞き直さない限り分離できません。
そこで、判定の仕方をこう変えました。
最良のスコアだけを採らない。「最良からわずかな差(0.05以内)に収まっているものは、同じくらい良いとみなす」ことにして、その中で一番グループ数が多い分け方を選ぶ。
つまり迷ったら分割する側に倒す。スコアがほぼ同じなら、多めに分けておく。これで「別人が混ざる」ケースが目に見えて減りました。
指標をそのまま信じるのではなく、「間違えたときにどちらが痛いか」で寄せる——この判断が、実運用ではけっこう効きます。
6. できあがるもの
逐語録はこんな形式で出力されます。
{14:31:53}山田【0分12秒】<それでは、先週の進捗から確認させてください>
{14:32:05}話者A【0分08秒】<こちらは予定どおり進んでいます>
- 絶対時刻(録音開始時刻+経過時間で計算)
- 話者名(実名または匿名)
- その発言にかかった時間
- 発言内容
録音ファイル名から開始日時を読み取っているので、「会議の何分後」ではなく**「何時何分の発言か」**が分かります。後から「あの話、14時半ごろだったよね」と探すときに効きます。
また、よく誤変換される固有名詞は置換ルールを持たせています。「グラビティ」→「Antigravity」、「オープンイヤー」→「OpenAI」といった具合です。カタカナで書き起こされる技術用語は、辞書で後から直す方が早いという割り切りです。
ただし、この辞書は万能ではありません。同じ単語が何通りにも化けることがある——この限界にぶつかった話は、8章で詳しく書きます。
処理が終わると、元の音声は圧縮してアーカイブへ(モノラル・低ビットレートのmp3に再エンコード)。会議録音は容量を食うので、ここは実用上けっこう効きます。
7. 正直な限界:話者判別は、まだ完成していません
ここが一番書いておきたい部分です。
実用に耐えるのは2〜4人程度の会議までです。それ以上になると精度が落ちます。具体的に苦手なのは:
- 声質が近い人(同性で似た声だと混ざります)
- 発言の重なり(同時に喋ると、その区間はどちらか一方に寄ります)
- 短い相槌(「うん」「はい」だけの区間は、声の特徴を取るには短すぎます)
さらに厄介なのは、合成音声できれいにテストすると、うまく分かれてしまうことです。ノイズがなく、一人ずつ順番に喋り、重なりもない——そんなデータでは問題が起きません。実際の会議で初めて崩れる。これは前回の記事で書いた「自分で作ったテストデータで測れるのは、自分が想定した失敗だけ」という話と、まったく同じ構図です。
8. 「その会議で挙がった課題と対策」の扱いで、いちばん悩みました
逐語録の先には、もう一つやりたいことがありました。その会議で何が課題として挙がり、どう対策するかを整理することです。ここが最後まで決まりませんでした。
タイトルで「AI会社を通さない」と書きましたが、それは逐語録づくりの話です。ここから先の「課題と対策の整理」は、結論から言うとAI会社の標準フロー側に戻します。理由をこの章で説明します。
まず「自動化しない」と決めていた
理由は単純で、この手の整理は間違っても気づけないからです。
逐語録なら、変な文字起こしがあっても「ここ違うな」と分かります。でも要約は、もっともらしい文章として仕上がってしまうので、元の発言と食い違っていても読んで気づけません。合意事項や数字が変わっていたら、記録として致命的です。
「ローカルのLLMなら外に出さずに済む」と考えた
ただ、毎回手で書くのも続きません。そこで自宅PCの中で動くLLMに書かせてみようと考えました。ローカルなら会議の内容が外に出ないので、いちばん筋が良いはずです。
順に試しました。
| 試したもの | 結果 |
|---|---|
| 7Bモデル | 10項目出したが、うち2項目は文章が完全に同一。水増しされていた |
| プロンプトを厳しく(重複禁止・5項目まで) | 重複は消えた。ただし中身の具体性は上がらず |
| 14Bモデル | 明確に改善。重複なし、論点も拾えるようになった |
14Bで「これならいけるかも」と思いました。7分の会議に約15分かかりましたが、この整理は急ぎの処理ではないので許容範囲です。
実際の会議音声で、はっきり壊れた
ところが、本物の会議音声で試したところ結果が変わりました。
原因は要約能力ではなく、文字起こしの表記ゆれでした。会議で何度も出てくるあるツール名が、聞き取りのたびに違う日本語になっていたのです。
「アンチュアルティ」「安徹ラビティ」「アンジュラブ」「アンチグラフィー」
これ、全部同じものです。(Antigravity のことです)6章で書いた置換辞書には「グラビティ」というパターンしか登録しておらず、この4つはどれも対象外でした。辞書は「知っている化け方」しか直せません。想定していなかった化け方には、当然ノータッチです。
ローカルLLMは、これを4つの別々の話題として扱いました。結果、同じ課題が別の見出しで何度も出てくる、意味の通らないまとめができあがりました。
同じ逐語録をClaudeに読ませたら、文脈から「全部同じツールの話だ」と判断して1つに束ねました。さらに「この語は何を指すか断定できない」という箇所には、確信度が低いと明記してきました。
差が出たのは要約の上手さではありません。その単語が何なのかを知っていて、前後の話から結びつけられるか——そこでした。これは小さいモデルには荷が重い作業です。
でも「じゃあClaudeで」とは、すぐにならなかった
Claudeに任せれば品質は解決します。しかし逐語録をClaudeに渡すということは、会議の内容がクラウドに送信されるということです。ここまで全部ローカルで完結させてきた意味が、最後の一歩で消えます。
しかも会議には他の参加者がいます。自分のデータなら自分で判断できますが、他人の発言を外に出す判断を、私が勝手に下していいのかという問題があります。
着地:「自動化しない」ではなく「自動で始まらないようにする」
最終的にこうしました。
- 逐語録ができると、「まとめ作成待ち」の依頼票が自動で作られる
- ただしその依頼票には特別な印が付いていて、常駐している監視プロセスは絶対に手を出しません
- 私が「これを処理して」と言ったときだけ、Claudeが逐語録を読んでまとめを作る
つまり、準備は自動、着手は手動です。
ポイントは、これを気合いや運用ルールではなく仕組みで縛ったことです。「うっかり自動で走ってしまった」が起きないように、監視プロセス側が構造的に触れないようにしてあります。実際に「本当に起動しないか」を確かめてから採用しました。
おまけ:ローカルLLMには「出典」が存在しない
この過程で気づいたことがあります。
ローカルLLMが書いた解決策に出典を付けようとして、できませんでした。当たり前といえば当たり前で、ローカルで動くLLMはインターネットを見ていません。学習したときの知識から書いているだけなので、「どのサイトに書いてあったか」という情報がそもそも存在しないのです。
Wikipediaを引いて補おうともしました。でもここでも同じ壁にぶつかります。「Antigravity」で引くと物理学の「反重力」の記事が返ってくる。関係のない出典を貼るくらいなら何も貼らない方がマシなので、その語は出典なしにしました。
Claudeに寄せたことで、ここは解決しました。その場でWeb検索して、実在するURLを確認してから付けられるようになったからです。
「AIが調べて書いてくれる」と一括りにしがちですが、検索する手足を持っているかどうかで、できることは全然違います。
まとめ
- フォルダに音声を置くだけで、話者別の逐語録ができてチャットに届く。依頼文は書かない。文字起こしも話者判別も自宅PC内で完結し、追加費用は0円。
- 常駐プロセスは議事録専用ではなく、もともとある秘書の監視ループに相乗りさせている。二重処理は「着手時に別フォルダへ移動」で防ぎ、重い処理は別プロセスに切り離して本体を止めない。
- 話者判別は「参照音声があれば実名、なければ匿名で分離」。人数も自動推定する。
- 山場は**「素直に指標に従うと人数を少なく見積もる」**問題。間違えたときにどちらが痛いかで判断を寄せた(混ざるより分かれすぎる方がマシ)。
- まだ完成していません。 実用域は2〜4人。声質・重なり・相槌は苦手。
- 「まとめ」はローカルLLMに書かせようとして実際の会議音声で壊れた。原因は要約能力ではなく、同じツール名が4通りに聞き取られていたのを同一だと気づけなかったこと。
- 着地は「準備は自動、着手は手動」。依頼票は自動で作られるが、指示するまで動かないように仕組みで縛った。外部に出すかどうかを、毎回人が決めるための関門です。タイトルで「通さない」と言ったAI会社の標準フローに、まとめだけは戻しました。
話者判別の改善案は、いまも探しています。pyannote.audio のような専用ライブラリに移すべきなのか、そもそもアプローチを変えるべきなのか——実際に運用されている方の知見をいただけると、とても助かります。
質問・ツッコミ歓迎です。
