ResNet《Deep Residual Learning for Image Recognition》

ResNet论文学习

  • 引言
  • Deep Residual Learning 深度残差学习
    • Residual Learning 残差学习
    • Identity Mapping by Shortcuts 通过捷径来恒等映射
    • 网络结构
    • Plain Network
    • Residual Network
    • 实现细节
  • 实验
  • 总结
  • 代码复现
    • Building block
    • Bottleneck
    • Resnet 18
    • Resnet 34
    • Resnet 50

引言

深度网络自然地以端到端的多层方式集成低/中/高级特征和分类器,特征的“级别”可通过堆叠层的数量来丰富

随着网络层数加深,提取的特征越强,但是

网络层数越深就一定性能越好吗?答案是否定的

深层网络存在的问题

  • 梯度消失/梯度爆炸
    这个问题已经通过 normalized initialization 和 intermediate normalization layers 得到了很大程度的解决,这使得具有数十层的网络能够开始收敛随机梯度下降(SGD)与反向传播

  • 网络退化
    随着网络层数的增加,准确率趋于饱和,然后迅速下降。这说明,并不是所有的网络结构都同样容易优化。

为了解释这一现象,作者考虑了一个较浅的网络结构及其对应的更深版本,后者在前者的基础上增加了更多的层。

理论上,对于这个更深的网络,可以构造一个解:新增加的层执行 identity mapping (即直接将输入传递到输出),而其他层则复制自较浅网络中已经学习到的层(就是把较浅的网络搬过来)。这样构造出的解应该能使得更深的网络不会比其较浅的对应网络有更高的训练误差。

然而,实验显示,目前使用的优化算法似乎无法找到与这个构造解相当好或更好的解,或者在合理的时间内无法找到这样的解。这意味着,尽管理论上更深的网络不应该有更差的性能,但在实际优化过程中却遇到了困难。这也是引入残差学习(ResNet)的初衷之一,即通过学习残差来缓解这种优化难题。

为了解决深度神经网络训练中的退化问题,作者提出了深度残差学习框架。

在这个框架中,不再期望网络的几层直接拟合一个 underlying mapping,而是让这些层去拟合一个残差映射(residual mapping)。

具体而言,

假设 underlying mapping 是 H ( x ) \mathcal{H}(x) H(x),那么让堆叠的非线性层去拟合另一个映射 F : = H ( x ) − x \mathcal{F}:=\mathcal{H}(x)-x F:=H(x)x,那么原始的映射就变成了 F ( x ) + x \mathcal{F}(x)+x F(x)+x

假设优化残差映射 (residual mapping) 比优化原始的、未引用的映射更容易。

在这里插入图片描述

图中展示了残差学习的一个基本构建块。右侧的分支直接将输入 x x x 通过一个“快捷连接”传递到输出,这就是所谓的恒等映射(identity mapping)。

F ( x ) \mathcal{F}(x) F(x) 表示的是一个由 权重层和非线性激活函数(例如ReLU)组成的映射,

其目的是学习输入 x x x 和输出 H ( x ) \mathcal{H}(x) H(x) 之间的残差,即 F ( x ) : = H ( x ) − x \mathcal{F}(x) := \mathcal{H}(x) - x F(x):=H(x)x

图中所示的构建块的输出是通过将 F ( x ) \mathcal{F}(x) F(x) x x x 相加得到的,即 F ( x ) + x \mathcal{F}(x) + x F(x)+x,这里的 F ( x ) \mathcal{F}(x) F(x) 是通过两个权重层和非线性激活函数ReLU构成的子网络来学习的。

这样设计的好处是,如果 F ( x ) \mathcal{F}(x) F(x) 的理想输出是0,即不存在残差,那么 shortcut connection 可以使得恒等映射成为可能,即直接输出 x x x,这对于网络训练的稳定性和加速有很大帮助。

Deep Residual Learning 深度残差学习

Residual Learning 残差学习

即使理论上更深的网络应该不会比较浅的网络性能更差,因为更深的网络至少可以通过学习 恒等映射 来达到和较浅网络一样的性能。

但在实际中,传统的优化算法(solvers)在训练时 很难通过多个非线性层来近似恒等映射

