今更ながら、ChatGPTをいじってみようと思います。

ただ、ChatGPTを触るだけだと味気ないので、RaspberryPiを使って音声でおしゃべりしてみたいと思います。

試行錯誤。。。

①マイクの音声をテキストに変換 ⇒ ②ChatGPTに送信 ⇒ ③受信したテキストを読み上げ

という感じの仕組みにしてみようと思います。
「②ChatGPTに送信」についてはOpenAIapi、
「③受信したテキストを読み上げ」についてはopenjtalkで難なくできそうです。
問題は「①マイクの音声をテキストに変換」を何でやるか。

候補としては以下があります。

  • Julius
  • SpeechRecognition
  • Wisper

Julius

過去にも使ったことがありますが、辞書を作る必要があって辞書がないと認識制度がかなり悪いという印象でした。
数年前の印象なので、良くなっているかと思い使ってみましたが、あまり変わっていませんでした。
今回は特定の言葉の認識とするつもりはないのでJuliusは除外しました。

SpeechRecognition

音声データを作成する機能と音声認識する機能を持っています。
音声認識は別のライブラリやGoogleのサービスが使えます。
Googleのサービスを使ってみようと思いましたが、どうやら数週間前にこのライブラリ内で利用しているGoogleのAPIKeyが不正利用とされたらしく、別途、GoogleのAPIKeyを取得しないといけないのですが、有料サービスだったので断念しました。

Wisper

いろいろ試してみましたが、以下のような条件が付きます。

  • OSは64Bit(つまりRaspberryPi4)じゃないとダメ
  • 精度の高い学習モデルを使うためには4Gぐらいのメモリが必要

Pytorchが必要となるのでインストールが面倒ですが、まともに動きそうなのがWisperだけだったので、利用することにします。
RaspberryPi4を使うことになりますが、メモリは4Gのため”medium”以上は使えません。
今回はとりあえず、”tiny”でやってみたいと思います。

今回の構成

以上の結果から今回の構成は以下の通りです。

  • raspberry pi 4 (4G)
  • Raspberry Pi OS 64-bit(A port of Debian Bookworm with the Raspberry Pi Desktop)
  • python 3.11(プレインストール)
  • PyAudio 0.2.13
  • openai 1.6.1
  • openai-whisper 20231117
  • SpeechRecognition 3.10.1
  • open-jtalk

ChatGPT利用の準備をする

PythonでChatGPTにアクセスするにはAPIを使用します。
まずはOpenAIのアカウントが必要なので作りましょう。

OpenAIアカウントを作る

以下にアクセスします。

https://chat.openai.com/auth/login

Sign upをクリックします

メールアドレスを入れるとメールが来ます。
「Verify email address」をクリックします。

名前と誕生日を入力します。

アカウントができました。

OpenAIのAPIKEYを取得する

続いてAPIKeyを作成します。
次に以下にアクセスします。

platform.openai.com/docs/overview

南京錠のアイコンにマウスを合わせると”API Key”が出るのでクリックします。

“Start verification”をクリックします。

電話番号を入力して”Send code”をクリックすると、スマホにコードが送られてきます。

スマホに送られてきたコードを入力します。

“Continue”をクリックします。

作成するAPIキーの名前を入力します。(なんでもいいです)

APIKeyが生成されます。

RaspberryPiのセットアップをする

アップデートする

まずはアップデートしましょう。

sudo apt-get -y update
sudo apt-get -y upgrade

マイク設定

今回はUSBマイクを使用します。

USBに刺したら以下のコマンドでカード番号とデバイス番号を調べます。

arecord -l
**** List of CAPTURE Hardware Devices ****
card 3: Device [USB PnP Sound Device], device 0: USB Audio [USB Audio]
  Subdevices: 1/1
  Subdevice #0: subdevice #0

上記の結果から以下のファイルを作成します。

nano ~/.asoundrc
pcm.!default {
  type asym
  capture.pcm "mic"
}
pcm.mic {
  type plug
  slave {
    pcm "hw:カード番号,デバイス番号"
  }
}

一度再起動します

