Attention Is All You Need – #4:Loss,神经网络犯错的代价
写在前面
天啊,天都黑了,从下午一点到晚上六点半,99MLP的XOR训练正确率还是好低,既然初始化对训练成功率没有显著帮助,那问题会不会在Loss上呢?这篇博客我就要好好研究一下Loss
Loss究竟是什么
现在99MLP使用的损失函数是:
其中:
- L:Loss,也就是损失值。
- \hat y:神经网络预测的输出。
- y:正确答案。
这叫做 平方误差损失(Squared Error Loss)。
例如,我们让神经网络学习 XOR:
| 输入 | 正确答案 y | 预测 \hat y | Loss |
|---|---|---|---|
| (0,0) | 0 | 0.1 | 0.01 |
| (0,1) | 1 | 0.8 | 0.04 |
| (1,0) | 1 | 0.9 | 0.01 |
| (1,1) | 0 | 0.2 | 0.04 |
每个样本都会产生自己的 Loss。四个样本的平均 Loss 为:
而你的 99MLP 当前是逐个样本更新权重的,所以每次反向传播实际上使用的是当前样本的 Loss 导数。
一个非常反直觉的问题
现在的99MLP,在输出层输出Loss的时候,由于输出层使用的是Sigmoid:
它的导数是:
根据链式法则:
问题就在这里,假如网络出了一个超级大的错误,比如正确答案是1,但是网络预测0.01
那带入以后,中间的\hat y(1-\hat y)就会让Loss变得很小很小
这就是平方误差与 Sigmoid 组合时可能出现的问题:网络明明犯了大错,但在输出接近 0 或 1 时,Sigmoid 的导数太小,导致反向传播的梯度被压缩。
GPT提到,另一种损失函数——Binary Cross-Entropy(二元交叉熵,BCE)——就能在数学上消除输出层这个额外的 Sigmoid 导数因子。
二元交叉熵 – Binary Cross-Entropy
为什么要有二元交叉熵
Binary Cross-Entropy,简称 BCE,中文叫二元交叉熵损失。
它的公式是这个:
天啊这都是啥啊连log都来了
这里的 \ln 是自然对数,是以 e 为底的对数。它满足:
最关键的求导性质:
即\ln x的导数是\frac1x
既然是Loss函数,那\hat{y}对BCE的导数是什么呢?
开始推导吧!
因为反向传播最终需要知道:激活前输出 z 变化一点,Loss 会怎么变。
根据链式法则:
一步一步慢慢来,先把y看做常数:
导数有个性质就是两个可导函数之和(或差)的导数,等于它们各自导数的和(或差)。
所以一个一个求吧!
第一项完成!
第二项:
所以:
ok!\hat{y}对L的导数求好啦!
而且也已知Sigmoid的导数:
所以:
得到了!!!!!!!!!!!
来写代码吧~
for epoch in range(20000):
for simple in np.random.permutation(len(train_data)):
y = train_data[simple][2]
haty = network.forward([train_data[simple][0],train_data[simple][1]])
# 因为forward最后返回的是每个神经元的输出,然后最后一层就一个神经元,所以用[0]
# dL_dhaty = 2*(haty[0] - y)
dL_dhaty = ((1-y) / (1 - haty)) - (y/haty)
network.backward(dL_dhaty,0.01)
因为backward里已经算了\frac{\partial \hat{y}}{\partial z},所以咱们直接传入\frac{\partial L}{\partial \hat{y}}就好啦~
当 Sigmoid 的输出在浮点计算中恰好变成 0 或 1 时,这个公式可能出现除以零的问题,产生 NaN 或无穷大,进而污染网络权重,导致训练失败。
不过,我们刚刚已经推导出了 BCE + Sigmoid 的合并梯度:
既然如此,为什么不直接把这个梯度传入反向传播呢?
这样既能简化计算,也可以避免前面分式带来的数值问题!
所以我打算改一改Layer.backward()这样就可以避免这个问题啦
给Layer.backward()加一个gradient_is_dz参数,告诉他输入的梯度是不是已经乘完Sigmoid的导数了
def backward(self, output_gradient, learning_rate,gradient_is_dz = False):
# dhaty_dz = self.leaky_ReLU_derivative(self.z)
if not gradient_is_dz:
if self.activation == "sigmoid":
dhaty_dz = self.sigmoid_derivative(self.z)
else:
dhaty_dz = self.leaky_ReLU_derivative(self.z)
dL_dhaty = output_gradient
delta = dL_dhaty * dhaty_dz
else:
delta = output_gradient
dL_dW = delta @ self.x.T #.T转置矩阵
dL_db = delta
#在更新梯度前算出Loss对贡献的影响
dL_dx = self.W.T @ delta
#梯度下降
self.W -= learning_rate * dL_dW
self.b -= dL_db * learning_rate
return dL_dx
同时Network.backward()也要做修改,判断如果是最后一层而且接受进来的输入是已经乘完Sigmoid导数的梯度,就让Layer.backward直接计算,不乘Sigmoid导数了
def backward(self, output_gradient, learning_rate,output_is_dz = False):
gradient = output_gradient
# hhh我上次写竟然忘了reversed这个函数
for layer in reversed(self.layers):
gradient = layer.backward(gradient, learning_rate, gradient_is_dz=(output_is_dz and self.layers[-1] is layer))
return gradient
最后训练完成以后,告诉Network.backward()这是最后一层而且输入给这个函数的是dL_dz
for epoch in range(20000):
for simple in np.random.permutation(len(train_data)):
y = train_data[simple][2]
haty = network.forward([train_data[simple][0],train_data[simple][1]])
# 因为forward最后返回的是每个神经元的输出,然后最后一层就一个神经元,所以用[0]
# dL_dhaty = 2*(haty[0] - y)
dL_dz = haty-y #最后直接传入dL_dz
network.backward(dL_dz,0.01,output_is_dz=True)
来看100次的实验结果!
xychart-beta
title "令人开心的实验结果"
x-axis ["标准正态","旧He","新He","随机打乱","BCE Loss"]
y-axis "训练成功率 (%)" 0 --> 100
bar [46,45,44,55,67]
这次有了更大的进步!
而且有一个好消息:成功的 67 次,全部达到比 0.5 更严格的标准——正确输出小于 0.1,正确输出 1 大于 0.9。
对于失败的33个样例,有两个现象:
现象一:有些网络输出全部接近 0.5
例如第 13 次:
| 输入 | 真实值 | 预测值 |
|---|---|---|
| (1,1) | 0 | 0.500049 |
| (0,1) | 1 | 0.500050 |
| (1,0) | 1 | 0.500049 |
| (0,0) | 0 | 0.500049 |
这几乎等于不管输入什么,网络都在猜 0.5!
XOR 数据集恰好有两个 0、两个 1,所以恒定输出 0.5 是一个特殊的停滞状态。
这可能意味着网络陷入了对称、梯度很小或其他难以逃离的优化区域。仅凭最终输出,还不能确定具体原因。
现象二:出现大量接近 1/3 和 2/3 的输出
网络似乎学会了区分其中一种输入,但对剩下三种输入给出了几乎相同的预测。
而这三组的真实标签分别是 0、1、0,平均值恰好为:
这正好解释了为什么 BCE 训练中可能出现接近 0.3333 的预测值。
如果网络暂时无法区分这三组输入,那么从 BCE 的角度看,预测它们标签的平均值就是合理的最优常数输出。
同理,也可以解释一些接近 0.6667 的输出。
当然,是否真的因为网络把某些输入映射到了相近的隐藏层表示,还需要进一步观察隐藏层数据。
在这次实验中,换用 BCE 后,99MLP 的训练成功率由之前的 55% 上升到了 67%,提高了 12 个百分点。不过两次实验没有使用完全相同的随机种子,因此不能排除随机波动。至少在当前实验中,我们观察到了 BCE 带来的积极变化。
不错不错,总算是有了大幅度提升!!
接下来嘛。。。。是时候认识下Transformer了!
评论(0)
暂无评论