当你问一个大模型一个问题,它吐出一串文字作为回答。这个过程看似简单,但模型内部究竟发生了什么?它有多确信自己的答案?如果它在"胡编乱造",我们能否察觉?

答案藏在模型输出的一个不起眼的数值里:logprobs(对数概率)。这个负数不仅揭示了模型对每个词的置信度,还能帮助我们检测幻觉、优化分类系统、评估RAG检索质量。它就像是模型的"内心独白",告诉我们它真正的想法。

从一次困惑说起

假设你让模型判断:“番茄是水果还是蔬菜?”

模型回答:“番茄是水果。”

看起来很简单。但如果我们追问:模型对这个判断有多确定?

一个天真的想法是直接问模型:“你对这个答案有多少信心?"——但这样做有问题。模型可能会回复一个看似合理的置信度,比如"我有85%的信心”。然而这个数字本身就是模型生成的,它和真实的置信度毫无关系。

真正的置信度隐藏在模型生成每个token时的概率分布中。当模型决定输出"水果"这个词时,它其实经过了这样的过程:

首先,模型计算所有可能下一个词的原始分数(logits)。然后通过softmax函数将这些分数转换为概率分布。在这个分布中,“水果"可能获得了某个概率值,比如0.92。而"蔬菜"可能获得了0.07的概率。其他词瓜分剩下的0.01。

这个0.92就是模型对"水果"这个词的置信度。但在实际应用中,我们通常不直接使用概率值,而是使用它的对数形式——logprob。在这个例子中,logprob = log(0.92) ≈ -0.083。

为什么要用对数?这涉及到机器学习中最核心的数值稳定性问题。

为什么是Log概率而非概率

概率值总是在$[0, 1]$区间内。当概率值很小时,比如$10^{-50}$,计算机直接存储这个值会遇到精度问题。更麻烦的是,当我们需要计算多个独立事件的联合概率时,需要做乘法:

$$P(A \cap B \cap C) = P(A) \times P(B) \times P(C)$$

当概率值都很小时,连续的乘法会迅速导致下溢出(underflow)——结果变成0。这在语言模型中是常态,因为一个句子可能包含几十个甚至上百个token,每个token的概率相乘后几乎必然下溢。

取对数后,乘法变成加法:

$$\log(P(A) \times P(B) \times P(C)) = \log P(A) + \log P(B) + \log P(C)$$
flowchart LR
    subgraph 直接计算
        A1["P(A) = 0.001"] --> A2["× P(B) = 0.001"]
        A2 --> A3["× P(C) = 0.001"]
        A3 --> A4["= 10⁻⁹"]
        A4 --> A5["可能下溢出!"]
    end
    
    subgraph 对数计算
        B1["log P(A) = -6.9"] --> B2["+ log P(B) = -6.9"]
        B2 --> B3["+ log P(C) = -6.9"]
        B3 --> B4["= -20.7"]
        B4 --> B5["数值稳定 ✓"]
    end

加法不会放大误差,也不会轻易下溢出。这就是为什么在机器学习的几乎所有场景中——从最大似然估计到交叉熵损失——我们都工作在对数空间。

Log-Sum-Exp技巧

但工作在对数空间也有自己的陷阱。当我们需要对概率归一化时,会遇到一个看似矛盾的需求:

假设我们有三个log概率值:$[-1000, -1000, -1000]$。要归一化,我们需要计算:

$$\text{normalized} = \frac{e^{-1000}}{e^{-1000} + e^{-1000} + e^{-1000}}$$

问题是$e^{-1000}$在浮点数中直接变成0,导致分母为0,程序崩溃。

这个问题的解法被称为Log-Sum-Exp技巧。核心观察是:我们可以给所有指数项加上一个任意常数而不改变最终结果。如果我们找到最大值$a = \max(x_i)$,那么:

$$\log\sum_i e^{x_i} = a + \log\sum_i e^{x_i - a}$$

这样做之后,最大的指数项变成$e^0 = 1$,其他项虽然变小但不会下溢出。这个技巧在现代深度学习框架中无处不在,是softmax函数数值稳定实现的基础。

