ChatGPT 与 TTS 之间奇妙的反应

一种很新的智能语音助手

Mar 14, 2023
3007
#ChatGPT#TTS#语音助手

本文最近一次更新于 6 个月前,其中的内容很可能已经有所发展或是发生改变。

https://cdn.blog.itswincer.net/img/chatgpt-and-tts-work-together.webp

其实早在去年底 ChatGPT 刚发布的时候,我就申请到了账号,不过当时的用户体验并不算好:ChatGPT 在说大段话的时候会突然断掉,然后还经常会出现请求量过多导致服务拒绝的情况,所以也就开始尝试了几天,新鲜感满足后就搁在一旁了。

谁知过了两个月之后,ChatGPT 在国内的概念突然又被炒了起来,它的更多用法也被挖掘了出来,同时它本身的用户体验也提升了不少:不会再出现大段话突然断掉的情况;拒绝服务的情况也越来越少了;还支持了历史对话等。于是这时候我开始在有意识地使用 ChatGPT 来处理一些日常的事物了:比如写 PPT、常识性的知识问答,以及重构代码等。虽然在重构代码这一块,错误还是不少,但也确实会给予我一些灵感。

当时就有一个想法,ChatGPT 什么都好,就是只有一点不好,只能通过打字交流(咳咳,要是能通过语音交流的话,面试啥的岂不是都可以用上了?)。可惜 ChatGPT 一直没有公开 API,想使用的话估计只能用无头浏览器来模拟了,不过这样一来还要破解 Cloudflare 的人机验证,工作量太大了…

终于,在三月的时候 OpenAI 发布了 ChatGPT 的 API 以及对应的语音转文字模型 Whisper 的 API,之前想与 ChatGPT 语音交流实现起来应该没多大难度了,不过我这人有点懒,还是缺一个契机或者动力来做这个事。直到前几天的时候,女朋友转给我她的一个学长在朋友圈发布的视频,视频内容正是通过语音与 ChatGPT 进行对话,她觉得很有趣,问我能不能给她做一个,我想这正巧和我的想法不谋而合,于是乎,我就开始研究了这件事起来。

模块拆分

很显然,要通过语音与 ChatGPT 对话,整体的功能可以分成 3 个模块完成,分别是:

  1. 语音识别模块:这部分将用户的语音转化成对应的文字,也即是 ChatGPT 的输入;
  2. ChatGPT 模块:这部分就是把文字发送给 ChatGPT,然后把ChatGPT 的回答保存下来;
  3. 语音合成模块:这部分就是把 ChatGPT 的回答转化成语音,然后播放。

以下是设计图:

image

下面我会分别阐述一下这三个模块的设计与实现。

语音识别模块

这个模块是我耗费时间最久的,我一开始并没有打算使用 Whisper API(白嫖惯了的人,总是想找个免费的),而是打算使用离线的方案,主要是考虑到用这种 API 会涉及到网络层面的开销,拖慢语音识别的速度,用户的体验也会相应地打折扣。

我相继尝试了 PocketSphinxDeepSpeech这两个离线的语音识别方案,可效果都不太理想。PocketSphinx 的准确率实在差劲,DeepSpeech 的准确率倒是还可以,就是识别的速度太慢了,而且离线的方案还需要我自己来维护一套训练模型,我这半吊子的机器学习水平,实在懒得维护这些模型。

于是兜兜转转我还是回到了找云服务商提供的语音识别方案上,找了半天,终于找到了一款 Python 的开源库 SpeechRecognition,虽然是个开源库,但它本身集成了市面上大部分服务商提供的语音识别方案,虽然大部分都是付费的(需要你自己去服务商购买然后输入 API KEY),但好在还是有 Google 家的方案还可以免费使用。

于是语音识别模块我基本上都是封装了 SpeechRecognition 这个库的一些操作,等后续如果发现有什么用得不爽的地方我再来自己定制。

ChatGPT

这个模块是最短时间完成的,也没啥好说的,直接调用 OpenAI 的 API 即可,不过我稍微研究了下 API 的参数,相比较默认的 API 进行了两个调整:

  1. temperature:按 OpenAI 的说法,较低的值会让回答更加稳定,比如用同样的语句问,temperature 越大,回答的答案也可能越会不同;
  2. messages:对于 messages 我更细化地进行了 2 个调整:
    1. 在所有会话的前面都加上 role=system 的指令,这一目的是让ChatGPT 的每一次回答都遵循 system 的指令,我目前设置的指令是:「Answer in concise language」,也就是用简洁的语言回答,如果不加这个指令,稍微长一点的话题就会反复说一些废话,这不利于对话的展开。这个指令在配置文件里是可更改的;
    2. 默认保留最近 3 次对话内容,ChatGPT API 默认并不会关联上下文的会话,这是与 Web 端最大的区别,如果想要关联上下文,那就只能把之前的会话内容一起发送,如果不限制一下保存的会话次数,那么越往后面消耗的 Token 就越恐怖;

