深度学习之抛弃法
抛弃法动机
[*]一个好的模子需要对输入数据的扰动鲁棒
[*]使用有噪音的数据等价于 Tikhonov 正则
[*]抛弃法:在层之间加入噪音
无偏差的加入噪音
[*]对 x \mathbf{x} x 加入噪音得到 x ′ \mathbf{x}' x′,我们盼望
E [ x ′ ] = x \mathbf{E}[\mathbf{x}'] = \mathbf{x} E=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} = p \cdot 0 + (1 - p) \cdot \frac{x_i}{1 - p} = x_i E=p⋅0+(1−p)⋅1−pxi=xi
https://i-blog.csdnimg.cn/direct/3a724918346f4fadb5c2ca226391b8f0.png
# 推理中的抛弃法
[*]正则项只在训练中使用:他们影响模子参数的更新
[*]在推理过程中,抛弃法直接返回输入,无需做任何操纵 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)
https://i-blog.csdnimg.cn/direct/747a0eaded944cb7851a00497bc8833f.png
简便实现
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)
https://i-blog.csdnimg.cn/direct/b00d3c08a13f4d66ad52e28b8ce288f9.png
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 =
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 = * n
def add(self, *args):
self.data =
def reset(self):
self.data = * len(self.data)
def __getitem__(self, idx):
return self.data
# 计算准确率
def accuracy(y_hat, y):
if len(y_hat.shape) > 1 and y_hat.shape > 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 / metric
# 单个 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)
metric.add(float(l.sum()), accuracy(y_hat, y), y.numel())
return metric / metric, metric / metric
# 绘制动画的类
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 =
n = len(y)
if not hasattr(x, "__len__"):
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.append(a)
self.Y.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=, ylim=,
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企服之家,中国第一个企服评测及商务社交产业平台。
页:
[1]