慢慢学,慢慢干。
大神博客:https://yolov5.blog.csdn.net/article/details/125148552
我老老实实的按照大神博主的方案进行修改。
第一步:common.py中添加BiFPN模型
# BiFPN # 两个特征图add操作class BiFPN_Add2(nn.Module):def __init__(self, c1, c2):super(BiFPN_Add2, self).__init__()# 设置可学习参数 nn.Parameter的作用是:将一个不可训练的类型Tensor转换成可以训练的类型parameter# 并且会向宿主模型注册该参数 成为其一部分 即model.parameters()会包含这个parameter# 从而在参数优化的时候可以自动一起优化self.w = nn.Parameter(torch.ones(2, dtype=torch.float32), requires_grad=True)self.epsilon = 0.0001self.conv = nn.Conv2d(c1, c2, kernel_size=1, stride=1, padding=0)self.silu = nn.SiLU()def forward(self, x):w = self.wweight = w / (torch.sum(w, dim=0) + self.epsilon)return self.conv(self.silu(weight[0] * x[0] + weight[1] * x[1]))# 三个特征图add操作class BiFPN_Add3(nn.Module):def __init__(self, c1, c2):super(BiFPN_Add3, self).__init__()self.w = nn.Parameter(torch.ones(3, dtype=torch.float32), requires_grad=True)self.epsilon = 0.0001self.conv = nn.Conv2d(c1, c2, kernel_size=1, stride=1, padding=0)self.silu = nn.SiLU()def forward(self, x):w = self.wweight = w / (torch.sum(w, dim=0) + self.epsilon)# Fast normalized fusionreturn self.conv(self.silu(weight[0] * x[0] + weight[1] * x[1] + weight[2] * x[2]))
第二步:修改yolo.py
使用Ctrl+F查询在elif m is Concat:
语句,在其后面加上BiFPN_Add
选项,确保yaml的BiFPN参数能够被识别到。
elif m is Concat:c2 = sum(ch[x] for x in f)# 添加bifpn_add结构elif m in [BiFPN_Add2, BiFPN_Add3]:c2 = max([ch[x] for x in f])
第三步:修改train.py,探讨重点篇。
- 将
BiFPN_Add2
和BiFPN_Add3
函数中定义的w
参数,加入g1
g0, g1, g2 = [], [], []# optimizer parameter groupsfor v in model.modules():if hasattr(v, 'bias') and isinstance(v.bias, nn.Parameter):# biasg2.append(v.bias)if isinstance(v, nn.BatchNorm2d):# weight (no decay)g0.append(v.weight)elif hasattr(v, 'weight') and isinstance(v.weight, nn.Parameter):# weight (with decay)g1.append(v.weight)# BiFPN_Concatelif isinstance(v, BiFPN_Add2) and hasattr(v, 'w') and isinstance(v.w, nn.Parameter):g1.append(v.w)elif isinstance(v, BiFPN_Add3) and hasattr(v, 'w') and isinstance(v.w, nn.Parameter):g1.append(v.w)
按照之前的版本是存在如上代码的,但是yolov5-v7.0版本中,这个部分优化成智能的optimizer。
通过观察可以发现,在大神博主的博客中修改的地方时160行左右附近,我按图索骥,来到这个地方。
与之前相比,这个地方变成了一个智能优化器函数。
smart_optimizer(model, opt.optimizer, hyp['lr0'], hyp['momentum'], hyp['weight_decay'])
震惊,我点了进去看看这个函数的实现,发现了惊天大咪咪。
没错,凭着多年直觉,我认为这个地方被重构了。之前的一重for变成了二重for,我来对比一下区别。
近似度非常高,作者偷懒没有修改注释,我们通过注释可以看出,虽然g[0] g[1] g[2 ]被g = [] [] []代替,但是我们还是可以读出来,这就是我们要的地方。
# optimizer parameter groups
我们要注意到博主老版本的地方g[1]代表:
#g[1] weight (with decay)
通过注释我们可以看出,v7.0的g[0]代表
# g【0 】 weight (with decay)
- 新版将这个地方关于weight的顺序翻转了一下,这样就导致一个问题,只要不是bias或者weight no decay,那么就全都归结于weight with decay上.
- 与之前需要elif 进行判断Bi_FPN进行模型的添加相比,这里不在需要添加判断条件了,因为最后的else会把 剩余非bias 和非weight nodecay 部分全部加到weight with decay上。
- 也就是说,添加其他Neck时,不需要额外对optimizer进行添加elif判断,也就实现了一个所谓智能的优化。
所以,我的结论就是第三步对参数g的修改不在需要,直接略过即可,智能优化器会帮我们对多余的部分进行自动增加权重。
第四步:修改yolov5.yaml
将Concat
全部换成BiFPN_Add,可以看到原来的Concat,全都变成新的BiFPN结构了。
# YOLOv5by Ultralytics, GPL-3.0 license# Parametersnc: 80# number of classesdepth_multiple: 0.33# model depth multiplewidth_multiple: 0.50# layer channel multipleanchors:- [10,13, 16,30, 33,23]# P3/8- [30,61, 62,45, 59,119]# P4/16- [116,90, 156,198, 373,326]# P5/32# YOLOv5 v6.0 backbonebackbone:# [from, number, module, args][[-1, 1, Conv, [64, 6, 2, 2]],# 0-P1/2 [-1, 1, Conv, [128, 3, 2]],# 1-P2/4 [-1, 3, C3, [128]], [-1, 1, Conv, [256, 3, 2]],# 3-P3/8 [-1, 6, C3, [256]], [-1, 1, Conv, [512, 3, 2]],# 5-P4/16 [-1, 9, C3, [512]], [-1, 1, Conv, [1024, 3, 2]],# 7-P5/32 [-1, 3, C3, [1024]], [-1, 1, SPPF, [1024, 5]],# 9]# YOLOv5 v6.1 BiFPN headhead:[[-1, 1, Conv, [512, 1, 1]], [-1, 1, nn.Upsample, [None, 2, 'nearest']], [[-1, 6], 1, BiFPN_Add2, [256, 256]],# cat backbone P4 [-1, 3, C3, [512, False]],# 13 [-1, 1, Conv, [256, 1, 1]], [-1, 1, nn.Upsample, [None, 2, 'nearest']], [[-1, 4], 1, BiFPN_Add2, [128, 128]],# cat backbone P3 [-1, 3, C3, [256, False]],# 17[-1, 1, Conv, [512, 3, 2]], [[-1, 13, 6], 1, BiFPN_Add3, [256, 256]],#v5s通道数是默认参数的一半 [-1, 3, C3, [512, False]],# 20[-1, 1, Conv, [512, 3, 2]], [[-1, 10], 1, BiFPN_Add2, [256, 256]],# cat head P5 [-1, 3, C3, [1024, False]],# 23[[17, 20, 23], 1, Detect, [nc, anchors]],# Detect(P3, P4, P5)]
现在开始实验有没有效果:
这里出现内存分配不足的情况,我的显卡显存是8G,查阅下资料,我把train的batchsize修改成-1,让训练的模型自适应epoch大小,不过可能会导致精度下降。
parser.add_argument('--epochs', type=int, default=30, help='total training epochs')
默认的batchsize是16,我修改成-1,再进行实验。
可以看出,进度图已经显示Bi_FPN结构,已经成功运行。
初学小白,难免有纰漏和错误,如有不同看法观点,欢迎交流。
大神博客:https://yolov5.blog.csdn.net/article/details/125148552