我目前的调教方法是,普通聊天就保持默认配置不动;如果想要进行特殊的会话,比如让它当百科全书来回答问题,那么会话保留的条数可以更短,system 指令可以设置成:「尽可能详尽地回答」;如果想要练习英文口语,那么可以设置 system 指令为:「Play a English teacher, Point out grammatical errors and ask questions according to the context」;如果想要模拟面试,那么可以设置 system 指令为:「扮演一名 xx 岗位的面试官进行面试,简洁地回答和提问」。

语音合成模块

这个模块也颇费了我一番功夫,一开始我也是想找离线的方案,同样也是因为想节省一些网络的开销,让对话进行地更顺畅。我也找到了对应的离线方案:pyttsx3,这个方案是调用操作系统本身的 TTS 引擎来朗读:Windows 是 SAPI5,MacOS 是 NSSpeechSynthesizer,其他平台使用 eSpeak。

这个方案虽然免费、省事,但是 pyttsx3 这个库有一个很严重的 bug:它不支持多线程,它如果放在非主线程之外运行,Speak 的时候不能阻塞住,五年前就有人在 Github 提了这个 Bug,反正目前是还没有解决。

因此我不得已只能放弃了这个离线方案,转而白嫖起了 Azure 和 Google 的 API)——分别是由 edge-ttsgTTS开源库提供。

另外值得一提的是,我优化了语音合成播放的流程,将 ChatGPT 的交互模块与语音合成模块解耦成两个线程,之间用队列通信。如果是线性地执行,等 ChatGPT 的回答完全返回之后再进行朗读的话,在 ChatGPT 回答比较长的时候等待的时间太久了,因此每当 ChatGPT 的一句话说完的时候,就先把这一句话的内容发送到队列中,同时语音合成的模块会不断地从队列取数据开始朗读。

这样,用户体验会好不少。

链接

演示视频:https://www.bilibili.com/video/BV1rY411z7tA/

代码仓库:https://github.com/WincerChan/talkgpt

一些思考

高中的时候,我的数学老师挺有趣的,我在之前的文章里也有提到他。他并不是数学专业毕业,而是学机械的,后来因为找不到相关的工作就转行来当数学老师了。

在某天上课的时候,他给我们普及历史上的三次数学危机(忘记是什么话题引起的了):第一次是因为无理数、第二次是因为无穷小、第三次是因为罗素悖论,老师像讲故事一样讲完三次危机之后,问了我们一个问题:你们觉得还会不会有第四次数学危机的出现?当时一下班上都叽叽喳喳讨论起来了,无非都是说「应该不会有吧?」、「如果有的话那是什么呢?」之类的话,他在讲台上静静地看着我们讨论了一会,然后说:「看来你们都觉得再有数学危机发生了吧」

看着大家都没有反对,应该是默认了他的话。紧接着他说:「我不这么认为,我想接下来一定还会有第四次、第五次数学危机,我们研究数学的,不能总是按照常识来做判断,在前几次危机发生前,当时的大家也不认为还会有数学危机发生,可事实就是发生了,所以我断定只要人类文明还在发展,接下来一定还会有数学危机发生」(原话已经过了快 10 年了,我自然是无法复述,只能说个大概的意思)。当时的我虽然听不太懂,但是也给我弱小的心灵带来了很强的震撼,现在回过头来我想我有点能理解了。

我们是属于科技行业的人,在 ChatGPT 这波「浪潮」来临的时候,我们首先感受到了,至于是忽略这波浪潮还是利用这波浪潮,在写这个小工具的时候,我想我已经有了决断了。

总结

花费了几天的空余时间,也算是把这个工具初步完成了。虽然使用起来还比较粗糙,但剩下的打磨可以慢慢进行了。

最近一段时间人有些浮躁,没怎么专心研究技术,做完这个项目下来,感觉还挺好的。我会慢慢拾起对技术的热情。

ChatGPT 与 TTS 之间奇妙的反应

https://blog.itswincer.com/posts/chatgpt-and-tts-work-together/

作者

Wincer

更新于

Mar 14, 2023

许可协议

CC BY-NC-ND 4.0
  1. May 5, 2021

    Type-Length-Value Encoding Scheme Practice
  2. Jul 8, 2020

    基于 ETS 的漏斗限流
  3. Apr 22, 2020

    使用 OpenCore 引导黑苹果踩坑记录
  4. Mar 16, 2020

    Kubernetes 初探
  5. Jul 4, 2019

    记一次反向代理的搭建
  6. Apr 27, 2019

    高校生使用教育网的一点姿势