使用PyTorch实现的ResNet-50模型

ResNet(残差网络)是一种深度卷积神经网络结构,通过引入残差块(Residual Block)解决了深度网络中的梯度消失和梯度爆炸问题,使得可以训练非常深的网络。

使用PyTorch实现的ResNet-50模型

FixedBatchNorm类

定义FixedBatchNorm类,继承自nn.BatchNorm2d,并在forward方法中使用F.batch_norm函数,这是为了固定批量归一化的行为。

class FixedBatchNorm(nn.BatchNorm2d):
    def forward(self, input):
    output = F.batch_norm(input,    # 输入数据,即卷积层的输出
                              self.running_mean,    # 训练过程中累积的样本均值,训练过程中被动更新,推理阶段用于标准化数据。
                              self.running_var,     # 训练过程中累积的样本方差,训练过程中被动更新,推理阶段用于标准化数据。
                              self.weight,
                              self.bias,
                              training=False,   # 推理阶段进行批量归一化,因此不需要计算新的均值和方差,而是使用之前训练时计算得到的self.running_mean和self.running_var。
                              eps=self.eps  # eps是为了数值稳定性而添加到方差的小常量。这可以防止除以接近于零的方差,避免数值不稳定性的问题。
                              )
        return output

计算标准化的值(normalized value):

        normalized_value = \frac{input - mean}{\sqrt (var+eps)}

        output = normaiized_value * weights + bias

F.batch_norm操作在模型的卷积层之后,常常与激活函数一起使用,以促使模型更快地学习和更好地泛化。

About BatchNorm Study

Bottleneck类

定义Bottleneck类,继承自nn.Module,表示ResNet中的残差块。

ResNet 中的基本构建块,它定义了残差网络中的瓶颈结构。瓶颈结构的主要目标是减少计算成本,同时提高网络的表示能力

具体而言,Bottleneck 块在每个阶段(stage)的每个残差块中被重复使用。

class Bottleneck(nn.Module):
    # ...
class Bottleneck(nn.Module):
    expansion = 4

    def __init__(self, inplanes, planes, stride=1, downsample=None, dilation=1):
        super(Bottleneck, self).__init__()
        self.conv1 = nn.Conv2d(inplanes, planes, kernel_size=1, bias=False) # 1x1 卷积,用于减少输入特征图的维度(降维),其输出通道数为 planes
        self.bn1 = FixedBatchNorm(planes)   # Batch Normalization, 对 1x1 卷积的输出进行批量归一化。
        self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=stride,
                               padding=dilation, bias=False, dilation=dilation) # 3x3 卷积,用于学习特征,而且由于通道数相对较小,计算成本相对较低,这也是为什么称之为“瓶颈”的原因。
        self.bn2 = FixedBatchNorm(planes)
        self.conv3 = nn.Conv2d(planes, planes * 4, kernel_size=1, bias=False)   # 1x1 卷积,用于恢复输入特征图的维度(升维),其输出通道数为 planes * 4。
        self.bn3 = FixedBatchNorm(planes * 4)
        self.relu = nn.ReLU(inplace=True)   # ReLU 激活函数:对归一化后的数据进行激活。
        self.downsample = downsample    #
        self.stride = stride
        self.dilation = dilation

主要作用是引入残差连接(residual connection),允许梯度更容易地通过网络传播。残差连接通过将输入直接加到块的输出中,有助于缓解梯度消失问题,并允许训练非常深的神经网络。 

最终的output是将残差连接的结果传递给 ReLU 激活函数,这是为了引入非线性。在整个 Bottleneck 类中,expansion 参数设置为 4,表示输出通道数是输入通道数的 4 倍。

ResNet类

定义了整个 Residual Network(ResNet)的架构。ResNet 是一种深度神经网络架构,引入了残差块(Residual Block),旨在解决深层网络训练过程中的梯度消失和梯度爆炸问题。

定义ResNet类,继承自nn.Module,包含了ResNet的整体结构,包括多个残差块的堆叠。

在ResNet类的构造函数中,定义了模型的各个组成部分,包括卷积层、批量归一化层、激活函数等。 

class ResNet(nn.Module):
        def __init__(self, block, layers, strides=(2, 2, 2, 2), dilations=(1, 1, 1, 1)):
        self.inplanes = 64  # 初始通道数=64
        super(ResNet, self).__init__()
        '''初始卷积层'''
        self.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size, stride=strides[0], padding,
                               bias=False)
        self.bn1 = FixedBatchNorm()
        self.relu = nn.ReLU(inplace=True)
        '''池化层'''
        self.maxpool = nn.MaxPool2d(kernel_size, stride=2, padding=1)
        '''四个阶段,每个阶段由多个 Bottleneck 块组成。Bottleneck 块的数量由 layers 参数决定。输入通道数和输出通道数随着阶段的增加而增加。'''
        # 主要用于捕捉输入数据中的低级特征,例如边缘、颜色等。由于这个阶段位于网络的起始部分,对图像的原始特征进行初步处理和提取。
        self.layer1 = self._make_layer(block, planes, layers[0], stride=1, dilation=dilations[0])
        # 在第一阶段的基础上,进一步学习中级特征,如纹理、形状等。引入更多的Bottleneck块,逐渐增加网络的深度和复杂性,提高特征表示的抽象程度。
        self.layer2 = self._make_layer(block, planes, layers[1], stride=strides[1], dilation=dilations[1])
        # 学习更高级的语义特征,例如物体部分和局部特征。增加的深度有助于网络理解图像中更复杂的模式,对物体的更深层次的特征有更好的捕捉。
        self.layer3 = self._make_layer(block, planes, layers[2], stride=strides[2], dilation=dilations[2])
        # 在网络的最深层次上进行全局特征建模,包括整体物体的语义信息。通过更深的层次,网络能够捕捉图像中更全局、更高级的特征,从而提高对整个图像内容的理解。
        self.layer4 = self._make_layer(block, planes, layers[3], stride=strides[3], dilation=dilations[3])

        self.inplanes = 1024


