ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

计算机如何学会说话:从语言模型到语音合成实战

计算机如何学会说话:从语言模型到语音合成实战

凌晨一点,我盯着屏幕上的对话气泡。我敲了一句“今天工作好累”,对面秒回了一段安抚的话,语气温柔,甚至带了个省略号。这台计算机正在“说话”——而且说得越来越像人。十年前如果有人跟我说计算机能这么自然地聊天,我一定会觉得这是科幻电影没关掉的声音。但今天,“计算机也会说话”已经不只是实验室里的Demo,它正在变成客服、语音助手、内容创作工具里最常见的基础能力。

这篇内容就是想拆开“计算机说话”这扇奇怪的门,看看背后到底是什么机制在运转。我会从语言模型最基本的原理讲起,讲到文本怎么变成真正的声音,再给出一个能直接在本地跑起来的“让它开口说话”的最小Demo,最后分享一些我在实际落地时踩过的坑和调优经验。不管你是刚接触AI的普通用户,还是正在做语音/文本产品、想给项目加上“对话能力”的技术人员,这篇文章都适合从头看一遍——因为不了解底层逻辑就去调参数,大概率只会得到一台说话很流利的“机器”,而不是一个“说话像人”的助手。

1. 会说话的背后:从“按键音”到“生成式表达”

1.1 误以为的“说话”:先分清舞台和演员

很多人对“计算机说话”的第一次感知,是早年电脑开机时的“叮”一声,或者电子词典里那个模仿人声读单词的功能。但那不是“说话”,是“播放”。系统里存了一段音频,按键触发后把它放出来,本质上和我们用录音机听磁带没有区别。后来聊天软件里的机器人开始回复“你好,我是小助手,请问有什么可以帮您”,那也不是“说”,那是提前把几十种常见问题的话术写进脚本,程序匹配关键词后“挑一句”回复你。这些话术之间互相不关联,问得稍微偏一点,它就卡壳了。

我经常跟刚入门的朋友说,判断一个系统是不是真的在“生成语言”,最简单的测试方法是:给它一个它绝对没见过的、结构很怪的问题,看它回答时是在话术库里翻答案,还是从零开始组织一个新的句子。过去的规则系统是前者,而今天我们能聊天的模型是后者。这个差别,就是“会说话”和“会播放话术”的分界线。

1.2 深度学习带来的分水岭:从匹配话术到生成内容

真正让计算机开始“说话”的,是语言模型这套思路。语言模型做的事情,听起来其实简单得可怕:给定上文,预测下一个词是什么。你给它“今天天气真”,它算一下“好”“不错”“差”各自出现的概率,然后挑一个写出来。再拿写出来的内容当“上文”,继续预测下一个词。如此循环,一个词接一个词,一整段话就被“吐”出来了。

关键来了:如果训练数据足够多,模型足够大,它学到的就不只是“词语接龙”,而是句法结构、常识逻辑、甚至某种程度的语义关联。你问它“今天工作好累”,它不会只接一个“啊”,它会顺着“工作累”这个主题,生成一段“你最近是不是太拼了,记得休息一下”这类在新数据里从未完整出现过的、真正由机器新组织出来的句子。这就是“生成式表达”,也是计算机“会说话”在技术层面真正的起点。

1.3 “会说话”的三个层级

整篇文章的结构,我也想借这个机会一起说清楚。“计算机说话”至少包含三个层级:

一是“有内容”:模型能根据你的输入,生成一段合理、连贯的文字。这是语言模型负责的部分,也是“像人说话”的地基。二是“有声音”:生成文字之后,还要让文字变成可以播放的音频,说得有抑扬顿挫、有停顿、有语气,这部分是语音合成(TTS)负责的。三是“有来有回”:一个完整的对话系统,还需要把你说的话先转成文字(语音识别ASR),再交给语言模型,再转成声音播出去。这三层每一层都是一门独立的学问,但它们串起来,才是我们感知中的“计算机说话”。

下面我就按这个线索,一步步拆开讲。