sudo reboot now

音量設定については”alsamixer”コマンドで行います。
今回は変更する必要はありませんでしたので割愛します。
音声出力についてはRaspberryPiの画面のスピーカーアイコンで設定できます。
RaspberryPi4は出力先がピンジャックとHDMIがあるので右クリックで選択してください。
音量は左クリックで変更できます。

pytorchをセットアップする

今回使用する”whisper”はpytorchが必要なので、先にインストールします。
以下のコマンドでインストールしてください。

sudo apt-get install -y python3-pip libjpeg-dev libopenblas-dev libopenmpi-dev libomp-dev
sudo -H pip3 install --break-system-packages setuptools==58.3.0
sudo -H pip3 install --break-system-packages Cython
sudo -H pip3 install --break-system-packages gdown
pip install --break-system-packages torch==2.1.1 torchvision==0.16.1 torchaudio==2.1.1 --index-url https://download.pytorch.org/whl/cpu

pip3としていますが、pipでもいいと思います。
RaspberryPi4でpipインストールするとvenvじゃないとエラーになるので、”–break-system-packages”オプションを付けてインストールしています。(アンインストールできないことがあるので不安な場合はvenvでインストールしてください。)
Pytorchはすんなりインストールできず、試行錯誤の結果、上記の手順となりました。
(これにたどり着くのに1日ぐらいかかりました)
OSやPytorchのバージョンが変わると手順が使えない場合がありますので注意してください。

whisperをセットアップする

Pytorchがインストール出来たらwhisperをインストールしましょう。
Pytorchインストール前にwhisperをインストールするとエラーになるので必ず先にPytorchをインストールしてください。

pip install --break-system-packages -U openai-whisper

一度テストしましょう。
音声ファイルを作成します。
以下のコマンドを実行した後でマイクに向かってしゃべってください。
しゃべり終わったらCtrl+Cで終了すると”out.wav”ファイルができます。

arecord out.wav

上記で作ったファイルをテキストに変換してみましょう。

nano whisper_test.py
import whisper
model = whisper.load_model("tiny") # モデルを読み込む
result = model.transcribe("out.wav", fp16=False) # 音声ファイルを指定する
print(result["text"]) # 認識結果を出力

実行してしゃべった言葉が文字で表示されればOKです。

python whisper_test.py

SpeechRecognitionをセットアップする

続けてSpeechRecognitionをインストールします。

sudo apt-get install -y python3-pyaudio 
pip install --break-system-packages SpeechRecognition
pip install --break-system-packages soundfile

マイクでしゃべったものをそのままテキストにしてみましょう。

nano mic_to_text.py
from io import BytesIO

import numpy as np
import soundfile as sf
import speech_recognition as sr
import whisper

if __name__ == "__main__":
    model = whisper.load_model("tiny")

    recognizer = sr.Recognizer()
    while True:
        # 「マイクから音声を取得」参照
        with sr.Microphone(sample_rate=16_000) as source:
            print("なにか話してください")
            audio = recognizer.listen(source)

        print("音声処理中 ...")
        # 「音声データをWhisperの入力形式に変換」参照
        wav_bytes = audio.get_wav_data()
        wav_stream = BytesIO(wav_bytes)
        audio_array, sampling_rate = sf.read(wav_stream)
        audio_fp32 = audio_array.astype(np.float32)

        result = model.transcribe(audio_fp32, fp16=False)
        print(result["text"])

実行して、「なにか話してください」と出たらしゃべってみてください。
しゃべった言葉がテキストで表示されればOKです。

python mic_to_text.py

OpenJtalkをインストールする

次にOpenJtalkをインストールします。

sudo apt install -y open-jtalk open-jtalk-mecab-naist-jdic hts-voice-nitech-jp-atr503-m001
wget https://sourceforge.net/projects/mmdagent/files/MMDAgent_Example/MMDAgent_Example-1.7/MMDAgent_Example-1.7.zip --no-check-certificate
unzip MMDAgent_Example-1.7.zip
sudo cp -R ./MMDAgent_Example-1.7/Voice/mei /usr/share/hts-voice/

