实现带L2正则化的Softmax交叉熵损失时遇形状不匹配及负损失问题
Hey,我看你在复现《A Fast and Accurate Dependency Parser using Neural Networks》里的损失函数时遇到了两个棘手问题:训练出现负损失,还触发了形状不匹配的报错。咱们一步步拆解解决:
1. 先搞定最直观的形状不匹配错误
你碰到的Cannot feed value of shape (48,) for Tensor u'Placeholder_2:0', which has shape '(48, 1)',核心是标签的形状和TensorFlow的要求对不上,分两种情况处理:
如果你用的是稀疏标签(每个样本就是一个类别索引,比如[3, 0, 2])
这种情况下别用tf.nn.softmax_cross_entropy_with_logits,它是给one-hot标签用的。换成tf.nn.sparse_softmax_cross_entropy_with_logits,这个函数专门适配稀疏标签,此时标签形状(48,)完全符合要求:
# 替换原来的交叉熵计算逻辑 cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits( logits=self.prediction, labels=self.train_labels ) # 加上L2正则项,记得补全你其他的权重参数 self.loss = tf.reduce_mean(cross_entropy) + 1e-8 * (tf.nn.l2_loss(w1) + tf.nn.l2_loss(w2))
同时检查你的self.train_labels占位符,定义成shape=(None,)或者shape=(48,)就行,不用多那一维。
如果你用的是one-hot编码标签(每个样本是[0,1,0]这种格式)
那标签的形状必须和logits完全一致。假设你的self.prediction(logits)是(48, num_classes),那train_labels也得是(48, num_classes)。如果现在你的标签是(48,),先转成one-hot:
# 喂数据前转换,或者在预处理时提前做好 train_labels_one_hot = tf.one_hot(train_labels, depth=你的类别数)
然后把占位符的形状改成(None, 你的类别数),确保和logits匹配。
2. 解决负损失的诡异问题
正常来说Softmax交叉熵损失肯定是非负的,出现负数大概率是这几个原因:
交叉熵函数用错了
要是你把稀疏标签塞进了tf.nn.softmax_cross_entropy_with_logits,函数会把索引值当成one-hot编码的数值(比如标签3会被当成非0/1的向量值),这就会导致交叉熵计算出负数。解决方法就是上面说的,根据标签类型选对函数。
标签数据有问题
检查下你的train_labels是不是有异常值:比如one-hot标签里出现了大于1或者小于0的数,或者稀疏标签超出了类别索引范围(比如只有8个类别,结果标签出现了10)。这些都会让交叉熵计算乱套,出现负数。
Logits维度错了
确保self.prediction的最后一维是类别数,tf.nn.softmax_cross_entropy_with_logits默认在最后一维算softmax。要是你把类别数放在了其他维度,softmax算错了,损失自然会出问题。可以加个断言验证:
tf.debugging.assert_equal(tf.shape(self.prediction)[-1], 你的类别数, message="Logits最后一维不是类别数!")
3. 实现忽略/屏蔽特定标签的功能
论文要求支持忽略特定标签,咱们用损失掩码就能搞定,假设你用-1标记要忽略的样本:
第一步:生成掩码
# 稀疏标签的情况:mask为True的样本参与损失计算 mask = tf.not_equal(self.train_labels, -1) # 如果是one-hot标签,假设忽略的是全0向量,就这么写: # mask = tf.reduce_any(self.train_labels, axis=-1)
第二步:过滤损失并计算有效均值
# 先算每个样本的交叉熵 if 用的是稀疏标签: cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=self.prediction, labels=self.train_labels) else: cross_entropy = tf.nn.softmax_cross_entropy_with_logits(logits=self.prediction, labels=self.train_labels) # 把要忽略的样本损失置为0 masked_cross_entropy = tf.where(mask, cross_entropy, tf.zeros_like(cross_entropy)) # 注意:不能直接用reduce_mean,得除以有效样本数,不然会被忽略的样本拉低损失 num_valid_samples = tf.reduce_sum(tf.cast(mask, tf.float32)) self.loss = (tf.reduce_sum(masked_cross_entropy) / num_valid_samples) + 1e-8 * (tf.nn.l2_loss(w1) + tf.nn.l2_loss(w2))
最后给你几个验证小技巧
- 先打印几个
train_labels样本,看看形状和数值是不是符合预期; - 单独计算一两个样本的交叉熵,手动算一遍验证是不是非负;
- 确认正则项是加进去的(你当前代码是对的,别写成减了)。
内容的提问来源于stack exchange,提问作者dreamin

