向李宏毅学深度学习(进阶)#task03#Datawhale X 李宏毅苹果书 AI夏令营

结合李宏毅老师《机器学习/深度学习课程》以及《深度学习详解》一书做的笔记,图片并非完全由本人原创,侵删。这里附上视频链接和书本pdf地址:

李宏毅《机器学习/深度学习》2021课程(国语版本,已授权)_哔哩哔哩_bilibili

GitHub - datawhalechina/leedl-tutorial: 《李宏毅深度学习教程》(李宏毅老师推荐👍,苹果书🍎),PDF下载地址:https://github.com/datawhalechina/leedl-tutorial/releases


Task3


批量归一化(Batch Normalization,BN)

误差表面受训练数据特征与分布的影响

在DL中,误差表面的复杂性和形状主要由神经网络架构、训练数据的特征与分布、优化算法、损失函数的选择、权重初始化等因素有关。在一般的优化问题中,就算我们已掌握了很多好用的优化算法,这依然是个不容小觑的问题。

误差表面受数据的影响

上图展示了一个现象,当模型参数发生微小变化后,其最终将导致损失函数 L 的变化。这取决于训练数据 x,x 越大,L 的变化越大,误差表面的变化越大。这意味着每更新一次参数,误差表面都会发生剧烈变化,显然是不利于优化的。

上图展示的第二个问题是训练数据每一个维度所在的区间差距大。 x_1  的范围是1,2.....而  x_2 为100,200..... ,这导致的问题是误差表面上不同方向的斜率非常不同,坡度也很不一样。这同样给优化制造了麻烦。

特征归一化(feature normalization)

那有没有办法解决上述问题呢?自然有。只要给数据的不同维度设置同样的合理的数值范围,也许就可以构造出比较理想的误差表面。这类方法统称为特征归一化(feature normalization)

Z值归一化(Z-score normalization)

Z值归一化也被称为标准化,是特征归一化的一种,其过程如下:

假设 x^1,x^2.......x^R 是所有训练数据的特征向量,将他们全部集合起来,形成一个矩阵。x_1^! 代表第一个特征向量的第一个元素,以此类推,将不同特征向量同一个维度的数值全部取出,对每个维度 i ,计算平均值 m_i 与标准差 \sigma_i ,接下来做归一化:

\tilde{x}_{i}^{r} \leftarrow \frac{x_{i}^{r} - m_{i}}{\sigma_{i}}

以下是归一化的图形示例:

 做完归一化后,这个维度里数值的平均值为0,方差为1,数值分布在0附近。因为对每一个维度都做了归一化,所以每个特征向量不同维度的值都在0附近,这样构造的误差表面往往对训练有帮助

深度神经网络中如何做特征归一化?

除了对原始特征向量的每一个维度做特征归一化之外,在神经网络内部也可以增加归一化,如图:

神经网络中间层的归一化

虽然原始特征向量在输入网络时已经做了归一化,在经过 W^1 后,第二层参数不同维度间的数值差异由变大了,也应该做归一化。一般而言,FN要放在激活函数之前。 

如上图,首先计算 z_1,z_2,z_3 的平均值:

\mu=\frac{1}{3}\sum_{i=1}^{3}z^{i}

接下来计算标准差:

\sigma = \sqrt{\frac{1}{3} \sum_{i=1}^{3} (z^i - \mu)^2}

最后,根据前两个式子进行归一化:

z^i = \frac{z^i - \mu}{\sigma}

批量归一化时,输入网络的是 训练数据的一个banch,这个banch需要足够大,才能代表整个数据集的分布,才能使用批量归一化。

批量归一化一般作为深度学习网络的一层,即BN层。

批量归一化后引入 \beta 和 \gamma 的作用

批量归一化后的层往往还会加入以下操作:

\hat{z}^{i} = \gamma \odot \tilde{z}^{i} + \beta

其中:

  • \gamma 和 \beta 是可学习的参数。
  • ⊙ 表示逐元素相乘(即Hadamard积)

 批量归一化的目的是使输入数据的均值为0,方差为1。但是,网络中的某些层可能需要不同的分布。如果仅仅通过归一化将均值定为0,可能对网络的学习造成一定的限制。因此,通过引入  \gamma 和 \beta  来进行线性变换,可以恢复原本的数据分布,允许模型学习不同的分布,而不仅仅是均值为0、方差为1的分布。

模型推理时怎样使用批量归一化?

