我正在使用这个仓库(https://github.com/SnailWalkerYC/LeNet-5_Speed_Up)并尝试学习神经网络细节。该仓库用 C 和 CUDA 实现了 LeNet5。我现在关注的是 CPU 部分及其在 seq/ 中的代码。我迷失的一个特别的地方是 seq/lenet.c 中的这个函数
static inline void softmax(double input[OUTPUT], double loss[OUTPUT], int label, int count){
double inner = 0;
for (int i = 0; i < count; ++i){
double res = 0;
for (int j = 0; j < count; ++j){
res += exp(input[j] - input[i]);
}
loss[i] = 1. / res;
inner -= loss[i] * loss[i];
}
inner += loss[label];
for (int i = 0; i < count; ++i){
loss[i] *= (i == label) - loss[i] - inner;
}
}
因为没有注释,所以我花了一些时间来了解这个功能。最后我发现它正在计算 MSE 损失函数相对于 softmax 层输入的导数。
然后我尝试将交叉熵损失函数与 softmax 一起使用,所以我用以下函数来替换上面的函数。
static inline void softmax(double input[OUTPUT], double loss[OUTPUT], int label, int count)
{
double inner = 0;
double max_input = -INFINITY;
// Find the maximum input value to prevent numerical instability
for (int i = 0; i < count; ++i)
{
if (input[i] > max_input)
max_input = input[i];
}
// Compute softmax and cross-entropy loss
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;
}
double softmax_output[OUTPUT];
for (int i = 0; i < count; ++i)
{
loss[i] /= sum_exp;
softmax_output[i] = loss[i];
}
// Compute cross-entropy loss and derivatives
inner = -log(softmax_output[label]);
for (int i = 0; i < count; ++i)
{
loss[i] = softmax_output[i] - (i == label);
}
}
但是,使用我的 softmax() 函数版本,MNIST 识别不起作用。原始版本的准确率>96%。我的交叉熵损失代码有什么问题?
好吧,我会回答我自己的问题。
我成功地使交叉熵损失与 softmax 一起工作。有两个地方需要调整:
loss[i] = (i == label) - softmax_output[i]; // opposite of the common form
而不是
loss[i] = softmax_output[i] - (i == label); // conforms to the common form
这是因为这个 CNN 更新权重和偏差的方式是违反直觉的:
double k = ALPHA / batchSize;
FOREACH(i, GETCOUNT(LeNet5))
((double *)lenet)[i] += k * buffer[i];
因此,要么使用损失导数的相反形式,要么将权重更新更改为
((double *)lenet)[i] -= k * buffer[i];
0.5
。我见过的大多数地方都使用过0.1
。通过上述 1) 和 2),模型现在一致地预测 >%96 水平。
嗯,在这个过程中我学到了很多东西。