基础组件详情-深度学习实战

844 阅读8分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第3天,点击查看活动详情

基础组件详解

划分数据集

当我们拿到一个完整的数据集时候,我们普遍将数据集划分成三部分:训练集,验证集,测试集。他们的比例是7:2:1。

image.png

划分原则:

  • 三个子集尽可能同分布:如果三个子集划分分布不均,可能导致训练出来的模型效果不好。
  • 不能数据泄露:测试集与训练集必须隔离,划分的测试集必须是模型从未见过的数据。
  • 数据较少时候,可以合并 dev/test 数据集。

预处理

参考:blog.csdn.net/program_dev…

不同的行业的数据,会有着不同的评价指标,就具有不同的量纲,例如:对于评价房价来说量纲指:面积、房价数、楼层等;对于预测某个人患病率来说量纲指:身高、体重等。)和量纲单位(例如:面积单位:平方米、平方厘米等;身高:米、厘米等),这些都会对数据分析发生影响。对数据进行归一化,可以消除数据之间的量纲的影响。

优点:

  1. 归一化加快梯度下降求最优解的速度
  2. 归一化有可能提高精度(归一化是让不同维度之间的特征在数值上有一定的比较性)。

方法:

  1. min-max标准化(Min-Max Normalization)(线性函数归一化)
  • 定义:也称为离差标准化,是对原始数据的线性变换,使得结果映射到0-1之间。
  • 本质:把数变为【0,1】之间的小数。
  • 转换函数:(X-Min)/(Max-Min)
  • 如果想要将数据映射到-1,1,则将公式换成:(X-Mean)/(Max-Min)

其中: max为样本数据的最大值,min为样本数据的最小值,Mean表示数据的均值。

缺陷: 当有新数据加入时,可导致max和min的变化,需要重新定义。

  1. 0 均值标准化(Z-score standardization
  • 定义:这种方法给与原始数据的均值(mean)和标准差(standard deviation)进行数据的标准化。经过处理的数据符合标准正态分布,即均值为0,标准差为1.
  • 本质:把有量纲表达式变成无量纲表达式。
  • 转换函数:(X-Mean)/(Standard deviation)

其中,Mean为所有样本数据的均值。Standard deviation为所有样本数据的标准差。

初始化模型参数

目的:

  • 加快模型收敛
  • 抑制梯度消失与爆炸

经典初始化方法:

  1. Xavier
wU(a,a), 其中 again6fanin +fanout w \sim U(-a, a) \text {, 其中 } a-\operatorname{gain} * \sqrt{\frac{6}{f a n_{\text {in }}+f a n_{\text {out }}}}

目的:使得网络中信息更好的流动,每一层输出的方差应该尽量相等。

实现:

from torch import nn
nn.init.xavier_uniform_(model.weight,gain=nn.init.calculate_gain('tahn'))
  1. Kaiming/He
wU( bound , bound ), 其中 bound =6(1+a2))+faninw \sim U(-\text { bound }, \text { bound }) \text {, 其中 bound }=\sqrt{\frac{6}{\left.\left(1+a^{2}\right)\right)+f a n_{i n}}}
  1. 前向传播的时候, 每一层的卷积计算结果的方差为1.
  2. 反向传播的时候, 每一 层的继续往前传的梯度方差为1(因为每层会有两个梯度的计算, 一个用来更新当前层的权重, 一个继续传播, 用于前面层的梯度的计算.)

实现:

nn.init.kaiming_uniform_(model.weight,a=1,mode= 'fan_in',nonlinearity='leaky_relu')

激活函数

image.png 作用:赋予神经网络非线性能力,以便使网络更加强大,增加它的能力,使它可以学习复杂的事物,复杂的表单数据,以及表示输入输出之间非线性的复杂的任意函数映射。

原因:

如果不用激活函数,每一层输出都是上层输入的线性函数,无论神经网络有多少层,输出都是输入的线性组合,这种情况就是最原始的感知机(Perceptron)。没有激活函数的每层都相当于矩阵相乘。就算你叠加了若干层之后,无非还是个矩阵相乘罢了。

