目录
习题4-2 试设计一个前馈神经网络来解决 XOR 问题,要求该前馈神经网络具有两个隐藏神经元和一个输出神经元,并使用 ReLU 作为激活函数。
习题4-3 试举例说明“死亡ReLU问题”,并提出解决方法。
【ReLU激活函数】
【死亡ReLU问题】
【死亡ReLU问题解决方法】
习题4-7 为什么在神经网络模型的结构化风险函数中不对偏置b进行正则化?
习题 4-8 为什么在用反向传播算法进行参数学习时要采用随机参数初始化的方式而不是直接令 w= 0, = 0?
习题4-9 梯度消失问题是否可以通过增加学习率来缓解?
参考资料
XOR问题其实就是我们常说的异或问题,当两数数值相同时为0,数值不同时为1。
x1 | x2 | x1 XOR x2 |
0 | 0 | 0 |
0 | 1 | 1 |
1 | 0 | 1 |
1 | 1 | 0 |
import torch
from torch.nn.init import constant_, normal_
import torch.nn as nn
import torch.optim as optim
import numpy as np
class XORModule(nn.Module):
def __init__(self):
super(XORModule, self).__init__()
self.fc1 = nn.Linear(2, 2)
self.fc2 = nn.Linear(2, 1)
self.relu = nn.ReLU()
def forward(self, x):
x = x.view(-1, 2)
x = self.relu((self.fc1(x)))
x = self.fc2(x)
return x
# 输入和输出数据
input_x = torch.Tensor([[0, 0], [0, 1], [1, 0], [1, 1]])
input_x1 = input_x.float()
real_y = torch.Tensor([[0], [1], [1], [0]])
real_y1 = real_y.float()
# 设置损失函数和参数优化函数
net = XORModule()
loss_function = nn.MSELoss() # 用交叉熵损失函数会出现维度错误
optimizer = optim.SGD(net.parameters(), lr=0.01) # 用Adam优化参数选不好会出现计算值超出0-1的范围
# 进行训练
for epoch in range(10000):
out_y = net(input_x1)
loss = loss_function(out_y, real_y1) # 计算损失函数
optimizer.zero_grad() # 对梯度清零,避免造成累加
loss.backward() # 反向传播
optimizer.step() # 参数更新
# 输出权值和偏置
print('w1 = ', net.fc1.weight.detach().numpy())
print('b1 = ', net.fc1.bias.detach().numpy())
print('w2 = ', net.fc2.weight.detach().numpy())
print('b2 = ', net.fc2.bias.detach().numpy())
# 测试
input_test = input_x1
out_test = net(input_test)
print('input_x:\n', input_test.detach().numpy())
print('out_y:\n', np.around(out_test.detach().numpy()))
运行结果:
ReLU激活函数是为了解决梯度消失问题提出的。其梯度只可以取两个值:当输入小于0时,梯度为0;当输入大于0时,梯度为1。使用ReLU激活函数的好处是:ReLU的梯度的连乘不会收敛到0 ,连乘的结果也只可以取两个值:0或1 ,如果值为1 ,梯度保持值不变进行前向传播;如果值为0 ,梯度从该位置停止前向传播。Sigmoid和ReLU函数对比如下:
但ReLU也存在着缺点,在反向传播过程中,如果学习率比较大,一个很大的梯度经过ReLU神经元,可能会导致ReLU神经元更新后的偏置和权重是负数,进而导致下一轮正向传播过程中ReLU神经元的输入是负数,输出是0。由于ReLU神经元的输出为0,在后续迭代的反向过程中,该处的梯度一直为0,相关参数不再变化,从而导致ReLU神经元的输入始终是负数,输出始终为0。即为“死亡ReLU问题”。
以二分类问题为例,采用交叉熵作损失函数,其中为真实值,为预测值
当与不同时,一个为0一个为1时,L为无穷大
反之与相同时,L为0
当遇到负例时,变小,W变小,此时,若学习率设置过大,则会导致W突然变得非常小,以至于输出为负数,进过ReLU激活函数后无法激活,后续将不会得到更新,导致了死亡结点。
使用Leaky ReLU、PReLU、ELU函数或者Softplus函数替换。
以LeakyReLU为例,LeakyReLU的提出就是为了解决神经元”死亡“问题,LeakyReLU与ReLU很相似,仅在输入小于0的部分有差别,ReLU输入小于0的部分值都为0,而LeakyReLU输入小于0的部分,值为负,且有微小的梯度。函数图像如下图:
LeakyReLU的α取值一般为0.01。使用LeakyReLU的好处就是:在反向传播过程中,对于LeakyReLU激活函数输入小于零的部分,也可以计算得到梯度,而不是像ReLU一样值为0。
在神经网络模型的结构化风险函数中加入正则化项,可以避免过拟合,而过拟合一般表现为模型对于输入的微小改变产生了输出的较大差异,模型对异常点非常敏感(即准确插入异常点),导致拟合函数中的曲率过大,这主要是由于有些参数w过大的关系,通过对||w||进行惩罚,可以缓解这种问题。
而对于偏置b进行正则化对于防止过拟合没有什么影响,因为在对输出结果的贡献中,参数b对于输入的改变是不敏感的,不管输入改变是大还是小,参数b的贡献就只是加个偏置而已。作为一个数,当对输入进行求导时偏置b会被直接约掉,对模型的曲率没有贡献,因此正则化偏置b对避免过拟合没有太大的意义。
反向传播就是要将神经网络的输出误差,一级一级地传播到输入。在计算过程中,计算每一个w对总的损失函数的影响,即损失函数对每个w的偏导。根据w的误差的影响,再乘以步长,就可以更新整个神经网络的权重。当一次反向传播完成之后,网络的参数模型就可以得到更新。更新一轮之后,接着输入下一个样本,算出误差后又可以更新一轮,再输入一个样本,又来更新一轮,通过不断地输入新的样本迭代地更新模型参数,就可以缩小计算值与真实值之间的误差,最终完成神经网络的训练。
当直接令w=0,b=0时,这种初始方法对于逻辑回归来说是可行的,而对于神经网络来说却不可行,因为如果我们令w,b都为 0,这将意味着我们第二层的所有激活单元都会有相同的值。同理,如果我们初始所有的参数都为一个非 0 的数,结果也是一样的,相当于隐层只有 1 个神经元。
在神经网络中,当前面隐藏层的学习速率低于后面隐藏层的学习速率,即随着隐藏层数目的增加,分类准确率反而下降了。这种现象叫做消失的梯度问题。
梯度更新公式:
梯度消失就意味着求导后的值越来越接近于0,导致w下沉的速度变得特别慢,从而导致收敛速度慢,训练时间长,如下图所示:
因此,适当增大学习率可使学习率与导数相乘结果变大,提高下沉速度,缓解梯度消失;
而当学习率过大时,可能会因学习率与导数相乘结果非常大而导致梯度过大发生梯度爆炸。
为什么在神经网络模型的结构化风险函数中不对偏置 进行正则化? - CSDN
前馈神经网络求解XOR问题 - 知乎 (zhihu.com)
梯度消失是如何产生的?怎么解决梯度消失? - 知乎 (zhihu.com)