3. 深度学习基础

308 阅读14分钟

1. 过拟合、欠拟合

1.1 欠拟合

1.欠拟合是指模型在训练集、验证集和测试集上均表现不佳的情况。

2.欠拟合的原因:

  • 模型复杂度过低
  • 特征量少:特征太少了,机器没学到什么东西,拟合不好。

3.解决欠拟合的方法:

  • 增加模型的复杂度
  • 增加特征量

1.2 过拟合

1.过拟合是指模型在训练集上表现很好,到了验证和测试阶段就很差,即模型的泛化能力很差。

2.过拟合的原因:

  • 样本数量太少:数据少特征多,那么这个特征就具有特殊性,机器难以学到数据的普遍规律。
  • 模型复杂度过高
  • 样本噪声干扰

3.解决过拟合的方法:

  • 增加数据量
  • 减少特征数量
  • 降低模型复杂度
  • 正则化:正则化为什么可以避免过拟合?当网络过拟合的时候,拟合函数波动较大,震荡的很剧烈,即拟合函数的系数往往很大(准确来说是绝对值很大),那么系数WW即网络的参数。正则化就是通过约束参数,使其不要太大(使拟合函数波动减小),达到减少过拟合。
  • BN层:BN层使一个min-batch中的所有样本都关联在一起,使网络不会从某一个样本中生成确定的结果,即同样一个样本的输出不仅取决于样本本身还取决于跟这个样本同属于一个min-batch的其他样本,而且BN层是每次随机抽batch。

图片.png

  • 使用dropout:不过分去依赖某些特定的特征。
  • early stoping:如果模型训练的效果不再提高,比如训练误差一直在降低但是验证误差却不再降低甚至上升,这时候便可以结束模型训练了。

2. batchsize、batch、iteration、epoch的关系

训练数据集有50000张图片,batchsize=500,那么batch=50000/500=100个,iteration=batch=100次。

1个epoch会训练所有的图片,只不过将这些图片分批次训练,分成100个批次(即100次迭代),每次迭代训练500张图片,每次迭代进行一次权重更新(反向传播)共进行了100次权重更新。

3. 反向传播

训练模型的一般流程:

注意我常常搞混optimizer.zero_grad()loss.backward()的梯度清零:当反向传播时,计算出梯度为原始的梯度,但是这个梯度不能直接用于更新参数,需要先给优化器进行优化,于是优化器得到优化后的梯度。这两个原始梯度和优化梯度都需要再新一轮batch进行清零,才能更新的。

#每个epoch中,将数据分成多个batch,每个batch进行一次权重更新,如下操作:

#1. 清空优化器中的梯度

optimizer.zero_grad() 
#当我们通过反向传播计算梯度时,这些梯度会被累加到之前的梯度上,
#而不是覆盖之前的梯度。所以在每次更新参数前,我们需要清空梯度缓存。

#2. 正向传播计算损失

outputs = model(images)
loss = criterion(outputs, labels) 

#3. 反向传播计算梯度,更新参数
    
loss.backward()# 计算梯度
#将当前计算图中所有需要求梯度的Tensor的梯度清零,
#然后对损失函数进行反向传播,计算所有需要求梯度的Tensor的梯度。

optimizer.step()# 更新模型参数

补充模型中的forward函数:过去一直不知道前向传播调怎么调用的forward函数,在学了一些源码之后,才算是有些体会。

import torch.nn as nn
#模型Venzhy,继承自模型nn.Module
class Venzhy(nn.Module):
    def __init__(self,age):
        super().__init__()
        self.age=age

    def __call__(self, *args, **kwargs):
        my_age=self.age+1
        return my_age

v=Venzhy(23) #创建对象,调用__init__()
my_age=v() #实际上:v.__call__()
print(my_age) #24
import torch.nn as nn
class Venzhy2(nn.Module):
    def __init__(self,age):
        super().__init__()
        self.age=age

    def forward(self):
        my_age=self.age+1
        return my_age

v2=Venzhy2(17) #创建对象,调用__init__()
my_age2=v2() 
#实际上v2调用了父类函数 __call__(),而父类中 __call__()函数中调用了self.forward()
#由于子类中有forward()肯定就是调用子类v2.forward()
print(my_age2)#18

1.定义:反向传播(Backpropagation)是误差反向传播的缩写,是一种与最优化方法(例如咱下面的优化器)结合使用的,用来训练神经网络的常用方法。该方法对所有权重计算损失函数的梯度,并反馈给最优化方法,用来更新权重以最小化损失函数。