如果使用的话,激活函数给神经元引入了非线性因素,使得神经网络可以任意逼近任何非线性函数,这样神经网络就可以应用到众多的非线性模型中。

常见激活函数:

  1. sigmoid 函数

优点:

  • Sigmoid函数的输出映射在(0,1)之间,单调连续,输出范围有限,优化稳定,可以用作输出层。
  • 求导容易。

缺点:

  • 由于其软饱和性,容易产生梯度消失,导致训练出现问题。
  • 其输出并不是以0为中心的。
f(x)=11+exf(x)=\frac{1}{1+e^{-x}}

image.png

  1. tanh 函数

优点:

  • 比Sigmoid函数收敛速度更快。
  • 相比Sigmoid函数,其输出以0为中心。

缺点:

  • 还是没有改变Sigmoid函数的最大问题——由于饱和性产生的梯度消失。
tanh(x)=1e2x1+e2x\tanh (x)=\frac{1-e^{-2 x}}{1+e^{-2 x}}

image.png

  1. ReLU

优点:

  • 相比起Sigmoid和tanh,ReLU在SGD中能够快速收敛。
  • Sigmoid和tanh涉及了很多很expensive的操作(比如指数),ReLU可以更加简单的实现。
  • 有效缓解了梯度消失的问题。
  • 在没有无监督预训练的时候也能有较好的表现。
  • 提供了神经网络的稀疏表达能力。

缺点:

  • 随着训练的进行,可能会出现神经元死亡,权重无法更新的情况。如果发生这种情况,那么流经神经元的梯度从这一点开始将永远是0。也就是说,ReLU神经元在训练中不可逆地死亡了。
