ResNet (Residual net)是残差网络的通用概念,而 ResNet50 是一个具体的网络结构,其由50个卷积层组成。ResNet50 是指包含了50个卷积层(包括卷积层、池化层、全连接层等)的 ResNet 网络。ResNet50 是基于 ImageNet 数据集上的训练所提出的一个具体网络结构。
ResNet 核心:在最终输出中,除了包含对输入 x 的通过传统的通过各卷积层得到的结果,还引入了 x 本身,保证了不会产生因为卷积层的加深而导致的梯度消失以及原有特征的消失的问题
ResNet 核心有两种 Block:Conv Block 和 Identify Block。Conv 块的目标是学习特征的非线性映射,从而提取图像的高级特征,Identify 块的目的是跳跃地将输入传递到输出口,方便和卷积后的结果线性相加。
代码具体如下 :
导入库:
import torch
from torch import nn
写一个名为 Bottleneck 的残差 Block:
'''
Block的各个plane值:
inplane:输出block的之前的通道数
midplane:在block中间处理的时候的通道数(这个值是输出维度的1/4)
midplane*self.extention:输出的维度
'''
class Bottleneck(nn.Module):
#定义了一个名为 Bottleneck 的 PyTorch 模型类,该类继承自 nn.Module
extention=4
#定义extention变量,表示 Bottleneck 残差块中维度拓展的倍数,即输出通道数相对于中间通道数的倍数,也即每个stage中维度拓展的倍数
#定义初始化的网络和参数
def __init__(self,inplane,midplane,stride,downsample=None):
super(Bottleneck,self).__init__()
#四个参数:inplane 表示输入通道数,midplane 表示中间处理时的通道数,stride 表示步长,downsample 用于定义跳跃连接中的下采样层。这里使用了可选参数的形式,并且选择了不传入 downsample,写为 None
#接着调用了父类 nn.Module 的初始化函数,确保正确地初始化 Bottleneck 类
self.conv1=nn.Conv2d(inplane,midplane,kernel_size=1,stride=stride,bias=False)
self.bn1=nn.BatchNorm2d(midplane)
#这两行定义了第一个卷积层 conv1 和批量归一化层 bn1。Conv2d 表示二维卷积层,inplane 为输入通道数,midplane 为输出通道数,kernel_size=1 表示卷积核大小为 1x1,stride=stride 表示步长与参数传入的 stride 相同,bias=False 表示不使用偏置。BatchNorm2d 则是二维批量归一化层,对 midplane 通道进行批量归一化
#批量归一化层(Batch Normalization)对于每个训练小批量数据,批量归一化层计算该批量数据的均值和标准差,并使用这些统计量对数据进行标准化处理,使得数据的均值接近于零,标准差接近于一
self.conv2=nn.Conv2d(midplane,midplane,kernel_size=3,stride=1,padding=1,bias=False)
self.bn2=nn.BatchNorm2d(midplane)
#这两行定义了第二个卷积层 conv2 和批量归一化层 bn2。这个卷积层的输入和输出通道数都是 midplane,kernel_size=3 表示卷积核大小为 3x3,stride=1 表示步长为 1,padding=1 表示在图像周围填充一个像素,保持输入输出大小相同
self.conv3=nn.Conv2d(midplane,midplane*self.extention,kernel_size=1,stride=1,bias=False)
self.bn3=nn.BatchNorm2d(midplane*self.extention)
#这两行定义了第三个卷积层 conv3 和批量归一化层 bn3。这个卷积层的输入通道数是 midplane,输出通道数是 midplane*self.extention,即 midplane 乘以类变量 extention 的值,相当于对中间通道数进行了拓展
self.relu=nn.ReLU(inplace=False)
#定义了 ReLU 激活函数
self.downsample=downsample
self.stride=stride
#这两行分别将初始化函数的参数 downsample 和 stride 分配给类的属性
def forward(self,x):
#参差数据
residual=x
#将输入 x 赋值给变量 residual,用于后续的跳跃连接
out=self.relu(self.bn1(self.conv1(x)))
out=self.relu(self.bn2(self.conv2(out)))
out=self.relu(self.bn3(self.conv3(out)))
#卷积操作,依次经过了三个卷积层和批量归一化层,并使用 ReLU 激活函数
#是否直连(如果时Identity block就是直连;如果是Conv Block就需要对参差边进行卷积,改变通道数和size)
if(self.downsample!=None):
residual=self.downsample(x)
#将参差部分和卷积部分相加
out+=residual
out=self.relu(out)
return out
写 ResNet 结构:
class ResNet(nn.Module):
#初始化网络结构和参数
def __init__(self,block,layers,num_classes=1000):
#self.inplane为当前的fm的通道数,初始化值为64
self.inplane=64
super(ResNet,self).__init__()
#调用了父类 nn.Module 的构造方法
#参数
self.block=block
self.layers=layers
#stem的网络层
self.conv1=nn.Conv2d(3,self.inplane,kernel_size=7,stride=2,padding=3,bias=False)
#定义了第一个卷积层(conv1),使用了7x7的核大小,2的步长,3的填充。它接受具有3个通道(用于RGB图像)的输入,并产生self.inplane个输出通道
self.bn1=nn.BatchNorm2d(self.inplane)
#初始化了第一个卷积层的批量归一化层
self.relu=nn.ReLU()
#初始化了ReLU激活函数
self.maxpool=nn.MaxPool2d(kernel_size=3,padding=1,stride=2)
#初始化了最大池化层,核大小为3x3,填充为1,步长为2
#64,128,256,512是指扩大4倍之前的维度,即Identity Block的中间维度
self.stage1=self.make_layer(self.block,64,self.layers[0],stride=1)
#使用make_layer方法创建了网络的第一个阶段。它采用了block类型,64作为中间平面维度,并采用layers[0]中指定的层数
#self.block指定了残差块类型,64是指此阶段中残差块的输出通道数,self.layers[0]是指这个阶段中残差块的数量,self.layers是一个列表,包含了每个阶段所包含的残差块的数量,[0]即代表这个第一个阶段
self.stage2=self.make_layer(self.block,128,self.layers[1],stride=2)
self.stage3=self.make_layer(self.block,256,self.layers[2],stride=2)
self.stage4=self.make_layer(self.block,512,self.layers[3],stride=2)
#与第一阶段同理
#后续的网络
self.avgpool=nn.AvgPool2d(7)
#初始化了平均池化层,核大小为7x7
self.fc = nn.Linear(512 * block.extention, num_classes)
#初始化了用于分类的全连接层(fc)。输入大小是根据最后一个阶段的输出通道数(512 * block.extention)和类别数(num_classes)计算得出的
def forward(self,x):
#定义了模型,输入为 x
#stem部分:conv+bn+relu+maxpool
out=self.conv1(x)
#通过第一个卷积层处理输入 x,得到输出 out
out=self.bn1(out)
#对输出进行批量归一化操作
out=self.relu(out)
#对输出进行 ReLU 激活函数操作
out=self.maxpool(out)
#对输出进行最大池化操作
#block
out=self.stage1(out)
out=self.stage2(out)
out=self.stage3(out)
out=self.stage4(out)
#分别通过四个阶段(stage)的残差块处理输出
#分类
out=self.avgpool(out)
#对输出进行全局平均池化操作
out = torch.flatten(out, 1)
#将输出展平为一维
out=self.fc(out)
#通过全连接层进行分类预测
return out
def make_layer(self,block,midplane,block_num,stride=1):
'''
block:block模块
midplane:每个模块中间运算的维度,一般等于输出维度/4
block_num:重复次数
stride:Conv Block的步长
'''
block_list=[]
#创建一个空列表,用于存储残差块
#先计算要不要加downsample模块
downsample=None
if(stride!=1or self.inplane!=midplane*block.extention):
#判断是否需要添加下采样模块
downsample=nn.Sequential(
nn.Conv2d(self.inplane,midplane*block.extention,stride=stride,kernel_size=1,bias=False),
nn.BatchNorm2d(midplane*block.extention)
)
#如果需要下采样,则创建一个下采样模块,包括一个 1x1 的卷积层和批量归一化层
#下采样模块的作用是将输入的特征图的空间尺寸减小,同时增加通道数量,以便在网络的不同阶段进行特征图的匹配和组合
#Conv Block
conv_block=block(self.inplane,midplane,stride=stride,downsample=downsample)
block_list.append(conv_block)
#创建一个 Conv Block,包括一个残差块,将 Conv Block 添加到 block_list 中
self.inplane=midplane*block.extention
#更新输入平面大小
#Identity Block
for i in range(1,block_num):
#循环创建 Identity Block
block_list.append(block(self.inplane,midplane,stride=1))
#创建一个 Identity Block 并添加到 block_list 中
return nn.Sequential(*block_list)
#返回由所有残差块组成的 Sequential 模块
实例调用:
resnet = ResNet(Bottleneck, [3, 4, 6, 3])
#由4个阶段组成,每个阶段包含不同数量的块。数字[3, 4, 6, 3]表示每个阶段中的块数
x=torch.randn(1,3,224,224)
#创建了一个张量x,其中包含从正态分布中采样的随机数。张量x的形状是(1, 3, 224, 224),表示它代表一个具有3个通道(RGB)和分辨率为224x224像素的图像
x=resnet(x)
#通过ResNet模型resnet将张量x传递进去,从而实现了对网络的前向传播。输出的x将是ResNet模型进行分类的结果
print(x.shape)
#打印输出张量x的形状,表明x的维度,形式为(batch_size, num_classes),其中batch_size是在批处理中处理的输入样本数,num_classes是模型预测的输出类别数
结果显示:
Torch. Size ([1,1000])