2. 机器学“人话”的底层逻辑:Token 与概率接龙

2.1 词元:计算机读到的“字”不是你以为的字

先说一个最常见的认知偏差。很多人以为,计算机处理中文的时候,是以“字”或者“词”为单位的——比如“我”“喜欢”“苹果”,一个词一个词地存。这样想不算错,但实际工程里完全不这么做。因为中文词汇量太大了,加上新词不断出现(去年流行的网络新词,今年就可能过气),如果每个词都单独编号,词表会爆炸。而且,碰到一个没见过的词,系统就直接“不认识”,根本没有办法处理。

行业里通用的做法,是先把文本切成更小的单位——子词,这个单位在各类框架里通常叫Token。比如“说话”这个词,可能被切成一个Token,也可能被切成“说”和“话”两个Token,具体取决于词表怎么训练的。这种做法的好处是:即使遇到“计算机也会说话吗”这种从来没有完整出现过的句子,模型也能把它拆成训练时见过的子词组合,照样能理解和生成。你可以把Token想象成乐高积木里最基础的颗粒,句子是用颗粒拼出来的造型。拼接逻辑不一定是你以为的“按词拼”,但最终拼出来的效果足够稳定。

2.2 自回归生成:一次只吐一个Token

接下来是生成的核心流程。目前主流对话模型都是自回归结构,意思就是“自己回归自己”:每次只预测一个Token,预测完把它拼到输入后面,再继续预测下一个。这个过程可以用一段极简的伪代码来描述:

def generate_one_token(prompt_text, model, max_len=1): token_ids = tokenizer.encode(prompt_text) for _ in range(max_len): probs = model.predict_next_token(token_ids) # 计算所有候选Token的概率 next_id = sample_from_probs(probs) # 按概率采样,挑一个 token_ids.append(next_id) prompt_text += tokenizer.decode([next_id]) return prompt_text

这里最容易被忽略、也最重要的,是sample_from_probs这一步。你把一串文字输给模型,模型会算出一个长长的概率表,表里每个候选Token都有一个0到1之间的概率。如果每次都直接选概率最大的那个Token,生成出来的内容会很平、很板正,甚至会出现“复读机”现象——因为它总走最安全的路径。但如果你纯随机地按概率抽,语气又可能太飘。怎么平衡,就是下一小节要讲的采样策略。

2.3 让接龙变得更“像人”:温度、Top-p 与重复惩罚

为了让生成的文字更像真人说话,工程上有几个特别常用的“旋钮”。我直接用一个表格把它们拆开讲:

策略直觉解释参数调大后果参数调小后果我的建议
温度(Temperature)控制概率分布的“锐利程度”,相当于给每个词的概率打分后做缩放回答更随机、更有想象力,但容易跑题、胡言乱语回答更保守、更安全,但容易无趣、模板化闲聊场景设0.8左右,任务型问答设0.2-0.5
Top-p(核采样)只在累计概率达到p的那一小撮候选词里抽样,砍掉低概率的“尾巴”候选范围大,内容更发散候选范围小,更稳定配合温度一起调,常用0.7-0.9
重复惩罚对已经出现的Token加大惩罚分,降低它再次被选中的概率惩罚太大会让语句变得破碎、不连贯惩罚小则容易复读遇到复读问题再加,从1.1倍起试

温度的原理,我用一个生活中的例子解释:想象你在食堂点菜,师傅手里有一份菜单,每个菜的“受欢迎程度”不一样。温度很低时,你基本只点最受欢迎的那一个;温度很高时,你可能会试试那些没人点过的菜,偶尔点到暗黑料理,但也偶尔会点到隐藏美食。AI写作时想要“稳定但不死板”,就要在温度和Top-p之间找一个平衡点。我在自己调参的经验里,最常用的组合是temperature=0.7, top_p=0.9,再配合极小的重复惩罚,这种配置在大多数中文对话场景下都不会出大错。

3. 从文字到声音:让声音真正“发出来”的合成链路

