深度学习之抛弃法

鼠扑  论坛元老 | 2025-3-31 17:12:01 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 2094|帖子 2094|积分 6282

抛弃法

动机



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

    • 使用有噪音的数据等价于 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−pxi​​​with 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中的元素:
  1. import torch
  2. from torch import nn
  3. from d2l import torch as d2l
  4. def dropout_layer(X, dropout): # X是张量,dropout是丢弃率
  5.     assert 0 <= dropout <= 1
  6.     # 在本情况中,所有元素都被丢弃
  7.     if dropout == 1: # 为1表示所有元素都丢弃,返回一个与X形状相同的全0张量
  8.         return torch.zeros_like(X)
  9.     # 在本情况中,所有元素都被保留
  10.     if dropout == 0: # 所有元素都保留
  11.         return X
  12.     # 生成一个与X形状相同的随机张量,值在[0, 1)之间,然后与dropout进行比较,大于的赋值为1.0,小于的赋值为0.0
  13.     mask = (torch.rand(X.shape) > dropout).float()
  14.     # 得到一个二值掩码张量 mask,用于指示哪些元素应该保留(值为1)
  15.     return mask * X / (1.0 - dropout)
复制代码
接下来来测试一下该函数:
  1. X= torch.arange(16, dtype = torch.float32).reshape((2, 8))
  2. print(X)
  3. print(dropout_layer(X, 0.))
  4. print(dropout_layer(X, 0.5)) # 有一半的概率将其中的元素变为0
  5. print(dropout_layer(X, 0.7))
  6. print(dropout_layer(X, 1.))
复制代码
下面定义模子参数(定义具有两个隐藏层的多层感知机,每个隐藏层包罗256个单位):
输入还是 28*28,输出是 10 个类别
  1. num_inputs, num_outputs, num_hiddens1, num_hiddens2= 784, 10, 256, 256
复制代码
下面定义模子:
  1. dropout1, dropout2= 0.2, 0.5
  2. class Net(nn.Module):
  3.     def __init__(self, num_inputs, num_outputs, num_hiddens1, num_hiddens2,
  4.                  is_training = True): # 要区别是在训练,还是在测试
  5.         super(Net, self).__init__()
  6.         self.num_inputs = num_inputs
  7.         self.training = is_training
  8.         self.lin1 = nn.Linear(num_inputs, num_hiddens1) # 定义第一个全连接层,输入为 num_inputs,输出为 num_hiddens1
  9.         self.lin2 = nn.Linear(num_hiddens1, num_hiddens2)
  10.         self.lin3 = nn.Linear(num_hiddens2, num_outputs)
  11.         self.relu = nn.ReLU()# 定义激活函数
  12.     def forward(self, X):
  13.         H1 = self.relu(self.lin1(X.reshape((-1, self.num_inputs)))) # 第一个隐藏层的输出
  14.         # 只有在训练模型时才使用dropout
  15.         if self.training == True:
  16.             # 在第一个全连接层之后添加一个dropout层
  17.             H1 = dropout_layer(H1, dropout1)
  18.         H2 = self.relu(self.lin2(H1))
  19.         if self.training == True:
  20.             # 在第二个全连接层之后添加一个dropout层
  21.             H2 = dropout_layer(H2, dropout2)
  22.         out = self.lin3(H2)
  23.         return out
  24. net = Net(num_inputs, num_outputs, num_hiddens1, num_hiddens2)
复制代码
下面训练和测试:
  1. num_epochs, lr, batch_size = 10, 0.5, 256
  2. loss = nn.CrossEntropyLoss(reduction='none') # 不对每个样本的损失进行平均或求和,而是返回每个样本的损失值
  3. train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
  4. trainer = torch.optim.SGD(net.parameters(), lr=lr)
  5. d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer)
复制代码