flowchart TD
    A["原始logits: -1000, -1000, -1000"] --> B["直接计算 e^logits"]
    B --> C["结果: 0, 0, 0"]
    C --> D["归一化失败! 除以0"]
    
    A --> E["Log-Sum-Exp技巧"]
    E --> F["找到最大值 a = -1000"]
    F --> G["计算 e^(x-a) = e^0 = 1"]
    G --> H["每个值变成 1/3"]
    H --> I["归一化成功!"]

信息论的视角:Log概率的本质含义

从信息论的角度看,log概率有一个深刻的物理意义:自信息(self-information)

克劳德·香农在1948年开创信息论时,定义一个事件的自信息为:

$$I(x) = -\log_2 P(x)$$

当使用以2为底的对数时,自信息的单位是比特(bit)。它表示"得知这个事件发生所获得的信息量”。

考虑两个极端情况:

  • 如果一个事件发生的概率是$P(x) = 1$(必然事件),那么$I(x) = 0$。它发生不带来任何新信息。
  • 如果一个事件发生的概率是$P(x) \to 0$(几乎不可能),那么$I(x) \to \infty$。它发生带来巨大的信息量。

回到大模型。当模型给某个token分配很高的logprob(接近0的负数)时,意味着这个token的自信息很低——模型早就"预料"到会输出它,就像你预料太阳从东方升起一样。当模型给某个token分配很低的logprob(绝对值很大的负数)时,意味着这个token的自信息很高——模型对这个选择感到"意外",输出它就像是赌对了黑马。

这个视角解释了为什么我们可以用logprobs来检测幻觉。当模型在编造信息时,它实际上在输出一些自信息很高的token——不是因为它真的"知道"这些信息,而是因为它被迫在缺乏确定性时做出选择。

graph LR
    subgraph 高置信度输出
        A1["logprob ≈ -0.1"] --> B1["自信息很低"]
        B1 --> C1["模型预料之中"]
        C1 --> D1["输出可靠 ✓"]
    end
    
    subgraph 低置信度输出
        A2["logprob ≈ -5.0"] --> B2["自信息很高"]
        B2 --> C2["模型感到意外"]
        C2 --> D2["可能幻觉 ⚠️"]
    end

从Logits到Logprobs:完整的计算链路

让我们看看模型内部究竟发生了什么。整个过程可以用下图表示:

flowchart LR
    A[输入文本] --> B[Transformer编码]
    B --> C[最后一层线性变换]
    C --> D[Logits向量]
    D --> E[Softmax归一化]
    E --> F[概率分布]
    F --> G[取对数]
    G --> H[Logprobs]
    
    subgraph 选择过程
        H --> I[采样策略]
        I --> J[输出Token]
    end

Transformer的最后一层输出一个向量,维度等于词表大小(比如GPT-3是50257维)。这个向量中的每个值就是logit——一个未归一化的原始分数。

Logits可以是任何实数,正的、负的、很大的、很小的。通过softmax函数,这些分数被转换为概率分布:

$$\text{softmax}(z_i) = \frac{e^{z_i}}{\sum_j e^{z_j}}$$

这个概率分布满足两个条件:每个值都在$[0, 1]$之间,所有值加起来等于1。

最终,logprob就是概率的对数:

$$\text{logprob}_i = \log\left(\frac{e^{z_i}}{\sum_j e^{z_j}}\right) = z_i - \log\sum_j e^{z_j}$$

注意最后这个等式:logprob实际上等于原始logit减去一个归一化常数。这就是为什么在许多实现中,我们可以直接从logits计算logprobs,而不需要先算概率再取对数。

Logprobs的数值范围与解读

Logprobs的一个关键特征是:它们永远是负数或零

为什么?因为概率$P$在$[0, 1]$区间内,而$\log(x)$在$x \in (0, 1]$时是负数或零。具体来说:

  • $\log(1) = 0$:概率为100%对应logprob为0
  • $\log(0.5) \approx -0.69$:概率为50%对应logprob约为-0.69
  • $\log(0.1) \approx -2.3$:概率为10%对应logprob约为-2.3
  • $\log(0.01) \approx -4.6$:概率为1%对应logprob约为-4.6

