目录
- 一、DySample轻量级动态上采样器
- 1.1DySample上采样模块介绍
- 1.2DySample核心代码
- 五、添加DySample上采样器
- 5.1STEP1
- 5.2STEP2
- 5.3STEP3
- 5.4STEP4
- 六、yaml文件与运行
- 6.1yaml文件
- 6.2运行成功截图
一、DySample轻量级动态上采样器
1.1DySample上采样模块介绍
DySample是一种基于采样的动态上采样(Sampling based dynamic upsampling)机制,用于提升图像或特征图的分辨率。主要分为三部分:采样点生成(sampling point generator)、静态和动态作用域因子(Static Scope Factor 和 Dynamic Scope Factor)。下面我将对每个部分的工作流程和其优势做一个简要介绍。
-
采样点生成和网格采样
采样点生成器(Sampling point generator) 会生成一个采样点集,用于决定在哪些点上进行上采样。接着,使用 网格采样(Grid Sample) 方法对原始特征图进行采样,生成一个新的高分辨率特征图 𝑋′。这种基于采样点的上采样方法具有动态性,意味着它可以根据输入特征图的不同自动调整采样点,避免了传统插值方法的局限性,使得上采样更加灵活和精确。 -
静态作用域因子(Static Scope Factor)
在这个模块中,输入特征图 𝑋,先通过一个线性变换生成低维特征。然后乘以一个固定因子(例如0.25),再进行 Pixel Shuffle 操作。最终,将生成的高分辨率特征 𝐺 和一个由固定范围因子 𝑂计算出来的偏移特征图相加,得到输出 𝑆。
优势:静态因子使得上采样具有稳定性和可控性,适合处理那些具有一致性尺度变化的场景。 -
动态作用域因子(Dynamic Scope Factor)
与静态作用域因子类似,但动态作用域因子会根据输入的特征图动态调整缩放因子(如图中的 0.5a),然后再通过 Pixel Shuffle 操作放大特征图。同样,生成的特征图 𝐺 和动态偏移特征图 𝑂相加得到最终输出 𝑆。
优势:动态因子能够自适应地调整不同输入的缩放尺度,特别适合处理多尺度特征或分辨率变化较大的图像。这种机制可以更好地捕捉不同尺度下的细节信息,提高模型的灵活性和适应性。
DySample动态点采样器的结构如下:
1.2DySample核心代码
import torch
import torch.nn as nn
import torch.nn.functional as F
__all__ = ['Dy_Sample']
def normal_init(module, mean=0, std=1, bias=0):
if hasattr(module, 'weight') and module.weight is not None:
nn.init.normal_(module.weight, mean, std)
if hasattr(module, 'bias') and module.bias is not None:
nn.init.constant_(module.bias, bias)
def constant_init(module, val, bias=0):
if hasattr(module, 'weight') and module.weight is not None:
nn.init.constant_(module.weight, val)
if hasattr(module, 'bias') and module.bias is not None:
nn.init.constant_(module.bias, bias)
class Dy_Sample(nn.Module):
def __init__(self, in_channels, scale=2, style='lp', groups=4, dyscope=False):
super().__init__()
self.scale = scale
self.style = style
self.groups = groups
assert style in ['lp', 'pl']
if style == 'pl':
assert in_channels >= scale ** 2 and in_channels % scale ** 2 == 0
assert in_channels >= groups and in_channels % groups == 0
if style == 'pl':
in_channels = in_channels // scale ** 2
out_channels = 2 * groups
else:
out_channels = 2 * groups * scale ** 2
self.offset = nn.Conv2d(in_channels, out_channels, 1)
normal_init(self.offset, std=0.001)
if dyscope:
self.scope = nn.Conv2d(in_channels, out_channels, 1)
constant_init(self.scope, val=0.)
self.register_buffer('init_pos', self._init_pos())
def _init_pos(self):
h = torch.arange((-self.scale + 1) / 2, (self.scale - 1) / 2 + 1) / self.scale
return torch.stack(torch.meshgrid([h, h])).transpose(1, 2).repeat(1, self.groups, 1).reshape(1, -1, 1, 1)
def sample(self, x, offset):
B, _, H, W = offset.shape
offset = offset.view(B, 2, -1, H, W)
coords_h = torch.arange(H) + 0.5
coords_w = torch.arange(W) + 0.5
coords = torch.stack(torch.meshgrid([coords_w, coords_h])
).transpose(1, 2).unsqueeze(1).unsqueeze(0).type(x.dtype).to(x.device)
normalizer = torch.tensor([W, H], dtype=x.dtype, device=x.device).view(1, 2, 1, 1, 1)
coords = 2 * (coords + offset) / normalizer - 1
coords = F.pixel_shuffle(coords.view(B, -1, H, W), self.scale).view(
B, 2, -1, self.scale * H, self.scale * W).permute(0, 2, 3, 4, 1).contiguous().flatten(0, 1)
return F.grid_sample(x.reshape(B * self.groups, -1, H, W), coords, mode='bilinear',
align_corners=False, padding_mode="border").view(B, -1, self.scale * H, self.scale * W)
def forward_lp(self, x):
if hasattr(self, 'scope'):
offset = self.offset(x) * self.scope(x).sigmoid() * 0.5 + self.init_pos
else:
offset = self.offset(x) * 0.25 + self.init_pos
return self.sample(x, offset)
def forward_pl(self, x):
x_ = F.pixel_shuffle(x, self.scale)
if hasattr(self, 'scope'):
offset = F.pixel_unshuffle(self.offset(x_) * self.scope(x_).sigmoid(), self.scale) * 0.5 + self.init_pos
else:
offset = F.pixel_unshuffle(self.offset(x_), self.scale) * 0.25 + self.init_pos
return self.sample(x, offset)
def forward(self, x):
if self.style == 'pl':
return self.forward_pl(x)
return self.forward_lp(x)
if __name__ == '__main__':
x = torch.rand(2, 64, 4, 7)
dys = Dy_Sample(64)
print(dys(x).shape)
五、添加DySample上采样器
5.1STEP1
首先找到ultralytics/nn文件路径下新建一个Add-module的python文件包【这里注意一定是python文件包,新建后会自动生成_init_.py】,如果已经跟着我的教程建立过一次了可以省略此步骤,随后新建一个DySample.py文件并将上文中提到的注意力机制的代码全部粘贴到此文件中,如下图所示
5.2STEP2
在STEP1中新建的_init_.py文件中导入增加改进模块的代码包如下图所示
5.3STEP3
找到ultralytics/nn文件夹中的task.py文件,在其中按照下图添加
5.4STEP4
定位到ultralytics/nn文件夹中的task.py文件中的def parse_model(d, ch, verbose=True): # model_dict, input_channels(3)函数添加如图代码,【如果不好定位可以直接ctrl+f搜索定位】
elif m in {Dy_Sample}:
c2=ch[f]
args=[c2,*args]
六、yaml文件与运行
6.1yaml文件
以下是添加DySample动态上采样的yaml文件,大家可以注释自行调节,效果以自己的数据集结果为准
# Ultralytics YOLO 🚀, AGPL-3.0 license
# YOLO11 object detection model with P3-P5 outputs. For Usage examples see https://docs.ultralytics.com/tasks/detect
# Parameters
nc: 80 # number of classes
scales: # model compound scaling constants, i.e. 'model=yolo11n.yaml' will call yolo11.yaml with scale 'n'
# [depth, width, max_channels]
n: [0.50, 0.25, 1024] # summary: 319 layers, 2624080 parameters, 2624064 gradients, 6.6 GFLOPs
s: [0.50, 0.50, 1024] # summary: 319 layers, 9458752 parameters, 9458736 gradients, 21.7 GFLOPs
m: [0.50, 1.00, 512] # summary: 409 layers, 20114688 parameters, 20114672 gradients, 68.5 GFLOPs
l: [1.00, 1.00, 512] # summary: 631 layers, 25372160 parameters, 25372144 gradients, 87.6 GFLOPs
x: [1.00, 1.50, 512] # summary: 631 layers, 56966176 parameters, 56966160 gradients, 196.0 GFLOPs
# YOLO11n backbone
backbone:
# [from, repeats, module, args]
- [-1, 1, Conv, [64, 3, 2]] # 0-P1/2
- [-1, 1, Conv, [128, 3, 2]] # 1-P2/4
- [-1, 2, C3k2, [256, False, 0.25]]
- [-1, 1, Conv, [256, 3, 2]] # 3-P3/8
- [-1, 2, C3k2, [512, False, 0.25]]
- [-1, 1, Conv, [512, 3, 2]] # 5-P4/16
- [-1, 2, C3k2, [512, True]]
- [-1, 1, Conv, [1024, 3, 2]] # 7-P5/32
- [-1, 2, C3k2, [1024, True]]
- [-1, 1, SPPF, [1024, 5]] # 9
- [-1, 2, C2PSA, [1024]] # 10
# YOLO11n head
head:
- [-1, 1, Dy_Sample, []]
- [[-1, 6], 1, Concat, [1]] # cat backbone P4
- [-1, 2, C3k2, [512, False]] # 13
- [-1, 1, Dy_Sample, []]
- [[-1, 4], 1, Concat, [1]] # cat backbone P3
- [-1, 2, C3k2, [256, False]] # 16 (P3/8-small)
- [-1, 1, Conv, [256, 3, 2]]
- [[-1, 13], 1, Concat, [1]] # cat head P4
- [-1, 2, C3k2, [512, False]] # 19 (P4/16-medium)
- [-1, 1, Conv, [512, 3, 2]]
- [[-1, 10], 1, Concat, [1]] # cat head P5
- [-1, 2, C3k2, [1024, True]] # 22 (P5/32-large)
- [[16, 19, 22], 1, Detect, [nc]] # Detect(P3, P4, P5)
以上添加位置仅供参考,具体添加位置以及模块效果以自己的数据集结果为准
6.2运行成功截图
OK 以上就是添加DySample上采样的全部过程了,后续将持续更新尽情期待