2.本人用手推导了一下,绘制如下图:

正向传播:

1.jpg

反向传播:

更新W5W5:

2.jpg

更新W1W1:

3.jpg

权重衰减是什么?

权重更新过程中,我们增加了一些惩罚项,用来惩罚权重,让权重不至于取值太大,例如L2正则化,就是为了惩罚权重的方式,即权重衰减。

QQ截图20230508191553.png

这样做的目的是为了防止过拟合。

4. 优化器

每次反向传播都会给各个可学习参数WW计算出一个偏导gtg_t,用于更新该参数。通常偏导不会直接作用到可学习参数WW上,而是通过优化器做一下处理,得到一个新的值g^t\hat{g}_t,处理的过程为函数FF,即g^t=F(gt)\hat{g}_t=F(g_t),然后和学习率lrlr(学习率就是步长)一起更新可学习参数WW,即Wt=Wt1g^tlrW_{t}=W_{t-1}-\hat{g}_t*lr

4.1 SGD

SGD随机梯度下降参数更新原则:单条数据就可对参数进行一次更新。每个epoch参数更新M(样本批数)次,这里的随机是指每次选取哪个样本是随机的,每个epoch样本更新的顺序是随机的。

优点:参数更新速度快。

缺点:由于每次参数更新时采用的数据量很小,造成梯度更新时震荡幅度大,容易受到异常值的影响,在最优解附近会有加大波动,但大多数情况都是向着梯度减小的方向。

4.2 momentum

SGD每次都会在当前位置上沿着负梯度方向更新,并不考虑之前的梯度方向、大小等等。而动量(momentum)通过引入一个新的变量vv去积累之前的梯度(通过指数衰减平均得到),在一定程度上减小权重优化过程中的震荡问题

Momentum的优化函数的权重更新公式如下:

vt=βvt1+(1β)gtv_t=\beta v_{t-1}+(1- \beta )g_t

Wt=Wt1lrvtW_t=W_{t-1}-lr*v_t

动量参数vv本质上就是到目前为止所有历史梯度值的加权平均,距离越远,权重越小。

特点:加快收敛减小震荡。

自适应学习率的优化算法包括AdaGrad、RMSProp和Adam:

4.3 Adagrad

我们在每一次更新参数时,对于所有的参数使用相同的学习率。而AdaGrad算法的思想是:每一次更新参数时(一次迭代),不同的参数使用不同的学习率。

Gt=Gt1+gt2G_t=G_{t−1}+g_t^2

Wt=Wt1lrGt+εgtW_t=W_{t-1}−\frac{lr }{\sqrt{G_t }+\varepsilon} g_t

优点:对于梯度较大的参数(GtG_t会很大)采用较小的学习率更新(lrGt+ε\frac{lr}{\sqrt{G_t }+ \varepsilon }会很小),对于梯度较小的参数采用较大的学习率更新,这样就可以使得参数在平缓的地方下降的稍微快些,不至于徘徊不前。

缺点: 由于是累积梯度的平方,到后面GtG_t累积的比较大,会导致梯度 lrGt+ε0\frac{lr}{\sqrt{G_t }+ \varepsilon} \rightarrow0,导致梯度消失。

4.4 RMSProp

RMSProp算法通过修改AdaGrad得来,其目的是针对梯度平方和累计越来越大的问题会导致梯度消失的问题。

vt=βvt1+(1β)gt2v_{t}=β v_{t-1}+(1−β)g_t^2

Wt=Wt1lrvt+εgtW_t=W_{t-1}−\frac{lr }{\sqrt{v_{t}}+\varepsilon} g_t

特点:能够克服AdaGrad梯度急剧减小的问题,在很多应用中都展示出优秀的学习率自适应能力。尤其在不稳定(Non-Stationary)的目标函数下,比基本的SGD、Momentum、AdaGrad表现更良好。

4.5 Adam

Adam实际上是把momentum和RMSprop结合起来的一种算法,既能适应稀疏梯度,又能缓解梯度震荡的问题。

图片.png

公式1:β1\beta_1系数为指数衰减率,控制动量和当前梯度的权重分配。计算 历史梯度的一阶指数平滑值,用于得到带有动量的梯度值

