专栏名称: AI开发者
AI研习社,雷锋网旗下关注AI开发技巧及技术教程订阅号。
目录
相关文章推荐
宝玉xp  ·  //@时蝇喜箭:@宝玉xp-20250311 ... ·  2 天前  
宝玉xp  ·  转发微博-20250310082610 ·  3 天前  
爱可可-爱生活  ·  //@爱可可-爱生活:欢迎参与!-20250 ... ·  3 天前  
宝玉xp  ·  回复@Timetopretenddd:这我还 ... ·  3 天前  
51好读  ›  专栏  ›  AI开发者

专栏 | 【从零开始学习YOLOv3】5. 网络模型的构建

AI开发者  · 公众号  · AI  · 2020-02-26 18:32

正文


点击上方“蓝字”关注“AI开发者”


本文来自 @BBuf 的社区专栏 GiantPandaCV ,文末扫码即可订阅专栏。

前言:之前几篇讲了cfg文件的理解、数据集的构建、数据加载机制和超参数进化机制,本文将讲解YOLOv3如何从cfg文件构造模型。本文涉及到一个比较有用的部分就是bias的设置,可以提升mAP、F1、P、R等指标,还能让训练过程更加平滑。

1. cfg文件

在YOLOv3中,修改网络结构很容易,只需要修改cfg文件即可。目前,cfg文件支持convolutional, maxpool, unsample, route, shortcut, yolo这几个层。

而且作者也提供了多个cfg文件来进行网络构建,比如:yolov3.cfg、yolov3-tiny.cfg、yolov3-spp.cfg、csresnext50-panet-spp.cfg文件(提供的yolov3-spp-pan-scale.cfg文件,在代码级别还没有提供支持)。

如果想要添加自定义的模块也很方便,比如说注意力机制模块、空洞卷积等,都可以简单地得到添加或者修改。

为了更加方便的理解cfg文件网络是如何构建的,在这里推荐一个Github上的网络结构可视化软件: Netron ,下图是可视化yolov3-tiny的结果:

2. 网络模型构建

train.py 文件入手,其中涉及的网络构建的代码为:

# Initialize model
model = Darknet(cfg, arc=opt.arc).to(device)

然后沿着Darknet实现进行讲解:

class Darknet(nn.Module):
   # YOLOv3 object detection model
   def __init__(self, cfg, img_size=(416, 416), arc='default'):
       super(Darknet, self).__init__()
       self.module_defs = parse_model_cfg(cfg)
       self.module_list, self.routs = create_modules(self.module_defs, img_size, arc)
       self.yolo_layers = get_yolo_layers(self)

       # Darknet Header
       self.version = np.array([0, 2, 5], dtype=np.int32)
       # (int32) version info: major, minor, revision
       self.seen = np.array([0], dtype=np.int64)
       # (int64) number of images seen during training

以上文件中,比较关键的就是成员函变量 module_defs module_list routs yolo_layers 四个成员函数,先对这几个参数的意义进行解释:

2.1 module_defs

调用了 parse_model_cfg 函数,得到了 module_defs 对象。实际上该函数是通过解析cfg文件,得到一个list,list中包含多个字典,每个字典保存的内容就是一个模块内容,比如说:

[convolutional]
batch_normalize=1
filters=128
size=3
stride=2
pad=1
activation=leaky

函数代码如下:

def parse_model_cfg(path):
   # path参数为: cfg/yolov3-tiny.cfg
   ifnot path.endswith('.cfg'):
       path += '.cfg'
   ifnot os.path.exists(path) and os.path.exists('cfg' + os.sep + path):
       path = 'cfg' + os.sep + path

   with open(path, 'r') as f:
       lines = f.read().split('\n')

   # 去除以#开头的,属于注释部分的内容
   lines = [x for x in lines if x andnot x.startswith('#')]
   lines = [x.rstrip().lstrip() for x in lines]
   mdefs = []  # 模块的定义
   for line in lines:
       if line.startswith('['):  # 标志着一个模块的开始
           '''
           比如:
           [shortcut]
           from=-3
           activation=linear
           '''

           mdefs.append({})
           mdefs[-1]['type'] = line[1:-1].rstrip()
           if mdefs[-1]['type'] == 'convolutional':
               mdefs[-1]['batch_normalize'] = 0  
               # pre-populate with zeros (may be overwritten later)
       else:
           # 将键和键值放入字典
           key, val = line.split("=")
           key = key.rstrip()

           if'anchors'in key:
               mdefs[-1][key] = np.array([float(x) for x in val.split(',')]).reshape((-1, 2))  # np anchors
           else:
               mdefs[-1][key] = val.strip()

   # 支持的参数类型
   supported = ['type', 'batch_normalize', 'filters', 'size',\
                'stride', 'pad', 'activation', 'layers', 'groups',\
                'from', 'mask', 'anchors', 'classes', 'num', 'jitter', \
                'ignore_thresh', 'truth_thresh', 'random',\
                'stride_x', 'stride_y']

   # 判断所有参数中是否有不符合要求的key
   f = []
   for x in mdefs[1:]:
       [f.append(k) for k in x if k notin f]
   u = [x for x in f if x notin supported]  # unsupported fields
   assertnot any(u), "Unsupported fields %s in %s. See https://github.com/ultralytics/yolov3/issues/631" % (u, path)

   return mdefs

