ChatGPTが出たのでVRChatに音声対話型AI Botを作った。

どうも、GesonAnkoです。ChatGPTが出たので GPTベースであるOpenAIの言語処理エンジンAPIとその他諸々を組み合わせて VRChatに音声対話型 ChatBotを作りました。  

https://twitter.com/GesonAnkoVR/status/1605205586031030278?s=20&t=2ztHKbMyOZOE0s5YEUpptQ

今回はこれについて書いていこうかなと思います。なお、ソースや詳しい使い方はGithubに公開していますので、詳細はそれを参照願います。  


https://github.com/Geson-anko/vrchatbot  

# インストール    
## 環境  
- Windows 10 64bit
- Python 3.9  
- Miniforge3 https://github.com/conda-forge/miniforge
- NVIDIA GeForce RTX Graphics card
- CMake https://cmake.org/download/

## 仮想オーディオインターフェースをインストール  
VB Cableなどを用いるとVRChat上にマイクを接続する行為を簡単にすることができます。  
https://vb-audio.com/Cable/

## コマンド

依存関係をインストールします。
```sh
pip3 install torch torchvision torchaudio --extra-index-url https://download.pytorch.org/whl/cu117

pip install git+https://github.com/openai/whisper.git@main
```

このリポジトリをクローンし、プロジェクトのルートフォルダで次のコマンドを実行してください。

```sh
pip install .
```

# 使い方  

