学习资源站

24-替换主干网络之MobileViTv2(移动视觉 Transformer 的高效可分离自注意力机制)_mobilevitv2 yolo

YOLOv5改进系列(23)——替换主干网络之MobileViTv2(移动视觉 Transformer 的高效可分离自注意力机制)

🚀一、MobileViT v2介绍   


1.1 简介 

上一篇介绍的MobileViT可以在多个移动视觉任务中实现最先进的性能,包括分类和检测。虽然这些模型的参数较少,但与基于卷积神经网络的模型相比,它们具有较高的延迟

其主要效率瓶颈:

  1. transformer中的多头自我注意(MHA),相对于token的数量k,它需要O(k^2)的时间复杂度。
  2. MHA需要昂贵的操作(例如,批量矩阵乘法)来计算自我注意,影响资源受限设备的延迟。 

本文介绍了一种针对MobileViT模型中多头自注意力(MHA)效率瓶颈的解决方案——可分离自注意力MobileViT v2。该方法具有线性复杂度的可分离自注意方法,并且使用基于元素的操作来计算自注意力,从而使其在资源受限的设备上的执行效率更高。

MobileViTv2在多个移动视觉任务上都是最先进的,包括ImageNet对象分类和MS-COCO对象检测。通过大约300万个参数,MobileViTv2在ImageNet数据集上获得了75.6%的top-1精度,比MobileViT高出约1%,同时在移动设备上运行速度快3.2倍。


1.2 网络结构

降低多头自注意时间复杂度有两个方向:

  • tokens:在自注意层引入sparsity,在输入序列中每个token引入tokens一个子集;使用预定义模式限制token输入(不接受所有的tokens而是接受子集,缺点训练样本少性能下降很快)或者使用局部敏感的hash分组tokens(大型序列上才能看到提升)
  • patches:通过低秩矩阵估计得到近似自注意矩阵,由线性连接将自注意操作分解成多个更小的自注意操作(Linformer使用batch-wise矩阵乘法)

本文主要是为了解决v1版本的高延迟问题:

  1. 分离自注意代替多头自注意提高效率
  2. 使用element-wise操作替代batch-wise矩阵乘法

  • (a)是一种标准的多头自注意(MHA)变压器。
  • (b)在(a)中通过引入token投影层扩展MHA,将k个token投影到预定义数量的token p,从而将复杂度从O(k2)降低到O(k)。然而,它仍然使用昂贵的操作(例如,批量矩阵乘法)来计算自我注意,影响资源受限设备上的延迟。
  • (c)是提出的可分离的自我注意层,其复杂性是线性的即O(k),并使用元素操作来更快的推断。
     

MHA

dh=d/h,最后输出k个d维tokens,这个输出会在做一次矩阵乘法变成k*d维向量,作为最后的输出。


Separable self-attention

论文中提到的解决方案的关键是将自我关注计算分成两个线性计算

具体来说,该方法使用一个潜在标记来计算上下文分数,然后使用这些分数来重新加权输入标记,生成一个上下文向量

由于自我关注计算与潜在标记的计算相关,因此这种方法可以将自我关注计算的复杂性从 O(k^2) 降低到 O(k),其中 k 是标记的数量。

  • 分支L:用矩阵(b)L将x中每个d维向量映射到标量,计算(b)L与x的距离得到一个k维向量,这个k维向量softmax后就是上下文得分cs;
  • 分支K:直接矩阵相乘得到输出Xk,与cs相乘并相加k层,得到cv,cv类似于MHA的a矩阵,也编码了所有x的输入;
  • 分支V:线性映射并由ReLU激活得到Xv,然后与cv element-wise相乘,最后通过线性层得到最后的输出。

1.3 实验

数据集

论文中使用了多个数据集进行实验,包括ImageNet-1kImageNet-21k-PMS-COCO

  • ImageNet-1k数据集上,作者使用了一个训练集和一个验证集,训练集包含128万张图像,验证集包含5万张图像。
  • ImageNet-21k-P数据集上,作者使用了一个训练集和一个验证集,训练集包含1100万张图像,验证集包含52万张图像。
  • MS-COCO数据集上,作者使用了一个训练集和一个验证集,训练集包含8万张图像,验证集包含4千张图像。

