【API不使用】AI VTuberをWindowsで動かし、簡単に始める方法(音声での呼びかけにも対応する)

AI VTuberをWindowsで動かし、簡単に始める方法(音声での呼びかけにも対応する) ソフトウェア

AIVtuberを始めるにあたって必要な処理フロー、ローカル環境でのGPUの必要なスペックなどを具体的コードとともに解説します。

私はVTuberというよりローカルPCに常駐してお話してくれる存在を作るのが目的のため、音声入力を可能にしていますが、単にAI VTuberの場合は文字での入力さえあれば良いので、音声入力の章は読み飛ばしてください。

また、今回は音声と文章の処理だけについて書くため、VTuberモデルを動かすことについては触れません。

結論から先に言うと、ユーザーの入力に対する適切な返答を生成する言語モデルさえあれば現状誰でもAIVTuberを始めることができます

コードをコピペするだけで以下のようになります。

チェック!!
さくいん! 本サイトのAI関連記事まとめ どれから見れば良いのか?!


数十記事ある本サイトのAI関連記事を体系的にまとめました。

目的別にどれから見れば良いのかわかります!!

AI記事索引ページはコチラ

1. AI VTuberの処理フロー(音声と文章について)

AI VTuberの文字関連の処理は以下のような流れになると思います。

  1. (音声での呼びかけを文字起こしする)YouTubeのコメントを拾う
  2. コメントを元に返答を生成
  3. 生成された返答文を読み上げ

それぞれの処理について具体的コードを書きます。

whisper:音声をファイルに保存せず文字起こしする

こちらのwhisperのハードウェア負荷低減策を参考にしました。ありがとうございます。

音声認識モデル Whisper の推論をほぼ倍速に高速化した話 - Qiita
本記事は MIXI DEVELOPERS Advent Calendar 2022 の4日目の記事です。TL;DRRomi チームでは自然言語処理をメインでやりつつ、最近は音声系も手を出しつつあ…

これを使い、モデルがlargeでもVRAM5GBくらいに収まりました。

また、いちいち音声をファイルに書き出すのは無駄な処理のため、こちらを参考にwavファイルを書き出さず文字起こししています。

上記2つを使い、以下のコードになりました。

from io import BytesIO
import numpy as np
import soundfile as sf
import speech_recognition as sr
import whisper

model = whisper.load_model("large", device="cpu")
_ = model.half()
_ = model.cuda()
for m in model.modules():
    if isinstance(m, whisper.model.LayerNorm):
        m.float()

recognizer = sr.Recognizer()
while True:
    with sr.Microphone(sample_rate=16_000) as source:  # 「マイクから音声を取得」参照
        print("なにか話してください")
        audio = recognizer.listen(source)
    print("処理中 ...")
    wav_bytes = audio.get_wav_data()  # 「音声データをWhisperの入力形式に変換」参照
    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,
        verbose=False,  # [00:00.000 --> 00:30.000]を表示するか
        language="japanese",
        beam_size=5,
        fp16=True,
        without_timestamps=True,
    )
    out_text = result["text"]
    print(f"認識結果:{out_text}\n")

これで音声が途切れるごと自動的にout_textとして文字列が出力されます。

VTuberをしたい方は、上記コードの代わりにYouTubeのAPIでコメントを取得しましょう。

返答を生成←ここが一番重要!!

雑談できるAIの個性を決めるのは応答を生成する言語モデルです。

残念ながらまともなチャット・雑談が行える日本語モデルは現状公開されていません

ガチるなら、独自のファインチューニングが必須です。
(キャラ・個性を付与するなら、どのみちファインチューニング必要ですね)

ただし、普通の雑談なら、Rinna3.6bをそのまま用いてもで十分でしょう。

ここでは「BlenderBot」というMeta(Facebook)の作成した英語のチャットボット用モデルを、軽量な翻訳AI「FuguMT」を通して無理やり日本語対応させました。

