深度学习之图像分类(一)
前言
图像回归主要是对全连接进行理解 而图像分类任务主要是对卷积的过程进行理解 这一部分会介绍一些基础的概念 卷积的过程(包括单通道和多通道) 理解一个卷积神经网络工作的过程 以及常见的模型的类别和创新点
图像分类是什么
定义
图像分类是指将输入的图像分配到一个或多个预定义的类别中的任务。简单来说,就是让计算机“看”一张图片,然后告诉用户这张图片属于什么类别。例如,判断一张图片是猫、狗还是汽车等。
与回归任务的区别
-
回归和分类的主要区别在于目标变量的类型(连续 vs. 离散)。
-
回归关注的是预测具体的数值,而分类关注的是将数据划分到不同的类别中。
-
回归解决函数拟合的问题 而回归任务注重决策边界划分的问题
训练流程的区别
图像分类与图像回归输出的对比
特性 | 图像分类 | 图像回归 |
---|---|---|
输出类型 | 类别标签或概率分布 | 连续数值 |
输出层激活函数 | Softmax(多类别)或Sigmoid(二分类) | 线性(无激活函数) |
损失函数 | 交叉熵损失 | 均方误差(MSE)、平均绝对误差(MAE) |
应用场景 | 判断图像内容(如猫/狗分类、疾病诊断) | 预测连续数值(如物体大小、位置、年龄) |
输出示例 | “猫” 或 “猫的概率为0.8,狗的概率为0.2” | “宽度为120像素” 或 “[100, 150, 20 |
评价的区别
卷积神经网络架构
一个典型的CNN结构通常按照以下顺序堆叠:
-
输入层:输入图像或特征图。
-
卷积层 + 激活层(可选):提取局部特征。
-
池化层(可选):降低特征图尺寸。
-
重复步骤2和3:通过多层卷积和池化提取更高级的特征。
-
归一化层(可选):对特征进行归一化处理。
-
Dropout层(可选):防止过拟合。
-
全连接层:整合特征,输出最终结果。
-
输出层:根据任务类型,输出分类概率(Softmax)或回归值。
卷积神经网络(CNN)相关概念
1. 卷积运算
卷积运算是CNN的核心操作,用于从输入数据中提取特征。它通过卷积核(滤波器)在输入数据上滑动并计算点积,生成特征图(Feature Map)。
2. 输入通道
输入通道是指输入图像的维度。例如:
-
对于灰度图像,输入通道为1。
-
对于RGB图像,输入通道为3(分别对应红、绿、蓝三个颜色通道)。
3. 输出通道
输出通道是指卷积层输出的特征图数量。每个卷积核会生成一个特征图,因此输出通道的数量等于卷积核的数量。
4. 卷积核(滤波器)
卷积核是一个小的矩阵,用于在输入数据上滑动并计算卷积。卷积核的大小(如3×3、5×5)和数量决定了卷积层的复杂性和特征提取能力。
5. 特征图(Feature Map)
特征图是卷积运算的输出,表示输入数据在某个特定卷积核下的特征表示。每个特征图对应一个卷积核。
6. Padding(填充)
填充是在输入数据的边界添加额外的零值像素,用于控制特征图的大小。常见的填充方式包括:
-
无填充(Valid Padding):不添加任何填充,特征图的大小会减小。
-
同态填充(Same Padding):添加填充使特征图的大小与输入数据相同。
控制输出大小:填充可以灵活调整输出特征图的空间维度。
6.1 填充是什么
填充是指在输入图像或特征图的边缘添加额外的像素(通常为0),以控制卷积操作后的输出尺寸。在每张图的长宽上填充 左右上下都要填充
6.2填充的作用
-
保留边界信息:在没有填充的情况下,卷积操作会使输出特征图的尺寸逐渐减小,导致边缘信息丢失。
-
保持输入输出尺寸一致:通过填充,可以使卷积后的特征图与输入图像保持相同的尺寸。
6.3填充的方式
-
零填充(Zero Padding):在输入图像周围添加零值像素。这是最常用的填充方式,因为它简单且计算效率高。
-
边缘复制填充(Edge Replication Padding):将输入图像边缘的像素复制到填充区域。
-
镜像填充(Mirror Padding):将输入图像边缘的像素进行镜像翻转后填充。
7. 步长(Stride)
步长是卷积核在输入数据上滑动的步数。步长为1表示卷积核每次移动一个像素;步长为2表示每次移动两个像素。较大的步长会导致特征图尺寸减小。
7.1 步长是什么
步长是指卷积核在输入图像上滑动的步数。步长越大,卷积核滑动越快,输出特征图的尺寸越小。
7.2 不同步长的作用
-
控制输出尺寸:较大的步长可以快速减小特征图的尺寸,减少计算量。
-
增加感受野:较大的步长可以使卷积核覆盖更广的区域,从而增加模型的感受野
8. 卷积过程计算
卷积过程是通过卷积核在输入数据上滑动并计算点积来完成的。
9. 多通道卷积
在多通道输入(如RGB图像)中,卷积核会同时作用于所有通道,并将结果相加生成一个特征图。例如,对于一个3×3×3的卷积核和一个5×5×3的输入数据,卷积核会分别与每个通道进行卷积运算,然后将结果相加。
池化层(Pooling Layer)
1. 池化层是什么?
池化层用于降低特征图的空间维度,减少计算量并提取重要特征。常见的池化操作包括:
-
最大池化(Max Pooling):取池化窗口内的最大值。
-
平均池化(Average Pooling):取池化窗口内的平均值。
2. 为什么使用池化层?
-
降低计算量:减少特征图的尺寸。
-
提取重要特征:保留关键信息,去除冗余信息。
-
增强模型的平移不变性:对输入数据的小范围平移不敏感。
3. 池化层工作原理?
池化层通常在卷积层之后使用,窗口大小和步长是其主要参数。例如,一个2×2的最大池化层,步长为2,会将特征图的尺寸减半。
归一化技术
1. 归一化是什么?
归一化是将输入数据或特征值调整到一个统一的范围(如0到1或-1到1)。常见的归一化技术包括:
-
Min-Max Scaling:将数据缩放到0到1之间。
-
Z-Score标准化:将数据转换为均值为0、标准差为1的分布。
-
Batch Normalization:在训练过程中对每个小批量数据进行归一化,加速训练并提高模型性能。
2. 为什么使用归一化?
-
加速训练:归一化后的数据分布更均匀,有助于优化算法更快收敛。
-
提高模型性能:避免某些特征对模型的影响过大。
-
减少数值不稳定:避免梯度爆炸或消失问题。
3. 怎么办?
-
数据预处理阶段:对输入图像进行归一化处理。
-
模型训练阶段:使用Batch Normalization等技术对中间层的特征进行归一化。
损失函数与交叉熵损失
1. 损失函数的定义
损失函数(Loss Function)是机器学习和深度学习中用于衡量模型预测值与真实值之间差异的函数。它的目的是通过优化模型的参数,使损失值最小化,从而提高模型的预测准确性。
2. 交叉熵损失的定义
交叉熵损失(Cross-Entropy Loss)是深度学习中分类问题常用的损失函数,特别适用于多分类任务。它通过度量预测分布与真实分布之间的差异来衡量模型输出的准确性。
3. 交叉熵损失的数学公式
-
二分类问题:假设真实标签 y∈{0,1},预测概率为 y^,则交叉熵损失公式为:
当真实类别 y=1 时,若模型预测 y^ 接近 1(正确预测),损失接近 0;若预测接近 0(错误预测),损失接近无穷大。
-
多分类问题:假设 K 为类别数,y 为真实类别(one-hot 编码),y^i 为第 i 类的预测概率,则交叉熵损失公式为:
由于 one-hot 编码中只有真实类别的 yi=1,其余为 0,因此公式可以简化为:
其中 c 是真实类别的索引。
4. 交叉熵损失与 Softmax 的关系
交叉熵损失通常与 Softmax 函数结合使用。Softmax 函数将模型输出的 logits(未归一化的分数)转换为概率分布:
其中 zi 是第 i 类的 logits。
5. 交叉熵损失的应用
交叉熵损失广泛应用于分类任务,如图像分类、文本分类等。它能够对错误的高置信度预测施加较大的惩罚,从而促进模型学习更准确的概率分布。
交叉熵损失 是分类问题中的核心工具,用于衡量预测分布与真实分布之间的差异。
它通过 Softmax 函数将 logits 转换为概率分布,然后计算损失。
常见网络架构
AlexNet
AlexNet的创新点
-
ReLU激活函数:引入了修正线性单元(ReLU)激活函数,它能够加速网络的训练并提高模型的泛化能力。
-
Dropout:使用了Dropout技术来防止过拟合,通过在训练过程中随机丢弃一部分神经元,使模型更加鲁棒。
-
池化:通过池化层降低特征图的维度,同时保留重要特征,增强模型的平移不变性。
-
归一化:它可以让模型关注数据的分布,而不受数据量纲的影响。 保持学习有效性, 缓解梯度消失和梯度爆炸。
实现
import torch.nn as nn
class myAlexNet(nn.Module):
def __init__(self, out_dim):
super(myAlexNet, self).__init__()
self.conv1 = nn.Conv2d(3,64,11,4,2)
self.pool1 = nn.MaxPool2d(3, 2)
self.conv2 = nn.Conv2d(64,192,5,1,2)
self.pool2 = nn.MaxPool2d(3, 2)
self.conv3 = nn.Conv2d(192,384,3,1,1)
self.conv4 = nn.Conv2d(384, 256, 3, 1, 1)
self.conv5 = nn.Conv2d(256, 256, 3, 1, 1)
self.pool3 = nn.MaxPool2d(3, 2)
self.pool4 = nn.AdaptiveAvgPool2d(6)
self.fc1 = nn.Linear(9216, 4096)
self.fc2 = nn.Linear(4096, 4096)
self.fc3 = nn.Linear(4096, out_dim)
def forward(self,x):
x =self.conv1(x)
x = self.pool1(x)
x =self.conv2(x)
x = self.pool2(x)
x =self.conv3(x)
x =self.conv4(x)
x = self.conv5(x)
x = self.pool3(x)
x = self.pool4(x)
x = x.view(x.size()[0], -1) #拉直。 batch
x = self.fc1(x)
x = self.fc2(x)
x = self.fc3(x)
return x
model = myAlexNet(1000)
VggNet
创新
用小卷积核代替大的卷积核
小卷积核能够代替大卷积核的关键在于它们可以通过堆叠多层来达到相同的感受野,同时减少参数数量,增加网络深度,提高模型的表达能力和计算效率。这种设计使得网络更加轻量化,易于训练和部署,同时保持或提高模型的性能。
模块化设计
比如下面的两次卷积 一次池化
实现
class vggLayer(nn.Module):#模块化
def __init__(self,in_cha, mid_cha, out_cha):
super(vggLayer, self).__init__()
self.relu = nn.ReLU() # 定义激活函数
self.pool = nn.MaxPool2d(2) # 定义池化方式
self.conv1 = nn.Conv2d(in_cha, mid_cha, 3, 1, 1) # 定义卷积核
self.conv2 = nn.Conv2d(mid_cha, out_cha, 3, 1, 1) # 定义卷积核
def forward(self,x):
x = self.conv1(x)
x= self.relu(x)
x = self.conv2(x)
x = self.relu(x)
x = self.pool(x)
return x
class MyVgg(nn.Module):
def __init__(self): # 定义结构
super(MyVgg, self).__init__()
self.layer1 = vggLayer(3, 64, 64)
self.layer2 = vggLayer(64, 128, 128)
self.layer3 = vggLayer(128, 256, 256)
self.layer4 = vggLayer(256, 512, 512)
self.layer5 = vggLayer(512, 512, 512)
self.adapool = nn.AdaptiveAvgPool2d(7)
self.relu = nn.ReLU()
self.fc1 = nn.Linear(25088, 4096)
self.fc2 = nn.Linear(4096, 4096)
self.fc3 = nn.Linear(4096, 1000)
def forward(self,x):
x = self.layer1(x)
x = self.layer2(x)
x = self.layer3(x)
x = self.layer4(x)
x = self.layer5(x)
x = self.adapool(x)
x = x.view(x.size()[0], -1)
x = self.fc1(x)
x = self.relu(x)
x = self.fc2(x)
x = self.relu(x)
x = self.fc3(x)
x = self.relu(x)
return x
ResNet
创新点
没问题,我们可以用更简单的方式来解释1x1卷积和残差连接(Residual Connection),这样即使是初学者也能理解。
1x1卷积
想象一下,你有一堆彩色的画笔(代表图像中的颜色通道),每个画笔都有不同的颜色。1x1卷积就像是一个神奇的画笔混合器,它不改变画笔的数量(也就是图像的宽度和高度),但可以改变画笔的颜色组合(也就是改变图像的通道数)。
-
为什么使用1x1卷积?
-
减少计算量:如果图像有很多通道,1x1卷积可以减少通道的数量,从而减少后续计算的复杂度。
-
增加非线性:虽然1x1卷积本身是线性操作,但通常我们会在它后面加上一个非线性激活函数(比如ReLU),这样可以增加模型的表达能力。
-
残差连接(Residual Connection)
现在,想象你正在学习画一幅画。残差连接就像是你有一个基础的画(输入),然后你尝试在这幅画上添加更多的细节(通过神经网络的层)。但是,有时候添加的细节太多,反而让画变得混乱。
-
什么是残差连接?
-
直接连接:残差连接允许你将基础的画(输入)直接复制一份,然后和添加的细节(通过神经网络层处理后的输出)放在一起。这样,即使添加的细节不完美,最终的画也不会太差,因为基础的画还在。
-
解决梯度消失问题:在深层神经网络中,信息从一层传递到另一层时可能会丢失(梯度消失)。残差连接通过直接将输入传递到后面的层,帮助信息更顺畅地流动,从而解决这个问题。
-
-
为什么使用残差连接?
-
更容易训练深层网络:残差连接使得训练非常深的神经网络变得更容易,因为信息可以更容易地从一层传递到另一层。
-
提高性能:在很多情况下,使用残差连接的网络比没有使用残差连接的网络表现得更好。
-
总结
-
1x1卷积:是一种特殊的卷积操作,可以改变图像的通道数,但不改变图像的宽度和高度。它常用于减少计算量和增加非线性。
-
残差连接:是一种网络结构设计,允许输入直接跳过一些层,与后面的层的输出相加。它有助于解决深层网络训练中的梯度消失问题,并提高网络的性能。
希望这个解释能帮助你更好地理解这两个概念!如果你还有其他问题,随时可以问。
实现
class Residual_block(nn.Module): #@save
def __init__(self, input_channels, out_channels, down_sample=False, strides=1):
super().__init__()
self.conv1 = nn.Conv2d(input_channels, out_channels,
kernel_size=3, padding=1, stride=strides)
self.conv2 = nn.Conv2d(out_channels, out_channels,
kernel_size=3, padding=1, stride= 1)
if input_channels != out_channels:
self.conv3 = nn.Conv2d(input_channels, out_channels,
kernel_size=1, stride=strides)
else:
self.conv3 = None
self.bn1 = nn.BatchNorm2d(out_channels)
self.bn2 = nn.BatchNorm2d(out_channels)
self.relu = nn.ReLU()
def forward(self, X):
out = self.relu(self.bn1(self.conv1(X)))
out= self.bn2(self.conv2(out))
if self.conv3:
X = self.conv3(X)
out += X
return self.relu(out)
class MyResNet18(nn.Module):
def __init__(self):
super(MyResNet18, self).__init__()
self.conv1 = nn.Conv2d(3, 64, 7, 2, 3)
self.bn1 = nn.BatchNorm2d(64)
self.pool1 = nn.MaxPool2d(3, stride=2, padding=1)
self.relu = nn.ReLU()
self.layer1 = nn.Sequential(
Residual_block(64, 64),
Residual_block(64, 64)
)
self.layer2 = nn.Sequential(
Residual_block(64, 128, strides=2),
Residual_block(128, 128)
)
self.layer3 = nn.Sequential(
Residual_block(128, 256, strides=2),
Residual_block(256, 256)
)
self.layer4 = nn.Sequential(
Residual_block(256, 512, strides=2),
Residual_block(512, 512)
)
self.flatten = nn.Flatten()
self.adv_pool = nn.AdaptiveAvgPool2d(1)
self.fc = nn.Linear(512, 1000)
def forward(self, x):
x = self.conv1(x)
x = self.bn1(x)
x = self.relu(x)
x = self.pool1(x)
x = self.layer1(x)
x = self.layer2(x)
x = self.layer3(x)
x = self.layer4(x)
x = self.adv_pool(x)
x = self.flatten(x)
x = self.fc(x)
return x
myres = MyResNet18()