鼠扑 发表于 2025-3-31 17:12:01

深度学习之抛弃法

抛弃法

动机



[*]一个好的模子需要对输入数据的扰动鲁棒

[*]使用有噪音的数据等价于 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−pxi​​​with 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]
查看完整版本: 深度学习之抛弃法