はじめに
本記事は、個人の技術検証として行った内容をまとめたものです。
業務とは切り離した形で、音声認識と話者分離の実装を検証しています。
普段はシステム設計に関わることが多いのですが、
今回は「議事録生成を自分でどこまで作れるか」をテーマに取り組みました。
これまでの取り組み
今回の内容は、以下の検証の続きになります。
-
Whisperを使った議事録生成の検証
https://qiita.com/miyakawa2449@github/items/be7a1e5c2a16ac934f13 -
AIを使った議事録生成の設計検証
https://qiita.com/miyakawa2449@github/items/9d33a5adf0b33680b00e
今回はそこから一歩進めて、
「話者分離を含めたパイプライン化」に取り組みました。
きっかけ
オンライン会議を録画していたにも関わらず、
会議後に議事録生成が動いていないことに気づきました。
原因は単純な設定ミスです。
ただ、このときに思いました。
それなら自分で作ってしまった方が早いのでは?
まずは文字起こしから
最初はシンプルに、音声の文字起こしから始めました。
OpenAI APIやWhisper系のツールを使えば、
- 音声 → テキスト
は比較的簡単に実現できます。
実際、ここはかなり早く形になりました。
文字起こしのPython実装は私にとって未経験でしたが、
まず Amazon Kiro に相談して仕様を作成してもらい、
その内容を精査しながら疑問点を解消しました。
その後は、普段の進め方と同じように Claude Code で実装し、
Code でテストを行い、最後にユーザテストまで実施しました。
ここまで、たしか1時間もかかっていなかったと思います。
つまり、少なくとも文字起こしまでなら、今のAI支援開発ではかなり短時間で実用レベルまで持っていける、という手応えがありました。
しかし「誰が話したか」が分からない
実際に使ってみると、すぐに問題に気づきました。
- 誰が話しているのか分からない
- 会話の流れが追いづらい
- 実用的な議事録としては弱い
もちろん、「誰が話したか分からない」という問題もありますが、
それ以上に感じたのは、議事録としての精度の限界でした。
会議で決まったことや背景を正確に整理するには、
- 発言の文脈
- 誰の意見なのか
- 話の流れ
これらが揃って初めて意味を持ちます。
つまり、
精度の高い議事録を作るためには、話者分離は必須である
というのが、最初の大きな気づきでした。
話者分離を入れた瞬間に難易度が上がる
文字起こしだけであれば、1本の処理で完結します。
しかし話者分離を入れた瞬間に、構成が大きく変わりました。
音声 → 話者分離 → 文字起こし → 統合 → 出力
実際には、
- 話者分離(誰が話したか)
- 音声認識(何を話したか)
この2つを別々のAIで並行処理し、
その結果を時間軸で統合する必要があります。
ここで感じたのは、
「難しい」というより、求められる精度と設計の質が変わったということでした。
AI駆動開発である以上、
実装そのものはClaude Codeなどを使えばある程度進められます。
ただ、これまでのように
「一気に仕上げる」
という進め方では成立しないと感じました。
特に大きかったのは、
- 話者分離と音声認識で使うAIが異なること
- それぞれの結果を整合させる必要があること
です。
この「整合させる」という部分は、
単なる実装ではなく設計の問題になります。
実際、このあたりはAmazon Kiroに相談しながら進めることになり、
処理の分割や統合の考え方を整理しながら進めていきました。
アーキテクチャ
全体の処理フローは以下のようになっています。
Audio/Video Input
↓
Audio Extraction (ffmpeg)
↓
WAV Audio
↓
┌───────────────┬───────────────┐
↓ ↓
Diarization ASR
(pyannote.audio) (faster-whisper / whisper)
↓ ↓
Speaker Turns ASR Segments
└───────────────┬───────────────┘
↓
Alignment
↓
JSON / Markdown Output
各処理の役割
Audio Extraction(ffmpeg)
動画・音声ファイルから解析用の音声(WAV)を抽出
Diarization(pyannote.audio)
「誰がいつ話したか」を推定(Speaker Turnsを生成)
ASR(faster-whisper / whisper)
「何を話したか」をテキスト化(ASR Segmentsを生成)
Alignment
話者情報と発話内容を時間軸で統合
Output
JSON(構造化データ)とMarkdown(議事録形式)を生成
この中で特に重要なのが Alignment(統合処理) です。
話者分離と音声認識はそれぞれ独立して動くため、
その結果をどのように結びつけるかが設計のポイントになります。
設計のポイント
今回の実装で重要だったのは、モデル選定よりも設計でした。
ただし、ここでいう設計は「自分でゼロから考える」というよりも、
Amazon Kiroと相談しながら、最終的な構成を決めていくプロセスでした。
特に意識したポイントは以下です。
- 話者分離と音声認識の結果をどう統合するか
- 精度と処理コストのバランスをどう取るか
- 出力をどの形式で持つか
出力設計(JSON + Markdown)
最終的に、
- JSON(構造化データ)
- Markdown(人間が読む議事録)
の2種類を出力する構成にしました。
これは、
- JSON → 後処理やAI連携に使える
- Markdown → そのまま議事録として使える
という役割分担を意識したものです。
このあたりは、Amazon Kiroと対話しながら
「最終的にどう使うのか」というゴールを整理しつつ決めていきました。
結果として、
単に動く処理ではなく、
「後工程に繋がるデータ構造」を持つ設計になったと感じています。
気づいたら1600行になっていた
気づいたらコードが約1600行になっていました。
今回の開発はAI駆動で進めていたこともあり、
実装自体はスムーズに進んでいました。
そのため、正直なところ少し油断していました。
違和感に気づいたのは、
開発中やテスト時に「なんとなく遅い」と感じ始めたタイミングです。
そこでソースコードを確認したところ、
1ファイルに1600行以上詰め込まれている状態になっていました。
普段の業務であれば、
- 300行を超えないように分割する
- タイミングを見てリファクタリングする
といったルールを設けて進めています。
(Claude Codeに対しても、CLAUDE.mdでその前提を指示しています)
ただ今回は個人の検証ということもあり、
夢中になって進めているうちに、そのあたりの管理が後回しになっていました。
いわゆる「自己研究あるある」です。
リファクタリング
このままでは保守できないと判断し、構造を分割しました。
models.py # データクラス定義
cli.py # CLI Parser
device.py # デバイス判定
diarization.py # 話者分離
asr.py # 音声認識
alignment.py # 統合処理
output.py # 出力生成
pipeline.py # 全体制御
結果として、
- 責務ごとに分離できた
- テストしやすくなった
- 処理の流れが追いやすくなった
など、設計としても改善できたと感じています。
環境の違い
以下は、個人の検証環境で測定した結果です。
| Device | Platform | Total |
|---|---|---|
| CPU | macOS | ~330s |
| MPS | macOS | ~140s |
| CUDA | WSL2/Windows | 24.5s |
検証環境
-
Windows(WSL2)
- CPU: Intel Core i7
- GPU: NVIDIA GeForce RTX 5070 (12GB)
- RAM: 32GB
もともとはRTX 3060環境で検証していましたが、
昨年RTX 5070にGPUを交換したため、今回の測定はその構成で行っています。
-
macOS
- MacBook Pro (M4 Pro)
- メモリ: 48GB
ハマりどころ
今回の実装で一番ヒヤッとしたのは、環境周りのトラブルでした。
仮想環境を作り忘れていた
これは完全に自分の不注意ですが、
Windows環境で仮想環境を作らずに、グローバルにPythonライブラリをインストールしていました。
途中で気づいたときは、正直かなりゾッとしました。
一番まずかったのはCTranslate2
特に問題だったのが CTranslate2 です。
今回の環境では、
- RTX 5070(CUDA 12系)
- CTranslate2 の pipパッケージ
の組み合わせがうまく動かず、
そのままでは実行できませんでした。
ソースビルドを決断
最終的に、
CTranslate2をCUDA環境に合わせてソースビルドする
という判断をしました。
これに約1時間ほどかかりましたが、
結果的には問題なく動作するようになりました。
そして気づく「グローバル環境問題」
ここでさらに問題が発覚します。
せっかくビルドしたCTranslate2が、
グローバル環境に入っている状態でした。
つまり、
仮想環境を作り直したら、またやり直し?
という状況です。
解決:グローバルから仮想環境へコピー
ここはAmazon Kiroに相談して解決しました。
グローバルにインストールしたパッケージを
仮想環境にコピーする方法を教えてもらい、
ビルドをやり直すことなく移行できました。
ハマったことによる学び
今回の件での学びはシンプルです。
- 仮想環境は最初に作る
- GPU系ライブラリはバージョン依存が強い
- pipで動かない場合はソースビルドも選択肢
- 環境を壊しても、リカバリ方法はある
特に、GPU + ライブラリの組み合わせ問題は想像以上にシビアだと感じました。
学び(話者分離プロジェクトを通して)
今回のプロジェクトを通して感じたことは、いくつかあります。
まず一番大きかったのは、
問題はモデルではなく設計にある
ということでした。
文字起こしのように単体で完結する処理は、
AIを使えば比較的短時間で形になります。
しかし話者分離のように複数の処理が関わると、
途端に「どう組み合わせるか」という設計の問題になります。
次に感じたのは、
スクリプトは作れるが、パイプラインは設計しないと作れない
ということです。
今回の構成では、
- 話者分離
- 音声認識
- アライメント
- 出力
それぞれが独立した処理として存在し、
それらを繋ぐことで初めて意味を持ちます。
これは単なるコードではなく、
明確な設計が必要な領域でした。
そしてもう一つ、
「動くもの」と「使えるもの」は別物である
ということです。
最初は動けばよいと思っていましたが、
- 出力形式(JSON / Markdown)
- 再利用性
- 後工程との連携
まで考えると、
単なるツールではなく「仕組み」として設計する必要がありました。
今後
今回の実装は、あくまで「話者分離付き文字起こし」までです。
ここからさらに発展させる予定です。
- 議事録生成(要約・整理)
- ローカルLLMによる処理
- OpenAI APIとの比較
特に、Qiitaでも触れている通り、
ローカルLLMで議事録生成まで完結させることを目標にしています。
リポジトリ
実装したコードはこちらで公開しています。
おわりに
今回の取り組みは、
「議事録を自動で作れなかった」という小さなトラブルから始まりました。
しかし実際に作ってみると、
単なる文字起こしツールではなく、
複数のAIを組み合わせたパイプライン設計の問題になっていました。
AIを使えば、確かに「すぐ動くもの」は作れます。
ただし、
「使える状態」にするためには設計が必要になる
というのが、今回の一番大きな学びです。
もし同じように、
- AIを業務に取り入れたい
- 自分でツールを作ってみたい
と考えている方がいれば、
まずは小さく作ってみること、
そしてそこから設計を見直していくことをおすすめします。