リアルタイムマルチモーダル対話システムツールキット「Remdis」を試す
GitHubレポジトリ
Remdis: Realtime Multimodal Dialogue System Toolkit
Remdisはテキスト・音声・マルチモーダル対話システム開発のためのプラットフォームです。 このページでは、Remdisを利用するための必要な情報を提供します。
特徴
- 非同期処理に基づくモジュールベースの対話システム
- Incremental Units (IU)を単位としたメッセージングとIncremental Modules (IM)による逐次処理
- Large Language Model (ChatGPT)の並列実行・ストリーミング生成による疑似的な逐次応答生成
- Voice Activity Projection (VAP)によるターンテイキング
- MMDAgent-EXとの連携によるエージェント対話
- Python実装、クロスプラットフォーム (Windows/MacOS/Ubuntu)
- マルチモーダル対応 (テキスト対話/音声対話)
referred from https://github.com/remdis/remdis
動画が公開されている
書籍もある
参考になる
セットアップ
GitHubのREADME+上記の記事を参考に進める。自分はリアルタイムな音声対話を試したいので以下の環境で。
- Ubuntu-22.04
- RTX4090
- Python 3.11.5
- CUDA-12.6
- Dockerインストール済
- CUDA Toolkit、CuDNNも一応インストール済
レポジトリクローン
git clone https://github.com/remdis/remdis && cd remdis
仮想環境作成
python -m venv .venv
. .venv/bin/activate
依存パッケージインストール
pip install -r requirements.txt
上記記事通りにparallel-waveganのインストールでコケる。記載されているとおりにレポジトリクローンからpython setup.py develop
を実行すると、
git clone https://github.com/kan-bayashi/ParallelWaveGAN && cd ParallelWaveGAN
python setup.py develop
numpyのエラー
error: numpy 2.2.2 is installed but numpy<2.2,>=1.24 is required by {'numba'}
ここでNumpyの最新版が入ってしまうのが良くない
"setup": [
"numpy",
"pytest-runner",
],
以下のように書き変える。numpyのバージョンはparallel-waveganのrequirements.txtで指定されているものに変えた。
"setup": [
"numpy==1.26.4",
"pytest-runner",
],
これで再度実行
python setup.py develop
Finished processing dependencies for parallel-wavegan==0.6.2a0
Remdisのレポジトリに戻って、parallel-waveganをrequirements.txtからコメントアウトして、再度パッケージインストール
cd .. # parallel-waveganのクローン先から戻る
sed -i 's/^parallel-wavegan/#parallel-wavegan/' requirements.txt
pip install -r requirements.txt
いろいろwarningが出るけれども一応完了。nnmnkwiiインストール時のエラーはすでに解消されている様子。
config/config.yaml
に、Google Cloud Speech-to-Text API のAPIキー(JSONファイル)のパス、OpenAIの APIキーを設定する。
(snip)
ASR:
buff_size: 10
json_key: ./XXXXXXXXXX.json
language: ja-JP
(snip)
ChatGPT:
api_key: XXXXXXXXXXXX
(snip)
VAP(Voice Activity Projection)のインストール。ちなみにVADは知っていたけど「VAP」ってなんだ?と思って調べてみたのが以下。
ChatGPTによるVADとVAPの違い
Voice Activity Projection (VAP) と Voice Activity Detection (VAD) は音声信号処理の文脈で使われる概念ですが、それぞれ異なる目的やアプローチがあります。
1. Voice Activity Detection (VAD)
目的:
VADは、音声信号内で「音声が話されている部分(音声活動)」と「無音または雑音のみの部分」を区別するための技術です。
主に音声処理や通信、音声認識システムで使用されます。主な特徴:
- 動作: 入力信号を時間ごとに分析し、音声が存在するかどうかを判定します。
- 応用分野:
- 音声認識前処理
- 音声通話の帯域幅削減
- 音声の開始と終了を見つける(トリミング)
- 手法:
- 信号エネルギーやゼロ交差率を閾値と比較
- 周波数領域でのスペクトル分析
- 機械学習やディープラーニングを活用した精密な検出
例:
通信中に静かな部分を除外することで、データ転送効率を向上させる用途など。
2. Voice Activity Projection (VAP)
目的:
VAPは、将来の音声活動を予測する技術または概念を指します。つまり、音声活動が発生する可能性を時間的に前もって推定します。主な特徴:
- 動作: 現在の信号や過去のコンテキストから、次に音声活動が発生する可能性を予測します。
- 応用分野:
- 会話のターンテイキング(誰が次に話すかを予測)
- スマートスピーカーや音声アシスタントのインタラクション最適化
- 音声録音システムでの効率化
- 手法:
- 時系列モデル(例: RNN, LSTM)
- 音声信号に基づくパターン分析
- コンテキストを考慮した予測アルゴリズム
例:
- 会話中に、相手が話し終わるタイミングを予測して適切に応答を開始する機能。
- 音声認識システムが、発話の終了を予測して処理を効率化する。
違いのまとめ
項目 Voice Activity Detection (VAD) Voice Activity Projection (VAP) 目的 現在の音声活動の検出 将来の音声活動の予測 対象 音声の有無を判定 音声活動が発生する可能性を予測 応用 音声認識、音声通話、ノイズ除去 会話のターン管理、スマートスピーカー、応答予測 技術アプローチ エネルギー閾値、スペクトル分析、ML 時系列予測、ディープラーニング、文脈分析
VADは「現在の音声有無を見つける」、VAPは「未来の音声活動を予測する」という違いに注目するとわかりやすいでしょう。どちらも音声インタラクションにおいて重要ですが、用途や設計目標が異なります。
pip install torch==2.0.1 torchvision torchaudio
git clone https://github.com/ErikEkstedt/VAP && cd VAP
pip install -r requirements.txt
pip install -e .
pip install torchsummary
VAPの日本語モデルを解凍
cd ../models/vap # VAPのクローン先から戻る
unzip sw2japanese_public0.zip
Remdisのクローン先に戻る。
cd ../..
テキスト対話
まず、RabbitMQをDockerで起動。最新版はもっと新しいのだが、手順に従うこととする。
docker run -it --rm --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3.12-management
次に、Remdisの各モジュールを起動していく。ターミナルを3つ立ち上げて、Remedisのクローン先ディレクトリに移動し、それぞれで仮想環境をactivate、Remedisのmodules
ディレクトリに移動
cd remedis
. .venv/bin/activate
cd modules
各ターミナルで、それぞれのモジュールを起動する。
tin.py
dialogue.py
tout.py
python tin.py
Type phrase and press Enter for ADD, or just press Enter for COMMIT:
python dialogue.py
Loading prompts[BC] from prompt/text_vap.txt
Loading prompts[RESP] from prompt/response.txt
Loading prompts[TO] from prompt/time_out.txt
python tout.py
(まだ何も出力されない)
では、まずtin.py
を起動したターミナルで、適当になにか入力してみる。入力の仕方は、
- 入力してENTER、ただしこの状態だとまだ入力終了ではない。
- 入力終了したら何も入力されていない状態でENTER、これで送信される
という感じで少しクセがある。
実際にやってみた感じだとこう。
Type phrase and press Enter for ADD, or just press Enter for COMMIT: こんばんは!
[1737978183.2073529] Body: こんばんは!, Update_type: add, ID: dde12a40-dca3-11ef-a2e9-df14eb64fd09
Type phrase and press Enter for ADD, or just press Enter for COMMIT: 今日は寒いですね。
[1737978186.3630457] Body: 今日は寒いですね。, Update_type: add, ID: dfc2ab40-dca3-11ef-a2e9-df14eb64fd09
Type phrase and press Enter for ADD, or just press Enter for COMMIT: 明日はどうなるでしょうか?
[1737978190.7390907] Body: 明日はどうなるでしょうか?, Update_type: add, ID: e25e654c-dca3-11ef-a2e9-df14eb64fd09
Type phrase and press Enter for ADD, or just press Enter for COMMIT:
こんな感じで最後はENTERだけを入力すると、dialog.py
のコンソールには以下のように出力される。
[1737978190.74403] Call ChatGPT: query='こんばんは!今日は寒いですね。明日はどうなるでしょうか?'
[1737978191.09264] Call ChatGPT: query='こんばんは!今日は寒いですね。明日はどうなるでしょうか?'
[1737978191.09359] ********** State: idle -> talking, Trigger: ASR_COMMIT **********
[1737978191.19408] Call ChatGPT: query=None
Resp: Selected user utterance: こんばんは!今日は寒いですね。明日はどうなるでしょうか?
[1737978191.81034] Body: こんばんは, Update_type: add, ID: e301dbbe-dca3-11ef-a2e9-df14eb64fd09
[1737978191.91633] Body: 本当に寒いですね, Update_type: add, ID: e301dbbf-dca3-11ef-a2e9-df14eb64fd09
[1737978192.1601079] Body: 特にこの時期は寒暖差が激しいですよね, Update_type: add, ID: e301dbc0-dca3-11ef-a2e9-df14eb64fd09
[1737978192.3087885] Body: 明日も冷え込むといいですね, Update_type: add, ID: e301dbc1-dca3-11ef-a2e9-df14eb64fd09
[1737978192.4494612] Body: 予報ではどうですか, Update_type: add, ID: e301dbc2-dca3-11ef-a2e9-df14eb64fd09
[1737978192.5657961] Body: {'action': 'nod'}, Update_type: add, ID: e301dbc3-dca3-11ef-a2e9-df14eb64fd09
End of selected llm response. Waiting next user uttenrance.
そしてtout.py
のコンソール
IN(asr):[1737978183.2073529] Body: こんばんは!, Update_type: add, ID: dde12a40-dca3-11ef-a2e9-df14eb64fd09
IN(asr):[1737978186.3630457] Body: 今日は寒いですね。, Update_type: add, ID: dfc2ab40-dca3-11ef-a2e9-df14eb64fd09
IN(asr):[1737978190.7390907] Body: 明日はどうなるでしょうか?, Update_type: add, ID: e25e654c-dca3-11ef-a2e9-df14eb64fd09
IN(asr):[1737978191.0893066] Body: , Update_type: commit, ID: e25e654d-dca3-11ef-a2e9-df14eb64fd09
IN(dialogue2):[1737978192.5657961] Body: {'action': 'nod'}, Update_type: add, ID: e301dbc3-dca3-11ef-a2e9-df14eb64fd09
IN(dialogue):[1737978191.81034] Body: こんばんは, Update_type: add, ID: e301dbbe-dca3-11ef-a2e9-df14eb64fd09
IN(dialogue):[1737978191.91633] Body: 本当に寒いですね, Update_type: add, ID: e301dbbf-dca3-11ef-a2e9-df14eb64fd09
IN(dialogue):[1737978192.1601079] Body: 特にこの時期は寒暖差が激しいですよね, Update_type: add, ID: e301dbc0-dca3-11ef-a2e9-df14eb64fd09
IN(dialogue):[1737978192.3087885] Body: 明日も冷え込むといいですね, Update_type: add, ID: e301dbc1-dca3-11ef-a2e9-df14eb64fd09
IN(dialogue):[1737978192.4494612] Body: 予報ではどうですか, Update_type: add, ID: e301dbc2-dca3-11ef-a2e9-df14eb64fd09
IN(dialogue):[1737978192.5691576] Body: , Update_type: commit, ID: e301dbc4-dca3-11ef-a2e9-df14eb64fd09
OUT(tts): [1737978197.8162038] Body: , Update_type: commit, ID: e6964d14-dca3-11ef-a2e9-df14eb64fd09
なるほど、この入力の仕方は、実際の音声での入力(ASR)のような段階的タイミングを模倣しているのではないかと思うが、ただ自分の環境だと、うまくいくときもあれば、なんか会話のタイミングが1ターン分ズレたような感じの会話になってしまう場合もある。ちょっとよくわからないけど、tout.py
が実際の会話のタイミングに近いものだと思うので、こちらを見ながら入力するのが良いのかなという気はする。
あと、何も入力しない状態が長く続くと、入力後等にエラーでコケる。
pika.exceptions.StreamLostError: Transport indicated EOF
とか。
RabbitMQのターミナルを見ると以下のような出力が見えるので、おそらく何らかのタイムアウトで接続状態がおかしくなってしまっているのだろうと思う。おそらく60秒以内に何かしらやり取りはしたほうが良さそうに思う。
2025-01-27 11:57:42.573634+00:00 [error] <0.1992.0> closing AMQP connection <0.1992.0> (172.17.0.1:45314 -> 172.17.0.3:5672):
2025-01-27 11:57:42.573634+00:00 [error] <0.1992.0> missed heartbeats from client, timeout: 60s
2025-01-27 11:57:42.575479+00:00 [error] <0.2005.0> closing AMQP connection <0.2005.0> (172.17.0.1:45318 -> 172.17.0.3:5672):
2025-01-27 11:57:42.575479+00:00 [error] <0.2005.0> missed heartbeats from client, timeout: 60s
2025-01-27 11:57:44.106664+00:00 [error] <0.2113.0> closing AMQP connection <0.2113.0> (172.17.0.1:45388 -> 172.17.0.3:5672):
2025-01-27 11:57:44.106664+00:00 [error] <0.2113.0> missed heartbeats from client, timeout: 60s
こうなると全部のモジュールを立ち上げ直すしかなさそう。
ただログを見てると面白い。
[1737979529.6577616] Body: こんにちは, Update_type: add, ID: 006d2eb2-dca7-11ef-a2e9-df14eb64fd09
[1737979529.9235475] Body: 最近はどんなことに興味を持っていますか, Update_type: add, ID: 006d2eb3-dca7-11ef-a2e9-df14eb64fd09
[1737979530.080141] Body: {'action': 'nod'}, Update_type: add, ID: 006d2eb4-dca7-11ef-a2e9-df14eb64fd09
End of selected llm response. Waiting next user uttenrance.
[1737979530.0850954] Body: , Update_type: commit, ID: 006d2eb5-dca7-11ef-a2e9-df14eb64fd09
[1737979532.66580] ********** State: talking -> idle, Trigger: TTS_COMMIT **********
[1737979544.27708] Call ChatGPT: query='最近は競馬に興味を持っているよ。'
[1737979547.81185] Call ChatGPT: query='最近は競馬に興味を持っているよ。'
[1737979547.81641] ********** State: idle -> talking, Trigger: ASR_COMMIT **********
Resp: Selected user utterance: 最近は競馬に興味を持っているよ。
[1737979547.819853] Body: 競馬に興味を持っているんですね, Update_type: add, ID: 0b407ca4-dca7-11ef-a2e9-df14eb64fd09
[1737979547.8246722] Body: どの馬やレースが特に好きですか, Update_type: add, ID: 0b407ca5-dca7-11ef-a2e9-df14eb64fd09
[1737979547.8265822] Body: {'expression': 'joy', 'action': 'nod'}, Update_type: add, ID: 0b407ca6-dca7-11ef-a2e9-df14eb64fd09
End of selected llm response. Waiting next user uttenrance.
{'action': 'nod'}
とかって相槌のことだよね。{'expression': 'joy', 'action': 'nod'}
とかもある。
音声対話
とりあえずまだ成功できていない
- デバイスが正しく認識されていない
- USBマイクもヘッドフォンもデスクトップでは普通に使える(J-Moshiを動かせている)
- のだが、
input.py
とoutput.py
ではどうもデバイスを認識できていないように見える
- CUDA-11.Xが多分必要
-
audio_vap.py
を実行すると、OSError: libcudart.so.11.0: cannot open shared object file: No such file or directory
になる
-
あたりで躓いている。Linuxのオーディオ難しいのと、あとCUDAのバージョンダウンはやりたくない思いが・・・
GPU効かせたかったんだけど、Macでやり直してみるかなーと思い直している・・・
とりあえずMacでやり直してみたらいけたー。
基本的に上の手順で進めればいいけど、以下だけが異なる。
ParallelWaveGANインストール時にnumpyをダウングレード
ParallelWaveGANインストール時にnumpyのバージョンでエラーになるので、
error: numpy 2.2.2 is installed but numpy<2.2,>=1.24 is required by {'numba'}
setup.pyを以下のように変更したが、
"setup": [
"numpy==1.26.4",
"pytest-runner",
],
同じエラーになるので、手動でダウングレードした。
pip install -U "numpy==1.26.4"
ParallelWaveGANインストール時に不足しているパッケージ
ParallelWaveGANのインストール時にwarningがいくつか出る。バージョンミスマッチはとりあえず無視してもいいと思ったが、以下のパッケージは「必要なのにインストールされていない」と出たので、手動でインストールした。
pip install "threadpoolctl>=3.1.0"
pip install "llvmlite<0.45,>=0.44.0dev0"
audio_vap.py
がエラー
どうやらtorchとtorchaudioのバージョン不一致。インストール作業の流れの中でtorchのバージョンが2.5.1に上がってしまっていたみたい。
OSError: dlopen(/Users/kun432/work/remdis/.venv/lib/python3.11/site-packages/torchaudio/lib/libtorchaudio.so, 0x0006): Symbol not found: __ZN3c1015SmallVectorBaseIjE8grow_podEPvmm
Referenced from: <F096D2C3-ADC0-3EF4-ACF6-E3075A1DF8EE> /Users/kun432/work/remdis/.venv/lib/python3.11/site-packages/torchaudio/lib/libtorchaudio.so
Expected in: <BA9C42A5-EA1D-3784-80E1-73FBFDE05847> /Users/kun432/work/remdis/.venv/lib/python3.11/site-packages/torch/lib/libc10.dylib
pip show torch torchaudio
Name: torch
Version: 2.5.1
(snip)
---
Name: torchaudio
Version: 2.0.2
(snip)
VAPの説明には以下とある
# pytorch, torchvision, torchaudioのインストール # torchは2.0.1以下 (= pyaudio 2.0.2)でのみ動作
以下のようにダウングレードしたらエラーが出なくなった。
pip install -U torch==2.0.1 torchvision==0.15.2 torchaudio==2.0.2
なお、自分が試した感じだと、audio_vap.py
は少し不安定な動きになることが多かった。text_vap.py
のほうが安定して動作していて、特に問題もないように思えた。(このモジュールの機能をきちんと理解できていないかもしれない)
ということで起動
ターミナルを6つ開いて、以下をそれぞれ実行する。
マイクからの音声入力モジュール
python input.py
音声認識(Automatic Speech Recognition)モジュール
python asr.py
テキストによる音声活動予測(Voice Activity Projection)モジュール
python text_vap.py
対話管理モジュール
python dialogue.py
音声合成(Text-to-Speech)モジュール
python tts.py
スピーカーへの音声出力モジュール
python output.py
実際に動かしてみたものがこちら
会話のリズムがとてもクイックに実現できている。会話が噛み合わないこともまあまああるのだけども、それでも音声チャットボットによくあるユーザの発話→チャットボットからのレスポンスまでがとても早いのがわかる。
なお、time_out.py
もあわせて起動しておくと、こちら側の発話がなければボット側から話しかけてくれる。
python time_out.py
まとめ
自分でもSTT・LLM・TTSを組み合わせて音声チャットを作ってみたりしたのだが、やはりどうしてもレイテンシーがあるので会話のリズムは悪くなる。それをクリアするための工夫もいろいろある。
ただ、これをやっていくとスレッドや非同期などを駆使する形になり、コードは複雑化する。
ある程度の複雑性は致し方ないとしても、Remdisのように、
- モジュールを分割、並列で実行
- RabbitMQを通じて非同期にやりとり
という実装は1つの参考になった。自分はこの手のpub/subやジョブキューみたいなものの経験がないのでとても新鮮でもあるし。RabbitMQについては以下で色々試してみたが、うまく使えれば・・・という期待感を感じている。(これはこれで考えることは多そうだけれども)
各モジュールの実装も、あまりPython力の高くない自分にはとても参考になるので、いろいろ読み解いてみたい。