💬

ChatGPTで英会話したい

2023/12/19に公開

株式会社medibaバックエンドエンジニアをしてるRetchです。
この記事はmediba Advent Calendar 2023 の19日目です。

ChatGPTで英会話

ChatGPTを利用した英会話のアプリが出てきてる中、タイピングするのも大変だし会話で色々話せたら良いなぁと思ったので実際にやってみた!という類の記事になります。
自分の声を認識してChatGPTと会話し、音声で返すところまで実装しました。
処理のおおまかな流れは以下です。

音声区間認識 (Voice Activity Detection)
↓
取得した音声から文章への変換 (Speech To Text)
↓
ChatGPTに文章を渡してレスポンスを受け取る 
↓
文章から音声への変換 (Text To Speech)

今回、ChatGPT部分以外は無料のものを使用しています。

音声区間認識 (VAD)

py-webrtcvadを使用しました。リポジトリは以下です。
https://github.com/wiseman/py-webrtcvad

conda環境でインストールする場合、Errorが発生することもあるようなので推奨の方法で失敗した場合は以下の方法を試してみてください。自分はErrorが発生しました。

pip install webrtcvad-wheels

参考
https://github.com/wiseman/py-webrtcvad/issues/40

主要なパラメータの解説

pyaudio

  • channels
    • PyAudioでオーディオストリームを開く際に、使用するチャネルの数を指定します。

    • 例1)モノラル(1チャネル)

      • モノラルは、1つのチャネルを使用します。これは、同じオーディオ信号がすべてのスピーカーやヘッドフォンの両耳に送られることを意味します。
    • 例2)ステレオ(2チャネル)

      • ステレオだと2つのチャネルで、通常は左と右で分かれます。

今回はマイクの信号のみなので1を指定しています。

py-webrtcvad

  • sample_rate
    • サンプリング周波数です。アナログ信号(連続値)をデジタル信号(離散値)に変換する際に1秒間にどれだけの回数で信号をサンプリング(測定)するかを示します。
       一般的にサンプリング周波数は44.1kHzにされますが今回はwebrtcvadが対応している8000,16000,32000,48000の内、最も高い周波数を設定しました。
  • frame_duration
    • フレーム長です。一度に処理されるサンプルの時間長を示します。要するに◯ms間の音声データを生成するために使用しています。webrtcのVADでは10,20,30msのみ対応されています。

sample_rateやframe_durationの対応については以下のページのHow to use itの3に記載があります。
https://github.com/wiseman/py-webrtcvad?tab=readme-ov-file#how-to-use-it
 
vad_aggressiveness:
 1~3まで指定できます。値が大きいほど敏感に認識するのでノイズが混ざる可能性があります。環境音が周りから入る環境なら2などに設定しても良いかも知れません。

    def record_voice(self,out="audio.wav",max_seconds=20,silence_timeout=2,channels=1,sample_rate=48000,vad_aggressiveness=3):
        audio = pyaudio.PyAudio()
        frame_duration = 30
        chunk = int(sample_rate * frame_duration / 1000)
        vad = webrtcvad.Vad(vad_aggressiveness)
        
        stream = audio.open(
                format=pyaudio.paInt16,
                channels=channels,
                rate=sample_rate,
                input=True,
                input_device_index=0,
                frames_per_buffer=chunk
                )
        
        frames = []
        start_time = time.time()
        last_voice_time = time.time()
        while time.time()-start_time < max_seconds:
            data=stream.read(chunk)
            if vad.is_speech(data, sample_rate):
                frames.append(data)
                last_voice_time = time.time()
            if time.time() - last_voice_time > silence_timeout:
                break
        
        stream.close()
        audio.terminate()

        waveFile = wave.open(out,'wb')
        waveFile.setnchannels(channels)
        waveFile.setsampwidth(audio.get_sample_size(pyaudio.paInt16))
        waveFile.setframerate(sample_rate)
        waveFile.writeframes(b''.join(frames))
        waveFile.close()

音声から文章への変換 (Speech To Text)

ここにはWhisperを使用しました。Whisperは2022年9月にOpenAIから発表されたオープンソースの音声認識モデルです。APIとしても用意されていますが個人利用ではあまり恩恵が無いのでここではローカルマシン上でwhisperを使用しています。

パラメータの解説

処理自体は少ないので、詰まった部分だけ紹介します。

  • fp16=False
    このパラメータを指定していない場合に、私は以下のようなWarningが出ました。
FP16 is not supported on CPU; using FP32 instead

CPUがFP16に対応していないとのこと。代わりにFP32を使えと言われるのですがfp16=Falseがその設定にあたるのでその設定をしています。使用するCPUによってはWarningは出ないかも知れません。

