dropout层的作用_【深度学习理论】一文搞透Dropout、L1L2正则化/权重衰减

前言

本文主要内容——一文搞透深度学习中的正则化概念,常用正则化方法介绍,重点介绍Dropout的概念和代码实现、L1-norm/L2-norm的概念、L1/L2正则化的概念和代码实现~

要是文章看完还不明白,欢迎交流、分享、拍砖:)

详细内容如下:

1.正则化Regularization

  • 1.1 过拟合Overfitting

  • 1.2 欠拟合Underfitting

  • 1.3 泛化能力generalization ability

  • 1.4 解决或改善?

2.L1 L2正则化/权重衰减

  • 2.1 范数和欧几里得空间

      • 范数Norm

      • 欧几里得空间

  • 2.2 L1-范数和L2-范数

      • L1-范数

      • L2-范数

  • 2.3 L1/L2正则化和权重衰减

      • L1/L2正则化

      • 权重衰减weight decay

      • 代码实现—pytorch

3.Dropout

  • 3.1概述

      • 提出

      • 应用

      • 发展

  • 3.2 Vanilla Dropout

  • 3.3 Inverted Dropout

  • 3.4 代码实现——pytorch

      • 从零开始实现

      • 简洁实现

参考

原文发表于语雀文档:

【深度学习理论+代码】一文搞透Dropout、L1L2正则化/权重衰减 · 语雀www.yuque.comdropout层的作用_【深度学习理论】一文搞透Dropout、L1L2正则化/权重衰减_第1张图片


1.正则化Regularization

什么是正则化?其作用是怎样的?了解正则化之前,我们需要先了解一下机器学习中欠拟合和过拟合的概念

1.1 过拟合Overfitting

过度拟合的意思,意味着模型训练过程中,对训练集的模拟和学习过度贴合;
过拟合带来的影响:模型训练时的检测率很高效果很好,但是用于实际检验时,效果很差,模型不能很准确地预测,即泛化能力差。

1.2 欠拟合Underfitting

和过拟合相对,欠拟合是指模型和数据集间的拟合程度不够,学习不足。
欠拟合的影响:和过拟合相对,欠拟合是指模型和数据集间的拟合程度不够,可能是学习轮数不够、数据集特征不规则、模型选择有问题等。欠拟合时,模型的泛化能力同样会很差。

1.3 泛化能力generalization ability

是指一个机器学习算法对于没有见过的样本的识别能力。泛化能力自然是越高越好。
在吴恩达机器学习中有一个线性回归预测房价的例子,非常形象直观:

dropout层的作用_【深度学习理论】一文搞透Dropout、L1L2正则化/权重衰减_第2张图片


如上图,x轴表示房子面积,y轴表示房屋售价,图表中有5个样本点(训练集),任务是找到合适的模型来拟合样本且用来预测未知的样本。
第一个模型是线性模型,对训练集样本欠拟合;
中间的二次方模型对样本拟合程度比较适中;
第三个四次方模型,对样本拟合程度更高,不过对于未来的新样本可能预测能力会很差,此为典型的过拟合。

1.4 解决或改善?

那么解决过拟合的方式有哪些?课程中给出了以下两点建议:1.丢弃一些不能帮助我们正确预测的特征。可以是手工选择保留哪些特征,或者使用一些模型选择的算法来帮忙(例如 PCA)2.正则化。保留所有的特征,但是减少参数的大小(magnitude)。当然,以上是机器学习中的一些通用做法,在深度学习中同样适用于。譬如深度学习中常见的降低过拟合/正则化方法:

  • 使用L1正则化降低权重;

  • 使用L2正则化/权重衰减weight decay;

  • Early stopping 早停,防止过拟合;

  • 使用BN层batch normalization来规范数据分布,间接起到正则化的作用;

  • 使用Dropout(及各种变种和改进版)随机丢弃一些神经元,来减低模型对部分神经元的依赖,加强模型的鲁棒性,达到正则化的效果;

  • Data augmentation数据增强,譬如将数据集图片处理后,增加平移旋转和缩放处理,增加噪声处理等,使得模型对各种复杂条件下的图像都能较好适应,增强鲁棒性。