如果恒等映射是最优的,solvers 可以简单地 将多个非线性层的权重推向零(就是 F ( x ) \mathcal{F}(x) F(x) 这个映射趋于 0 0 0) 来近似恒等映射,而不是去学习一个复杂的映射关系

Identity Mapping by Shortcuts 通过捷径来恒等映射

作者对若干个堆叠层应用残差学习,
在这里插入图片描述

具体而言,一个 building block 被定义为:
y = F ( x , { w i } ) + x \textbf{y}=\mathcal{F}(\textbf{x},\{w_i\})+\textbf{x} y=F(x,{wi})+x 其中, x , y \textbf{x},\textbf{y} x,y 是输入和输出向量, F ( x , { w i } ) \mathcal{F}(\textbf{x},\{w_i\}) F(x,{wi}) 代表被学习的残差映射

就像图 2,有 2 个层,那么 F = W 2 σ ( W 1 x ) \mathcal{F}=W_2\sigma(W_1 \textbf{x}) F=W2σ(W1x), 其中 σ \sigma σ 代表 ReLU 函数,为了简化符号,省略了 bias

F + x \mathcal{F}+\textbf{x} F+x 操作 是通过一个 shortcut connections 和 element-wise 加法来执行

加法执行后采用第二个 ReLU 函数应用非线性,如图 2

其中 x \textbf{x} x F \mathcal{F} F 的维度必须一致,如果不一致,

可以执行一个线性投影 W s W_s Ws 匹配维度然后 shortcut connections: y = F ( x , { w i } ) + W s x \textbf{y}=\mathcal{F}(\textbf{x},\{w_i\})+W_s \textbf{x} y=F(x,{wi})+Wsx

残差函数 F \mathcal{F} F 的形式是灵活的,本文的实验中,涉及有 2 层或 3 层的残差函数 F \mathcal{F} F ,但是只有 1 层的残差函数 F \mathcal{F} F 近似于线性层 y = W 1 x + x \textbf{y}=W_1\textbf{x}+\textbf{x} y=W1x+x,作者并未观察到其优越性。

尽管为了简化表述,这一部分的描述和公式是基于全连接层的情形,但是这些概念和方法也同样适用于卷积层。

在实际的深度残差网络(ResNet)中,作者主要是在讨论卷积层的应用,因为卷积层是现代卷积神经网络中的核心组件。

函数 F ( x , { w i } ) \mathcal{F}(\textbf{x},\{w_i\}) F(x,{wi}) 可以表示多个卷积层,逐个通道地在两个特征图上执行元素加法。

网络结构

请添加图片描述

Plain Network

plain baselines 主要受 VGG nets 启发,卷积层大多具有3×3滤波器,并遵循两个简单的设计规则:

(i) 对于相同的输出特征图大小,各层具有相同数量的滤波器;

(ii) 如果特征图大小减半,则滤波器的数量增加一倍,以保持每层的时间复杂度。

通过步长为 2 的卷积层直接执行下采样。网络以一个全局平均池化层和一个带有 softmax 的 1000 路全连接层结束。图3(中间部分)加权层总数为34层。

作者的模型比VGG网络具有更少的卷积滤波器和更低的复杂度。

Residual Network

在 Plain Network 的基础上,插入 shortcut connections 将网络转换为对应的残差版本。

当输入和输出维度相同时 (图3中的实线 shortcuts),可以直接使用 y = F ( x , { w i } ) + x \textbf{y}=\mathcal{F}(\textbf{x},\{w_i\})+\textbf{x} y=F(x,{wi})+x

当维度增加时 (图3中的虚线 shortcuts),作者考虑两种选择:

(A) shortcut 仍然执行恒等映射,增加维度时填充额外的零项。这个选项不引入额外的参数;

(B) y = F ( x , { w i } ) + W s x \textbf{y}=\mathcal{F}(\textbf{x},\{w_i\})+W_s \textbf{x} y=F(x,{wi})+Wsx 中的投影用于匹配维度 (通过1×1卷积完成)。

对于这两个选项,当 shortcuts 跨越不同大小的特征图时,卷积核的步长为 2。

实现细节

对 ImageNet 的实现,随机采样图像的短边,以进行尺度增强。

从图像或其水平翻转中随机采样224×224裁剪,并减去每像素平均值。

