这不是你期望的介绍深度学习大模型的文章。而是使用人人都能理解、编程、运行的简单技术(包括机器学习和非机器学习技术),通过一系列工程进行整合、组装,从而实现一个 AI 虚拟主播的故事。
muvtuber
让 AI 成为虚拟主播:看懂弹幕,妙语连珠,悲欢形于色,以一种简单的实现
元宇宙终于不再铺天盖地,ChatGPT 又来遮天蔽日。看不下去了,那当然是选择——加入这虚浮的演出,诶嘿~(误)
想法
蹭热度的想法是这样:元宇宙 => 皮套人,ChatGPT => 中之人,放到一起:AI 主播!
咳,实际上没那么假大空啦。真实的故事是这样:
既然我可以让 AI 看懂你的心情,并推荐应景的音乐,以一种简单的实现,那么,我还想再进一步!让 AI 站到唱机背后,聆听你的故事,为你播出此时此刻萦绕心中、却难以言表的那段旋律。曲终意犹未尽当时 ,一段 AI 细语入耳更入心 —— 机器不是冰冷的玩具,她比你更懂你,至少她更懂那个真实的你:她不会说谎,而你每天都在骗自己。
这个想法指向的产品是一个 AI 电台,我把它叫做 muradio。在细化设计的过程中,我不得不考虑电台的传播问题。我当然可以在本地完成一切,让 AI 电台只为你一人播,这样实现起来还简单;但是,电台果然还是要大家一起分享吧。所以,我考虑用一种现代的方式——视频直播。
那么,在实现 muradio 之前,先让 AI 学会直播吧!
设计
我把这个 AI 主播叫做 muvtuber 1。
人是如何做的
要设计一个直播的机器,其实很简单。首先考察人是如何完成直播的:
- 首先需要一个人作为主播:这个人是会动会说话的,心情是会写在脸上的;(只是以普遍理性而论,非必要条件,更非歧视残障人士。)
- 在开始前有一个预订的主题,也可能没有;
- 主线:推进主题:打游戏、一起看、点歌 …;
- 支线:主线被弹幕打断,转而与观众互动:聊天;
- 最后,需要一个直播推流工具,比如 OBS。
互动是直播的灵魂。直播没有主题仍是直播,而没有了互动就成了电视。简单起见,我们先忽略主题问题,只考虑注入灵魂的互动实现。互动的过程如下:
- 观察:看到弹幕
- 过滤:选择性忽略一些没有意义或太有意义的话
- 思考:对过滤后留下来的话进行思考 => 回应的话
- 过滤:想到的部分话可能是不适合说出的,也要过滤掉
- 发声:把回应说出来
- 动作与表情:伴随观察、思考、表达这个流程的动作控制、表情管理。
用机器模拟人
清楚了直播的处理过程,就容易用机器模拟:
- 主播:我们可以用 Live2D 模型来作为主播本人。
- 看弹幕:使用弹幕姬类程序,暴露接口即可:
func danmaku(room) chan text
- 思考:机器文本生成:
func chat(question) answer
- 发声:机器语音合成:
func tts(text) audio
- 过滤:
func filter(text) bool
- 动作表情:Live2D 模型可以具有动作、表情,可以通过某种方式驱动。
Live2D 和弹幕姬是现成的解决方案,而从看弹幕、到思考、到发声的整个流程也是非常模块化、函数式的,直观清晰。难点是如何让 Live2D 模型动起来?
模型何时该摆什么样的表情,何时该做什么样的动作?真人主播解决这个问题靠得是本能,虚拟主播解决这个问题靠得是动捕——归根到底还是人。看来现实不能直接借鉴,那么只好做个抽象了。
我认为,表情是心情的反映,动作是表达的延伸。淦我写不清关于心情 => 表达 => 动作
,所以 动作 = f(心情)
的观点😭
(怎么会有人问 ChatGPT “我要如何解释我的观点?”😭)
所以说,我们可以用“心情”来驱动 Live2D,赋予 AI 主播表情与动作。
那么,我们又如何让机器拥有“心情”呢?事实上,在前文提到的 让 AI 看懂你的心情,并推荐应景的音乐,以一种简单的实现 一文的“中文文本情感分析”小节中,我们用一种及其简单的方式,实现了一种 文本 -> 心情
的模型。这里我们就沿用当时的成果,不作额外介绍了,新读者可以先去阅读那篇文章的对应部分。
总之,我们可以用“思考”的输出,即主播要说的话,通过文本情感分析技术,反推出主播的心情,然后根据心情,就可以控制 Live2D 模型做出合适的表情、动作。
整体设计
小结一下上述设计,我们用机器来模拟人类主播,完成与观众的闲聊交互,整个过程如下图所示:
我们把 muvtuber 分成了一个个独立的模块。每个模块使用独立的技术栈,实现自己的工作,并通过某种方式暴露 API,供他人调用。最后,我们会通过一个“驱动程序”muvtuberdriver
来调用各个模块,通过一个顺序流程,实现上图所示的数据流动。
组件实现
本节介绍对上述 muvtuber 设计方案的实现。我会先写各个独立组件,再写如何把他们组装到一起完成工作。喜欢“自顶向下”的读者请自行安排阅读顺序。
这是一个拥有众多组件的项目,装起 numpy 从头撸似乎不够明智。我们会心怀感激地借助一些开源项目。我希望提供一种最简单的实现——面向麻(my)瓜(self)编程——只写必要的、能理解的代码。
先修课程:这是个 Web 前端 + 后端 + 机器学习的综合项目,需要使用到 Vue(TypeScript)、Go、Python 等相关语言和技术。
文短码长,下文只给出必要的设计、核心的代码,忽略错误处理、并发安全等等工程中实际的问题。真实的代码实现,请读者参考源码:
服务 | 说明 |
---|---|
xfgryujk/blivechat | 获取直播间弹幕消息 |
Live2dView | 前端:显示 Live2D 模型 |
Live2dDriver | 驱动前端 Live2D 模型动作表情 |
ChatGPTChatbot | 基于 ChatGPT 的优质聊天机器人 |
MusharingChatbot | 基于 ChatterBot 的简单聊天机 |
Emotext | 中文文本情感分析 |
muvtuberdriver | 组装各模块,驱动整个流程 |
弹幕姬
技术栈:Node,Python。
为了和其他模块对接,我们需要那种能通过比较底层的方式输出弹幕的工具。我们要给机器看的,而不是一个给人看的黑箱的图形界面。
好在也不必自己从头写。xfgryujk/blivedm 就是这样的一个工具。作者大大还顺别提供了显示给人看的界面 blivechat。
这里我们直接使用封装好的 blivechat
。这个东西后端通过 WebSocket 把弹幕、礼物等消息发送给前端的界面。拉取、编译、运行 blivechat(blivechat 的 README 里有详细的安装、使用方法,此处从简。):
# clone repo
git clone --recursive https://github.com/xfgryujk/blivechat.git
cd blivechat
# 编译前端
cd frontend
npm install
npm run build
cd ..
# 运行服务
pip3 install -r requirements.txt
python3 main.py
虽然没有文档,但稍微抓包,就可以模仿出一个前端,通过 WebSocket 接口获取到弹幕:
// 建立链接
let ws = new WebSocket('ws://localhost:12450/api/chat');
// 订阅直播间
ws.send(JSON.stringify({
"cmd": 1,
"data": {
"roomId": 000 }
}));
// 接收弹幕消息
ws.onmessage = (e) => {
console.log(JSON.parse(e.data))
};
// KeepAlive:每 10 秒发一次 heartbeat
setInterval(() => {
ws.send(`{"cmd":0,"data":{}}`)
}, 1000 * 10);
除此之外,我们还可以使用它自带的图形界面(有没有一种可能,这才是这个项目的正确用法),这个弹幕框可以由 OBS 采集输出到直播间里:
优质聊天机器人:ChatGPT
技术栈:Python
这应该是最有意思的部分,却也是最难的。幸好,有 ChatGPT。
acheong08/ChatGPT 项目提供了一个很好的 ChatGPT 接口。
安装:
pip3 install revChatGPT
使用:
from revChatGPT.V1 import Chatbot
chatbot = Chatbot(config={
"access_token": "浏览器访问https://chat.openai.com/api/auth/session获取"
})
prompt = "你好"
response = ""
for data in chatbot.ask(prompt):
response = data["message"]
print(response)
我们只要编写一条合适的 prompt,即可让 ChatGPT 化身“虚拟主播”,例如:
请使用女性化的、口语化的、抒情的、感性的、可爱的、调皮的、幽默的、害羞的、态度傲娇的语言风格,扮演一个正在直播的 vtuber,之后我的输入均为用户评论,请用简短的一句话回答它们吧。
大概是这种效果:
我并不擅长写 prompt,上面这段也改编自网络(原版大概是让做猫娘的),如果你有更好的 prompt,欢迎与我分享。
注:prompt 笑话一则:
嘿,知道吗?以后不需要程序员啦!只要描述出需求,ChatGPT 就能写出代码!
——太棒了,那么如何写出简洁清晰无歧义的需求描述呢?
呃,未来或许会有那么一种语言…
——你说的可能是:计算机程序设计语言 😃
我们把这个东西简单封装出一个 HTTP API,方便其他组件调用:
$ curl -X POST localhost:9006/renew -d '{"access_token": "eyJhb***99A"}' -i
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Content-Length: 2
ok
$ curl -X POST localhost:9006/ask -d '{"prompt": "你好"}'
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Content-Length: