题目重点:KL(q‖p)由q加权,p在q支持集为零时结果为无穷。
题意与假设
这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。
- p/q为同长度非负有限概率向量,归一化和近似1。自然对数nats,不自动归一化任意权重,不把logits直接当概率。
- q_i=0项定义贡献0;q_i>0而p_i=0时KL为+∞,不能静默加epsilon并声称仍是原KL。
解题路线
- 公式Σq_i(log q_i−log p_i),只在q>0索引上计算,避免0×log0产生NaN。先检测p在该支持集是否为0,再计算有限项。
- H(q,p)=H(q)+KL(q‖p),q固定时最小化cross entropy与该方向KL等价。q也可学习时H(q)不再常数,不能直接删除。logits蒸馏通常先固定teacher target,再对student logprob求loss。
- KL不对称,也不满足三角不等式,不是metric。交换p/q会改变哪个支持集要求非零;讨论mode-seeking/covering需要给模型家族与优化条件,不能仅由方向名字断言所有训练行为。
- 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支持条件有何区别?