公式2:β2\beta_2系数为指数衰减率,控制之前的梯度平方的影响情况。计算 历史梯度平方的一阶指数平滑值,用于得到每个权重参数的学习率权重参数

公式3:计算变量更新权重WW值。

特点:

1.既能适应稀疏梯度,又能缓解梯度震荡的问题。

2.为不同的参数设置不同的自适应学习率。

3.实现简单,对内存需求少。

4.很适合应用于大规模数据及参数的场景。

5.适用于不稳定目标。

5. BN层

BN层放在激活函数之前。

1.BN层出现的背景:

梯度消失:

随着网络深度的加深,每一层的参数更新会导致上层的输入数据分布发生变化。

→输入数据分布一旦发生变化有可能会导致整体输入分布往非线性函数取值区间靠近。(你可以想一想反向传播公式的分子有激活函数相乘的,如果是极端分布的输入(输入极大或极小),Sigmoid激活函数可能取值趋于0,那么梯度就会消失啊)

→反向传播梯度消失,收敛速度变慢。

2.BN层的作用:

为了防止因输入数据分布不同导致反向传播发生梯度消失的问题,BN层从输入解决问题:BN层使深度神经网络训练过程中每一层神经网络输入保持相同的分布。

这样带来的效果就是:

→防止梯度消失和梯度爆炸。(数据同一分布了,不会出现过大过小的极端值)

→加速训练过程和收敛速度。(数据都是同一分布均值0方差1,训练快,收敛快)

→防止过拟合。(BN层使一个min-batch中的所有样本都关联在一起,使网络不会从某一个样本中生成确定的结果,即同样一个样本的输出不仅取决于样本本身还取决于跟这个样本同属于一个min-batch的其他样本,而且BN层是每次随机抽batch。)

3.前向传播时BN层的公式:

  • 计算min-batch均值和方差:m是min-batch的batchsize

μB=1mi=1mxi\mu_B=\frac{1}{m}\sum_{i=1}^mx_i

σB2=1mi=1m(xiμB)2\sigma^2_B=\frac{1}{m}\sum_{i=1}^m(x_i-\mu_B)^2

  • min-batch的数据进行标准化:

x^ixiμBσB2+ϵ\hat{x}_i\leftarrow\frac{x_i-\mu_B}{\sqrt{\sigma^2_B+\epsilon}}

  • 通过β\betaγ\gamma可以使数据进行移动和缩放,BN层的β\betaγ\gamma是可学习参数。由于进行归一化会打乱原输入学习到的分布,这一步相当于恢复出原输入学习到的分布。

yiγx^i+βy_i\leftarrow\gamma \hat{x}_i+\beta

4.训练和测试BN层的不同:

训练时,我们可以对每一批的训练数据进行归一化,计算每一批数据的均值和方差。

但在测试时,用的均值和方差是全量训练数据的均值和方差。

6. 梯度消失,梯度爆炸

关于梯度消失和梯度爆炸的原因,强烈建议推导一个简单的正向、反向传播过程:在进行正向传播 net=w1x1+w2x2net=w_1x_1+w_2x_2 时,由于输入或者初始化权重比较小,反向传播求梯度时容易梯度消失;由于输入或者初始化权重比较大,反向传播求梯度时容易梯度爆炸。这时候就可以在net后面,也就是激活函数前面,加一个BN来解决。

在将net送入激活函数11+enet\frac{1}{1+e^{net}}时,由于net比较小,反向传播求梯度时容易梯度消失。这时候就可以用换一个激活函数来解决。

1.梯度消失、梯度爆炸概念:

反向传播是一个链式法则,当层数变深时,梯度以指数形式传播,当梯度值接近0或者特别大时,就会发生梯度消失或者爆炸,影响收敛速度。

2.梯度消失、梯度爆炸原因:

梯度消失:层数较深、使用了Sigmoid激活函数。

  • 使用BN层。
  • 将Sigmoid激活函数替换成ReLU\Leaky ReLU激活函数。

梯度爆炸:层数较深、权重初始化比较大、输入的值较大。

  • 使用权重初始化:合适的权重初始化可以避免梯度爆炸的问题,例如使用Xavier或He初始化。
  • 使用BN层。
  • 减小学习率:减小学习率可以使梯度下降更加平缓,避免梯度爆炸的问题。

7. 激活函数有哪些

激活函数的作用是给神经网络加一些非线性因素,使得神经网络可以逼近任何非线性函数,从而使神经网络应用到更多非线性模型中。

