Research Engineer / ML · ML Coding

KL Divergence:方向、零概率和Cross Entropy关系

KL(q‖p)由q加权,p在q支持集为零时结果为无穷。

题目重点:KL(q‖p)由q加权,p在q支持集为零时结果为无穷。

题意与假设

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

  • p/q为同长度非负有限概率向量,归一化和近似1。自然对数nats,不自动归一化任意权重,不把logits直接当概率。
  • q_i=0项定义贡献0;q_i>0而p_i=0时KL为+∞,不能静默加epsilon并声称仍是原KL。

解题路线

  1. 公式Σq_i(log q_i−log p_i),只在q>0索引上计算,避免0×log0产生NaN。先检测p在该支持集是否为0,再计算有限项。
  2. H(q,p)=H(q)+KL(q‖p),q固定时最小化cross entropy与该方向KL等价。q也可学习时H(q)不再常数,不能直接删除。logits蒸馏通常先固定teacher target,再对student logprob求loss。
  3. KL不对称,也不满足三角不等式,不是metric。交换p/q会改变哪个支持集要求非零;讨论mode-seeking/covering需要给模型家族与优化条件,不能仅由方向名字断言所有训练行为。
  4. PyTorch kl_div的input通常是log p、target是q;使用sum才是单向量公式,batchmean按batch归一化,mean按元素数归一化。接口名相同并不代表参数顺序相同。

手动走一遍

q=[0,1],p=[1,0],KL无穷;q=p=[0,1]时0项略过,结果0。q=[0.5,0.5],p=[0.9,0.1],KL约0.5108nats;交换方向约0.3681,不相等。对平滑target,CE含其非零熵;不能把CE数值直接报告为KL。

Python 参考实现

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

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

import numpy as np

def cross_entropy(logits,targets,mask=None,smoothing=0.0):
    x=np.asarray(logits,dtype=float);y=np.asarray(targets)
    if x.ndim!=2 or x.shape[1]<1 or y.shape!=(len(x),) or not np.issubdtype(y.dtype,np.integer):raise ValueError('logits N,C / integer targets N')
    if not np.isfinite(x).all() or not 0<=smoothing<=1 or np.any(y<0) or np.any(y>=x.shape[1]):raise ValueError('values')
    active=np.ones(len(x),dtype=bool) if mask is None else np.asarray(mask,dtype=bool)
    if active.shape!=(len(x),):raise ValueError('mask')
    count=int(active.sum())
    if not count:return 0.0,np.zeros_like(x)
    shifted=x-x.max(axis=1,keepdims=True);exp=np.exp(shifted);prob=exp/exp.sum(axis=1,keepdims=True)
    logprob=shifted-np.log(exp.sum(axis=1,keepdims=True))
    q=np.full_like(x,smoothing/x.shape[1]);q[np.arange(len(x)),y]+=1-smoothing
    loss=float((-(q*logprob).sum(axis=1))[active].mean())
    gradient=(prob-q)/count;gradient[~active]=0
    return loss,gradient

def kl_divergence(q,p):
    q=np.asarray(q,dtype=float);p=np.asarray(p,dtype=float)
    if q.ndim!=1 or q.shape!=p.shape or np.any(q<0) or np.any(p<0) or not np.isfinite(q).all() or not np.isfinite(p).all():raise ValueError('finite probability vectors')
    if not np.isclose(q.sum(),1) or not np.isclose(p.sum(),1):raise ValueError('normalized')
    if np.any((q>0)&(p==0)):return float('inf')
    active=q>0
    return float(np.sum(q[active]*(np.log(q[active])-np.log(p[active]))))

复杂度

V类别,时间O(V)、示例转换/索引临时空间O(V)。若输入已经是log probabilities,可减少log调用;稀疏q仍需确认p在所有q支持项的有效值。

易错点与追问

容易失分的边界

  • 用p加权但标题仍写KL(q‖p)。
  • 任意小epsilon既改变分布也改变无穷支持边界。
  • 默认库reduction mean多除一个类别数,使loss尺度误差。

继续深挖

  • 温度蒸馏为何常乘T²,梯度及高温近似怎样推导?
  • 需要有界对称比较时,Jensen–Shannon与KL支持条件有何区别?
✓已复制微信号!