计算机视觉方面的一些模块

all 是一个可选的列表,定义在模块级别。当使用 from ... import * 语句时,如果模块中定义了

all,则只有 all 列表中的名称会被导入。这是模块作者控制哪些公开API被导入的一种方式。

使用 * 导入的行为

如果模块中有 all 列表:只有 all 列表中的名称会被导入

如果模块中没有 all 列表:Python 解释器会尝试导入模块中定义的所有公共名称(即不

是以下划线 _ 开头的名称)。但是,这通常不包括以单下划线或双下划线开头的特殊方法或变量

def autopad(k, p=None, d=1): # kernel, padding, dilation

if d > 1:

k = d * (k - 1) + 1 if isinstance(k, int) else [d * (x - 1) + 1 for x in k] # actual kernel-size

if p is None:

p = k // 2 if isinstance(k, int) else [x // 2 for x in k] # auto-pad

return p

class Conv(nn.Module):

default_act = nn.SiLU() # default activation

def init(self, c1, c2, k=1, s=1, p=None, g=1, d=1, act=True):

super().init()

self.conv = nn.Conv2d(c1, c2, k, s, autopad(k, p, d), groups=g, dilation=d, bias=False)

self.bn = nn.BatchNorm2d(c2)

self.act = self.default_act if act is True else act if isinstance(act, nn.Module) else nn.Identity()

def forward(self, x): # (b,c1,...)

(b,c1,...)-->(b,c2,...),卷积块

return self.act(self.bn(self.conv(x)))

def forward_fuse(self, x):

return self.act(self.conv(x))

class Conv2(Conv):

def init(self, c1, c2, k=3, s=1, p=None, g=1, d=1, act=True):

super().init(c1, c2, k, s, p, g=g, d=d, act=act)

self.cv2 = nn.Conv2d(c1, c2, 1, s, autopad(1, p, d), groups=g, dilation=d, bias=False) # add 1x1 conv

def forward(self, x): # (b,c1,...)

self.cv2(x):(b,c1,...)-->(b,c2,...) # 点卷积

self.conv(x):(b,c1,...)-->(b,c2,...) # 普通卷积

先把两种卷积的处理结果做残差,之后批次标准化,激活函数

return self.act(self.bn(self.conv(x) + self.cv2(x)))

def forward_fuse(self, x): # (b,c1,...)

return self.act(self.bn(self.conv(x)))

def fuse_convs(self):

具有conv权重w形状的全0张量

w = torch.zeros_like(self.conv.weight.data)

