题目重点:从线性logit到sigmoid推导平均损失,用有限差分核梯度,说明训练与阈值不同步骤。
题意与假设
这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。
- 训练二分类线性logit=z=x·w+b,无正则/权重,平均BCE;代码提供loss和梯度,优化循环可按约定迭代。
- 特征和参数有限,但过大线性和仍需限制数值范围;目标标签0/1。
解题路线
- 完整训练循环在稳定 BCE 梯度之后初始化权重/偏置,每轮先算本轮 loss 与梯度,再同步执行 w←w−ηdw、b←b−ηdb,保留更新前 loss。示例使用 full-batch、零初始化、固定迭代数、无正则,不把训练 loss 改善当泛化证据;验证集划分与标准化参数只能从训练集估计。
- 模型p=σ(z),它是概率模型,不先把p按0.5硬化再求损失。BCE=−ylogp−(1−y)log(1−p),可改写softplus(z)−yz。
- 稳定softplus=max(z,0)+log1p(exp(−|z|)),避免exp(1000)溢出;sigmoid正负分支分别避免大正指数。不要通过随意clip概率改变正确logit损失。
- 单样本对z梯度p−y,权重为(p−y)x,偏置为p−y;全batch平均。零特征时权重梯度0但偏置仍可学习基础率,bias不能漏。
- 训练更新w←w−lr·dw、b←b−lr·db,检查损失、梯度与验证集。每次loss下降并非泛化保证,学习率太大可震荡;特征尺度差会影响优化条件。
- 正则可加L2等,但要明确是否惩罚bias、系数中的1/2以及mean/sum约定;多类softmax与多标签独立sigmoid不是相同模型。
- 0.5决策阈值只在特定代价下自然,业务可用验证集选阈值,AUC排名与校准另测;不要以训练准确率独自决定质量。
- 有限差分分别扰动每个w与b,核解析梯度;极端logit单测forward稳定,不在sigmoid饱和处单凭相对误差判断错误。
手动走一遍
w=0,b=0时每例p0.5,正例梯度对z为−0.5,负例+0.5。若x=0且正例更多,只bias会调整基础概率;大正z预测负类时BCE仍应近似z而非无穷。
Python 参考实现
下面的代码按上文假设实现,适合本地运行并追加自己的测试用例。
代码区可横向滚动;使用键盘时,先聚焦代码区再按方向键查看。
from math import exp, log1p, isfinite
def sigmoid(z):
if z >= 0:
return 1 / (1 + exp(-z))
e = exp(z)
return e / (1 + e)
def logistic_loss_gradient(x, y, weights, bias):
if not x or len(x) != len(y) or any(len(row) != len(weights) for row in x):
raise ValueError('matching rectangular training data required')
if any(label not in (0, 1) for label in y):
raise ValueError('binary labels required')
if any(not isfinite(v) for row in x for v in row) or any(not isfinite(v) for v in weights) or not isfinite(bias):
raise ValueError('finite data required')
loss, grad, db = 0.0, [0.0] * len(weights), 0.0
for row, label in zip(x, y):
z = bias + sum(a * b for a, b in zip(row, weights))
loss += max(z, 0) - label * z + log1p(exp(-abs(z)))
residual = sigmoid(z) - label
for j, value in enumerate(row):
grad[j] += residual * value
db += residual
n = len(x)
return loss / n, [g / n for g in grad], db / n
def train_logistic(x, y, learning_rate=0.1, steps=100):
"""Full-batch gradient descent, zero initialization; no regularization.
History is the objective before each update. Fixed iteration budget."""
if not isfinite(learning_rate) or learning_rate <= 0 or steps < 0:
raise ValueError('positive finite rate and nonnegative steps required')
if not x:
raise ValueError('nonempty training data required')
weights, bias, history = [0.0] * len(x[0]), 0.0, []
logistic_loss_gradient(x, y, weights, bias) # validate even for zero steps
for _ in range(steps):
loss, dw, db = logistic_loss_gradient(x, y, weights, bias)
history.append(loss)
weights = [w - learning_rate * g for w, g in zip(weights, dw)]
bias -= learning_rate * db
return weights, bias, history
复杂度
N×D输入每步O(ND),梯度O(D)工作空间,数据O(ND)由调用方持有。T次全batch训练O(TND),不承诺一步求最优。
易错点与追问
容易失分的边界
- 先threshold再反向。
- 偏置漏梯度。
- 损失mean但梯度sum。
- sigmoid单公式数值溢出。
继续深挖
- L2正则怎样改?
- 稀疏特征如何加速?
- 类别不平衡怎样选损失/阈值?