テスト実行してみましょう。
まず、以下のシェルを作ります。

nano jtalk.sh
#!/bin/bash
tempfile=$(mktemp)
option="-m /usr/share/hts-voice/mei/mei_normal.htsvoice \
-x /var/lib/mecab/dic/open-jtalk/naist-jdic \
-ow $tempfile"

echo "$1" | open_jtalk $option
aplay -q $tempfile
rm $tempfile

シェルに実行権限を与えて実行してみましょう。

chmod 755 jtalk.sh
./jtalk.sh "こんにちは。ラズベリーパイです"

スピーカーから読み上げ内容が出力されればOKです。

pythonで呼び出してみましょう。

nano jtalk.py
#coding: utf-8
import subprocess

def jtalk(ptext):
    open_jtalk=['open_jtalk']
    mech=['-x','/var/lib/mecab/dic/open-jtalk/naist-jdic']
    htsvoice=['-m','/usr/share/hts-voice/mei/mei_normal.htsvoice']
    speed=['-r','1.0']
    outwav=['-ow','open_jtalk.wav']
    cmd=open_jtalk+mech+htsvoice+speed+outwav
    c = subprocess.Popen(cmd,stdin=subprocess.PIPE)
    c.stdin.write(ptext.encode())
    c.stdin.close()
    c.wait()
    aplay = ['aplay','-q','open_jtalk.wav']
    wr = subprocess.Popen(aplay)

def say_test():
    text = 'こんにちは。ラズベリーパイです'
    jtalk(text)

if __name__ == '__main__':
    say_test()

OpenAI Apiをインストールする

最後にOpenAIのセットアップをします。

pip install --break-system-packages openai

OpenAIApiをテストしてみましょう

nano gpt_test.py
import os
from openai import OpenAI

client = OpenAI(
    # This is the default and can be omitted
    api_key="<APIキーをここに設定します>",
)

stream  = client.chat.completions.create(
    messages=[
        {
            "role": "user",
            "content": "Say this is a test",
        }
    ],
    model="gpt-3.5-turbo",
    stream=True,
)
for chunk in stream:
    print(chunk.choices[0].delta.content or "", end="")

OpenAIのサイトで取得したAPIKeyを上記のソースにセットしてください。
実行すると「This is a test.」と出力されます。

ChatGPTとしゃべってみよう。

上記のサンプルを組み合わせて以下のようなソースを書きました。
実行するとChatGPTさんとおしゃべりできます。

nano gpt_session.py
from io import BytesIO

from openai import OpenAI
import subprocess
import numpy as np
import soundfile as sf
import speech_recognition as sr
import whisper

client = OpenAI(
    api_key="<APIキーをここに設定します>",
)
system_settings = """
あなたはとてもやさしく、話し相手として最高の友人です。
"""


def jtalk(ptext):
    open_jtalk=['open_jtalk']
    mech=['-x','/var/lib/mecab/dic/open-jtalk/naist-jdic']
    htsvoice=['-m','/usr/share/hts-voice/mei/mei_normal.htsvoice']
    speed=['-r','1.0']
    outwav=['-ow','open_jtalk.wav']
    cmd=open_jtalk+mech+htsvoice+speed+outwav
    c = subprocess.Popen(cmd,stdin=subprocess.PIPE)
    c.stdin.write(ptext.replace('\n', '').encode())
    c.stdin.close()
    c.wait()
    aplay = ['aplay','-q','open_jtalk.wav']
    wr = subprocess.Popen(aplay)

def completion(new_message_text:str, past_messages:list = []):
    if len(past_messages) == 0 and len(system_settings) != 0:
        system = {"role": "system", "content": system_settings}
        past_messages.append(system)
    new_message = {"role": "user", "content": new_message_text}
    past_messages.append(new_message)

    result = client.chat.completions.create(
        model="gpt-3.5-turbo",
        messages=past_messages,
    )
    response_message = {"role": "assistant", "content": result.choices[0].message.content}
    past_messages.append(response_message)
    response_message_text = result.choices[0].message.content
    return response_message_text, past_messages

