题目重点:从 logit 到 sigmoid/BCE,再推导参数梯度并用中心差分找实现错误。
题意与假设
这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。
- 训练代码做二分类 y∈{0,1}、batch 平均 BCE、无正则,logit z=x·w+b;题目未公开正则化、样本权重、数据格式或 bug 行。
- 用 logits 形式的稳定损失 max(z,0)-zy+log(1+exp(-|z|)),避免直接算 log(sigmoid(z))。
解题路线
- 先把 forward 分层检查:shape、z=x·w+b、稳定 sigmoid、loss;对极大正/负 z 试运行,防 exp 溢出与 log(0)。
- 由 dL/dz=sigmoid(z)-y 得 batch 梯度 ∂L/∂w_j=(1/N)Σ_i x_ij(p_i-y_i),偏置梯度=(1/N)Σ_i(p_i-y_i)。如果先对 sigmoid 求导再和 BCE 连乘,代数上会约掉 p(1-p),实现时重复乘就错。
- 调试先用单样本、零权重和两个极端标签验证符号,再对每个参数用中心差分 [L(θ+ε)-L(θ-ε)]/(2ε) 检查解析梯度;选择中等输入避免饱和,比较绝对/相对误差。
- 训练代码在 z≥0/负值分别计算 sigmoid,loss 用 log1p(exp(-abs(z)));N 样本求平均仅在最后除一次 N。若引入 L2 正则,只作用于指定权重且梯度另加 λw,通常不惩罚偏置。
手动走一遍
单样本 x=2、w=0、b=0、y=1:z=0、p=0.5,dL/dw=2×(−0.5)=−1,dL/db=−0.5;梯度下降会提高 w/b,使正样本预测概率上升。
Python 参考实现
下面的代码按上文假设实现,适合本地运行并追加自己的测试用例。
代码区可横向滚动;使用键盘时,先聚焦代码区再按方向键查看。
from math import exp, log1p
def sigmoid(z: float) -> float:
if z >= 0:
return 1.0 / (1.0 + exp(-z))
ez = exp(z)
return ez / (1.0 + ez)
def loss_and_gradient(features, labels, weights, bias):
if not features or len(features) != len(labels):
raise ValueError("empty or mismatched batch")
d = len(weights)
if any(len(row) != d for row in features):
raise ValueError("feature dimension mismatch")
total_loss = 0.0
grad = [0.0] * d
grad_bias = 0.0
for row, y in zip(features, labels):
if y not in (0, 1):
raise ValueError("binary labels only")
z = sum(x * w for x, w in zip(row, weights)) + bias
total_loss += max(z, 0.0) - z * y + log1p(exp(-abs(z)))
error = sigmoid(z) - y
grad_bias += error
for j, x in enumerate(row):
grad[j] += x * error
n = len(features)
return total_loss / n, [g / n for g in grad], grad_bias / n
复杂度
N 个样本、d 个特征:一次 loss+gradient O(Nd) 时间、O(d) 梯度空间。数值差分逐参数重算 loss,约 O(Nd²) 时间,适合测试小样本而非训练循环。3,000 组随机样本与中心差分独立比对通过。
易错点与追问
容易失分的边界
- 把 sigmoid 导数 p(1-p) 再乘到已简化的 BCE 梯度上。
- loss 先平均、梯度又除 N 两次。
- 直接 log(p),p≈0 时数值崩溃。
- 偏置梯度误乘某个特征。
- 只比较 training accuracy,不做梯度数值检查。
继续深挖
- class imbalance 时样本权重怎么改梯度?
- L2 正则项如何加、是否正则化 bias?
- 多分类 softmax 交叉熵梯度如何推广?
- 极端 logits 仍稳定但预测梯度很小怎么办?