y={0(x0)x(x>0)y=\left\{\begin{array}{ll} 0 & (x \leq 0) \\ x & (x>0) \end{array}\right.

image.png

  1. Mish

经过ReLUSwish、Mish个不同激活函数后的输出对比,从中可以发现Mish相对于ReLUSwish显得更加平滑一些。

image.png

f(x)=xtanh(softplus(x))f ( x ) = x ∗ t a n h ( s o f t p l u s ( x ) )

image.png

优化器

θ=θαJ(θ)θ\theta \leftarrow=\theta-\alpha * \frac{\partial J(\theta)}{\partial \theta}

参考:www.cnblogs.com/guoyaohua/p…

  1. BGD(Batch Gradient Descent )
θ=θηθJ(θ)\theta=\theta-\eta \cdot \nabla_{\theta} J(\theta)

缺点:由于这种方法是在一次更新中,就对整个数据集计算梯度,所以计算起来非常慢,遇到很大量的数据集也会非常棘手,而且不能投入新数据实时更新模型。

Batch gradient descent 对于凸函数可以收敛到全局极小值,对于非凸函数可以收敛到局部极小值。

  1. SGD(Stochastic Gradient Descent )
θ=θηθJ(θ;x(i);y(i))\theta=\theta-\eta \cdot \nabla_{\theta} J\left(\theta ; x^{(i)} ; y^{(i)}\right)

**缺点:**SGD 因为更新比较频繁,会造成 cost function 有严重的震荡。

BGD 可以收敛到局部极小值,当然 SGD 的震荡可能会跳到更好的局部极小值处。

当我们稍微减小 learning rate,SGD 和 BGD 的收敛性是一样的。

  1. Nesterov Accelerated Gradient
vt=γvt1+ηθJ(θγvt1).θ=θvt\begin{array}{l} v_{t}=\gamma v_{t-1}+\eta \nabla_{\theta} J\left(\theta-\gamma v_{t-1}\right) . \\ \theta=\theta-v_{t} \end{array}

超参数设定值: 一般 γ 仍取值 0.9 左右。

image.png

NAG 会先在前一步的累积梯度上(brown vector)有一个大的跳跃,然后衡量一下梯度做一下修正(red vector),这种预期的更新可以避免我们走的太快。

NAG 可以使 RNN 在很多任务上有更好的表现。

  1. Adam
θt+1=θtηv^t+ϵm^t.\theta_{t+1}=\theta_{t}-\frac{\eta}{\sqrt{\hat{v}_{t}}+\epsilon} \hat{m}_{t} .

超参数设定值: 建议 β1 = 0.9,β2 = 0.999,ϵ = 10e−8

Normalization

相关使用:

""" NHWC
N: batch size : 8
H: height: 224,
W: width: 224,
C: channel: 16
 
NCHW, NHWC
"""
 
""" batch norm
在N 求E(x), Var(x)
适用于大的 batch size
不太适用于变长数据:text, speech
"""
bn_norm = nn.BatchNorm2d(num_features=16) # input shape: NCHW
norm_out = bn_norm(output_from_pre_layer.permute(0, 3, 1, 2)) # NHWC -> NCHW
print('norm from bn', norm_out.shape)
 
""" layer normalization
在[224,224,16]求E(x), Var(x)
对batch size 不敏感, 适用sequence data(序列变长的数据): RNN/Transformer
"""
ln_norm = nn.LayerNorm([224, 224, 16]) # input shape: [N, *]
norm_out = ln_norm(output_from_pre_layer)
print('norm from ln', norm_out.shape)
 
""" instance normalization
在channel 这个维度上求的E(x), Var(x)
适用GAN(生成式神经网络)
"""
in_norm = nn.InstanceNorm2d(16) # input shape: NCHW
norm_out = in_norm(output_from_pre_layer.permute(0, 3, 1, 2))
print('norm from in', norm_out.shape)
 
""" group normalization
在分组后的group上求的E(x), Var(x): [224, 224, group_number, 16/group_number]
group number: 需要精心设置
"""
gn_norm = nn.GroupNorm(num_groups=4, num_channels=16) # input shape: (N, C, *)
norm_out = gn_norm(output_from_pre_layer.permute(0, 3, 1, 2))
print('norm from gn', norm_out.shape)
 
final_output = F.relu(norm_out)

提升模型表现

参考:blog.csdn.net/m0_37347812…

我们在训练模型时候,会出现下面三种情况:

image.png

左-右:欠拟合 正常 过拟合

常见的是过拟合情况,解决过拟合的方案如下:

  1. 增加训练数据

这是解决过拟合现象的根本办法,若没有过多的训练数据,我们可以通过数据增广方式来在增加数据的数量,从而让模型的泛化能力增强。

  1. 控制模型复杂度

过于复杂的模型容易造成过拟合现象。对于模型的设计而言,我们应该选择简单、合适的模型解决复杂的问题。

  1. L1/L2 正则化

L1 正则化:

C=C0+λniwiC=C_{0}+\frac{\lambda}{n} \sum_{i}\left|w_{i}\right|

L2 正则化:

C=C0+λ2nwi2C=C_{0}+\frac{\lambda}{2 n} \cdot \sum w_{i}^{2}
  1. dropout 机制

Dropout 指的是在训练过程中每次按一定的概率(比如50%)随机地“删除”(将该部分神经元的激活函数设为0(激活函数的输出为0),让这些神经元不计算而已。)一部分隐藏单元(神经元)。

Dropout 为什么有助于防止过拟合呢?

  • 在训练过程中会产生不同的训练模型,不同的训练模型也会产生不同的的计算结果。随着训练的不断进行,计算结果会在一个范围内波动,但是均值却不会有很大变化,因此可以把最终的训练结果看作是不同模型的平均输出。
  • 它消除或者减弱了神经元节点间的联合,降低了网络对单个神经元的依赖,从而增强了泛化能力。
  1. Early stopping (早停)

为了获得性能良好的神经网络,训练过程中可能会经过很多次 epoch(遍历整个数据集的次数,一次为一个epoch)。

如果epoch数量太少,网络有可能发生欠拟合;

如果epoch数量太多,则有可能发生过拟合。

Early stopping旨在解决epoch数量需要手动设置的问题。具体做法:每个epoch(或每N个epoch)结束后,在验证集上获取测试结果,随着epoch的增加,如果在验证集上发现测试误差上升,则停止训练,将停止之后的权重作为网络的最终参数。