😎😎😎物体检测-系列教程 总目录
有任何问题欢迎在下面留言
本篇文章的代码运行界面均在Pycharm中进行
本篇文章配套的代码资源已经上传
点我下载源码
14、Model类
14.2 前向传播
def forward(self, x, augment=False, profile=False):if augment:img_size = x.shape[-2:] # height, widths = [1, 0.83, 0.67] # scalesf = [None, 3, None] # flips (2-ud, 3-lr)y = [] # outputsfor si, fi in zip(s, f):xi = scale_img(x.flip(fi) if fi else x, si)yi = self.forward_once(xi)[0] # forwardyi[..., :4] /= si # de-scaleif fi == 2:yi[..., 1] = img_size[0] - yi[..., 1] # de-flip udelif fi == 3:yi[..., 0] = img_size[1] - yi[..., 0] # de-flip lry.append(yi)return torch.cat(y, 1), None # augmented inference, trainelse:return self.forward_once(x, profile) # single-scale inference, train
这段代码是forward
方法的实现,它定义了模型的前向传播过程,支持正常和增强两种推理模式:
- 前向传播函数,输入
x
,是否进行数据增强augment
,是否分析性能profile
- 是否使用数据增强
- img_size ,获取输入图像的长宽
- s,定义缩放尺度
- f,定义翻转模式,这里
None
表示不翻转,3
表示左右翻转 - y,初始化输出列表
- 使用zip函数将尺度因子列表s和翻转指示列表f组合起来,然后遍历每一对尺度因子和翻转指示
- xi,如果fi不为None,先根据fi的值对图像进行翻转,然后调用scale_img函数根据si的值缩放处理图像;否则直接调用scale_img函数根据si的值缩放处理图像
- yi,将xi进行一次前向传播,取第一个输出
- 对输出yi的前四个维度进行缩放调整,以恢复到原始的尺度。这通常是对边界框坐标的调整
- 如果使用了上下翻转
- 则调整y的坐标
- 如果使用了左右翻转
- 则调整x坐标
- 将处理后的输出添加到列表
- 将list y的所有输出按照第一个维度进行拼接
- 如果在当前循环中没有使用数据增强
- 直接进行一次正常的前向传播
前向传播方法,包括了一个可选的图像增强步骤。在增强模式下,通过对输入图像应用不同的尺度和翻转,生成多个变体,对每个变体单独进行前向传播,并对输出进行调整以适应原始图像的尺寸和方向,最后将所有变体的输出合并。这种方法可以增加模型的泛化能力,因为它让模型在训练时见到更多的数据变化。如果不进行图像增强,它将执行一次标准的前向传播。通过这种设计,模型可以更灵活地应对不同的输入和训练需求
14.3 forward_once函数
def forward_once(self, x, profile=False):y, dt = [], [] # outputsfor m in self.model:if m.f != -1: # if not from previous layerx = y[m.f] if isinstance(m.f, int) else [x if j == -1 else y[j] for j in m.f]if profile:try:import thopo = thop.profile(m, inputs=(x,), verbose=False)[0] / 1E9 * 2 # FLOPSexcept:o = 0t = 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))x = m(x) # runy.append(x if m.i in self.save else None) # save outputif profile:print('%.1fms total' % sum(dt))return x
- forward_once函数,输入和forward函一样
- y, dt ,初始化两个空列表,y用于存储每一层的输出,dt用于在性能分析模式下存储每一层的执行时间
- 遍历模型的每一层
- 如果当前层的输入不是来自上一层的输出
- 如果m.f是整数,则直接从y中获取对应的层输出作为输入。如果m.f是一个列表,则根据列表中的索引从y中选择输入,如果索引为-1,则使用原始输入x
- 是否开启性能分析模式
- try
- 导入thop库,用于计算浮点运算数(FLOPS)
- o,使用thop.profile计算当前层m的FLOPS,结果除以1E9转换为GigaFLOPS,并乘以2。这里假设thop.profile返回的是一个元组,其第一个元素是所需的FLOPS
- 如果尝试执行失败
- 则将o(FLOPS)设置为0
- t,调用time_synchronized函数,获取当前精确的时间
- 循环10次
- 为了稳定测量时间,通过多次执行减少偶然误差
- 调用time_synchronized函数计算执行当前层操作的总时间,并将其添加到dt列表中
- 打印当前层的FLOPS、参数数量、执行时间和层类型。为性能分析提供详细信息
- 执行当前层的前向传播,并更新x为该层的输出
- 如果当前层的索引m.i在保存列表self.save中,则将输出x保存到y列表中;否则,保存
None
. 这样做可以减少内存占用,只保存那些后续步骤中需要的层的输出 - 再次检查是否开启了性能分析模式。这个检查是为了在性能分析完成后打印总的执行时间
- 如果开启了性能分析,计算所有层执行时间的总和并打印。这提供了整个前向传播过程的总执行时间,帮助了解模型的性能瓶颈
- 返回最后一层的输出
14.4 _initialize_biases函数
def _initialize_biases(self, cf=None):m = self.model[-1] # Detect() modulefor mi, s in zip(m.m, m.stride): # fromb = mi.bias.data.view(m.na, -1).clone()obj_add = math.log(8 / (640 / s) ** 2) # 计算obj层需要增加的值cls_add = math.log(0.6 / (m.nc - 0.99)) if cf is None else torch.log(cf / cf.sum())b[:, 4] = b[:, 4] + obj_addb[:, 5:] = b[:, 5:] + cls_addmi.bias = torch.nn.Parameter(b.view(-1), requires_grad=True)
- 初始化偏执的函数,接受一个可选的参数,这个参数用于根据数据集中各类别出现的频率来调整分类(cls)层的偏置
- m,获取模型中的最后一个模块,检测层(Detect模块),用于目标检测
- 遍历检测层中的每个子模块mi及其对应的步长stride,这里的步长是指输入图像被缩减的尺度,对目标尺寸预测非常关键
- b,获取子模块mi的偏置项,并将其重塑(reshape)成(m.na, -1)的形状,其中m.na是每个特征图位置预测的锚框数量。.clone()确保在修改b时不会影响原始的偏置值
- obj_add ,计算对象(obj)层偏置需要增加的值。这个公式基于假设每640像素的图像中有8个对象,并根据特征图的尺度(通过步长s计算)来调整。目的是调整检测层对于不同尺寸特征图上对象数量预测的偏置
- cls_add ,计算分类(cls)层偏置需要增加的值。如果没有提供类频率(cf为None),则使用一个基于类数量m.nc的固定公式。如果提供了类频率,那么使用类频率来计算每个类的偏置调整值,以此反映数据集中类别的分布
- 将计算出的对象层偏置调整值加到b的第4列上,这是因为在目标检测中,偏置项通常包括4个坐标偏置和一个对象存在的偏置,后者位于第5个位置(索引为4)
- 将计算出的分类层偏置调整值加到b的第5列及之后的所有列上,对应于每个类别的偏置
- 将调整后的偏置b重塑回原始形状并设置为mi的偏置,确保这些偏置在训练过程中可以被进一步调整(requires_grad=True)
14.5 其他辅助函数
def _print_biases(self):m = self.model[-1] # Detect() modulefor mi in m.m: # fromb = 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()))
- 获取模型的最后一个模块,这里假设是一个目标检测模块(Detect模块)
- 遍历检测模块中的每个子模块mi
- 取得当前子模块mi的偏置,通过.detach()确保不会影响梯度计算,.view(m.na, -1)调整形状以匹配锚点数量m.na和偏置的其它维度,最后进行转置以便于处理
- 打印当前子模块卷积层的输入通道数和偏置的统计信息,包括前五个偏置的平均值和之后所有偏置的平均值
fuse函数,用于融合模型中的卷积层(Conv2d)和批归一化层(BatchNorm2d)
def fuse(self): # fuse model Conv2d() + BatchNorm2d() layersprint('Fusing layers... ')for m in self.model.modules():if type(m) is Conv:m._non_persistent_buffers_set = set() # pytorch 1.6.0 compatabilitym.conv = fuse_conv_and_bn(m.conv, m.bn) # update convm.bn = None # remove batchnormm.forward = m.fuseforward # update forwardself.info()return self
- 遍历模型中的所有模块
- 检查当前模块是否为卷积层
- 为了兼容PyTorch 1.6.0,清空非持久性缓冲区集合
- 使用fuse_conv_and_bn函数来融合当前卷积层和其后的批归一化层
- 将批归一化层设为None,表示移除批归一化层
- 更新模块的前向传播函数为融合后的版本
- 在完成融合后,调用info方法打印模型信息
- 返回更新后的模型实例
def info(self): # print model informationmodel_info(self)
调用一个model_info函数,传入当前模型实例,用于收集和打印模型的详细信息,如参数数量、层的类型等