使用标准颜色增强。在每次卷积之后在激活函数之前采用批归一化(BN)。

初始化权值,并从头开始训练所有的 plain/residual 网络。

使用SGD的小批量大小为256。学习率从0.1开始,当误差趋于平稳时除以10,模型的训练次数可达60 × 104次。

使用0.0001的权重衰减和0.9的动量。 没有使用dropout

在测试中,采用标准的 10 种作物测试进行比较研究。

为了获得最佳结果,采用了全卷积形式,并在多个尺度上平均得分(图像被调整大小)。

实验

首先评估了18层和34层的普通网。34层平面网如图3(中)所示。

18层的平面网也是类似的形式。参见表1了解详细的体系结构。

在这里插入图片描述
上表是用于测试 ImageNet 的架构。方括号中显示了构建块(参见图5),并显示了堆叠块的数量。下采样由conv3_1、conv4_1和conv5_1进行,步长为2。

在这里插入图片描述
在这里插入图片描述

总结

残差连接最核心的地方是 Identity Mapping by Shortcuts

就是说 resnet 是结合了 identify mappingshortcut connection

shortcut connection 是那些跳过一个或多个层的连接。在本文中, shortcut connection 只是执行 identify mapping,其输出被添加到堆叠层的输出中。

identify mapping 连接既不增加额外的参数,也不增加计算复杂度。整个网络仍然可以通过反向传播的SGD进行端到端训练。

下面这个残差块中,旁边的分支首先是一个 shortcut connection,而在这个 shortcut connection 之上,执行了 identify mapping 将输入恒等映射到输出端,并与 underlying mapping 进行相加
在这里插入图片描述

代码复现

Building block

import torch
import torch.nn as nn
import torch.nn.functional as F

class BuildingBlock(nn.Module):
    def __init__(self,inchannel,outchannel,stride=1):
        super(BuildingBlock,self).__init__()
        self.left = nn.Sequential(
            nn.Conv2d(inchannel,outchannel,kernel_size=3,stride=stride,padding=1,bias=False),
            nn.BatchNorm2d(outchannel),
            nn.ReLU(inplace=True),
            nn.Conv2d(outchannel,outchannel,kernel_size=3,stride=1,padding=1,bias=False),
            nn.BatchNorm2d(outchannel)
        )
        self.shortcut = nn.Sequential()
        # 卷积核步长输入和输出通道不同时需要进行下采样
        if stride != 1 or inchannel != outchannel:
            self.shortcut = nn.Sequential(
                nn.Conv2d(inchannel,outchannel,kernel_size=1,stride=stride,bias=False),
                nn.BatchNorm2d(outchannel)
            ) 
    def forward(self,x):
        out = self.left(x)
        out += self.shortcut(x)
        out = F.relu(out)
        return out

  1. 不带参数的 nn.Sequential() 会返回什么?

    不带参数的 nn.Sequential() 会返回一个空的顺序容器。这意味着如果你通过这个空的 nn.Sequential() 容器传递一个张量,它将直接返回这个张量而不做任何处理。在上面代码中,self.shortcut 初始化为一个空的 nn.Sequential(),这意味着如果没有进行下采样(即 stride == 1 并且 inchannel == outchannel),那么 shortcut 分支将简单地返回输入张量 x

  2. nn.Conv2d(inchannel, outchannel, kernel_size=3, stride=stride, padding=1, bias=False) 中等号右边的 stride 来自哪里?

    在这段代码中,strideBuildingBlock 类的构造函数的一个参数,默认值为 1。当你创建一个 BuildingBlock 实例时,你可以指定 stride 的值,这个值将用于卷积层中的步长(stride 参数)。nn.Conv2d(inchannel, outchannel, kernel_size=3, stride=stride, padding=1, bias=False) 中,等号右边的 stride 来自于 BuildingBlock 类的构造函数 def __init__(self, inchannel, outchannel, stride=1) 中的 stride 参数。

  3. if stride != 1 or inchannel != outchannel: 时,是如何实现下采样的?

    stride != 1inchannel != outchannel 时,需要对输入进行下采样以匹配主分支 (left) 的输出维度。这是通过 self.shortcut 分支实现的,它包含一个步长为 stride1x1 卷积层和一个批量归一化层。1x1 卷积用于改变通道数(如果需要),而步长 stride 用于减小特征图的空间尺寸(宽度和高度)。这样,shortcut 分支的输出就可以与主分支的输出相加了。

  4. 上面的代码中,已经在def __init__(self, inchannel, outchannel, stride=1)中指定了stride=1,为什么还需要对stride != 1进行判断?

    当创建 BuildingBlock 实例时,可以指定 stride 参数的值,包括 stride != 1 的情况。因此,需要对 stride != 1 进行判断,以决定是否需要进行下采样以匹配主分支和残差分支的输出尺寸。

