深度学习丨多层感知机

多层感知机概念

多层感知机为最简单的神经网络,由多层神经元构成,每一层的输出成为下一层的输入。

加入隐藏层

单层线性模型所能拟合的情况十分有限,比如无法解决xor问题。为了拟合更复杂的情况,可以将多个全连接层堆叠到一起,每一层的输出成为下一层的输入,直到生成最终的输出,最后一层为输出层,中间层为隐藏层,这种架构即为多层感知机

激活函数

两个线性层的堆叠形成的仍然是线性层,例如有以下多层感知机,其中X为输入,H为隐藏层变量,O为输出 消去H得到O和X的关系: 可以看到,O和X仍然存在线性关系,加入隐藏层也就失去了意义。因此,我们需要对每个隐藏层应用激活函数δ,使模型变为非线性: 常见的激活函数如下:

ReLU函数

ReLU函数实现简单且实际表现良好,因此得以广泛应用。 ReLU函数将相应的激活值设为0,仅保留正元素并丢弃所有负元素。当输入为负时,ReLU函数导数为0,输入为正时,导数为1。当输入为0时,ReLU不可导,我们可以直接默认导数为0,因为在实际中这并不会产生影响。

sigmoid函数

sigmoid函数将输入变换为区间(0,1)上的输出,因此也被成为挤压函数。 sigmoid是一个平滑、可微的函数,但是它具有以下缺点:

  • 由于涉及指数运算,需要消耗大量的资源;
  • sigmoid的导数值域为(0,0.25],因此在进行反向传播时经过连乘容易发生下溢,造成梯度消失。

在隐藏层中,sigmoid一般被更简单、更容易训练的ReLU所取代。

tanh函数

与sigmoid函数类似,tanh函数将输入压缩到区间(-1,1)上。

多层感知机的手动实现

下面使用Fashion-MNIST数据集尝试实现一个多层感知机,以便更好地理解其原理。Fashion-MNIST数据集中每张图像由28X28=784个灰度像素值组成,所有图像分为10个类别。因此忽略空间结构可以将每张图像视为具有784个输入特征和10个类别的简单分类数据集。本例将实现一个具有单隐藏层的多层感知机,其中包含256个隐藏单元。

1
2
batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)

初始化模型参数

在多层感知机中,每一层都需要一个权重矩阵和一个偏置向量。

1
2
3
4
5
6
7
8
9
10
num_inputs, num_outputs, num_hiddens = 784, 10, 256
#初始化隐藏层参数
W1 = nn.Parameter(torch.randn(
num_inputs, num_hiddens, requires_grad=True) * 0.01)
b1 = nn.Parameter(torch.zeros(num_hiddens, requires_grad=True))
#初始化输出层参数
W2 = nn.Parameter(torch.randn(
num_hiddens, num_outputs, requires_grad=True) * 0.01)
b2 = nn.Parameter(torch.zeros(num_outputs, requires_grad=True))
params = [W1, b1, W2, b2]

定义激活函数

采用最常使用的ReLU函数。

1
2
3
def relu(X):
a = torch.zeros_like(X)#生成一个shape与X相同的零矩阵
return torch.max(X,a)

定义模型

如前所述,我们将输入视为一个长度为784的向量,可以简单定义模型如下:

1
2
3
4
5
6
def net(X):
X = X.reshape(-1, num_inputs)#将X转化为num_inputs行矩阵,列数自动计算得到
H = relu(X @ W1 + b1)#隐藏层,@表示矩阵乘法
return(H @ W2 + b2)#输出层

loss = nn.CrossEntropyLoss()#交叉熵损失

训练

多层感知机的训练过程和softmax相同,这里直接调用d2l包中的train_ch3函数,不再手动实现。

1
2
3
num_epochs, lr = 10, 0.1
updater = torch.optim.SGD(params, lr=lr)
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, updater)

可以看到具有较好的拟合效果。

![epoch][epoch.png]

过拟合

机器学习的目的是发现泛化的模式,也就是模型捕获了总体规律,能适用于没有见过的个体。模型在训练数据上的拟合比在潜在分布的拟合更接近的现象称为过拟合,用于对抗过拟合的技术称为正则化

