标准回答
一、先说 Softmax 做什么
它把每个类别的 logit 转成概率,所有类别加起来等于 1。实现时一定要先减掉每行最大值,这是数值稳定技巧,不会改变结果,但能防止 exp 溢出。
二、再说交叉熵
对单个样本来说,就是取真实类别概率的负对数:真实类概率越接近 1,loss 越小;越接近 0,loss 越大。批量时对样本取平均。
三、最后给面试官最想听的结论
Softmax 和交叉熵合起来后,对 logits 的梯度非常干净:dL/dz = p − y。所以工程里通常把它们合并实现,既稳定,也方便反向传播。代码如下:
面试里不要只停在公式或名词,可以补 手撕代码:实现 Softmax 与交叉熵损失 对训练稳定性、泛化、收敛速度或显存/算力的影响。再说明一个常见调参或排错场景,面试官会更容易判断你真的用过这些方法。
import numpy as np
def softmax(z):
"""数值稳定 Softmax:减去每行最大值防止 exp 溢出。z: (N, C)。"""
z = z - z.max(axis=1, keepdims=True) # 平移不改变结果
ez = np.exp(z)
return ez / ez.sum(axis=1, keepdims=True)
def cross_entropy(logits, labels):
"""logits: (N, C);labels: (N,) 整数类别。返回平均损失与梯度 (p - y)。"""
N = logits.shape[0]
p = softmax(logits)
eps = 1e-12
# 取每个样本真实类别的概率,做负对数
loss = -np.mean(np.log(p[np.arange(N), labels] + eps))
# 反向:dL/dz = p - y,其中 y 为 one-hot
grad = p.copy()
grad[np.arange(N), labels] -= 1.0
grad /= N
return loss, grad
if __name__ == '__main__':
rng = np.random.default_rng(0)
logits = rng.normal(0, 5, (4, 3)) # 故意用大值检验稳定性
labels = np.array([0, 2, 1, 2])
loss, grad = cross_entropy(logits, labels)
print('softmax row sums =', softmax(logits).sum(1)) # 全为 1
print('loss =', round(float(loss), 4))
print('grad shape =', grad.shape)常见误区
⚠️ 常见踩坑
误区一:直接 exp。 不减最大值时 logits 大了会溢出成 inf,最后变 NaN。误区二:广播维度写错。 按类别轴归约时忘记 keepdims=True,很容易悄悄算错。误区三:把 softmax 和 log 分得太开。 类别多时最好用 log-softmax / log-sum-exp 思路提升稳定性。
追问
追问 1:为什么 Softmax+交叉熵的梯度是 p−y?
可以先给结论:因为 softmax 的雅可比和交叉熵的 log 导数刚好抵消,最后剩下 p−y。如果展开推导,就是把 L=−Σy_i log p_i 和 p=softmax(z) 复合,对 z 求导;one-hot 的 y 求和为 1,最后化简成 p_k−y_k。这也是工程上常把两者合并实现的原因。
追问 2:Softmax 的平移与尺度不变性?
Softmax 平移不变:所有 logits 同加一个常数,分子分母都会乘同一个 e^c,所以概率不变,这就是减 max 的依据。它不是尺度不变:乘一个系数会改变分布尖锐度,温度越低越接近 argmax,温度越高越平滑。
🔗 相似问题
同一考点的不同问法,换着练更稳
没找到想看的面试题?把你想看的告诉我们 →
延伸学习
按主题分类的相关资源,便于系统复习
