多层感知机(multilayer perceptron,简称MLP):由多层神经元组成,每一层都与下面一层和上面一层完全相连。
图1的感知机模型层数为2,输入层不涉及计算,所以一般不算输入层。隐藏层和输出层是全连接的。每个输入都会影响隐藏层中的每个神经元,而隐藏层中的每个神经元又会影响输出层中的每个神经元。
我们通过矩阵 X ∈ R n × d X\in R^{n×d} X∈Rn×d来表示n
个样本的小批量,其中每个样本具有d
个输入特征。对于具有h
个隐藏单元的单隐藏层多层感知机,用 H ∈ R n × h H∈R^{n×h} H∈Rn×h表示隐藏层的输出。隐藏层权重 W ( 1 ) ∈ R d × h W^{(1)}∈R^{d×h} W(1)∈Rd×h和隐藏层偏置 b ( 1 ) ∈ R 1 × h b^{(1)}∈R^{1×h} b(1)∈R1×h以及输出层权重 W ( 2 ) ∈ R h × q W^{(2)}∈R^{h×q} W(2)∈Rh×q 和输出层偏置 b ( 2 ) ∈ R 1 × q b^{(2)}∈R^{1×q} b(2)∈R1×q。形式上,我们按如下方式计算单隐藏层多层感知机的输出 O ∈ R n × q O∈R^{n×q} O∈Rn×q:
H = σ ( X W ( 1 ) + b ( 1 ) ) O = H W ( 2 ) + b ( 2 ) H=\sigma (XW^{(1)}+b^{(1)})\\ O=HW^{(2)}+b^{(2)} H=σ(XW(1)+b(1))O=HW(2)+b(2)在仿射变换之后应用非线性激活函数 σ ( ⋅ ) \sigma(·) σ(⋅)防止多层感知机退化成线性模型。
(由于 X X X中的每一行对应于小批量中的一个样本,出于记号习惯的考量,我们定义非线性函数σ也以按行的方式作用于其输入,即一次计算一个样本。)
多层感知机在输出层和输入层之间增加一个或多个全连接隐藏层,并通过激活函数转换隐藏层的输出。
常用的激活函数包括ReLU函数、Sigmoid函数和Tanh函数。
Fashion-MNIST中的每个图像由 28 × 28 = 784
个灰度像素值组成。所有图像共分为10
个类别。忽略像素之间的空间结构,我们可以将每个图像视为具有784
个输入特征和10
个类的简单分类数据集。首先,我们将实现一个具有单隐藏层的多层感知机,它包含256
个隐藏单元。通常,我们选择2的若干次幂作为层的宽度。因为内存在硬件中的分配和寻址方式,这么做往往可以在计算上更高效。
import torch
from torch import nn
from d2l import torch as d2l
batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
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激活函数
def relu(X):
a = torch.zeros_like(X)
return torch.max(X, a)
# 多重感知机
def net(X):
X = X.reshape((-1, num_inputs))
H = relu(X@W1 + b1) # 这里“@”代表矩阵乘法
return (H@W2 + b2)
loss = nn.CrossEntropyLoss()
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)
d2l.predict_ch3(net, test_iter)
import torch
from torch import nn
from d2l import torch as d2l
net = nn.Sequential(nn.Flatten(),
nn.Linear(784, 256),
nn.ReLU(),
nn.Linear(256, 10))
def init_weights(m):
if type(m) == nn.Linear:
nn.init.normal_(m.weight, std=0.01)
net.apply(init_weights)
batch_size, lr, num_epochs = 256, 0.1, 10
loss = nn.CrossEntropyLoss()
trainer = torch.optim.SGD(net.parameters(), lr=lr)
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer)
权重衰减是正则化的技术之一,它通常也被称为L2正则化。
L1正则化线性回归通常被称为套索回归(lasso regression),L1 正则化会导致模型将权重集中在一小部分特征上, 而将其他权重清除为零,可用于特征选择。
L2正则化线性模型构成经典的岭回归(ridge regression)算法。L2范数对权重向量的大分量施加了巨大的惩罚,这使得我们的学习算法偏向于在大量特征上均匀分布权重的模型。
• 正则化是处理过拟合的常用方法:在训练集的损失函数中加入惩罚项,以降低学习到的模型的复杂度。
L2正则化线性模型损失函数:
L ( w , b ) = 1 n ∑ i = 1 n l ( i ) ( w , b ) + λ 2 ∣ ∣ w ∣ ∣ 2 L(\pmb w,b)=\frac{1}{n}\sum_{i=1}^nl^{(i)}(\pmb w,b)+\frac{\lambda}{2}||\pmb w||^2 L(www,b)=n1i=1∑nl(i)(www,b)+2λ∣∣www∣∣2
L2正则化回归的小批量随机梯度下降更新如下式:
w ← ( 1 − η λ ) w − η ∣ B ∣ ∑ i ∈ B ∂ w l ( i ) ( w , b ) \pmb w\gets{\color{Red}(1-\eta\lambda)\pmb w}-\frac{\eta}{|\mathcal B|}\sum_{i\in\mathcal B}{\partial}_{\pmb w}l^{(i)}(\pmb w,b) www←(1−ηλ)www−∣B∣ηi∈B∑∂wwwl(i)(www,b)
根据估计值与观测值之间的差异来更新 w \pmb w www,同时也在试图将 w \pmb w www的大小缩小到零。这就是为什么这种方法有时被称为权重衰减。较小的λ
值对应较少约束的 w \pmb w www,而较大的λ
值对 w \pmb w www的约束更大。
%matplotlib inline
import torch
from torch import nn
from d2l import torch as d2l
n_train, n_test, num_inputs, batch_size = 20, 100, 200, 5
true_w, true_b = torch.ones((num_inputs, 1)) * 0.01, 0.05
train_data = d2l.synthetic_data(true_w, true_b, n_train)
train_iter = d2l.load_array(train_data, batch_size)
test_data = d2l.synthetic_data(true_w, true_b, n_test)
test_iter = d2l.load_array(test_data, batch_size, is_train=False)
def train_concise(wd):
net = nn.Sequential(nn.Linear(num_inputs, 1))
for param in net.parameters():
param.data.normal_()
loss = nn.MSELoss(reduction='none')
num_epochs, lr = 100, 0.003
# 偏置参数没有衰减
trainer = torch.optim.SGD([
{"params":net[0].weight,'weight_decay': wd},
{"params":net[0].bias}], lr=lr)
animator = d2l.Animator(xlabel='epochs', ylabel='loss', yscale='log',
xlim=[5, num_epochs], legend=['train', 'test'])
for epoch in range(num_epochs):
for X, y in train_iter:
trainer.zero_grad()
l = loss(net(X), y)
l.sum().backward()
trainer.step()
if (epoch + 1) % 5 == 0:
animator.add(epoch + 1,
(d2l.evaluate_loss(net, train_iter, loss),
d2l.evaluate_loss(net, test_iter, loss)))
print('w的L2范数:', net[0].weight.norm().item())
# train_concise(0)
train_concise(3)
• 正则化是处理过拟合的常用方法:在训练集的损失函数中加入惩罚项,以降低学习到的模型的复杂度。
• 保持模型简单的一个特别的选择是使用L2惩罚的权重衰减。这会导致学习算法更新步骤中的权重衰减。
• 权重衰减功能在深度学习框架的优化器中提供。
如图有1个隐藏层和5个隐藏单元的多层感知机。将暂退法应用到隐藏层,以p
的概率将隐藏单元置为零时,结果可以看作是一个只包含原始神经元子集的网络。比如删除了 h 2 h_2 h2和 h 5 h_5 h5, 因此输出的计算不再依赖于 h 2 h_2 h2或 h 5 h_5 h5 ,并且它们各自的梯度在执行反向传播时也会消失。这样,输出层的计算不能过度依赖于 h 1 , . . . , h 5 h_1, . . . , h_5 h1,...,h5的任何一个元素。
可以将暂退法应用于每个隐藏层的输出(在激活函数之后),并且可以为每一层分别设置暂退概率:常⻅的技巧是在靠近输入层的地方设置较低的暂退概率。一般暂退法只在训练期间有效。在测试时,Dropout层仅传递数据。
dropout1, dropout2 = 0.2, 0.5
num_epochs, lr, batch_size = 10, 0.5, 256
net = nn.Sequential(nn.Flatten(),
nn.Linear(784, 256),
nn.ReLU(),
# 在第一个全连接层之后添加一个dropout层
nn.Dropout(dropout1),
nn.Linear(256, 256),
nn.ReLU(),
# 在第二个全连接层之后添加一个dropout层
nn.Dropout(dropout2),
nn.Linear(256, 10))
def init_weights(m):
if type(m) == nn.Linear:
nn.init.normal_(m.weight, std=0.01)
loss = nn.CrossEntropyLoss()
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
net.apply(init_weights)
trainer = torch.optim.SGD(net.parameters(), lr=lr)
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer)
训练神经网络时,交替使用前向传播和反向传播,利用反向传播给出的梯度来更新模型参数。
注意,反向传播重复利用前向传播中存储的中间值,以避免重复计算。带来的影响之一是我们需要保留中间值,直到反向传播完成。这也是训练比单纯的预测需要更多的内存(显存)的原因之一。此外,这些中间值的大小与网络层的数量和批量的大小大致成正比。
梯度爆炸(gradient exploding)问题:参数更新过大,破坏了模型的稳定收敛;
梯度消 失(gradient vanishing)问题:参数更新过小,在每次更新时几乎不会移动,导致模型无法学习。
模型评估指标可以看这个(模型验证方法、欠拟合和过拟合等)
Holdout检验:将原始的样本集合随机划分成训练集和验证集两部分,常见的是70%的样本为训练集;30% 的样本为验证集。
k折交叉验证:将数据集划分为k个相等的子集,每次取一个子集作为验证集,其余k-1个作为训练集,最后将k次结果取平均,实际中,k常取10。
自助法(Bootstrap):当数据集规模比较小时再划分训练集和测试集会影响模型训练效果。对于总数为n的样本集合,进行n次有放回的随机抽样,得到大小为n的训练集。n次采样过程中,有的样本会被重复采样,有的样本没有被抽出过,将这些没有被抽出的样本作为验证集,进行模型验证,这就是自助法的验证过程。
过拟合:指模型对于训练数据拟合过当,导致模型在训练集上的表现很好,但在测试集和新数据上的表现较差。
欠拟合:指模型在训练和预测时表现都不好的情况。
过拟合说明模型过于复杂,把噪声数据的特征也学习到模型中,导致模型泛化能力下降。降低“过拟合”,可以通过以下方法:
1)使用更多的训练数据让模型学习到更多更有效的特征,减小噪声的影响。
2)降低模型复杂度以避免模型拟合过多的采样噪声。
3)正则化,如L1正则化 L = L 0 + λ ∣ ∣ w ∣ ∣ 1 L=L_0+\lambda||w||_1 L=L0+λ∣∣w∣∣1,L2正则化 L = L 0 + λ 2 ∣ ∣ w ∣ ∣ 2 2 L=L_0+\frac{\lambda}{2}||w||^2_2 L=L0+2λ∣∣w∣∣22。
4)集成学习,如基于Bagging的算法和基于Boosting的算法。
降低“过拟合”,可以通过以下方法:
1)添加新特征,当特征不足或者现有特征与样本标签的相关性不强时,模型容易出现欠拟合。
2)增加模型复杂度。
3)减小正则化系数。