从概率到logprob的转换公式:

$$\text{logprob} = \ln(\text{probability})$$

从logprob还原概率的公式:

$$\text{probability} = e^{\text{logprob}}$$
概率 Logprob(自然对数) 解读
1.0 0 完全确定
0.9 -0.105 高置信度
0.5 -0.693 不确定
0.1 -2.303 低置信度
0.01 -4.605 很不自信
0.001 -6.908 几乎是猜测
xychart-beta
    title "概率与Logprob的对应关系"
    x-axis "概率" [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]
    y-axis "Logprob" -6 --> 0
    line [-2.3, -1.6, -1.2, -0.9, -0.7, -0.5, -0.4, -0.2, -0.1, 0]

在实际的大模型输出中,常见的情况是:大多数token的logprob在-0.1到-1之间,表示模型对这些选择有相当高的置信度。偶尔会出现logprob在-3或更低的情况,这通常意味着模型在猜测或者被迫做出了一个不太可能的选择。

序列级别的置信度:聚合方法

单个token的logprob有价值,但更多时候我们关心的是整个回答的置信度。这就涉及到如何聚合多个token的logprobs。

算术平均

最直接的方法是计算所有token logprobs的平均值:

$$\text{seq\_logprob} = \frac{1}{n}\sum_{i=1}^{n} \log P(t_i)$$

这个方法的优点是简单、可解释。缺点是对序列长度不敏感——一个很长的不确定序列和一个很短的不确定序列可能得到相同的平均logprob。

几何平均与困惑度

实际上,序列概率是各个token概率的乘积:

$$P(\text{sequence}) = \prod_{i=1}^{n} P(t_i)$$

取对数后变成:

$$\log P(\text{sequence}) = \sum_{i=1}^{n} \log P(t_i)$$

而**困惑度(perplexity)**正是这个概念的一个变体:

$$\text{PPL} = \exp\left(-\frac{1}{n}\sum_{i=1}^{n} \log P(t_i)\right)$$

困惑度可以理解为模型的"平均分支因子"。困惑度为10意味着模型在每个位置平均有10个等概率的选择。困惑度越低,模型越确定;困惑度越高,模型越困惑。

困惑度和平均logprob的关系是:

$$\text{PPL} = e^{-\text{avg\_logprob}}$$
xychart-beta
    title "平均Logprob与困惑度的关系"
    x-axis "平均Logprob" [-3, -2, -1, -0.5, -0.1]
    y-axis "困惑度" 0 --> 25
    bar [20.1, 7.4, 2.7, 1.6, 1.1]

加权聚合

在某些场景中,不同位置的token可能有不同的重要性。比如在分类任务中,最关键的是第一个token(分类标签),后面的解释文字可能不那么重要。

一种实践是只看关键token的logprob。比如在做情感分类时,如果模型输出"积极",我们只关心"积极"这个词的logprob,而不是整个句子的平均logprob。

实战场景一:分类任务的置信度评估

在分类任务中,logprobs尤其有用。假设我们构建一个垃圾邮件分类器:

系统提示:你是一个邮件分类器。判断以下邮件是否为垃圾邮件。
只回答"是"或"否",不要输出其他内容。

邮件内容:恭喜您中奖了!点击链接领取100万现金大奖...

模型输出:“是”。

我们获取这个token的logprob,假设是-0.05。转换为概率:$e^{-0.05} \approx 0.95$。模型有95%的置信度认为这是垃圾邮件。

但如果logprob是-2.0呢?这意味着模型只有约13.5%的置信度。在这种情况下,我们可能不应该自动处理这封邮件,而是标记为需要人工审核。

