MLE / Research · ML Coding / Fundamentals

TikTok Softmax CE:稳定前向、LogSumExp 与反向梯度

从logits推导平均交叉熵和p−onehot梯度,不先取易下溢的概率对数,覆盖大logit。

题目重点:从logits推导平均交叉熵和p−onehot梯度,不先取易下溢的概率对数,覆盖大logit。

题意与假设

这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。

  • 训练互斥C类分类,输入N×C有限logits及整数标签,batch平均,不含权重、温度或label smoothing。
  • 示例返回loss、probability与dLoss/dLogits;数学有限值仍可能超浮点范围,需要适当数值预算。

解题路线

  1. 每行p_j=exp(z_j)/Σexp(z_k)。减去该行最大值m不改变概率,exp(z_j−m)不再因为单个大正logit直接溢出;行方向要与类别轴一致。
  2. 单样本CE=−log p_y=logΣexp(z)−z_y。直接以logΣexp(z−m)−(z_y−m)计算,不先求可能下溢为0的p_y再log;[1000,−1000]且y=1仍得到loss2000。
  3. softmax导数∂p_j/∂z_k=p_j(δ_jk−p_k),与−log p_y合成后∂L/∂z_k=p_k−1[k=y],平均batch再除N。不要对每个类再多除一次C。
  4. 梯度各类之和0,对整行加同一常数概率和loss不变,两个性质是有效自检。只比较forward数值不能证明backward实现正确,须有限差分每个logit。
  5. one-hot可以用减1索引而无需构造整矩阵;稀疏标签与soft target是不同API,后者标签应归一化并梯度为p−target。ignore/padding需从平均分母中正确排除。
  6. NaN、空batch、类别越界和形状错要拒绝;all-masked attention等情形不能直接塞此分类loss。多标签任务各类可同时为真,通常用独立sigmoid BCE而非同一softmax。
  7. 高置信错例梯度仍明显,loss稳定计算不改变优化目标。验证以Decimal独立算前向,再以扰动logits检查反向,避免用同一推导另一实现互证。

手动走一遍

logits=[0,0]得到[0.5,0.5],标签0,loss=log2,单样本梯度[−0.5,0.5]。两行batch梯度要除2。所有logit加1000结果相同;错类差2000时loss仍有限而非log(0)。

Python 参考实现

下面的代码按上文假设实现,适合本地运行并追加自己的测试用例。

代码区可横向滚动;使用键盘时,先聚焦代码区再按方向键查看。

from math import exp, log, isfinite


def softmax_ce(logits, targets):
    if not logits or len(logits) != len(targets):
        raise ValueError('nonempty matching batch required')
    classes = len(logits[0])
    if not classes or any(len(row) != classes or any(not isfinite(z) for z in row) for row in logits):
        raise ValueError('finite rectangular logits required')
    if any(type(y) is not int or not 0 <= y < classes for y in targets):
        raise ValueError('invalid class index')
    probabilities, gradient, total = [], [], 0.0
    n = len(logits)
    for row, target in zip(logits, targets):
        offset = max(row)
        shifted = [z - offset for z in row]
        weights = [exp(z) for z in shifted]
        denom = sum(weights)
        probs = [w / denom for w in weights]
        total += log(denom) - shifted[target]
        probabilities.append(probs)
        gradient.append([(p - (j == target)) / n for j, p in enumerate(probs)])
    return total / n, probabilities, gradient

复杂度

前向与反向O(NC),保存输出概率/梯度O(NC),单行工作O(C);如只需要loss可流式减少保存,必须计要求输出本身。

易错点与追问

容易失分的边界

  • 直接exp大logit溢出。
  • 先概率再log导致下溢。
  • batch平均梯度忘除N。
  • 多标签误用互斥softmax。

继续深挖

  • soft target和label smoothing怎样改?
  • 带类别权重如何归约?
  • temperature怎样影响梯度?
✓已复制微信号!