在ResNet的基本块(building block)中,对于输入和输出的维度不同的情况,会使用 padding=1 来使维度相同,这样可以方便进行残差连接。

这样做的目的是为了确保在每个基本块中,输入和输出的尺寸相同,以便能够将它们相加。

nn.Conv2d() 方法用于创建一个二维卷积层。其中,bias 是一个布尔值参数,用于指定是否在卷积操作中使用偏置项(bias)。如果设置为 True,则会添加一个可学习的偏置项到输出;如果设置为 False,则不会添加偏置项。默认值为 True。

偏置(bias)是一个可学习的参数,用于调整每个卷积核在卷积操作中的作用偏置的作用类似于线性回归中的截距,它允许模型学习在没有输入时的输出值。具体地说,在卷积操作中,偏置被添加到每个卷积核的输出中,然后通过激活函数进行处理,以产生最终的特征图

偏置的引入可以增加模型的灵活性,使其能够更好地拟合训练数据。通过学习适当的偏置值,模型可以更好地捕获数据中的偏移和非线性关系。

Bottleneck

class Bottleneck(nn.Module):
    def __init__(self, inchannel, outchannel, stride=1):
        super(Bottleneck, self).__init__()
        self.left = nn.Sequential(
            nn.Conv2d(inchannel, int(outchannel / 4), kernel_size=1, stride=stride, padding=0, bias=False),
            nn.BatchNorm2d(int(outchannel / 4)),
            nn.ReLU(inplace=True),
            nn.Conv2d(int(outchannel / 4), int(outchannel / 4), kernel_size=3, stride=1, padding=1, bias=False),
            nn.BatchNorm2d(int(outchannel / 4)),
            nn.ReLU(inplace=True),
            nn.Conv2d(int(outchannel / 4), outchannel, kernel_size=1, stride=1, padding=0, bias=False),
            nn.BatchNorm2d(outchannel),
        )
        self.shortcut = nn.Sequential()
        if stride != 1 or inchannel != outchannel:
            self.shortcut = nn.Sequential(
                nn.Conv2d(inchannel, outchannel, kernel_size=1, stride=stride, bias=False),
                nn.BatchNorm2d(outchannel)
            )
 
    def forward(self, x):
        out = self.left(x)
        y = self.shortcut(x)
        out += self.shortcut(x)
        out = F.relu(out)
  • 为什么self.left 结构中,三个卷积层除了第一层的输入通道数和第三层的输出通道数以外,全部是outchannel / 4

在 ResNet 的 Bottleneck 结构中,前两个卷积层的输出通道数被设为 int(outchannel / 4) 是为了减少计算量和参数数量,同时保持网络的表达能力。

Bottleneck 结构由三个卷积层组成:第一个和第三个卷积层使用 1x1 卷积核,主要用于改变通道数;第二个卷积层使用 3x3 卷积核,负责提取空间特征。在这种设计中,第一个 1x1 卷积层将输入通道数减少到 outchannel / 4,这样第二个 3x3 卷积层就可以在较小的通道数上操作,从而减少计算量。最后,第三个 1x1 卷积层再次将通道数增加到 outchannel,恢复到原来的维度。

通过这种方式,Bottleneck 结构能够在减少计算量的同时,保持足够的表达能力,这是 ResNet 在深层网络中有效减少参数数量和计算复杂度的关键设计之一。

  • 为什么三个卷积层的padding分别是0,1,0?