有时L2正则化和weight decay常被人理解为同一种意思,实际上二者有时不可等价,见:  zhuanlan.zhihu.com/p/40


当然,这么多正则化方式,每一点拎出来都可以出好几篇论文了~本文主要介绍的是常用的L2正则/权重衰减和Dropout,BN等放在以后的文章中介绍。

2.L1 L2正则化/权重衰减

本小节,讲讲L1正则化L2正则化和权重衰减(weight decay),重点分为三个部分:

  • 1.讲解L1/L2范数和L1/L2正则化的概念、来源、其在深度学习中的应用;

  • 2.L2正则化和权重衰减的概念;

  • 3.L2正则化/权重衰减在深度学习pytorch代码中的实现。

2.1 范数和欧几里得空间

范数Norm

在了解L1/L2正则化前,必须先了解L1-norm(L1范数)和L2-norm(L2范数),因为本质上,L1/L2正则化,即在机器学习/深度学习的loss损失函数中添加L1/L2范数的正则化项,以起到惩罚权重,达到正则化的效果,故称为L1/L2正则化。范数norm的定义是基于向量空间(Vector space)的,向量空间也称为线性空间,其概念起源于17世纪的解析几何、矩阵和欧几里得空间(Euclidean space)。我这里就不摆公式定义了,可以参考wiki。

欧几里得空间

简单理解,欧几里得空间(Euclidean space)是向量空间的子集,起源于是古希腊学者欧几里得对物理空间的一种抽象(来源于生活中常见的二维平面,三维空间)并在其中定义了点线、内积、距离、角的概念。譬如:二维平面中一个点可以有坐标x,y;两点之间的距离表现为一条直线,其大小可以用$$d = \sqrt{(x_1-x_2)^2 + (y_1-y_2)^2}$$)表示;三维空间中,两点之间距离表现为一个向量,其大小可以用:$$d = \sqrt{(x_1-x_2)^2 + (y_1-y_2)^2 + (z_1-z_2)^2}$$表示,两向量a和b的内积定义为:$$\vec{a} \cdot \vec{b}=|\vec{a}||\vec{b}| \cos \theta$$。当然,欧式空间也可以拓展至n维度。在n=时,一个点需要用3个坐标来定义,而n维欧式空间中,需要用n个坐标定义。

2.2 L1-范数和L2-范数

明明讨论的是范数,怎么扯到欧几里得空间去了?!

dropout层的作用_【深度学习理论】一文搞透Dropout、L1L2正则化/权重衰减_第3张图片


我也很无奈啊,因为L2范数也被称为欧几里得范数:)这里,我就不摆范数的定义了。简单来说,我们可以把L2-norm放在欧式空间中来理解,且直观地理解其意义为向量的模长。p-范数的公式定义如下:

L1-范数

p = 1时为L1范数(L1-norm):

L1范数(又称为Taxicab norm or Manhattan norm),看起来L1-norm即一组数的绝对值累加和。

L2-范数

p = 2时为L2范数(L2-norm):
L2范数又称为欧几里得范数/平方范数(还可称为L norm, ℓ norm, 2-norm),其用于表示向量_x = (_xx, ..., x__n)距离原点的距离。

2.3 L1/L2正则化和权重衰减

L1/L2正则化

前面说了那么多,有木有一脸懵逼???别慌,都是浮云~我们只要记住L1范数是绝对值和,L2范数是平方和(开根号)即可。简单来说,L1/L2正则化就是在机器学习/深度学习中应用了L1/L2范数,具体来说,就是在损失函数loss上增加了L1或L2范数项,达到参数惩罚的作用,即实现了正则化的效果,从而称为L1/L2正则化。

在之前的专栏文章:【吴恩达机器学习】第三周—逻辑回归、过拟合、正则化中,有个栗子比较形象地展示了L2正则化的过程,如下图:

dropout层的作用_【深度学习理论】一文搞透Dropout、L1L2正则化/权重衰减_第4张图片

图中最右边的线性回归模型表达式为:

                               

由于其高次项参数的使用,使得模型对训练数据过分拟合,导致对未来更一般的数据预测性大大下降,为了缓解这种过拟合的现象,我们可以采用L2正则化。具体来说就是在原有的损失函数上添加L2正则化项(l2-norm的平方):
                                                                           =>