i = [x // 2 for x in w.shape[2:]] # (1,1)

将w中最后两维1:2的数据用cv2的权重替换

w[:, :, i[0] : i[0] + 1, i[1] : i[1] + 1] = self.cv2.weight.data.clone()

用conv.weight和w做残差,结果做为conv的权重

self.conv.weight.data += w

self.delattr("cv2") # 删除cv2属性

self.forward = self.forward_fuse # 更改对象的forward方法

class DWConv(Conv):

假设 c1 = 6(输入通道数),c2 = 8(输出通道数),并且最大公约数 g = math.gcd(6, 8) = 2

g是指组数,输出通道数 (8):表示最终的输出通道数。输入通道组数 (3):表示每个输出通道对应的输入通道的一个子集(组),

这里每个组包含 3 个输入通道。卷积核大小 (3x3):表示卷积核的大小,这里是 3x3。

def init(self, c1, c2, k=1, s=1, d=1, act=True): # ch_in, ch_out, kernel, stride, dilation, activation

super().init(c1, c2, k, s, g=math.gcd(c1, c2), d=d, act=act)

class LightConv(nn.Module):

def init(self, c1, c2, k=1, act=nn.ReLU()):

super().init()

self.conv1 = Conv(c1, c2, 1, act=False) # 点卷积

self.conv2 = DWConv(c2, c2, k, act=act) # 深度卷积

def forward(self, x): # (b,c1,...)

(b,c1,...)-->(b,c2,...)

先点卷积切换通道,之后深度卷积处理

用light_conv.conv1.conv.weight来访问属性权重

conv2_weight.shape[18, 1, 3, 3]

18指输出和输入通道被分成18组,每组包含1个通道,

每个组包含 1个输出和输入通道。18表示最终的输出通道数。

输入通道组数 (18):表示每个输出通道对应的输入通道的一个子集(组),这里每个组包含 1个输入通道。

卷积核大小 (3x3):表示卷积核的大小,这里是 3x3。

return self.conv2(self.conv1(x))

class DWConvTranspose2d(nn.ConvTranspose2d):

def init(self, c1, c2, k=1, s=1, p1=0, p2=0): # ch_in, ch_out, kernel, stride, padding, padding_out

super().init(c1, c2, k, s, p1, p2, groups=math.gcd(c1, c2))

class ConvTranspose(nn.Module):

default_act = nn.SiLU() # default activation

def init(self, c1, c2, k=2, s=2, p=0, bn=True, act=True):

super().init()

self.conv_transpose = nn.ConvTranspose2d(c1, c2, k, s, p, bias=not bn)

self.bn = nn.BatchNorm2d(c2) if bn else nn.Identity()

self.act = self.default_act if act is True else act if isinstance(act, nn.Module) else nn.Identity()

def forward(self, x):

return self.act(self.bn(self.conv_transpose(x)))

def forward_fuse(self, x):

return self.act(self.conv_transpose(x))

class Focus(nn.Module):

def init(self, c1, c2, k=1, s=1, p=None, g=1, act=True):

super().init()

self.conv = Conv(c1 * 4, c2, k, s, p, g, act=act)

def forward(self, x):

这里的切片切分空间操作,是先切分奇数行奇数列,之后是偶数行奇数列,之后奇数行偶数列,之后是偶数行偶数列

之后在通道维度合并特征,整个空间采样每个像素位置都被恰好取样了一次。没有任何像素被重复取样,没有任何像素被遗漏

合并特征后经过卷积处理,我第一感觉是这样采样,有助于模型发现图片数据的行梯度和列梯度的变化

return self.conv(torch.cat((x[..., ::2, ::2], x[..., 1::2, ::2], x[..., ::2, 1::2], x[..., 1::2, 1::2]), 1))

class GhostConv(nn.Module):

def init(self, c1, c2, k=1, s=1, g=1, act=True):

super().init()

c_ = c2 // 2 # hidden channels

self.cv1 = Conv(c1, c_, k, s, None, g, act=act)

self.cv2 = Conv(c_, c_, 5, 1, None, c_, act=act) # 核大小为5的深度卷积

def forward(self, x): # (b,c1,...)

gc=GhostConv(6,12,k=3)时,cv1.conv.weight.shape[6, 6, 3, 3]

y = self.cv1(x) # (b,c_,...)

self.cv2(y):(b,c_,...)-->(b,c_,...)

之后在通道维度合并通道,变成(b,2c_,...)

cv2是步长为1,核大小为5的深度卷积

return torch.cat((y, self.cv2(y)), 1)

class RepConv(nn.Module):

default_act = nn.SiLU() # default activation

def init(self, c1, c2, k=3, s=1, p=1, g=1, d=1, act=True, bn=False, deploy=False):

super().init()

assert k == 3 and p == 1

self.g = g

self.c1 = c1

self.c2 = c2

self.act = self.default_act if act is True else act if isinstance(act, nn.Module) else nn.Identity()

使用批次标准化的条件

self.bn = nn.BatchNorm2d(num_features=c1) if bn and c2 == c1 and s == 1 else None

self.conv1 = Conv(c1, c2, k, s, p=p, g=g, act=False) #普通卷积

self.conv2 = Conv(c1, c2, 1, s, p=(p - k // 2), g=g, act=False) # 点卷积

def forward_fuse(self, x): # (b,c1,...) 卷积块处理

return self.act(self.conv(x))

def forward(self, x):

id_out = 0 if self.bn is None else self.bn(x)

如果id_out =self.bn(x),普通卷积和点卷积和原数据的批次标准化做残差连接

return self.act(self.conv1(x) + self.conv2(x) + id_out)

将各个卷积核及其偏置融合成一个等效的卷积核和偏置。

def get_equivalent_kernel_bias(self):

kernel3x3, bias3x3 = self._fuse_bn_tensor(self.conv1)

kernel1x1, bias1x1 = self._fuse_bn_tensor(self.conv2)

kernelid, biasid = self._fuse_bn_tensor(self.bn)

return kernel3x3 + self._pad_1x1_to_3x3_tensor(kernel1x1) + kernelid, bias3x3 + bias1x1 + biasid

def _pad_1x1_to_3x3_tensor(self, kernel1x1):

if kernel1x1 is None:

return 0

else:

return torch.nn.functional.pad(kernel1x1, [1, 1, 1, 1])

对于 batch normalization 层,如果存在 self.bn,则构造一个中间变量 self.id_tensor,

这是一个形状为 (c1, input_dim, 3, 3) 的张量,其中心位置为 1,其余位置为 0。

def _fuse_bn_tensor(self, branch):

if branch is None:

return 0, 0

if isinstance(branch, Conv):

kernel = branch.conv.weight

running_mean = branch.bn.running_mean

running_var = branch.bn.running_var

gamma = branch.bn.weight

beta = branch.bn.bias

eps = branch.bn.eps

elif isinstance(branch, nn.BatchNorm2d):

if not hasattr(self, "id_tensor"):

input_dim = self.c1 // self.g

kernel_value = np.zeros((self.c1, input_dim, 3, 3), dtype=np.float32)

for i in range(self.c1):

kernel_value[i, i % input_dim, 1, 1] = 1

self.id_tensor = torch.from_numpy(kernel_value).to(branch.weight.device)

kernel = self.id_tensor

running_mean = branch.running_mean

running_var = branch.running_var

gamma = branch.weight

beta = branch.bias

eps = branch.eps

std = (running_var + eps).sqrt()

t = (gamma / std).reshape(-1, 1, 1, 1)

return kernel * t, beta - running_mean * gamma / std

这个方法创建一个新的卷积层,该层包含了等效的卷积核和偏置,并删除不再需要的旧卷积层。

def fuse_convs(self):

if hasattr(self, "conv"):

return

kernel, bias = self.get_equivalent_kernel_bias()

self.conv = nn.Conv2d(

in_channels=self.conv1.conv.in_channels,

out_channels=self.conv1.conv.out_channels,

kernel_size=self.conv1.conv.kernel_size,

stride=self.conv1.conv.stride,

padding=self.conv1.conv.padding,

dilation=self.conv1.conv.dilation,

groups=self.conv1.conv.groups,

bias=True,

).requires_grad_(False)

self.conv.weight.data = kernel

self.conv.bias.data = bias

for para in self.parameters():

para.detach_()

self.delattr("conv1")

self.delattr("conv2")

if hasattr(self, "nm"):

self.delattr("nm")

if hasattr(self, "bn"):

self.delattr("bn")

if hasattr(self, "id_tensor"):

self.delattr("id_tensor")

class ChannelAttention(nn.Module):

这个通道注意力少了一个压缩点卷积,有意为之吗?

def init(self, channels: int) -> None:

super().init()

self.pool = nn.AdaptiveAvgPool2d(1)

self.fc = nn.Conv2d(channels, channels, 1, 1, 0, bias=True) # 点卷积

self.act = nn.Sigmoid()

def forward(self, x: torch.Tensor) -> torch.Tensor: # (b,c,...)

平均池化后,每个通道经过点卷积混合通道信息,sigmoid为通道打重要性分数

之后对原数据加权,这样随着训练的进行,重要的特征会越来越明显,不重要的特征会被忽略

return x * self.act(self.fc(self.pool(x)))

class SpatialAttention(nn.Module):

def init(self, kernel_size=7):

super().init()

assert kernel_size in {3, 7}, "kernel size must be 3 or 7"

padding = 3 if kernel_size == 7 else 1

self.cv1 = nn.Conv2d(2, 1, kernel_size, padding=padding, bias=False)

self.act = nn.Sigmoid()

def forward(self, x):# (b,c,...)

首先对x在索引1的维度求均值和最大值,就是对通道维度聚合操作,之后聚合后形状为(b,1,...),之后在特征轴合并特征

之后经过卷积处理,通道变成1,之后sigmoid处理,这样sigmoid会给每个空间位置打分,这个分数表示空间位置对当前任务的重要性

之后与原数据加权,这样空间的重要性被分配到各个通道的空间中

return x * self.act(self.cv1(torch.cat([torch.mean(x, 1, keepdim=True), torch.max(x, 1, keepdim=True)[0]], 1)))

class CBAM(nn.Module):

def init(self, c1, kernel_size=7):

super().init()

self.channel_attention = ChannelAttention(c1) # 通道注意力

self.spatial_attention = SpatialAttention(kernel_size) # 空间注意力

def forward(self, x): #(b,c1,...)

先做通道注意力,之后做空间注意力

return self.spatial_attention(self.channel_attention(x))

class Concat(nn.Module):

def init(self, dimension=1):

super().init()

self.d = dimension

def forward(self, x): # (b,c1)

return torch.cat(x, self.d) # 对列表元素在通道维度合并特征

GhostBottleneck 类是一个用于卷积神经网络中的瓶颈模块

class GhostBottleneck(nn.Module):

def init(self, c1, c2, k=3, s=1):

super().init()

c_ = c2 // 2

self.conv = nn.Sequential(

GhostConv:用于减少参数数量的模块。

GhostConv(c1, c_, 1, 1), # 点卷积切换通道,之后和深度卷积后的数据在通道维度合并

DWConv:深度可分离卷积,用于降低计算复杂度

DWConv(c_, c_, k, s, act=False) if s == 2 else nn.Identity(), # dw

GhostConv(c_, c2, 1, 1, act=False), # pw-linear

)

nn.Identity() 是 PyTorch 中的一个类,它代表一个不执行任何操作的模块。当你实例化 nn.Identity()

并将其作为模块的一部分时,它实际上会返回输入而不做任何改变。shortcut:用于实现残差连接的路径。

self.shortcut = (

nn.Sequential(DWConv(c1, c1, k, s, act=False), Conv(c1, c2, 1, 1, act=False)) if s == 2

else nn.Conv2d(c1, c2, kernel_size=1, stride=1, padding=0, bias=False) if c1 != c2 else nn.Identity()

)

def forward(self, x): # (b,c1,...)

return self.conv(x) + self.shortcut(x)

class DFL(nn.Module):

def init(self, c1=16):

super().init()

点卷积

self.conv = nn.Conv2d(c1, 1, 1, bias=False).requires_grad_(False)

x = torch.arange(c1, dtype=torch.float)

设置conv的权重

self.conv.weight.data[:] = nn.Parameter(x.view(1, c1, 1, 1)) # (1,c,1,1)

self.c1 = c1

def forward(self, x):

b, _, a = x.shape # batch, channels, anchors

(b,c,a)-->(b,4,16,a)-->(b,16,4,a)-->softmax-->conv-->(b,4,a)

softmax在索引1的轴归一化

return self.conv(x.view(b, 4, self.c1, a).transpose(2, 1).softmax(1)).view(b, 4, a)

class Proto(nn.Module):

def init(self, c1, c_=256, c2=32):

super().init()

self.cv1 = Conv(c1, c_, k=3) # 普通卷积,核大小3

上采样,核大小是2

self.upsample = nn.ConvTranspose2d(c_, c_, 2, 2, 0, bias=True)

self.cv2 = Conv(c_, c_, k=3) # 普通卷积,核大小3

self.cv3 = Conv(c_, c2)# 点卷积

def forward(self, x):

(b,h,w,c1)-->(b,h,w,c_)-->(b,2h,2w,c_)-->(b,2h,2w,c_)-->(b,2h,2w,c2)

return self.cv3(self.cv2(self.upsample(self.cv1(x))))

class HGStem(nn.Module):

def init(self, c1, cm, c2):

super().init()

核大小3,步长2

self.stem1 = Conv(c1, cm, 3, 2, act=nn.ReLU())

核大小2,步长1

self.stem2a = Conv(cm, cm // 2, 2, 1, 0, act=nn.ReLU())

self.stem2b = Conv(cm // 2, cm, 2, 1, 0, act=nn.ReLU()) # 核大小2,步长1

self.stem3 = Conv(cm * 2, cm, 3, 2, act=nn.ReLU()) # 核大小3,步长2

self.stem4 = Conv(cm, c2, 1, 1, act=nn.ReLU()) # 点卷积

ceil_mode=True 指的是当计算输出尺寸的时候,使用向上取整而不是向下取整(默认行为)。这在某些情

况下可能会导致输出的大小比通常预期的大一点。

举个例子,如果输入的尺寸为 (N, C, H, W),其中 N 是batch size,C 是通道数,H 和 W 分别是高

度和宽度。假设 H 和 W 都是偶数,比如 (4, 4),那么应用上述的 MaxPool2d 层之后,如果使用默认的

floor 模式,输出的高度和宽度将是 (3, 3);但如果使用了 ceil_mode=True,输出的高度和宽度将会是 (4, 4)。

self.pool = nn.MaxPool2d(kernel_size=2, stride=1, padding=0, ceil_mode=True)

def forward(self, x): # (b,h,w,c1)

x = self.stem1(x) # (b,h/2,w/2,cm)

x = F.pad(x, [0, 1, 0, 1])

x2 = self.stem2a(x) # (b,h/2,w/2,cm/2)

x2 = F.pad(x2, [0, 1, 0, 1])

x2 = self.stem2b(x2) # (b,h/2,w/2,cm)

x1 = self.pool(x) # (b,h/2,w/2,cm)

x = torch.cat([x1, x2], dim=1) # (b,h/2,w/2,2cm)

x = self.stem3(x) # (b,h/4,w/4,cm)

x = self.stem4(x) # (b,h/4,w/4,c2)

return x

class HGBlock(nn.Module):

def init(self, c1, cm, c2, k=3, n=6, lightconv=False, shortcut=False, act=nn.ReLU()):

super().init()

block = LightConv if lightconv else Conv

在nn.ModuleList中存储的对象是不同的对象,它们在内存中的地址也是不同的。nn.ModuleList是一个有序集合,

用于存储子模块,它可以像普通的Python列表一样索引,但它确保每个添加的项都是torch.nn.Module的实例或其子类。

每次调用block()时,都会创建一个新的实例,并将其添加到ModuleList中。这意味着每个block对象都有自己的状态和参数,

并且在模型的前向传播过程中会被独立地调用。

self.m = nn.ModuleList(block(c1 if i == 0 else cm, cm, k=k, act=act) for i in range(n))

self.sc = Conv(c1 + n * cm, c2 // 2, 1, 1, act=act) # 点卷积

self.ec = Conv(c2 // 2, c2, 1, 1, act=act) # 点卷积

self.add = shortcut and c1 == c2 # 残差条件

def forward(self, x):

y = [x]

这些block对象依次应用于输入数据

这段代码遍历self.m中的每个block对象,并将上一个block的输出作为当前block的输入,从而形成一个序

列化的处理流程。因为每次m(y[-1])处理后的结果都被添加到y中,而y[-1]是取出列表最后一个元素

这样每次m处理后的结果都被当成了下次的输入

y.extend(m(y[-1]) for m in self.m) # 6个卷积块处理

在通道轴合并特征

y = self.ec(self.sc(torch.cat(y, 1)))

满足残差条件,返回残差,否则返回处理后结果

return y + x if self.add else y

class SPP(nn.Module):

def init(self, c1, c2, k=(5, 9, 13)):

super().init()

c_ = c1 // 2 # hidden channels

self.cv1 = Conv(c1, c_, 1, 1) # 点卷积

self.cv2 = Conv(c_ * (len(k) + 1), c2, 1, 1) # 点卷积

self.m = nn.ModuleList([nn.MaxPool2d(kernel_size=x, stride=1, padding=x // 2) for x in k])

def forward(self, x): # (b,h,w,c1)

x = self.cv1(x) # (b,h,w,c_)

(b,h,w,c_)-->(b,h,w,4c_)-->(b,h,w,c2)

这里不同尺寸的最大池化核会处理相同的x,之后列表内的对象在索引1的轴(通道轴)合并

self.m中的每个m处理的都是相同的输入x,而不是上一个最大池化的输出

return self.cv2(torch.cat([x] + [m(x) for m in self.m], 1))

class SPPF(nn.Module):

def init(self, c1, c2, k=5):

super().init()

c_ = c1 // 2 # hidden channels

self.cv1 = Conv(c1, c_, 1, 1) # 点卷积

self.cv2 = Conv(c_ * 4, c2, 1, 1) # 点卷积

self.m = nn.MaxPool2d(kernel_size=k, stride=1, padding=k // 2)

def forward(self, x):

(b,h,w,c1)-->(b,h,w,c_)

y = [self.cv1(x)]

通过列表推导式和extend方法将多次迭代的结果追加到y列表中。每次迭代都会使用self.m

处理列表y的最后一个元素,并将结果追加到y列表中。因此,y[-1]始终是指向列表中最新追加的元素

y.extend(self.m(y[-1]) for _ in range(3))

将列表y中的所有元素沿着通道维度(维度1)拼接起来

(b,4c_,h,w)-->(b,c2,h,w)

return self.cv2(torch.cat(y, 1))

class C1(nn.Module):

def init(self, c1, c2, n=1):

super().init()

self.cv1 = Conv(c1, c2, 1, 1) # 点卷积

self.m = nn.Sequential(*(Conv(c2, c2, 3) for _ in range(n)))

def forward(self, x):

(b,c1,h,w)-->(b,c2,h,w)

y = self.cv1(x)

序列化栈前后残差

return self.m(y) + y

class Bottleneck(nn.Module): # 标准瓶颈模块

def init(self, c1, c2, shortcut=True, g=1, k=(3, 3), e=0.5):

super().init()

c_ = int(c2 * e) # hidden channels

self.cv1 = Conv(c1, c_, k[0], 1) # 普通卷积

self.cv2 = Conv(c_, c2, k[1], 1, g=g) # g是groups

残差条件:shortcut为True并且c1 == c2

self.add = shortcut and c1 == c2

def forward(self, x): # (b,c1,h,w)

self.cv2(self.cv1(x)):(b,c1,h,w)-->(b,c_,h,w)-->(b,c2,h,w)

return x + self.cv2(self.cv1(x)) if self.add else self.cv2(self.cv1(x))

class C2(nn.Module):

def init(self, c1, c2, n=1, shortcut=True, g=1, e=0.5):

super().init()

self.c = int(c2 * e) # hidden channels

self.cv1 = Conv(c1, 2 * self.c, 1, 1) # 点卷积

self.cv2 = Conv(2 * self.c, c2, 1)

self.m = nn.Sequential(*(

Bottleneck(self.c, self.c, shortcut, g, k=((3, 3), (3, 3)), e=1.0) for _ in range(n)))

def forward(self, x): # (b,c1,h,w)

(b,c1,h,w)-->(b,2c,h,w)

chunk(2, 1):将输出张量沿着通道维度(维度1)分割成两个部分,分别命名为a和b。

2是指分成两个部分,1指索引1的轴

a, b = self.cv1(x).chunk(2, 1)

将分割出的a传递给模块self.m,将处理后的a与未处理的b沿通道维度拼接起来。

将拼接后的张量再次通过卷积层cv2进行处理

(b,2c,...)-->(b,c2,...)

return self.cv2(torch.cat((self.m(a), b), 1))

class C2f(nn.Module):

def init(self, c1, c2, n=1, shortcut=False, g=1, e=0.5):

super().init()

self.c = int(c2 * e) # hidden channels

self.cv1 = Conv(c1, 2 * self.c, 1, 1)

self.cv2 = Conv((2 + n) * self.c, c2, 1) # optional act=FReLU(c2)

self.m = nn.ModuleList(Bottleneck(self.c, self.c, shortcut, g, k=((3, 3), (3, 3)), e=1.0) for _ in range(n))

def forward(self, x):# (b,c1,...)

(b,c1,...)-->(b,2c,...),在通道维度分成两份

y = list(self.cv1(x).chunk(2, 1))

依次追加self.m中每个m处理后的结果,y[-1]是上个处理结果

y.extend(m(y[-1]) for m in self.m)

将列表中的所有特征图在通道轴合并

(b,(2 + n)c,...)-->(b,c2,...)

return self.cv2(torch.cat(y, 1))

def forward_split(self, x):

y = list(self.cv1(x).split((self.c, self.c), 1))

y.extend(m(y[-1]) for m in self.m)

return self.cv2(torch.cat(y, 1))

class SCDown(nn.Module):

def init(self, c1, c2, k, s):

super().init()

self.cv1 = Conv(c1, c2, 1, 1) # 点卷积

self.cv2 = Conv(c2, c2, k=k, s=s, g=c2, act=False)

def forward(self, x): # (b,c1,...)

(b,c1,...)-->(b,c2,...)-->(b,c2,...)

先点卷积切换通道,之后深度卷积

return self.cv2(self.cv1(x))

class Attention(nn.Module):

def init(self, dim, num_heads=8, attn_ratio=0.5):

super().init()

self.num_heads = num_heads # h

self.head_dim = dim // num_heads # dk

self.key_dim = int(self.head_dim * attn_ratio)

self.scale = self.key_dim**-0.5 # 缩放系数

nh_kd = self.key_dim * num_heads # 所有头加起来的维数

h = dim + nh_kd * 2 # 中间维度

self.qkv = Conv(dim, h, 1, act=False)

self.proj = Conv(dim, dim, 1, act=False) # 点卷积

深度卷积

self.pe = Conv(dim, dim, 3, 1, g=dim, act=False)

def forward(self, x): # (b,d,...)

B, C, H, W = x.shape

N = H * W

(b,d,...)-->(b,h,...)

qkv = self.qkv(x)

(b,h,...)-->(b,h,dk+k_dk*2,s),在通道维度进行拆分,q,k特征维度相同

v单独一个特征维度

q和k主要用于计算注意力权重,因此减少它们的特征维度可以减少冗余信息,提高计算效率。

v用于根据注意力权重进行加权求和,保持较大的特征维度可以保留更多的信息。

q, k, v = qkv.view(B, self.num_heads, self.key_dim * 2 + self.head_dim, N).split(

[self.key_dim, self.key_dim, self.head_dim], dim=2

)

(b,h,s_q,k_dk)@(b,h,k_dk,s_k)-->(b,h,s_q,s_k)

attn = (q.transpose(-2, -1) @ k) * self.scale

attn = attn.softmax(dim=-1) # 在s_k上归一化

(b,h,dk,s_v)@(b,h,s_k,s_q)-->(b,h,dk,s_q)-->(b,d,h,w)

v:(b,h,dk,s)-->(b,d,h,w)-->(b,d,...)

注意力后的v和经过深度卷积的v做残差

x = (v @ attn.transpose(-2, -1)).view(B, C, H, W) + self.pe(v.reshape(B, C, H, W))

(b,d,h,w)-->(b,d,h,w)

x = self.proj(x)

return x

class PSA(nn.Module):

def init(self, c1, c2, e=0.5):

super().init()

assert c1 == c2

self.c = int(c1 * e)

self.cv1 = Conv(c1, 2 * self.c, 1, 1) # 点卷积切通道

self.cv2 = Conv(2 * self.c, c1, 1)

self.attn = Attention(self.c, attn_ratio=0.5, num_heads=self.c // 64)

self.ffn = nn.Sequential(Conv(self.c, self.c * 2, 1), Conv(self.c * 2, self.c, 1, act=False))

def forward(self, x): # (b,c1,h,w)

(b,c1,h,w)-->(b,2c,...),split会在通道维度拆分

a, b = self.cv1(x).split((self.c, self.c), dim=1)

对b做注意力前后的残差连接

b = b + self.attn(b) # (b,c,...)

(b,c,...)-->(b,2c,...)-->(b,c,...)

整个多头自注意力过程没有层标准化和dropout

b = b + self.ffn(b) # 前馈前后残差

在通道维度合并a,b的特征,之后经过卷积处理

(b,2c,...)-->(b,c1,...)

return self.cv2(torch.cat((a, b), 1))

class C2fCIB(C2f):

def init(self, c1, c2, n=1, shortcut=False, lk=False, g=1, e=0.5):

super().init(c1, c2, n, shortcut, g, e)

self.m = nn.ModuleList(CIB(self.c, self.c, shortcut, e=1.0, lk=lk) for _ in range(n))

class C3(nn.Module):

def init(self, c1, c2, n=1, shortcut=True, g=1, e=0.5):

super().init()

c_ = int(c2 * e) # hidden channels

self.cv1 = Conv(c1, c_, 1, 1) # 点卷积

self.cv2 = Conv(c1, c_, 1, 1)

self.cv3 = Conv(2 * c_, c2, 1) # optional act=FReLU(c2)

self.m = nn.Sequential(*(Bottleneck(c_, c_, shortcut, g, k=((1, 1), (3, 3)), e=1.0) for _ in range(n)))

def forward(self, x): # (b,c1,...)

x经过cv1,之后被m模块处理,另一个x经过cv2处理,之后两者在通道维度合并特征

(b,c1,...)-->(b,c_,...)-->(b,c,...)

b,c1,...)-->(b,c_,...)

之后在通道维度合并,-->(b,2c,...)-->(b,c2,...)

return self.cv3(torch.cat((self.m(self.cv1(x)), self.cv2(x)), 1))

class C3x(C3):

def init(self, c1, c2, n=1, shortcut=True, g=1, e=0.5):

super().init(c1, c2, n, shortcut, g, e)

self.c_ = int(c2 * e)

self.m = nn.Sequential(*(Bottleneck(self.c_, self.c_, shortcut, g, k=((1, 3), (3, 1)), e=1) for _ in range(n)))

class C3TR(C3):

def init(self, c1, c2, n=1, shortcut=True, g=1, e=0.5):

super().init(c1, c2, n, shortcut, g, e)

c_ = int(c2 * e)

self.m = TransformerBlock(c_, c_, 4, n)

class C3Ghost(C3):

def init(self, c1, c2, n=1, shortcut=True, g=1, e=0.5):

super().init(c1, c2, n, shortcut, g, e)

c_ = int(c2 * e) # hidden channels

self.m = nn.Sequential(*(GhostBottleneck(c_, c_) for _ in range(n)))

class RepC3(nn.Module):

def init(self, c1, c2, n=3, e=1.0):

super().init()

c_ = int(c2 * e) # hidden channels

self.cv1 = Conv(c1, c2, 1, 1)

self.cv2 = Conv(c1, c2, 1, 1)

self.m = nn.Sequential(*[RepConv(c_, c_) for _ in range(n)])

self.cv3 = Conv(c_, c2, 1, 1) if c_ != c2 else nn.Identity()

def forward(self, x):

return self.cv3(self.m(self.cv1(x)) + self.cv2(x))

相关推荐
小于小于大橙子3 小时前
视觉SLAM数学基础
人工智能·数码相机·自动化·自动驾驶·几何学
埃菲尔铁塔_CV算法4 小时前
图像算法之 OCR 识别算法:原理与应用场景
图像处理·python·计算机视觉
封步宇AIGC5 小时前
量化交易系统开发-实时行情自动化交易-3.4.2.Okex行情交易数据
人工智能·python·机器学习·数据挖掘
封步宇AIGC5 小时前
量化交易系统开发-实时行情自动化交易-2.技术栈
人工智能·python·机器学习·数据挖掘
陌上阳光5 小时前
动手学深度学习68 Transformer
人工智能·深度学习·transformer
OpenI启智社区5 小时前
共筑开源技术新篇章 | 2024 CCF中国开源大会盛大开幕
人工智能·开源·ccf中国开源大会·大湾区
AI服务老曹5 小时前
建立更及时、更有效的安全生产优化提升策略的智慧油站开源了
大数据·人工智能·物联网·开源·音视频
YRr YRr5 小时前
PyTorch:torchvision中的dataset的使用
人工智能
love_and_hope6 小时前
Pytorch学习--神经网络--完整的模型训练套路
人工智能·pytorch·python·深度学习·神经网络·学习
思通数据6 小时前
AI与OCR:数字档案馆图像扫描与文字识别技术实现与项目案例
大数据·人工智能·目标检测·计算机视觉·自然语言处理·数据挖掘·ocr