flowchart TD
    A["模型输出: 是"] --> B{"Logprob阈值判断"}
    B -->|"logprob > -0.5<br/>置信度 > 60%"| C["自动处理"]
    B -->|"-1.5 < logprob < -0.5<br/>置信度 22%-60%"| D["标记为低置信度"]
    B -->|"logprob < -1.5<br/>置信度 < 22%"| E["转人工审核"]
    
    C --> F["✓ 高效自动化"]
    D --> G["⚠ 需要关注"]
    E --> H["🔍 人工介入"]

这种置信度阈值策略在实际系统中非常有效。一个典型的配置是:

  • 置信度 > 90%:自动接受分类结果
  • 置信度 50%-90%:接受结果但标记为低置信度
  • 置信度 < 50%:转人工审核或拒绝处理

多分类场景

在多分类场景中,logprobs同样有效。假设我们分类新闻文章的类型:体育、财经、科技、娱乐。

模型输出:“科技”,logprob = -0.3(概率约74%)。

但更有价值的是看top_logprobs——模型在"科技"这个词上还考虑了哪些其他选项。API通常会返回top-5个候选token及其logprobs:

科技: -0.3 (74%)
财经: -1.5 (22%)
体育: -4.2 (1.5%)
娱乐: -5.8 (0.3%)

这个分布告诉我们,模型主要在"科技"和"财经"之间犹豫。这种信息对于理解模型的决策边界非常有价值。

实战场景二:幻觉检测

大模型最大的问题之一是幻觉(hallucination)——模型会自信地编造虚假信息。传统的检测方法需要额外的模型或复杂的后处理,但logprobs提供了一个简单有效的信号。

直觉是这样的:当模型在"编造"时,它实际上在缺乏训练数据支持的情况下被迫做出选择。这种情况下,输出token的logprob通常会偏低。

一项来自Gusto工程团队的实验很有说服力。他们对1000个客户支持问题进行了测试,让专家标注模型输出的质量,然后分析logprobs与质量的关系:

xychart-beta
    title "Logprobs置信度与输出准确率的关系"
    x-axis ["低置信度", "中低置信度", "中高置信度", "高置信度"]
    y-axis "准确率 (%)" 0 --> 100
    bar [45, 58, 68, 76]

结果显示,置信度最低的四分之一输出的准确率只有45%,而置信度最高的四分之一达到76%——差距高达69%的相对差异。

这个发现意味着:我们可以用logprobs作为幻觉检测的过滤器。当模型输出的平均logprob低于某个阈值时,我们有理由怀疑它在"乱编"。

一个具体的阈值建议

根据多项研究和实践经验,以下是一些参考阈值:

  • 平均logprob > -0.5:高置信度,通常可靠
  • 平均logprob在-0.5到-1.0之间:中等置信度,需要关注
  • 平均logprob在-1.0到-2.0之间:低置信度,建议人工复核
  • 平均logprob < -2.0:很可能存在幻觉

但要注意:这些阈值高度依赖于具体的模型、任务和prompt设计。一个在分类任务上校准好的模型,在创意写作任务上可能表现得完全不同。最佳实践是在自己的数据上进行校准。

实战场景三:RAG系统中的可靠性评估

检索增强生成(RAG)是解决幻觉问题的主要方案之一。但RAG本身也有问题:检索到的文档可能不相关,或者根本不包含用户需要的信息。模型仍然可能在上下文不足的情况下"编造"答案。

Logprobs可以作为一个检索质量的后验信号

当RAG系统检索到相关文档时,模型通常会在答案中引用这些文档。如果检索质量好,模型对答案的logprobs应该较高。如果检索到的文档与问题无关,模型会发现自己被迫在缺乏支持的情况下回答,logprobs会降低。

flowchart TD
    A["用户问题"] --> B["检索文档"]
    B --> C{"文档相关?"}
    C -->|是| D["上下文丰富"]
    D --> E["模型自信回答"]
    E --> F["logprob高"]
    
    C -->|否| G["上下文不足"]
    G --> H["模型被迫猜测"]
    H --> I["logprob低"]
    
    F --> J["✓ 可信输出"]
    I --> K["⚠ 可能幻觉"]