这里,通过设置正则化系数  可以较好地惩罚高次项的特征,从而起到降低过拟合,正则化的效果。至于高次项的特征如何受到系数的影响,如何被惩罚的,详见:【吴恩达机器学习】第三周—逻辑回归、过拟合、正则化。添加L2正则化修正以后的模型曲线:

dropout层的作用_【深度学习理论】一文搞透Dropout、L1L2正则化/权重衰减_第5张图片

权重衰减weight decay

权重衰减weight decay,并不是一个规范的定义,而只是俗称而已,可以理解为削减/惩罚权重。在大多数情况下weight dacay 可以等价为L2正则化。L2正则化的作用就在于削减权重,降低模型过拟合,其行为即直接导致每轮迭代过程中的权重weight参数被削减/惩罚了一部分,故也称为权重衰减weight decay。从这个角度看,不论你用L1正则化还是L2正则化,亦或是其他的正则化方法,只要是削减了权重,那都可以称为weight dacay。
设:

  • 参数矩阵为p(包括weight和bias);

  • 模型训练迭代过程中计算出的loss对参数梯度为d_p;

  • 学习率lr;

  • 权重衰减参数为decay

则不设dacay时,迭代时参数的更新过程可以表示为:                                                             p* = *p - lr×d_p;
增加weight_dacay参数后表示为:                                                p = p - lr ×(d_p + p × dacay)

代码实现—pytorch

在深度学习框架的实现中,可以通过设置weight_decay参数,直接对weight矩阵中的数值进行削减(而不是像L2正则一样,通过修改loss函数)起到正则化的参数惩罚作用。二者通过不同方式,同样起到了对权重参数削减/惩罚的作用,实际上在通常的随机梯度下降算法(SGD)中,通过数学计算L2正则化完全可以等价于直接权重衰减。(少数情况除外,譬如使用Adam优化器时,可以参考:L2正则=Weight Decay?并不是这样)


正因如此,深度学习框架通常实现weight dacay/L2正则化的方式很简单,直接指定weight_dacay参数即可。


在pytorch/tensorflow等框架中,我们可以方便地指定weight_dacay参数,来达到正则化的效果,譬如在pytorch的sgd优化器中,直接指定weight_decay = 0.0001:

optimizer = torch.optim.SGD(net.parameters(), lr=0.001, weight_decay=0.0001)

在模型训练过程中,每一轮迭代时通过:l.backward()来自动求梯度;之后通过optimizer.step()进行参数矩阵的梯度更新。

在sgd的代码实现中,我们重点看step()方法的第26行:if weight_decay != 0: 表示了应用weight_decay参数的情况;倒数第二行:p.add_(d_p, alpha=-group['lr'])表示了参数矩阵的梯度更新。

@torch.no_grad()
def step(self, closure=None):
"""Performs a single optimization step. Arguments: closure (callable, optional): A closure that reevaluates the model and returns the loss. """
loss = None
if closure is not None:
with torch.enable_grad():
loss = closure()

# param_groups >>>>>>> {'params':[...],'lr': 0.001, 'momentum': 0, 'dampening': 0, 'weight_decay': 0.0001, 'nesterov': False}
for group in self.param_groups:
weight_decay = group['weight_decay']
momentum = group['momentum']
dampening = group['dampening']
nesterov = group['nesterov']
# params即为网络中所有的待更新参数矩阵如weight、bias
for p in group['params']:
if p.grad is None:
continue
# p即为待更新的参数矩阵(权重/bias);d_p为.backward()方法计算出的loss对参数的梯度矩阵
d_p = p.grad
if weight_decay != 0: #等价于d_p = d_p + p × dacay
d_p = d_p.add(p, alpha=weight_decay)

if momentum != 0:
param_state = self.state[p]
if 'momentum_buffer' not in param_state:
buf = param_state['momentum_buffer'] = torch.clone(d_p).detach()
else:
buf = param_state['momentum_buffer']
buf.mul_(momentum).add_(d_p, alpha=1 - dampening)
if nesterov:
d_p = d_p.add(buf, alpha=momentum)
else:
d_p = buf
# p = p - lr×d_p
p.add_(d_p, alpha=-group['lr'])
return loss