模型泛化

出自于训练数据的误差称为训练误差,将模型应用于新数据时的误差称为泛化误差,在实际中,使用测试集来估计泛化误差。(测试集指在确定了所有超参数之后使用的一次性测试数据,在实际应用中,为了在模型选择的过程中判断过拟合,引入验证集来对每轮实验进行测试。)

影响模型泛化的因素有:

  • 可调整参数的数量
  • 参数的取值范围
  • 训练样本的数量

这也是对抗过拟合的几个思路。

K折交叉验证

当训练数据稀缺时,可以采用K折交叉验证的方法:

  • 将原始数据划分为K个部分
  • 对于i = 1, 2, ..., K
  • 使用第i个部分作为验证集,其余部分用于训练
  • 报告K个部分在验证时的平均错误

权重衰减

通过收集更多训练数据可以缓解过拟合,但这并不总是容易做到,因此我们需要正则化技术来对抗过拟合。

权重衰减是使用最广泛的正则化技术之一,这项技术通过函数与零的距离来度量函数的复杂度。

一种简单的方法是通过权重向量的某个范数来度量复杂度。要保证权重向量较小,最常用的方法是将其范数作为惩罚项添加到最小化损失中: 这里加入w的L2范数作为惩罚,其中λ用于权衡w的范数带来的额外损失,当λ=0时恢复了原来的损失函数,λ/2是为了求导之后更加美观简单。

权重更新的递推式就变为: 可以理解为,在梯度下降前,先将权重在原方向上进行距离的衰减,这样就可以使权重的L2范数始终保持在一定范围内(拉格朗日乘数法)。

丢弃法

丢弃法又称暂退法,是一种通过在网络各层加入噪声提高函数平滑性,以对抗过拟合的方法。

通常以无偏的方式注入噪声,即: 在标准暂退法正则化中,每个中间激活值h以暂退概率p被随机变量h'替换: $$

h' =

$$ 期望值保持不变,即E[h']=h。

通俗地理解,即在前向传播的过程中以概率p丢弃一些神经元,并放大未被丢弃的输出来保持无偏。

丢弃法仅在训练期间使用。

数值稳定性

求梯度使用链式求导法则,需要大量的偏导数进行连乘。从而产生梯度爆炸梯度消失的问题。一个直观的例子:

梯度爆炸

假设现有一个MLP(为简单没有bias): ReLU、sigmoid等激活函数均为一元函数,故梯度为对角矩阵。

使用ReLU作为激活函数时,当x>0导数为1,当x<0导数为0。递推可得到其对W_i求导结果为W_i的部分元素和。若选中的是很大的元素,则会导致梯度很大,从而导致模型参数更新过大,破坏了模型的稳定收敛。

梯度消失

深度学习通常使用16位浮点数,多个小于1的偏导数连乘很容易发生数值下溢。例如sigmoid函数的导数上限为0.25,随着网络层数的增长梯度很容易就会消失。

梯度消失面临的问题是:梯度为0或几乎为0,导致参数更新过小,模型无法学习。

稳定性训练

权重初始化

使用适当范围内的随机值初始化权重

训练的开始容易受到数值不稳定的影响

  • 远离最优点的表面可能很复杂
  • 接近最优点的表面可能更平坦

批量归一化

损失发生在最后一层

  • 后面的层可以快速学习

数据插入在第一层

  • 底层的变化会向上传递
  • 上层需要经过重新学习
  • 导致收敛缓慢

因此要想办法避免在学习第一层时改变最后一层。

批量归一化步骤:

  • 固定小批量里面的均值和方差(引入噪声避免方差为0)

  • 然后再做额外的调整:

批量归一化首先进行标准化,使变量均值趋向于0、方差趋向于1。为保持无偏性,在变化中引入参数γ和β,其中γ用来控制方差,β用来控制均值,这两个参数通过学习得到。

通过挑战,分布变得更加规范,从而防止因分布过散而导致梯度爆炸和梯度消失。(批量归一化发生于激活函数前)