文章目录
7 Dropout
7.1 重新审视过拟合
当面对更多的特征而样本不足时,线性模型往往会过度拟合。
当给出更多样本而不是特征,我们通常可以指望线性模型不会过拟合。
泛化性和灵活性之间的这种基本权衡被描述为偏差-方差权衡(bias-variance tradeoff)。线性模型有很高的偏差:它们只能表示一小类函数,泛化性差。然而,这些模型的方差很低:它们在不同的随机数据样本上给出了相似的结果。
7.2 扰动的鲁棒性Robust
我们期待模型能在看不见的数据上有很好的表现。经典泛化理论认为,为了缩小训练和测试性能之间的差距,我们应该以简单的模型为目标。简单性以较小维度的形式出现。
参数的范数也代表了一种有用的简单性度量。简单性的另一个有用角度是平滑性,即函数不应该对其输入的微小变化敏感。例如,当我们对图像进行分类时,我们预计向像素添加一些随机噪声应该是基本无影响的。
他们的想法被称为丢弃法(dropout),dropout在正向传播过程中,计算每一内部层的同时注入噪声,这已经成为训练神经网络的标准技术。这种方法之所以被称为 dropout ,因为我们从表面上看是在训练过程中丢弃(drop out)一些神经元。 在整个训练过程的每一次迭代中,dropout包括在计算下一层之前将当前层中的一些节点置零。
7.3 实践中的dropout
当我们将dropout应用到隐藏层,以 p 的概率将隐藏单元置为零时,结果可以看作是一个只包含原始神经元子集的网络。在下图,删除了 h2 和 h5 。因此,输出的计算不再依赖于 h2 或 h5 ,并且它们各自的梯度在执行反向传播时也会消失。这样,输出层的计算不能过度依赖于 h1,…,h5 的任何一个元素。
通常,我们在测试时禁用dropout。给定一个训练好的模型和一个新的样本,我们不会丢弃任何节点,因此不需要标准化。然而,也有一些例外:一些研究人员使用测试时的dropout作为估计神经网络预测的“不确定性”的启发式方法:如果预测在许多不同的dropout掩码上都是一致的,那么我们可以说网络更有自信心。
7.4 从零开始实现
要实现单层的dropout函数,我们必须从伯努利(二元)随机变量中提取与我们的层的维度一样多的样本,其中随机变量以概率 1−p 取值 1 (保持),以概率 p 取值 0 (丢弃)。
import torch
from torch import nn
from d2l import torch as d2l
import matplotlib.pyplot as plt
def dropout_layer(X, dropout):
assert 0 <= dropout <= 1
# 在本情况中,所有元素都被丢弃。
if dropout == 1:
return torch.zeros_like(X)
# 在本情况中,所有元素都被保留。
if dropout == 0:
return X
mask = (torch.Tensor(X.shape).uniform_(0, 1) > dropout).float()
return mask * X / (1.0 - dropout)
uniform_函数记录:
a = torch.zeros(6)
print(a)
a = (torch.Tensor(2,3).uniform_(0,1) > 0.5)
print(a)
a = a.float()
print(a)
#############
tensor([[False, False, True],
[ True, False, False]])
tensor([[0., 0., 1.],
[1., 0., 0.]])
7.4.1 定义模型参数
定义具有两个隐藏层的多层感知机,每个隐藏层包含256个单元。
num_inputs, num_outputs, num_hiddens1, num_hiddens2 = 784, 10, 256, 256
7.4.2 定义模型
注释的很详细了。
dropout1, dropout2 = 0.2, 0.5 # 概率P
class Net(nn.Module):
def __init__(self, num_inputs, num_outputs, num_hiddens1, num_hiddens2,
is_training = True):
super(Net, self).__init__()
self.num_inputs = num_inputs
self.training = is_training
self.lin1 = nn.Linear(num_inputs, num_hiddens1)
self.lin2 = nn.Linear(num_hiddens1, num_hiddens2)
self.lin3 = nn.Linear(num_hiddens2, num_outputs)
self.relu = nn.ReLU()
def forward(self, X):
H1 = self.relu(self.lin1(X.reshape((-1, self.num_inputs))))
# 只有在训练模型时才使用dropout
if self.training == True:
# 在第一个全连接层之后添加一个dropout层
H1 = dropout_layer(H1, dropout1)
H2 = self.relu(self.lin2(H1))
if self.training == True:
# 在第二个全连接层之后添加一个dropout层
H2 = dropout_layer(H2, dropout2)
out = self.lin3(H2)
return out
net = Net(num_inputs, num_outputs, num_hiddens1, num_hiddens2)
7.4.3 训练和测试
net = Net(num_inputs, num_outputs, num_hiddens1, num_hiddens2)
num_epochs, lr, batch_size = 10, 0.5, 256
loss = nn.CrossEntropyLoss()
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
trainer = torch.optim.SGD(net.parameters(), lr=lr)
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer)
plt.show()
7.5 简洁实现
对于高级API,我们所需要做的就是在每个全连接层之后添加一个Dropout层,将丢弃概率作为唯一的参数传递给它的构造函数。
在训练过程中,Dropout层将根据指定的丢弃概率随机丢弃上一层的输出(相当于下一层的输入)。当不处于训练模式时,Dropout层仅在测试时传递数据。
import torch
from torch import nn
from d2l import torch as d2l
import matplotlib.pyplot as plt
dropout1, dropout2 = 0.2, 0.5
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)
net.apply(init_weights)
num_epochs, lr, batch_size = 10, 0.5, 256
loss = nn.CrossEntropyLoss()
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
trainer = torch.optim.SGD(net.parameters(), lr=lr)
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer)
plt.show()
8 正向传播、反向传播和计算图
8.1 正向传播
正向传播(forward propagation或forward pass)指的是:按顺序(从输入层到输出层)计算和存储神经网络中每层的结果。
【官方双语】深度学习之神经网络的结构 Part 1 ver 2.0
我们之前做的所有的模型准备,基本都是基于正向传播的。
8.2 正向传播计算图
8.3 反向传播
反向传播指的是计算神经网络参数梯度的方法。简言之,该方法根据微积分中的链式规则,按相反的顺序从输出层到输入层遍历网络。
【官方双语】深度学习之反向传播算法 上/下 Part 3 ver 0.9 beta
视频小结:反向传播算法算的是单个训练样本想怎样修改权重和偏置,不仅是说每个参数应该变大还是变小,还包括了这些变化的比例是多大才能最快地降低代价。真正的梯度下降得对好几万个训练范例都这么操作,然后对这些变化值取平均。但算起来太慢了,所以你会先把所有的样本分到各个minibatch中去。计算个minibatch来作为梯度下降的一步,计算每个minibatch的梯度调整参数不断循环,最终你就会收敛到代价函数的一个局部最小值上。此时就可以说你的神经网络对付训练数据已经很不错。
小批量梯度下降 Mini-Batch Gradient Descent
在训练神经网络时,正向传播和后向传播相互依赖。对于正向传播,我们沿着依赖的方向遍历计算图并计算其路径上的所有变量。然后将这些用于反向传播,其中计算顺序与计算图的相反。
一方面,在正向传播期间计算正则项取决于模型参数 W(1) 和 W(2) 的当前值。它们是由优化算法根据最近迭代的反向传播给出的。另一方面,反向传播期间参数的梯度计算取决于由正向传播给出的隐藏变量 h 的当前值。
9 数值稳定和模型初始化
到目前为止,我们实现的每个模型都是根据某个预先指定的分布来初始化模型的参数。
初始化方案的选择在神经网络学习中起着非常重要的作用,它对保持数值稳定性至关重要。
这些选择可以与非线性激活函数的选择以有趣的方式结合在一起。我们选择哪个函数以及如何初始化参数可以决定优化算法收敛的速度有多快。糟糕选择可能会导致我们在训练时遇到梯度爆炸或梯度消失。
9.1 梯度消失
import torch
from d2l import torch as d2l
import matplotlib.pyplot as plt
x = torch.arange(-8.0, 8.0, 0.1, requires_grad=True)
y = torch.sigmoid(x)
y.backward(torch.ones_like(x))
d2l.plot(x.detach().numpy(), [y.detach().numpy(), x.grad.numpy()],
legend=['sigmoid', 'gradient'], figsize=(4.5, 2.5))
plt.show()
torch.ones_like函数记录:
import torch
X = torch.arange(12).reshape((3, 4))
C = torch.ones_like(X)
print(C)
据给定张量,生成与其形状相同的全1张量。
torch与numpy的数据转化
import torch
import numpy as np
a = np.arange(12, dtype=float).reshape((2, 6))
print(a)
b = torch.Tensor(a)
print(b.type())
c = b.detach().numpy()
print(c)
#########
[[ 0. 1. 2. 3. 4. 5.]
[ 6. 7. 8. 9. 10. 11.]]
torch.FloatTensor
[[ 0. 1. 2. 3. 4. 5.]
[ 6. 7. 8. 9. 10. 11.]]
9.2 梯度爆炸
为了更好地说明这一点,我们生成100个高斯随机矩阵,并将它们与某个初始矩阵相乘。对于我们选择的尺度(方差 σ2=1 ),矩阵乘积发生爆炸。当这种情况是由于深度网络的初始化所导致时,我们没有机会让梯度下降优化器收敛。
M = torch.normal(0, 1, size=(4,4))
print('一个矩阵 \n',M)
for i in range(100):
M = torch.mm(M,torch.normal(0, 1, size=(4, 4)))
print('乘以100个矩阵后\n', M)
############3
一个矩阵
tensor([[-1.2695, -1.1007, -2.4047, 1.4628],
[-0.9313, 0.5309, 1.1947, 1.6727],
[-2.4429, -1.4279, 0.5502, -0.3605],
[ 1.3904, -0.2678, -0.3999, 0.7862]])
乘以100个矩阵后
tensor([[-1.6421e+26, 3.0056e+25, -1.1612e+26, 4.1458e+26],
[ 6.0489e+26, -1.1072e+26, 4.2774e+26, -1.5272e+27],
[-1.1773e+26, 2.1549e+25, -8.3251e+25, 2.9724e+26],
[ 2.2893e+26, -4.1904e+25, 1.6189e+26, -5.7800e+26]])
torch.mul() 和 torch.mm() 函数记录:
torch.mul() 实现的是点乘。点乘都是broadcast的,可以用torch.mul(a, b)实现,也可以直接用*实现。
torch.mm(a, b)就是按照线性代数里面的规则计算。(mm意为 matmul)
pytorch之torch中的几种乘法 #点乘torch.mm() #矩阵乘torch.mul(),torch.matmul() #高维Tensor相乘维度要求
9.3 参数初始化
9.3.1 默认初始化
我们使用正态分布来初始化权重值。如果我们不指定初始化方法,框架将使用默认的随机初始化方法,对于中等规模的问题,这种方法通常很有效。
9.3.2 Xavier初始化
9.4 小结
梯度消失和爆炸是深度网络中常见的问题。在参数初始化时需要非常小心,以确保梯度和参数可以得到很好的控制。