# tensor.add()的python代码定义见:https://pytorch.org/docs/stable/tensors.html?highlight=torch%20add#torch.Tensor.add
# 其底层实现依赖c++,依据tensor类型的不同,有着不同的c++实现,以cpu为例,其最终实现在c++:aten/src/ATen/native/cpu/BinaryOpsKernel.cpp
# 中的add_kernel()方法,该方法通过REGISTER_DISPATCH(add_stub, &add_kernel)注册以提供外部调用。
# 具体调用的代码在:aten/src/ATen/native/BinaryOps.cpp中的方法:Tensor& add_(Tensor& self, const Tensor& other, Scalar alpha)
# 和Tensor add(const Tensor& self, const Tensor& other, Scalar alpha)

3.Dropout

3.1概述

提出

Dropout的提出,源于2012年Hinton的一篇论文——《Improving neural networks by preventing co-adaptation of feature detectors》。论文中描述了当数据集较小时而神经网络模型较大较复杂时,训练时容易产生过拟合,为了防止过拟合,可以通过阻止特征检测器间的共同作用来提高模型性能。

应用

2012年,大名鼎鼎的AlexNet网络的论文——《ImageNet Classification with Deep Convolutional Neural Networks》中,应用了Dropout,并且证明了其在提高模型精度和降低过拟合方面效果出色。由于AlexNet有效的网络结构+Dropout的应用,此模型在12年的ImageNet分类赛上以大幅优势领先第二名,从而使得深度卷积神经网络CNN在图像分类上的应用掀起一波热潮~

发展

在这以后,围绕Dropout又涌现出不少论文:
《Dropout:A Simple Way to Prevent Neural Networks from Overfitting》
《Improving Neural Networks with Dropout》
《Dropout as data augmentation》
等等。

不过总结起来,Dropout的原理都类似,只是实现方式有不同而已。比较流行的实现主要有两种类型:

  • Vanilla Dropout

  • Inverted Dropout

其中Vanilla Dropout是原论文中提出的,朴素实现版本;而Inverted Dropout则是更广为使用和流行的实现

3.2 Vanilla Dropout

Vanilla Dropout是原论文中提出的,这里我简单介绍下主要原理和流程。从上面的介绍中可知,Dropout的提出是为了降低过拟合的,具体是应用在深度神经网络的中间隐藏层上,对于某一层l,如果应用了概率p(可选超参数,例如可设置p = 0.5)的Dropout,即表面该层的神经元,在网络的训练/测试过程中,每个神经元都有50%的概率被“丢弃”,即此神经元不参与权重矩阵的计算。
具体流程如下:

dropout层的作用_【深度学习理论】一文搞透Dropout、L1L2正则化/权重衰减_第6张图片


模型训练时应用Dropout的流程,概况一下描述就是:

  • 1.随机概率p随机dropout部分神经元,并前向传播

  • 2.计算前向传播的损失,应用反向传播和梯度更新(对剩余的未被dropout的神经元)

  • 3.恢复所有神经元的,并重复过程1


此训练过程理解起来很简单,但是有个问题,就是测试时会比较麻烦。为了保持模型的分布相同,测试时也需要保持模型分布和训练时一样,需要以一个概率p来丢失部分神经元(即乘以1-p的概率来保留),这样会不太方便,而且同一个输入可能每次预测的结果不一样,不稳定。所以Vanilla Dropout并没有得到广泛应用,取而代之的是更方便的Inverted Dropout,我们在之前的文章——【吴恩达深度学习】—参数、超参数、正则化中介绍的就是这种Dropout。

3.3 Inverted Dropout

Inverted Dropout是在在各大深度学习框架中是更广泛使用的版本,其原理类似,就是实现起来和原始版本的dropout稍微有些区别。Inverted Dropout在训练阶段,同样应用p的概率来随机失活,不过额外提前除以1-p,这样相当于将网络的分布提前“拉伸”了,好处就是在预测阶段,网络无需再乘以1-p(来压缩分布),这样预测时网络无需改动,输出也更加稳定。丢弃法不改变输入的期望
假设随机变量  为0和1的概率分别为p和1-p(丢弃和保留的概率),则任意神经元  可以表示为:  而  ,所以有:  即,Dropout并不改变神经元的期望值,即不改变整个输入的期望。

