(注:本专栏只讨论相关技术,不涉及任何其他目的,如侵删)
基于ChatGPT、VITS和Live2D的赛博女友/AI老婆最近越来越受到人们的关注。然而,使用ChatGPT时的步骤繁琐、连接不稳定等问题大幅降低了人们的使用体验。针对该问题,本专栏介绍了一种基于VITS+中文GPT2+机器翻译+RenPy的离线简易版AI老婆,可以利用自己电脑的CPU/GPU实现与老婆的实时交互。需要声明的是,受限于数据和设备,本次实现并未包含新的训练过程,所有模型均使用网上开源的预训练模型,因此最后的效果比较粗糙,远不如各up主使用ChatGPT的版本,但也达到了我一开始的预期。本期专栏技术含量不高,只是给大家提供一个思路,欢迎批评指正和交流。
(注:本专栏较长,有一些操作细节可能描述过于细致,导致废话较多,大家可能要做好心理准备)
今年年初,我在B站上看到有up主将ChatGPT和VITS模型结合起来,可以实现与AI老婆的自由对话:
我看完之后大受震撼,惊觉AI居然还有这样的应用(不觉得这很酷吗?这很符合我对理工男浪漫的想象),便想着自己是不是也能尝试一下,于是就有了后面的尝试。虽然自己代码能力很弱,也缺乏调参的经验,但在这个过程中确实受益匪浅。
后面我也注意到,B站上出现了越来越多的up主,用ChatGPT+VITS实现与二次元角色的实时对话,但我自己尝试过一次后,对语音合成模型已经没有那么大的兴趣,而且我对RenPy和Live2D也不熟悉,所以便搁置了自己搭建AI老婆的想法。
直到有一天,有人私信我,说能不能实现一个个性化训练的AI老婆,可以装进U盘里带走,因为ChatGPT的API需要花钱购买和调试,有一点点不方便,希望能做个离线版的。我觉得这个想法有一定道理,这两个星期趁着空闲做了一些尝试,便有了这期专栏。
不过就我个人而言,这期专栏实际上并没有什么技术含量,我只不过做了一些缝合,最后的效果也挺粗糙的,大家权当图一乐。
(今早起来看到GPT4也出来了,不禁感慨AI发展之迅猛,我做的这点小儿科可能不一会就会被重新整合且大幅升级。好时代,来临力)
为了让语音合成模型保持原汁原味,这里我使用的是日文的VITS模型。整体的思路其实很简单,主要包括以下几个部分:
输入中文,GPT2输出回答文本
文本经过预训练的机器翻译模型,输出对应的日文文本
日文文本输入VITS模型,生成语音文件并播放
将上述功能与RenPy结合,利用GalGame引擎实现真正的交互对话
下面是各部分的介绍。
中文GPT2的对话模型我在上一期专栏中有详细描述,具体细节见:
这里我们需要做的事情很简单,先去https://github.com/yangjianxin1/GPT2-chitchat下载完整的项目代码,以及预训练模型文件。解压后,将config文件夹中的config.json和下载的pytorch_model.bin移动到model文件夹中(这一步是使用Hugging Face模型的惯例):
打开interact.py,可以在set_args函数中设置对应的路径(可以不用改),然后在命令行中启动interact.py,做法见https://github.com/yangjianxin1/GPT2-chitchat。如果模型能正常对话,说明GPT2已经配置成功了。
我在网上搜了一下,中日翻译的预训练模型似乎不是很多,目前只找到Hugging Face上的几个模型:
可以选择下载量最高的K024/mt5-zh-ja-en-trimmed模型,我这里选择的是下面那个fine-tuned之后的版本,进入Files,下载其中的5个文件:
新建一个translation_model文件夹,将上面的5个文件移入文件夹中,再将该文件夹置于中文GPT2的文件夹下:
这个模型的翻译能力只能说差强人意,有些句子翻译的不错,但有时候又会抽风,甚至连“晚安(おやすみ)”都翻不对,大家凑合着用吧。不知道有没有大佬知道更好的模型,或是愿意自己训)
语音合成部分,大概效果可以参考原up的视频:
同样,也需要先去https://github.com/CjangCjengh/vits下载完整的项目代码,注意要将文件夹重命名为“vits”而不是“vits-main”,路径中不能带“-”,否则后面的RenPy部分会报错。
vits模型的配置有点麻烦,需要手动编译,如果直接跑会报错“No module named 'monotonic_align.core'”。按照官方的说法(https://github.com/jaywalnut310/vits),需要先在命令行cd到monotonic_align文件夹,然后开始编译,也就是在命令行中输入python setup.py build_ext --inplace:
运行完之后最后两行会显示有error:
这时候运行,仍然会提示“No module named 'monotonic_align.monotonic_align'”,我看到网上有各种做法,对我自己适用的是进入monotonic_align中的build文件夹,然后进入lib.win-amd64-cpython-39文件夹,将该文件夹下的monotonic_align文件夹直接复制到build的上一级文件夹中,相当于最后你的vits/monotonic_align文件夹应该长下面这样(也就是monotonic_align文件夹下还有一个同名的子文件夹):
做完这步之后,如果你直接运行,还是会报错,提示找不到opencc\zaonhe.json。我在Google上搜索了这个问题,按照https://east.moe/archives/1342的说法,需要注释掉cleaners.py中的shanghainese和cantonese:
然后下载预训练模型,网上有不少预训练好的VITS模型,这里我用的是原up的七人语音模型,链接为https://github.com/CjangCjengh/TTSModels。下载完之后需要指定配置文件,注意japanese_base.json和japanese_base2.json对应的是两个不同的模型,一个是7人的,一个是8人的,注意区分(柚子社7人的对应japanese_base.json,实际上和github上的config file是一样的):
如果使用的是七人模型,cleaners选用的是japanese_cleaners,因此需要修改一下text文件夹中的symbols.py,取消japanese_cleaners对应字符的注释,否则会报错模型尺寸不匹配:
到这里VITS就基本配置好了,接下来引入RenPy。
RenPy是一个可视化的小说引擎。在做这一步之前,我对RenPy一窍不通,但官方提供了教程,而且非常简单易懂,相信大家不到半小时就能大概了解它在做什么。这是官方的快速入门教程https://renpy.cn/doc/quickstart.html#ren-py-launcher。由于是初学者,我这里也只使用了一些基本的功能,下面简单介绍一下。
首先,下载完之后打开RenPy,是下面这个界面,点击右下角的“启动项目”即可进入游戏:
建议第一次用的先启动教程,可以大概把基础功能过一遍。然后在设置中把项目目录设置在自己需要的位置(注意:不要有中文路径),然后创建新项目,这样项目目录中就会多了一个子文件夹,里面有game文件夹:
此时在Launcher界面可以看到新建的项目:(如果想打开现有项目,需要在设置里将项目目录改为现有项目的父文件夹,不是game所在的文件夹)
进入game文件夹,里面就是游戏相关的各种文件:
其中,script.rpy就是游戏的文本文件,也是我们需要编辑的,这里的rpy文件参考了https://github.com/cjyaddone/ChatWaifuL2D。
为了让老婆动起来,我们还需要配置好Live2D文件。由于不会画画,也不会用Live2D,这里我直接借用了ChatWaifuL2D中的Hiyori的Demo。具体做法是进入这个项目文件夹中的ChatWaifuGameL2D/game文件夹,将Resources文件夹复制到你自己的game文件夹中(Live2D大佬可以忽略这步,直接把自己的配置文件加进去):
然后按照官方的教程,下载Live2D Cubism:https://renpy.cn/doc/live2d.html#installing-live2d。大概的效果如下:
script.rpy的代码如下:
其中,第一句define定义了你的老婆,而后面两行是用来配置Live2D的。这里需要把Character中的字符串换成你老婆的名字:
RenPy只能导入它自带的python包,而光是一个torch就有几个G,所以把所有包复制到RenPy目录并不现实,因此可以利用socket库实现本地通信,思路来源于下面这篇专栏:
具体而言,初始化时import socket,选择本地通信地址127.0.0.1,代码如下(python关键字会引出一段python代码块,$符号代表这一行也是python语句):
初始化完成后,会进入start语句块,随后跳转到modelChoiceJP语句块,其中menu代表选择支,不同的speaker id对应不同的角色语音,然后程序将speaker id发送给真正运行模型的脚本run_gal.py(代码后文会说)。然后跳转到talk_keyboard语句块,input中的字符串会在对话框中显示(见下图)。随后输入文本也发送给run_gal.py。如果输入退出,会退回到起始界面。
随后,跳转到checkRes语句块,这也是实现交互功能的核心。由于发送和接收有一定延迟,因此如果run_gal.py在生成输出后直接播放语音,会导致语音播放先于RenPy的文字显示,因此在RenPy接收到回复文本后,会向run_gal.py再发送一条允许播放的消息,此时程序才播放对应语音,也符合实时对话的效果:
等待文本生成时,对话框会显示......,需要时不时点一下屏幕(尝试跳转到下一对话),如果已经接收到回复,就会显示对应的文本,同时开始播放语音:
到这里,关于RenPy的部分就大概做完了,接下来介绍真正运行模型的run_gal.py文件。
run_gal.py文件定义在vits文件夹下,代码如下:
首先,我们要导入GPT2文件夹中interact.py中的set_args和top_k_top_p_filtering函数,所以需要将GPT2-chitchat文件夹添加到系统路径:
然后是初始化函数,其中我们需要指定VITS的配置文件(configs/japanese_base.json)和预训练模型文件(我这里是365_epochs.pth)。这里我还修改了一下gpt_args.repetition_penalty的值,具体取值取多少可以自己试一试。
然后是chat函数,参数中的text代表输入文本,返回是GPT2预测的回答文本,这一部分不需要自己改动。顺便提一句,其实GPT2也可以学习前文的提示,这里的gpt_args.max_history_len参数默认是3,即模型输出时会参考之前的3句话。当然,可以把max_history_len调大一点,提高模型提取上下文的能力,不过模型输出一句话的时间也会相应增加。
在主程序部分,首先需要配置端口,作为本地通信socket的服务器:
在主循环中就不断重复接收输入、生成回答、播放语音文件的过程。正如前面提到的,为了避免RenPy中的文本显示与语音播放的延迟,server在接收到client发送的指令之后,才开始播放语音文件:
最后,当server接收到“退出”时会退出当前循环,需要重新输入speaker id,开启新一轮的对话。至此,所有代码部分就讲完了,下面是操作环节。
操作部分就比较简单了。由于socket通信需要先启动server(我猜的,望大佬指正),所以我们先在命令行中cd到vits所在文件夹,然后输入python run_gal.py运行脚本文件,过一会便会显示“socket prepared”,说明server已经启动了:
然后我们打开RenPy,启动项目,可以顺利进入初始界面:
(如果报错提示“由于目标计算机积极拒绝,无法连接”,说明server没启动,可以重新运行一下run_gal.py)
点击“开始游戏”,会进入角色选取菜单:
选择完角色之后,即可进入输入界面:
输入文字后回车,你老婆会表示......:
此时命令行中也会显示接收到的文本,模型输出完成后,命令行中会显示生成的回答文本和相应的日文翻译(有时需要在命令行中回车一下):
然后在你老婆所在的界面点击一下,就会显示回答和播放语音,命令行中也会显示“sound playing”:
这样一次简单的交互就完成了,如果想重新开始就在对话栏输入“退出”,然后回车,这样系统就会跳回到开始界面,老婆的记忆也重置了。
(这里顺便提一下,如果使用的是VS Code编辑rpy文件,后面编辑py脚本的时候可能会提示找不到你安装的那些包(虽然运行是没问题,但是会有warning),这是因为RenPy貌似自带了一个Python解释器,可以在VS Code通过Ctrl+Shift+P重新选择Python解释器)
写到这里,一个简易版的离线AI老婆就做完了。整个过程断断续续持续了两个星期,最终的效果虽然粗糙,但也符合我一开始的预期。但这期专栏说到底还是各种缝合,没什么技术含量,在这个过程中我也没学到太多东西(我甚至懒得训练一个VITS),最多算一个支线吧,大家看得开心就好。我自己还是偏向于做一些GalGame和AI结合的项目,下一个项目的主题我已经想好了,是图像相关的,感觉有一定难度,也有可能最后做不出来,看看什么时候有时间尝试吧。