简便实现

  1. net = nn.Sequential(nn.Flatten(),# 将输入张量展平为一维向量
  2.         nn.Linear(784, 256), # 定义一个全连接层
  3.         nn.ReLU(),
  4.         # 在第一个全连接层之后添加一个dropout层
  5.         nn.Dropout(dropout1),
  6.         nn.Linear(256, 256),
  7.         nn.ReLU(),
  8.         # 在第二个全连接层之后添加一个dropout层
  9.         nn.Dropout(dropout2),
  10.         nn.Linear(256, 10))
  11. def init_weights(m):
  12.     if type(m) == nn.Linear: # 检查是否为全连接层
  13.         nn.init.normal_(m.weight, std=0.01)
  14. net.apply(init_weights);
  15. trainer = torch.optim.SGD(net.parameters(), lr=lr)
  16. 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来对训练集加入一些噪音,使得训练数据的分布更加普适,从而保证模子的鲁棒性。所以预测的时候就没须要再加入噪声了。
后记

明白了之后写的一段代码:
  1. import torch
  2. import torchvision
  3. from torchvision import transforms
  4. from torch.utils import data
  5. import matplotlib.pyplot as plt
  6. from tqdm import tqdm  # 导入 tqdm 库
  7. # 定义 Dropout 层
  8. def dropout_layer(X, dropout):
  9.     assert 0 <= dropout <= 1
  10.     if dropout == 1:
  11.         return torch.zeros_like(X)
  12.     if dropout == 0:
  13.         return X
  14.     mask = (torch.rand(X.shape) > dropout).float()
  15.     return mask * X / (1.0 - dropout)
  16. # 定义神经网络
  17. class Net(torch.nn.Module):
  18.     def __init__(self, num_inputs, num_outputs, num_hiddens1, num_hiddens2, is_training=True):
  19.         super(Net, self).__init__()
  20.         self.num_inputs = num_inputs
  21.         self.training = is_training
  22.         self.lin1 = torch.nn.Linear(num_inputs, num_hiddens1)
  23.         self.lin2 = torch.nn.Linear(num_hiddens1, num_hiddens2)
  24.         self.lin3 = torch.nn.Linear(num_hiddens2, num_outputs)
  25.         self.relu = torch.nn.ReLU()
  26.     def forward(self, X):
  27.         H1 = self.relu(self.lin1(X.reshape((-1, self.num_inputs))))
  28.         if self.training:
  29.             H1 = dropout_layer(H1, dropout=0.2)
  30.         H2 = self.relu(self.lin2(H1))
  31.         if self.training:
  32.             H2 = dropout_layer(H2, dropout=0.5)
  33.         return self.lin3(H2)  # 输出层不需要激活函数
  34. # 加载 Fashion-MNIST 数据集
  35. def load_data_fashion_mnist(batch_size, resize=None):
  36.     trans = [transforms.ToTensor()]
  37.     if resize:
  38.         trans.insert(0, transforms.Resize(resize))
  39.     trans = transforms.Compose(trans)
  40.     mnist_train = torchvision.datasets.FashionMNIST("../data", train=True, transform=trans, download=True)
  41.     mnist_test = torchvision.datasets.FashionMNIST("../data", train=False, transform=trans, download=True)
  42.     return (
  43.         data.DataLoader(mnist_train, batch_size, shuffle=True),
  44.         data.DataLoader(mnist_test, batch_size, shuffle=False)
  45.     )
  46. # 累积器类
  47. class Accumulator:
  48.     def __init__(self, n):
  49.         self.data = [0.0] * n
  50.     def add(self, *args):
  51.         self.data = [a + float(b) for a, b in zip(self.data, args)]
  52.     def reset(self):
  53.         self.data = [0.0] * len(self.data)
  54.     def __getitem__(self, idx):
  55.         return self.data[idx]
  56. # 计算准确率
  57. def accuracy(y_hat, y):
  58.     if len(y_hat.shape) > 1 and y_hat.shape[1] > 1:
  59.         y_hat = y_hat.argmax(axis=1)
  60.     cmp = y_hat.type(y.dtype) == y
  61.     return float(cmp.type(y.dtype).sum())
  62. # 评估模型在数据集上的准确率
  63. def evaluate_accuracy(net, data_iter):
  64.     if isinstance(net, torch.nn.Module):
  65.         net.eval()
  66.     metric = Accumulator(2)
  67.     with torch.no_grad():
  68.         for X, y in data_iter:
  69.             metric.add(accuracy(net(X), y), y.numel())
  70.     return metric[0] / metric[1]
  71. # 单个 epoch 的训练
  72. def train_epoch_ch3(net, train_iter, loss, updater):
  73.     if isinstance(net, torch.nn.Module):
  74.         net.train()
  75.     metric = Accumulator(3)
  76.     # 使用 tqdm 添加进度条
  77.     for X, y in tqdm(train_iter, desc="Training"):
  78.         y_hat = net(X)
  79.         l = loss(y_hat, y)
  80.         if isinstance(updater, torch.optim.Optimizer):
  81.             updater.zero_grad()
  82.             l.mean().backward()
  83.             updater.step()
  84.         else:
  85.             l.sum().backward()
  86.             updater(X.shape[0])
  87.         metric.add(float(l.sum()), accuracy(y_hat, y), y.numel())
  88.     return metric[0] / metric[2], metric[1] / metric[2]
  89. # 绘制动画的类
  90. class Animator:
  91.     def __init__(self, xlabel=None, ylabel=None, legend=None, xlim=None,
  92.                  ylim=None, xscale='linear', yscale='linear',
  93.                  fmts=('-', 'm--', 'g-.', 'r:'), figsize=(3.5, 2.5)):
  94.         if legend is None:
  95.             legend = []
  96.         self.xlabel = xlabel
  97.         self.ylabel = ylabel
  98.         self.legend = legend
  99.         self.xlim = xlim
  100.         self.ylim = ylim
  101.         self.xscale = xscale
  102.         self.yscale = yscale
  103.         self.fmts = fmts
  104.         self.figsize = figsize
  105.         self.X, self.Y = [], []
  106.     def add(self, x, y):
  107.         if not hasattr(y, "__len__"):
  108.             y = [y]
  109.         n = len(y)
  110.         if not hasattr(x, "__len__"):
  111.             x = [x] * n
  112.         if not self.X:
  113.             self.X = [[] for _ in range(n)]
  114.         if not self.Y:
  115.             self.Y = [[] for _ in range(n)]
  116.         for i, (a, b) in enumerate(zip(x, y)):
  117.             if a is not None and b is not None:
  118.                 self.X[i].append(a)
  119.                 self.Y[i].append(b)
  120.     def show(self):
  121.         plt.figure(figsize=self.figsize)
  122.         for x_data, y_data, fmt in zip(self.X, self.Y, self.fmts):
  123.             plt.plot(x_data, y_data, fmt)
  124.         plt.xlabel(self.xlabel)
  125.         plt.ylabel(self.ylabel)
  126.         if self.legend:
  127.             plt.legend(self.legend)
  128.         if self.xlim:
  129.             plt.xlim(self.xlim)
  130.         if self.ylim:
  131.             plt.ylim(self.ylim)
  132.         plt.xscale(self.xscale)
  133.         plt.yscale(self.yscale)
  134.         plt.grid()
  135.         plt.show()
  136. # 主训练函数
  137. def train_ch3(net, train_iter, test_iter, loss, num_epochs, updater):
  138.     animator = Animator(xlabel='epoch', xlim=[1, num_epochs], ylim=[0.3, 0.9],
  139.                         legend=['train loss', 'train acc', 'test acc'])
  140.     for epoch in range(num_epochs):
  141.         train_metrics = train_epoch_ch3(net, train_iter, loss, updater)
  142.         test_acc = evaluate_accuracy(net, test_iter)
  143.         animator.add(epoch + 1, train_metrics + (test_acc,))
  144.         print(f"Epoch {epoch + 1}: Train Metrics = {train_metrics}, Test Acc = {test_acc}")
  145.     train_loss, train_acc = train_metrics
  146.     animator.show()  # 展示最终结果图
  147. # 主程序
  148. if __name__ == "__main__":
  149.     # 参数设置
  150.     num_epochs, lr, batch_size = 10, 0.5, 256
  151.     num_inputs, num_outputs, num_hiddens1, num_hiddens2 = 784, 10, 256, 256
  152.     # 加载数据
  153.     train_iter, test_iter = load_data_fashion_mnist(batch_size)
  154.     # 定义模型、损失函数和优化器
  155.     net = Net(num_inputs, num_outputs, num_hiddens1, num_hiddens2)
  156.     loss = torch.nn.CrossEntropyLoss(reduction='none')
  157.     trainer = torch.optim.SGD(net.parameters(), lr=lr)
  158.     # 开始训练
  159.     train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer)