训练时根据一个批量中同一维度的数值做批量归一化,推理时因为没有一个批量的数据所以不行。实际上,pytorch等框架在训练时通过一个个banch计算得来的 \mu 和 \sigma ,都会被用作计算移动平均值。假设有 \mu_1,\mu_2.......\mu_t ,计算移动平均值:

\mu \leftarrow p\bar{\mu} + (1-p)\mu^t

\bar{\mu} 是 \mu 的平均值,p 在pytorch中默认0.1,同理计算 \bar{\sigma} ,代入推理网络:

这样就在避免直接进行批量归一化的情况下使网络进行正常的前向传播

为什么批量归一化会有用?

一个可能的原因是内部协变量偏移,这里手绘了过程草图辅助理解:

意思就是反向传播中后面更新的参数的梯度应该依赖于前面更新后的参数,而梯度下降则是依赖于网络前半部分更新前的参数。批量归一化缓解了这个问题。

事实上现在更认可的观点是批量归一化平滑了误差表面,让梯度下降更加稳定,平滑了梯度下降的过程。

层归一化(以Transformer为例)

实际上还有很多归一化的方法,这里介绍层归一化(Layer Normalization, LN,也是一种用的比较多的归一化方法。

批量归一化是对一个banch的所有数据的同一维度归一化,方法提出来后自然有人想到可以对一个向量的所有维度进行归一化,事实上这种方法也是有效的,大名鼎鼎的transformer用的就是层归一化,这里以transformer举例讲一下层归一化。

多头自注意力机制之后的层归一化

多头自注意力机制负责计算输入序列中不同位置之间的相关性,并将这些信息整合到每个输入向量中。

Attention_Output = LayerNorm(Attention(Q,K,V) + Input)

这里的 Attention(Q,K,V) 是多头自注意力机制的输出,Input 是原始的输入向量。层归一化将注意力输出和原始输入相加后的结果进行归一化,以稳定输出。 

前馈神经网络之后的层归一化

FFN\_Output = LayerNorm(FFN(Attention\_Output) + Attention\_Output)

这里的 FFN(Attention_Output) 表示前馈网络的输出,Attention_Output 是注意力机制后的归一化输出。层归一化使得前馈网络的输出不受输入变化的影响。

为什么Transformer选择层归一化? 

Transformer模型中的输入序列长度和内容可以变化,因此需要一种不依赖于批量大小的归一化方法。层归一化正是因为它只对每个样本的内部进行归一化,能够很好地适应这种动态输入。 

在Transformer的训练中,有时使用的小批量样本数量可能较少,甚至可能是逐个样本进行推断。层归一会更适合这种场景。 

总结 

根据场景的不同我们可以选择不同的归一化方法,常见一点的有批量归一化,层归一化,批量重归一化等等,不过大多数情况下批量归一下都已经足够啦。

卷积神经网络

什么是卷积

卷积神经网络是现代计算机视觉领域的基础,是一种很经典的网络架构,常用于图像分类、语义分割等任务。

卷积操作指的是将一个可以移动的小窗口与图像进行逐元素相乘并求和的过程。这个小窗口是一组固定的权重,被称为滤波器(filter)或卷积核(kernel)。通过这个操作,我们可以提取图像中的特征。“卷积”这个名称正是来源于这种元素级的相乘与求和操作.。

简而言之,卷积操作就是用一个可移动的小窗口来提取图像中的特征,这个小窗口包含了一组特定的权重,通过与图像的不同位置进行卷积操作,网络能够学习并捕捉到不同特征的信息。

进行卷积操作需要哪些关键参数?

步长(stride):每次滑动的步长,上图中可以理解为向右滑动的格数

填充值(zero-padding):在图像边缘补0,使得边缘位置的参数也能被“扫描”到

卷积核大小(kernel size): 卷积核的尺寸,决定了每次操作中参与运算的像素区域的大小,就是“小窗”的大小

通道数: 输入图像的深度,卷积核的深度(个数),决定了卷积核的维度和输出特征图的深度

为什么要进行padding? 

多数情况下卷积核的步长设为1,也有些时候设为2,在不进行填充的情况下,图像边缘部分的特征会被卷积核跳过 

可以看出卷积核大小为3*3,步长为2的情况下,矩阵边缘地区只有进行填充才会被卷积核”扫描“到,常用的填充一般是0填充。

卷积神经网络中常用的层

 卷积层:卷积层是 CNN 的核心层,用于从输入数据中提取局部特征。通过卷积操作,卷积层能够捕捉输入中的空间信息和模式,上文中带有卷积核的层即为卷积层。

池化层(Pooling Layer)

池化层用于下采样特征图,减少数据的空间尺寸,降低计算复杂度,同时保留关键信息。它还可以增加网络对输入的平移、旋转等小变换的鲁棒性。

上图展示了 池化的两种方式:最大池化和平均池化。顾名思义。前者是在池化窗口中选择一个最大的数值,后者则是选择一个平均数值

激活层

激活层为网络引入非线性,使得模型能够学习到复杂的模式和特征。如果没有激活函数,卷积操作只是线性的,无法解决复杂问题。

全连接层

全连接层将卷积或池化层输出的高维特征映射到最终的类别上,通常位于网络的末端,用于整合特征并生成预测结果。

归一化层

 归一化层用于稳定和加速训练,通过调整输入的分布,使网络更容易训练,减少梯度消失或爆炸的现象。

直观理解卷积神经网络

其实卷积神经网络中的卷积核就相当于一般神经网络的权重 W ,类似地也要加上一个偏置 b

所以本质上还是可以将一个卷积核+偏置认为是 一个传统的隐藏层神经元

什么是共享参数

在卷积神经网络中,共享参数指的是在整个输入特征图上使用相同的卷积核进行卷积操作。这个卷积核在所有位置上共享同样的一组权重和偏置。

图像底层特征的位置无关性

我们知道,图像的底层特征是跟具体位置无关的,比如边缘。无论是在图像中间的边缘特征,还是图像四角的边缘特征,都可以用类似于微分的特征提取器提取

  • 低层次特征通常是泛化的、易于描述的,比如纹理、颜色、边缘和角点等。
  • 高层次特征则更加复杂,难以具体说明,例如金色的头发、瓢虫的翅膀、五彩斑斓的花朵等。

为什么需要参数共享

 卷积层通过参数共享极大地减少了需要学习的参数数量。例如,在一个卷积层中,假设输入为 32x32x3 的图像,使用 3x3x3 的卷积核进行卷积,如果使用全连接层,则需要大量的参数。而使用卷积层,整个卷积层只需要学习9个权重参数(3x3)和一个偏置,共10个参数。

参数共享还可以增加移不变性,即要检测的对象在图片中发生了平移,还可以照常检测。因为减少了要学习的参数,还可以预防过拟合。

感受野 

卷积神经网络中,感受野表示的是网络中某一层的神经元所感知到的输入数据的局部区域。例如,在图像处理中,一个卷积层的神经元可能只对输入图像的某个小块区域有响应,这个小块区域就是该神经元的感受野。

随着网络层次的加深,感受野会逐渐扩大。这意味着网络的高层神经元能够“看到”更大范围的输入数据,并且能够捕捉到更高级别、更全局的特征。

  • 层数较低的感受野”看到“的范围受卷积核大小的直接影响
  • 层数较高的感受野能看到更大的范围

由上图可知,浅层检测范围有限,提取到的是低层次特征,深层检测的范围成倍增加,能提取到复杂模式下的特征。

经典卷积神经网络回顾-----VGG-16

VGG-16由16层网络组成,包括 13 个卷积层和 3 个全连接层。VGG-16 以其简单性和有效性,以及在各种计算机视觉任务上的强大性能闻名。该模型的架构具有一堆卷积层,然后是最大池化层,深度逐渐增加,其架构非常经典。

VGG-16架构

VGG-16 是一个由 13 个卷积层和 3 个全连接层组成的深度网络模型,所有卷积层使用 3x3 的滤波器,池化层使用 2x2 的最大池化。

如上图,VGG-16由9层卷积层、4层池化层、3层全连接层、softmax输出层构成,所有隐层的激活单元都采用ReLU函数。具体安排为:

  • 输入层: 224x224x3 的彩色图像
  • 卷积层和池化层:二者交叉组合,两到三层卷积层加一个池化层为固定组合,卷积核的通道数在不断变化,64-->128-->256-->512-->512,经过池化层通道数不变。卷积核大小固定为3*3,也是开创了时代
  • 池化层: 在卷积层后添加池化层,逐步减少特征图的尺寸
  • 全连接层: 3 个全连接层,维度分别为4096-->4096-->1000,最后跟进softmax进行分类

前面卷积层+池化层的组合实际上是在做特征提取,后面全连接层负责完成分类

VGG-16的卷积核

VGG-16的卷积核的设计非常经典,早期的CNN架构,如LeNet或AlexNet,使用尺寸更大的卷积核如5x5或11x11,而VGG-16则采用了统一的3x3卷积核,这种设计有几个好处:

  • 通过堆叠多个3*3的卷积核,增加了网络的深度和非线性,使得网络能学习更复杂的表示
  • 虽然单个3x3卷积核的感受野较小,但通过堆叠多个卷积层,VGG-16同样能够获得较大的感受野
  • 相比5*5或11*11的卷积核,可以在保证感受野的情况下减少参数量

VGG-16代码实现

导入库 torch,torch.nn
  • torch:PyTorch 基础模块,提供张量操作,类似于 NumPy 库,但它支持在 GPU 上进行加速计算
  • torch.nn:PyTorch 中的神经网络模块,提供了构建和训练神经网络的基本结构
import torch
import torch.nn as nn
定义模型结构

具体有六个块结构,每个含有2~3层卷积层,一层池化层,激活函数用的relu,最后定义了3个全连接神经网络,用于分类。

# 定义 VGG16 模型
class VGG16(nn.Module):
    def __init__(self, num_classes=1000):
        super(VGG16, self).__init__()
        self.features = nn.Sequential(
            # 第一个块结构

            #三维卷积,通道数64,卷积核大小为3,填充为1
            nn.Conv2d(3, 64, kernel_size=3, padding=1), 

            #激活函数设为relu
            nn.ReLU(inplace=True),
            nn.Conv2d(64, 64, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),

            池化层,窗口大小为2,步长为2
            nn.MaxPool2d(kernel_size=2, stride=2),
 
            # 第二个块结构
            nn.Conv2d(64, 128, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(128, 128, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2, stride=2),
 
            # 第三个块结构
            nn.Conv2d(128, 256, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(256, 256, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(256, 256, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2, stride=2),
 
            # 第四个块结构
            nn.Conv2d(256, 512, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(512, 512, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(512, 512, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2, stride=2),
 
            # 第五个块结构
            nn.Conv2d(512, 512, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(512, 512, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(512, 512, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2, stride=2)
        )
        self.classifier = nn.Sequential(
            nn.Linear(512 * 7 * 7, 4096),
            nn.ReLU(inplace=True),
            nn.Linear(4096, 4096),
            nn.ReLU(inplace=True),
            nn.Linear(4096, num_classes)
        )
 
    def forward(self, x):
        x = self.features(x)
        x = torch.flatten(x, 1)
        x = self.classifier(x)
        return x
 
 

其中几个关键参数:

  • in_channels: 输入通道数,第一层为 3(RGB 图像),后续层为前一层的输出通道数。
  • out_channels: 输出通道数,决定特征图的数量,如 64、128、256、512。
  • kernel_size: 卷积核大小,VGG-16 全部为 3x3。表示池化窗口的话设为 2x2
  • padding: 填充大小,设为 1 以保持特征图尺寸不变。
  • in_features: 输入特征数,第一个全连接层为 512 * 7 * 7
  • out_features: 输出特征数,前两个全连接层为 4096,最后一层为类别数(如 1000)。
  • num_classes:输出类别数,默认1000
实例化模型
model = VGG16(num_classes=1000)
预训练VGG-16模型的使用

实际上VGG-16模型已经被封装好了,包含训练好的权重。平时使用只需简答调用即可

from torchvision import models
vgg16 = models.vgg16(pretrained=True)

关于VGG-16的参数总量

现在来试着计算 VGG-16 模型的参数数量。要做的就是逐层计算每个卷积层和全连接层的参数。卷积层的参数通过计算输入通道数、卷积核大小和输出通道数来得出,并加上偏置项。全连接层的参数则是通过输入特征数乘以输出特征数,再加上偏置项。最后得到 VGG-16 模型的总参数量,大约为 138,357,544 。

是的,参数量已经超过1.38 亿了。与其他经典模型(如 AlexNet 和 ResNet)相比,参数量显得很大。这反映了模型的复杂性和强大学习能力,也使得模型具有强大的捕捉细节特征的能力,所以在大型数据集(如 ImageNet)上表现出色。然而,这也意味着在训练和推理时需要消耗大量的计算资源和存储空间。

总结

通过VGG-16的例子,回顾了前文卷积神经网络所涉及的知识点,包括卷积操作、激活函数、池化层、全连接层以及模型的整体架构设计。通过对 VGG-16 的分析,我们进一步理解了卷积神经网络在图像处理中的应用模式,这为我们在更复杂的任务中应用卷积神经网络打下理论基础。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值