返回的内容通过debug模式进行查看:

其中需要关注的就是anchor的组织:

可以看出,anchor是按照每两个一对进行组织的,与我们的理解一致。

2.2 module_list&routs

这个部分是本文的核心,也是理解模型构建的关键。

在pytorch中,构建模型常见的有通过Sequential或者ModuleList进行构建。

通过Sequential构建

model=nn.Sequential()
model.add_module('conv',nn.Conv2d(3,3,3))
model.add_module('batchnorm',nn.BatchNorm2d(3))
model.add_module('activation_layer',nn.ReLU())

或者

model=nn.Sequential(
   nn.Conv2d(3,3,3),
   nn.BatchNorm2d(3),
   nn.ReLU()
   )

或者

from collections import OrderedDict
model=nn.Sequential(OrderedDict([
   ('conv',nn.Conv2d(3,3,3)),
   ('batchnorm',nn.BatchNorm2d(3)),
   ('activation_layer',nn.ReLU())
]))

通过sequential构建的模块内部 实现了forward函数 ,可以直接传入参数,进行调用。

通过ModuleList构建

model=nn.ModuleList([nn.Linear(3,4),
nn.ReLU(),
nn.Linear(4,2)])

ModuleList类似list,内部 没有实现forward函数 ,使用的时候需要构建forward函数,构建自己模型常用ModuleList函数建立子模型,建立forward函数实现前向传播。

在YOLOv3中,灵活地结合了两种使用方式,通过解析以上得到的module_defs,进行构建一个ModuleList,然后再通过构建forward函数进行前向传播即可。

具体代码如下:

def create_modules(module_defs, img_size, arc):
   # 通过module_defs进行构建模型
   hyperparams = module_defs.pop(0)
   output_filters = [int(hyperparams['channels'])]
   module_list = nn.ModuleList()
   routs = []  # 存储了所有的层,在route、shortcut会使用到。
   yolo_index = -1

   for i, mdef in enumerate(module_defs):
       modules = nn.Sequential()
       '''
       通过type字样不同的类型,来进行模型构建
       '''

       if mdef['type'] == 'convolutional':
           bn = int(mdef['batch_normalize'])
           filters = int(mdef['filters'])
           size = int(mdef['size'])
           stride = int(mdef['stride']) if'stride'in mdef else (int(
               mdef['stride_y']), int(mdef['stride_x']))
           pad = (size - 1) // 2if int(mdef['pad']) else0
           modules.add_module(
               'Conv2d',
               nn.Conv2d(
                   in_channels=output_filters[-1],
                   out_channels=filters,
                   kernel_size=size,
                   stride=stride,
                   padding=pad,
                   groups=int(mdef['groups']) if'groups'in mdef else1,
                   bias=not bn))
           if bn:
               modules.add_module('BatchNorm2d',
                                  nn.BatchNorm2d(filters, momentum=0.1))
           if mdef['activation'] == 'leaky':  # TODO: activation study https://github.com/ultralytics/yolov3/issues/441
               modules.add_module('activation', nn.LeakyReLU(0.1,
                                                             inplace=True))
           elif mdef['activation'] == 'swish':
               modules.add_module('activation', Swish())
           # 在此处可以添加新的激活函数

       elif mdef['type'] == 'maxpool':
           # 最大池化操作
           size = int(mdef['size'])
           stride = int(mdef['stride'])
           maxpool = nn.MaxPool2d(kernel_size=size,
                                  stride=stride,
                                  padding=int((size - 1) // 2))
           if size == 2and stride == 1:  # yolov3-tiny
               modules.add_module('ZeroPad2d', nn.ZeroPad2d((0, 1, 0, 1)))
               modules.add_module('MaxPool2d', maxpool)
           else:
               modules = maxpool

       elif mdef['type'] == 'upsample':
           # 通过近邻插值完成上采样
           modules = nn.Upsample(scale_factor=int(mdef['stride']),
                                 mode='nearest')

       elif mdef['type'] == 'route':
           # nn.Sequential() placeholder for 'route' layer
           layers = [int(x) for x in mdef['layers'].split(',')]
           filters = sum(
               [output_filters[i + 1if i > 0else i] for i in layers])
           # extend表示添加一系列对象
           routs.extend([l if l > 0else l + i for l in layers])

       elif mdef['type'] == 'shortcut':
           # nn.Sequential() placeholder for 'shortcut' layer
           filters = output_filters[int(mdef['from'])]
           layer = int(mdef['from'])
           routs.extend([i + layer if layer < 0else layer])

       elif






请到「今天看啥」查看全文