if __name__ == "__main__":
    model = whisper.load_model("tiny")

    recognizer = sr.Recognizer()
    messages = []
    while True:
        # 「マイクから音声を取得」参照
        with sr.Microphone(sample_rate=16_000) as source:
            print("なにか話してください")
            audio = recognizer.listen(source)

        print("音声処理中 ...")
        wav_bytes = audio.get_wav_data()
        wav_stream = BytesIO(wav_bytes)
        audio_array, sampling_rate = sf.read(wav_stream)
        audio_fp32 = audio_array.astype(np.float32)

        result = model.transcribe(audio_fp32, fp16=False)
        recog_text = result["text"]
        print('ユーザー:', recog_text)
        new_message, messages = completion(recog_text, messages)
        print('ChatGPT:', new_message)
        jtalk(new_message)

実行するとこんな感じです。

$ python gpt_test1.py 
This is a test.youichi@raspberrypi:~/chatgpt_session $ python gpt_session.py 
ALSA lib pcm_asym.c:105:(_snd_pcm_asym_open) capture slave is not defined
ALSA lib confmisc.c:1369:(snd_func_refer) Unable to find definition 'cards.0.pcm.front.0:CARD=0'

...
JackShmReadWritePtr::~JackShmReadWritePtr - Init not done for -1, skipping unlock
JackShmReadWritePtr::~JackShmReadWritePtr - Init not done for -1, skipping unlock
なにか話してください
音声処理中 ...
ユーザー: 日本の政治は何とかなりませんか
ChatGPT: 私たちが友人であるように、日本の政治も改善する可能性があります。政治改革は困難な挑戦かもしれませんが、私たちが団結し、意見を交換し、行動を起こすことで、変化をもたらすことができます。政府に対して声を上げ、問題を解決するための政策や制度改革を提案することも重要です。また、有権者として、選挙で自分たちの考えを反映させ、良い指導者を選ぶことも重要です。一人ひとりが意識を持ち、行動を起こすことで、日本の政治にポジティブな変化をもたらすことができると信じています。私はあなたが日本の政治について気にかけていることをとても素晴らしいと思いますし、いつでも話し相手としてサポートします。
ALSA lib pcm_asym.c:105:(_snd_pcm_asym_open) capture slave is not defined
ALSA lib confmisc.c:1369:(snd_func_refer) Unable to find definition 'cards.0.pcm.front.0:CARD=0'

...

jack server is not running or cannot be started
JackShmReadWritePtr::~JackShmReadWritePtr - Init not done for -1, skipping unlock
JackShmReadWritePtr::~JackShmReadWritePtr - Init not done for -1, skipping unlock
なにか話してください

PyAudioのログ(”ALSA lib …”)が邪魔ですが、ちゃんと会話できました。
最終的にはコンソールに出ている文字を読み上げてくれるので会話っぽくなっています。
かなりタイムラグがありますが。。。
whisperも”tiny”で十分テキスト変換してくれます。

まとめ

今回はRaspberryPi4を使ってChatGPTとおしゃべりしてみました。
いろいろと時代の流れが速いので、whisperやSpeechRecognitionのインストールの情報がなく、めちゃめちゃ苦労しましたが、何とか出来た感じです。
もう少しスペックの低いハードで実現出来たらよかったんですが、現状これが限界という感じです。
やはり、クラウドサービスを使うとなるとお金が絡んでくるので課金ルールを調べるだけでも一苦労ですね。
今回、OpenAIの課金についてはあまり説明していませんが、いろいろ調べてみるとAPIを使用するときは有料サービスに登録する必要があるという説明がたくさんありました。
しかし、今回はUpgradeしないでChatGPT3.5で作りましたので、有料サービスに登録することなく使用できているようです。(正直よくわかりません)
もちろん、製品の一部として公開するときにはそういうわけにはいかないと思うので業務で使う時にはちゃんと登録してChatGPT4.0を使ったほうがいいと思います。

おもちゃを作って遊ぶだけなら問題ないと思いますので、知見を広げるにはいいと思います。
ぜひやってみてください。