## OpenAIのAPI KEY 取得
[OpenAIのAPIKEYをここから取得してください](https://beta.openai.com/account/api-keys)  
取得したAPI KEYは `data/API_KEY.txt` を作成しその中に書き込んでください。  


## オーディオデバイスの確認  
次のコマンドを実行して、利用するスピーカとマイクを探します。    

```sh
python -m vrchatbot audio-devices
```

* output  
```
--- Microphones ---
Index: 0, Name: CABLE Output (VB-Audio Virtual Cable)
Index: 1, Name: ヘッドホン (Oculus Virtual Audio Device)

--- Speakers ---
Index: 0, Name: Name: CABLE Input (VB-Audio Virtual Cable)
Index: 1, Name: ヘッドホン (Oculus Virtual Audio Device)
```

使用するマイクとスピーカの名前を[`botconfig.toml`](/botconfig.toml)の中の `mic_index_or_name`および`speaker_index_or_name`にしてしてください。Indexによる指定も可能ですが、よくその値は変更されるため名前による指定が望ましいです。  

VRChatに実装する場合は、 マイクにループバックとしてスピーカーの名前を指定し、スピーカーに`CABLE Input (VB-Audio Virtual Cable)`といった仮想オーディオインターフェースを指定すると良いでしょう。そしてVRChat上のマイクに`CABLE Output (VB-Audio Virtual Cable)`などを指定することによって他のプレイヤーと対話できるようになります。  


## 実行  
それでは実行してみましょう!  
```sh
python -m vrchatbot run
```

* output   
```
Setting up...
Ready.
Recongnized: <話した声>

Responce: <AIのレスポンス>

...
```

### テキストチャットのみのモード  
音声認識を使用せずに、テキストチャットのみも使用できます。  

```sh
python -m vrchatbot chat
```  

* output

```
Setting up...
Ready.
人間: こんばんわ
人工知能:   「こんばんわにゃあ」
人間: <入力待ち>  
...
```  


### 文字起こしのみのモード
言語処理を行わず、音声認識モデルのみを使用することもできます。  

```sh
python -m vrchatbot recognize 
```

* output

```
Setting up...
Ready.
ja: 文字認識は始まっています。
ja: このようにして文字を認識できます。
...
```

# 仕組み   
みなさん一番最初に気になるのが仕組みでしょう。一番シンプルなシーケンシャルな対話アルゴリズムを採用しました。  

```mermaid  

graph TD
    
    A["音声録音(一定時間 無音が続くまで)"]
    B["文字起こし"]
    C["言語モデル応答"]
    D["音声合成"]
    E[Play Sound]  

    A --> |Audio| B
    B --> |Input Text| C
    C --> |Responce Text| D
    D --> |Speech Data| E
    E --> |roop| A

```

この方式なので、Botが喋り終わってから話す必要があったり、録音タイミングが絶妙にわかりにくかったりしますが、そこはご愛嬌ということで今回は済ませました。

main関数にはこのように定義されています。詳細はGithubリポジトリ内の`vrchatbot/__main__.py`を参照ください。　

```python  
def main(args, config: dict) -> None:
    print("Setting up...")
    recorder = Recorder(**config["Recorder"])
    speech_recognizer = SpeechRecongition(
        options=DecodingOptions(**config["DecodingOption"]), **config["SpeechRecognition"]
    )
    chatbot = ChatBot(**config["ChatBot"])
    speaker = TextSpeaker(**config["Speaker"])
    print("Ready.")

    log_file_name = datetime.now().strftime("%Y-%m-%d %H-%M-%S.log")

    with open(os.path.join(args.log_dir, log_file_name), "a", encoding="utf-8") as logf:
        while True:
            wave = recorder.record_audio_until_silence(5)
            if wave is None:
                continue
            _, text = speech_recognizer.recongnize(wave)
            if text == "":
                continue

            rec_txt = f"Recongnized: {text}\n"
            logf.write(rec_txt)
            print(rec_txt)
            responce = chatbot.responce(text)
            rsp_txt = f"Responce: {responce}\n"
            logf.write(rsp_txt)
            print(rsp_txt)

            speaker.speak_text(responce)  

```

## 材料  
今回は録音アルゴリズムのみ自作で、あとは外部のツールやAPIを用いました。 

- 自作録音アルゴリズム   
- OpenAI Whisper (文字起こし)  
- OpenAI Text Davinci 003 (言語処理)  
- OpenJTalk (音声合成)  
 
### 録音アルゴリズム  

波形を録音し、音量が一定以上に鳴ったら録音を開始し、一定時間無音が続いたら録音を終了するというとてもシンプルなアルゴリズムです。無音の判別には波形のルート自乗平均を用いました。実際のソースでは波形のチェックチャンクサイズとストライドが存在し、 細かく無音判別をできるようなアルゴリズムになっています。  
    $$ \sqrt{\frac{1}{N} \sum_i^N{wave[i]^2}} $$  

### OpenAI Whisper  
OpenAIが公開している非常に高品質な音声認識モデルです。このモデル単体で文字起こしだけでなくさまざまなタスクを解くことができます。  

https://github.com/openai/whisper  

使用するに当たって、このサイトを参考にしました。
[PCで再生中の音声をWhisperでリアルタイムに文字起こしする](https://tadaoyamaoka.hatenablog.com/entry/2022/10/15/175722)  


- Note:
    GPUを使用した処理を行わせたい場合は、事前にGPU版のPyTorchをインストールしておく必要があります。  


### OpenAI Text Davinci 003  
これはOpenAI が公開している GPT3ベースの言語モデルのクラウドAPIです。非常に今品質なテキスト補完が可能であり、この補完機能を利用して対話を生成して行きます。  


使用するに当たって、このサイトを参考にしました。
[【GPT-3】PythonでGPT-3を用いてChatGPT風のコマンドラインツールを作ろう！](https://aiacademy.jp/media/?p=3559)


```py 
import openai
openai.api_key = "<API KEY>"  


resp = openai.Completion.create(
    engine="text-davinci-003",
    prompt="Your input prompt",
    max_tokens=4000,
    temperature=0.9,
    stop=[]
)

text = resp["choices"][0]["text"]
```

#### 会話分を生成するには  
会話を行わせたい場合は、次のようなプロンプトを送信する必要があります。これは会話という行為をテキスト補完タスクとして扱うためによく使われる手段です。  

```
次の会話は人工知能と人間の会話です。人工知能はとても知的で、創造的で、気さくで、日本語を話します。
また人工知能の語尾は「にゃあ」です。話題をよく変えます。

人間: 君は僕と話すために生まれた人工知能だ。
人工知能: よろしくにゃあ
人間: 君の自己紹介をしてくれないか？  
人工知能: 
```


この方法から、一貫した会話を生成するには、常に会話の履歴を全て入力プロンプトとして送信しなければならないことがわかります。[VRChat Botの `ChatBot`クラスはそういった履歴の処理も内部で行うように作られています。](https://github.com/Geson-anko/vrchatbot/blob/main/vrchatbot/chatbot.py)   
 
参考: https://beta.openai.com/docs/guides/completion/conversation  
**注意**: 出力停止ワードを`stop=["人工知能:", "人間:" ]`といった形で書き込む必要があります。これにより、人間側まで文章を生成してしまうことを防ぐことができます。    


### OpenJTalk  
名古屋工業大学が開発した日本語の音声合成システムです。Pythonで扱うため `pyopenjtalk`を用います。  
インストール時にビルドが走るので、以下の項目がインストールされている必要があります。  
- C/C++ compilers (to build C/C++ extentions)
- cmake
- cython

https://github.com/r9y9/pyopenjtalk  

使い方は非常に簡単です。  

```py
import pyopenjtalk
from scipy.io import wavfile

x, sr = pyopenjtalk.tts("おめでとうございます")
wavfile.write("test.wav", sr, x.astype(np.int16))
```


# 余談  
今回Chat Botを作ってみて思ったことは、**真の対話**とは 何か？です。  


私たちは普段から人と流暢に言語を使ってコミュケーションをとることができます。しかし、対話そのものは言語のみのコミュニケーションで説明できるものではありません。ジェスチャーといった非言語コミュニケーションが存在し、もっとさまざまな要因が有機的に絡み合っていることが想像されます。
もはや、世界の状態遷移・観測の遷移そのものを近似せざるを得ないのではとさえ思わされます。     
 
今回作成した言語のみでのBotシステムは、実装が比較的容易で、何かアシスタントAIとして動くには十分かもしれません。人間とさして変わらない知能を実現する上で非常に重要なマイルストーンではありますが、何か決定的に欠けているものがあり、これからも研究しがいのある領域だと深く感じる結末でした。  

# 参考資料  
- [OpenAI Whisper](https://github.com/openai/whisper )
- [PCで再生中の音声をWhisperでリアルタイムに文字起こしする](https://tadaoyamaoka.hatenablog.com/entry/2022/10/15/175722)  
- [【GPT-3】PythonでGPT-3を用いてChatGPT風のコマンドラインツールを作ろう！](https://aiacademy.jp/media/?p=3559)
- 会話文章を生成するためには  
    https://beta.openai.com/docs/guides/completion/conversation  
- [【音声合成】Pythonで音声を生成してみよう！](https://aiacademy.jp/media/?p=3480)  
- [pyopenjtalk](https://github.com/r9y9/pyopenjtalk)  
- [pyopenjtalkのインストール](https://note.com/npaka/n/n6a5307cf8fe1)  
- [【深層学習】GPT-3 ①-1 モデルと Sparse Transformer について【ディープラーニングの世界vol.39】](https://youtu.be/CBZWzQVcXE4)