创造力是温柔的谎言精选
调用大模型的时候,总会看到几个耳熟能详的参数:Temperature、Top-p、Top-k。文档里通常的解释都是:控制输出的随机性。也就是:Temperature 和 Top-p 的值越高,模型输出的结果会越随机、越富有创造性,反之,数值越低,输出的结果就越确定、越保守。
随机性,到底是个什么意思?为啥随机性就可以表现为创造性?
回答这个问题,得先从一个最朴素的问题开始:模型是如何回答问题的。
我之前在 《从统计学习到通用智能》 中曾经提到过,大模型在输出文本的时候,本质上是在 滚动地预测下一个最有可能出现的词。
而预测机制大概可以拆分为三个具体的逻辑步骤:生成分数 → 转换概率 → 加权采样。
模型的打分机制
模型在回答问题时,并不是一次性把整句话想好再说出来,而是一个词元一个词元地往外蹦,每次输出一个词元(Token)之前,它都会把目前为止的所有内容(包括输入的问题和已经输出的部分回答)重新扫一遍,然后给词汇表(Vocabulary)里的每一个词元打一个分 —— 这个分在技术上叫 Logit 。
由于目前一些技术名词已经有了 正式的中文命名 ,下文中就用「词元」来代替 Token,用「词汇表」来代替 Vocabulary。
比如,假设用户问:博主写过哪些关于前端的文章?
模型收到这个问题之后,会把它认识的所有词元全部扫一遍,然后给每个词元打分。
这个打分过程本身很复杂,是整个 Transformer 架构的核心。
简单说的话:模型把当前所有输入按照词元切分,经过层层注意力计算(Attention)让每个词元都「读懂」上下文,最后通过矩阵乘法得到每个词元的分数,相关程度越高,打分越高。 类似于我之前在《从统计学习到通用智能》曾提到过的 向量嵌入中的相似度搜索 ,只是这里要打的不是语义相似度,而是「在当前上下文里出现的可能性」。
不过,具体怎么算,是 Transformer 内部的事。作为调用方,只需要知道:词汇表里的每一个词元,都会有一个对应的分数。 类似这样:
| 词元(Token) | 分数(Logit) |
|---|---|
| TypeScript | 3.1 |
| React | 3.0 |
| Vue | 2.8 |
| 禅修 | 0.24 |
| 摩托车 | 0.1 |
| ... | ... |
即便是一些完全不相关的词元,比如「摩托车」和「禅修」,它们也有分,只是分很低。
分数转换为概率
分打完了,下一步模型的任务就是从这些词元中选一个最合适的去输出。
最直接的想法是:直接挑分值最高的那个不就行了吗?
在技术上,这完全可行,甚至这种策略也有个名字,叫 Greedy Decoding (贪心解码)。也就是每一步中,模型都会选择分值最高(匹配度最高)的词元,永远追求局部最优解,而不考虑整体的输出效果。
但贪心解码有一个明显的问题:同样的问题,永远只会得到同样的答案。
还是刚才的例子,用户问:博主写过哪些关于前端的文章?
模型会永远输出:博主,写,过,关于,TypeScript,的,文章。
永远是 TypeScript,不会是 React,哪怕分数只是差了 0.1。
这样生成的文字陷入一种机械的重复感,AI 更像是在复读,而不是思考。就像我们用手机输入法一直点第一个「建议词」,最后打出来的一直就是那几句车轱辘话一样。
为了解决这个问题,有人提出了 Beam Search (束搜索)。它的思路是:不只盯着分值最高的那条分支走到黑,而是走每一步的同时,保留分值最高的 k 条候选路径,最后选整体概率最高的那条。
还是前面的例子,假设 k=2,在挑选「关于」之后的词元的时候,会同时保留「TypeScript」和「React」两条路;接下来在每条路的基础上继续延伸,再各自保留最优的两个分支,以此类推。最终从所有走完的路径里,挑出整体概率最高的那条作为输出。
Beam Search 确实比贪心解码更「聪明」一点,它不会因为某一步的局部最优而错过更好的整体结果。但它也有自己的问题:输出往往过于保守,容易陷入重复,而且计算成本随 k 值的增大显著上升。
这里还有个更根本的问题是:人类的自然语言本来就不遵循「整体概率最高」的规律。许多直击心灵的好句子,往往都不是文字逻辑上的「最优解」,所谓的「人味」也就是从这里来的。
所以实际上,这两种确定性策略都只会在特定的、需要稳定性和可预测性的场景使用(比如搜索)。而在需要「创造性」的场景中,模型并不会只盯着分值最高的「第一名」,而是需要给每个词元一个「被选中的机会」,分越高机会越大,分越低机会越小,但不能直接为零。
要做到这一点,就需要先把分数转换成概率。这里用到的是一个叫 Softmax 的函数,这个函数说来也简单,就是 把所有词元的分数归一化,让它们加起来等于 100%。
这里不能直接用 Logit 分数来表示这个「机会」,因为 Logit 是原始分数,它可以是任意数值,没有上下界,无法直接用来做概率采样。
还是以上面的例子为例(假设词汇表里只有这几个词元),把各自的分数代入 Softmax,算出来的概率大致是:
| 词元(Token) | 分数(Logit) | Softmax 计算 | 概率 |
|---|---|---|---|
| TypeScript | 3.1 | 22.20 / 61.10 | 36.3% |
| React | 3.0 | 20.09 / 61.10 | 32.9% |
| Vue | 2.8 | 16.44 / 61.10 | 26.9% |
| 禅修 | 0.24 | 1.27 / 61.10 | 2.1% |
| 摩托车 | 0.1 | 1.11 / 61.10 | 1.8% |
可以看到,三个「前端」有关的词元,它们的概率加起来已经超过了 96%,「禅修」和「摩托车」加在一起连 4% 都不到。分高的词概率高,分低的词概率低,但 只要概率不是零,就永远有被选中的可能。
而整个公式的计算过程,如果用 JavaScript 来实现的话,大概是这样的:
JavaScript
123456789101112131415
// 由模型打分得出的原始分数(Logit)
const logits = [3.1, 3.0, 2.8, 0.24, 0.1];
// 这里对每个 Logit 取自然指数 e^z(Math.E 是自然常数,约等于 2.718)
const exps = logits.map((z) => Math.exp(z));
// [22.20, 20.09, 16.44, 1.27, 1.11]
// 把所有指数加起来,得到分母(归一化常数)
const sum = exps.reduce((acc, val) => acc + val, 0);
// 61.10
// 每个指数除以分母,得到概率
const probs = exps.map((exp) => exp / sum);
// [0.363, 0.329, 0.269, 0.021, 0.018]
// [36.3%, 32.9%, 26.9%, 2.1%, 1.8%]
基于概率的随机采样
现在概率有了,模型去「随机抽」就简单多了。
可以把它想象成,在一条数轴上按概率分配区间,每个词元按照自身的概率作为比例来占据数轴的一部分。然后生成一个 0 到 100 的随机数,落在哪个区间就选哪个词元。这样每个词元被选中的概率,刚好就等于它的概率数值。
输出完这个词元,再把它加进输入,重新打分,重新算概率,重新抽…… 如此往复,直到输出一个代表「我说完了」的终止符。
这就是大模型「说话」的全过程。没有计划,没有草稿,每一步都只在问自己:接下来最可能出现的词元是什么?
而这个「随机抽」的过程,有一个专业的名字,叫随机采样(Random Sampling)。按照这种随机的模式来输出,内容就有了新鲜感,也就是「人味」。
但随机采样本身还有问题:到底随机到啥程度?业务决定还是模型决定? 这就引出了文章一开始提到的三个参数:Temperature、Top-p、Top-k。
概率差距(Temperature)
Temperature 就是「温度」,通过调低或调高这个 T,就可以让词元的概率分布变得更「尖锐」或者更「平滑」。
它的原理是:在 Softmax 计算概率之前,给原来公式里的分子和分母分别都除以这个 T,从而改变每个词元之间的概率差距。
也就是公式变成了 e^(zi/T) / Σ e^(zj/T)。
- T 越小,差距越大,高分词元的优势被放大。
- T 越大,差距越小,所有词元的机会趋于均等。
这一步看起来微不足道,但带来的效果非常剧烈。
T 调低(比如 T=0.1)时,每个词元的分数都会先除以 0.1,也就是放大 10 倍。原本 TypeScript 和 React 只差了 0.1 分,放大后差距变成 1 分。而 Softmax 里有指数运算,线性的差距在指数面前会被疯狂放大。
最终算出来的概率,TypeScript 从原来的 36.3% 暴涨到约 70.5%,React 从 32.9% 跌到 25.9%,Vue 只剩 3.5%,「禅修」和「摩托车」基本接近于零。
| 词元(Token) | 分数(Logit) | T=1.0 概率 | T=0.1 概率 |
|---|---|---|---|
| TypeScript | 3.1 | 36.3% | 70.5% |
| React | 3.0 | 32.9% | 25.9% |
| Vue | 2.8 | 26.9% | 3.5% |
| 禅修 | 0.24 | 2.1% | ≈0% |
| 摩托车 | 0.1 | 1.8% | ≈0% |
这就形成了一个类似「赢家通吃」的局面:模型几乎每次都只会选 TypeScript,输出变得稳定、确定,同时也很死板。
T 调高(比如 T=2.0)时,每个词元的分数都除以 2,差距被压缩了一半。原本悬殊的差距被抹平,第一名的优势也变得不再明显。
| 词元(Token) | 分数(Logit) | T=1.0 概率 | T=2.0 概率 |
|---|---|---|---|
| TypeScript | 3.1 | 36.3% | 30.5% |
| React | 3.0 | 32.9% | 29.1% |
| Vue | 2.8 | 26.9% | 26.3% |
| 禅修 | 0.24 | 2.1% | 7.3% |
| 摩托车 | 0.1 | 1.8% | 6.8% |
最终算出来的概率里,连「摩托车」这种完全不相关的词元,都有了相当的概率(6.8%)被选中。输出变得多样,有时候冒出意想不到的词,但也有时候像在胡说八道。
Temperature 这名字也起得很应景。
- 低温让分子运动减缓,结构稳固且确定(冷静理智)。
- 高温让分子剧烈活动,不可预测且随机(热情奔放)。
老外也是懂意象的。
候选排除(Top-k / Top-p)
Temperature 解决了「概率差距」的问题,但还有另一个隐患没有处理。
词汇表里有几万到几十万个词元。头部那几个词元的概率加起来可能已经到了 95%,但剩下那 5% 被稀释在了几十万个或者更多数量的词元上。每个词元的概率都极低,但数量非常庞大。只要运气够好(或者模型够傻),那些完全不相关的词元,也有机会被随机采样选中。
于是,就可能会出现这样的句子:「他打开门,发现里面有一台正在呼吸的电视机」。你可能也会觉得:哎,不错,有点东西。
但更多时候,它是这样的:你还是问「博主写过哪些前端相关的文章」,结果它回答「摩托车脉冲点火的架构与设计」。
这就称不上是创意了,这算事故,P0 级别的。
Top-k 和 Top-p 本质上是介于贪心解码和完全随机采样之间的方法。它既不像贪心解码那样只认第一名,也不像完全随机采样那样对所有词元一视同仁,它负责的是:在采样之前把诸如「摩托车」、「禅修」、「股票」这类上下文无关的「长尾垃圾词」排除掉。
Top-k 的逻辑直接粗暴:只保留分数最高的前 k 个词元,其余的全部丢弃,概率直接归零。 k=10,就只从前十名里选。k=1,就退回到了贪心解码。
还是用刚才的例子:如果设置 Top-k=3,那么保留的就是 TypeScript(36.3%)、React(32.9%)、Vue(26.9%)这三个词元,「禅修」和「摩托车」就直接被丢弃了,然后把这三个词元的概率重新归一化,让它们加起来重新等于 100%,再进行采样。
| 词元 | 原始概率 | Top-k=3 后(归一化) |
|---|---|---|
| TypeScript | 36.3% | 37.8% |
| React | 32.9% | 34.2% |
| Vue | 26.9% | 28.0% |
| 禅修 | 2.1% | ✕ 丢弃 |
| 摩托车 | 1.8% | ✕ 丢弃 |
而 Top-p 的全称是 Top Cumulative Probability,也叫核采样(Nucleus Sampling)。它的逻辑动态一些:从概率最高的词元开始往下累加概率,累加到超过阈值 p 就停,剩下的全部丢弃。 p=0.9,就意味着只保留累计概率能覆盖 90% 的那些词元,不管这个集合里有几个词元。
同样的例子:TypeScript 36.3%,加上 React 32.9%,已经到了 69.2%;再加上 Vue 26.9%,累计概率达到 96.1%,超过了 0.9 的阈值,停止。「禅修」和「摩托车」两个词元被排除。而如果把 p 设为 0.6,那只需要 TypeScript(36.3%)加上 React(32.9%)累计到 69.2% 就够了,Vue 也不要了。
| 词元 | 原始概率 | 累计概率 | Top-p=0.9 后(归一化) |
|---|---|---|---|
| TypeScript | 36.3% | ↓ 36.3% | 37.8% |
| React | 32.9% | ↓ 69.2% | 34.2% |
| Vue | 26.9% | ✓ 96.1% | 28.0% |
| 禅修 | 2.1% | — | ✕ 丢弃 |
| 摩托车 | 1.8% | — | ✕ 丢弃 |
这两个参数解决的是同一个问题,但角度不同:Top-k 固定候选词元的数量,Top-p 固定候选词元的概率密度。 当概率分布比较平坦时,Top-p 会自动多保留几个候选;分布集中时,又会自动收窄。相比之下,Top-k 的 k 值选多少很难提前确定,对不同的输入,同一个 k 值的效果可能差异很大。
这也是为什么两者经常叠用:先用 Top-k 砍掉数量上的长尾,再用 Top-p 按密度做二次收束。 如果几个参数同时启用,执行顺序就会是:Top-k → Top-p → Temperature。
为了便于直观感受,我用 Claude 生成了一个 DEMO,可以在下面测试效果或者 新窗口打开 。
创造力的调节指南
现在 Temperature、Top-k、Top-p 这三个参数的含义已经很清楚了,参考 OpenAI 的官方建议,一般的实践原则是:
- Temperature 和 Top-p 只调其中一个。
- 优先调 Temperature。
- 推理模型尽量不要调这些参数(不需要)。
但如果你对模型的极限也充满了好奇,不妨试试这些简单粗暴的配置思路:
确定性任务(代码生成、事实问答、数据提取)
Temperature 调到极低(0.1 甚至更低),Top-p 设为 1.0,Top-k 设为 1。输出会非常稳定、保守,几乎每次都选概率最高的词。
平衡场景(日常对话、内容摘要、普通写作)
Temperature 保持在 0.7 ~ 1.0,Top-p 设为 0.9,Top-k 设为 40 ~ 60。在确定性和多样性之间保持一个平衡,回答自然又带有变化,出错的概率也比较低。这也是大多数模型的默认配置区间,也是 NodePress AI 摘要能力的 默认配置 。
创造性任务(故事生成、头脑风暴、诗歌、营销文案)
Temperature 拉到 1.2 ~ 1.5,Top-p 设为 0.95,Top-k 设为 80 ~ 100。输出会更大胆、更容易跳出常规逻辑,但也更容易跑题或胡说八道,需要多筛一轮。
极度随机(游戏 NPC 对话、艺术实验)
Temperature 超过 1.5,Top-p 和 Top-k 全部放开。每个词的概率被拉得几乎均等,输出天马行空,狂荡不羁。这种配置下,大部分结果可能没有意义,靠的是多次尝试里偶尔出现的惊喜。
温柔的谎言
总的来说,我们每一次调高 Temperature 的「温度」,或者拓宽 Top-p 的阈值,都是在告诉模型:允许你适当地「胡编」,允许你更大胆一些。它本质上是希望模型 在「合理」的范围内,制造适当的意外。
这通常被称作:增加创造力。
而人对「创造力」的感知,大部分时候就是来自于这份「意外」。比如那首广为人知的:灯,把黑夜烫了一个洞。
换句话说,AI 的「创造」来源,并不是无聊的 Top1,也不是荒谬的长尾词,而是概率分布的「边缘」采样。甚至也可以认为:AI 不是在「创造」,它只是在不断地「没选最优解」。
但如果认真去追问「创造力」这个词。
如果「创造力」的定义是:包含了情感温度、联想幻想、故事体验、独一无二的这个过程的话,那模型从未实现过创造。 它永远只是在概率的公式里随机跳跃,而且还是伪随机。
它有结果,但没有真正意义上的创造过程。 它不知道自己为什么选了这个词,也没有「选」的意志,只是概率落点恰好在这里。
不过,大部分时候,人们要的本来就不是「真正的创造力」,而是「创造力所带来的结果」。 一段看起来有爆点的文案、一个「别出心裁」的转场,或者墙上挂的那副 AI 画出来的壁画…… 这些需求,机器确实可以满足。
倒是有个更严重的问题是:比起 AI 有多大「创造力」,人们似乎逐渐习惯了把这种统计意义上的受控偏离,误读成了某种有意识的灵感涌现。
这种人类的自我投射,和机器毫无关系。
(完)