训练方法

  • ImageNet-1k数据集上,作者使用了AdamW算法进行训练,使用了一个有效的批次大小为1024的图像(128个图像每个GPU×8个GPU),训练300个时期。
  • ImageNet-21k-P数据集上,作者使用了与ImageNet-1k数据集相同的训练方法,但是使用了一个有效的批次大小为4096的图像(64个图像每个GPU×64个GPU),训练80个时期。
  • MS-COCO数据集上,作者使用了与ImageNet-1k数据集相同的训练方法,但是使用了一个有效的批次大小为128的图像。

(1)与自我注意方法的比较

 (2)ImageNet-1k验证集上的分类性能

 (3)ADE20k 和 PASCAL VOC 2012 数据集上的语义分割结果

(4)在MS-COCO数据集上使用SSDLite进行对象检测

(5)MobileViTv2 模型不同输出步幅 (OS) 的上下文分数图


🚀二、具体添加方法 

第①步:在common.py中添加MobileViTv2模块

首先,定义卷积层。

分为普通卷积层和深度可分离卷积层

  1. def autopad(k, p=None): # kernel, padding
  2. if p is None:
  3. p = k // 2 if isinstance(k, int) else [x // 2 for x in k] # auto-pad
  4. return p
  5. # 普通卷积层
  6. class Conv(nn.Module):
  7. # Standard convolution
  8. def __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True): # ch_in, ch_out, kernel, stride, padding, groups
  9. super().__init__()
  10. self.conv = nn.Conv2d(c1, c2, k, s, autopad(k, p), groups=g, bias=False)
  11. self.bn = nn.BatchNorm2d(c2)
  12. self.act = nn.SiLU() if act is True else (act if isinstance(act, nn.Module) else nn.Identity())
  13. def forward(self, x):
  14. return self.act(self.bn(self.conv(x)))
  15. def forward_fuse(self, x):
  16. return self.act(self.conv(x))
  17. # 深度可分离卷积
  18. class DWConv(Conv):
  19. # Depth-wise convolution class
  20. def __init__(self, c1, c2, k=1, s=1, act=True): # ch_in, ch_out, kernel, stride, padding, groups
  21. super().__init__(c1, c2, k, s, g=math.gcd(c1, c2), act=act)
  22. # 带bn的1×1卷积分支
  23. def conv_1x1_bn(inp, oup):
  24. return nn.Sequential(
  25. nn.Conv2d(inp, oup, 1, 1, 0, bias=False),
  26. nn.BatchNorm2d(oup),
  27. nn.SiLU()
  28. )

 接着,构造ViT模块。

