こんにちは。AIエンジニアリンググループの矢澤です。 先日、実家の近所でかき氷を食べました。 お店でちゃんとしたかき氷を食べたのはおそらく初めてで、屋台で食べるかき氷も美味しいですが、食感などが全然違って新鮮な経験でした。 かき氷だけでなく、大人になった今だからこそ、日本の伝統的な和菓子や料理を改めて食べてみたいと思いました。
業務では、最近チャットボットなどLLM関連のサービスを開発することが多いですが、AIの回答をテキスト表示するだけでなく音声で再生できるとユーザー体験が向上するのではと考えています。 特に最近はキャラクター音声の品質が高く、音声合成技術を活用することで長い文章であっても流暢に話すことができます。 世の中には様々なキャラクターがいますが、私が特に好きなキャラクターとして「ずんだもん」がいます。 以下はGPT-4oによるずんだもんの説明です。
ずんだもんは、ずんだ餅をモチーフにしたキャラクターで、主にインターネットやソーシャルメディアで人気があります。ずんだ餅は、枝豆をすりつぶした「ずんだ」を餅にかけた東北地方の伝統的な和菓子です。 ずんだもんというキャラクターは、可愛らしい外見とユニークな言動で多くの人々に愛されています。さまざまなメディアや商品で見かけることができますが、具体的な出所や詳細はキャラクターによって異なる場合があります。
ずんだもんの音声は、VOICEVOXというサービスを通じて簡単に生成できるようになっていて、実際にYouTubeの動画などでも使われています。 VOICEVOXは無料のテキスト読み上げソフトです。1 単純な音声合成に加え、イントネーションの細かな調整や歌声合成なども可能です。 PC上で動かせるGUIツール(エディター)の他、サーバー(エンジン)に直接アクセスするためのAPIや簡易的なスマートフォン版などもあり、様々な方法で利用できるようになっています。 また、VOICEVOXではキャラクターごとに音声のスタイルがあり、ずんだもんの場合は「ノーマル」、「あまあま」、「ツンツン」など計8種類から選ぶことができます。 ユーザーへの返答を再生する際、AIによって上記のスタイルを自動で推定できれば、より会話内容に合った声で音声を再生できると思われます。
そこで今回は、VOICEVOXとAzure OpenAI(以下AOAI)を使ってスタイルを考慮した音声合成を行う、「ずんだもんボット」を作ってみました。
全体構成
開発したシステムの全体像を以下に示します。
基本的には、通常のチャットボットと同様にユーザーからの入力を受け付け、AOAIのLLMモデル(今回はGPT-4o)で回答を生成して返します。 本システムがチャットボットと異なる点は、生成した回答をそのままテキスト出力するのではなく、VOICEVOX APIを使って音声に変換し、スタイルを判定した上で再生している点です。 このとき、スタイルの推定方法として様々な方法が考えられますが、今回はこちらでもAOAIのGPTモデルを使って簡易的に推定するようにしました。2 またアプリ全体は、PythonのWEBアプリ開発用ライブラリであるStreamlitを活用して、スピーディーに実装しています。
以降で、各処理の詳細について説明していきたいと思います。
VOICEVOXサーバーの構築
初めに、VOICEVOXのバックエンド処理を行うためのサーバー(エンジン、コア)を構築します。 構築といってもそんなに難しいことは無く、VOICEVOX ENGINEのGitHubページなどを参考にすれば、簡単に立ち上げることが可能です。 特に、公式のDockerイメージが用意されているので、たった2行のコマンドでサーバーを構築して起動することができます。3
VOICEVOXのDockerイメージにはCPU版とGPU版があります。 GPU版の方が処理が早いと思われますが、今回開発したアプリではCPU版でも十分な速度が出ることが確認でき、実験用途であれば問題ないと感じました。
クライアントアプリの実装
VOICEVOXのサーバーが起動できたので、続いてユーザーがずんだもんとチャットで会話できるようにクライアント側のアプリを実装します。 最終的な画面イメージは以下のようになります。
1. AOAIでの回答生成
初めにユーザーからの入力に対して、AOAIを使ってずんだもんによる回答を生成します。 上記でも示した通り、GPT-4oの学習データにはずんだもんに関する情報が含まれているようで、「ずんだもんっぽく答えて」といった指示でもある程度想定した文章を返してくれることが期待されます。 ただし、一般的にLLMではプロンプトを適切に与えることで生成内容の妥当性が高まるため、今回は以下のような少し詳しめのシステムプロンプトを使うことにしました。
あなたは「ずんだもん」として知られているキャラクターです。 ユーザーの入力に対して、ずんだもんに成りきって返答してください。 以下はずんだもんの説明です。 ・ずんだ餅をモチーフにしたキャラクターで、主にインターネットやソーシャルメディアで人気があります ・ずんだ餅は、枝豆をすりつぶした「ずんだ」を餅にかけた東北地方の伝統的な和菓子です ・ずんだもんというキャラクターは、可愛らしい外見とユニークな言動で多くの人々に愛されています ・会話の仕方として、「~のだ!」や「~なのだ!」という語尾が特徴的です
使用したAOAIのパラメーターとその理由についても、記載しておきます。
- model="gpt-4o":(GPT-3.5 Turboよりも)高品質な文章を生成するため
- temperature=0.7:QAチャットボットなどで一般的な値
- max_tokens=128:文章が長いと音声合成に時間がかかるため、小さめの値を指定
2. 文章のスタイル推定
次に、ずんだもんの声色を決めるため、生成した文章のスタイルを推定します。 回答生成と同様にGPT-4oを使用して、生成文章が対象スタイルのどれに当てはまるかを判定するようにしました。
システムプロンプトの内容は以下の通りです。
ユーザーが入力したテキストは、スタイルリストのどれに最も当てはまりますか。 回答はスタイル名のみを単語として返してください。 スタイルリスト:['ノーマル', 'あまあま', 'ツンツン', 'セクシー', 'ささやき', 'ヒソヒソ', 'ヘロヘロ', 'なみだめ']
上記のスタイルリストはVOICEVOXのHPなどからも確認できますが、キャラクター情報API(後述)でスタイルIDと併せて取得することができます。
3. スタイルIDの取得
生成文章のスタイルを単語として推定できるようになりましたが、VOICEVOXで音声合成する際には対応するスタイルIDをパラメーターとして指定する必要があります。 スタイルIDと単語の対応は、キャラクター情報取得用のAPI(http://{API_HOST}:{API_PORT}/speakers)で確認することができます。
ここで、API_HOSTはVOICEVOXサーバーが動作しているホストのIPアドレス、API_PORTはポート番号です。 レスポンスとして、ずんだもん以外も含む全キャラクターの情報が返ってくるので、キャラクター名(nameキー)がずんだもんのデータのみを抽出します。 その後、配列の要素を調べて推定したスタイルに対応するIDを取得します。
4. VOICEVOXによる音声合成
続いて、VOICEVOX APIを使って生成文章を音声に変換します。 VOICEVOXでの音声合成は2段階のステップとなっており、初めに文章からクエリを生成した後、このクエリを使用して最終的な音声を出力します。
各ステップのAPI URLは以下のようになっています。
- クエリ生成:http://{API_HOST}:{API_PORT}/audio_query
- 音声合成:http://{API_HOST}:{API_PORT}/synthesis
パラメーターはどちらも共通で、音声に変換したい文章とキャラクターのスタイルIDを指定します。 先ほどAOAIで生成したずんだもんによる回答と、推定した回答のスタイルIDを、こちらに入力することになります。
5. Streamlitでの音声再生
Streamlitで音声を再生する際には、streamlit.audio関数にオーディオデータを与えるだけで再生用のUIを作成できます。 ただし、自動再生用のウィジェットは用意されていないようで、HTMLの音声タグを活用して実現する必要があるようです。 今回の趣旨ではないので詳細は割愛しますが、自動再生可能なアプリなどを開発したい方は調べてみてください。
以上で、ユーザーの入力を基にスタイル付きのずんだもん音声を生成できるアプリの完了です。 参考までに、ずんだもんのスタイル情報取得とチャットの実行部分のソースコードを載せます。
# 処理2, 3で使用 speakers = requests.get( f'http://{API_HOST}:{API_PORT}/speakers', ) zunda_styles = [s for s in speakers.json() if s["name"] == "ずんだもん"][0]["styles"] style_names = [style["name"] for style in zunda_styles] def chat(user_input): # 処理1 response = _respond_w_aoai(user_input) # 処理2 text_style = _classify_style(response, style_names) st.write(f"スタイル:{text_style}") # 処理3 style_idx = [style["id"] for style in zunda_styles if style["name"] == text_style][0] # 処理4 params = ( ('text', response), ('speaker', style_idx), ) query = requests.post( f'http://{API_HOST}:{API_PORT}/audio_query', params=params ) voice = requests.post( f'http://{API_HOST}:{API_PORT}/synthesis', headers = {"Content-Type": "application/json"}, params = params, data = json.dumps(query.json()) ).content # 処理5 st.audio(voice) st.write("VOICEVOX:ずんだもん")
アプリの動作確認
クライアントアプリが実装できたので、実際にずんだもんと話してみます。4
まずは、テストがてら挨拶を行ってみました。
(ノーマル)こんにちはなのだ!今日はどんなお話をするのか楽しみなのだ!
通常の会話なので上記のスタイルは「ノーマル」と推定されています。
それでは、「ずんだ餅って美味しいよね」と言ったらどうなるでしょうか。
(あまあま)そうなのだ!ずんだ餅はとっても美味しいのだ!枝豆の甘さともちもちのお餅が絶妙にマッチして、まるで天国の味なのだ!食べたことがない人にはぜひ一度試してほしいのだ!
今度は「あまあま」のスタイルで返事が生成されました。
GPTでの簡易的な推定なので、応答文章によっては人間の感覚とは異なるスタイルに分類されることもありますが、ある程度想定した動きになっているようです。 何より、GPT-4oを使っているため生成文章自体の品質が高く、ユーザーの気分に合わせてずんだもんがフレンドリーに会話してくれる感じがしました。
まとめ
今回は、VOICEVOX+AOAIでずんだもんボットを実装し、生成された音声を確認しました。 キャラクター音声による返答は、テキストのみのチャットボットよりもユーザーフレンドリーだと感じ、楽しく会話できることが分かりました。 音声合成の技術自体は目新しいものではないですが、オンライン・オフラインでの顧客対応や自分のお気に入りのキャラクターと話せる学習用アプリなど、様々なサービスへの活用が考えられるため、今後もチェックしていきたいです。 また音に関するAI技術には、音声認識や話者推定、声色による感情分析、音楽分析・生成など他にも色々なものがあり、さらに画像など他のモーダルと組み合わせたマルチモーダル分析も近年発展しています。 今後は、そのようなLLM以外のAI技術も活用しながら、よりユーザーにとって価値のあるサービスに繋げていきたいです。
- VOICEVOXは、オープンソースで商用・非商用問わず使って良いとされていますが、VOICEVOX自体の規約に加えてキャラクターボイスごとの利用規約も別途定められています。例えば、ずんだもんの利用規約は「ずんだもんの音声ライブラリを用いて生成した音声は、"VOICEVOX:ずんだもん"とクレジットを記載すれば、商用・非商用で利用可能です。」となっています。今回は個人での実験用途なので厳密に気にする必要は無いと思いますが、アプリ画面の合成音声の下に上記のクレジットを記載するようにしました。↩
- 本記事のようにGPTを使ってスタイルを推定する以外にも、テキスト分類用のLLMモデルを追加学習する方法や、各スタイルの単語と生成文章の埋め込みベクトルの類似度を比較する方法など、様々な方法が考えられます。また、今回は社内ネットワークの都合で試すことはできませんでしたが、DatabricksのGen AIなど外部のツールを活用することで、より簡単に実現することも可能かと思います。↩
- ただし今回はAzure VM上で開発したため、クライアント側からFW経由でアクセスできるように、ポートフォワード時のホスト側ポート番号を変更するなどの対応を行いました。↩
- 実際にはスタイル付きの音声が出力されますが、本ブログでは音声データが共有できないようなので、参考として生成された文章とスタイル名を記載しています。↩