在这个 Bottleneck 结构中,三个卷积层的 padding 设置是为了控制特征图的空间尺寸:

  1. 第一个卷积层使用 kernel_size=1padding=0。因为 1x1 卷积不会改变特征图的空间尺寸,所以不需要填充(padding=0)。

  2. 第二个卷积层使用 kernel_size=3padding=13x3 卷积会减小特征图的尺寸,除非使用填充。为了保持特征图的空间尺寸不变,需要使用 padding=1(当 stride=1 时)。这样,卷积操作后,特征图的宽度和高度保持不变。

  3. 第三个卷积层再次使用 kernel_size=1padding=0,原因同第一个卷积层一样,1x1 卷积不改变特征图的空间尺寸,所以不需要填充。

通过这种设置,Bottleneck 结构可以在不改变特征图空间尺寸的情况下,有效地进行特征提取和通道数的变换。

Resnet 18

在这里插入图片描述

import torch
import torch.nn as nn
import torch.nn.functional as F

def conv3x3(in_channels, out_channels, stride=1):
    return nn.Conv2d(in_channels, out_channels, kernel_size=3, stride=stride, padding=1, bias=False)

def conv1x1(in_channels, out_channels, stride=1):
    return nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=stride, bias=False)

class BasicBlock(nn.Module):
    expansion = 1

    def __init__(self, in_channels, out_channels, stride=1, downsample=None):
        super(BasicBlock, self).__init__()
        self.conv1 = conv3x3(in_channels, out_channels, stride)
        self.bn1 = nn.BatchNorm2d(out_channels)
        self.relu = nn.ReLU(inplace=True)
        self.conv2 = conv3x3(out_channels, out_channels)
        self.bn2 = nn.BatchNorm2d(out_channels)
        self.downsample = downsample

    def forward(self, x):
        identity = x

        out = self.conv1(x)
        out = self.bn1(out)
        out = self.relu(out)

        out = self.conv2(out)
        out = self.bn2(out)

        if self.downsample is not None:
            identity = self.downsample(x)

        out += identity
        out = self.relu(out)

        return out

class ResNet(nn.Module):
    def __init__(self, block, layers, num_classes=1000):
        super(ResNet, self).__init__()
        self.in_channels = 64
        self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3, bias=False)
        self.bn1 = nn.BatchNorm2d(64)
        self.relu = nn.ReLU(inplace=True)
        self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
        self.layer1 = self._make_layer(block, 64, layers[0])
        self.layer2 = self._make_layer(block, 128, layers[1], stride=2)
        self.layer3 = self._make_layer(block, 256, layers[2], stride=2)
        self.layer4 = self._make_layer(block, 512, layers[3], stride=2)
        self.avgpool = nn.AdaptiveAvgPool2d((1, 1))
        self.fc = nn.Linear(512 * block.expansion, num_classes)

    def _make_layer(self, block, out_channels, blocks, stride=1):
        downsample = None
        if stride != 1 or self.in_channels != out_channels * block.expansion:
            downsample = nn.Sequential(
                conv1x1(self.in_channels, out_channels * block.expansion, stride),
                nn.BatchNorm2d(out_channels * block.expansion),
            )

        layers = []
        layers.append(block(self.in_channels, out_channels, stride, downsample))
        self.in_channels = out_channels * block.expansion
        for _ in range(1, blocks):
            layers.append(block(self.in_channels, out_channels))

        return nn.Sequential(*layers)

    def forward(self, x):
        x = self.conv1(x)
        x = self.bn1(x)
        x = self.relu(x)
        x = self.maxpool(x)

        x = self.layer1(x)
        x = self.layer2(x)
        x = self.layer3(x)
        x = self.layer4(x)

        x = self.avgpool(x)
        x = torch.flatten(x, 1)
        x = self.fc(x)

        return x

def resnet18(num_classes=1000):
    return ResNet(BasicBlock, [2, 2, 2, 2], num_classes)

# 示例化模型
model = resnet18(num_classes=1000)


下面这个代码实现之所以一些参数和论文网络结构原图不一样,是因为这是一个简化/修改版的 ResNet18 结构。例如,在处理较小的图像数据集时(比如 CIFAR-10,其图像尺寸为 32x32),可能不需要在一开始就迅速减小空间尺寸,这种情况下使用较小的卷积核和不使用步长为 2 的操作可能更为合适。

在原始的 ResNet 结构中,较大的卷积核和步长用于减小输出特征图的尺寸,同时增加感受野,使得网络可以捕捉到更大范围的输入信息。这在处理较大的图像(如 ImageNet 数据集中的图像,尺寸通常为 224x224)时是有优势的。因此,如果代码是针对不同尺寸的数据集设计的,那么这种调整是合理的。

