社内面談動画を文字起こしするツールを内製した話
はじめに
atama plus株式会社でcorporate engineerとして活動しているzenです。
この記事はatama plus Advent Calendar 2023の11日目です。
普段は社内のCorporateやビジネスチームの方に向けて業務効率化系のツールを開発している私ですが、今回は流行りのLLMを利用した社内向けツールをSREの方々と開発した話をします。
先に要旨をお伝えしますと、なるべくコストを抑えて録画した動画を文字起こしするツールを内製したというお話です。
こんな方向けの記事
- 社内外の日々のミーティング等のキャッチアップ工数削減や分析のために会話内容を文字起こししたい方
- なるべくコストを最小限にして音声認識サービスを使ってみたい方
製作背景
atama plusでは、新しい機能やプロダクトなどの開発にあたって、生徒や保護者、先生といった「現場」の声を聞くことを大切にしています。コロナ禍を経てオンラインコミュニケーションが普及したこともあり、社外の方とオンラインでインタビューする機会も増えていました。
動画でしか得られない相手の表情や言葉の機微などはある一方で、インタビュー内容をキャッチアップするためにメンバーが各々全ての録画を見ていては、チームの生産性が下がってしまいます。そのため、実施したインタビューの内容を比較的短時間で把握できるように「動画内の会話を文字起こしできないか」という声があがっていました。
解決に向けたアプローチ
このアイディアを実現するために、まずは国内外の音声認識→文字起こし系のSaaSを調査することから始めました。
しかし、それらのサービスは文字起こし以外の機能も豊富であることが多く、今回の我々が実現したい要件に対してtoo muchであることがわかりました。また、これらのサービスは基本的に変換した時間に対して課金されていくため、コスト面からも断念することにしました。
そこで、現在世の中には天下のAmazon、Google、Microsoft等から音声認識系のAPIが公開されており、SaaSに比べ安価で使えることを踏まえ、SaaSを使わずに文字起こしするツールを社内自製する方針に決定しました。
開発ツール解説
アーキテクチャ
文字起こしツールを利用するユーザーがGoogle Drive / Google Sheetsでの業務を普段行っているため、利用者のI/Fは運用親和性の高いGoogle Drive / Google Sheetsで実装するようにしました。
それに伴い、認証情報の取り回しが容易になるため、バックエンドのスタックもGoogle系で全て実装しました。
- 利用者I/F
- Sheets
- ユーザー側のエントリーポイント
- Drive
- 動画管理
- 文字起こしファイルアウトプット
- Sheets
- バックエンド
- Google Apps Script (GAS)
- Cloud Functionsのキッカー
- Cloud Functions
- バックエンドの処理全般
- Cloud Strage (GCS)
- 音声ファイル管理
- Cloud Speech-to-Text V2
- 音声データから文字データへの変換
- Pub/Sub
- 非同期処理のブローカー
アーキテクチャの全体像
- 非同期処理のブローカー
- Google Apps Script (GAS)
実装の解説
1. 動画指定〜Cloud Functionsへのリクエストまで(図中1, 2)
前述の通り、I/Fはメンバーが使い慣れているSheetsを選択しました。以下のように該当する面談を行った人のIDと動画URLの2つを記入してもらい、文字起こししたい行にチェックを入れて実行ボタンを押してもらうことでリクエストパラメータ付きでCloud FunctionsがKickされます。
I/FのSheets
2. 動画データ取得〜文字起こしAPIのレスポンスをGCSに格納するまで(図中3, 4)
ハンドラーとなる関数に以下のような順序でロジックを組んでいます。
-
googleapiclient
のMediaIoBaseDownload
を用いてDrive内の動画ファイルを取得 -
moviepyの
VideoFileClip
にて動画内の音声データを抽出 - 音声データをGCSにアップロードし格納先のpathを取得
- google.cloud.speech.v2でGCS内の音声データをjson形式のデータに変換しGCSに格納
Speech-to-Text V2 APIでは、1分以上の音声ファイルに対してテキストデータ変換を行う場合、GCSからの音声データ入力が必要なため、一度音声データをGCSに格納する構成としております。
補足として、Speech-to-Text V2 は、対象の言語と実行リージョンごとにサポートされているモデルや、モデルにより利用できる機能に制約があります。
今回は検証を重ねた結果、モデルとしてlong
を使用することとしました。
また、modelのパラメーターも今回は動画の時間軸と会話内容でマトリクスにした情報を得るためにenable_word_time_offsets
を使用しています(参考:RecognitionFeatures)。
本当は話者分離のためにdiarization_config
もパラメータに含めたかったのですが、開発時点ではGoogleカスタマーサポートから「日本語は非対応」と情報を得たので断念しています
(さらに補足:サポート問い合わせ時は上記のモデル一覧ページ上で日本語でもdiarizationが可能と記載されていましたが、これはドキュメントのミスだったようで、サポートから回答もらった後、ドキュメント側も修正されていました)。
どのmodelやパラメーターが最適かは元の音声ファイル及び会話の内容に依存するので各使用場面でチューニングが必要かと思います。
文字起こししたレスポンスのサンプルは以下のようになります。今回の例ではenable_word_time_offsets
を有効にしているため、文節で区切られたtranscript結果に加えて、wordsというリストに単語ごとのOffsetが出力されるようになっています。指定したパラメータによって得られるjsonのデータ形式は異なるので欲しい出力結果に合わせてチューニングしていく必要があります。
{
"results": [
{
"alternatives": [
{
"transcript": "エラー ディフェンスサービスについて紹介します",
"confidence": 0.9178572,
"words": [
{ "startOffset": "4s", "endOffset": "4.600s", "word": "▁エ" },
{ "startOffset": "4.600s", "endOffset": "4.700s", "word": "ラー" },
{ "startOffset": "4.700s", "endOffset": "4.800s", "word": "▁" },
{ "startOffset": "4.800s", "endOffset": "4.800s", "word": "ディ" },
{ "startOffset": "4.800s", "endOffset": "4.900s", "word": "フェ" },
{ "startOffset": "4.900s", "endOffset": "5s", "word": "ンス" },
{ "startOffset": "5s", "endOffset": "5.600s", "word": "▁サービス" },
{ "startOffset": "5.600s", "endOffset": "5.900s", "word": "▁に" },
{ "startOffset": "5.900s", "endOffset": "5.900s", "word": "▁つい" },
{ "startOffset": "5.900s", "endOffset": "5.900s", "word": "▁て" },
{ "startOffset": "5.900s", "endOffset": "6.300s", "word": "▁紹介" },
{ "startOffset": "6.300s", "endOffset": "6.400s", "word": "▁し" },
{ "startOffset": "6.400s", "endOffset": "6.500s", "word": "▁ます" }
]
}
],
"languageCode": "ja-jp"
},
3. GCS内の文字起こしデータ取得〜ユーザー側に結果を返すまで(図中5, 6)
2.とは別のハンドラーとなる関数が存在し、その中で以下のような処理を行っています。
- 2.で格納したGCSの文字起こし結果をダウンロード(json形式)
- ダウンロードしたデータをjson→csv形式にコンバート
- 対象となるDriveのディレクトリにcsv形式のファイルを格納
- 格納したファイルURLをユーザー側のSheetsに出力
このCloud Functionsは、前段のSpeech-to-Text V2の結果ファイルがGCSに格納されたことをトリガーとするPub/Subから起動されるようになっています。
このようにイベント駆動型の非同期処理とした理由は、Speech-to-Text V2での文字起こし実行時のコンピュートリソースコスト削減を目論むためです。元の音声データサイズにもよりますが、Speech-to-Text V2 APIでのテキストデータ変換処理は、比較的長い時間かかり、同期的に処理するようにすると、その待ち時間の間、ずっとCloud Functionsが起動し続けることとなり、コスト効率が悪くなってしまいます。
そのため、前段のCloud FunctionsではSpeech-to-Text V2 APIのキックまでの責務とし、後段のCloud Funcsionsはその結果ファイルを受け取ったPub/Subからイベント駆動で起動する構成とすることで、無駄にCloud Functionsが待機してリソースコストを消費する状況を抑制しました。
実行結果
文字起こしの精度
発話者のイントネーションや滑舌で精度が変化するので評価は難しいところですが、個人的な所感としては実動画と比べると100点満点中80~90点くらいの精度です。「どんなトピックの会話がされているか」をざっくり把握するには十分な精度かと思います。
※具体的な文字起こし結果は個人情報の観点で割愛させていただきます。
ランニングコスト
- 30分間の動画を文字起こしするのにかかるクラウドリソース費用は約30円(60円/h)程度だった。
- 仮に月間合計1,000時間の面談動画を全て文字起こししたとしても6万円程度。
- 検討していた一番安いSaaSを使ったとしても700万円掛かる見込み。
処理時間
同じ時間の長さの動画でも会話の発話量の差で処理時間にズレがあるので動画時間に対して線形的な値ではなさそうでした。以下、実際に試した際の参考値です。
動画名(仮) | 動画時間 | 実行から文字起こし完了までの時間 |
---|---|---|
動画A | 15分 | 3分 |
動画B | 15分 | 5分 |
動画C | 30分 | 15分 |
社内トライアルした話と今後の展望
現場の声
執筆時点ではまだこのツールは社内でトライアルをしている状況です。トライアルをしてくれたビジネスチームのメンバーからは以下の声をもらっています。
<現場の声>
- 今までわざわざ動画を見に行かないといけなかったところが、文字起こしをしていると文字列検索できるのは嬉しい。
- 文字起こししたファイルをフィルタして、あとは必要な部分だけ動画を見にいけば良いから無駄が減る。
個人的には、ミニマムに内製したツールとしてはなかなか良い声を多くもらうことができているのではないかと実感しています。
課題感と今後の展望
文字起こしの精度改善関連
- 現在の文字起こしの精度では「なんとなく◯◯について話しているな」というトピックが読み取れる程度になっているので改善の余地あり。
- 発話者のイントネーションや滑舌で精度が変化するので動画間での差はあり
- 音声認識系のサービスの技術的な発展と共に精度は向上してきそうだが現状でのパフォーマンチューニングの検討余地はまだ未検証
- 「あー」「えっと」等のノイズが含まれているのでフィラー除去を取り入れたい。
- OpenAI APIなどのLLMによる処理を間に挟めばそもそも動画の要点だけ掴むことも可能かもしれない
- 2者間の動画であることが多いので話者分離されることがより望ましい。
まとめ
今回は社内の「動画内の会話を文字起こしできないか」という要望に対して、コストをなるべく抑えて実現しようと思った結果動画文字起こしツールを自作してみた話をしました。まだまだ改善余地も大きいツールですが、もし日々のミーティング動画などを文字起こししたい方がいればぜひご参考ください!
ここまで御覧いただきありがとうございました。
明日は「製造業の生産技術の人がソフトウェア開発チームのQAになった話」です。どうぞお楽しみに!
Discussion