3.4 代码实现——pytorch

下面,我们借用《动手学习深度学习-pytorch版》的一个例子来描述Inverted Dropout的原理和代码实现。


首先,我们的神经网络模型如下:

dropout层的作用_【深度学习理论】一文搞透Dropout、L1L2正则化/权重衰减_第7张图片


很简单总共3层,输入层4个神经元;中间隐藏层5个神经元  ;输出层3个;ϕ_为激活函数,输入为  ,隐藏层中单元i的权重参数为:  ,偏差bias为bi
则隐藏层激活单元表达式如下:
对隐藏层使用Dropout,设隐藏层中任意神经元被“丢弃” 的概率设为p,(保留的概率为1-p)。所谓的丢弃指将此神经元的值设为0(故其值不能被传递到下一层,相当于被砍掉了)。使用Dropout后,可能的一种网络形态:

dropout层的作用_【深度学习理论】一文搞透Dropout、L1L2正则化/权重衰减_第8张图片


代码实现主要是这个简单神经网络模型在Fashion-mnist数据集上的训练,部分和书中一样,有两种实现方式:

  • 1.从零开始实现

  • 2.简洁版实现

1.从零开始实现主要是自定义dropout函数,自定义网络等;2.简洁实现即用pytorch现成的dropout和网络定义来实现模型的训练。

从零开始实现

为了表达对于某一层输入X应用dropout,我们可以定义以下函数:

def dropout(X, drop_prob):
X = X.float()
assert 0 <= drop_prob <= 1 # dropout概率为[0,1]之间
keep_prob = 1 - drop_prob # keep_prob = 1-p即为保留率
if keep_prob == 0: # p = 1时表示全部丢弃,返回空矩阵即可
return torch.zeros_like(X)
# 设置mask矩阵(1.0、0.0),用来和输入X相乘,模拟dropout过程
mask = (torch.rand(X.shape) < keep_prob).float()
Y = mask * X / keep_prob
return Y

dropout函数核心有两点:

  • 1.mask = (torch.rand(X.shape) < keep_prob).float()

  • 2. Y = mask * X / keep_prob

1.mask矩阵使用torch.rand可以产生0~1之间均匀分布的数字,< keep_prob(=1-p)处理后得到的矩阵中元素为True的概率为1-p,为False的概率为p,float()转换为1和0后,即完成了dropout的过程。mask矩阵和X相乘来模拟随机dropout(×0.0的即被失活,×1.0的即得到保留)2.mask * X即完成了p概率的dropout,再除以1-p是为了后面预测阶段无需更改网络
完整代码如下:

import torch
import numpy as np
import torchvision


def dropout(X, drop_prob):
X = X.float()
assert 0 <= drop_prob <= 1 # dropout概率为[0,1]之间
keep_prob = 1 - drop_prob # keep_prob = 1-p即为保留率
if keep_prob == 0: # p = 1时表示全部丢弃,返回空矩阵即可
return torch.zeros_like(X)
# 设置mask矩阵(1.0、0.0),用来和输入X相乘,模拟dropout过程
mask = (torch.rand(X.shape) < keep_prob).float()
Y = mask * X / keep_prob
return Y


# 定义模型参数
num_inputs, num_outputs, num_hiddens1, num_hiddens2 = 784, 10, 256, 256
W1 = torch.tensor(np.random.normal(0, 0.01, size=(num_inputs, num_hiddens1)), dtype=torch.float, requires_grad=True)
b1 = torch.zeros(num_hiddens1, requires_grad=True)
W2 = torch.tensor(np.random.normal(0, 0.01, size=(num_hiddens1, num_hiddens2)), dtype=torch.float, requires_grad=True)
b2 = torch.zeros(num_hiddens2, requires_grad=True)
W3 = torch.tensor(np.random.normal(0, 0.01, size=(num_hiddens2, num_outputs)), dtype=torch.float, requires_grad=True)
b3 = torch.zeros(num_outputs, requires_grad=True)
params = [W1, b1, W2, b2, W3, b3]


