Japanese-GPT-1bのチャットボットをVoiceVoxのAPIでしゃべらせてみる

スポンサーリンク
自然言語処理
スポンサーリンク

transformersの日本語特化学習済み事前言語処理モデルJapanese-GPT-1bと音声合成APIのVoiceVoxを組み合わせて、しゃべるチャットボットを作成してみました。

環境構築(1) VoiceVox

VoiceVoxのAPIはVoiceVoxエンジンをインストールし、起動することで、ローカルのwebAPIとして動作させることが可能です。環境構築方法はこちらのまとめ記事をご参考ください。

 VOICEVOXエンジンを使ったPythonでの「高」品質音声合成API

インストールさせたあと、起動して、APIをスタンバイ状態にします。

CPUで起動する場合(VoiceVoxの「run.py」ファイルがあるディレクトリで実行)

> python run.py --voicevox_dir="./engines/windows-cpu"

GPUで起動する場合(VoiceVoxの「run.py」ファイルがあるディレクトリで実行)

> python run.py --voicevox_dir=".\engines\windows-nvidia" --use_gpu

環境構築(2) GPU+Pytorch+Transformers環境の構築

Windowsネイティブ環境にTransformersの環境を構築します。環境構築については、以前の記事もご参考ください。ここでは、venv仮想環境にcuda+pytorch+transformersの環境を作成します。あらかじめ、cudaの環境は準備してください。(参考記事:WindowsへのNVIDIA CUDAのGPU環境構築)cpu環境でもできますが、チャットの生成に時間が掛かってしまうので、できればGPUの環境をお使いください。

> mkdir chatbot
> cd chatbot
> py -3.9 -m venv venv
> .\venv\Scripts\Activate.ps1
(venv)> python -m pip install -U pip setuptools
(venv)> pip3 install torch torchvision torchaudio --extra-index-url https://download.pytorch.org/whl/cu117 
(venv)> pip install transformers
(venv)> pip install sentencepiece
(venv)> pip install pyaudio

環境構築(3) Japanese-GPT-1bとチャットボット

続いて、ChatBotのエンジンをテストします。ChatBotの詳しい説明はUbuntu環境に作成した、下の記事をご覧ください。

日本語特化GPT言語モデルJapanese-GPT-1bで簡単チャットボット

今回は上のリンクのコードを改造してwavファイルの生成と再生を実装します。wavファイルとの連動とかを考えて、Windowsネイティブ環境で完結するようにしています。生成したwavファイルをtmpフォルダに生成して、再生しています。speakerのパラメータには好きな声のspeaker idを設定することで好みの声で出力できます。urlのパラメータには、VoiceVoxのAPIのアドレスを入力します。私のPCでは少し声の生成にタイムラグはありますが、ちゃんとチャットでしゃべることができました。

import torch
from transformers import T5Tokenizer, AutoModelForCausalLM
import requests
import json
import datetime
import pyaudio
import wave
import os
import time

# VoiceVoxのspeaker idを入れる
speaker = 2

# 会話文を入れる
def my_turn():
    while True:
        s = input("話してください?")
        if s == "":
            exit()
        elif len(s)<=40:
            break
    return s

# bot生成
class ChatBot:
    def __init__(self, bot_chara):
        self.bot_chara = bot_chara        

    def conv_generate(self, prompt, tokenizer, model):

        token_ids = tokenizer.encode(prompt, add_special_tokens=False, return_tensors="pt")
        prompt_length = len(prompt)

        with torch.no_grad():
            output_ids = model.generate(
                token_ids.to(model.device),
                max_length=100,
                min_length=100,
                do_sample=True,
                num_beams=20,
                early_stopping=True,
                no_repeat_ngram_size=1,
                remove_invalid_values=True,
                temperature=0.8,

                pad_token_id=tokenizer.pad_token_id,
                bos_token_id=tokenizer.bos_token_id,
                eos_token_id=tokenizer.eos_token_id,
            )

        output = tokenizer.decode(output_ids.tolist()[0])
        res = output[prompt_length:]
        if "」" in res:
            res = res.split("」")[0]
        voice_gen(res, speaker)
        print(self.bot_chara+":「"+res+"」")

        return output[0:(prompt_length+len(res))]

# promptの字数を調整
def reducer(prompt):
    if len(prompt) >= 100:
        return prompt[-100:]
    return prompt

# キャラクター選択
def select_chara():
    print("誰と話しますか?")
    chara_list = ["フリー入力","正統派美少女", "しっかり者の女性", "理系女子","もの知りのおばあちゃん", "電話のオペレーターのおねえさん"]
    for i in range(len(chara_list)):
        print(i+1, ": "+chara_list[i])
    chara_no = int(input("キャラクタを選んでください(番号で選択)?"))
    if chara_no == 1:
        return input("話したい人を入れてください(フリーで入力)?")
    elif chara_no>=2 and chara_no<=len(chara_list):
        return chara_list[chara_no-1]
    else:
        exit()

def voice_gen(text, speaker):
    # urlはVOICEVOXのAPIのURLを入れる
    url = 'http://127.0.0.1:50021/audio_query'
    # 音声合成クエリの作成
    res1 = requests.post(url,params = {'text': text, 'speaker': speaker})
    # 音声合成データの作成
    res2 = requests.post(url,params = {'speaker': speaker},data=json.dumps(res1.json()))
    # wavデータの生成
    now = datetime.datetime.now()
    file_name = './tmp/'+now.strftime('%y%m%d_%H%M%S')+'.wav'
    with open(file_name, mode='wb') as f:
        f.write(res2.content)
    wav_loader(file_name)
    return

# 発声部
def wav_loader(file_name):
    while not os.path.exists(file_name):
        time.sleep(0.2)
    CHUNK = 1024
    
    wf = wave.open(file_name, 'rb')
    p = pyaudio.PyAudio()
    
    stream = p.open(format=p.get_format_from_width(wf.getsampwidth()),
                    channels=wf.getnchannels(),
                    rate=wf.getframerate(),
                    output=True) 
    data = wf.readframes(CHUNK)   
    while len(data) > 0:
        stream.write(data)
        data = wf.readframes(CHUNK)    
    stream.stop_stream()
    stream.close()    
    p.terminate()
    return

# 実行部
def main():
    bot_chara = select_chara()
    print(bot_chara+"ですね。")
    print()

    tokenizer = T5Tokenizer.from_pretrained("rinna/japanese-gpt-1b")
    model = AutoModelForCausalLM.from_pretrained("rinna/japanese-gpt-1b")

    if torch.cuda.is_available():
        model = model.to("cuda")

    chatbot = ChatBot(bot_chara)
    voice_gen("楽しいお話をしましょう。", speaker)
    initial_prompt = chatbot.bot_chara+":「楽しいお話をしましょう。」"
    print(initial_prompt)
    text = initial_prompt

    while True:
        text = text + "私:「"+my_turn()+"」"+chatbot.bot_chara+":「"
        text = reducer(text)
        text = chatbot.conv_generate(text, tokenizer, model)

if __name__ == "__main__":
    main()  

コメント