3.1 文本转语音不是“对着文本念”那么简单

模型生成了一段文字,但文字还躺在屏幕上,没有变成空气里的振动。要让它“出声”,就必须走语音合成(TTS)这条链路。很多没接触过TTS的人觉得,这就是“把文字逐字读出来”,实际上远没那么简单。一句话里哪里有停顿、哪里重读、哪个字变声调,人脸做起来毫不费力,但要让机器做到,得拆成好几个子任务。

一个典型的TTS链路大致是这样:先把原始文本做前端文本分析,包括分词、数字归一化(比如“2.5万”要处理成“两万五”还是“二点五万”)、多音字消歧(“重庆”的“重”读chóng还是zhòng);接着做韵律预测——决定哪里该停顿、停顿多长,这是机器声音“自然不自然”的关键;然后交给声学模型,生成声谱图之类的中间表示;最后用声码器把声谱转成实际的音频波形。这四个阶段,每一步做不好,声音都会“露馅”。

3.2 为什么老式TTS像机器人:参数合成与拼接的局限

你小时候如果有机会用过早期的中英文朗读软件,一定会记得那种一字一顿、完全没有感情的“机器腔”。那种声音让人一听就知道“这是机器”,是因为它用的是旧方案:要么把语音拆成很短的单元(比如一个音节一个音节存起来),识别到文字后“拼接”播放,拼接处总有生硬断裂感;要么用一组参数去逼近声带振动,算出来的波形干净到失真,少了人声里最关键的随机性和气息感。真人说话之所以好听,恰恰是因为声音里有大量不被注意的“不完美”——气音、轻微抖动、口唇变化时的过渡音。这些在拼接和参数化的时代,几乎无法建模。

3.3 端到端时代的技术选型与基础参数

直到端到端TTS模型出现,情况才彻底改变。这类模型不再走“拼语音单元”的老路,而是直接学习“文本到声谱”的映射,再用神经声码器还原成波形。现在的云平台TTS接口和一批开源语音模型,基本都能做到“听不出来是机器”的水平。

如果是自己动手接入,有两条路线。一条是直接用云端API——传一段文字,返回一个音频文件,优点是省事、音质稳,缺点是数据出境、按调用量计费、有网络延迟。另一条是在本地跑开源模型——灵活性高、离线可用,但需要一张能跑的显卡,显存8GB以上会舒服很多。无论走哪条路线,有两个基础参数我建议你先记牢:

参数常见值影响
采样率22050 Hz 或 24000 Hz值越高音质细节越好,文件也越大;过低会发闷、有“电话味”
音频格式WAV / MP3 / OGGWAV无损但体积大;MP3适合传输;OGG体积小音质尚可,很多TTS服务默认它

我第一次接TTS时,犯过一个特别低级的错误:模型输出的采样率是22050Hz,但我在播放器里用了44100Hz去强制播放,结果声音整个变快了半拍,像开了倍速。这类“声音发飘、发尖”的问题,十有八九是采样率不匹配导致的。先确认采样率,再去查别的,千万别一开始就怀疑模型不行。

4. 第一次让它“开口”:搭建一个会说话的Demo

4.1 Demo目标与整体架构

理论归理论,真正让人兴奋的时刻,是你亲手把代码跑起来,对着麦克风喊一句,机器用自然的声音回你一句。这里我不堆概念,直接给出一个最小架构,目标是:你在终端输入文字,它生成回复,再把回复变成语音文件播放出来。如果你想把语音识别(ASR)也加上,做成完整的“语音助手”,只需要在中间多加一步“录音转文字”,架构会从“输入文字 → 大模型 → TTS”变成“ASR → 大模型 → TTS”,核心逻辑完全不变。

这句话值得多强调一遍:市面上那些看起来很科幻的AI语音助手,本质就是这三个模块串起来的管道。单看每个模块都不算神秘,串起来才显得“像个人”。

4.2 最小代码实现:模型加载与对话循环