Transformer Encoder模块中编码

  1. #---ViT部分---#
  2. # 规范化层的类封装
  3. class PreNorm(nn.Module):
  4. def __init__(self, dim, fn):
  5. '''
  6. dim: 输入和输出维度
  7. fn: 前馈网络层,选择Multi-Head Attn和MLP二者之一
  8. '''
  9. super().__init__()
  10. # LayerNorm: ( a - mean(last 2 dim) ) / sqrt( var(last 2 dim) )
  11. # 数据归一化的输入维度设定,以及保存前馈层
  12. self.norm = nn.LayerNorm(dim)
  13. self.fn = fn
  14. def forward(self, x, **kwargs):
  15. return self.fn(self.norm(x), **kwargs)
  16. # FFN
  17. class FeedForward(nn.Module):
  18. def __init__(self, dim, hidden_dim, dropout=0.):
  19. '''
  20. dim: 输入和输出维度
  21. hidden_dim: 中间层的维度
  22. dropout: dropout操作的概率参数p
  23. '''
  24. super().__init__()
  25. self.net = nn.Sequential(
  26. nn.Linear(dim, hidden_dim),
  27. nn.SiLU(),
  28. nn.Dropout(dropout),
  29. nn.Linear(hidden_dim, dim),
  30. nn.Dropout(dropout)
  31. )
  32. def forward(self, x):
  33. return self.net(x)
  34. # Attention
  35. class Attention(nn.Module):
  36. def __init__(self, dim, heads = 8, dim_head = 64, dropout = 0.):
  37. super().__init__()
  38. inner_dim = heads * dim_head
  39. project_out = not (heads == 1 and dim_head == dim)
  40. self.heads = heads
  41. # 表示1/(sqrt(dim_head))用于消除误差,保证方差为1,避免向量内积过大导致的softmax将许多输出置0的情况
  42. # 可以看原文《attention is all you need》中关于Scale Dot-Product Attention如何抑制内积过大
  43. self.scale = dim_head ** -0.5
  44. # dim = > 0 时,表示mask第d维度,对相同的第d维度,进行softmax
  45. # dim = < 0 时,表示mask倒数第d维度,对相同的倒数第d维度,进行softmax
  46. self.attend = nn.Softmax(dim = -1)
  47. # 生成qkv矩阵,三个矩阵被放在一起,后续会被分开
  48. self.to_qkv = nn.Linear(dim, inner_dim * 3, bias = False)
  49. # 如果是多头注意力机制则需要进行全连接和防止过拟合,否则输出不做更改
  50. self.to_out = nn.Sequential(
  51. nn.Linear(inner_dim, dim),
  52. nn.Dropout(dropout)
  53. ) if project_out else nn.Identity()
  54. def forward(self, x):
  55. # 分割成q、k、v三个矩阵
  56. # qkv为 inner_dim * 3,其中inner_dim = heads * dim_head
  57. qkv = self.to_qkv(x).chunk(3, dim = -1)
  58. # qkv的维度是(3, inner_dim = heads * dim_head)
  59. # 'b n (h d) -> b h n d' 重新按思路分离出8个头,一共8组q,k,v矩阵
  60. # rearrange后维度变成 (3, heads, dim, dim_head)
  61. # 经过map后,q、k、v维度变成(1, heads, dim, dim_head)
  62. q, k, v = map(lambda t: rearrange(t, 'b n (h d) -> b h n d', h = self.heads), qkv)
  63. # query * key 得到对value的注意力预测,并通过向量内积缩放防止softmax无效化部分参数
  64. # heads * dim * dim
  65. dots = torch.matmul(q, k.transpose(-1, -2)) * self.scale
  66. # 对最后一个维度进行softmax后得到预测的概率值
  67. attn = self.attend(dots)
  68. # 乘积得到预测结果
  69. # out -> heads * dim * dim_head
  70. out = torch.matmul(attn, v)
  71. # 重组张量,将heads维度重新还原
  72. out = rearrange(out, 'b h n d -> b n (h d)')
  73. return self.to_out(out)
  74. # Transformer模块编码
  75. class Transformer(nn.Module):
  76. def __init__(self, dim, depth, heads, dim_head, mlp_dim, dropout=0.):
  77. super().__init__()
  78. self.layers = nn.ModuleList([])
  79. for _ in range(depth):
  80. self.layers.append(nn.ModuleList([
  81. PreNorm(dim, Attention(dim, heads, dim_head, dropout)),
  82. PreNorm(dim, FeedForward(dim, mlp_dim, dropout))
  83. ]))
  84. def forward(self, x):
  85. for attn, ff in self.layers:
  86. x = attn(x) + x
  87. x = ff(x) + x
  88. return x

 然后,MV2模块

