作业4:第四章课后题
习题4-2 试设计一个前馈神经网络来解决 XOR 问题,要求该前馈神经网络具有两个隐藏神经元和一个输出神经元,并使用 ReLU 作为激活函数.
分析:
XOR输入为2个神经元,输出为一个神经元,此题要求隐藏神经元为2个,故只要两层全连接层即可
- 数据集:输入为两位二进制,共四种情况,输出为一个二进制表示的数,要么为0,要么为1
- 网络模型:用pytorch搭建两层全连接层神经网络
- 训练:计算网络输出,计算损失函数,反向传播,参数更新
求权重和偏置 - 测试
代码:
import torch
from torch.nn.init import constant_, normal_
import numpy as np
# 数据输入
input_x = torch.tensor([[0, 0], [0, 1], [1, 0], [1, 1]])
input_x = input_x.float()
real_y = torch.tensor([[0], [1], [1], [0]])
real_y = real_y.float()
class XOR_module(torch.nn.Module):
def __init__(self, input_size=2, output_size=1, mean_init=0., std_init=1., b_init=0.0):
super(XOR_module, self).__init__()
self.fc1 = torch.nn.Linear(input_size, 2)
normal_(tensor=self.fc1.weight, mean=mean_init, std=std_init)
constant_(tensor=self.fc1.bias, val=b_init)
self.fc2 = torch.nn.Linear(2, output_size)
normal_(tensor=self.fc2.weight, mean=mean_init, std=std_init)
constant_(tensor=self.fc2.bias, val=b_init)
# 使用'torch.nn.ReLU'定义 relu 激活函数
self.act = torch.nn.ReLU()
# 前向计算
def forward(self, inputs):
outputs = self.fc1(inputs)
outputs = self.act(outputs)
outputs = self.fc2(outputs)
outputs = self.act(outputs)
return outputs
net = XOR_module()
learing_rate = 0.1
epochs = 10000
loss_function = torch.nn.MSELoss() # 用交叉熵损失函数会出现维度错误
optimizer = torch.optim.SGD(net.parameters(), lr=learing_rate)
# 进行训练
for epoch in range(epochs):
out_y = net(input_x)
loss = loss_function(out_y, real_y) # 计算损失函数
loss.backward() # 反向传播
optimizer.step() # 参数更新
optimizer.zero_grad() # 对梯度清零,避免造成累加
# 进行测试
input_test = input_x
out_test = net(input_test)
print('输入x', input_test.detach().numpy())
print('预测值y', np.around(out_test.detach().numpy()))
运行结果:
习题4-3 试举例说明“死亡ReLU问题”,并提出解决方法.
“死亡ReLU问题”:在反向传播过程中,如果学习率比较大,一个很大的梯度经过ReLU神经元,可能会导致ReLU神经元更新后的偏置和权重是负数,进而导致下一轮正向传播过程中ReLU神经元的输入是负数,输出是0。由于ReLU神经元的输出为0,在后续迭代的反向过程中,该处的梯度一直为0,相关参数不再变化,从而导致ReLU神经元的输入始终是负数,输出始终为0,即为“死亡ReLU问题”。
**原因分析:**ReLU 的全称是 Rectified Linear Unit,其函数图像如下图所示:
异常输入杀死神经元,这是一个典型的神经元
由于 ReLU 在 x>0x>0 时,导数恒为 1。因此在反向传播的过程中,不会因为导数连乘,而使得梯度特别小,以至于参数无法更新。在这个意义上,ReLU 确实避免了梯度消失问题。
当权重参数变为负值时,输入网络的正值会和权重相乘后也会变为负值,负值通过ReLu后就会输出0;如果在后期有机会被更新为正值也不会出现大问题,但是当relu函数输出值为0时,relu的导数也为0,因此会导致后边Δω一直为0,进而导致ω一直不会被更新,因此会导致这个神经元永久性死亡(一直输出0)。
如此看来,尽管 ReLU 解决了因激活函数导数的绝对值小于 1,在反向传播连乘的过程中迅速变小消失至 0 的问题,但由于它在输入为负的区段导数恒为零,而使得它对异常值特别敏感。这种异常值可能会使 ReLU 永久关闭,而杀死神经元。
例子:
解决死亡ReLU问题的方法:
使用下列函数
1.带泄露的ReLU函数(LeakyReLU)
2.带参数的ReLU函数(PReLU)
3.ELU(指数线性单元)
4.Softplus(Rectifier 函数的平滑版本)
替换。
习题4-7 为什么在神经网络模型的结构化风险函数中不对偏置𝒃 进行正则化?
首先正则化主要是为了防止过拟合,而过拟合一般表现为模型对于输入的微小改变产生了输出的较大差异,这主要是由于有些参数w过大的关系,通过对||w||进行惩罚,可以缓解这种问题。而如果对||b||进行惩罚,其实是没有作用的,因为在对输出结果的贡献中,参数b对于输入的改变是不敏感的,不管输入改变是大还是小,参数b的贡献就只是加个偏置而已。
举个例子,如果你在训练集中,w和b都表现得很好,但是在测试集上发生了过拟合,b是不背这个锅的,因为它对于所有的数据都是一视同仁的(都只是给它们加个偏置),要背锅的是w,因为它会对不同的数据产生不一样的加权。或者说,模型对于输入的微小改变产生了输出的较大差异,这是因为模型的“曲率”太大,而模型的曲率是由w决定的,b不贡献曲率(对输入进行求导,b是直接约掉的)。
这个偏置𝒃对于函数来说只是平移,并且𝒃对输入的改变是不敏感的,无论输入变大还是变小,𝒃对结果的贡献只是一个偏置。因此其对过拟合没有帮助,并且在《DeepLearning》Chapter 7.1中说到:对 𝒃进行正则化容易导致欠拟合。
习题4-8 为什么在用反向传播算法进行参数学习时要采用随机参数初始化的方式而不是直接令 w= 0, 𝒃 = 0?
反向传播就是要将神经网络的输出误差,一级一级地传播到输入。在计算过程中,计算每一个w ww对总的损失函数的影响,即损失函数对每个w ww的偏导。根据w ww的误差的影响,再乘以步长,就可以更新整个神经网络的权重。当一次反向传播完成之后,网络的参数模型就可以得到更新。更新一轮之后,接着输入下一个样本,算出误差后又可以更新一轮,再输入一个样本,又来更新一轮,通过不断地输入新的样本迭代地更新模型参数,就可以缩小计算值与真实值之间的误差,最终完成神经网络的训练。当直接令w=0,b=0时,会让下一层神经网络中所有神经元进行着相同的计算,具有同样的梯度,同样权重更新。
习题4-9 梯度消失问题是否可以通过增加学习率来缓解?
首先解释一下什么是梯度消失和梯度爆炸,
- 梯度消失:在神经网络反向传播中,当梯度从后往前传时,梯度不断减小,最后变为零,此时,浅层的神经网络权重得不到更新,那么前面隐藏层的学习速率低于后面隐藏层的学习速率,即随着隐藏层数目的增加,分类准确率反而下降了;
- 梯度爆炸:当权值过大,神经网络前面层比后面层梯度变化更快,会引起梯度爆炸问题。梯度爆炸就是由于初始化权值过大,w大到乘以激活函数的导数都大于1,因为前面层比后面层变化的更快,就会导致神经网络前面层的权值越来越大,
其实不管是梯度消失还是梯度爆炸本质上都是由于——深度神经网络的反向传播造成的。
梯度消失导致后层的权重更新的快,靠近输出层的权值更新相对正常,而前层网络由于梯度传递不过去而得不到更新。靠近输入层的权值更新会变得很慢,导致靠近输入层的隐藏层权值几乎不变,接近于初始化的权值。这样在网络很深的时候,学习的速度很慢或者无法学习。
增加学习率可以一定程度上缓解梯度消失问题。适当增大学习率可以使学习率与导数相乘结果变大,缓解梯度消失;过大学习率可能梯度巨大,导致梯度爆炸。
总结
本次内容是课后习题作业,是对第四章内容的总结回顾,所以与上次实验有很多相似的内容,例如死亡Relu问题、梯度消失、梯度爆炸问题等,这章内容在西瓜书中也都涉及过,在这本书中更深入的了解了前馈神经网络,对理论知识有了更深的理解,知道了增加学习率可以一定程度上缓解梯度消失问题,但学习率过大会导致梯度爆炸。