'''----------ResNet18----------'''
class ResNet_18(nn.Module):
    def __init__(self, ResidualBlock, num_classes=10):
        super(ResNet_18, self).__init__()
        self.inchannel = 64
        self.conv1 = nn.Sequential(
            nn.Conv2d(3, 64, kernel_size=3, stride=1, padding=1, bias=False),
            nn.BatchNorm2d(64),
            nn.ReLU(),
        )
        self.layer1 = self.make_layer(ResidualBlock, 64, 2, stride=1)
        self.layer2 = self.make_layer(ResidualBlock, 128, 2, stride=2)
        self.layer3 = self.make_layer(ResidualBlock, 256, 2, stride=2)
        self.layer4 = self.make_layer(ResidualBlock, 512, 2, stride=2)
        self.fc = nn.Linear(512, num_classes)
 
    def make_layer(self, block, channels, num_blocks, stride):
        strides = [stride] + [1] * (num_blocks - 1)  # strides=[1,1]
        layers = []
        for stride in strides:
            layers.append(block(self.inchannel, channels, stride))
            self.inchannel = channels
        return nn.Sequential(*layers)
 
    def forward(self, x):  # 3*32*32 3是通道数,32*32是特征图尺寸
        out = self.conv1(x)  # 64*32*32 卷积层conv1输出通道是64
        #layer1的输出通道是64,且strile=1,所以输出的通道数不改变
        out = self.layer1(out)  # 64*32*32 
        #layer2 strile=2,特征图尺寸宽度和高度各减小一半,通道数翻倍
        out = self.layer2(out)  # 128*16*16  
        out = self.layer3(out)  # 256*8*8 layer3同理
        out = self.layer4(out)  # 512*4*4 layer4同理
        out = F.avg_pool2d(out, 4)  # 512*1*1
        out = out.view(out.size(0), -1)  # 512
        out = self.fc(out)
        return out
  1. 为什么 self.inchannel = 64

    在 ResNet18 的初始层中,卷积层 conv1 将输入的 RGB 图像(3 通道)转换为 64 通道的特征图。因此,设置 self.inchannel = 64 是为了记录当前特征图的通道数,以便在构建后续的残差层时使用。这个值会随着网络的深入而更新,以匹配不同残差层的输入通道数。

  2. make_layer 中的参数 channels 是输入通道还是输出通道?

    make_layer 方法中,参数 channels 指的是输出通道数。每个残差块(ResidualBlock)的输出通道数由这个参数确定,而输入通道数则由 self.inchannel 维护和提供。

  3. 详细地解释 strides = [stride] + [1] * (num_blocks - 1)*layers

    • strides = [stride] + [1] * (num_blocks - 1):这行代码用于生成一个列表,其中第一个元素是 stride(可以是 1 或 2),其余元素都是 1。列表的长度等于 num_blocks。这是因为在每个残差层中,只有第一个残差块可能会改变特征图的尺寸(通过 stride 控制),而后续的残差块则保持特征图尺寸不变(即 stride=1)。例如,对于 self.layer2strides 将为 [2, 1],意味着第一个残差块将特征图尺寸减半,而第二个残差块保持尺寸不变。

    • *layers:这是 Python 中的解包操作符,用于将 layers 列表中的元素作为独立的参数传递给 nn.Sequential。这样,nn.Sequential 可以接收一个由多个残差块组成的序列,从而构建出一个完整的残差层。在这个例子中,layers 列表包含了一个残差层中的所有残差块。

在PyTorch中,avg_pool2d函数用于对二维信号(例如图像)进行平均池化操作。函数的调用形式一般是:

torch.nn.functional.avg_pool2d(input, kernel_size, stride=None, padding=0, ceil_mode=False, count_include_pad=True, divisor_override=None)

其中,input是输入的四维张量,形状为(N, C, H, W),分别代表批大小、通道数、高度和宽度。

在你提到的avg_pool2d(out, 4)中:

  • out:输入的四维张量,即要进行池化的数据。
  • 4kernel_size参数,表示池化窗口的大小。如果是一个单一的整数,那么池化窗口的高度和宽度都将是这个值。在这个例子中,池化窗口的大小是4x4。