MaxPool2d study

定义_make_layer方法,用于构建每个阶段的残差块。

def _make_layer(self, block, planes, blocks, stride=1, dilation=1):
    ''' 如果stride不为1,或者self.inplanes不等于planes*block.expansion则创建一个下采样'''
    downsample = None
    if stride != 1 or self.inplanes != planes * block.expansion:
        downsample = nn.Sequential(
            nn.Conv2d(self.inplanes, planes * block.expansion,
            kernel_size=1, stride=stride, bias=False),    # 1*1卷积
            FixedBatchNorm(planes * block.expansion),   # 批量归一化
            )

    layers = [block(self.inplanes, planes, stride, downsample, dilation=1)]
    '''更新self.inplanes以匹配当前阶段的输出通道数'''
    self.inplanes = planes * block.expansion
    for i in range(1, blocks):
    layers.append(block(self.inplanes, planes, dilation=dilation))
    '''将列表中的块组成一个序列,并返回这个序列作为阶段的输出'''
    out_layer = nn.Sequential(*layers)
    return out_layer

 为了保持输入和输出的维度一致,当 stride 不等于 1 或者当前层的输入通道数不等于 planes * block.expansion 时,会引入下采样(downsample)操作。

        具体来说,当 stride 不为 1 时,卷积操作会导致特征图的尺寸减小,为了保持尺寸一致,需要引入下采样。同时,如果输入通道数与输出通道数不一致,也需要引入下采样,以便将输入的通道数调整到与输出通道数一致,从而能够正确地进行残差连接

        下采样的引入有助于保持网络中特征图尺寸的一致性,使得残差块能够正确地进行残差连接,从而更好地传播梯度并促进模型训练。这种设计也是 ResNet 架构成功的一个关键因素,有助于训练非常深层的神经网络。

定义前向传播方法forward,描述了数据在模型中的流动。

def forward(self, x):
    # ···

 resnet50函数

定义resnet50函数,用于创建ResNet-50模型实例。如果pretrained为True,还会加载预训练权重。

def resnet50(pretrained=True, **kwargs):

    model = ResNet(Bottleneck, layers, **kwargs)
    # 预训练模型
    if pretrained
        state_dict = torch.load(model_path)
        # 删除不需要的全连接层权重
        state_dict.pop('fc.weight', None)
        state_dict.pop('fc.bias', None)
        model.load_state_dict(state_dict)
    
    return model

这样设计的 ResNet-50 模型是由论文"Deep Residual Learning for Image Recognition"中提出的,。这个结构的设计旨在在保持模型深度的同时,有效地捕捉和学习图像中的抽象特征。

  • 25
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
以下是ResNet101的PyTorch代码实现: ```python import torch import torch.nn as nn import torch.nn.functional as F class Bottleneck(nn.Module): expansion = 4 def __init__(self, in_planes, planes, stride=1): super(Bottleneck, self).__init__() self.conv1 = nn.Conv2d(in_planes, planes, kernel_size=1, bias=False) self.bn1 = nn.BatchNorm2d(planes) self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=stride, padding=1, bias=False) self.bn2 = nn.BatchNorm2d(planes) self.conv3 = nn.Conv2d(planes, self.expansion*planes, kernel_size=1, bias=False) self.bn3 = nn.BatchNorm2d(self.expansion*planes) self.shortcut = nn.Sequential() if stride != 1 or in_planes != self.expansion*planes: self.shortcut = nn.Sequential( nn.Conv2d(in_planes, self.expansion*planes, kernel_size=1, stride=stride, bias=False), nn.BatchNorm2d(self.expansion*planes) ) def forward(self, x): out = F.relu(self.bn1(self.conv1(x))) out = F.relu(self.bn2(self.conv2(out))) out = self.bn3(self.conv3(out)) out += self.shortcut(x) out = F.relu(out) return out class ResNet(nn.Module): def __init__(self, block, num_blocks, num_classes=1000): super(ResNet, self).__init__() self.in_planes = 64 self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3, bias=False) self.bn1 = nn.BatchNorm2d(64) self.layer1 = self._make_layer(block, 64, num_blocks[0], stride=1) self.layer2 = self._make_layer(block, 128, num_blocks[1], stride=2) self.layer3 = self._make_layer(block, 256, num_blocks[2], stride=2) self.layer4 = self._make_layer(block, 512, num_blocks[3], stride=2) self.avgpool = nn.AdaptiveAvgPool2d((1, 1)) self.fc = nn.Linear(512*block.expansion, num_classes) def _make_layer(self, block, planes, num_blocks, stride): strides = [stride] + [1]*(num_blocks-1) layers = [] for stride in strides: layers.append(block(self.in_planes, planes, stride)) self.in_planes = planes * block.expansion return nn.Sequential(*layers) def forward(self, x): out = F.relu(self.bn1(self.conv1(x))) out = F.max_pool2d(out, kernel_size=3, stride=2, padding=1) out = self.layer1(out) out = self.layer2(out) out = self.layer3(out) out = self.layer4(out) out = self.avgpool(out) out = out.view(out.size(0), -1) out = self.fc(out) return out def ResNet101(): return ResNet(Bottleneck, [3, 4, 23, 3]) ``` 这个代码实现ResNet101的结构。其中,`Bottleneck`类实现ResNet中的一个基本块,`ResNet`类按照ResNet的结构将多个基本块组成一个完整的网络,并且实现了前向传播的函数。最后,`ResNet101`函数返回一个ResNet101的实例。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值