抛弃法
动机
- 一个好的模子需要对输入数据的扰动鲁棒
- 使用有噪音的数据等价于 Tikhonov 正则
- 抛弃法:在层之间加入噪音
无偏差的加入噪音
- 对 x \mathbf{x} x 加入噪音得到 x ′ \mathbf{x}' x′,我们盼望
E [ x ′ ] = x \mathbf{E}[\mathbf{x}'] = \mathbf{x} E[x′]=x
- 抛弃法对每个元素进行如下扰动
x i ′ = { 0 with probability p x i 1 − p otherwise x_i' = \begin{cases} 0 & \text{with probability } p \\ \frac{x_i}{1 - p} & \text{otherwise} \end{cases} xi′={01−pxiwith probability potherwise
实际上,上述 Dropout 后期望是没有发生变革的,具体期望计算如下: E [ x i ′ ] = p ⋅ 0 + ( 1 − p ) ⋅ x i 1 − p = x i \mathbf{E}[x_i'] = p \cdot 0 + (1 - p) \cdot \frac{x_i}{1 - p} = x_i E[xi′]=p⋅0+(1−p)⋅1−pxi=xi
# 推理中的抛弃法
- 正则项只在训练中使用:他们影响模子参数的更新
- 在推理过程中,抛弃法直接返回输入,无需做任何操纵 h = dropout ( h ) \mathbf{h} = \text{dropout}(\mathbf{h}) h=dropout(h)
- 如许也能保证确定性的输出
总结
- 抛弃法将一些输出项随机置 0 来控制模子复杂度
- 常作用在多层感知机的隐藏层输出上,很少用在 CNN 之类的模子上
- 抛弃概率是控制模子复杂度的超参数
代码实现
从零开始实现
首先导入须要的库,同时实现一个 dropout_layer 函数,该函数以dropout的概率抛弃张量输入X中的元素:
- import torch
- from torch import nn
- from d2l import torch as d2l
- def dropout_layer(X, dropout): # X是张量,dropout是丢弃率
- assert 0 <= dropout <= 1
- # 在本情况中,所有元素都被丢弃
- if dropout == 1: # 为1表示所有元素都丢弃,返回一个与X形状相同的全0张量
- return torch.zeros_like(X)
- # 在本情况中,所有元素都被保留
- if dropout == 0: # 所有元素都保留
- return X
- # 生成一个与X形状相同的随机张量,值在[0, 1)之间,然后与dropout进行比较,大于的赋值为1.0,小于的赋值为0.0
- mask = (torch.rand(X.shape) > dropout).float()
- # 得到一个二值掩码张量 mask,用于指示哪些元素应该保留(值为1)
- return mask * X / (1.0 - dropout)
复制代码 接下来来测试一下该函数:
- X= torch.arange(16, dtype = torch.float32).reshape((2, 8))
- print(X)
- print(dropout_layer(X, 0.))
- print(dropout_layer(X, 0.5)) # 有一半的概率将其中的元素变为0
- print(dropout_layer(X, 0.7))
- print(dropout_layer(X, 1.))
复制代码 下面定义模子参数(定义具有两个隐藏层的多层感知机,每个隐藏层包罗256个单位):
输入还是 28*28,输出是 10 个类别
- num_inputs, num_outputs, num_hiddens1, num_hiddens2= 784, 10, 256, 256
复制代码 下面定义模子:
- dropout1, dropout2= 0.2, 0.5
- 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) # 定义第一个全连接层,输入为 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 = 10, 0.5, 256
- loss = nn.CrossEntropyLoss(reduction='none') # 不对每个样本的损失进行平均或求和,而是返回每个样本的损失值
- 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)
复制代码
简便实现
- 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);
- trainer = torch.optim.SGD(net.parameters(), lr=lr)
- d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer)
复制代码
QA 思索
Q1:Dropout 随机抛弃,怎样保证效果的精确性和可重复性?
A1:实际上,机器学习是没有精确性这一说的,只有效果好不好。没须要可重复,只需要精度差不多就可以。
概念1:抛弃法是在训练中把神经元抛弃后训练,在预测时网络中的神经元没有抛弃。
概念2:抛弃法是每次迭代一次,随机抛弃一次。也就是每一个层在调用前向运算的时候,随机抛弃一次。
Q2:训练时使用dropout,推理时不用。那会不会导致推理时输出效果翻倍了?比如dropout=0.5,推理时输出效果是训练时2个子神经网络的叠加而翻倍?
A2:这就是为啥需要除以一个 (1 - p),保证期望不会发生变革,避免我的输出效果是我的训练时的 N 倍。
Q3:在同样的Ir下,dropout的到场会不会造成参数收敛更慢,需要比没有dropout的环境下适当调大Ir吗?
A3:由于Dropout的加入,导致梯度值减小,因此参数收敛汇编慢,但是并没有研究表明一定需要适当增大 lr 的。
Q4:为什么推理中的 Dropout 是直接返回输入?
A4:预测,也就是不对权重做更新的时候,Dropout 是不需要的,因为 Dropout 是一个正则项,正则项唯一的作用就是在更新权重的时候让模子复杂度变低一点,在做推理的时候是不会更新模子的,因此是不需要 Dropout 的,当然也可以用,但是用的话就会出现随机性了。因此为了避免这些随机性,如许需要多算几次推理来将这个方差降下来,但是训练的时候是没有题目的,因为我训练的时候需要跑许多轮,如许对整个体系的稳定性来说是没有题目的,但是在推理的时候,就关心某一个样本效果的话,可能就需要做平均了。
对于这个的明白是,训练集的分布与真实分布可能并不完全雷同,因此引用dropout来对训练集加入一些噪音,使得训练数据的分布更加普适,从而保证模子的鲁棒性。所以预测的时候就没须要再加入噪声了。
后记
明白了之后写的一段代码:
- import torch
- import torchvision
- from torchvision import transforms
- from torch.utils import data
- import matplotlib.pyplot as plt
- from tqdm import tqdm # 导入 tqdm 库
- # 定义 Dropout 层
- def dropout_layer(X, dropout):
- assert 0 <= dropout <= 1
- if dropout == 1:
- return torch.zeros_like(X)
- if dropout == 0:
- return X
- mask = (torch.rand(X.shape) > dropout).float()
- return mask * X / (1.0 - dropout)
- # 定义神经网络
- class Net(torch.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 = torch.nn.Linear(num_inputs, num_hiddens1)
- self.lin2 = torch.nn.Linear(num_hiddens1, num_hiddens2)
- self.lin3 = torch.nn.Linear(num_hiddens2, num_outputs)
- self.relu = torch.nn.ReLU()
- def forward(self, X):
- H1 = self.relu(self.lin1(X.reshape((-1, self.num_inputs))))
- if self.training:
- H1 = dropout_layer(H1, dropout=0.2)
- H2 = self.relu(self.lin2(H1))
- if self.training:
- H2 = dropout_layer(H2, dropout=0.5)
- return self.lin3(H2) # 输出层不需要激活函数
- # 加载 Fashion-MNIST 数据集
- def load_data_fashion_mnist(batch_size, resize=None):
- trans = [transforms.ToTensor()]
- if resize:
- trans.insert(0, transforms.Resize(resize))
- trans = transforms.Compose(trans)
- mnist_train = torchvision.datasets.FashionMNIST("../data", train=True, transform=trans, download=True)
- mnist_test = torchvision.datasets.FashionMNIST("../data", train=False, transform=trans, download=True)
- return (
- data.DataLoader(mnist_train, batch_size, shuffle=True),
- data.DataLoader(mnist_test, batch_size, shuffle=False)
- )
- # 累积器类
- class Accumulator:
- def __init__(self, n):
- self.data = [0.0] * n
- def add(self, *args):
- self.data = [a + float(b) for a, b in zip(self.data, args)]
- def reset(self):
- self.data = [0.0] * len(self.data)
- def __getitem__(self, idx):
- return self.data[idx]
- # 计算准确率
- def accuracy(y_hat, y):
- if len(y_hat.shape) > 1 and y_hat.shape[1] > 1:
- y_hat = y_hat.argmax(axis=1)
- cmp = y_hat.type(y.dtype) == y
- return float(cmp.type(y.dtype).sum())
- # 评估模型在数据集上的准确率
- def evaluate_accuracy(net, data_iter):
- if isinstance(net, torch.nn.Module):
- net.eval()
- metric = Accumulator(2)
- with torch.no_grad():
- for X, y in data_iter:
- metric.add(accuracy(net(X), y), y.numel())
- return metric[0] / metric[1]
- # 单个 epoch 的训练
- def train_epoch_ch3(net, train_iter, loss, updater):
- if isinstance(net, torch.nn.Module):
- net.train()
- metric = Accumulator(3)
- # 使用 tqdm 添加进度条
- for X, y in tqdm(train_iter, desc="Training"):
- y_hat = net(X)
- l = loss(y_hat, y)
- if isinstance(updater, torch.optim.Optimizer):
- updater.zero_grad()
- l.mean().backward()
- updater.step()
- else:
- l.sum().backward()
- updater(X.shape[0])
- metric.add(float(l.sum()), accuracy(y_hat, y), y.numel())
- return metric[0] / metric[2], metric[1] / metric[2]
- # 绘制动画的类
- class Animator:
- def __init__(self, xlabel=None, ylabel=None, legend=None, xlim=None,
- ylim=None, xscale='linear', yscale='linear',
- fmts=('-', 'm--', 'g-.', 'r:'), figsize=(3.5, 2.5)):
- if legend is None:
- legend = []
- self.xlabel = xlabel
- self.ylabel = ylabel
- self.legend = legend
- self.xlim = xlim
- self.ylim = ylim
- self.xscale = xscale
- self.yscale = yscale
- self.fmts = fmts
- self.figsize = figsize
- self.X, self.Y = [], []
- def add(self, x, y):
- if not hasattr(y, "__len__"):
- y = [y]
- n = len(y)
- if not hasattr(x, "__len__"):
- x = [x] * n
- if not self.X:
- self.X = [[] for _ in range(n)]
- if not self.Y:
- self.Y = [[] for _ in range(n)]
- for i, (a, b) in enumerate(zip(x, y)):
- if a is not None and b is not None:
- self.X[i].append(a)
- self.Y[i].append(b)
- def show(self):
- plt.figure(figsize=self.figsize)
- for x_data, y_data, fmt in zip(self.X, self.Y, self.fmts):
- plt.plot(x_data, y_data, fmt)
- plt.xlabel(self.xlabel)
- plt.ylabel(self.ylabel)
- if self.legend:
- plt.legend(self.legend)
- if self.xlim:
- plt.xlim(self.xlim)
- if self.ylim:
- plt.ylim(self.ylim)
- plt.xscale(self.xscale)
- plt.yscale(self.yscale)
- plt.grid()
- plt.show()
- # 主训练函数
- def train_ch3(net, train_iter, test_iter, loss, num_epochs, updater):
- animator = Animator(xlabel='epoch', xlim=[1, num_epochs], ylim=[0.3, 0.9],
- legend=['train loss', 'train acc', 'test acc'])
- for epoch in range(num_epochs):
- train_metrics = train_epoch_ch3(net, train_iter, loss, updater)
- test_acc = evaluate_accuracy(net, test_iter)
- animator.add(epoch + 1, train_metrics + (test_acc,))
- print(f"Epoch {epoch + 1}: Train Metrics = {train_metrics}, Test Acc = {test_acc}")
- train_loss, train_acc = train_metrics
- animator.show() # 展示最终结果图
- # 主程序
- if __name__ == "__main__":
- # 参数设置
- num_epochs, lr, batch_size = 10, 0.5, 256
- num_inputs, num_outputs, num_hiddens1, num_hiddens2 = 784, 10, 256, 256
- # 加载数据
- train_iter, test_iter = load_data_fashion_mnist(batch_size)
- # 定义模型、损失函数和优化器
- net = Net(num_inputs, num_outputs, num_hiddens1, num_hiddens2)
- loss = torch.nn.CrossEntropyLoss(reduction='none')
- trainer = torch.optim.SGD(net.parameters(), lr=lr)
- # 开始训练
- train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer)
复制代码 扔一个简化版:
- import torch
- from torch import nn
- from package import load_data_fashion_mnist, train_ch3
- # 定义 Dropout 层
- def dropout_layer(X, dropout):
- assert 0 <= dropout <= 1
- if dropout == 1:
- return torch.zeros_like(X)
- if dropout == 0:
- return X
- mask = (torch.rand(X.shape) > dropout).float()
- return mask * X / (1.0 - dropout)
- # 定义神经网络(复杂版)
- class Net(torch.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 = torch.nn.Linear(num_inputs, num_hiddens1)
- self.lin2 = torch.nn.Linear(num_hiddens1, num_hiddens2)
- self.lin3 = torch.nn.Linear(num_hiddens2, num_outputs)
- self.relu = torch.nn.ReLU()
- def forward(self, X):
- H1 = self.relu(self.lin1(X.reshape((-1, self.num_inputs))))
- if self.training:
- H1 = dropout_layer(H1, dropout=0.2)
- H2 = self.relu(self.lin2(H1))
- if self.training:
- H2 = dropout_layer(H2, dropout=0.5)
- return self.lin3(H2) # 输出层不需要激活函数
- # 定义神经网络(简化版)
- class Net_Simple(nn.Module):
- def __init__(self, num_inputs, num_outputs, num_hiddens1, num_hiddens2):
- super(Net_Simple, self).__init__()
- self.net = nn.Sequential(
- nn.Flatten(),
- nn.Linear(num_inputs, num_hiddens1),
- nn.ReLU(),
- nn.Dropout(0.2),
- nn.Linear(num_hiddens1, num_hiddens2),
- nn.ReLU(),
- nn.Dropout(0.5),
- nn.Linear(num_hiddens2, num_outputs)
- )
- def forward(self, X):
- return self.net(X)
- # 权重初始化函数
- def init_weights(m):
- if type(m) == nn.Linear:
- nn.init.normal_(m.weight, std=0.01)
- # 主程序
- if __name__ == "__main__":
- # 参数设置
- num_epochs, lr, batch_size = 10, 0.5, 256
- num_inputs, num_outputs, num_hiddens1, num_hiddens2 = 784, 10, 256, 256
- # 加载数据
- train_iter, test_iter = load_data_fashion_mnist(batch_size)
- # 定义复杂模型、损失函数和优化器
- net = Net(num_inputs, num_outputs, num_hiddens1, num_hiddens2)
- loss = nn.CrossEntropyLoss(reduction='none')
- trainer = torch.optim.SGD(net.parameters(), lr=lr)
- # 开始训练复杂模型
- print("Training complex model...")
- train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer)
- print("===========================================================")
- # 定义简化模型、损失函数和优化器
- net_simple = Net_Simple(num_inputs, num_outputs, num_hiddens1, num_hiddens2)
- net_simple.apply(init_weights) # 初始化权重
- loss = nn.CrossEntropyLoss(reduction='none')
- trainer = torch.optim.SGD(net_simple.parameters(), lr=lr)
- # 开始训练简化模型
- print("Training simple model...")
- train_ch3(net_simple, train_iter, test_iter, loss, num_epochs, trainer)
复制代码 免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
|