我选一个比较常见、在社区里资料也多的开源对话模型来做示例。下面的代码是一个极简的对话循环,你用任何一台上网的主机都能跑,只需要提前装好Python环境和一个支持推理的深度学习框架:

import os def chat_once(model, tokenizer, user_input): messages = [ {"role": "system", "content": "你是一个说话自然的语音助手,回复请用短句,不要长篇大论。"}, {"role": "user", "content": user_input} ] input_text = tokenizer.apply_prompt_template(messages) output_ids = model.generate( input_text, max_new_tokens=256, temperature=0.7, top_p=0.9, repetition_penalty=1.1 ) return tokenizer.decode(output_ids) def main(): print("你说的每一句话,我都会回答。输入 quit 退出。") while True: user_input = input("\n你:") if user_input.strip().lower() == "quit": break reply = chat_once(model, tokenizer, user_input) print(f"TA:{reply}") if __name__ == "__main__": main()

代码逻辑本身没有多神奇:把用户输入拼进对话模板,调generate函数,解码输出。真正值得借鉴的是那三行Prompt提示——我在system prompt里专门写了“请用短句,不要长篇大论”,这会让后续接TTS时更容易合成出自然的声音。因为TTS对长句子的断句处理,比短句子要麻烦得多,产出的停顿也更容易出错。

4.3 接通语音输出:文字到音频落地

文字对话跑通之后,加上语音输出其实非常简单。现在主流的云端TTS接口,基本都遵循“POST一段文本,返回音频流”的RESTful风格。本地开源TTS方案也大同小异,只是把请求HTTP换成本地函数调用:

import requests def text_to_speech(text, output_path, api_key): headers = { "Authorization": f"Bearer {api_key}", "Content-Type": "application/json" } payload = { "text": text, "voice": "zh-CN-Natural", "audio_format": "wav", "sample_rate": 22050, "speed": 1.0 } resp = requests.post( "https://your-tts-endpoint.example.com/v1/synthesize", headers=headers, json=payload, timeout=30 ) if resp.status_code == 200: with open(output_path, "wb") as f: f.write(resp.content) print(f"已生成语音文件:{output_path}") else: print("TTS请求失败,状态码:", resp.status_code, resp.text)

调用接口时,有几个小细节值得特别注意。第一,文本里如果有“,”“。”这类标点,TTS会依据标点处理停顿,所以在把模型输出交给TTS之前,最好先把过多换行符和Markdown符号清洗掉,不然它可能会把“#标题”都读出来。第二,sample_rate参数要和播放环境匹配,很多平台支持22050、24000、44100甚至48000,但并不意味着越高越好——如果后续要走低带宽传输,24k已经足够清晰,文件体积还能小一半以上。第三,speed参数建议先设1.0,等听到实际效果后再微调,从1.0直接跳到1.25,声音会立刻显得“赶”。

4.4 第一次跑不通的常见原因

这个Demo我在不同配置的机器上都跑过,发现新手最常见的三个问题其实高度一致。我整理成一个清单,如果你卡住了,按顺序排查基本能解决:

现象最常见原因处理方式
文字回复正常,但生成语音时长太长或太短采样率不匹配,或TTS文本里残留Markdown符号确认模型输出采样率;清洗文本,去掉*、#、换行符
模型生成很慢,甚至报显存不足本地推理显存不够,或上下文过长关闭网页浏览器等占显存程序;限制max_new_tokens;不行就换云端API
TTS声音“一个字一个字往外蹦”文本断句粒度不对,或标点丢失检查传入TTS前文本是否保留了完整的、;!等标点,必要时人工加标点
程序报错401API密钥未正确设置不要直接硬编码在代码里,用环境变量读取

多说一句,API密钥管理这一点虽然不起眼,但很重要。很多教程为了让演示简单,会把密钥直接写在代码里,传出去之后密钥一旦泄露,账号就会被盗刷。我建议从一开始就养成用环境变量的习惯:os.environ.get("TTS_API_KEY"),哪怕只是本地Demo,也别嫌麻烦。这个东西就跟安全带一样,平时用不上,用上的时候救你一命。

5. 效果调优与常见陷阱:从“能说话”到“会说人话”

5.1 文本层:让回答读起来有“人味”

Demo能跑通之后,你大概率会遇到一个尴尬:机器说的话虽然语法正确,但听起来就是“一股AI味”。我总结了一下,AI味最重的地方通常有两个。第一个是句子太长、分句太多——人类说话很少一口气讲一长串,基本是“短句+停顿+补充”;第二个是语气词和过渡词太规整,“首先……其次……最后……”这种框架在书面文章里还好,口语对话里一出现就非常出戏。

解决这个问题的办法,不在模型层,而在Prompt层。我会在系统提示里直接加上这样一段话:“用口语化的短句,像朋友聊天一样;不要用‘首先、其次、综上’这样的过渡词;回答控制在三句以内;允许在一定范围内使用‘嗯’‘其实’‘不过’这类语气小词。”实测下来,同样的模型、同样的参数,加了这段话之后输出的文本再交给TTS合成,整体听感会自然非常多。这个调整的成本是零,效果却常常比换一个大模型更明显。

5.2 声音层:多音字、停顿、中英文混读怎么处理

文本像人话之后,声音层又会冒出新问题。多音字是中文TTS最难缠的一个点。我手里有个蛮有代表性的例子:用户问“三乘以二点五万等于多少”,模型回答“等于七点五万”,但TTS把“点五万”读成了“点五wan”还是“点五mò”?这个字的音完全取决于上下文,规则根本写不全。很多中文TTS接口支持“自定义发音”或“SSML标记”,你可以在合成前把容易读错的字词替换成拼音注释,比如把“点五万”处理成“点五wan”,手段土但稳定管用。

还有一个特别常见的问题是停顿。标点符号在TTS里是“休息指令”,逗号停半拍,句号停一拍,问号语调上扬。如果模型输出的文本没有标点、或者一行换一个完整长句,声音就会连成一片,听着很累。我的习惯是,在交给TTS之前,先做一步轻量的文本后处理:超过20个字且没有标点的句子,主动在关键词后补一个逗号。这属于“小技巧换大体验”的典型,改动一行代码,听感提升半个量级。

5.3 工程层:延迟、并发与成本,以及我最后想说的

最后聊工程层面的一些体会。一个对话型语音系统,真正上线后你最先遇到的是延迟问题。大模型生成文字需要时间,TTS合成也需要时间,两段延迟叠加,用户就会觉得“它反应慢半拍”。业界常见的做法是“流式输出”——模型每生成一小段文字,就先送给TTS合成这一段,音频一边生成一边播放,不用等全文完结。如果你只是做Demo,可以先不做流式,但心里要清楚:这个优化点是迟早要面对的。

并发和成本同样要提前想。本地跑模型时,一张显卡同时只能服务有限几个用户,请求一多就排队;云端API虽然能扛并发,但按字符/请求计费,对话一长成本就上去了。我见过不少项目,Demo阶段“玩得很爽”,上线后第一个月账单出来才傻眼。我的建议是,在任何动辄调用TTS/LLM接口的系统里,都先加一层“缓存”:用户相同的输入直接返回历史结果,别每次都真的去调模型。一个非常简单的字典缓存,往往能省掉一大半成本。

写了这么多,最后分享一点我自己的感受。我刚开始接触“计算机说话”这个概念时,总觉得背后一定是些高深莫测的数学原理。真正把这套东西跑通之后才发现,它最迷人的地方恰恰在于“拆开来看都不难”——Token是积木,生成是接龙,语音合成是波形拟合,对话系统是管道。难的是把它们串起来,还要在延迟、成本、音质、稳定性之间做取舍。读过这篇文章,如果你也能跑出第一句“你好,我是AI助手”或者更自然的语音回复,那我这个“第一章”就算没有白写。这一章结束,下一章该轮到你自己玩出花样了。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进