なお、FuguMTCPUで実用的速度(1文1秒未満で動きRAM(VRAMではありません)の消費量が160MBくらいです。

ちなみに「facebook/blenderbot-400M-distill」のVRAM消費量は約2GBです。

まともにVTuberするならもっとデカいGPT-2T5OPT系などをファインチューニングして動かすことになりましょう。

import string
from transformers import pipeline
from transformers import BlenderbotTokenizer, BlenderbotForConditionalGeneration

def is_halfwidth(s):  # 半角のみならTrue
    return all(
        c in string.ascii_letters + string.digits + string.punctuation + " " for c in s
    )

def translate(input_text):
    if is_halfwidth(input_text) == True:
        result = ej_translator(input_text)
    elif is_halfwidth(input_text) == False:
        result = je_translator(input_text)
    result = str(result).replace("[{'translation_text': '", "").replace("'}]", "")
    return result

# BlenderBotと対話
def blender_bot(input_text):
    inputs = tokenizer_blender([input_text], return_tensors="pt")
    reply_ids = model_blender.generate(
        **inputs.to(model_blender.device),
        max_length=200,
    )
    result = tokenizer_blender.decode(reply_ids.tolist()[0])
    return result

model_blender = BlenderbotForConditionalGeneration.from_pretrained(  # 会話
    "facebook/blenderbot-400M-distill"  # 400M-distill 3B
)
tokenizer_blender = BlenderbotTokenizer.from_pretrained(
    "facebook/blenderbot-400M-distill"
)
model_blender = model_blender.to("cuda")

ej_translator = pipeline("translation", model="staka/fugumt-en-ja")  # 翻訳用
je_translator = pipeline("translation", model="staka/fugumt-ja-en")
while True:
    input_text = input("ここを取得した入力に置き換える")
    if input_text == "":
        pass
    else:
        input_text = (
            str(je_translator(input_text))
            .replace("[{'translation_text'", "")
            .replace(': "', "")
            .replace(": '", "")
            .replace("'}]", "")
            .replace('"}]', "")
        )  # 入力を英語にする
        print("翻訳後:" + input_text)
        answer = blender_bot(input_text).replace("</s>", "").replace("<s>", "")
        print("AI氏>>" + answer)
        answer = (
            str(ej_translator(answer))
            .replace("[{'translation_text'", "")
            .replace(': "', "")
            .replace(": '", "")
            .replace("'}]", "")
            .replace('"}]', "")
        )  # 出力を日本語にする
        print("翻訳後:" + answer + "\n")

あとは生成されたanswerを読み上げるだけですね。

VOICEVOXで音声読み上げ(いちいちファイルに出力はしない)

こちらを参考にインストール&読み上げコードを形にしました。ありがとうございます。

Windows/Macに入れたVOICEVOXをPython経由で使う方法
VOICEVOXをPython/Curl経由で別PCから使う方法を紹介。例え邪道な手段と言われても構わない(笑)ラズパイにリアルタイムで可愛く喋らせよう!
import requests
import argparse
import json
import pyaudio
import time

# ターミナルで以下実行してまず起動すること!!
# C:\Users\loveanime\AppData\Local\Programs\VOICEVOX\run.exe --host 192.168.2.100 --use_gpu
# VOICEVOXをインストールしたPCのホスト名を指定してください
HOSTNAME = "192.168.2.100"

while True:
    input_texts = input("読み上げたい文章入力:")
    speaker = 1  # ずんだもん
    texts = input_texts.split("。")  # 「 。」で文章を区切り1行ずつ音声合成させる

    for i, text in enumerate(texts):  # 音声合成処理のループ
        if text == "":  # 文字列が空の場合は処理しない
            continue
        res1 = requests.post(  # audio_query (音声合成用のクエリを作成するAPI)
            "http://" + HOSTNAME + ":50021/audio_query",
            params={"text": text, "speaker": speaker},
        )
        res2 = requests.post(  # synthesis (音声合成するAPI)
            "http://" + HOSTNAME + ":50021/synthesis",
            params={"speaker": speaker},
            data=json.dumps(res1.json()),
        )
        data = res2.content
        p = pyaudio.PyAudio()
        stream = p.open(
            format=pyaudio.paInt16,  # 16ビット整数で表されるWAVデータ
            channels=1,  # モノラル
            rate=24000,  # サンプリングレート
            output=True,
        )
        # 再生を少し遅らせる(開始時ノイズが入るため)
        time.sleep(0.2)  # 0.2秒遅らせる
        stream.write(data)  # WAV データを直接再生
        stream.stop_stream()
        stream.close()
        p.terminate()

VOICEVOXをインストール&ローカルのAPIサーバーとして起動した後に、上記コードを実行したら入力した文字を読み上げてくれます。

黄色マーカー部分は環境に合わせて書き換え赤文字部分を生成された返答に置き換えればAIの返答を音声化できます。

VOICEVOXはVRAM 0.7GBくらい消費します。

音声の出力先をWindowsの設定で仮想スピーカーに変更し、それを立ち絵とともにOSB等の配信に乗せれば、コメントに応答するAI VTuberの完成です。

2. ここまでの全部をまとめたコード(音声認識付き)

ワードプレスは複数のコードを分割して乗せるのに適していないので、ここまでのものを全部1つにつなげたコードを載せておきます。

計149行あります。これを全文コピペするだけで動画のような挙動になります。

"""AI音声お話"""
import json
import string
import time
import requests
from io import BytesIO
import numpy as np
import soundfile as sf
import speech_recognition as sr
import pyaudio
import whisper
from transformers import BlenderbotTokenizer, BlenderbotForConditionalGeneration
from transformers import pipeline


# ターミナルで以下実行してまず起動すること!!
# C:\Users\loveanime\AppData\Local\Programs\VOICEVOX\run.exe --host 192.168.2.100 --use_gpu
HOSTNAME = "192.168.2.100"


def is_halfwidth(s):  # 半角のみならTrue
    return all(
        c in string.ascii_letters + string.digits + string.punctuation + " " for c in s
    )


def translate(input_text):
    if is_halfwidth(input_text) == True:
        result = ej_translator(input_text)
    elif is_halfwidth(input_text) == False:
        result = je_translator(input_text)
    result = str(result).replace("[{'translation_text': '", "").replace("'}]", "")
    return result


# BlenderBotと対話
def blender_bot(input_text):
    inputs = tokenizer_blender([input_text], return_tensors="pt")
    reply_ids = model_blender.generate(
        **inputs.to(model_blender.device),
        max_length=200,
    )
    result = tokenizer_blender.decode(reply_ids.tolist()[0])
    return result


ej_translator = pipeline("translation", model="staka/fugumt-en-ja")  # 翻訳用
je_translator = pipeline("translation", model="staka/fugumt-ja-en")

model_whisper = whisper.load_model("medium", device="cpu")  # 文字起こし
_ = model_whisper.half()
_ = model_whisper.cuda()
for m in model_whisper.modules():
    if isinstance(m, whisper.model.LayerNorm):
        m.float()

model_blender = BlenderbotForConditionalGeneration.from_pretrained(  # 会話
    "facebook/blenderbot-400M-distill"  # 400M-distill 3B
)
tokenizer_blender = BlenderbotTokenizer.from_pretrained(
    "facebook/blenderbot-400M-distill"
)
model_blender = model_blender.to("cuda")

recognizer = sr.Recognizer()
while True:
    with sr.Microphone(sample_rate=16_000) as source:  # 「マイクから音声を取得」参照
        print("なにか話してください")
        audio = recognizer.listen(source)
    print("音声処理中 ...")
    wav_bytes = audio.get_wav_data()  # 「音声データをWhisperの入力形式に変換」参照
    wav_stream = BytesIO(wav_bytes)
    audio_array, sampling_rate = sf.read(wav_stream)
    audio_fp32 = audio_array.astype(np.float32)

    result = model_whisper.transcribe(
        audio_fp32,
        verbose=False,  # [00:00.000 --> 00:30.000]を表示するか
        language="japanese",
        beam_size=5,
        fp16=True,
        without_timestamps=True,
    )
    out_text = result["text"]
    """if (
        out_text == "ご視聴ありがとうございました。"
        or "ご視聴ありがとうございました!"
        # or "ご覧いただきありがとうございます。"
        # or "ご覧いただきありがとうございます!"
    ):
        out_text = """ ""
    print(f"認識結果:{out_text}\n")
    # こっから会話BOT
    input_text = out_text
    if input_text == "":
        pass
    else:
        input_text = (
            str(je_translator(input_text))
            .replace("[{'translation_text'", "")
            .replace(': "', "")
            .replace(": '", "")
            .replace("'}]", "")
            .replace('"}]', "")
        )  # 入力を英語にする
        print("翻訳後:" + input_text)
        answer = blender_bot(input_text).replace("</s>", "").replace("<s>", "")
        print("AI氏>>" + answer)
        answer = (
            str(ej_translator(answer))
            .replace("[{'translation_text'", "")
            .replace(': "', "")
            .replace(": '", "")
            .replace("'}]", "")
            .replace('"}]', "")
        )  # 出力を日本語にする
        print("翻訳後:" + answer + "\n")
        # こっから読み上げ
        input_texts = answer
        speaker = 1  # ずんだもん
        texts = input_texts.split("。")  # 「 。」で文章を区切り1行ずつ音声合成させる

        for i, text in enumerate(texts):  # 音声合成処理のループ
            if text == "":  # 文字列が空の場合は処理しない
                continue
            res1 = requests.post(  # audio_query (音声合成用のクエリを作成するAPI)
                "http://" + HOSTNAME + ":50021/audio_query",
                params={"text": text, "speaker": speaker},
            )
            res2 = requests.post(  # synthesis (音声合成するAPI)
                "http://" + HOSTNAME + ":50021/synthesis",
                params={"speaker": speaker},
                data=json.dumps(res1.json()),
            )

            data = res2.content
            p = pyaudio.PyAudio()  # PyAudioのインスタンスを生成
            stream = p.open(  # ストリームを開く
                format=pyaudio.paInt16,  # 16ビット整数で表されるWAVデータ
                channels=1,  # モノラル
                rate=24000,  # サンプリングレート
                output=True,
            )
            # 再生を少し遅らせる(開始時ノイズが入るため)
            time.sleep(0.2)  # 0.2秒遅らせる
            stream.write(data)  # WAV データを直接再生する
            stream.stop_stream()  # ストリームを閉じる
            stream.close()
            p.terminate()  # PyAudio のインスタンスを終了する

3. 実演してみた動画(恥ずかしいので入力音声は合成音声にしてます)

普段は私の肉声で話しかけますが、ここでは「この動画の音声は音読さんを使用しています。」ということで合成音声でいきます。

一連の応答を録画してみました。

日本語チャットボット系のモデルがその辺に落ちていたらいいのに……
追記:無いなら作る!雑談専用モデルをファインチューニングで作成しました

他にも色々面白いAIを動かしているので、是非合わせてご覧ください!!

タイトルとURLをコピーしました