一种实用的策略是:

  1. 设置一个logprob阈值
  2. 如果模型答案的logprob低于阈值,可能意味着检索文档没有提供足够信息
  3. 在这种情况下,返回一个默认回复:“抱歉,我没有找到相关信息来回答您的问题”

这种方法比简单地问模型"你能回答这个问题吗"更可靠,因为它反映的是模型真实的内部状态,而不是生成的文本。

温度参数如何影响Logprobs

温度(temperature)是控制模型输出多样性的关键参数,它直接影响logprobs的计算。

在softmax中加入温度参数$T$:

$$\text{softmax}_T(z_i) = \frac{e^{z_i/T}}{\sum_j e^{z_j/T}}$$

温度越高,概率分布越平滑;温度越低,概率分布越尖锐。

xychart-beta
    title "温度对概率分布的影响"
    x-axis ["Token A", "Token B", "Token C", "Token D"]
    y-axis "概率" 0 --> 0.8
    bar [0.7, 0.15, 0.1, 0.05]
    bar [0.4, 0.3, 0.2, 0.1]
    bar [0.28, 0.26, 0.24, 0.22]

从logprobs的角度看:

  • 当$T=0$(贪婪解码):模型只选择概率最高的token,输出的logprob总是整个分布中最大的那个。这个logprob反映了模型对"最佳选择"的置信度。
  • 当$T=1$(默认):概率分布保持原始形状。
  • 当$T>1$:概率分布被压平,原本概率低的token有更高机会被选中。如果这些token被选中,它们的logprob会比正常情况更低。

一个关键观察:如果你用温度采样(T > 0),返回的logprobs反映的是采样后token在原始分布中的概率,而不是采样后的概率。这意味着即使一个低概率token因为高温度被选中,它的logprob仍然会是较低的负数。

这给我们的启示是:在做置信度评估时,应该使用$T=0$(贪婪解码)。这样可以确保你看到的是模型"真心认为最好的选择"及其对应的置信度。

校准问题:Logprobs真的是"真实"置信度吗

到这里你可能会想:logprobs看起来很完美,它不就是模型的"真实"置信度吗?

问题是:模型可能过度自信或自信不足

一个校准完美的模型,如果输出logprob对应概率0.8,那么它应该在实际中有80%的时候是对的。但研究发现,大模型往往过度自信——它可能输出0.95的概率,但实际只有70%是对的。

这种现象被称为校准误差(calibration error)。最常用的度量指标是期望校准误差(Expected Calibration Error, ECE)

$$\text{ECE} = \sum_{m=1}^{M} \frac{|B_m|}{n} | \text{acc}(B_m) - \text{conf}(B_m) |$$

其中$B_m$是第$m$个置信度区间,$\text{acc}(B_m)$是该区间的实际准确率,$\text{conf}(B_m)$是该区间的平均置信度。

graph TD
    A["校准问题"] --> B["过度自信"]
    A --> C["自信不足"]
    
    B --> D["模型输出高置信度"]
    D --> E["实际准确率较低"]
    
    C --> F["模型输出低置信度"]
    F --> G["实际准确率较高"]
    
    H["校准方法"] --> I["温度缩放"]
    H --> J["Platt缩放"]
    H --> K["直方图分箱"]

好消息是,我们可以对logprobs进行后处理校准。温度缩放是最简单的方法:用一个标量$T$调整logits:

$$\text{calibrated\_logprob} = \text{logprob} / T$$

$T > 1$会降低置信度,$T < 1$会提高置信度。最优的$T$可以通过在验证集上优化ECE来获得。

工程实践中的注意事项

在实际部署中使用logprobs,有几个关键细节需要注意:

API限制

不是所有模型API都支持返回logprobs。OpenAI的API在2023年底引入了这个功能,但某些模型(如gpt-4-vision-preview)不支持。Anthropic的Claude API在2024年中开始支持logprobs。

返回的logprobs数量也有限制。OpenAI API默认返回top-5个候选token的logprobs,最多可以设置到20个。

计算开销

