1.批量归一化
什么是批量归一化?
批量归一化(Batch Normalization),通常缩写为BN,是一种在训练深度神经网络时用于提高训练速度、稳定性和性能的技术。它通过规范化(归一化)层的输入来减少内部协变量偏移,从而使得网络的每一层都输入到一个相对稳定的环境中。批量归一化的核心思想是通过对神经网络中的输入进行归一化处理,从而稳定这些输入的分布,减少训练过程中输入分布的变化。
这种方法不仅有助于加速训练,还能够提高模型的泛化能力和稳定性。通过引入可学习的缩放和平移参数,批量归一化保持了网络的表达能力,允许模型学习到有用的特征表示。
为什么需要批量归一化?
在训练过程中,每层输入的分布不断的变化,这使得下一层需要不断的去适应新的数据分布,在深度神经网络中,这让训练变得非常复杂而且缓慢。对于这样,往往需要设置更小的学习率、更严格的参数初始化。通过使用批量归一化(Batch Normalization, BN),在模型的训练过程中利用小批量的均值和方差调整神经网络中间的输出,从而使得各层之间的输出都符合均值、方差相同高斯分布,这样的话会使得数据更加稳定,无论隐藏层的参数如何变化,可以确定的是前一层网络输出数据的均值、方差是已知的、固定的,这样就解决了数据分布不断改变带来的训练缓慢、小学习率等问题。
特征归一化
计算公式如上图。其中,代表的第一个元素,代表的第一个元素,以此类推。
BN源码实现
m = K.mean(X, axis=-1, keepdims=True)#计算均值
std = K.std(X, axis=-1, keepdims=True)#计算标准差
X_normed = (X - m) / (std + self.epsilon)#归一化
out = self.gamma * X_normed + self.beta#重构变换
在卷积神经网络中卷积层和全连接层都可以使用批量归一化。
1.1 考虑深度学习
深度学习的归一化如下图:
在这里,代表归一化的特征,把它丢到深度网络中,去做接下来的计算和训练。通过第一层得到 ,有可能通过激活函数,不管是选 sigmoid 或者 ReLU 都可以,再得到 ,接着再通过下一层等等。对每个都做类似的事情。
虽然 已经做归一化了,但是通过 以后,没有做归一化。如果 通过 得到,而不同的维度间,它的数值的分布仍然有很大的差异,训练,第二层的参数也会有困难。对于 ,或其实也是一种特征,也应该要对这些特征做归一化。如果选择 sigmoid比较推荐对做特征归一化,因为sigmoid是一个的形状,其在0附近斜率比较大,如果对做特征归一化,把所有的值都挪到0附近,到时候算梯度的时候,算出来的值会比较大。如果使用别的激活函数,可能对归一化也会有好的结果。一般而言,特征归一化,要放在激活函数之前,之后都是可以的,在实现上,没有太大的差别。
下面对进行归一化,计算公式如下图:
归一化过程如下图:
但是!存在这么个问题:如上图所示,接下来可以通过激活函数得到其他向量,跟 都是根据 ,,计算出来的。改变了 的值,的值也会改变,和也会改变。,改后,,,,的值也会改变。之前的,,是独立分开处理的,但是在做特征归一化以后,这三个样本变得彼此关联了。所以有做特征归一化的时候,可以把整个过程当做是网络的一部分。即有一个比较大的网络,该网络吃一堆输入,用这堆输入在这个网络里面计算出,,接下来产生一堆输出。这边就会有一个问题了,因为训练数据非常多,现在一个数据集可能有上百万笔数据,GPU 的显存无法把它整个数据集的数据都加载进去。因此,在实现的时候,我们不会让这一个网络考虑整个训练数据里面的所有样本,而是只会考虑一个批量里面的样本。比如批量设 64,这个网络就是把 64笔数据读进去,计算这 64 笔数据的 ,,对这 64笔数据做归一化。因为实际实现的时候,只对一个批量里面的数据做归一化,所以技巧称为批量归一化。一定要有一个够大的批量,才算得出,。所以批量归一化适用于批量大小比较大的时候,批量大小如果比较大,也许这个批量大小里面的数据就足以表示整个数据集的分布。这个时候就不需要对整个数据集做特征归一化,而改成只在一个批量上做特征归一化作为近似。
在做批量归一化的时候,如下图所示,往往还会做如下操作:
其中,代表逐元素的相乘。, 可以想成是网络的参数,需要另外再被学习出来。
还要对这个归一化的数值进行伸缩(Scale)和偏移(Shift)。是一个伸缩系数,可以对数据扩大或缩小,它和的维度相同,表示中的每个元素与归一化之后的按元素(Element-wise)做乘法。是一个偏移系数,它和的维度相同,按元素做加法。和是两个需要学习的参数。对于一个输入,经过后,得到的数据符合均值为,方差为的分布。
一个疑问是,明明是为了进行归一化,我们得到了均值为0,方差为单位误差的分布了,为什么又在归一化的基础上加了伸缩和平移?怎么又变回去了?答案是:为了保障模型的表达能力不因为归一化而下降。直观上来讲,神经网络某一层的在努力地学习,如果只做,那么这一层的输出会被粗暴地限制到某个固定的范围,后续层输入都是这样一个分布的数据。有了伸缩和平移,而且这两个参数是需要学习的参数,那么这一层的输出就不再被粗暴归一化的了。
1.2 测试时的批量归一化
以上都为训练部分,在测试部分,或者说预测部分使用批量归一化训练时,可以将批量大小设得大一点,从而使批量内样本的均值和方差的计算都较为准确。将训练好的模型用于预测时,我们希望模型对于任意输入都有确定的输出。因此,单个样本的输出不应取决于批量归一化所需要的随机小批量中的均值和方差。一种常用的方法是通过移动平均估算整个训练数据集的样本均值和方差,并在预测时使用它们得到确定的输出。可见,批量归一化层在训练模式和预测模式下的计算结果也是不一样的。
测试阶段
- 使用全局均值和方差:在测试阶段,不再计算每个小批量的均值和方差,而是使用训练阶段计算并保存的全局均值和方差进行归一化。这些全局统计量是对整个训练数据集的估计,能够更好地反映数据的整体特性。
- 固定缩放和偏移参数:缩放因子 和偏移量 在测试阶段不再更新,它们保持在训练结束时的值。
- 推理模式:在PyTorch中,需要将模型设置为评估模式(使用
model.eval()
),这样批量归一化层就会知道模型正在测试阶段,并使用全局均值和方差进行归一化。
2.卷积神经网络
2.1 结构介绍
(图片来源:https://www.bilibili.com/video/BV1AJ411Q72b/?vd_source=080c58ed744f7682e4eb0e56a511ad78【子豪兄】深度学习之卷积神经网络)
上图为卷积神经网络的基本架构,简单来说,卷积神经网络分为卷积层、降or下采样层(池化层),再来一层卷积层,再来一层池化层,接着全连接层将之前卷积层和池化层得到的图像的底层的特征和信息进行汇总,最后进行输出。
卷积层用来提取图像的底层特征;
池化层用来防止过拟合,并且减少数据维度;
全连接层用来汇总之前卷积层和池化层得到的图像的底层的特征和信息,最后进行输出。
输出可以是softmax(相当于归一化)输出,比如:dog(0.01),cat(0.04),boat(0.94),bird(0.02);也可以是sigmod输出(二分类输出),比如:是or不是。例如下图。
网络的输入往往是向量,因此,将代表图像的三维张量“丢”到网络里之前,需要先将它“拉直”。在这个例子里面,张量有100x100x3个数字,所以一张图像是由100x100x3个数字所组成的,把这些数字排成一排就是一个巨大的向量。这个向量可以作为网络的输入,而这个向量里面每一维里面存的数值是某一个像素在某一个通道下的颜色强度。
如果把向量当做全连接网络的输入,输入的特征向量(feature vector)的长度就是 100 × 100 × 3。这是一个非常长的向量。由于每个神经元跟输入的向量中的每个数值都需要一个权重,所以当输入的向量长度是 100 × 100 × 3,且第 1 层有 1000 个神经元时,第 1 层的权重就需要 1000×100×100×3 = 3 ×107 个权重,这是一个非常巨大的数目。更多的参数为模型带来了更好的弹性和更强的能力,但也增加了过拟合的风险。模型的弹性越大,就越容易过拟合。为了避免过拟合,在做图像识别的时候,考虑到图像本身的特性,并不一定需要全连接,即不需要每个神经元跟输入的每个维度都有一个权重。接下来就是针对图像识别这个任务,对图像本身特性进行一些观察。
下面主要介绍一下结构:
卷积层
那么卷积是如何计算的呢?
以下图为例:
当其他的滑动的时候也是这样计算的,比如当滑动到下一个时,output=(-1)×0+0×2+1×3+(-1)×4+0×2+1×0+(-1)×4+0×5+1×6=1,以此类推。
那么输出图片的大小是怎么计算的呢?
计算公式:
输入图片矩阵I大小: w×w
卷积核K : k×k
步长S : s(就是每次滑动的长度)
填充大小(padding): p
输出图片大小为:o×o
根据上图可知:输入图片矩阵为5×5,卷积核为3×3,步长为1,这里我们没有填充补0,因此p=0,因此计算可得o=3,因此,经过卷积层输出的图片大小为3×3。
那么如果有padding呢?
首先先讲一下为何要加padding,对于一张图片,就以上面的那个图为例,第一个数字2,经过卷积核计算只有1次,而对于中间的数字会被计算很多次,这样边缘信息可能就会被忽略掉,因此,会进行填充,在周围一圈补0。
和上面的计算一样,只是在周围补了一圈0罢了。
也就是说卷积这个操作,把大的操作变成小的输出,把大的图变成小的图。
如果用一个卷积核生成一个输出,假设再来一个卷积核还会再来一个输出,有多少个卷积核就有多少个feature map,每一个卷积核都对应一个feature map,生成的很多个小feature map对原始的大原图进行压缩和特征提取就是卷积干的事情。
https://ezyang.github.io/convolution-visualizer/index.html(参考这个网站的图,这里图像是单通道的,卷积核是只有一个)
我们平常看到的图片是RGB三通道的。https://thomelane.github.io/convolutions/2DConvRGB.html
这里呢就是红色的权重和原图红色的通道的像素进行卷积;绿色的权重和原图绿色的通道的像素进行卷积;蓝色的权重和原图蓝色的通道的像素进行卷积然后加在一起作为一个feature map。
池化层
池化层的作用:
- 减少参数量
- 防止过拟合
为卷积神经网络带来平移不变性
以下图来解析池化层的作用:
这里有四个大框,分别在大框里面只选一个值作为大框里面的代表值,可以选择大框里面的平均值(平均池化),也可以选择大框里的最大值(最大池化)。减少参数,防止过拟合。如下图所示:
我们平时用的最多的是最大池化,因为往往图片上的一些锐利的尖角,一些高亮的区域都是用最大池化进行捕捉的。
对于池化层的计算涉及以下参数:
kernel_size:最大的窗口大小
stride:窗口的步幅,默认值为kernel_size
padding:填充值,默认为0
dilation:控制窗口中元素步幅的参数
-o:输出大小
-w:输入大小
-k:卷积核大小
-p:填充值的大小
-s:步长大小,stride:窗口的步幅,默认值为kernel_size ,因此这里计算为2
-d: dilation:控制窗口中元素步幅的参数,默认为1
import torch
import torch.nn as nn
m0 = nn.MaxPool2d(2)
input_x = torch.randn(64,30,30)
print("输入数据input_x.shape:",input_x.shape)
output_x = m0(input_x)
print("输出数据output_x.shape:",output_x.shape)
可以通过以上代码验证一下。
全连接层
全连接层(fully connected layers,FC)是神经网络的一种基本层类型,通常位于网络的最后几层,用于分类任务的输出层。全连接层的主要特点是每一个神经元与前一层的每一个神经元都相连接,这意味着每个输入都影响每个输出。在全连接层中,输入向量通过一个权重矩阵进行线性变换,然后加上一个偏置项,最后通过激活函数(如ReLU、Sigmoid、Tanh等)进行非线性变换。
在上述分类任务中,全连接层通常作为网络的最后一层,直接将全连接层的维度设为类别数量或通过Softmax函数输出每个类别的概率分布,从而实现对输入数据的分类。如果说卷积层、池化层和激活函数等操作是将原始数据映射到隐层特征空间的话,全连接层则起到将学到的“分布式特征表示”映射到样本标记空间的作用。
关于猫狗分类的一个代码案例可以参考:利用卷积神经网络实现猫狗识别 - Heywhale.com