说明:此博客是笔者根据李沐沐神2021年动手学深度学习进行的笔记整理,其中也包含了个人的一些学习理解和代码。如有错误请指正!
当面对更多的特征数量有限时,线性模型往往不能考虑特征之间的交互作用,模型会更容易发生过拟合。(也就是模型学习到了量数据的部分特征,而可能忽略了其中更为普遍的规律)
在传统的说法中,范化性和灵活性之间的这种基本权衡可以描述为“偏差-方差权衡”(bias-variance trade off),简单的线性模型有很大的偏差:其仅仅能拟合一小类简单的函数,但是这些模型的方差很低(也就是说对于这些模型而言,给定不同的随机数据,其结果类似,精确度基本不受数据的影响),对于这些简单的函数过拟合的风险较小。
对于深度神经网络,偏差-方差谱位于另一端。神经网络其不局限于单独查看某一个特征,而是更广泛地学习了各种不同特征之间的交互关系。所以即使我们样本数量远大于特征数量,深度神经网络也可能出现过拟合(简单想象多个特征的组合排列就可以想到这一点了,样本数量增长远不及这些特征排列组合的关系增长多,加入一个新的特征其交互关系可能是爆炸增长)
深度神经网络有令人费解的范化性质,但是这些性质的数学基本原理仍然未知。
一个好的模型需要对输入数据的扰动鲁棒,参数的范数代表了一种有用的简单性度量,简单性的另一个又用角度是平滑性,即函数不应该对输入的微小变化敏感。
在毕晓普的工作中,他将高斯噪声添加到线性模型的输入中。在每次训练迭代中,他将从均值为零的分布 ϵ ∼ N ( 0 , σ 2 ) \epsilon \sim \mathcal{N}(0,\sigma^2) ϵ∼N(0,σ2)采样噪声添加到输入 x \mathbf{x} x,从而产生扰动点 x ′ = x + ϵ \mathbf{x}' = \mathbf{x} + \epsilon x′=x+ϵ。预期是 E [ x ′ ] = x E[\mathbf{x}'] = \mathbf{x} E[x′]=x。
E [ x ′ ] = x E[\mathbf{x}'] = \mathbf{x} E[x′]=x
h ′ = { 0 概率为 p h 1 − p 其他情况 \begin{aligned} h' = \begin{cases} 0 & \text{ 概率为 } p \\ \frac{h}{1-p} & \text{ 其他情况} \end{cases} \end{aligned} h′={01−ph 概率为 p 其他情况
请注意:
E [ h ′ ] = p ∗ 0 + ( 1 − p ) ∗ h 1 − p = h E[\mathbf{h}'] = p*0 +(1-p)*\frac{h}{1-p} = h E[h′]=p∗0+(1−p)∗1−ph=h
根据设计,期望值保持不变,即 E [ h ′ ] = h E[h'] = h E[h′]=h。
h = d r o p o u t ( h ) h = dropout(h) h=dropout(h)
要实现单层的dropout函数,我们必须从伯努利(二元)随机变量中提取与我们的层的维度一样多的样本,其中随机变量以概率 1 − p 1-p 1−p取值 1 1 1(保持),以概率 p p p取值 0 0 0(丢弃)。实现这一点的一种简单方式是首先从均匀分布 U [ 0 , 1 ] U[0, 1] U[0,1]中抽取样本。那么我们可以保留那些对应样本大于 p p p的节点,把剩下的丢弃。
在下面的代码中,(我们实现 dropout_layer
函数,该函数以dropout
的概率丢弃张量输入X
中的元素),如上所述重新缩放剩余部分:将剩余部分除以1.0-dropout
。
import torch
from torch import nn
from d2l import torch as d2l
import torchvision
from torchvision import transforms
from torch.utils import data
def dropout_layer(X,dropout):
assert 0<=dropout <= 1
# 如果dropout概设置为1,全部元素被丢弃
if dropout == 1:
return torch.zeros_like(X)
# 如果dropout概设置为0,全部元素被保留
if dropout == 0:
return X
mask = (torch.rand(X.shape)>dropout).float() #使用mask而不是直接置零是为了提高计算效率
return mask *X/(1.0-dropout)
# 测试dropout layer层
X = torch.arange(16,dtype=torch.float32).reshape((2,8))
print(X)
print(dropout_layer(X,0))
print(dropout_layer(X,0.5))
print(dropout_layer(X,1))
测试Dropout的结果:
# 定义数据加载函数
def get_dataloader_workers():
"""使用四个进程读取数据"""
return 4
def load_data_fashion_mnist(batch_size,resize=None):
"""下载Fashion-MNIST数据集,并将其保存至内存中"""
trans = [transforms.ToTensor()]
if resize:
trans.insert(0,transforms.Resize(resize)) # transforms.Resize将图片最小的一条边缩放到指定大小,另一边缩放对应比例
trans = transforms.Compose(trans) # compose用于串联多个操作
mnist_train = torchvision.datasets.FashionMNIST(root="./data",
train=True,
transform=trans,
download=True)
mnist_test = torchvision.datasets.FashionMNIST(root="./data",
train=False,
transform=trans,
download=True)
return (data.DataLoader(mnist_train,batch_size,shuffle=True,
num_workers=get_dataloader_workers()),
data.DataLoader(mnist_test,batch_size,shuffle=True,
num_workers = get_dataloader_workers()))
# 使用Fashion-MNIST数据进行测试
num_inputs, num_outputs, num_hiddens1, num_hiddens2 = 784, 10, 1024, 1024
# 定义模型
dropout1, dropout2 = 0.5, 0.9
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)
# 训练和测试
num_epochs, lr, batch_size = 20, 0.5, 256
loss = nn.CrossEntropyLoss()
train_iter,test_iter = load_data_fashion_mnist(batch_size)
trainer = torch.optim.SGD(net.parameters(), lr=lr)
不加dropout(设置dropout值为0):前面net的参数设置中
# 不加dropout(设置dropout值为0)
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer)
加上dropout(设置第一层dropout 0.2,第二层dropout 0.5)
# 加上dropout(设置第一层dropout 0.2,第二层dropout 0.5)
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer)
加上dropout(设置第一层dropout 0.5,第二层dropout 0.9,相应每一个隐藏层的神经元都增加一部分)
# 加上dropout(设置第一层dropout 0.5,第二层dropout 0.9,相应每一个隐藏层的神经元都增加一部分)
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer)
加载数据、定义损失函数、更新方法(SGD)等如同前面
net = nn.Sequential(nn.Flatten(), nn.Linear(784, 1024), nn.ReLU(),
# 在第一个全连接层之后添加一个dropout层
nn.Dropout(dropout1), nn.Linear(1024, 1024), nn.ReLU(),
# 在第二个全连接层之后添加一个dropout层
nn.Dropout(dropout2), nn.Linear(1024, 10))
def init_weights(m):
if type(m) == nn.Linear:
nn.init.normal_(m.weight, std=0.01)
net.apply(init_weights);
trainer = torch.optim.SGD(net.parameters(), lr=lr)
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer)