1. LLM 的“自信”为什么不能信
先说一个我踩过的大坑。有段时间我在做一个基于开源大模型的意图分类服务,模型对“用户想退款”这个判断给出了 0.97 的概率分数,我直接拿这个分数去做自动化工单流转,结果线上连续三天误判率奇高。后来一查,那些被判成 0.97 的样本里,有相当一部分只是句子长得像退款请求,模型的“高置信度”根本不是真自信。
这不是个别模型的毛病,是所有用 softmax 输出概率的 LLM 共通的陷阱。softmax 有个数学特性:它会把 logits 之间的差值指数级放大,也就是说,只要某个 token 比其他 token 稍微突出一点,输出的概率就会迅速逼近 1。这个概率在数学上叫“归一化相对分数”,它衡量的只是“这堆候选里谁更占优”,完全不是“我对这件事的判断有多可靠”。听起来绕,但生活里有个特别贴切的类比:十个选手赛跑,冠军跑得再慢,他也是第一名;裁判说他拿了第一,并不能说明他跑得快。LLM 给你的 0.97,只是告诉你“这句话在候选标签里排第一”,至于这个第一到底靠不靠谱,它没说。
具体到业务场景,问题就严重了。我做过的几个真实案例里,LLM 置信度的失真体现在三个层面。第一层是“分布偏移失真”:模型训练时见过的数据和你线上遇到的数据不一样,哪怕只差一点点,它的概率分布也会整体漂移,训练时校准好的分数到线上就废了。第二层是“竞争不足失真”:候选集如果只有一个选项最突出,其他选项都模棱两可,softmax 照样能给那个最突出的打出高分,但“模棱两可”恰恰说明信息不充分,这分不该高。第三层最阴险,叫“生成式自欺”:LLM 是逐个 token 生成答案的,前面的 token 一旦确定,后面的概率计算会天然倾向于延续前面的选择,形成一种路径依赖,模型会“越说越自信”,哪怕一开始的判断就是错的。
所以结论很直接:LLM 输出的原始置信度,不是“模型对答案正确性的相信程度”,它只是一个排序结果被强行包装成了概率。你如果直接拿它去做风险决策、自动化流程、可信度判断,本质上是把赛跑名次当成运动员的绝对实力,出问题是迟早的事。
要解决这个问题,思路也很清楚:我们需要一个独立的机制,重新学习“模型输出什么特征时,答案倾向于是对的”,把这个特征映射成一个真正的概率。这就是概率校准。校准后的分数,才能说“在 0.9 这个分数段里,90% 的预测确实是对的”。
2. 校准概率引擎:33ms 背后的核心设计
想清楚问题之后,我开始动手做这个“校准概率引擎”。它的目标非常明确:输入 LLM 的原始输出及其相关特征,输出一个真正可信的、经过校准的概率。而且要做到单次处理耗时 33ms 以内,这样才能在线上实时场景里无感接入。
整个引擎的核心设计分三个模块:特征提取、置信度映射、时序平滑。很多人以为校准就是“拿温度系数调一下 softmax”,那是静态校准,只对分布稳定的场景有效。我的做法是动态校准,让引擎能感知不同输入场景下的置信度差异。
特征提取模块负责把 LLM 输出的“自信线索”全部挖出来。除了原始的 softmax 概率,我还会提取几个关键特征:token 间概率差(最大概率和第二大概率的差值,差值越大说明模型越“纠结”,反而要降信)、生成时的解码熵(整个序列的熵值,熵越大说明生成过程越不稳定)、语义自洽度(把答案重新喂给模型,让模型判断这段回答是否自洽,用二次输出的概率作为特征)。这几个特征组合起来,能比单一的概率分数更全面地反映“模型这次到底稳不稳”。
置信度映射模块采用了一个小规模的神经网络,把上述特征映射成一个经过校准的概率值。训练数据来自哪里?我用了一个很笨但很有效的方法:构造一批“已知答案”的评测集,输入给 LLM,记录它的输出特征,然后根据答案是否正确打标签。正确就是 1,错误就是 0,用这批数据训练回归模型。这样学到的映射关系,本质上是在回答“具备这些特征时,模型答对的概率有多大”。
第三个模块是时序平滑,这是我后来加上的,也是踩了坑才明白的必要模块。我发现如果没有时序平滑,单次请求的校准结果会有抖动,尤其是在流式对话场景里,前一秒校准概率 0.8,后一秒掉到 0.4,业务侧根本没法用。于是我在输出端加了一个轻量级的滑动窗口,对同一会话内的校准概率做指数加权平均。这样既保留了对异常突变的响应速度,又剔除了随机噪声。整个计算链路全部跑在 CPU 上,没有用 GPU,因为模型本身很小,推理开销极低。实测下来,单次校准的端到端耗时稳定在 25ms 到 33ms 之间。
选型上我踩过几次坑,最初想直接用现成的模型置信度校准库,比如 temperature scaling 或者 isotonic regression,但效果都不好。原因是这些方法都假设“模型输出概率本身含有足够信息”,可 LLM 的概率恰恰是失真的,输入失真数据、做映射修正,本质上是“垃圾进垃圾出”。后来换成“外部特征 + 独立回归器”的思路,效果才有了质的提升。这给了一个很重要的启发:当你要修正一个不可信信号时,最好别只盯着信号本身修,应该找到导致信号不可信的根因特征,再重新建模。
3. 核心实操:从特征到校准概率的完整实现
这一节把引擎的实操过程完整写出来,按照我当时搭建的顺序展开,内容包括特征设计、模型训练、推理部署,以及中间遇到的关键参数选择。
3.1 第一步:提取有效特征,而不是只用原始概率
特征是校准引擎的地基。地基不牢,后面全白搭。我最终确定的核心特征有四个,每个都经过线上 A/B 测试验证过有效性。
第一个特征叫概率裕度。定义是最高概率减去次高概率,公式很简单,但信息量很大。当模型给出 0.97 和 0.03 的分布时,裕度是 0.94,看起来很“笃定”;可当最高分只有 0.45、次高分有 0.41 时,裕度只有 0.04,说明模型在几个答案之间根本拿不准。但有意思的是,在评测集上我发现,裕度大的样本并不一定正确率更高,这在某些生成式任务里表现得尤其明显。所以裕度只能作为特征之一,不能单独当校准依据。
第二个特征是解码路径熵。做法是保留模型解码过程中的完整概率分布,计算每个 token 选择的熵值,再加总成全局路径熵。这个计算有点开销,但值得做。我见过一个典型案例:某个模型生成法律建议时,前几个 token 的熵很低,后面直接进入“背诵模式”,全程几乎零不确定性,但生成的内容里有一个关键条款引用错了。如果只看最终答案的 softmax 概率,它完美得很;看解码熵就露馅了,因为正常情况下,一个有理解能力的过程应该在某些判断点上体现出轻微的不确定性,全程零熵反而可疑。
第三个特征是自洽性得分。这招是我从不少评测文章里学来的,也被证明非常有效。做法是:让 LLM 生成答案后,把这个答案用另一个 prompt 重新发回去,问“这段回答是否正确”,得到一个二分类的置信度。这个二次概率可以作为校准引擎的一个输入特征。原理是:模型如果真“懂”这个答案,它二次确认时应该保持稳定自信;如果只是蒙对的,二次确认时往往会露怯,概率会显著下降。当然,这个特征会增加一次 LLM 调用,所以我在工程上做了缓存,同一答案重复出现时直接复用结果。
第四个特征是输入分布距离。这个是针对真实场景漂移的。做法是把用户输入 embedding 化,和训练集的 embedding 中心做距离计算。距离越远,说明这个输入越偏离模型“熟悉”的区域,校准概率就应该适度下调。这个特征是三个离线评估实验里提升最明显的,直接让总体 AUC 涨了约 7%。
四个特征合起来,组成了校准引擎的输入向量。维度不高,但覆盖了模型的输出分布、生成过程、语义确认、输入分布四个层面,信息彼此互补。
3.2 第二步:训练校准映射器,让概率回归到“真实正确率”
有了特征之后,需要一个映射器把这些特征转化为校准概率。我选择的是一个三层的 MLP(多层感知机),隐藏层维度分别是 64 和 16,输出层是单个 sigmoid 节点。输入维度四个,输出一个概率值。
训练数据的构造是重中之重。我的做法是:准备一个有标准答案的评测集,规模在 15000 条左右,覆盖目标业务会遇到的各类 query。把这些 query 依次送进目标 LLM,记录下每个样本的四个特征,同时根据它的输出是否正确打上标签。正确为 1,错误为 0。然后训练 MLP,用二元交叉熵作为损失,优化器用 Adam,学习率初始设 0.001,训练大约 20 个 epoch,加 early stopping 防过拟合。
这里有个关键细节:训练集和校准集要分开。我一开始为了省事,直接在同一条数据集上训练和验证,结果显示校准曲线异常完美,上线后立刻拉胯。后来改成时间前向后向分隔:用前 80% 时间段的样本训练,后 20% 时间段的样本验证。验证结果掉了一些,但上线后的真实表现反而更符合预期。这是校准模型最容易踩的坑,样本集分区不严谨,训练评估全是自欺欺人。
还有一个细节是负样本的挖掘。单纯拿“答案错误”当负样本还不够,因为我发现很多样本的答案是“部分正确”,比如给了一段可行代码,但漏了异常处理。这种样本在硬标签体系里会被当成负样本,但它实际代表的是“模型有真实能力,但不够完整”。我后来引入了软标签:引入一个自动判分器,先做规则判断,再做关键词抽取,最后实在不确定的才交给人工审核,把每一条样本标注为 0、0.3、0.7、1 这样的分级分数。这让训练出的校准器对“部分正确”的情况也能给出合理的中间概率,而不是要么 0 要么 1 的武断判断。
3.3 第三步:33ms 推理链路,延迟怎么压下来的
校准引擎的推理链路有三个环节:特征提取(其中自洽性特征的重复调用做了缓存)、MLP 前向计算、时序平滑。跑通后实测平均耗时在 28ms 左右,我预留了一点余量,给出的服务承诺上限是 33ms。
MLP 前向计算本身极快,一个四维输入、64 个隐藏神经元,在 CPU 上就是几十微秒的事,耗时大户是特征提取里的 embedding 距离计算和自洽性检查。embedding 距离计算要对全量训练样本的 embedding 中心做余弦距离,我把这个中心向量预先算好存在内存里,线上只做一次点积运算,微秒级完成。
自洽性检查虽然缓存了一部分,但缓存命中率不是 100%,未命中时需要多一次 LLM 调用。为了控制 33ms 预算,我的做法是采用异步预取:在用户请求进入主链路的同时,后台任务提前发起自洽性检查,主链路先用其余特征算出临时概率,等自洽性结果返回后再做加权修正。这样既保留了自洽性特征的高价值,又不会让主链路的延迟被拖垮。实测最差情况下增加耗时 6ms,仍在预算内。
时序平滑模块的参数也值得记录。滑动窗口长度我取 5,指数加权系数取 0.65。窗口太短平滑效果差,窗口太长则会滞后于真实置信度的突变。调参依据是我在模拟数据上做了一个延迟实验:人为在置信度序列中插入阶跃变化,观察校准输出的跟随延迟,选了 5 和 0.65 作为平衡点。这部分属于经验调试,没有通用标准,业务场景不同可以自行调节。
3.4 第四步:流式输出场景里的一个妥协
在做客服系统接入时,我遇到了流式场景的特殊问题。LLM 在流式输出时,答案还没完整生成,特征提取拿不到全局熵和自洽性,怎么办?
我的解法是分两阶段校准。第一个阶段叫前缀预校准:只取已生成前缀,计算前缀的概率分布特征,用一个针对短文本训练的轻量校准器粗略估计置信度,这一步耗时不到 5ms。第二个阶段叫完整校准:等完整答案生成后,用完整特征做精确校准,覆盖之前的结果。这样用户在流式过程中看到的是一个逐步收紧的置信度指示,而不是一直等到答案全部出完才看到结果。体验上更自然,业务上也能提前做低优先级的风险预判。
这两个校准器是分开训练的。前缀校准器的训练数据来自“截断答案”:把完整答案随机截取 30% 到 90% 的长度,用截断后的特征训练。这样做的一个附加好处是,我间接发现了一个规律:答案生成到 40% 长度时,置信度就已经和最终值高度相关了;到了 70% 长度,相关系数已经超过 0.9。这说明大多数情况下,模型的“自信”在生成早期就已经定了调,后面的生成更多是路径延续。这个洞察对做流式交互的同行应该有参考价值。
4. 上线效果:校准概率比原始概率好多少
任何技术方案,最终都要拿数据说话。我把校准概率引擎接到一个真实的客服工单自动分类系统里做对比测试,效果相当直观。
先看可靠性曲线。原始 LLM 概率说 0.9 以上的预测,实际正确率只有 76% 左右。我能接受的最低下限是正确率不低于 0.9,显然它差了 14 个百分点。而经过校准引擎之后,分数在 0.9 以上的预测,实际正确率是 89.5%,基本贴近理想线。而最值得说的,是低分段的校准效果好得出乎意料。原始概率给到 0.3 到 0.4 区间的预测,实际正确率只有 19%,低于随机水平。这说明原始概率在低分段严重“失真”——模型其实在说“我不确定”,但这个不确定里有大量“看似像但是错”的混淆样本。校准引擎把这个区间拉到了 28%,虽然不完美,但至少可靠了 9 个百分点。
再看决策阈值的迁移。业务要求 90% 的精确率才允许自动化处理,原始概率需要把阈值定在 0.97 才能达到这个要求,这意味着大量本来可以自动化的工单被迫转入人工。校准后,阈值降到了 0.82,自动化覆盖率直接从 41% 提升到了 68%。对中大型客服团队来说,这个提升意味着每个月可以多自动处理大量工单,节约的成本非常可观。
延迟方面,当时我也做了完整压测。校准引擎接入后,P99 延迟从原链路的 412ms 增加到 447ms,增幅 35ms。考虑到这个增量主要是自洽性检查的 LLM 调用造成的,而正常请求又走了缓存,这个增幅属于可接受范围。我在生产环境里做了个开关:当系统负载超过阈值时,自动降级为“无自洽性检查模式”,校准精度会下降一些,但延迟能稳住。
最后提一个上线初期踩的坑,给大家提个醒:接入为什么差点出事。我当时直接对生产流量开启了完整校准,结果凌晨流量低谷期出现了大量校准概率突跳,从 0.8 直接掉到 0.1。排查后发现原因是 embedding 中心的缓存更新逻辑有 bug,每半小时重新计算中心向量时只用了最近批次的样本,中心点漂移,距离特征突变。修复方式是中心向量改成全量历史样本的滑动平均更新,并且加了异常检测,中心点单次移动超过阈值就告警冻结。这个细节让我意识到:校准引擎不是静态模型,它依赖的特征如果本身不稳定,输出的概率就会跟着发疯。
5. 常见问题速查:校准引擎实操中踩过的坑
这节整理成速查表,方便遇到问题时对照排查。都是我自己踩过的坑,或者反复被同事问到的点。
| 问题 | 现象 | 根因 | 解决方案 |
|---|---|---|---|
| 校准曲线在测试集上完美,上线后崩 | 离线验证 AUC 高达 0.97,线上实际效果还不如原始概率 | 训练集和验证集划分不严谨,存在时间穿越 | 训练集用前 80% 时间段样本,验证集用后 20% 时间段样本,杜绝未来信息泄露 |
| 校准概率突然大范围跳变 | 凌晨时段校准概率从 0.8 突降到 0.1,持续几小时 | embedding 中心向量更新逻辑有 bug,用了最近一批样本导致中心漂移 | 中心向量改全量滑动平均,加异常检测,单次移动超阈值自动冻结 |
| 校准概率总是偏向 0.5 | 无论输入多明确,输出概率都挤在 0.4 到 0.6 之间 | 负样本占比过高,模型学成了“平均主义” | 负样本下采样,正负比调至 1:1.2 左右,或改用焦点损失 |
| 自洽性检查拖慢整体响应 | 自洽性特征未命中缓存时,整体耗时增加超 100ms | 自洽性检查是同步调用,卡住主链路 | 改成异步预取,主链路先用其余特征出临时概率,结果返回后再修正 |
| 时序平滑导致反应迟缓 | 置信度发生快速变化时,平滑后输出迟迟不动 | 滑动窗口太大或加权系数太小 | 调参依据:人为注入阶跃变化观察跟随延迟,我选了窗口 5、系数 0.65 |
| 新业务场景校准效果差 | 把客服场景的校准器直接用到法务问答上,掉点严重 | 分布偏移:不同业务的特征分布差距大,通用校准器不成立 | 分场景训练专属校准器,或用领域自适应方法对特征做归一化 |
再多说一句负样本的话题。这是我觉得整个校准项目里最容易忽略却最致命的一环。第一次跑出的校准模型,在 0.5 附近的概率极其密集,我当时以为模型坏了,后来发现是负样本里混入了大量“看似对实则错”的样本,比如“发了 500 元红包”被模型算成“发了 5000 元红包”,这类样本的错误模式特别有迷惑性,模型学到的是“这种句子大概率是错的”,结果把概率全压到 0.5 以下。后来我把负样本按错误类型做了细分类,再往下采样训练,才把低分段的分布拉回来。
6. 校准引擎还可以这样扩展
写完上面这些,核心方案已经完整了。但我觉得还有些扩展思路值得分享,尤其如果你已经决定自己动手做一套校准引擎的话。
第一个扩展是置信度区间输出。当前方案输出的是点估计概率,只回答“答对的可能性有多大”。很多高风险场景其实需要“区间估计”,比如“我认为概率在 0.72 到 0.87 之间”。实现方式是用蒙特卡洛 dropout 或深度集成(deep ensemble),训练多个校准器,用输出的方差构造置信区间。这能让业务侧更清楚风险的波动范围,而不是只有一个孤零零的数字。
第二个扩展是场景自适应更新。我遇到过很多次:某个新业务上线时,因为没有对应的历史样本,校准器完全不适用。解决思路是做一个两阶段流程:先用一个小规模冷启动数据集粗校准,让引擎先跑起来;然后设计主动采样策略,倾向于采集校准器当前不确定性最高的样本,快速积累数据,再在一周内切换到正式校准器。我在内部实践里,这个冷启动周期大概是 3 到 5 天,就能达到正式校准器的 90% 精度。
第三个扩展是把校准信号反哺给 LLM 提示词。思路很简单:如果当前请求的校准概率偏低,就在下一次追问时,把低置信度信息显式写入 prompt,比如“你对上一个问题的判断置信度偏低,请重新确认或补充参考信息”。这相当于让模型自己知道“我不太确定”,有时能显著提升修正后的准确性。我在一个法律问答场景里做了验证,二次追问后的准确率提升了 12%。
第四个扩展方向是把校准引擎和 RAG 检索链路打通。我看过几个 RAG 实际项目,发现检索返回的文档与用户 query 的相关性分数,同样存在“自欺”的问题——top 文档的相关性分数虚高。如果把校准引擎的特征设计扩展成包含检索结果的特征,比如“生成答案时是否真正用上了检索文档”“答案是否忠实地源自文档”,就能校准“RAG 回答是否可靠”这一层。这是我认为当前所有 LLM 应用里最值得做、也最容易被忽略的校准点。
回到最初的问题。LLM 的置信度为什么在骗你?因为它的评分逻辑和“可信度”本来就是两码事。校准引擎做的事情,是把“候选里谁最占优”翻译成“答案有多可靠”,中间靠的是重新学习特征到真实正确率的映射,再加时序平滑去噪。整个方案不依赖 GPU,延迟能控制在 33ms 以内,在线接入不伤业务性能。如果你也在用 LLM 做决策、做自动化、做风险判断,希望这篇分享对你有用。有些坑我已经替你踩了,剩下的,你得自己踩一遍才会真正记住。