当前位置: 首页 > news >正文

深度学习之丢弃法

丢弃法

动机

  • 一个好的模型需要对输入数据的扰动鲁棒
    • 使用有噪音的数据等价于 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={01pxiwith 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]=p0+(1p)1pxi=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)

相关文章:

  • 太原网站上排名网络营销广告策划
  • 一起合伙做项目的网站广州品牌seo推广
  • 广东专业网站建设百度推广点击收费标准
  • 企业网站不付服务费应该怎么做发布新闻的平台有哪些
  • 企业网站备案要钱嘛产品如何做网络推广
  • 政府网站建设运维情况自查免费建设网站平台
  • 音视频 ColorSpace色彩空间详解
  • JS数组复制方法及注意事项
  • [BJDCTF2020]Mark loves cat [git泄露][变量覆盖漏洞]
  • Java单列集合[Collection]
  • 【Vue3知识】Vue3集成富文本编辑器TinyMCE
  • Croe 11.0建模入门笔记:1.2 快捷键
  • C++的四种类型转换
  • 走进 detect.tflite:树莓派目标检测背后的核心模型详解
  • rust学习笔记21-闭包
  • 多人协同进行qt应用程序开发应该注意什么2?
  • H5S USC 宇视LiteAPI协议支持
  • C#从入门到精通(4)
  • CPU飙高系统反应慢怎么排查?
  • AI赋能职教革新:生成式人工智能(GAI)认证重构技能人才培养新范式
  • 告别枯燥工作,走向自动化
  • 人形机器人行业研究
  • 1304-习题1_4-课后习题-高等数学
  • Cesium知识总结(一)
  • 洛谷P3128 [USACO15DEC] Max Flow P
  • RUST学习笔记1:Rust开发环境搭建(Winodws11 x64)