题目重点:推导二分类负对数似然,并用 softplus 分段计算避免大 logit 的溢出和消差。
题意与假设
这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。
- 本文取二分类标签 y∈{0,1},输入为模型原始 logit z=w·x+b,返回 n 个样本的平均负对数似然;题目只说 loss,需现场确认 reduction 与 regularization。
- 不在本文损失中额外加入 L1/L2 或样本权重;输入须有限且非空。
解题路线
- sigmoid 概率 p=1/(1+exp(−z));单样本交叉熵为 −y log p−(1−y)log(1−p)。
- 代数整理得 `softplus(z)−yz`,其中 softplus(z)=log(1+exp(z)),直接计算 exp(z) 在大正数时会溢出。
- 若 z≥0,稳定写作 `log1p(exp(−z))+(1−y)z`;若 z<0,写作 `log1p(exp(z))−yz`。两支指数的自变量都≤0,且避免大数相减。
- 累加各样本再除以 n;对中等 logit 与直接概率公式 202 组独立核对,±1000 极端 logit 另测数值稳定。
手动走一遍
z=0 时 p=1/2,不论 y=0 或 1,loss=ln2。z=1000 且 y=1 时正确损失接近 0;y=0 时损失接近 1000。直接算 `exp(1000)` 会溢出,本分段公式不会。
Python 参考实现
下面的代码按上文假设实现,适合本地运行并追加自己的测试用例。
代码区可横向滚动;使用键盘时,先聚焦代码区再按方向键查看。
from math import exp, isfinite, log1p
def mean_logistic_loss(logits: list[float], labels: list[int]) -> float:
"""Numerically stable binary cross-entropy from raw logits."""
if not logits or len(logits) != len(labels):
raise ValueError("non-empty, equal-length logits and labels required")
total = 0.0
for z, y in zip(logits, labels):
if y not in (0, 1) or not isfinite(z):
raise ValueError("binary labels and finite logits required")
if z >= 0:
total += log1p(exp(-z)) + (1 - y) * z
else:
total += log1p(exp(z)) - y * z
return total / len(logits)
复杂度
给定 n 个 logit 的损失 O(n) 时间、O(1) 额外空间;若从稀疏特征算 z,还需各样本非零特征数的点积成本。
易错点与追问
容易失分的边界
- 把 logit 当概率直接取 log。
- 用 `log(sigmoid(z))` 导致 z 很负时下溢到 log(0)。
- 直接计算 `log(1+exp(z))`,z 很大时溢出。
- 未声明 mean/sum、标签是 0/1 还是 ±1、是否加正则化。
- 把训练损失低等同于线上广告排序指标好,不做离线/线上评估。
继续深挖
- 从 loss 推出对 z 的梯度为何是 sigmoid(z)−y?
- 类别不平衡时样本权重怎样进入公式?
- L2 正则化放在平均损失前还是后,λ 的含义如何变化?
- 多分类 softmax cross-entropy 如何用 log-sum-exp 稳定计算?