复制代码
扔一个简化版:
  1. import torch
  2. from torch import nn
  3. from package import load_data_fashion_mnist, train_ch3
  4. # 定义 Dropout 层
  5. def dropout_layer(X, dropout):
  6.     assert 0 <= dropout <= 1
  7.     if dropout == 1:
  8.         return torch.zeros_like(X)
  9.     if dropout == 0:
  10.         return X
  11.     mask = (torch.rand(X.shape) > dropout).float()
  12.     return mask * X / (1.0 - dropout)
  13. # 定义神经网络(复杂版)
  14. class Net(torch.nn.Module):
  15.     def __init__(self, num_inputs, num_outputs, num_hiddens1, num_hiddens2, is_training=True):
  16.         super(Net, self).__init__()
  17.         self.num_inputs = num_inputs
  18.         self.training = is_training
  19.         self.lin1 = torch.nn.Linear(num_inputs, num_hiddens1)
  20.         self.lin2 = torch.nn.Linear(num_hiddens1, num_hiddens2)
  21.         self.lin3 = torch.nn.Linear(num_hiddens2, num_outputs)
  22.         self.relu = torch.nn.ReLU()
  23.     def forward(self, X):
  24.         H1 = self.relu(self.lin1(X.reshape((-1, self.num_inputs))))
  25.         if self.training:
  26.             H1 = dropout_layer(H1, dropout=0.2)
  27.         H2 = self.relu(self.lin2(H1))
  28.         if self.training:
  29.             H2 = dropout_layer(H2, dropout=0.5)
  30.         return self.lin3(H2)  # 输出层不需要激活函数
  31. # 定义神经网络(简化版)
  32. class Net_Simple(nn.Module):
  33.     def __init__(self, num_inputs, num_outputs, num_hiddens1, num_hiddens2):
  34.         super(Net_Simple, self).__init__()
  35.         self.net = nn.Sequential(
  36.             nn.Flatten(),
  37.             nn.Linear(num_inputs, num_hiddens1),
  38.             nn.ReLU(),
  39.             nn.Dropout(0.2),
  40.             nn.Linear(num_hiddens1, num_hiddens2),
  41.             nn.ReLU(),
  42.             nn.Dropout(0.5),
  43.             nn.Linear(num_hiddens2, num_outputs)
  44.         )
  45.     def forward(self, X):
  46.         return self.net(X)
  47. # 权重初始化函数
  48. def init_weights(m):
  49.     if type(m) == nn.Linear:
  50.         nn.init.normal_(m.weight, std=0.01)
  51. # 主程序
  52. if __name__ == "__main__":
  53.     # 参数设置
  54.     num_epochs, lr, batch_size = 10, 0.5, 256
  55.     num_inputs, num_outputs, num_hiddens1, num_hiddens2 = 784, 10, 256, 256
  56.     # 加载数据
  57.     train_iter, test_iter = load_data_fashion_mnist(batch_size)
  58.     # 定义复杂模型、损失函数和优化器
  59.     net = Net(num_inputs, num_outputs, num_hiddens1, num_hiddens2)
  60.     loss = nn.CrossEntropyLoss(reduction='none')
  61.     trainer = torch.optim.SGD(net.parameters(), lr=lr)
  62.     # 开始训练复杂模型
  63.     print("Training complex model...")
  64.     train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer)
  65.     print("===========================================================")
  66.     # 定义简化模型、损失函数和优化器
  67.     net_simple = Net_Simple(num_inputs, num_outputs, num_hiddens1, num_hiddens2)
  68.     net_simple.apply(init_weights)  # 初始化权重
  69.     loss = nn.CrossEntropyLoss(reduction='none')
  70.     trainer = torch.optim.SGD(net_simple.parameters(), lr=lr)
  71.     # 开始训练简化模型
  72.     print("Training simple model...")
  73.     train_ch3(net_simple, train_iter, test_iter, loss, num_epochs, trainer)
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
继续阅读请点击广告

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

鼠扑

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表