视频讲解
【布尔大学士】啊!浙大大学一区Top!15分钟速通非相邻层次间语义信息渐进式特征融合策略~~~~
Asymptotic Feature Pyramid Network for Labeling Pixels and Regions 用于标记像素和区域的渐近特征金字塔网络(2024-1区)
论文地址:https://arxiv.org/pdf/2306.15988
1、摘要
在视觉任务中,多尺度特征对于编码不同尺度的物体至关重要。经典的自上而下和自下而上的特征金字塔网络是多尺度特征提取的常用策略。然而,这些方法都存在特征信息丢失或退化的问题,从而影响了非相邻层次的融合效果。
本文提出了一种渐近特征金字塔网络(AFPN),用于解决经典特征金字塔网络中非相邻层次间信息损失或降级的问题。
AFPN通过融合相邻低层特征开始,并逐步将更高层次的特征纳入融合过程,从而避免了不同层次间的较大语义差距。此外,为了缓解多对象信息冲突,引入了自适应空间融合操作。
2、背景
2.1 引言与现有工作存在的问题
传统的物体检测方法通常只从图像中提取单一尺度的特征,这限制了不同大小或场景下的物体检测性能。为了有效地编码具有尺度变化的对象,多尺度特征提取变得至关重要。
- 一种常见的策略是采用经典的自顶向下和自底向上的特征金字塔网络。然而,这些方法在非相邻层次之间的特征融合中可能会导致信息损失或降级。
- 为了解决这一局限性,GraphFPN [18] 引入了图神经网络,以实现非相邻尺度特征之间的直接交互。然而,额外的图神经网络结构大大增加了模型的参数和计算量,得不偿失。现有的特征金字塔网络通常会将骨干网络中的高级特征向上采样为低级特征。
- 现有的特征金字塔架构要求高级特征通过多个中间尺度传播,并在与低级特征融合之前与这些尺度的特征交互。然而,这种传播和交互过程可能会导致高级特征的语义信息或低级特征的详细信息丢失或退化。
2.2 启发来源
HRNet [25], [26]在特征提取过程中会保留低层次特征,并反复合并低层次特征和高层次特征,以生成更丰富的低层次特征。这种方法在人体姿态估计方面取得了卓越的成果。
HRNet如下图所示
论文名称: Deep High-Resolution Representation Learning for Human Pose Estimation
论文下载地址:https://arxiv.org/abs/1902.09212
)
3、创新点与解决问题
AFPN的主要贡献在于:
- 渐近融合:AFPN支持非相邻层次之间直接的特征融合,避免了信息传输和交互过程中的损失或降级。
- 自适应空间融合:在多层特征融合过程中引入自适应空间融合操作,以抑制不同层次特征间的信息矛盾。
- 实验结果:AFPN在Faster R-CNN和Dynamic R-CNN等两阶段检测器以及YOLOv5等单阶段检测器中都表现出显著的性能提升,特别是在大目标检测方面。
3.1 . Asymptotic Architecture
非相邻分层特征之间的语义差距大于相邻分层特征之间的语义差距,尤其是底部和顶部特征。这就导致直接使用非相邻层次特征的融合效果不佳。因此,直接使用 C2、C3、C4 和 C5 进行特征融合是不合理的。AFPN 的架构如图 2 所示。
在骨干网络自下而上的特征提取过程中,AFPN 对低层、高层和顶层特征进行渐进式融合。具体来说,AFPN 首先融合低层次特征,然后融合深层次特征,最后融合最顶层的特征,即最抽象的特征。
由于 AFPN 的架构是渐近式的,这将使不同层次特征的语义信息在渐近式融合过程中更加接近,从而缓解上述问题。例如,C2 和 C3 之间的特征融合可以缩小它们之间的语义差距。由于 C3 和 C4 是相邻的分层特征,因此 C2 和 C4 之间的语义差距会减小。
为了对齐维度并为特征融合做准备,我们利用 1×1 卷积和双线性插值方法对特征进行上采样。另一方面,我们根据所需的下采样率,使用不同的卷积核和跨度进行下采样。例如,我们使用步长为 2 的 2×2 卷积来实现 2 倍降采样,使用步长为 4 的 4×4 卷积来实现 4 倍降采样,使用步长为 8 的 8×8 卷积来实现 8 倍降采样。
特征融合后,我们继续使用四个残差块学习特征,这与 ResNet [36] 类似。每个残差块由两个 3 × 3 卷积组成。由于 YOLO 只使用三级特征,因此不存在 8 次上采样和 8 次下采样。
3.2 . Adaptive spatial fusion
在多层次特征融合过程中,我们利用 ASFF [32],为不同层次的特征分配不同的空间权重,从而增强关键层次的重要性,并减轻来自不同对象的矛盾信息的影响。如图 3 所示,我们对三个层次的特征进行融合。
4、实验
MS COCO
如表 I 所示,当输入图像尺寸为 640×640 时,我们的方法取得了很好的性能,AP 为 39.0%,甚至超过了一些更大分辨率的模型。
PASVAL VOC
Two-stage Detectors
One-stage Detectors
AFPN 在 YOLOv5 上取得了更好的性能,同时使用了更少的参数。
Fusion Operation
为了研究自适应空间融合操作在我们的 AFPN 中的功效,我们在消融研究中用另外两种融合操作(即元素相加和元素相联)取代了自适应空间融合操作。我们的实验采用了以 ResNet50 为骨干的 Faster R-CNN 框架。
Number of Blocks
为了确定最佳块数,对不同数量的块进行了实验并记录了相应的性能指标,如表 X 所示。实验结果表明,在 AFPN 中,当块数从 1 增加到 4 时,AP 明显增加了 1.2%。然而,当区块数增加到 5 和 6 时,模型的准确度开始下降。同样,在 LightAFPN 中,当块的数量从 1 增加到 2 时,精度有所提高,但当块的数量增加到 3 时,精度略有下降,当块的数量增加到 4 时,与最佳的 2 块数量相比,精度下降了 0.3%。
因此得出结论:虽然增加块数可以提高模型的准确性,但过多的块数会导致准确性下降。基于这些结论,为 AFPN 选择了 4 个残差块,为 LightAFPN 选择了 2 个轻量级块,从而实现了最佳性能。
5、结论
在本文中,我们提出了渐近特征金字塔网络(AFPN)来解决非相邻层级之间的间接交互导致的信息丢失和退化问题。我们的 AFPN 使用渐近方式进行特征融合和自适应空间融合操作,以在融合过程中提取更多有用信息。
对于轻量级 AFPN,我们进一步提出了受重新参数化启发的轻量级渐近特征金字塔网络(LightAFPN)。与 AFPN 相比,它具有更少的参数、更少的计算和更快的推理速度。
大量实验结果表明,与各种检测和分割框架中的基线方法相比,我们的方法具有更优异的性能。未来,我们将探索我们的方法在其他视觉任务中的适用性。
6、即插即用代码
from collections import OrderedDict
import torch
import torch.nn as nn
import torch.nn.functional as F
# 论文:https://arxiv.org/pdf/2306.15988
# 题目:Asymptotic Feature Pyramid Network for Labeling Pixels and Regions(2024-1区)
# 中文:用于标记像素和区域的渐近特征金字塔网络(2024-1区)
def BasicConv(filter_in, filter_out, kernel_size, stride=1, pad=None):
# 如果没有指定填充(pad),则根据卷积核的大小计算默认的填充
if not pad:
# 当卷积核大小为奇数时,计算两边对称的填充值
pad = (kernel_size - 1) // 2 if kernel_size else 0
else:
# 如果指定了填充,则使用指定的填充值
pad = pad
# 使用nn.Sequential创建一个有序字典形式的序列模型
return nn.Sequential(OrderedDict([
# 卷积层
("conv", nn.Conv2d(in_channels=filter_in, out_channels=filter_out,
kernel_size=kernel_size, stride=stride, padding=pad, bias=False)),
# 批量归一化层
("bn", nn.BatchNorm2d(num_features=filter_out)),
# ReLU激活函数
("relu", nn.ReLU(inplace=True)),
]))
class BasicBlock(nn.Module):
def __init__(self, filter_in, filter_out):
# 初始化父类 nn.Module
super(BasicBlock, self).__init__()
# 定义第一个卷积层
self.conv1 = nn.Conv2d(in_channels=filter_in, out_channels=filter_out, kernel_size=3, padding=1)
# 定义第一个批量归一化层
self.bn1 = nn.BatchNorm2d(num_features=filter_out, momentum=0.1)
# 定义 ReLU 激活函数
self.relu = nn.ReLU(inplace=True)
# 定义第二个卷积层
self.conv2 = nn.Conv2d(in_channels=filter_out, out_channels=filter_out, kernel_size=3, padding=1)
# 定义第二个批量归一化层
self.bn2 = nn.BatchNorm2d(num_features=filter_out, momentum=0.1)
def forward(self, x):
# 保存输入作为残差连接
residual = x
# 第一层卷积 + 批量归一化 + ReLU 激活
out = self.conv1(x)
out = self.bn1(out)
out = self.relu(out)
# 第二层卷积 + 批量归一化
out = self.conv2(out)
out = self.bn2(out)
# 残差连接
out += residual
# 最后一次 ReLU 激活
out = self.relu(out)
# 返回最终输出
return out
class Upsample(nn.Module):
def __init__(self, in_channels, out_channels, scale_factor=2):
super(Upsample, self).__init__()
# 定义上采样层,先进行1x1卷积调整通道数,再进行双线性插值上采样
self.upsample = nn.Sequential(
BasicConv(in_channels, out_channels, 1), # 1x1卷积
nn.Upsample(scale_factor=scale_factor, mode='bilinear') # 上采样
)
def forward(self, x):
# 通过上采样层
x = self.upsample(x)
return x
class Downsample_x2(nn.Module):
def __init__(self, in_channels, out_channels):
super(Downsample_x2, self).__init__()
# 定义下采样层,使用步长为2的卷积实现2倍下采样
self.downsample = nn.Sequential(
BasicConv(in_channels, out_channels, 2, 2, 0) # 2x2卷积,步长2
)
def forward(self, x):
# 通过下采样层
x = self.downsample(x)
return x
class Downsample_x4(nn.Module):
def __init__(self, in_channels, out_channels):
super(Downsample_x4, self).__init__()
# 定义下采样层,使用步长为4的卷积实现4倍下采样
self.downsample = nn.Sequential(
BasicConv(in_channels, out_channels, 4, 4, 0) # 4x4卷积,步长4
)
def forward(self, x):
# 通过下采样层
x = self.downsample(x)
return x
class Downsample_x8(nn.Module):
def __init__(self, in_channels, out_channels):
super(Downsample_x8, self).__init__()
# 定义下采样层,使用步长为8的卷积实现8倍下采样
self.downsample = nn.Sequential(
BasicConv(in_channels, out_channels, 8, 8, 0) # 8x8卷积,步长8
)
def forward(self, x):
# 通过下采样层
x = self.downsample(x)
return x
class ASFF_2(nn.Module):
def __init__(self, inter_dim=512):
super(ASFF_2, self).__init__()
self.inter_dim = inter_dim
compress_c = 8
# 定义两个压缩卷积层
self.weight_level_1 = BasicConv(self.inter_dim, compress_c, 1, 1)
self.weight_level_2 = BasicConv(self.inter_dim, compress_c, 1, 1)
# 定义权重融合层
self.weight_levels = nn.Conv2d(compress_c * 2, 2, kernel_size=1, stride=1, padding=0)
# 定义融合后的卷积层
self.conv = BasicConv(self.inter_dim, self.inter_dim, 3, 1)
def forward(self, input1, input2):
# 对两个输入特征图进行压缩
level_1_weight_v = self.weight_level_1(input1)
level_2_weight_v = self.weight_level_2(input2)
# 将压缩后的特征图拼接
levels_weight_v = torch.cat((level_1_weight_v, level_2_weight_v), 1)
# 计算每个级别的权重
levels_weight = self.weight_levels(levels_weight_v)
levels_weight = F.softmax(levels_weight, dim=1)
# 根据权重融合特征图
fused_out_reduced = input1 * levels_weight[:, 0:1, :, :] + \
input2 * levels_weight[:, 1:2, :, :]
# 通过卷积层进一步处理融合后的特征图
out = self.conv(fused_out_reduced)
return out
class ASFF_3(nn.Module):
def __init__(self, inter_dim=512):
super(ASFF_3, self).__init__()
self.inter_dim = inter_dim
compress_c = 8
# 定义三个压缩卷积层
self.weight_level_1 = BasicConv(self.inter_dim, compress_c, 1, 1)
self.weight_level_2 = BasicConv(self.inter_dim, compress_c, 1, 1)
self.weight_level_3 = BasicConv(self.inter_dim, compress_c, 1, 1)
# 定义权重融合层
self.weight_levels = nn.Conv2d(compress_c * 3, 3, kernel_size=1, stride=1, padding=0)
# 定义融合后的卷积层
self.conv = BasicConv(self.inter_dim, self.inter_dim, 3, 1)
def forward(self, input1, input2, input3):
# 对三个输入特征图进行压缩
level_1_weight_v = self.weight_level_1(input1)
level_2_weight_v = self.weight_level_2(input2)
level_3_weight_v = self.weight_level_3(input3)
# 将压缩后的特征图拼接
levels_weight_v = torch.cat((level_1_weight_v, level_2_weight_v, level_3_weight_v), 1)
# 计算每个级别的权重
levels_weight = self.weight_levels(levels_weight_v)
levels_weight = F.softmax(levels_weight, dim=1)
# 根据权重融合特征图
fused_out_reduced = input1 * levels_weight[:, 0:1, :, :] + \
input2 * levels_weight[:, 1:2, :, :] + \
input3 * levels_weight[:, 2:, :, :]
# 通过卷积层进一步处理融合后的特征图
out = self.conv(fused_out_reduced)
return out
class ASFF_4(nn.Module):
def __init__(self, inter_dim=512):
super(ASFF_4, self).__init__()
self.inter_dim = inter_dim
compress_c = 8
# 定义四个压缩卷积层
self.weight_level_0 = BasicConv(self.inter_dim, compress_c, 1, 1)
self.weight_level_1 = BasicConv(self.inter_dim, compress_c, 1, 1)
self.weight_level_2 = BasicConv(self.inter_dim, compress_c, 1, 1)
self.weight_level_3 = BasicConv(self.inter_dim, compress_c, 1, 1)
# 定义权重融合层
self.weight_levels = nn.Conv2d(compress_c * 4, 4, kernel_size=1, stride=1, padding=0)
# 定义融合后的卷积层
self.conv = BasicConv(self.inter_dim, self.inter_dim, 3, 1)
def forward(self, input0, input1, input2, input3):
# 对四个输入特征图进行压缩
level_0_weight_v = self.weight_level_0(input0)
level_1_weight_v = self.weight_level_1(input1)
level_2_weight_v = self.weight_level_2(input2)
level_3_weight_v = self.weight_level_3(input3)
# 将压缩后的特征图拼接
levels_weight_v = torch.cat((level_0_weight_v, level_1_weight_v, level_2_weight_v, level_3_weight_v), 1)
# 计算每个级别的权重
levels_weight = self.weight_levels(levels_weight_v)
levels_weight = F.softmax(levels_weight, dim=1)
# 根据权重融合特征图
fused_out_reduced = input0 * levels_weight[:, 0:1, :, :] + \
input1 * levels_weight[:, 1:2, :, :] + \
input2 * levels_weight[:, 2:3, :, :] + \
input3 * levels_weight[:, 3:, :, :]
# 通过卷积层进一步处理融合后的特征图
out = self.conv(fused_out_reduced)
return out
import torch.nn as nn
class BlockBody(nn.Module):
def __init__(self, channels=[64, 128, 256, 512]):
# 初始化父类 nn.Module
super(BlockBody, self).__init__()
# 定义四个1x1卷积层,用于通道调整
self.blocks_scalezero1 = nn.Sequential(
BasicConv(channels[0], channels[0], 1),
)
self.blocks_scaleone1 = nn.Sequential(
BasicConv(channels[1], channels[1], 1),
)
self.blocks_scaletwo1 = nn.Sequential(
BasicConv(channels[2], channels[2], 1),
)
self.blocks_scalethree1 = nn.Sequential(
BasicConv(channels[3], channels[3], 1),
)
# 定义2倍下采样和2倍上采样操作
self.downsample_scalezero1_2 = Downsample_x2(channels[0], channels[1])
self.upsample_scaleone1_2 = Upsample(channels[1], channels[0], scale_factor=2)
# 定义两个特征融合模块 ASFF_2
self.asff_scalezero1 = ASFF_2(inter_dim=channels[0])
self.asff_scaleone1 = ASFF_2(inter_dim=channels[1])
# 定义两个残差块序列
self.blocks_scalezero2 = nn.Sequential(
BasicBlock(channels[0], channels[0]),
BasicBlock(channels[0], channels[0]),
BasicBlock(channels[0], channels[0]),
BasicBlock(channels[0], channels[0]),
)
self.blocks_scaleone2 = nn.Sequential(
BasicBlock(channels[1], channels[1]),
BasicBlock(channels[1], channels[1]),
BasicBlock(channels[1], channels[1]),
BasicBlock(channels[1], channels[1]),
)
# 定义多个上采样和下采样操作
self.downsample_scalezero2_2 = Downsample_x2(channels[0], channels[1])
self.downsample_scalezero2_4 = Downsample_x4(channels[0], channels[2])
self.downsample_scaleone2_2 = Downsample_x2(channels[1], channels[2])
self.upsample_scaleone2_2 = Upsample(channels[1], channels[0], scale_factor=2)
self.upsample_scaletwo2_2 = Upsample(channels[2], channels[1], scale_factor=2)
self.upsample_scaletwo2_4 = Upsample(channels[2], channels[0], scale_factor=4)
# 定义三个特征融合模块 ASFF_3
self.asff_scalezero2 = ASFF_3(inter_dim=channels[0])
self.asff_scaleone2 = ASFF_3(inter_dim=channels[1])
self.asff_scaletwo2 = ASFF_3(inter_dim=channels[2])
# 定义三个残差块序列
self.blocks_scalezero3 = nn.Sequential(
BasicBlock(channels[0], channels[0]),
BasicBlock(channels[0], channels[0]),
BasicBlock(channels[0], channels[0]),
BasicBlock(channels[0], channels[0]),
)
self.blocks_scaleone3 = nn.Sequential(
BasicBlock(channels[1], channels[1]),
BasicBlock(channels[1], channels[1]),
BasicBlock(channels[1], channels[1]),
BasicBlock(channels[1], channels[1]),
)
self.blocks_scaletwo3 = nn.Sequential(
BasicBlock(channels[2], channels[2]),
BasicBlock(channels[2], channels[2]),
BasicBlock(channels[2], channels[2]),
BasicBlock(channels[2], channels[2]),
)
# 定义多个上采样和下采样操作
self.downsample_scalezero3_2 = Downsample_x2(channels[0], channels[1])
self.downsample_scalezero3_4 = Downsample_x4(channels[0], channels[2])
self.downsample_scalezero3_8 = Downsample_x8(channels[0], channels[3])
self.upsample_scaleone3_2 = Upsample(channels[1], channels[0], scale_factor=2)
self.downsample_scaleone3_2 = Downsample_x2(channels[1], channels[2])
self.downsample_scaleone3_4 = Downsample_x4(channels[1], channels[3])
self.upsample_scaletwo3_4 = Upsample(channels[2], channels[0], scale_factor=4)
self.upsample_scaletwo3_2 = Upsample(channels[2], channels[1], scale_factor=2)
self.downsample_scaletwo3_2 = Downsample_x2(channels[2], channels[3])
self.upsample_scalethree3_8 = Upsample(channels[3], channels[0], scale_factor=8)
self.upsample_scalethree3_4 = Upsample(channels[3], channels[1], scale_factor=4)
self.upsample_scalethree3_2 = Upsample(channels[3], channels[2], scale_factor=2)
# 定义四个特征融合模块 ASFF_4
self.asff_scalezero3 = ASFF_4(inter_dim=channels[0])
self.asff_scaleone3 = ASFF_4(inter_dim=channels[1])
self.asff_scaletwo3 = ASFF_4(inter_dim=channels[2])
self.asff_scalethree3 = ASFF_4(inter_dim=channels[3])
# 定义四个残差块序列
self.blocks_scalezero4 = nn.Sequential(
BasicBlock(channels[0], channels[0]),
BasicBlock(channels[0], channels[0]),
BasicBlock(channels[0], channels[0]),
BasicBlock(channels[0], channels[0]),
)
self.blocks_scaleone4 = nn.Sequential(
BasicBlock(channels[1], channels[1]),
BasicBlock(channels[1], channels[1]),
BasicBlock(channels[1], channels[1]),
BasicBlock(channels[1], channels[1]),
)
self.blocks_scaletwo4 = nn.Sequential(
BasicBlock(channels[2], channels[2]),
BasicBlock(channels[2], channels[2]),
BasicBlock(channels[2], channels[2]),
BasicBlock(channels[2], channels[2]),
)
self.blocks_scalethree4 = nn.Sequential(
BasicBlock(channels[3], channels[3]),
BasicBlock(channels[3], channels[3]),
BasicBlock(channels[3], channels[3]),
BasicBlock(channels[3], channels[3]),
)
def forward(self, x):
# 解包输入特征图
x0, x1, x2, x3 = x
# 通过1x1卷积层调整通道
x0 = self.blocks_scalezero1(x0)
x1 = self.blocks_scaleone1(x1)
x2 = self.blocks_scaletwo1(x2)
x3 = self.blocks_scalethree1(x3)
# 特征融合:ASFF_2
scalezero = self.asff_scalezero1(x0, self.upsample_scaleone1_2(x1))
scaleone = self.asff_scaleone1(self.downsample_scalezero1_2(x0), x1)
# 通过残差块序列
x0 = self.blocks_scalezero2(scalezero)
x1 = self.blocks_scaleone2(scaleone)
# 特征融合:ASFF_3
scalezero = self.asff_scalezero2(x0, self.upsample_scaleone2_2(x1), self.upsample_scaletwo2_4(x2))
scaleone = self.asff_scaleone2(self.downsample_scalezero2_2(x0), x1, self.upsample_scaletwo2_2(x2))
scaletwo = self.asff_scaletwo2(self.downsample_scalezero2_4(x0), self.downsample_scaleone2_2(x1), x2)
# 通过残差块序列
x0 = self.blocks_scalezero3(scalezero)
x1 = self.blocks_scaleone3(scaleone)
x2 = self.blocks_scaletwo3(scaletwo)
# 特征融合:ASFF_4
scalezero = self.asff_scalezero3(x0, self.upsample_scaleone3_2(x1), self.upsample_scaletwo3_4(x2), self.upsample_scalethree3_8(x3))
scaleone = self.asff_scaleone3(self.downsample_scalezero3_2(x0), x1, self.upsample_scaletwo3_2(x2), self.upsample_scalethree3_4(x3))
scaletwo = self.asff_scaletwo3(self.downsample_scalezero3_4(x0), self.downsample_scaleone3_2(x1), x2, self.upsample_scalethree3_2(x3))
scalethree = self.asff_scalethree3(self.downsample_scalezero3_8(x0), self.downsample_scaleone3_4(x1), self.downsample_scaletwo3_2(x2), x3)
# 通过残差块序列
scalezero = self.blocks_scalezero4(scalezero)
scaleone = self.blocks_scaleone4(scaleone)
scaletwo = self.blocks_scaletwo4(scaletwo)
scalethree = self.blocks_scalethree4(scalethree)
# 返回最终融合后的特征图
return scalezero, scaleone, scaletwo, scalethree
class AFPN(nn.Module):
def __init__(self,
in_channels=[256, 512, 1024, 2048], # 输入特征图的通道数列表
out_channels=256): # 输出特征图的通道数
super(AFPN, self).__init__()
# 设置是否使用半精度浮点数 fp16
self.fp16_enabled = False
# 定义1x1卷积层,用于调整输入特征图的通道数
self.conv0 = BasicConv(in_channels[0], in_channels[0] // 8, 1)
self.conv1 = BasicConv(in_channels[1], in_channels[1] // 8, 1)
self.conv2 = BasicConv(in_channels[2], in_channels[2] // 8, 1)
self.conv3 = BasicConv(in_channels[3], in_channels[3] // 8, 1)
# 定义 BlockBody 模块,用于多尺度特征融合
self.body = nn.Sequential(
BlockBody([in_channels[0] // 8, in_channels[1] // 8, in_channels[2] // 8, in_channels[3] // 8])
)
# 定义1x1卷积层,用于调整输出特征图的通道数到统一的 `out_channels`
# in_channels[0] // 8 的目的是为了减少输入特征图的通道数,从而达到降低计算复杂度、减少模型参数以及优化特征表示的目的。
self.conv00 = BasicConv(in_channels[0] // 8, out_channels, 1)
self.conv11 = BasicConv(in_channels[1] // 8, out_channels, 1)
self.conv22 = BasicConv(in_channels[2] // 8, out_channels, 1)
self.conv33 = BasicConv(in_channels[3] // 8, out_channels, 1)
self.conv44 = nn.MaxPool2d(kernel_size=1, stride=2) # 用于生成额外的下采样特征图
# 初始化权重
for m in self.modules():
if isinstance(m, nn.Conv2d): # 如果是卷积层
nn.init.xavier_normal_(m.weight, gain=0.02) # 使用 Xavier 正态分布初始化权重
elif isinstance(m, nn.BatchNorm2d): # 如果是批量归一化层
torch.nn.init.normal_(m.weight.data, 1.0, 0.02) # 初始化权重为正态分布
torch.nn.init.constant_(m.bias.data, 0.0) # 初始化偏置为常数0
def forward(self, x):
# 解包输入特征图
x0, x1, x2, x3 = x
# 通过1x1卷积层调整输入特征图的通道数
x0 = self.conv0(x0)
x1 = self.conv1(x1)
x2 = self.conv2(x2)
x3 = self.conv3(x3)
# 通过 BlockBody 模块进行多尺度特征融合
out0, out1, out2, out3 = self.body([x0, x1, x2, x3])
# 通过1x1卷积层调整输出特征图的通道数
out0 = self.conv00(out0)
out1 = self.conv11(out1)
out2 = self.conv22(out2)
out3 = self.conv33(out3)
# 生成额外的下采样特征图
out4 = self.conv44(out3)
# 返回最终的融合后的特征图
return out0, out1, out2, out3, out4
if __name__ == "__main__":
# 创建实例,输入通道数为 32
block = AFPN()
# 创建随机输入张量 1,形状为 [batch_size, channels, height, width]
input1 = torch.rand(16, 256, 200, 200)
# 创建随机输入张量 2,形状与输入张量 1 相同
input2 = torch.rand(16, 512, 100, 100)
# 创建随机输入张量 3,形状为 [batch_size, channels, height, width]
input3 = torch.rand(16, 1024, 50, 50)
# 创建随机输入张量 4,形状与输入张量 1 相同
input4 = torch.rand(16, 2048, 25, 25)
# 应用 Model 实例处理输入张量
x = (input1, input2,input3, input4)
output = block(x)
output1, output2, output3, output4,output5 = output
# 打印输出张量的形状
print(output1.size())
print(output2.size())
print(output3.size())
print(output4.size())
print(output5.size())
print("抖音、B站、小红书、CSDN同号")
print("布尔大学士 提醒您:代码无误~~~~")