分文stride=1和stride=2两种。

  1. # MV2模块
  2. class MV2Block(nn.Module):
  3. def __init__(self, inp, oup, stride=1, expansion=4):
  4. super().__init__()
  5. self.stride = stride
  6. assert stride in [1, 2]
  7. hidden_dim = int(inp * expansion)
  8. self.use_res_connect = self.stride == 1 and inp == oup
  9. if expansion == 1: # 扩张率
  10. self.conv = nn.Sequential(
  11. nn.Conv2d(hidden_dim, hidden_dim, 3, stride, 1, groups=hidden_dim, bias=False),# 3×3的卷积层
  12. nn.BatchNorm2d(hidden_dim), # BN层
  13. nn.SiLU(), # SiLU函数
  14. nn.Conv2d(hidden_dim, oup, 1, 1, 0, bias=False), # 1×1的卷积层
  15. nn.BatchNorm2d(oup), # BN层
  16. )
  17. else:
  18. self.conv = nn.Sequential(
  19. # pw
  20. nn.Conv2d(inp, hidden_dim, 1, 1, 0, bias=False), # 1×1的卷积层
  21. nn.BatchNorm2d(hidden_dim), # BN层
  22. nn.SiLU(), # SiLU函数
  23. nn.Conv2d(hidden_dim, hidden_dim, 3, stride, 1, groups=hidden_dim, bias=False), # 1×1的卷积层
  24. nn.BatchNorm2d(hidden_dim),# BN层
  25. nn.SiLU(), # SiLU函数
  26. nn.Conv2d(hidden_dim, oup, 1, 1, 0, bias=False), # 1×1的卷积层
  27. nn.BatchNorm2d(oup), # BN层
  28. )
  29. def forward(self, x):
  30. if self.use_res_connect:
  31. return x + self.conv(x)
  32. else:
  33. return self.conv(x)

最后,核心模块 MobileViT_Block

 分为局部表征模块和全局表征模块。

  1. # MobileViTv2_Block模块(核心部分)
  2. class MobileViTv2_Block(nn.Module):
  3. def __init__(self, sim_channel, dim=64, depth=2, kernel_size=3, patch_size=(2, 2), mlp_dim=int(64 * 2), dropout=0.):
  4. super().__init__()
  5. self.ph, self.pw = patch_size # 获取h和w
  6. self.dwc = DWConv(sim_channel, sim_channel, kernel_size) # 3×3可分离卷积
  7. self.conv2 = conv_1x1_bn(sim_channel, dim) # 1×1的卷积层
  8. self.transformer = Transformer(dim, depth, 4, 8, mlp_dim, dropout) # Transformer进行编码操作
  9. self.conv3 = conv_1x1_bn(dim, sim_channel) # 1×1的卷积层
  10. self.mv2 = MV2Block(sim_channel, sim_channel) # MV2模块
  11. def forward(self, x):
  12. # Local representations #mg
  13. x = self.dwc(x)
  14. x = self.conv2(x)
  15. # Global representations #mg
  16. _, _, h, w = x.shape
  17. x = rearrange(x, 'b d (h ph) (w pw) -> b (ph pw) (h w) d', ph=self.ph, pw=self.pw)
  18. x = self.transformer(x)
  19. x = rearrange(x, 'b (ph pw) (h w) d -> b d (h ph) (w pw)', h=h // self.ph, w=w // self.pw, ph=self.ph,
  20. pw=self.pw)
  21. x = self.conv3(x)
  22. x = self.mv2(x)
  23. return x

以下是完整代码:

