yolo。py——0126v2 cursor
# YOLOv5 YOLO-specific modules
import argparse
import logging
import sys
from pathlib import Path
from copy import deepcopy
import torch.nn as nn
import onnx.external_data_helper
sys.path.append('./') # to run '$ python *.py' files in subdirectories
# Optional YOLO11 modules (backbone only)
_ULTRA_ROOT = Path(__file__).resolve().parents[2] / "ultralytics-main"
if _ULTRA_ROOT.exists():
sys.path.append(str(_ULTRA_ROOT))
try:
from ultralytics.nn.modules.block import C3k2, C2PSA, SPPF
except Exception:
class _MissingYOLO11Module(nn.Module):
def __init__(self, *args, **kwargs):
raise ImportError(
"YOLO11模块未找到,请确保 `ultralytics-main` 目录存在并可导入。"
)
C3k2 = C2PSA = SPPF = _MissingYOLO11Module
logger = logging.getLogger(__name__)
from models.common import *
from models.experimental import *
from utils.autoanchor import check_anchor_order
from utils.general import make_divisible, check_file, set_logging
from utils.torch_utils import time_synchronized, fuse_conv_and_bn, model_info, scale_img, initialize_weights, \
select_device, copy_attr
try:
import thop # for FLOPS computation
except ImportError:
thop = None
# 尽可能按照原版bisenet的头试过了,同单独一个ARM不够深(本身只是一个3*3加注意力),和检测冲突精度降低;在ARM前面增加模块精度很好,但是本身三层融合三层都加模块速度不可接受,因此魔改用非线性较强的RFB2替代ARM
# 模仿bisenet魔改头upsample的refine改成up前降低计算量(bise upsample后有3*3卷积)
# bisenetv1是一层3*3降到64(辅助128)这里辅助同,辅助损失系数bisenet两个1,这里是一个0.15一个0.05(检测分割多任务以及YOLO本身的backbone目前aux loss实验没有明显改进,不排除与我用了COCO预训练有关,除此头外放弃aux loss)
# 删除ARM(实验结论此处ARM没用,FFM有用但1*1后3*3减小通道到64分类不如直接3*3FFM后分类,可能与我最终使用16层而不是双流或者更浅层有关,融合浅层需要更深一点见SegMaskLab)
class SegMaskBiSe(nn.Module): # 配置文件输入[16, 19, 22]通道无效
def __init__(self, n_segcls=19, n=1, c_hid=256, shortcut=False, ch=()): # n是C3的, c_hid是C3的输出通道数(接口保留了,没有使用,可用子模块控制s,m,l加深加宽)
super(SegMaskBiSe, self).__init__()
self.c_in8 = ch[0] # 16 Lab实验用4更好,但是BiSe实验用16更好(原因可能在1/8通道一个48一个128)
self.c_in16 = ch[1] # 19
self.c_in32 = ch[2] # 22
self.c_out = n_segcls
self.m8 = nn.Sequential( # 未采用双流结构
Conv(self.c_in8, 128, k=1, s=1),
)
self.m16 = nn.Sequential(
RFB2(self.c_in16, 128, map_reduce=4, d=[2,3], has_globel=False), # 魔改模块(和RFB没啥关系了,原则是增强分割入口非线性,同时扩大感受野和兼顾多尺度),实验速度精度效果还不错
# Attention(128), # 可选,这层与1/32up相加,有相加处用Attention也是BiSeNet的ARM模块设计的初衷。前面有复杂模块,Attention就够了,没必要用ARM多个3*3计算量,核心目的是一样的
# ARM(128, 128),
)
self.m32 = nn.Sequential(
RFB2(self.c_in32, 128, map_reduce=8, d=[2,3], has_globel=True), # 舍弃原GP,在1/32(和1/16,可选)处加全局特征
# Attention(128), # 改变了globel特征的获取方式,这层不用和globel特征相加,因此没必要用ARM或者Attention
# ARM(128, 128),
)
# self.GP = nn.Sequential(
# nn.AdaptiveAvgPool2d(1),
# Conv(self.c_in32, 128, k=1),
# )
self.up16 = nn.Sequential(
Conv(128, 128, 3), # refine论文源码每次up后一个3*3refine,降低计算量放在前
nn.Upsample(scale_factor=2, mode='bilinear', align_corners=True),
)
self.up32 = nn.Sequential(
Conv(128, 128, 3), # refine
nn.Upsample(scale_factor=2, mode='bilinear', align_corners=True),
)
self.out = nn.Sequential(
FFM(256, 256, k=3),
nn.Dropout(0.1), # 最后一层改用3*3,我认为用dropout不合适(dropout对3*3响应空间维度形成遮挡),改为dropout2d(随机整个通道置0增强特征图独立性,空间上不遮挡)
nn.Conv2d(256, self.c_out, kernel_size=1, padding=0),
nn.Upsample(scale_factor=8, mode='bilinear', align_corners=True),
)
# 辅助分割头,训练用,推理丢弃
self.aux16 = nn.Sequential(
Conv(128, 128, 3),
nn.Conv2d(128, self.c_out, kernel_size=1),
nn.Upsample(scale_factor=8, mode='bilinear', align_corners=True),
)
self.aux32 = nn.Sequential(
Conv(128, 128, 3),
nn.Conv2d(128, self.c_out, kernel_size=1),
nn.Upsample(scale_factor=16, mode='bilinear', align_corners=True),
)
def forward(self, x):
# GP = self.GP(x[2]) # 改成直接用广播机制加 F.interpolate(self.GP(x[2]), (x[2].shape[2], x[2].shape[3]), mode='nearest') # 全局
feat3 = self.up32(self.m32(x[2])) # + GP)
feat2 = self.up16(self.m16(x[1]) + feat3)
feat1 = [self.m8(x[0]), feat2]
return self.out(feat1) if not self.training else [self.out(feat1), self.aux16(feat2), self.aux32(feat3)]
# DeepLabV3+的encoder-decoder结构其实只涨了1个点(VOC上),启示是ASPP放在1/16图上结合浅层图也能有很好的效果(如果放在1/8图是不会考虑在此模型尝试ASPP的,太重了,放在1/32试验过精度掉了,延时也没下去很多)
# 模仿DeepLabV3+(论文1/4和1/16) 但是YOLO的1/4图太过于浅且通道太少(s只有64,deeplab的backbone常有256以上所以1*1降维)而且1/4最后用3*3 refine计算量太大,这里取1/8和1/16
# 融合部分加了FFM(k改3),deeplabv3+是两层3*3保持256通道(太奢侈),深浅并联融合第一层最好是3*3
# deeplabv3+论文经验是编码器解码器结构中,解码部分使用更少的浅层通道利于学习(论文48,32或64也接近,论文提了VOC当中用全局后提升,citys用全局后下降,这里没有用全局)
class SegMaskLab(nn.Module): # 配置文件[3, 16, 19, 22], 通道配置无效
def __init__(self, n_segcls=19, n=1, c_hid=256, shortcut=False, ch=()): # n此处用于控制ASPP的map_reduce,配置文件写3, c_hid是输出通道数配置文件写256
super(SegMaskLab, self).__init__()
self.c_detail = ch[0] # 4 YOLO的FPN是cat不是add,16cat了完整的4,理论上可以学出来,然而直接用4效果略好于16(同cat后1*1包含了add却并不总是比add好,问题在正则而不是容量)。
self.c_in16 = ch[1] # 19
self.c_out = n_segcls
# 实验效果细节层4>16, 使用1/8,没像deeplabv3+原文一样直接用1/4(l等大模型追求精度可以考虑用1/4相应的我认为融合层也该增加为两个3*3同原文)
self.detail = nn.Sequential(Conv(self.c_detail, 48, k=1),
Conv(48, 48, k=3),
)
self.encoder = nn.Sequential(
# hid砍得越少精度越高(这里问题在容量),maep_reduce=1相当于标准ASPP
# 未使用全局,一方面遵照论文,一方面用了全局后出现边界破碎的情况
Conv(self.c_in16, c_hid*2, k=1),
ASPP(c_hid*2, 256, d=[3, 6, 9], has_globel=False, map_reduce=5-n), # ASPP确实好,但是太重了,砍到了1/4通道 s:5-1=4, m:5-2=3, l:5-3=2
# 这两个都是ASPP的替代品, ASPP也有一个问题,光一个ASPP不够深,ASPPs和RFB1中间输入一起砍,ASPPs砍完可以选择前面加其他模块,RFB1砍后增加了3*3和5*5
# ASPPs(256, 256, d=[4, 7, 10], has_globel=False, map_reduce=5-n), #
# RFB1(self.c_in16, 256, d=[3, 5, 7], has_globel=False, map_reduce=max(4-n, 2)),
nn.Upsample(scale_factor=2, mode='bilinear', align_corners=True),
)
self.decoder = nn.Sequential(
# 原论文两个3*3保持256(文中实验表示保持256最重要,其次是3*3),此处为了速度还是得砍到128(第一个融合处想继续用3*3保证深浅融合效果)
FFM(256+48, 256, k=1, is_cat=True), # 融合用bisenet的配置
Conv(256, c_hid, k=3), # 经验是不管多宽,k取3还是1,用三层融合输出(有浅层融合)
nn.Conv2d(c_hid, self.c_out, kernel_size=1, padding=0),
nn.Upsample(scale_factor=8, mode='bilinear', align_corners=True),
)
def forward(self, x):
feat16 = self.encoder(x[1]) # 1/16主语义
feat8 = self.detail(x[0]) # 1/8浅层
return self.decoder([feat8, feat16])
# 一个性能不错的分割头140+FPS,验证集72.7~73.0,把1.5改成1.0则是72.4到72.7
# SPP增大了感受野,也提高了多尺度但还不够(我认为比起ASPP等的差距是本backbone和指标体现不出的,在数据集外的图上可视化能体现),1/8比较大,SPP比较小,没有更大感受野
class SegMaskBase(nn.Module):
def __init__(self, n_segcls=19, n=1, c_hid=256, shortcut=False, ch=()): # n是C3的, c_hid是C3的输出通道数
super(SegMaskBase, self).__init__()
self.c_in = ch[0] # 此版本Head暂时只有一层输入
self.c_out = n_segcls
self.m = nn.Sequential(C3(c1=self.c_in, c2=c_hid, n=n, shortcut=shortcut, g=1, e=0.5),
# SPP(c_hid, c_hid, k=(5, 9, 13)),
C3SPP(c1=c_hid, c2=int(c_hid*1.5), k=(5, 9, 13), g=1, e=0.5),
#C3(c1=c_hid, c2=c_hid, n=n, shortcut=shortcut, g=1, e=0.5),
#Conv(c1=c_hid, c2=c_hid, k=1, s=1),
nn.Dropout(0.1, True),
nn.Conv2d(int(c_hid*1.5), self.c_out, kernel_size=(3, 3), stride=(1, 1),
padding=(1, 1), groups=1, bias=False), # 后续几个头实验表明最后一层kernel还是1*1略好, base没有重训
nn.Upsample(scale_factor=8, mode='bilinear', align_corners=True), )
def forward(self, x):
return self.m(x[0]) # self.up(self.conv(self.c3(x[0])))
# 一个性能不错的分割头140+FPS,验证集72.7~73.0,把1.5改成1.0则是72.4到72.7
# SPP增大了感受野,也提高了多尺度但还不够(我认为比起ASPP等的差距是本backbone和指标体现不出的,在数据集外的图上可视化能体现),1/8比较大,SPP比较小,没有更大感受野
class SegMaskPSP_tte(nn.Module): # PSP头,多了RFB2和FFM,同样砍了通道数,没找到合适的位置加辅助损失,因此放弃辅助损失
def __init__(self, n_segcls=19, n=1, c_hid=256, shortcut=False, ch=()): # n是C3的, (接口保留了,没有使用)c_hid是隐藏层输出通道数(注意配置文件s*0.5,m*0.75,l*1)
super(SegMaskPSP_tte, self).__init__()
self.c_in8 = ch[0] # 16 # 用16,19,22宁可在融合处加深耗费一些时间,检测会涨点分割也很好。严格的消融实验证明用17,20,23分割可能还会微涨,但检测会掉3个点以上,所有头如此
self.c_in16 = ch[1] # 19
self.c_in32 = ch[2] # 22
# self.c_aux = ch[0] # 辅助损失 找不到合适地方放辅助,放弃
self.c_out = n_segcls
# 注意配置文件通道写256,此时s模型c_hid=128
self.out = nn.Sequential( # 实验表明引入较浅非线性不太强的层做分割会退化成检测的辅助(分割会相对低如72退到70,71,检测会明显升高),PP前应加入非线性强一点的层并适当扩大感受野
RFB2(c_hid*3, c_hid, d=[2,3], map_reduce=6), # 3*128//6=64 RFB2和RFB无关,仅仅是历史遗留命名(训完与训练模型效果不错就没有改名重训了)
PyramidPooling(c_hid, k=[1, 2, 3, 6]), # 按原文1,2,3,6,PSP加全局更好,但是ASPP加了全局后出现边界破碎
FFM(c_hid*2, c_hid, k=3, is_cat=False), # FFM改用k=3, 相应的砍掉部分通道降低计算量(原则就是差距大的融合哪怕砍通道第一层也最好用3*3卷积,FFM融合效果又比一般卷积好,除base头外其他头都遵循这种融合方式)
nn.Conv2d(c_hid, self.c_out, kernel_size=1, padding=0),
nn.Upsample(scale_factor=8, mode='bilinear', align_corners=True),
)
self.m = nn.Sequential(
C3(c1=c_hid * 3, c2=c_hid, n=n, shortcut=shortcut, g=1, e=0.5),
C3SPP(c1=c_hid, c2=int(c_hid), k=(5, 9, 13), g=1, e=0.5),
#nn.Dropout(0.1, True),
# nn.Conv2d(int(c_hid * 1.5), self.c_out, kernel_size=(3, 3), stride=(1, 1),
# padding=(1, 1), groups=1, bias=False), # 后续几个头实验表明最后一层kernel还是1*1略好, base没有重训
nn.Conv2d(c_hid, self.c_out, kernel_size=1, padding=0),
nn.Upsample(scale_factor=8, mode='bilinear', align_corners=True), )
self.m8 = nn.Sequential(
Conv(self.c_in8, c_hid, k=1),
)
self.m32 = nn.Sequential(
Conv(self.c_in32, c_hid, k=1),
nn.Upsample(scale_factor=4, mode='bilinear', align_corners=True),
)
self.m16 = nn.Sequential(
Conv(self.c_in16, c_hid, k=1),
nn.Upsample(scale_factor=2, mode='bilinear', align_corners=True),
)
# self.aux = nn.Sequential(
# Conv(self.c_aux, 256, 3),
# nn.Dropout(0.1, False),
# nn.Conv2d(256, self.c_out, kernel_size=1),
# nn.Upsample(scale_factor=8, mode='bilinear', align_corners=True),
# )
def forward(self, x):
# 这个头三层融合输入做过消融实验,单独16:72.6三层融合:73.5,建议所有用1/8的头都采用三层融合,在Lab的实验显示三层融合的1/16输入也有增长
feat = torch.cat([self.m8(x[0]), self.m16(x[1]), self.m32(x[2])], 1)
# return self.out(feat) if not self.training else [self.out(feat), self.aux(x[0])]
return self.m(feat)
class ConvTranspose2d(nn.ConvTranspose2d):
def __init__(self, in_channels, out_channels, kernel_size, stride=1,
padding=0, output_padding=0, groups=1, bias=True,
dilation=1, padding_mode='zeros'):
super(ConvTranspose2d, self).__init__(in_channels, out_channels, kernel_size, stride,
padding, output_padding, groups, bias,
dilation, padding_mode)
def get_params(self):
wd_params, nowd_params = [], []
wd_params.append(self.weight)
if not self.bias is None:
nowd_params.append(self.bias)
return wd_params, nowd_params
class SegMaskPSP(nn.Module): # PSP头,多了RFB2和FFM,同样砍了通道数,没找到合适的位置加辅助损失,因此放弃辅助损失
def __init__(self, n_segcls=19, n=1, c_hid=256, shortcut=False, ch=()): # n是C3的, (接口保留了,没有使用)c_hid是隐藏层输出通道数(注意配置文件s*0.5,m*0.75,l*1)
super(SegMaskPSP, self).__init__()
self.c_in8 = ch[0] # 16 # 用16,19,22宁可在融合处加深耗费一些时间,检测会涨点分割也很好。严格的消融实验证明用17,20,23分割可能还会微涨,但检测会掉3个点以上,所有头如此
self.c_in16 = ch[1] # 19
self.c_in32 = ch[2] # 22
# self.c_aux = ch[0] # 辅助损失 找不到合适地方放辅助,放弃
self.c_out = n_segcls
# 注意配置文件通道写256,此时s模型c_hid=128
self.class_num1 = 14
self.m = nn.Sequential(
Conv(c_hid // 2 * 3, c_hid, k=1),
C3(c1=c_hid, c2=c_hid, n=n, shortcut=shortcut, g=1, e=0.5),
C3SPP(c1=c_hid, c2=int(c_hid), k=(5, 9, 13), g=1, e=0.5),
nn.Conv2d(c_hid, self.c_out, kernel_size=1, padding=0),
# nn.Upsample(scale_factor=8, mode='bilinear', align_corners=True),
# ConvTranspose2d(64, 32, kernel_size=4, stride=2, padding=1, groups=1, bias=False),
# ConvTranspose2d(32, 16, kernel_size=4, stride=2, padding=1, groups=1, bias=False),
# ConvTranspose2d(16, self.c_out, kernel_size=4, stride=2, padding=1, groups=1, bias=False)
nn.Upsample(scale_factor=2, mode='bilinear',align_corners=True),
nn.Upsample(scale_factor=2, mode='bilinear',align_corners=True),
nn.Upsample(scale_factor=2, mode='bilinear',align_corners=True),
)
self.decoder1 = nn.Sequential(
Conv(c_hid // 2 * 3, c_hid, k=1),
C3(c1=c_hid, c2=c_hid, n=n, shortcut=shortcut, g=1, e=0.5),
C3SPP(c1=c_hid, c2=int(c_hid), k=(5, 9, 13), g=1, e=0.5),
nn.Conv2d(c_hid, self.class_num1, kernel_size=1, padding=0),
# ConvTranspose2d(64, 32, kernel_size=4, stride=2, padding=1, groups=1, bias=False),
# ConvTranspose2d(32, 16, kernel_size=4, stride=2, padding=1, groups=1, bias=False),
# ConvTranspose2d(16, self.class_num1, kernel_size=4, stride=2, padding=1, groups=1, bias=False), align_corners=False
nn.Upsample(scale_factor=2, mode='bilinear',align_corners=True),
nn.Upsample(scale_factor=2, mode='bilinear',align_corners=True),
nn.Upsample(scale_factor=2, mode='bilinear',align_corners=True)
# nn.Upsample(scale_factor=8, mode='bilinear', align_corners=True),
)
self.m8 = nn.Sequential(
# Conv(self.c_in8, c_hid, k=1),
C3(c1=self.c_in8, c2=c_hid//2, n=n, shortcut=shortcut, g=1, e=0.5),
C3(c1=c_hid//2, c2=c_hid//2, n=n, shortcut=shortcut, g=1, e=0.5)
)
self.m32 = nn.Sequential(
# Conv(self.c_in32, c_hid, k=1),
C3(c1=self.c_in32, c2=c_hid, n=n, shortcut=shortcut, g=1, e=0.5),
C3(c1=c_hid, c2=c_hid//2, n=n, shortcut=shortcut, g=1, e=0.5),
nn.Upsample(scale_factor=2, mode='bilinear',align_corners=True),
nn.Upsample(scale_factor=2, mode='bilinear',align_corners=True),
# ConvTranspose2d(c_hid, c_hid, kernel_size=4, stride=2, padding=1, groups=1, bias=False),
# ConvTranspose2d(c_hid, c_hid, kernel_size=4, stride=2, padding=1, groups=1, bias=False),
)
self.m16 = nn.Sequential(
# Conv(self.c_in16, c_hid, k=1),
C3(c1=self.c_in16, c2=c_hid, n=n, shortcut=shortcut, g=1, e=0.5),
C3(c1=c_hid, c2=c_hid//2, n=n, shortcut=shortcut, g=1, e=0.5),
nn.Upsample(scale_factor=2, mode='bilinear',align_corners=True),
# ConvTranspose2d(c_hid, c_hid, kernel_size=4, stride=2, padding=1, groups=1, bias=False),
)
# self.m_1 = nn.Sequential(
# Conv(self.c_in8, c_hid, k=1),
# # C3(c1=self.c_in8, c2=c_hid, n=n, shortcut=shortcut, g=1, e=0.5),
# C3(c1=c_hid, c2=c_hid//2, n=n, shortcut=shortcut, g=1, e=0.5),
# C3SPP(c1=c_hid//2, c2=int(c_hid//2), k=(5, 9, 13), g=1, e=0.5),
# nn.Conv2d(c_hid//2, self.c_out, kernel_size=1, padding=0),
# nn.Upsample(scale_factor=8, mode='bilinear', align_corners=True),
# )
def forward(self, x):
# 这个头三层融合输入做过消融实验,单独16:72.6三层融合:73.5,建议所有用1/8的头都采用三层融合,在Lab的实验显示三层融合的1/16输入也有增长
feat = torch.cat([self.m8(x[0]), self.m16(x[1]), self.m32(x[2])], 1)
output_fsd = self.m(feat)
output_rm = self.decoder1(feat)
# return self.out(feat) if not self.training else [self.out(feat), self.aux(x[0])] .sigmoid()
# return feat
# return torch.argmax(output_fsd, 1, keepdim=True),torch.argmax(output_rm, 1, keepdim=True) # 下位机量化
return output_fsd,output_rm
class SegMaskPSP_upsample(nn.Module): # PSP头,多了RFB2和FFM,同样砍了通道数,没找到合适的位置加辅助损失,因此放弃辅助损失
def __init__(self, n_segcls=19, n=1, c_hid=256, shortcut=False, ch=()): # n是C3的, (接口保留了,没有使用)c_hid是隐藏层输出通道数(注意配置文件s*0.5,m*0.75,l*1)
super(SegMaskPSP, self).__init__()
self.c_in8 = ch[0] # 16 # 用16,19,22宁可在融合处加深耗费一些时间,检测会涨点分割也很好。严格的消融实验证明用17,20,23分割可能还会微涨,但检测会掉3个点以上,所有头如此
self.c_in16 = ch[1] # 19
self.c_in32 = ch[2] # 22
# self.c_aux = ch[0] # 辅助损失 找不到合适地方放辅助,放弃
self.c_out = n_segcls
# 注意配置文件通道写256,此时s模型c_hid=128
self.class_num1 = 14
self.m = nn.Sequential(
Conv(c_hid // 2 * 3, c_hid, k=1),
C3(c1=c_hid, c2=c_hid, n=n, shortcut=shortcut, g=1, e=0.5),
C3SPP(c1=c_hid, c2=int(c_hid), k=(5, 9, 13), g=1, e=0.5),
nn.Conv2d(c_hid, self.c_out, kernel_size=1, padding=0),
# nn.Upsample(scale_factor=8, mode='bilinear', align_corners=True),
nn.Upsample(scale_factor=2, mode='bilinear', align_corners=True),
nn.Upsample(scale_factor=2, mode='bilinear', align_corners=True),
nn.Upsample(scale_factor=2, mode='bilinear', align_corners=True),
)
self.decoder1 = nn.Sequential(
Conv(c_hid // 2 * 3, c_hid, k=1),
C3(c1=c_hid, c2=c_hid, n=n, shortcut=shortcut, g=1, e=0.5),
C3SPP(c1=c_hid, c2=int(c_hid), k=(5, 9, 13), g=1, e=0.5),
nn.Conv2d(c_hid, self.class_num1, kernel_size=1, padding=0),
nn.Upsample(scale_factor=2, mode='bilinear', align_corners=True),
nn.Upsample(scale_factor=2, mode='bilinear', align_corners=True),
nn.Upsample(scale_factor=2, mode='bilinear', align_corners=True),
# nn.Upsample(scale_factor=8, mode='bilinear', align_corners=True),
)
self.m8 = nn.Sequential(
# Conv(self.c_in8, c_hid, k=1),
C3(c1=self.c_in8, c2=c_hid//2, n=n, shortcut=shortcut, g=1, e=0.5),
C3(c1=c_hid//2, c2=c_hid//2, n=n, shortcut=shortcut, g=1, e=0.5)
)
self.m32 = nn.Sequential(
# Conv(self.c_in32, c_hid, k=1),
C3(c1=self.c_in32, c2=c_hid, n=n, shortcut=shortcut, g=1, e=0.5),
C3(c1=c_hid, c2=c_hid//2, n=n, shortcut=shortcut, g=1, e=0.5),
nn.Upsample(scale_factor=2, mode='bilinear', align_corners=True),
nn.Upsample(scale_factor=2, mode='bilinear', align_corners=True),
# ConvTranspose2d(c_hid, c_hid, kernel_size=4, stride=2, padding=1, groups=1, bias=False),
# ConvTranspose2d(c_hid, c_hid, kernel_size=4, stride=2, padding=1, groups=1, bias=False),
)
self.m16 = nn.Sequential(
# Conv(self.c_in16, c_hid, k=1),
C3(c1=self.c_in16, c2=c_hid, n=n, shortcut=shortcut, g=1, e=0.5),
C3(c1=c_hid, c2=c_hid//2, n=n, shortcut=shortcut, g=1, e=0.5),
nn.Upsample(scale_factor=2, mode='bilinear', align_corners=True),
# ConvTranspose2d(c_hid, c_hid, kernel_size=4, stride=2, padding=1, groups=1, bias=False),
)
# self.m_1 = nn.Sequential(
# Conv(self.c_in8, c_hid, k=1),
# # C3(c1=self.c_in8, c2=c_hid, n=n, shortcut=shortcut, g=1, e=0.5),
# C3(c1=c_hid, c2=c_hid//2, n=n, shortcut=shortcut, g=1, e=0.5),
# C3SPP(c1=c_hid//2, c2=int(c_hid//2), k=(5, 9, 13), g=1, e=0.5),
# nn.Conv2d(c_hid//2, self.c_out, kernel_size=1, padding=0),
# nn.Upsample(scale_factor=8, mode='bilinear', align_corners=True),
# )
def forward(self, x):
# 这个头三层融合输入做过消融实验,单独16:72.6三层融合:73.5,建议所有用1/8的头都采用三层融合,在Lab的实验显示三层融合的1/16输入也有增长
feat = torch.cat([self.m8(x[0]), self.m16(x[1]), self.m32(x[2])], 1)
output_fsd = self.m(feat)
output_rm = self.decoder1(feat)
# return self.out(feat) if not self.training else [self.out(feat), self.aux(x[0])] .sigmoid()
# return feat
# return torch.argmax(output_fsd, 1, keepdim=True),torch.argmax(output_rm, 1, keepdim=True)
return output_fsd,output_rm
# def forward(self, x):
# # 这个头三层融合输入做过消融实验,单独16:72.6三层融合:73.5,建议所有用1/8的头都采用三层融合,在Lab的实验显示三层融合的1/16输入也有增长
# feat = torch.cat([self.m8(x[0]), self.m16(x[1]), self.m32(x[2])], 1)
# output1 = self.m_1(x[0])
# output2 = self.decoder1(feat)
# # return self.out(feat) if not self.training else [self.out(feat), self.aux(x[0])] .sigmoid()
# # return self.m(feat)
# return output1,output2
class SegMaskPSP_rmfsd(nn.Module): # PSP头,多了RFB2和FFM,同样砍了通道数,没找到合适的位置加辅助损失,因此放弃辅助损失
def __init__(self, n_segcls=19, n=1, c_hid=256, shortcut=False, ch=()): # n是C3的, (接口保留了,没有使用)c_hid是隐藏层输出通道数(注意配置文件s*0.5,m*0.75,l*1)
super(SegMaskPSP_rmfsd, self).__init__()
self.c_in8 = ch[0] # 16 # 用16,19,22宁可在融合处加深耗费一些时间,检测会涨点分割也很好。严格的消融实验证明用17,20,23分割可能还会微涨,但检测会掉3个点以上,所有头如此
self.c_in16 = ch[1] # 19
self.c_in32 = ch[2] # 22
self.c_in4 = ch[3] #4
# self.c_aux = ch[0] # 辅助损失 找不到合适地方放辅助,放弃
self.c_out = n_segcls
# 注意配置文件通道写256,此时s模型c_hid=128
self.class_num1 = 14
self.m = nn.Sequential(
Conv(c_hid // 2 * 3, c_hid, k=1),
C3(c1=c_hid, c2=c_hid, n=n, shortcut=shortcut, g=1, e=0.5),
C3SPP(c1=c_hid, c2=int(c_hid), k=(5, 9, 13), g=1, e=0.5),
nn.Conv2d(c_hid, self.c_out, kernel_size=1, padding=0),
nn.Upsample(scale_factor=8, mode='bilinear', align_corners=True),
)
self.decoder1 = nn.Sequential(
Conv(c_hid // 2 * 3, c_hid, k=1),
C3(c1=c_hid, c2=c_hid, n=n, shortcut=shortcut, g=1, e=0.5),
C3SPP(c1=c_hid, c2=int(c_hid), k=(5, 9, 13), g=1, e=0.5),
nn.Conv2d(c_hid, self.class_num1, kernel_size=1, padding=0),
nn.Upsample(scale_factor=8, mode='bilinear', align_corners=True),
)
self.m4 = nn.Sequential(
C3(c1=self.c_in4, c2=c_hid//2, n=n, shortcut=shortcut, g=1, e=0.5)
)
self.m8_4 = nn.Sequential(
C3(c1=self.c_in8, c2=c_hid//2, n=n, shortcut=shortcut, g=1, e=0.5)
)
self.m8 = nn.Sequential(
# Conv(self.c_in8, c_hid, k=1),
C3(c1=self.c_in8, c2=c_hid//2, n=n, shortcut=shortcut, g=1, e=0.5),
nn.Upsample(scale_factor=2, mode='bilinear', align_corners=True),
)
self.m32 = nn.Sequential(
# Conv(self.c_in32, c_hid, k=1),
C3(c1=self.c_in32, c2=c_hid//2, n=n, shortcut=shortcut, g=1, e=0.5),
nn.Upsample(scale_factor=8, mode='bilinear', align_corners=True),
# ConvTranspose2d(c_hid, c_hid, kernel_size=4, stride=2, padding=1, groups=1, bias=False),
# ConvTranspose2d(c_hid, c_hid, kernel_size=4, stride=2, padding=1, groups=1, bias=False),
)
self.m16 = nn.Sequential(
# Conv(self.c_in16, c_hid, k=1),
C3(c1=self.c_in16, c2=c_hid//2, n=n, shortcut=shortcut, g=1, e=0.5),
nn.Upsample(scale_factor=4, mode='bilinear', align_corners=True),
# ConvTranspose2d(c_hid, c_hid, kernel_size=4, stride=2, padding=1, groups=1, bias=False),
)
def forward(self, x):
# 这个头三层融合输入做过消融实验,单独16:72.6三层融合:73.5,建议所有用1/8的头都采用三层融合,在Lab的实验显示三层融合的1/16输入也有增长
up1 = self.m8_4(x[0])
feat = torch.cat([self.m8(x[0]), self.m16(x[1]), self.m32(x[2]),self.m4(x[3]+up1)], 1)
output1 = self.m(feat)
output2 = self.decoder1(feat)
# return self.out(feat) if not self.training else [self.out(feat), self.aux(x[0])] .sigmoid()
# return self.m(feat)
return output1,output2
class Detect_yolov8(nn.Module): # 检测头
stride = None # strides computed during build
export = False # onnx export
def __init__(self, nc=80, anchors=(), ch=()): # detection layer
super(Detect, self).__init__()
self.nc = nc # number of classes
self.no = nc + 5 # number of outputs per anchor 每个anchor输出通道=nc类别+1是否有目标+4放缩偏移量
self.nl = len(anchors) # number of detection layers anchors是列表的列表,外层几个列表表示有几个层用于输出
self.na = len(anchors[0]) // 2 # number of anchors 内层列表表示该层anchor形状尺寸,//即该层anchor数
self.grid = [torch.zeros(1)] * self.nl # init grid
a = torch.tensor(anchors).float().view(self.nl, -1, 2)
self.register_buffer('anchors', a) # shape(nl,na,2) anchor参数是模型非计算图参数,用register_buffer保存(buffer parameter)
self.register_buffer('anchor_grid', a.clone().view(self.nl, 1, -1, 1, 1, 2)) # shape(nl,1,na,1,1,2)
self.m = nn.ModuleList(nn.Conv2d(x, self.no * self.na, 1) for x in ch) # output conv 三个输出层输入通道不一样
self.kpt_shape = [4,2]
self.nk = self.kpt_shape[0] * self.kpt_shape[1] # number of keypoints total
c4 = max(ch[0] // 4, self.nk)
self.cv4 = nn.ModuleList(nn.Sequential(Conv_v8(x, c4, 3), Conv_v8(c4, c4, 3), nn.Conv2d(c4, self.nk, 1)) for x in ch) #关键点分支
def kpts_decode(self, bs, kpts):
"""Decodes keypoints."""
ndim = self.kpt_shape[1]
if self.export: # required for TFLite export to avoid 'PLACEHOLDER_FOR_GREATER_OP_CODES' bug
y = kpts.view(bs, *self.kpt_shape, -1)
a = (y[:, :, :2] * 2.0 + (self.anchors - 0.5)) * self.strides
if ndim == 3:
a = torch.cat((a, y[:, :, 1:2].sigmoid()), 2)
return a.view(bs, self.nk, -1)
else:
y = kpts.clone()
if ndim == 3:
y[:, 2::3].sigmoid_() # inplace sigmoid
y[:, 0::ndim] = (y[:, 0::ndim] * 2.0 + (self.anchors[0] - 0.5)) * self.strides
y[:, 1::ndim] = (y[:, 1::ndim] * 2.0 + (self.anchors[1] - 0.5)) * self.strides
return y
# gird和输出特征图一样大,值对应此anchor中心, anchor_grid张量也同尺寸, 两个值对应了此anchor的尺寸
def forward(self, x):
# x = x.copy() # for profiling
z = [] # inference output
self.training |= self.export
bs = x[0].shape[0] # batch size
kpt = torch.cat([self.cv4[i](x[i]).view(bs, self.nk, -1) for i in range(self.nl)], -1) # (bs, 17*3, h*w)
for i in range(self.nl): # 分别对三个输出层处理
x[i] = self.m[i](x[i]) # conv
bs, _, ny, nx = x[i].shape # x(bs,255,20,20) to x(bs,3,20,20,85)
# 输出x[i]变形BCHW(C=na*no) --> B,na,H,W,no(由第二维区分三个anchor), no=nc+5, x是3个张量的列表, 一个张量表一个输出层
x[i] = x[i].view(bs, self.na, self.no, ny, nx).permute(0, 1, 3, 4, 2).contiguous()
#x[i] = x[i].sigmoid()
if not self.training: # inference
if self.grid[i].shape[2:4] != x[i].shape[2:4]:
self.grid[i] = self._make_grid(nx, ny).to(x[i].device)
y = x[i].sigmoid() # 所有通道输出sigmoid, 后1+类别数通道自然表示有无目标和目标种类, 前4个通道按公式偏移放缩anchor
y[..., 0:2] = (y[..., 0:2] * 2. - 0.5 + self.grid[i]) * self.stride[i] # xy 中心偏移公式见issue
y[..., 2:4] = (y[..., 2:4] * 2) ** 2 * self.anchor_grid[i] # wh 大小放缩公式见issue
z.append(y.view(bs, -1, self.no)) # 0输入时保证0偏移, 中心0输入0.5输出,偏到grid中心(yolo anchor从左上角算起))
# 训练直接返回变形后的x去求损失, 推理对 # 大小0输入1输出,乘以anchor尺寸不变, 公式限制最大放大倍数为4倍
if self.training:
return (x, kpt)
pred_kpt = self.kpts_decode(bs, kpt)
# return torch.cat([x, pred_kpt], 1) if self.export else (torch.cat([x[0], pred_kpt], 1), (x[1], kpt))
# return x if self.training else (torch.cat(z, 1), x) # 注意训练模式和测试(以及推理)模式不同, 训练模式仅返回变形后的x, 测试推理返回放缩偏移后的box(即z)和变形后x
return ((torch.cat(z, 1), x) , pred_kpt)
@staticmethod
def _make_grid(nx=20, ny=20): # 用来生成anchor中心(特征图每个像素下标即其anchor中心)的函数
yv, xv = torch.meshgrid([torch.arange(ny), torch.arange(nx)])
return torch.stack((xv, yv), 2).view((1, 1, ny, nx, 2)).float()
class Detect(nn.Module):
stride = None # strides computed during build
export_cat = False # onnx export cat output
def __init__(self, nc=80, anchors=(), ch=()): # detection layer
super(Detect, self).__init__()
self.nc = nc # number of classes
#self.no = nc + 5 # number of outputs per anchor
self.no = nc + 5 + 8 + 6 # number of outputs per anchor
self.nl = len(anchors) # number of detection layers
self.na = len(anchors[0]) // 2 # number of anchors
self.grid = [torch.zeros(1)] * self.nl # init grid
a = torch.tensor(anchors).float().view(self.nl, -1, 2)
self.register_buffer('anchors', a) # shape(nl,na,2)
self.register_buffer('anchor_grid', a.clone().view(self.nl, 1, -1, 1, 1, 2)) # shape(nl,1,na,1,1,2)
self.m = nn.ModuleList(nn.Conv2d(x, self.no * self.na, 1) for x in ch) # output conv
def forward(self, x):
# x = x.copy() # for profiling
z = [] # inference output
if self.export_cat:
for i in range(self.nl):
x[i] = self.m[i](x[i]) # conv
bs, _, ny, nx = x[i].shape # x(bs,255,20,20) to x(bs,3,20,20,85)
x[i] = x[i].sigmoid()
# y = torch.full_like(x[i], 0)
# y = x[i].sigmoid()
# y[bs, 5:13, ny, nx] = x[i][bs, 5:13, ny, nx]
# y[bs, 27:35, ny, nx] = x[i][bs, 27:35, ny, nx]
# y[bs, 49:57, ny, nx] = x[i][bs, 49:57, ny, nx]
# z.append(y)
# x[i] = x[i].view(bs, self.na, self.no, ny, nx).permute(0, 1, 3, 4, 2).contiguous()
# if self.grid[i].shape[2:4] != x[i].shape[2:4]:
# # self.grid[i] = self._make_grid(nx, ny).to(x[i].device)
# self.grid[i], self.anchor_grid[i] = self._make_grid_new(nx, ny,i)
# y = torch.full_like(x[i], 0)
# y = y + torch.cat((x[i][:, :, :, :, 0:5].sigmoid(), torch.cat((x[i][:, :, :, :, 5:13], x[i][:, :, :, :, 13:13+self.nc].sigmoid()), 4)), 4)
# box_xy = (y[:, :, :, :, 0:2] * 2. - 0.5 + self.grid[i].to(x[i].device)) * self.stride[i] # xy
# box_wh = (y[:, :, :, :, 2:4] * 2) ** 2 * self.anchor_grid[i] # wh
# # box_conf = torch.cat((box_xy, torch.cat((box_wh, y[:, :, :, :, 4:5]), 4)), 4)
# landm1 = y[:, :, :, :, 5:7] * self.anchor_grid[i] + self.grid[i].to(x[i].device) * self.stride[i] # landmark x1 y1
# landm2 = y[:, :, :, :, 7:9] * self.anchor_grid[i] + self.grid[i].to(x[i].device) * self.stride[i] # landmark x2 y2
# landm3 = y[:, :, :, :, 9:11] * self.anchor_grid[i] + self.grid[i].to(x[i].device) * self.stride[i] # landmark x3 y3
# landm4 = y[:, :, :, :, 11:13] * self.anchor_grid[i] + self.grid[i].to(x[i].device) * self.stride[i] # landmark x4 y4
# # landm5 = y[:, :, :, :, 13:15] * self.anchor_grid[i] + self.grid[i].to(x[i].device) * self.stride[i] # landmark x5 y5
# # landm = torch.cat((landm1, torch.cat((landm2, torch.cat((landm3, torch.cat((landm4, landm5), 4)), 4)), 4)), 4)
# # y = torch.cat((box_conf, torch.cat((landm, y[:, :, :, :, 15:15+self.nc]), 4)), 4)
# y = torch.cat([box_xy, box_wh, y[:, :, :, :, 4:5], landm1, landm2, landm3, landm4, y[:, :, :, :, 13:13+self.nc]], -1)
# z.append(y.view(bs, -1, self.no))
return x
for i in range(self.nl):
x[i] = self.m[i](x[i]) # conv
bs, _, ny, nx = x[i].shape # x(bs,255,20,20) to x(bs,3,20,20,85)
x[i] = x[i].view(bs, self.na, self.no, ny, nx).permute(0, 1, 3, 4, 2).contiguous()
if not self.training: # inference
if self.grid[i].shape[2:4] != x[i].shape[2:4]:
self.grid[i] = self._make_grid(nx, ny).to(x[i].device)
y = torch.full_like(x[i], 0)
class_range = list(range(5)) + list(range(13,19+self.nc))
y[..., class_range] = x[i][..., class_range].sigmoid()
y[..., 5:13] = x[i][..., 5:13]
#y = x[i].sigmoid()
y[..., 0:2] = (y[..., 0:2] * 2. - 0.5 + self.grid[i].to(x[i].device)) * self.stride[i] # xy
y[..., 2:4] = (y[..., 2:4] * 2) ** 2 * self.anchor_grid[i] # wh
#y[..., 5:15] = y[..., 5:15] * 8 - 4
y[..., 5:7] = y[..., 5:7] * self.anchor_grid[i] + self.grid[i].to(x[i].device) * self.stride[i] # landmark x1 y1
y[..., 7:9] = y[..., 7:9] * self.anchor_grid[i] + self.grid[i].to(x[i].device) * self.stride[i]# landmark x2 y2
y[..., 9:11] = y[..., 9:11] * self.anchor_grid[i] + self.grid[i].to(x[i].device) * self.stride[i]# landmark x3 y3
y[..., 11:13] = y[..., 11:13] * self.anchor_grid[i] + self.grid[i].to(x[i].device) * self.stride[i]# landmark x4 y4
# y[..., 13:15] = y[..., 13:15] * self.anchor_grid[i] + self.grid[i].to(x[i].device) * self.stride[i]# landmark x5 y5
#y[..., 5:7] = (y[..., 5:7] * 2 -1) * self.anchor_grid[i] # landmark x1 y1
#y[..., 7:9] = (y[..., 7:9] * 2 -1) * self.anchor_grid[i] # landmark x2 y2
#y[..., 9:11] = (y[..., 9:11] * 2 -1) * self.anchor_grid[i] # landmark x3 y3
#y[..., 11:13] = (y[..., 11:13] * 2 -1) * self.anchor_grid[i] # landmark x4 y4
#y[..., 13:15] = (y[..., 13:15] * 2 -1) * self.anchor_grid[i] # landmark x5 y5
z.append(y.view(bs, -1, self.no))
return x if self.training else (torch.cat(z, 1), x)
@staticmethod
def _make_grid(nx=20, ny=20):
yv, xv = torch.meshgrid([torch.arange(ny), torch.arange(nx)])
return torch.stack((xv, yv), 2).view((1, 1, ny, nx, 2)).float()
def _make_grid_new(self,nx=20, ny=20,i=0):
d = self.anchors[i].device
if '1.10.0' in torch.__version__: # torch>=1.10.0 meshgrid workaround for torch>=0.7 compatibility
yv, xv = torch.meshgrid([torch.arange(ny).to(d), torch.arange(nx).to(d)], indexing='ij')
else:
yv, xv = torch.meshgrid([torch.arange(ny).to(d), torch.arange(nx).to(d)])
grid = torch.stack((xv, yv), 2).expand((1, self.na, ny, nx, 2)).float()
anchor_grid = (self.anchors[i].clone() * self.stride[i]).view((1, self.na, 1, 1, 2)).expand((1, self.na, ny, nx, 2)).float()
return grid, anchor_grid
class Model(nn.Module): # 核心模型
def __init__(self, cfg='yolov5s.yaml', ch=3, nc=None, anchors=None): # model, input channels, number of classes
super(Model, self).__init__()
if isinstance(cfg, dict): # 配置可直接接收字典
self.yaml = cfg # model dict
else: # is *.yaml 更多是用yaml解析配置
import yaml # for torch hub
self.yaml_file = Path(cfg).name
with open(cfg) as f:
self.yaml = yaml.load(f, Loader=yaml.SafeLoader) # model dict
# Define model
ch = self.yaml['ch'] = self.yaml.get('ch', ch) # input channels 字典的get方法,配置文件有ch就把模型输入通道配成ch,没有就按默认值ch=3
if nc and nc != self.yaml['nc']: # 若Model类初始化指定了nc(非None)且和配置文件不等,以Model类初始化值为准,并修改字典值
logger.info(f"Overriding model.yaml nc={self.yaml['nc']} with nc={nc}")
self.yaml['nc'] = nc # override yaml value
if anchors: # 若Model类初始化指定了anchor值,以Model类初始化为准,并修改字典值
logger.info(f'Overriding model.yaml anchors with anchors={anchors}')
self.yaml['anchors'] = round(anchors) # override yaml value
self.model, self.save = parse_model(deepcopy(self.yaml), ch=[ch]) # model, savelist 解析配置文件
self.save.append(len(self.model) - 2) # 记录分割层(默认倒数第二层)
self.names = [str(i) for i in range(self.yaml['nc'])] # default names
# print([x.shape for x in self.forward(torch.zeros(1, ch, 64, 64))])
# Build strides, anchors
m = self.model[-1] # Detect() Detect头
if isinstance(m, Detect):
s = 256 # 2x min stride
m.stride = torch.tensor([s / x.shape[-2] for x in self.forward(torch.zeros(2, ch, s, s))[0]]) # forward
m.anchors /= m.stride.view(-1, 1, 1)
check_anchor_order(m)
self.stride = m.stride
self._initialize_biases() # only run once
# print('Strides: %s' % m.stride.tolist())
# Init weights, biases
initialize_weights(self) # 初始化, 看代码只初始化了BN和激活函数,跳过了卷积层
self.info()
logger.info('')
def forward(self, x, augment=False, profile=False):
if augment:
img_size = x.shape[-2:] # height, width
s = [1, 0.83, 0.67] # scales
f = [None, 3, None] # flips (2-ud, 3-lr)
y = [] # outputs
for si, fi in zip(s, f):
xi = scale_img(x.flip(fi) if fi else x, si, gs=int(self.stride.max()))
yi = self.forward_once(xi)[0] # forward
# cv2.imwrite(f'img_{si}.jpg', 255 * xi[0].cpu().numpy().transpose((1, 2, 0))[:, :, ::-1]) # save
yi[..., :4] /= si # de-scale
if fi == 2:
yi[..., 1] = img_size[0] - yi[..., 1] # de-flip ud
elif fi == 3:
yi[..., 0] = img_size[1] - yi[..., 0] # de-flip lr
y.append(yi)
return torch.cat(y, 1), None # augmented inference, train
else:
return self.forward_once(x, profile) # single-scale inference, train
def forward_once(self, x, profile=False):
y, dt = [], [] # outputs 用于记录中间输出的y, profile时间的dt
out = [] # 用于保存改版后的分割+检测输出
zh = 0
for m in self.model:
zh+=1
if m.f != -1: # if not from previous layer 非单纯上一层则需要调整此层输入
x = y[m.f] if isinstance(m.f, int) else [x if j == -1 else y[j] for j in m.f] # from earlier layers
# 输入来自单层, 直接取那层输出 来自多层, 其中-1取输入x, 非-1取那层输出
if profile:
o = thop.profile(m, inputs=(x,), verbose=False)[0] / 1E9 * 2 if thop else 0 # FLOPS
t = time_synchronized()
for _ in range(10):
_ = m(x)
dt.append((time_synchronized() - t) * 100)
print('%10.1f%10.0f%10.1fms %-40s' % (o, m.np, dt[-1], m.type))
# 调好输入每层都是直接跑, detect是最后一层, for循环最后一个自然是detect结果
x = m(x) # run
# print(m.i, m.type, x.shape if m.f !=-1 else [a.shape for a in x])
y.append(x if m.i in self.save else None) # save output 解析时self.save记录了需要保存的那些层(后续层输入用到),仅保存这些层输出即可(改版代码新增记录分割层24)
# if zh == 2:
# break
if profile:
print('%.1fms total' % sum(dt))
# return [y[-2],x] # 检测, 分割
zh = y[-2]
return [x, y[-2]] # 检测, 分割
def _initialize_biases(self, cf=None): # initialize biases into Detect(), cf is class frequency
# https://arxiv.org/abs/1708.02002 section 3.3
# cf = torch.bincount(torch.tensor(np.concatenate(dataset.labels, 0)[:, 0]).long(), minlength=nc) + 1.
m = self.model[-1] # Detect() module
for mi, s in zip(m.m, m.stride): # from
b = mi.bias.view(m.na, -1) # conv.bias(255) to (3,85)
b.data[:, 4] += math.log(8 / (640 / s) ** 2) # obj (8 objects per 640 image)
b.data[:, 5:] += math.log(0.6 / (m.nc - 0.99)) if cf is None else torch.log(cf / cf.sum()) # cls
mi.bias = torch.nn.Parameter(b.view(-1), requires_grad=True)
def _print_biases(self):
m = self.model[-1] # Detect() module
for mi in m.m: # from
b = mi.bias.detach().view(m.na, -1).T # conv.bias(255) to (3,85)
print(('%6g Conv2d.bias:' + '%10.3g' * 6) % (mi.weight.shape[1], *b[:5].mean(1).tolist(), b[5:].mean()))
# def _print_weights(self):
# for m in self.model.modules():
# if type(m) is Bottleneck:
# print('%10.3g' % (m.w.detach().sigmoid() * 2)) # shortcut weights
def fuse(self): # fuse model Conv2d() + BatchNorm2d() layers
print('Fusing layers... ')
for m in self.model.modules():
if type(m) is Conv and hasattr(m, 'bn'):
m.conv = fuse_conv_and_bn(m.conv, m.bn) # update conv
delattr(m, 'bn') # remove batchnorm
m.forward = m.fuseforward # update forward
self.info()
return self
def nms(self, mode=True): # add or remove NMS module
present = type(self.model[-1]) is NMS # last layer is NMS
if mode and not present:
print('Adding NMS... ')
m = NMS() # module
m.f = -1 # from
m.i = self.model[-1].i + 1 # index
self.model.add_module(name='%s' % m.i, module=m) # add
self.eval()
elif not mode and present:
print('Removing NMS... ')
self.model = self.model[:-1] # remove
return self
def autoshape(self): # add autoShape module
print('Adding autoShape... ')
m = autoShape(self) # wrap model
copy_attr(m, self, include=('yaml', 'nc', 'hyp', 'names', 'stride'), exclude=()) # copy attributes
return m
def info(self, verbose=False, img_size=640): # print model information
model_info(self, verbose, img_size)
def parse_model(d, ch): # model_dict, input_channels(3)
logger.info('\n%3s%18s%3s%10s %-40s%-30s' % ('', 'from', 'n', 'params', 'module', 'arguments'))
anchors, nc, gd, gw, n_segcls = d['anchors'], d['nc'], d['depth_multiple'], d['width_multiple'], d['n_segcls']
na = (len(anchors[0]) // 2) if isinstance(anchors, list) else anchors # number of anchors
no = na * (nc + 5) # number of outputs = anchors * (classes + 5) yolo输出通道数 = anchor数 * (类别+1个是否有目标+4个偏移放缩量)
layers, save, c2 = [], [], ch[-1] # layers, savelist, ch out
for i, (f, n, m, args) in enumerate(d['backbone'] + d['head']): # from, number, module, args
m = eval(m) if isinstance(m, str) else m # eval strings 执行字符串表达式,block名转函数/类,字符数字转数字
for j, a in enumerate(args):
try:
args[j] = eval(a) if isinstance(a, str) else a # eval strings 同上,
except:
pass
# n控制深度, yaml配置文件中num为1就1次,num>1就 num*depth_multiple次, 即此block本身以及block子结构重复次数
n = max(round(n * gd), 1) if n > 1 else n # depth gain
if m in [Conv, GhostConv, Bottleneck, GhostBottleneck, SPP, SPPF, DWConv, MixConv2d, Focus, CrossConv, BottleneckCSP,
C3, C3TR, C3k2, C2PSA, ASPP, Conv_TI, Focus_TI, SPP_TI, C2f, C3_Res2Block, C2f_Res2Block, VoVCSP]:
c1, c2 = ch[f], args[0] # 指定层输入(c1)输出(c2)通道数(ch记录各层输出通道,f表输入层下标,输入层的输出通道就是本层输入通道)
if c2 != no: # if not output 对非输出层, 原作者此处代码有风险
c2 = make_divisible(c2 * gw, 8) # 实际输出通道数是 配置文件的c2 * width_multiple 并向上取到可被8整除
args = [c1, c2, *args[1:]]
if m in [BottleneckCSP, C3, C3TR, C3k2, C2PSA]:
args.insert(2, n) # number of repeats 对C3和BottleneckCSP来说深度n代表残差模块的个数, C3TR的n表transformer的head数
n = 1 # 置1表示深度对这三个模块是控制子结构重复, 而不是本身重复
elif m is nn.BatchNorm2d:
args = [ch[f]] # 对BN层, 参数就是输入层的通道数
elif m is Concat:
c2 = sum([ch[x] for x in f]) # Concat层, 输出通道就是几个输入层通道数相加
elif m is Detect:
args.append([ch[x] for x in f]) # 检测层, 把来源下标列表f中的层输出通道数加入args中, 用于构建Detect的卷积输入通道数
if isinstance(args[1], int): # number of anchors 一般跑不进这句, args[1]是anchors在配置文件中已用列表写好, 非int
args[1] = [list(range(args[1] * 2))] * len(f)
elif m in [SegMaskBiSe, SegMaskLab, SegMaskBase, SegMaskPSP, SegMaskPSP_tte]: # 语义分割头
args[1] = max(round(args[1] * gd), 1) if args[1] > 1 else args[1] # SegMask 中 C3 的n(Lab里用来控制ASPP砍多少通道)
args[2] = make_divisible(args[2] * gw, 8) # SegMask C3(或其他可放缩子结构) 的输出通道数
args.append([ch[x] for x in f])
# n = 1 不用设1了, SegMask自己配置文件的n永远1
elif m is Contract:
c2 = ch[f] * args[0] ** 2
elif m is Expand:
c2 = ch[f] // args[0] ** 2
# 添加bifpn_concat结构
elif m in [Concat, BiFPN_Concat2, BiFPN_Concat3]:
c2 = sum(ch[x] for x in f)
else:
c2 = ch[f]
m_ = nn.Sequential(*[m(*args) for _ in range(n)]) if n > 1 else m(*args) # module 深度控制C3等的block子结构重复次数(见上if中n置为1), 对Conv等则是其本身重复次数
t = str(m)[8:-2].replace('__main__.', '') # module type
np = sum([x.numel() for x in m_.parameters()]) # number params
m_.i, m_.f, m_.type, m_.np = i, f, t, np # attach index, 'from' index, type, number params
logger.info('%3s%18s%3s%10.0f %-40s%-30s' % (i, f, n, np, t, args)) # print
save.extend(x % i for x in ([f] if isinstance(f, int) else f) if x != -1) # append to savelist 由来源记哪些层的结果保存
layers.append(m_) # 解析结果加到layers列表
if i == 0:
ch = [] # 如果第一层,新建ch列表保存输出通道数
ch.append(c2) # 保存此层输出通道数, 下一层输入通道
return nn.Sequential(*layers), sorted(save)
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('--cfg', type=str, default='models/yolov5s_custom_seg.yaml', help='model.yaml')
parser.add_argument('--device', default='0', help='cuda device, i.e. 0 or 0,1,2,3 or cpu')
opt = parser.parse_args()
opt.cfg = check_file(opt.cfg) # check file
set_logging()
device = select_device(opt.device)
# Create model
model = Model(opt.cfg).to(device)
model.train()
# model.eval()
pass
# a = torch.randn((1, 3, 1024, 2048), device=device)
# model(a)
# Profile
# img = torch.rand(8 if torch.cuda.is_available() else 1, 3, 640, 640).to(device)
# y = model(img, profile=True)
# Tensorboard
# from torch.utils.tensorboard import SummaryWriter
# tb_writer = SummaryWriter()
# print("Run 'tensorboard --logdir=models/runs' to view tensorboard at http://localhost:6006/")
# tb_writer.add_graph(model.model, img) # add model to tensorboard
# tb_writer.add_image('test', img[0], dataformats='CWH') # add model to tensorboard
更多推荐



所有评论(0)