其他参数在这个例子中使用的是默认值:

  • stride:窗口移动的步长。默认情况下,stride等于kernel_size
  • padding:在输入数据周围添加的零填充的数量。默认为0。
  • ceil_mode:是否使用向上取整来计算输出大小。默认为False。
  • count_include_pad:在计算平均值时是否包括填充的零。默认为True。
  • divisor_override:如果设置了,它将用作除数,否则使用池化区域的大小。默认为None。

因此,avg_pool2d(out, 4)将对输入张量out执行4x4的平均池化操作。

4x4的平均池化操作将会根据池化窗口的大小和步长来减小特征图(feature maps)的尺寸。在avg_pool2d(out, 4)的情况下,池化窗口的大小是4x4,且如果没有另外指定步长(stride),步长默认也是4。

假设输入特征图的尺寸是(N, C, H, W),其中:

  • N是批大小(batch size)
  • C是通道数(channel number)
  • H是特征图的高度
  • W是特征图的宽度

经过4x4的平均池化操作后,输出特征图的尺寸将变为(N, C, H/4, W/4)。这是因为池化窗口沿着特征图的高度和宽度每次移动4个单位,因此,输出特征图的高度和宽度都将是输入特征图的高度和宽度除以4(假设H和W都能被4整除)。如果H或W不能被4整除,实际的输出尺寸还会受到填充(padding)和向上取整(ceil_mode)等参数的影响。

Resnet 34

Resnet 50

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/476504.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

23.合并两个有序链表

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 示例 1: 输入:l1 [1,2,4], l2 [1,3,4] 输出:[1,1,2,3,4,4]示例 2: 输入:l1 [], l2 [] 输出:[]示…

【计算机考研】杭电 vs 浙工大 怎么选?

想求稳上岸的话,其他几所学校也可以考虑,以留在本地工作的角度考虑,这几所学校都能满足你的需求。 如果之后想谋求一份好工作,肯定优先杭电是比较稳的,当然复习的时候也得加把劲。 这个也可以酌情考虑,报…

TikTok小店运营经验分享,美国本土小店怎么做?

作为资深跨境老玩家,虽不说是经验丰富,至少也是摸清了基本的玩法思路。TikTok作为近来的跨境新蓝海,他的玩法其实并不难,作为第一批试错玩家,今天也诚心给大家分享一些美国本土小店运营经验,感兴趣的话就看…

50、C++/类的继承和多态相关学习20240318

一、c编程实现: 封装一个动物的基类,类中有私有成员:姓名,颜色,指针成员年纪; 再封装一个狗这样类,共有继承于动物类,自己拓展的私有成员有:指针成员:腿的个…

IT部门领导的角色与责任:在挑战中塑造未来

前言 在当今快节奏的商业环境中,IT部门领导扮演着至关重要的角色。他们需要具备技术专长,同时也需要展现出卓越的领导力来有效地管理团队和应对各种挑战。 一、技术创新的引领者 1. 重要角色转变 随着信息技术的迅猛发展,IT部门领导已逐渐…

[QT] QTextBrowser取消默认右键菜单项 复制链接地址