将以下代码复制粘贴到common.py文件的末尾

  1. # MobileViTv2
  2. from einops import rearrange
  3. import math
  4. def autopad(k, p=None): # kernel, padding
  5. if p is None:
  6. p = k // 2 if isinstance(k, int) else [x // 2 for x in k] # auto-pad
  7. return p
  8. # 普通卷积层
  9. class Conv(nn.Module):
  10. # Standard convolution
  11. def __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True): # ch_in, ch_out, kernel, stride, padding, groups
  12. super().__init__()
  13. self.conv = nn.Conv2d(c1, c2, k, s, autopad(k, p), groups=g, bias=False)
  14. self.bn = nn.BatchNorm2d(c2)
  15. self.act = nn.SiLU() if act is True else (act if isinstance(act, nn.Module) else nn.Identity())
  16. def forward(self, x):
  17. return self.act(self.bn(self.conv(x)))
  18. def forward_fuse(self, x):
  19. return self.act(self.conv(x))
  20. # 深度可分离卷积
  21. class DWConv(Conv):
  22. # Depth-wise convolution class
  23. def __init__(self, c1, c2, k=1, s=1, act=True): # ch_in, ch_out, kernel, stride, padding, groups
  24. super().__init__(c1, c2, k, s, g=math.gcd(c1, c2), act=act)
  25. # 带bn的1×1卷积分支
  26. def conv_1x1_bn(inp, oup):
  27. return nn.Sequential(
  28. nn.Conv2d(inp, oup, 1, 1, 0, bias=False),
  29. nn.BatchNorm2d(oup),
  30. nn.SiLU()
  31. )
  32. #---ViT部分---#
  33. # 规范化层的类封装
  34. class PreNorm(nn.Module):
  35. def __init__(self, dim, fn):
  36. '''
  37. dim: 输入和输出维度
  38. fn: 前馈网络层,选择Multi-Head Attn和MLP二者之一
  39. '''
  40. super().__init__()
  41. # LayerNorm: ( a - mean(last 2 dim) ) / sqrt( var(last 2 dim) )
  42. # 数据归一化的输入维度设定,以及保存前馈层
  43. self.norm = nn.LayerNorm(dim)
  44. self.fn = fn
  45. def forward(self, x, **kwargs):
  46. return self.fn(self.norm(x), **kwargs)
  47. # FFN
  48. class FeedForward(nn.Module):
  49. def __init__(self, dim, hidden_dim, dropout=0.):
  50. '''
  51. dim: 输入和输出维度
  52. hidden_dim: 中间层的维度
  53. dropout: dropout操作的概率参数p
  54. '''
  55. super().__init__()
  56. self.net = nn.Sequential(
  57. nn.Linear(dim, hidden_dim),
  58. nn.SiLU(),
  59. nn.Dropout(dropout),
  60. nn.Linear(hidden_dim, dim),
  61. nn.Dropout(dropout)
  62. )
  63. def forward(self, x):
  64. return self.net(x)
  65. # Attention
  66. class Attention(nn.Module):
  67. def __init__(self, dim, heads = 8, dim_head = 64, dropout = 0.):
  68. super().__init__()
  69. inner_dim = heads * dim_head
  70. project_out = not (heads == 1 and dim_head == dim)
  71. self.heads = heads
  72. # 表示1/(sqrt(dim_head))用于消除误差,保证方差为1,避免向量内积过大导致的softmax将许多输出置0的情况
  73. # 可以看原文《attention is all you need》中关于Scale Dot-Product Attention如何抑制内积过大
  74. self.scale = dim_head ** -0.5
  75. # dim = > 0 时,表示mask第d维度,对相同的第d维度,进行softmax
  76. # dim = < 0 时,表示mask倒数第d维度,对相同的倒数第d维度,进行softmax
  77. self.attend = nn.Softmax(dim = -1)
  78. # 生成qkv矩阵,三个矩阵被放在一起,后续会被分开
  79. self.to_qkv = nn.Linear(dim, inner_dim * 3, bias = False)
  80. # 如果是多头注意力机制则需要进行全连接和防止过拟合,否则输出不做更改
  81. self.to_out = nn.Sequential(
  82. nn.Linear(inner_dim, dim),
  83. nn.Dropout(dropout)
  84. ) if project_out else nn.Identity()
  85. def forward(self, x):
  86. # 分割成q、k、v三个矩阵
  87. # qkv为 inner_dim * 3,其中inner_dim = heads * dim_head
  88. qkv = self.to_qkv(x).chunk(3, dim = -1)
  89. # qkv的维度是(3, inner_dim = heads * dim_head)
  90. # 'b n (h d) -> b h n d' 重新按思路分离出8个头,一共8组q,k,v矩阵
  91. # rearrange后维度变成 (3, heads, dim, dim_head)
  92. # 经过map后,q、k、v维度变成(1, heads, dim, dim_head)
  93. q, k, v = map(lambda t: rearrange(t, 'b n (h d) -> b h n d', h = self.heads), qkv)
  94. # query * key 得到对value的注意力预测,并通过向量内积缩放防止softmax无效化部分参数
  95. # heads * dim * dim
  96. dots = torch.matmul(q, k.transpose(-1, -2)) * self.scale
  97. # 对最后一个维度进行softmax后得到预测的概率值
  98. attn = self.attend(dots)
  99. # 乘积得到预测结果
  100. # out -> heads * dim * dim_head
  101. out = torch.matmul(attn, v)
  102. # 重组张量,将heads维度重新还原
  103. out = rearrange(out, 'b h n d -> b n (h d)')
  104. return self.to_out(out)
  105. # Transformer模块编码
  106. class Transformer(nn.Module):
  107. def __init__(self, dim, depth, heads, dim_head, mlp_dim, dropout=0.):
  108. super().__init__()
  109. self.layers = nn.ModuleList([])
  110. for _ in range(depth):
  111. self.layers.append(nn.ModuleList([
  112. PreNorm(dim, Attention(dim, heads, dim_head, dropout)),
  113. PreNorm(dim, FeedForward(dim, mlp_dim, dropout))
  114. ]))
  115. def forward(self, x):
  116. for attn, ff in self.layers:
  117. x = attn(x) + x
  118. x = ff(x) + x
  119. return x
  120. # ---MobileViTv2部分--- #
  121. # MV2模块
  122. class MV2Block(nn.Module):
  123. def __init__(self, inp, oup, stride=1, expansion=4):
  124. super().__init__()
  125. self.stride = stride
  126. assert stride in [1, 2]
  127. hidden_dim = int(inp * expansion)
  128. self.use_res_connect = self.stride == 1 and inp == oup
  129. if expansion == 1: # 扩张率
  130. self.conv = nn.Sequential(
  131. nn.Conv2d(hidden_dim, hidden_dim, 3, stride, 1, groups=hidden_dim, bias=False),# 3×3的卷积层
  132. nn.BatchNorm2d(hidden_dim), # BN层
  133. nn.SiLU(), # SiLU函数
  134. nn.Conv2d(hidden_dim, oup, 1, 1, 0, bias=False), # 1×1的卷积层
  135. nn.BatchNorm2d(oup), # BN层
  136. )
  137. else:
  138. self.conv = nn.Sequential(
  139. # pw
  140. nn.Conv2d(inp, hidden_dim, 1, 1, 0, bias=False), # 1×1的卷积层
  141. nn.BatchNorm2d(hidden_dim), # BN层
  142. nn.SiLU(), # SiLU函数
  143. nn.Conv2d(hidden_dim, hidden_dim, 3, stride, 1, groups=hidden_dim, bias=False), # 1×1的卷积层
  144. nn.BatchNorm2d(hidden_dim),# BN层
  145. nn.SiLU(), # SiLU函数
  146. nn.Conv2d(hidden_dim, oup, 1, 1, 0, bias=False), # 1×1的卷积层
  147. nn.BatchNorm2d(oup), # BN层
  148. )
  149. def forward(self, x):
  150. if self.use_res_connect:
  151. return x + self.conv(x)
  152. else:
  153. return self.conv(x)
  154. # MobileViTv2_Block模块(核心部分)
  155. class MobileViTv2_Block(nn.Module):
  156. def __init__(self, sim_channel, dim=64, depth=2, kernel_size=3, patch_size=(2, 2), mlp_dim=int(64 * 2), dropout=0.):
  157. super().__init__()
  158. self.ph, self.pw = patch_size # 获取h和w
  159. self.dwc = DWConv(sim_channel, sim_channel, kernel_size) # 3×3可分离卷积
  160. self.conv2 = conv_1x1_bn(sim_channel, dim) # 1×1的卷积层
  161. self.transformer = Transformer(dim, depth, 4, 8, mlp_dim, dropout) # Transformer进行编码操作
  162. self.conv3 = conv_1x1_bn(dim, sim_channel) # 1×1的卷积层
  163. self.mv2 = MV2Block(sim_channel, sim_channel) # MV2模块
  164. def forward(self, x):
  165. # Local representations #mg
  166. x = self.dwc(x)
  167. x = self.conv2(x)
  168. # Global representations #mg
  169. _, _, h, w = x.shape
  170. x = rearrange(x, 'b d (h ph) (w pw) -> b (ph pw) (h w) d', ph=self.ph, pw=self.pw)
  171. x = self.transformer(x)
  172. x = rearrange(x, 'b (ph pw) (h w) d -> b d (h ph) (w pw)', h=h // self.ph, w=w // self.pw, ph=self.ph,
  173. pw=self.pw)
  174. x = self.conv3(x)
  175. x = self.mv2(x)
  176. return x

