卷积神经网络
目录
一、卷积(convolution)
——按照原始空间结构保存
卷积是一种常用的图像处理和深度学习中的操作,它通过滑动一个小的矩阵(称为卷积核或滤波器)在输入数据上进行计算,从而得到输出特征图。下面是卷积的基本步骤:
![卷积](https://img-blog.csdnimg.cn/20201114144337983.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2JpdDQ1Mg==,size_16,color_FFFFFF,t_70)
-
定义输入数据和卷积核:输入数据通常表示为一个多维数组,比如二维图像可以表示为一个矩阵。卷积核也是一个多维数组,它定义了在输入数据上进行计的方式。
-
填充(可选):在执行卷积之前,可以选择对输入数据进行填充。填充是在输入数据周围添加额外的值,以便更好地处理边缘像素。常见的填充方式包括零填充和边界填充。
-
执行卷积运算:将卷积核与输入数据进行逐元素相乘,并将所有乘积结果相加得到一个标量值。这个过程可以看作是在输入数据上滑动卷积核并计算每个位置上的乘积累加。
-
移动步幅(可选):执行完一次卷积后,可以选择按照指定的步幅将卷积核移动到下一个位置继续执行。步幅决定了每次移动的距离,较大的步幅可以减小输出特征图的尺寸。
-
输出特征图:重复执行卷积运算直到遍历完整个输入数据,最终得到一个或多个输出特征图。每个输出特征图对应着输入数据中某种特定的局部模式。
需要注意的是,卷积操作通常还包括非线性激活函数(如ReLU)和其他一些技巧(如批归一化),以提高模型的表达能力和性能。此外,在深度学习中还有更复杂的卷积变体,如带有跳跃连接的残差网络等。以上是卷积操作的基本步骤,具体实现时可能会根据不同的框架和应用场景有所差异。
在卷积中,通道(channel)是指输入数据和卷积核中的特征维度。通常情况下,输入数据是一个多维数组,其中最常见的是二维图像,它具有两个空间维度(宽度和高度)以及一个颜色通道维度。
一个RGB图像的输入数据可以表示为一个三维数组(高度 x 宽度 x 3),x3代表3个通道。彩色图像通常由红色(R)、绿色(G)和蓝色(B)三个通道组成,即RGB图像。每个通道表示了图像中对应颜色的强度信息。
一个示例:
import torch
import torch.nn as nn
# 定义一个输入张量,假设它有 3 个通道,大小为 32x32
input_tensor = torch.randn(1, 3, 32, 32)
# 定义卷积层
conv_layer = nn.Conv2d(in_channels=3, out_channels=16, kernel_size=3, stride=1, padding=1)
# 将输入张量传递给卷积层
output_tensor = conv_layer(input_tensor)
print("Input shape:", input_tensor.shape)
print("Output shape:", output_tensor.shape)
在上述示例中,我们使用nn.Conv2d
来定义了一个卷积层。in_channels=3
表示输入张量的通道数为3,out_channels=16
表示输出张量的通道数为16,kernel_size=3
表示卷积核的大小为3x3。
我们创建了一个随机初始化的输入张量 input_tensor
,它具有1个批次(batch)样本,3个通道,大小为32x32。然后将输入张量传递给卷积层进行卷积操作。
输出张量 output_tensor
的形状与输入张量相同,只是通道数发生了变化(从3变成了16),这是因为我们定义的卷积层具有16个输出通道。
具体参数的含义如下:
in_channels
:输入通道数,它告诉模型或函数应该期望多少个输入通道,每个通道包含不同的特征或信息。out_channels
:输出通道数,即卷积核的数量。它决定了卷积层输出特征图的通道数。kernel_size
:卷积核的大小,可以是一个整数或一个元组。如果是一个整数,则表示正方形卷积核的边长;如果是一个元组,则表示矩形卷积核的高度和宽度。stride
:步幅大小,可以是一个整数或一个元组。如果是一个整数,则表示水平和垂直方向上的步幅相同;如果是一个元组,则分别表示水平和垂直方向上的步幅。padding
:填充大小,可以是一个整数或一个元组。如果是一个整数,则表示在输入特征图周围添加相同数量的零填充;如果是一个元组,则分别表示水平和垂直方向上的填充大小。
二、池化(pooling)
——通道数不变
池化也叫下采样,是指将图像或信号的采样率降低的过程(将其中最具有代表性的特征提取出来,可以起到减小过拟合和降低维度的作用)。在图像处理中,池化可以通过减少图像中的像素数量来降低图像的分辨率。
常见的下采样方法包括平均池化(Average Pooling)和最大池化(Max Pooling)。在平均池化中,将输入图像划分为不重叠的小区域,并计算每个区域内像素值的平均值作为输出。在最大池化中,同样将输入图像划分为小区域,但输出为每个区域内像素值的最大值。
![池化](https://img-blog.csdnimg.cn/img_convert/b1099ab35698c59d5c4eea63cc92b2be.jpeg)
下采样可以用于多种情况,例如减少计算量、压缩图像、降低噪声等。
优点:
-
在减少参数量的同时,还保留了原图像的原始特征
-
有效防止过拟合
-
为卷积神经网络带来平移不变性
缺点:在进行下采样时会损失一部分细节信息和精度
因此需要根据具体应用场景权衡利弊。
一个示例:
import torch
import torch.nn as nn
# 定义一个输入张量,假设它有 10 个通道,大小为 32x32
input_tensor = torch.randn(1, 10, 32, 32)
# 定义最大池化层
max_pool = nn.MaxPool2d(kernel_size=2, stride=2)
# 将输入张量传递给最大池化层
output_tensor = max_pool(input_tensor)
print("Input shape:", input_tensor.shape)
print("Output shape:", output_tensor.shape)
在上述示例中,我们使用nn.MaxPool2d
来定义了一个最大池化层。kernel_size=2
表示池化窗口的大小为2x2,stride=2
表示在每个维度上移动步长为2。
我们创建了一个随机初始化的输入张量 input_tensor
,它具有1个批次(batch)样本,10个通道,大小为32x32。然后将输入张量传递给最大池化层进行池化操作。
输出张量 output_tensor
的形状与输入张量相同,只是在空间维度上缩小了一半。这是因为我们使用了2x2的池化窗口和步长为2。
三、全连接(fully connected)
——会使数据丧失原有空间结构信息
将前一层的特征映射(或称为输入向量)通过矩阵乘法和非线性激活函数转换为新的特征表示,通常会将输入数据从二维(如图像)或更高维度的形式转换为一维向量。
此步我们将所有的特征都展开并进行运算,最后会得到一个概率值,这个概率值就是输入的数据对每个标签的概率或置信度。
![全连接](https://img-blog.csdnimg.cn/img_convert/d2ce75fbfc5c8f5d8dbcda3e02cabd35.jpeg)
通常情况下,全连接层会应用一个softmax函数来将输出转化为概率分布。Softmax函数将原始输出映射到一个[0,1]范围内,并确保所有类别的概率之和等于1。这样做有助于解释模型对不同标签的相对置信度。
一个示例:
import torch
import torch.nn as nn
# 定义一个输入张量,假设它有 10 个特征
input_tensor = torch.randn(1, 10)
# 定义全连接层
fc_layer = nn.Linear(in_features=10, out_features=5)
# 将输入张量传递给全连接层
output_tensor = fc_layer(input_tensor)
print("Input shape:", input_tensor.shape)
print("Output shape:", output_tensor.shape)
在上述示例中,我们使用nn.Linear
来定义了一个全连接层。in_features=10
表示输入张量的特征数为10,out_features=5
表示输出张量的特征数为5。
我们创建了一个随机初始化的输入张量 input_tensor
,它具有1个批次(batch)样本和10个特征。然后将输入张量传递给全连接层进行线性变换操作。
输出张量 output_tensor
的形状与输入张量相同(都是1xN),只是特征数发生了变化(从10变成了5),这是因为我们定义的全连接层具有5个输出特征。
四、网络模型的示意图
五、Softmax和Sigmoid
Softmax和Sigmoid都是常见的激活函数,用于将原始输出映射到概率分布或对应类别的预测结果。它们有一些相似之处,但也有一些重要的区别。
-
相似之处:
- 都是非线性函数:Softmax和Sigmoid都是非线性函数,可以引入非线性特征学习能力。
- 输出范围:Softmax和Sigmoid函数都将输出限制在[0, 1]之间。
-
区别:
- 应用范围:Sigmoid主要用于二分类问题,而Softmax常用于多分类问题。
- 输出形式:Sigmoid函数产生一个单一的输出值,表示某个类别的概率或置信度。而Softmax函数产生一个包含多个元素的向量,每个元素表示对应类别的概率。
- 输出关系:在Sigmoid中,所有类别的概率之和不一定等于1;而在Softmax中,所有类别的概率之和总是等于1。
- 多标签问题处理:对于多标签分类问题(每个样本可以属于多个类别),可以使用Sigmoid作为激活函数,并独立地对每个标签进行二分类预测。而使用Softmax则需要将多标签问题转化为多分类问题。
总结起来,Sigmoid适用于二分类问题和多标签问题,而Softmax适用于多分类问题。它们在输出形式和输出关系上有所不同,但都可以将原始输出映射到概率分布或对应类别的预测结果。
两个示例:
import torch
import torch.nn.functional as F
# 定义一个输入张量,假设它有 5 个类别的得分
input_tensor = torch.tensor([1.0, 2.0, 3.0, 4.0, 5.0])
# 使用softmax函数进行归一化操作
output_tensor = F.softmax(input_tensor, dim=0)
print("Input tensor:", input_tensor)
print("Output tensor:", output_tensor)
在上述示例中,我们使用F.softmax
函数对输入张量进行归一化操作。dim=0
参数表示在维度0上进行归一化计算。
我们创建了一个包含5个类别得分的输入张量 input_tensor
。然后,通过应用softmax函数将这些得分转换为概率值,获得输出张量 output_tensor
。
输出张量 output_tensor
是经过softmax函数处理后的结果,它表示每个类别的概率分布。可以看到,在这个例子中,得分最高的类别(5)具有最高的概率值(约为0.6364),而其他类别则具有较低的概率值。
import torch
import torch.nn.functional as F
# 定义一个输入张量
input_tensor = torch.tensor([1.0, 2.0, 3.0, 4.0, 5.0])
# 使用sigmoid函数进行激活操作
output_tensor = torch.sigmoid(input_tensor)
print("Input tensor:", input_tensor)
print("Output tensor:", output_tensor)
在上述示例中,我们使用torch.sigmoid
函数对输入张量进行激活操作。
我们创建了一个包含5个元素的输入张量 input_tensor
。然后,通过应用sigmoid函数将这些元素转换为介于0和1之间的值,获得输出张量 output_tensor
。
输出张量 output_tensor
是经过sigmoid函数处理后的结果,它表示每个元素经过激活后的值。可以看到,在这个例子中,较大的输入值会得到接近于1的输出值,而较小的输入值则会得到接近于0的输出值。
六、预测结果
预测结果是模型对输入数据进行推断或预测后得到的输出。它表示了模型对输入数据的估计或判断。
-
二分类问题:对于二分类任务,预测结果通常是一个包含一个元素的一维向量,表示模型对于某个样本属于正类的概率或置信度。
-
多分类问题:对于多分类任务,预测结果通常是一个包含多个元素的一维向量。每个元素表示模型对于某个类别的概率或置信度。
-
回归问题:对于回归任务,预测结果通常是一个一维向量或多维向量。每个元素表示模型对于某个目标变量(如房价、温度等)的预测值。
-
目标检测问题:对于目标检测任务,预测结果通常是一个包含多个元素的二维向量或矩阵。每个元素表示模型对于图像中某个位置是否存在目标物体的概率或置信度
七、真实标签
真实标签是指在监督学习任务中,用于训练和评估模型的数据集中每个样本所对应的正确答案或目标值。它通常由人工标注或专家提供,并作为模型的训练目标。
将真实标签存储在txt文件中是一种常见的做法。每个样本的真实标签可以作为文本文件中的一行,并与相应的样本数据对应。通过读取该txt文件,我们可以将真实标签加载到程序中,并与模型预测结果进行比较,从而评估模型的性能。
训练机器学习模型时,我们希望通过最小化预测结果与真实标签之间的差异(损失)来优化模型参数,使其能够更准确地预测未知数据。
残差神经网络
前言
在传统的深层神经网络中,随着网络层数的增加,模型往往会遇到梯度消失或梯度爆炸的问题。这是因为在反向传播算法中,梯度通过每一层进行反向传递时,会不断乘以权重参数。当权重参数小于1时,多次相乘后梯度会逐渐衰减至接近零,导致信息无法有效地传播到较浅的层。而当权重参数大于1时,多次相乘后梯度会呈指数级增长,导致训练过程变得不稳定甚至发散。
ResNet通过引入"残差块"来解决这个问题。残差块包含了一个跳跃连接(shortcut connection),即将输入直接添加到输出上。这样可以确保原始输入信息能够直接传递到后续层,并且不受多次权重乘法带来的影响。通过这种方式,残差块使得模型可以更容易地学习恒等映射(identity mapping),从而避免了梯度消失和梯度爆炸问题。
定义
残差神经网络(Residual Neural Network,简称ResNet)是一种深度卷积神经网络架构,通过使用残差块(Residual Block)来解决深层网络中的梯度消失和梯度爆炸问题。下面是使用残差块构建ResNet的一般步骤:
-
输入数据:将输入数据传入第一个卷积层,通常为一个7x7的卷积核,然后进行批量归一化(Batch Normalization)和ReLU激活函数处理。
-
下采样:接下来进行最大池化或平均池化操作来减小特征图的尺寸。
-
残差块:构建残差块作为ResNet的基本单元。每个残差块由两个或三个卷积层组成,之间包括批量归一化和ReLU激活函数。残差块可以分为两类:
- 恒等映射(Identity Mapping):如果输入和输出通道数相同且步长为1,则直接将输入添加到输出上。
- 降维映射(Projection Mapping):如果输入和输出通道数不同或者步长不为1,则使用1x1的卷积核对输入进行调整,并将调整后的结果与输出相加。
-
堆叠残差块:根据需求,将多个残差块堆叠在一起构建深层的ResNet网络。可以通过调整每个残差块的数量和通道数来控制网络的深度和复杂度。
-
全局平均池化:在最后一个残差块之后,使用全局平均池化操作对特征图进行降维,将特征图转换为向量。
-
全连接层:添加一个全连接层用于分类任务,将降维后的特征向量映射到目标类别的数量上。
-
Softmax激活函数:对全连接层输出进行Softmax激活函数处理,得到最终的分类结果。
残差块
ResNet 网络中的基本组成单元是残差块(Residual Block)。每个残差块都包含了一些卷积层和激活函数,以及一个特殊的“跳跃连接”或称为“短路连接”。
![残差神经网络(ResNet)](https://img-blog.csdnimg.cn/img_convert/363f7739d86a864a469dee69a7dc21a8.png)
一般来说,一个标准的残差块包括两个3x3卷积层,这两个卷积层之间有一个 ReLU 激活函数。但也有三层结构。
整个模块实际上是在学习如何对输入 x 进行微调(即学习残差映射 F(x)),而不是从头开始去学习如何产生输出。如果没有新的信息需要添加(F(x)=0),那么网络可以轻松地通过短路连接将原始输入传递下去。
跳跃连接(短路连接)
“跳跃连接”或“短路连接”的作用是将输入直接传到后面的层,
-
梯度消失问题的解决:在传统的深层神经网络中,梯度在反向传播过程中可能会逐层衰减,导致低层的参数更新非常缓慢甚至停止更新。通过引入跳跃连接,可以将底层特征与上层特征直接相加或拼接起来,并将这些组合后的特征传递到下一层。这样一来,在反向传播过程中,梯度可以更容易地从高层传播到低层,避免了梯度消失问题。
-
梯度爆炸问题的解决:在某些情况下,反向传播时梯度可能会增长非常快,导致参数更新过大甚至无法收敛。通过引入跳跃连接并结合合适的技巧(如剪裁),可以控制梯度的范围,避免其超出可接受的范围。例如,在计算梯度时可以使用剪裁技术对超过某个阈值的梯度进行截断,从而限制其大小。
(梯度:梯度是一个向量,用于表示函数在某一点处的变化率和方向。在机器学习和优化算法中,我们通常使用梯度来指代目标函数相对于参数的偏导数。
通过计算梯度,我们可以确定当前位置在函数表面上的陡峭程度和下降方向。在优化问题中,我们希望找到使得目标函数最小化或最大化的参数值。因此,在许多优化算法(如梯度下降)中,利用梯度信息来更新参数,并朝着更优解的方向进行迭代。)
一个示例:
import tensorflow as tf
def residual_block(inputs, filters, kernel_size):
# 第一个卷积层
x = tf.keras.layers.Conv2D(filters, kernel_size, padding='same')(inputs)
x = tf.keras.layers.BatchNormalization()(x)
x = tf.keras.layers.Activation('relu')(x)
# 第二个卷积层
x = tf.keras.layers.Conv2D(filters, kernel_size, padding='same')(x)
x = tf.keras.layers.BatchNormalization()(x)
# 跳跃连接,将输入与输出相加
residual = inputs + x
# 使用激活函数处理结果
output = tf.keras.layers.Activation('relu')(residual)
return output
在这个示例中,我们使用了TensorFlow库来实现残差块。residual_block
函数接受输入张量 inputs
、滤波器数量 filters
和卷积核大小 kernel_size
作为参数。
在函数内部,我们首先应用一个卷积层、批归一化层和激活函数来处理输入。然后,再次应用类似的操作得到第二个卷积层的输出。最后,通过添加操作将输入与第二个卷积层的输出相加,并使用ReLU激活函数处理结果。
二维批量归一化
批量归一化通过对每个小批量数据进行归一化操作,使得输入特征具有零均值和单位方差。具体来说,在二维卷积层中,批量归一化对每个通道的特征图进行标准化操作。这样可以减少内部协变量偏移,并且可以使得后续层更容易学习到适当的权重和偏置。
二维批量归一化操作的计算过程如下:
- 对每个通道计算该通道上所有样本的均值和方差。
- 使用计算得到的均值和方差对该通道上所有样本进行标准化。
- 将标准化后的样本通过可学习参数进行线性变换(缩放和平移)。
- 输出最终的归一化结果。
一个示例:
import torch
import torch.nn as nn
# 定义一个二维批量归一化层
batchnorm = nn.BatchNorm2d(num_features=3)
# 定义一个输入张量
input_tensor = torch.randn(4, 3, 32, 32) # 假设输入为四个3通道的32x32图像
# 应用二维批量归一化操作
output_tensor = batchnorm(input_tensor)
print("Input tensor shape:", input_tensor.shape)
print("Output tensor shape:", output_tensor.shape)
在上述示例,我们使用nn.BatchNorm2d
类来定义一个二维批量归一化层。
我们创建了一个大小为4x3x32x32的输入张量 input_tensor
,假设这是四个3通道的32x32图像。然后,通过将输入张量传递给二维批量归一化层 batchnorm
来应用归一化操作。
输出张量 output_tensor
具有与输入张量相同的形状,并且每个通道都被独立地进行了归一化处理。
VGG网络
定义
![VGG16](https://img-blog.csdnimg.cn/6560972079ae435f9cee70a56effbdfb.png)
如上图,VGG由5个卷积层,3个全连接层,1个softmax层组成。
VGG16总共包含16个子层,第1层卷积层由2个conv3-64组成,第2层卷积层由2个conv3-128组成,第3层卷积层由3个conv3-256组成,第4层卷积层由3个conv3-512组成,第5层卷积层由3个conv3-512组成,然后是2个FC4096,1个FC1000。总共16层,这也就是VGG16名字的由来。
![](https://img-blog.csdnimg.cn/img_convert/7ee5eee61be3dd136a47f1b449579570.png)
一个示例:
import torch
import torch.nn as nn
# VGG网络模型定义
class VGG(nn.Module):
def __init__(self, num_classes=1000):
super(VGG, self).__init__()
self.features = nn.Sequential(
nn.Conv2d(3, 64, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
nn.Conv2d(64, 64, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
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.BatchNorm2D(512)
#nn.ReLU(True)
#nn.MaxPool2D(kernal-size = 3,stride =1)
#nn.AvgPool2D(kernel-size = 7,stride =1)
)
self.avgpool = nn.AdaptiveAvgPool2d((7 ,7 ))
self.classifier = nn.Sequential(
nn.Linear(256 * 7 * 7, 4096),
nn.ReLU(inplace=True),
nn.Dropout(),
nn.Linear(4096, 4096),
nn.ReLU(inplace=True),
nn.Dropout(),
nn.Linear(4096, num_classes)
)
def forward(self, x):
x = self.features(x)
x = self.avgpool(x)
x = torch.flatten(x, 1)
x = self.classifier(x)
return x
# 创建VGG模型实例
model = VGG(num_classes=1000)
# 打印模型结构
print(model)
VGG全连接转卷积
将VGG网络中的全连接层转换为卷积层,可以将其应用于输入尺寸不固定的图像。
步骤:
-
确定输入特征图的大小:在VGG网络中,全连接层之前通常会经过多次卷积和池化操作,这些操作会改变特征图的大小。假设最后一个卷积层之后得到的特征图大小为h × w。
-
将全连接层参数转换为卷积核:由于全连接层将前一层所有神经元与当前层每个神经元都进行连接,因此权重矩阵的维度是 [前一层神经元数量, 当前层神经元数量]。我们可以将这个权重矩阵 reshape 成 [1, 1, 前一层神经元数量, 当前层神经元数量] 的四维张量。
-
新建卷积层:使用上述得到转换后的权重作为新建卷积层的参数。设置该卷积层的 stride(步长)为 1,padding(填充)为 0,以保持特征图尺寸不变。
-
替换全连接层:将原始的全连接层替换为新建的卷积层。在替换之前,需要将前一层的特征图进行展平操作,即将其 reshape 成 [1, 前一层神经元数量, h, w] 的四维张量。
比如VGG16图片示例的第一层全连接层:
输入为7x7x512的FeatureMap,使用4096个7x7x512的卷积核进行卷积,由于卷积核尺寸与输入的尺寸完全相同,即卷积核中的每个系数只与输入尺寸的一个像素值相乘一一对应,根据公式:
(input_size + 2 * padding - kernel_size) / stride + 1=(7+2*0-7)/1+1=1
得到输出是1x1x4096。相当于4096个神经元,但属于卷积层。
特点
-
网络结构简洁:VGG网络的整体结构非常简单直观,由多个相同大小的卷积层和池化层堆叠而成。这种简洁的结构使得VGG网络易于理解和实现。
-
小卷积核:VGG网络使用了较小的卷积核尺寸(通常是3x3),这样可以减少模型参数量,同时增加了非线性表达能力。
-
小池化核:VGG网络采用了小尺寸的池化核(通常是2x2),这样可以在保持特征图大小不变的情况下减少空间维度,并且增强了特征的平移不变性。
-
通道数更多,特征度更宽:VGG网络相对于以往的模型来说,在每个卷积层中使用更多的通道数,这样可以增加模型对图像细节和纹理等低级信息的感知能力。通过增加通道数,VGG网络可以捕获更广泛和丰富的特征。
-
层数更深:相比于AlexNet等早期CNN模型,VGG网络采用了更深的结构。它使用了16或19层的卷积层和全连接层,这样更深的网络结构可以提取更高级别、抽象化的特征表示。