setTextInteractionFlags(Qt::TextSelectableByMouse);原理 QTextBrowser默认下有三个标志位,QTextBrowser右键菜单相关源码如下 源码链接 if ((d->interactionFlags & Qt::LinksAccessibleByKeyboard)|| (d->interactionFlags & Qt::LinksAccessible…

【SpringSecurity】十七、OAuth2授权服务器 + 资源服务器Demo

文章目录 0、库表准备1、项目结构2、基于数据库的认证3、授权服务器配置4、授权服务器效果测试5、资源服务器配置6、其他授权模式测试6.1 密码模式6.2 简化模式6.3 客户端模式6.4 refresh_token模式 相关📕:【Spring Security Oauth2 配置理论部分】 0、…

C++临时变量

本博客将讲述我学习过程中对临时变量的疑惑与理解 为什么写这篇文章? 我在学习C过程中,发现C在发生隐式转换时或者出现未命名的变量如字符串再或者在求值的时候,会出现C临时变量(系统自动生成),而这个临时…

【Hadoop大数据技术】——ZooKeeper分布式协调服务(学习笔记)

📖 前言:ZooKeeper是一个开源的分布式协调服务,它是Google Chubby的开源实现,其设计目标是将那些复杂且容易出错的分布式应用封装起来,构成一个高效可靠的原语集,并以一系列简单易用的接口提供给用户使用。…

C# 右键快捷菜单(上下文菜单)的两种实现方式

在C#中,ContextMenuStrip是一种用于创建右键菜单的控件。它提供了一种方便的方式来为特定的控件或窗体添加自定义的上下文菜单选项。有两种实现方式,如下: 一.通过ContextMenuStrip控件实现 1.从工具箱中拖一个ContextMenuStrip控件到窗体上…

尝试搭建谷粒商城 记录(四)

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 本文链接:https://blog.csdn.net/weixin_44190665/article/details/121043585 ———————————————— 版权声明&#xff1…

爬虫工作量由小到大的思维转变---<第四十九章 Scrapy 降维挖掘---中间件系列(1)>

前言: Scrapy是一个功能强大的网络爬虫框架,但在实际应用过程中,中间件问题可能会成为一个令人头痛的难题。为了彻底解决Scrapy中的各种疑难杂症,我决定进行第四次全面的学习和实践,并将中间件的问题一一拆解&#xff…

【DL经典回顾】激活函数大汇总(四十二)(CosReLU附代码和详细公式)

激活函数大汇总(四十二)(CosReLU附代码和详细公式) 更多激活函数见激活函数大汇总列表 一、引言 欢迎来到我们深入探索神经网络核心组成部分——激活函数的系列博客。在人工智能的世界里,激活函数扮演着不可或缺的角色,它们决定着神经元的输出,并且影响着网络的学习能…

幸运数字(第十四届蓝桥杯JavaB组省赛真题)

进制转换可以参考如下的十进制,基本一样的,只是把10变成了其他数字, sum就是各个数位之和 public static int myUtil(int n) {int sum 0;while(n > 0) {sum n % 10;n / 10;}return sum;} 注意: 如果写在同一个类里面&…

@arco.design radioGroup 组件手写 beforeChange 方法

官方是没有提供 beforeChange 事件的&#xff0c;只能自己写一个 子组件&#xff08;CustomRadioGroup&#xff09; <template><a-radio-group :model-value"modelValue" change"onRadioChange"><a-radio v-for"item in list" …

面试算法-72-删除排序链表中的重复元素

题目 给定一个已排序的链表的头 head &#xff0c; 删除所有重复的元素&#xff0c;使每个元素只出现一次 。返回 已排序的链表 。 示例 1&#xff1a; 输入&#xff1a;head [1,1,2] 输出&#xff1a;[1,2] 解 class Solution {public ListNode deleteDuplicates(ListNo…

鸿蒙一次开发,多端部署(八)典型布局场景

虽然不同应用的页面千变万化&#xff0c;但对其进行拆分和分析&#xff0c;页面中的很多布局场景是相似的。本小节将介绍如何借助自适应布局、响应式布局以及常见的容器类组件&#xff0c;实现应用中的典型布局场景。 说明&#xff1a; 在本文 媒体查询 小节中已经介绍了如何通…

按键模拟精灵

按键模拟精灵功能简单&#xff1a; 1.添加模拟按键 2.删除模拟按键 3.开始模拟 4.停止模拟 适合简单的按键操作&#xff0c;有需要的可以点赞收藏关注我&#xff01;

Linux学习之自定义协议

前言&#xff1a; 首先对于Tcp的socket的通信&#xff0c;我们已经大概了解了&#xff0c;但是其中其实是由一个小问题&#xff0c;我们目前是不得而知得&#xff0c;在我们客户端与服务端连接成功后&#xff0c;服务端读取来自客户端得消息&#xff0c;我们使用的是函数read,…

电子科技大学链时代工作室招新题C语言部分---题号H

1. 题目 最有操作的一道题&#xff0c;有利于对贪心算法有个初步了解。 这道题的开篇向我们介绍了一个叫汉明距离的概念。 汉明距离指的就是两个相同长度的字符串的不同字符的个数。 例如&#xff0c;abc和acd&#xff0c;b与c不同&#xff0c;c与d不同&#xff0c;所以这两个…