7.1 Sigmoid

Sigmoid函数用在二分类中预测最后层输出概率。

梯度消失:由于当x较小时,Sigmoid输出趋于0,导数也会趋于0,反向传播会导致梯度消失,梯度一旦消失,那么参数就不能沿着loss降低的方向优化。

图片.png

7.2 Tanh

图片.png

7.3 ReLU

图片.png

7.4 Leaky ReLU

图片.png

7.5 PReLU

图片.png

8. CNN参数计算

8.1 卷积层输出

O=IK+2PS+1O=\frac{I-K+2P}{S}+1

OO:输出图像尺寸;II:输入图像尺寸;KK:卷积核尺寸;SS:移动步长;PP:填充数

图片.png

例:

输入图像尺寸为227×227×3;卷积层有96个11×11×3的卷积核;步长为4,填充数为0。计算卷积后的输出尺寸?

O=IK+2PS+1=22711+204+1=55O=\frac{I-K+2P}{S}+1=\frac{227-11+2*0}{4}+1=55

卷积后的输出尺寸为:55×55×96

8.2 池化层的输出

池化不是一个层,没有参数,池化只是一个操作。

O=IKS+1O=\frac{I-K}{S}+1

OO:输出图像尺寸;II:输入图像尺寸;KK:卷积核尺寸;SS:移动步长

最大池化:

图片.png

例:

输入尺寸为55×55×96;池化层尺寸为3×3;步长为2。计算池化后的输出尺寸?

O=IKS+1=5532+1=27O=\frac{I-K}{S}+1=\frac{55-3}{2}+1=27

池化后的输出尺寸为:27×27×96

8.3 全连接层的输出

经全连接后的输出向量长度等于全连接层的向量长度。

8.4 卷积层参数数量

假设考虑偏置。

W=(Cin(KK)+1)CoutW=(C_{in}*(K*K)+1)*C_{out}

CinC_{in}:输入通道数;CoutC_{out}:输出通道数;KK:卷积核的尺寸

例:

输入尺寸为32×32×3,输出尺寸为28×28×8,卷积核长宽5×5。计算卷积层的参数量?

W=(Cin(KK)+1)Cout=(3×(5×5)+1)×8=608W=(C_{in}*(K*K)+1)*C_{out}=(3×(5×5)+1)×8=608

8.5 全连接层的参数数量

假设考虑偏执。

W=M×N+NW=M×N+N

输入在全连接之前会进行flattern的操作,于是将变成(1,M)的向量,若全连接为(M,N)的矩阵向量,那么输出为(N,1)。若考虑偏执参数,偏执为N个参数。

例: 最后一层卷积层的输出为:7×7×512,经falttern后为:1×25088;接着是全连接层;全连接后下一层的输出为:4096×1。那么全连接层的参数量为?

W=M×N+N=25088×4096+4096=10276544W=M×N+N=25088×4096+4096=10276544

8.6 MobileNet

MobileNet v1

亮点一:DW卷积

1.传统卷积:

卷积核channel=输入特征矩阵channel

输出特征矩阵channel=卷积核个数

图片.png

2.DW卷积(Depthwise Conv):

卷积核channel=1

输入特征矩阵channel=卷积核的个数=输出特征矩阵channel

图片.png

3.PW卷积(Pointwise Conv):

是普通卷积,只不过卷积核大小为1×1。

图片.png

4.计算量:(DW卷积+PW卷积)/普通卷积

下图计算可知,(DW卷积+PW卷积)的计算量是普通卷积的计算量的9倍。

图片.png

注:默认上述的卷积步距s=1

亮点二:两个超参数

第一个是卷积核个数的倍率。第二个是图片分辨率。

MobileNet v2

亮点一:Inverted Residuals(倒残差结构)

图片.png

亮点二:Linear Boottlenecks(线性瓶颈结构)

将ReLU函数替换为线性函数。

8.7 分组卷积

IMG_20230324_213854.jpg

原来卷积核参数量:3×3×3c×6

分组卷积核参数量:3×3×c×6 要记住,去想他的操作过程!

将输入特征图 channel 分为 group=3 组,每组输入通道数变为 3c/group=3c/3=c 深度,每个卷积核因此通道数也变成 c 深度。

降低了 1/group=1/3 倍参数量。

注:分组卷积等价于,分别将每一组输入特征图和相应卷积核拿出来进行卷积,最后结果按通道拼接起