如下图所示:


第②步:修改yolo.py文件

再来修改yolo.py,在parse_model函数中找到 elif m is Concat: 语句,在其后面加上下面代码:

  1. # mobilevit v2
  2. elif m in [MobileViTv2_Block]:
  3. c1, c2 = ch[f], args[0]
  4. if c2 != no:
  5. c2 = make_divisible(c2 * gw, 8)
  6. args = [c1, c2]
  7. if m in [MobileViTv2_Block]:
  8. args.insert(2, n)
  9. n = 1

如下图所示:


 第③步:创建自定义的yaml文件  

yaml文件配置完整代码如下:

  1. # YOLOv5 🚀 by Ultralytics, GPL-3.0 license
  2. # Parameters
  3. nc: 80 # number of classes
  4. depth_multiple: 0.33 # model depth multiple
  5. width_multiple: 0.50 # layer channel multiple
  6. anchors:
  7. - [10,13, 16,30, 33,23] # P3/8
  8. - [30,61, 62,45, 59,119] # P4/16
  9. - [116,90, 156,198, 373,326] # P5/32
  10. # YOLOv5 v6.0 backbone
  11. backbone:
  12. # [from, number, module, args]
  13. [[-1, 1, Conv, [64, 6, 2, 2]], # 0-P1/2
  14. [-1, 1, Conv, [128, 3, 2]], # 1-P2/4
  15. [-1, 3, C3, [128]],
  16. [-1, 1, Conv, [256, 3, 2]], # 3-P3/8
  17. [-1, 6, C3, [256]],
  18. [-1, 1, Conv, [512, 3, 2]], # 5-P4/16
  19. [-1, 9, C3, [512]],
  20. [-1, 1, Conv, [1024, 3, 2]], # 7-P5/32
  21. [-1, 3, C3, [1024]],
  22. [-1, 1, SPPF, [1024, 5]], # 9
  23. ]
  24. # YOLOv5 v6.0 head
  25. head:
  26. [[-1, 1, Conv, [512, 1, 1]],
  27. [-1, 1, nn.Upsample, [None, 2, 'nearest']],
  28. [[-1, 6], 1, Concat, [1]], # cat backbone P4
  29. [-1, 3, C3, [512, False]], # 13
  30. [-1, 1, Conv, [256, 1, 1]],
  31. [-1, 1, nn.Upsample, [None, 2, 'nearest']],
  32. [[-1, 4], 1, Concat, [1]], # cat backbone P3
  33. [-1, 3, C3, [256, False]], # 17 (P3/8-small)
  34. [-1, 1, Conv, [256, 3, 2]],
  35. [[-1, 14], 1, Concat, [1]], # cat head P4
  36. [-1, 3, MobileViTv2_Block, [512, False]], # 20 (P4/16-medium)
  37. [-1, 1, Conv, [512, 3, 2]],
  38. [[-1, 10], 1, Concat, [1]], # cat head P5
  39. [-1, 3, C3, [1024, False]], # 23 (P5/32-large)
  40. [[17, 20, 23], 1, Detect, [nc, anchors]], # Detect(P3, P4, P5)
  41. ]

第④步 验证是否加入成功

yolo.py

 这样就OK啦~