fp16=Trueでは浮動小数点数の精度が落ちる分、音声から文章変換する速度がほぼ倍加するため、fp16=Trueで動作に問題がなければそのほうが良いと思っていますが、警告で推奨された方に合わせています。
Whisperは音声認識のモデルなので音声区間認識(VAD)で取得したaudio.wavを対象に文章化を行なっています。

    def voice_recognition(self,file="audio.wav"):
        model = whisper.load_model("base")
        result = model.transcribe("./audio.wav",fp16=False,language="en")
        return result["text"]

ChatGPT

GPTはGenerative Pre-trained Transformerの略で、「生成可能な事前学習済み変換器」と訳されます。その会話に特化したものが"Chat"GPTです。今回はその"チャット"に最適化されたopenaiのモデルである「gpt-3.5-turbo」を使用しています。

    def conversation(self,s):
        self.messages+={"role":"user","content":s},
        completion = client.chat.completions.create(
        model="gpt-3.5-turbo",
        messages=self.messages
        )
        response=completion.choices[0].message
        self.messages+=response,
        return response.content

利用料金に関する注意点

APIで過去のメッセージを含めた内容を反映しながら会話をする場合、APIに投げるリクエストにその会話履歴も含めて投げる必要があります。現在の実装ではその履歴の数に制限をつけていないので会話が長引くほど1リクエストで消費されるトークン数(日本語では1文字1トークン)が増えるのでコードを参考にする場合は気をつけてください。

現在(2023/12/18)、gpt-3.5-turboは1000トークンあたり0.003$です。

参考

openai APIドキュメント
https://platform.openai.com/docs/api-reference/chat
openai API料金表
https://openai.com/pricing

文章から音声への変換 (Text To Speech)

ここではWindowsに元から入っている「Microsoft Speech API(SAPI)」を使用しました。

事前準備

スタートボタンを右クリックして設定を開きます。

「時刻と言語」から「音声認識」を選択します。

「音声の管理」から「音声を追加」を選択します。

読み上げてもらいたい言語を選択して追加します。

処理の説明

コード内の「HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Speech_OneCore\Voices」はWindowsのレジストリ内の値を示しています。言語の追加終了時点でこのレジストリのPATHは存在するので以下のコードが実行可能になります。

    def speak(self,text):
        speech = win32com.client.Dispatch("Sapi.SpVoice")
        cat  = win32com.client.Dispatch("SAPI.SpObjectTokenCategory")
        cat.SetID(r"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Speech_OneCore\Voices", False)
        v = [t for t in cat.EnumerateTokens() if t.GetAttribute("Name") == "Microsoft Mark"]
        speech.Voice = v[0]
        speech.Speak(text)

どんな音声が存在するかは先程のページで「音声を選択する」で確認ができます。こちらは「音声を追加」でインストールされたものだけが見えます。

存在する言語ごとの音声一覧は以下で確認可能です。
https://support.microsoft.com/ja-jp/windows/付録-a-サポートされている言語と音声-4486e345-7730-53da-fcfe-55cc64300f01#WindowsVersion=Windows_11

コード

完成したコードは以下になります。
https://github.com/reatoretch/chatgpt-talk/tree/main

言語追加方法

言語を追加したい場合は

class ChatGpt:
    def __init__(self,lang):
        if lang=="ja":
            self.client=ChatGptJa()
        elif lang=="en":
            self.client=ChatGptEn()

__init__
にif文で初期化処理を追加。

class ChatGpt〇〇:
    def __init__(self):
        self.system_settings = """
        日本語学習者と対話をしてもらいます。
        答えやすい質問をして、話を続けてください
        """
        self.system_messages = [
            "エンターキーを押して話します.",
            "音声認識中…"
        ]

    def speak(self,text):
        speech = win32com.client.Dispatch("Sapi.SpVoice")
        cat  = win32com.client.Dispatch("SAPI.SpObjectTokenCategory")
        cat.SetID(r"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Speech_OneCore\Voices", False)
        v = [t for t in cat.EnumerateTokens() if t.GetAttribute("Name") == "Microsoft Mark"]
        speech.Voice = v[0]
        speech.Speak(text)
    
    def voice_recognition(self,file="audio.wav"):
        model = whisper.load_model("base")
        result = model.transcribe("./audio.wav",fp16=False,language="ja")
        return result["text"]

__init__
文章を対応する言語に翻訳して入れ直す。

speak
"Microsoft Mark"の部分を対応する言語の声に変える。

voice_recogniton
language="ja"を対応する言語に変える。

この方法で追加が可能です。

最後に

韓国語や中国語など多くの言語に対応できるので言語勉強にChatGPTは本当に便利だと思いました。コード書き換えは自由です。なにかの参考になれば幸いです。

Discussion