# 定义模型
def net(X, is_training=True):
X = X.view(-1, num_inputs)
H1 = (torch.matmul(X, W1) + b1).relu()
if is_training: # 只在训练模型时使用丢弃法
H1 = dropout(H1, drop_prob1) # 在第一层全连接后添加丢弃层
H2 = (torch.matmul(H1, W2) + b2).relu()
if is_training:
H2 = dropout(H2, drop_prob2) # 在第二层全连接后添加丢弃层
return torch.matmul(H2, W3) + b3


def load_data_fashion_mnist(batch_size, root='', num_workers=4):
"""Download the fashion mnist dataset and then load into memory."""
trans = [torchvision.transforms.ToTensor()]
transform = torchvision.transforms.Compose(trans)
mnist_train = torchvision.datasets.FashionMNIST(root=root, train=True, download=False, transform=transform)
mnist_test = torchvision.datasets.FashionMNIST(root=root, train=False, download=False, transform=transform)

train_iter = torch.utils.data.DataLoader(mnist_train, batch_size=batch_size, shuffle=True, num_workers=num_workers)
test_iter = torch.utils.data.DataLoader(mnist_test, batch_size=batch_size, shuffle=False, num_workers=num_workers)
return train_iter, test_iter


def evaluate(data_iter, net):
acc_sum, n = 0.0, 0
for X, y in data_iter:
if isinstance(net, torch.nn.Module):
net.eval() # 评估模式, 这会关闭dropout
acc_sum += (net(X).argmax(dim=1) == y).float().sum().item()
net.train() # 改回训练模式
else: # 自定义的模型
if 'is_training' in net.__code__.co_varnames: # 如果有is_training这个参数
# 将is_training设置成False
acc_sum += (net(X, is_training=False).argmax(dim=1) == y).float().sum().item()
else:
acc_sum += (net(X).argmax(dim=1) == y).float().sum().item()
n += y.shape[0]
return acc_sum / n


def sgd(params, lr, batch_size):
# 为了和原书保持一致,这里除以了batch_size,但是应该是不用除的,因为一般用PyTorch计算loss时就默认已经
# 沿batch维求了平均了。
for param in params:
param.data -= lr * param.grad / batch_size # 注意这里更改param时用的param.data


def train(net, train_iter, test_iter, loss, num_epochs, batch_size,
params=None, lr=None, optimizer=None):
for epoch in range(num_epochs):
train_l_sum, train_acc_sum, n = 0.0, 0.0, 0
for X, y in train_iter:
y_hat = net(X)
l = loss(y_hat, y).sum()

# 梯度清零
if optimizer is not None:
optimizer.zero_grad()
elif params is not None and params[0].grad is not None:
for param in params:
param.grad.data.zero_()

l.backward()
if optimizer is None:
sgd(params, lr, batch_size)
else:
optimizer.step() # “softmax回归的简洁实现”一节将用到

train_l_sum += l.item()
train_acc_sum += (y_hat.argmax(dim=1) == y).sum().item()
n += y.shape[0]
test_acc = evaluate(test_iter, net)
print('epoch %d, loss %.4f, train acc %.3f, test acc %.3f'
% (epoch + 1, train_l_sum / n, train_acc_sum / n, test_acc))

# 设置两种丢弃率
drop_prob1, drop_prob2 = 0.2, 0.5
# 训练参数
num_epochs, lr, batch_size = 5, 100.0, 256
loss = torch.nn.CrossEntropyLoss()
train_iter, test_iter = load_data_fashion_mnist(batch_size, root='data')
train(net, train_iter, test_iter, loss, num_epochs, batch_size, params, lr)

# 输出
# epoch 1, loss 0.0045, train acc 0.555, test acc 0.756
# epoch 2, loss 0.0023, train acc 0.786, test acc 0.749
# epoch 3, loss 0.0019, train acc 0.823, test acc 0.833
# epoch 4, loss 0.0017, train acc 0.838, test acc 0.846
# epoch 5, loss 0.0016, train acc 0.850, test acc 0.847

简洁实现

import torch
import torch.nn as nn
import torchvision


