一、 什么是卷积神经网络
CNN 是卷积神经网络(Convolutional Neural Network)的缩写,是一种常用于图像和视频处理的深度学习算法。CNN 的主要思想是通过卷积、池化等操作来提取数据的空间特征,从而实现对数据的自动分类和识别。**通常用于图像分类、目标检测、语义分割等。
CNN 的结构通常包括若干个卷积层、池化层和全连接层。其中卷积层是 CNN 最重要的部分,其主要作用是通过卷积操作提取数据的局部特征,从而捕获数据的空间信息。在卷积层中,通常使用一组滤波器(也称为卷积核)对输入数据进行卷积运算,得到一组输出特征图。同时,卷积层通常还会使用激活函数对输出进行非线性变换,增加网络的表达能力。
另外,CNN 还常常使用池化层来进一步减少数据的维度,并且提高网络对平移不变性的鲁棒性。池化层通常采用最大值池化或平均值池化的方式对输入数据进行降采样,从而得到更小的输出特征图。
最后,全连接层通常被用于将 CNN 输出的特征图转换为分类或回归结果。全连接层通常包括若干个全连接层和激活函数,用于将特征图中的每个元素映射到最终的输出类别或数值。
常见且需要掌握的卷积神经网络有:VGGNet、 GoogleNet、 ResNet、SENet、MobileNet、 ShuffleNet
二、神经网络的主要层次
主要分为:数据输入层(Input Layer)、卷积计算层(CONV Layer)、ReLU激励层(ReLU Incentive Layer)、池化层(Pooling Layer)、全连接层(FC Layer)、Batch Normalization Layer(可能有)
2.1 数据输入层(Input Layer)
2.1.1 为什么需要对输入的数据需要进行预处理操作?
- 输入数据单位不一样, 可能会导致神经网络收敛速度慢, 训练时间长
- 数据范围大的输入在模式分类中的作用可能偏大, 而数据范围小的作用就有可能偏 小
- 由于神经网络中存在的激活函数是有值域限制的, 因此需要将网络训练的目标数据 映射到激活函数的值域
- S形激活函数在(-4,4)区间以外区域很平缓, 区分度太小
2.1.2 常见的数据预处理方式
- 去均值:将输入数据的各个维度中心化到0
- 标准化:将输入数据的各个维度的幅度标准化到同样的范围
- PCA、白化
2.2 卷积计算层(CONV Layer)
2.2.1 卷积层的理解和过程
- 在卷积计算层中,通常会定义多个卷积核(也称为滤波器),每个卷积核都是一个小的二维矩阵。这些卷积核与输入数据进行逐元素相乘和求和操作,从而得到输出特征图。
- 将卷积核与输入数据进行逐元素相乘并求和,得到一个单独的数值;将卷积核在输入数据上按一定的步幅(stride)和填充(padding)进行滑动,重复步骤1,得到另一个数值;不断重复上述操作,直到覆盖完整个输入数据,得到输出特征图。
- 一组固定的权重和窗口内数据做矩阵内积后求和的过程叫做卷积
2.2.2 卷积层的特点
- 局部感知:卷积通过卷积核进行局部区域的特征提取**(本质就是一个局部的线性转换)**
- 参数共享:一个图像/Feature Map的不同区域采用相同的卷积核进行特征提取;不同的Feature Map/通道之间参数默认不共享的
2.3 激励层(Activation Layer)
2.3.1 什么是激活层?
神经网络中的激活层是指在神经网络的每个神经元输出后,通过一个非线性函数将其激活成非零值的层。**在深度学习中,常见的激活函数有sigmoid、ReLU、tanh等。
激活层的作用是增加神经网络的非线性特性,使得神经网络可以更好地逼近复杂的非线性函数,并提高模型的表达能力。通过引入激活函数,神经网络可以学习到更加复杂的特征表示,从而提高模型的性能。
2.3.2 常见的激活函数?
-
Sigmoid函数(Logistic函数):将输入值映射到0到1之间,简单容易理解,常用于二分类任务和输出概率值的场景。缺点:容易饱和和终止梯度传递(死神经元),SIGMOD函数的输出没有0中心化
-
双曲正切函数(Tanh函数):将输入值映射到-1到1之间,常用于多分类任务,易理解,0中心化。缺点:容易饱和和终止梯度传递(死神经元)
-
ReLU函数(整流线性单元):在输入值大于0时直接输出输入值,小于等于0时输出0。相对于Sigmoid函数和Tanh函数提升了收敛速度,并且不会产生梯度消失和梯度爆炸。能够有效缓解梯度消失问题。缺点:没有边界,比较脆弱,容易陷入死神经元的情况(解决方案是设置较小的学习率)
梯度爆炸通常指的是,在反向传播过程中,梯度值变得非常大,甚至超过了计算机可以表示的范围,导致数值溢出的情况。这种情况下,由于参数更新量过大,模型可能无法正确地收敛,甚至完全无法训练
-
Leaky ReLU函数:与ReLU函数类似,但在输入值小于零时引入了一个小的线性斜率,以解决ReLU函数中负数区域的神经元“死亡”问题,但是实际场景效果不佳
-
参数化ReLU(PReLU函数):是对Leaky ReLU的扩展,其中斜率参数可以通过学习得到
2.3.3 激励层的建议
- CNN尽量不要使用sigmoid, 如果要使用, 建议只在全连接层使用
- 首先使用ReLU, 因为迭代速度快, 但是有可能效果不佳
- 如果使用ReLU失效的情况下, 考虑使用Leaky ReLu或者Maxout, 此时一般情况都可以解决啦
- tanh激活函数在某些情况下有比较好的效果, 但是应用场景比较少
2.4 池化层(Pooling Layer)
2.4.1 池化层的理解
池化层是深度学习神经网络中常用的一种层级结构,通常用于减少特征图的尺寸、提取主要特征以及减少计算量。池化层主要有两种类型:最大池化(Max Pooling)和平均池化(Average Pooling)。
在最大池化中,对于每个池化窗口,池化层会选择窗口内的最大值作为输出;而在平均池化中,池化层会选择窗口内的平均值作为输出。这两种池化方法都能够有效地减少特征图的尺寸,并且在一定程度上保留主要特征。
2.4.2 池化层的作用
- 尺寸缩减:通过池化操作,可以减小特征图的尺寸,有助于减少后续网络层的参数数量和计算复杂度,从而加速模型训练和推理过程。
- 特征提取:池化操作可以帮助网络集中关注主要特征,提高网络对目标的抽象能力,使得网络对位置变化具有一定的鲁棒性
- 参数减少:通过减小特征图的尺寸,池化操作可以降低后续网络层的参数数量,有助于减轻过拟合问题
2.5 全连接层(FC Layer)
2.5.1 全连接层的理解
全连接层(Fully Connected Layer),也称为密集连接层或者仿射层,是深度学习神经网络中最基本的层之一。在全连接层中,每个神经元与上一层的所有神经元相连,每个连接都有一个对应的权重,因此全连接层的参数数量较大。
全连接层通常出现在深度学习模型的尾部,用于整合前面卷积层或者其他特征提取层提取的特征,以便进行最终的分类或者预测任务。然而,由于全连接层的参数量较大,容易产生过拟合问题,并且在处理高分辨率图片时会导致参数量过大,计算量过高。
2.5.2 全连接层的作用
- 特征提取:全连接层通过学习输入特征之间的复杂非线性关系,可以提取高级抽象的特征,帮助网络更好地理解输入数据。
- 表征学习:通过权重的学习,全连接层能够将输入数据映射到一个更高维度的特征空间,从而帮助网络学习更复杂的表示。
- 分类和预测:全连接层通常是深度学习模型中用于分类、回归等任务的最后一层,它可以将高级抽象的特征映射到输出类别或者数值预测空间。
三、5种 Normalization Layer(归一化层)
3.1 Batch Normalization(论文)
3.1.1 什么是Batch Normalization?
Batch Normalization(批归一化)层是深度学习神经网络中常用的一种技术,用于在模型训练过程中加速收敛并提高模型的稳定性,并且减少了对超参数的敏感性。Batch Normalization的主要思想是对每个批次(batch)的输入进行归一化处理,使得输入数据的均值接近于0,方差接近于1。
Batch Normalization层通常被插入到卷积层或者全连接层之后,激活函数和池化层之前,而实际应用的时候有时候会放到激励层后。它可以通过对每个输入进行归一化、缩放和平移操作来实现。
3.1.2 Batch Normalization的主要作用
- 加速训练:通过将每个批次的输入进行归一化处理,可以减少网络中不同层之间的内部协变量偏移(Internal Covariate Shift)。这有助于网络更快地收敛和训练,减少梯度消失和爆炸问题
- 提高模型稳定性:Batch Normalization可以使得网络对输入数据中小的扰动更加鲁棒,从而提高模型的稳定性和泛化能力。
- 减少对初始权重的依赖:Batch Normalization能够通过归一化处理,缓解了对初始权重选择的敏感性,使得网络更容易训练和优化。
- 正则化效果:Batch Normalization在一定程度上具有正则化的效果,可以减少模型的过拟合问题。
3.1.3 BN的训练步骤
- 求解每个训练批次数据的均值
- 求解每个训练批次数据的方差
- 使用求得的均值和方差对该批次的数据做标准化处理, 获得0-1分布。
- 尺度变换和偏移: 使用标准化之后的x乘以γ调整数值大小, 再加上β增加偏移后得到输出值y。这个γ是尺度因子, β是平移因子, 属于BN的核心精髓, 由于标准化后的x基本会被限制在正态分布下, 会使得网络的表达能力下降, 为了解决这个问题, 引入两个模型参数γ、 β进行平移变化。
3.1.4 Batch Normalization的优点和缺点
优点:
- 梯度传递(计算)更加顺畅, 不容易导致神经元饱和 (防止梯度消失(梯度弥散)/梯度爆炸, 允许使用饱和性激活函数< eg : sigmoid、tanh等>)
- 学习率可以设置的大一点, 加快训练速度
- 对于模型参数的初始化方式和模型参数取值不太敏感, 使得网络学习更加稳定, 提高模型训练精度。
- 具有一定的正则化效果, 类似Dropout、 L1、 L2等正则化的效果
缺点:
- 如果网络层次比较深, 加BN层的话, 可能会导致模型训练速度很慢。
- 训练批次不能设置太小, 一般建议批次大小16以上。
import torch
import torch.nn as nn
class BN(nn.Module): # 实现了对输入数据进行归一化的功能
def __init__(self, num_features):
super(BN, self).__init__() # 调用父类nn.Module的构造函数,确保正确地初始化BN类
self.num_features = num_features
self.weight = nn.Parameter(torch.Tensor(num_features)) # 创建一个可学习的参数 weight,它是一个形状为 (num_features,) 的张量。该参数用于缩放归一化后的数据。
self.bias = nn.Parameter(torch.Tensor(num_features)) # 该参数用于平移归一化后的数据。
self.running_mean = torch.zeros(num_features) # 初始化running_mean,它是一个形状为 (num_features,) 的全零张量。running_mean 用于在训练过程中记录每个通道的均值。
self.running_var = torch.ones(num_features) # 用于在训练过程中记录每个通道的方差
def forward(self, x): # 前向传播函数 forward
if self.training:
mean = x.mean(dim=(0, 2, 3)) # 计算每个通道在当前batch中的均值
var = x.var(dim=(0, 2, 3)) # 计算每个通道在当前batch中的方差
self.running_mean = 0.9 * self.running_mean + 0.1 * mean # 更新running_mean,采用动量平均
"""
具体来说,这行代码的作用是将当前计算得到的均值 mean 与之前记录的 running_mean 进行加权平均。其中,0.9 是旧
值(self.running_mean)的权重,0.1 是新值(mean)的权重。这种加权平均的方式可以使得 running_mean 在每次
迭代中都向新的均值靠近,同时保留了一定程度的历史信息
"""
self.running_var = 0.9 * self.running_var + 0.1 * var # 更新running_var,采用动量平均
else: # 如果当前处于推断阶段。
mean = self.running_mean # 使用之前训练好的 running_mean
var = self.running_var # 使用之前训练好的 running_var
x_hat = (x - mean[None, :, None, None]) / torch.sqrt(var[None, :, None, None] + 1e-5) # 归一化
"""
x 是输入的图像数据,它的形状为 (batch_size, channels, height, width),表示批量大小、通道数、高度和宽度。mean 是
图像数据在各个通道上的均值,它的形状为 (channels,),其中每个元素对应一个通道的均值。var 是图像数据在各个通道上的方差,它
的形状也为 (channels,),其中每个元素对应一个通道的方差。None 的作用是在相应的位置增加一个维度,以便进行广播运算。
torch.sqrt(var[None, :, None, None] + 1e-5) 表示对方差进行开方,并增加维度以便与 x 进行广播运算。在开方之前,添加
一个小的常数 1e-5 是为了避免除以零的情况。(x - mean[None, :, None, None]) 表示将每个像素点的原始值减去相应通道上的均值。
最后整个表达式的结果 x_hat 表示归一化后的图像数据,即将每个像素点的原始值减去均值,并除以标准差。
"""
y = self.weight[None, :, None, None] * x_hat + self.bias[None, :, None, None] # 线性变换
"""
x_hat 是输入数据 x 经过归一化处理后的结果。在神经网络中,常用的归一化方法是将输入数据减去均值并除以标准差,从而使数据分
布更接近标准正态分布。self.weight 是该层的权重参数,它通过乘以 x_hat 实现线性变换。这里使用了广播操作,将 self.weight 扩
展为与 x_hat 相同的维度。self.bias 是该层的偏置参数,它通过加到 self.weight * x_hat 上实现线性变换的偏移。
综合起来,这段代码的作用是将经过归一化处理的输入数据 x_hat 与权重参数 self.weight 做线性变换,并加上偏置参数 self.bias。这
是神经网络中常见的一种操作,用于引入非线性和适应不同数据分布的能力。
"""
return y
"""
第 0 维:batch_size,即输入数据的样本数量。
第 1 维:特征维度数量,也称为通道数或特征通道。
第 2 维:高度维度,表示输入数据的高度。
第 3 维:宽度维度,表示输入数据的宽度
"""
3.2 Layer Normalization(论文)
3.2.1 什么是Layer Normalization?
Layer Normalization的主要思想是对每个样本的特征维度进行归一化处理,使得输入数据的均值接近于0,方差接近于1。与Batch Normalization不同的是,Layer Normalization不依赖于批次数据的统计信息,而是仅使用当前样本在特征维度上的均值和方差进行归一化。
3.2.2 Layer Normalization的优点?
不受样本批次大小的影响,在RNN上效果比较明显, 但是在CNN上, 效果不及BN。
class LN(nn.Module): # 自定义的 Layer Normalization(层归一化)模块 LN
def __init__(self, num_features, eps=1e-5): # eps 是一个小的常数,用于避免除以零的情况
super(LN, self).__init__()
self.num_features = num_features
self.eps = eps
self.weight = nn.Parameter(torch.ones(num_features)) # weight 和 bias 是可学习的张量参数,分别用于缩放和偏置操作
self.bias = nn.Parameter(torch.zeros(num_features))
def forward(self, x):
# 计算输入数据的均值和标准差
mean = x.mean(dim=-1, keepdim=True) # keepdim=True表示保持计算结果的维度和输入数据一致,
std = x.std(dim=-1, keepdim=True, unbiased=False) # unbiased=False表示计算无偏估计的标准差
# 标准化输入
# eps 是加到标准差分母上的一个小量,用于防止标准差为零的情况
x_normalized = (x - mean) / (std + self.eps)
# 应用缩放和偏置操作
out = self.weight * x_normalized + self.bias
return out
3.3 Instance Normalization(论文)
3.3.1 什么是Instance Normalization?
Instance Normalization(实例归一化)是一种归一化技术,与Batch Normalization、 Layer Normalization和Group Normalization类似,但其计算方式和应用场景有所不同。Instance Normalization主要用于对单个样本在特征维度上进行归一化处理。
Instance Normalization的主要思想是对每个样本的特征维度进行独立的归一化处理,使得输入数据的均值接近于0,方差接近于1。
3.3.2 Instance Normalization优点
不受样本批次的大小影响,保证每个feature map的独立性
import torch
import torch.nn as nn
class IN(nn.Module):
def __init__(self, num_features, eps=1e-5):
super(IN, self).__init__()
self.num_features = num_features
self.eps = eps
self.weight = nn.Parameter(torch.ones(num_features))
self.bias = nn.Parameter(torch.zeros(num_features))
def forward(self, x):
# 计算输入数据的均值和方差
mean = x.mean(dim=(2, 3), keepdim=True)
std = x.std(dim=(2, 3), keepdim=True, unbiased=False)
# 标准化输入
x_normalized = (x - mean) / (std + self.eps)
# 应用缩放和偏置操作
out = self.weight * x_normalized + self.bias
return out
3.4 Group Normalization(论文)
3.4.1 什么是Group Normalization?
Group Normalization(组归一化)是一种归一化技术,与Batch Normalization和Layer Normalization类似,但其计算方式和应用场景有所不同。Group Normalization将特征通道分成若干组,并对每个组内的特征进行独立的归一化处理。
Group Normalization的主要思想是通过将特征通道分组,减少了对整个批次数据或单个样本的依赖性,而是更关注于每个特征通道内的统计信息。相较于Batch Normalization,Group Normalization不仅可以在小批次训练时获得较好的效果,也适用于单个样本或小样本量的情况。
N是多少个样本的批量数据,C是通道数,H是高度,W是宽度
N=8 C H W
①BN C ——> C个均值 (8×H×W)
②LN N ——> 8个均值 (C×H×W)
③IN NC ——> 8×C个均值 (H×W)
3.5 Switchabel Normalization(论文)
3.5.1 Switchable Normalization的理解
Switchable Normalization是一种结合了不同归一化方法的技术,它可以在训练过程中动态地选择适合当前场景的归一化方式。Switchable Normalization基于Batch Normalization、Layer Normalization、Instance Normalization和Group Normalization这几种常见的归一化方法,通过学习参数来决定不同归一化方式的权重,从而根据实际情况选择最适合的归一化方式。
Switchable Normalization的主要思想是在网络中引入一个控制开关,该开关通过学习参数来决定使用哪种归一化方法。在前向传播过程中,Switchable Normalization会根据学习到的权重来加权不同归一化方法的输出,从而实现动态选择最适合当前场景的归一化方式。
3.5.2 Switchable Normalization的优点
- 灵活性:Switchable Normalization能够根据实际情况动态地选择最合适的归一化方式,提高了模型的适应性和灵活性。
- 综合利用:Switchable Normalization可以综合利用多种归一化方法的优点,避免了单一归一化方法的局限性。
- 自适应性:Switchable Normalization可以根据不同层的特性和输入数据的分布自适应地选择最佳的归一化方式,提高了模型的性能和泛化能力