获取logprobs本身几乎没有额外计算开销——它是softmax输出的副产品。但存储和处理这些数据会增加内存和后处理时间。对于高吞吐量的系统,需要权衡是否真的需要完整的logprobs信息。

模型间差异

不同模型的logprobs分布可能有显著差异。一个在GPT-4上校准好的阈值,直接应用到Claude或Llama上可能完全不适用。每个模型都需要独立校准。

Tokenization的影响

Logprobs是针对token而非字符或单词的。不同的tokenizer会把文本切成不同的token序列,这会影响logprobs的计算和比较。比如,同一个词在不同模型中可能被切成不同数量的token,每个token有自己的logprob。

从Logprobs到更广阔的不确定性估计

Logprobs是模型不确定性的一种度量,但它不是唯一的方法。在研究文献中,不确定性估计方法可以分为几类:

白盒方法(需要访问模型内部状态):

  • Logprobs:本文讨论的主要内容
  • MC Dropout:在推理时启用dropout,多次采样估计不确定性
  • 梯度分析:通过梯度信息判断模型对输入的敏感程度

黑盒方法(只需要模型输出):

  • Self-consistency:让模型多次生成答案,观察一致性
  • 语义熵:分析多个生成答案的语义差异
flowchart TD
    A["不确定性估计方法"] --> B["白盒方法"]
    A --> C["黑盒方法"]
    
    B --> D["Logprobs<br/>计算简单"]
    B --> E["MC Dropout<br/>更准确"]
    B --> F["梯度分析<br/>信息丰富"]
    
    C --> G["Self-Consistency<br/>无需访问内部"]
    C --> H["语义熵<br/>处理语义等价"]
    
    D --> I["★ 推荐首选<br/>成本低效果好"]

在实际应用中,logprobs因其计算简单、无需多次推理而成为最实用的选择。Self-consistency等方法虽然可能更准确,但需要多次调用模型,成本和延迟都会增加。

写在最后

Logprobs就像是模型的"测谎仪"。它不完美——模型可能过度自信,也可能在正确时表现得犹豫不决。但它提供了一个窗口,让我们窥见模型生成文字背后的概率世界。

当模型输出一个答案时,不要只看文字本身。问一句:logprobs是多少?这个负数里藏着模型的真实想法。

在实践中,最有效的策略是:

  1. 建立基线:在你的任务上收集logprobs数据,观察分布
  2. 设置阈值:根据准确率与召回率的权衡确定决策边界
  3. 持续校准:定期检查校准误差,必要时重新校准
  4. 组合使用:将logprobs与其他不确定性估计方法结合,提高可靠性

Logprobs不能解决所有问题,但它是一个起点——一个从"模型说了什么"到"模型有多确信"的起点。在AI系统越来越重要的今天,理解这个起点,对于构建可靠的AI应用至关重要。


参考文献

  1. Guo, C., et al. “On Calibration of Modern Neural Networks.” ICML 2017.
  2. Xiao, Y., & Wang, W. Y. “On Hallucination and Predictive Uncertainty in Conditional Language Generation.” NAACL 2021.
  3. Kadavath, S., et al. “Language Models (Mostly) Know What They Know.” arXiv 2022.
  4. Lin, S., et al. “Generating with Confidence: Uncertainty Quantification for Black-box Large Language Models.” arXiv 2023.
  5. Kuhn, L., et al. “Semantic Uncertainty: Linguistic Invariances for Uncertainty Estimation in Natural Language Generation.” ICLR 2023.
  6. OpenAI. “Using Logprobs.” OpenAI Cookbook, 2023.
  7. Gusto Engineering. “Tackling AI Hallucinations in LLM Apps.” Medium, 2025.
  8. Gundersen, G. “The Log-Sum-Exp Trick.” 2020.
  9. Shannon, C. E. “A Mathematical Theory of Communication.” Bell System Technical Journal, 1948.
  10. Gal, Y., & Ghahramani, Z. “Dropout as a Bayesian Approximation: Representing Model Uncertainty in Deep Learning.” ICML 2016.