def load_data_fashion_mnist(batch_size, root='', num_workers=4):
"""Download the fashion mnist dataset and then load into memory."""
trans = [torchvision.transforms.ToTensor()]
transform = torchvision.transforms.Compose(trans)
mnist_train = torchvision.datasets.FashionMNIST(root=root, train=True, download=True, transform=transform)
mnist_test = torchvision.datasets.FashionMNIST(root=root, train=False, download=True, transform=transform)

train_iter = torch.utils.data.DataLoader(mnist_train, batch_size=batch_size, shuffle=True, num_workers=num_workers)
test_iter = torch.utils.data.DataLoader(mnist_test, batch_size=batch_size, shuffle=False, num_workers=num_workers)
return train_iter, test_iter


def evaluate(data_iter, net):
acc_sum, n = 0.0, 0
for X, y in data_iter:
if isinstance(net, torch.nn.Module):
net.eval() # 评估模式, 这会关闭dropout
acc_sum += (net(X).argmax(dim=1) == y).float().sum().item()
net.train() # 改回训练模式
else: # 自定义的模型
if 'is_training' in net.__code__.co_varnames: # 如果有is_training这个参数
# 将is_training设置成False
acc_sum += (net(X, is_training=False).argmax(dim=1) == y).float().sum().item()
else:
acc_sum += (net(X).argmax(dim=1) == y).float().sum().item()
n += y.shape[0]
return acc_sum / n


def train(net, train_iter, test_iter, loss, num_epochs,
params=None, optimizer=None):
for epoch in range(num_epochs):
train_l_sum, train_acc_sum, n = 0.0, 0.0, 0
for X, y in train_iter:
y_hat = net(X)
l = loss(y_hat, y).sum()

# 梯度清零
if optimizer is not None:
optimizer.zero_grad()
elif params is not None and params[0].grad is not None:
for param in params:
param.grad.data.zero_()

l.backward()
optimizer.step() # “softmax回归的简洁实现”一节将用到

train_l_sum += l.item()
train_acc_sum += (y_hat.argmax(dim=1) == y).sum().item()
n += y.shape[0]
test_acc = evaluate(test_iter, net)
print('epoch %d, loss %.4f, train acc %.3f, test acc %.3f'
% (epoch + 1, train_l_sum / n, train_acc_sum / n, test_acc))


# # 设置两种丢弃率
drop_prob1, drop_prob2 = 0.2, 0.5
# # 训练参数
num_epochs, lr, batch_size = 5, 100.0, 256
loss = torch.nn.CrossEntropyLoss()
train_iter, test_iter = load_data_fashion_mnist(batch_size, root='data')


class FlattenLayer(torch.nn.Module):
def __init__(self):
super(FlattenLayer, self).__init__()

def forward(self, x): # x shape: (batch, *, *, ...)
return x.view(x.shape[0], -1)


# 定义模型参数
num_inputs, num_outputs, num_hiddens1, num_hiddens2 = 784, 10, 256, 256

net = nn.Sequential(
FlattenLayer(),
nn.Linear(num_inputs, num_hiddens1),
nn.ReLU(),
nn.Dropout(drop_prob1),
nn.Linear(num_hiddens1, num_hiddens2),
nn.ReLU(),
nn.Dropout(drop_prob2),
nn.Linear(num_hiddens2, 10)
)

for param in net.parameters():
nn.init.normal_(param, mean=0, std=0.01)
optimizer = torch.optim.SGD(net.parameters(), lr=0.5)
train(net, train_iter, test_iter, loss, num_epochs, batch_size, None, None, optimizer)

# 输出:
# epoch 1, loss 0.0043, train acc 0.576, test acc 0.646
# epoch 2, loss 0.0024, train acc 0.773, test acc 0.810
# epoch 3, loss 0.0019, train acc 0.819, test acc 0.796
# epoch 4, loss 0.0018, train acc 0.836, test acc 0.844
# epoch 5, loss 0.0016, train acc 0.849, test acc 0.852

参考

  • 深度学习中Dropout原理解析

  • 神经网络Dropout层中为什么dropout后还需要进行rescale?

  • 都9102年了,别再用Adam + L2 regularization了

  • l1正则与l2正则的特点是什么,各有什么优势?

  • 《动手学习深度学习》-pytorch版

  • en.wikipedia.org/wiki/N

你可能感兴趣的:(dropout层的作用,pytorch,l2正则化,pytorch,正则化)