LeNet-5 CPU实现中Softmax+交叉熵损失函数改写后MNIST识别失效问题排查
LeNet-5 CPU实现中Softmax+交叉熵损失函数改写后MNIST识别失效问题排查
嘿,我来帮你梳理下为啥你的交叉熵+Softmax改写完之后,MNIST识别没效果——毕竟原版本用MSE损失能跑到96%以上的准确率,说明模型架构本身是没问题的。
最可能的原因:学习率不匹配
原代码用的是MSE损失,而交叉熵损失的梯度量级和MSE完全不是一个路子。你写的交叉熵梯度公式softmax输出 - 标签的one-hot向量是对的,但这个梯度的幅度和原MSE损失的梯度差异很大。原来适配MSE的学习率,放到交叉熵上可能太大导致模型发散,或者太小导致训练收敛极慢。
解决办法:把学习率大幅降低试试(比如除以10或者100),多试几个值找合适的范围。比如原学习率是0.01,先改成0.001跑一轮看看效果。
潜在的数值不稳定问题(log(0)风险)
在训练初期或者模型初始化不好的时候,正确标签对应的Softmax概率可能会非常接近0,这时候-log(0)会得到负无穷,直接把数值搞崩,后续的梯度更新全乱套。
解决办法:给log里的数值加个极小的epsilon(比如1e-10),避免出现log(0)的情况:
inner = -log(softmax_output[label] + 1e-10);
这个小数值不会影响损失计算的准确性,但能彻底避免数值异常。
可以优化的冗余代码(不影响功能,但更简洁)
你代码里的softmax_output数组其实是多余的——loss数组在计算完Softmax之后已经存了输出值,直接用它来计算梯度就行,不用额外开数组:
// 计算Softmax double sum_exp = 0; for (int i = 0; i < count; ++i) { double exp_val = exp(input[i] - max_input); sum_exp += exp_val; loss[i] = exp_val; } for (int i = 0; i < count; ++i) { loss[i] /= sum_exp; } // 现在loss里存的就是Softmax输出,直接用来计算梯度 double inner = -log(loss[label] + 1e-10); for (int i = 0; i < count; ++i) { loss[i] = loss[i] - (i == label); }
验证步骤
- 先加上epsilon解决log(0)的问题,排除数值异常的可能;
- 调整学习率,从原学习率的1/10开始尝试;
- 训练时打印损失值
inner,如果出现inf或者nan,那就是数值稳定性的锅;如果损失在下降但准确率上不去,基本就是学习率不合适。
备注:内容来源于stack exchange,提问作者bssrdf
相关产品推荐
相关产品推荐

