利用yaml定义卷积网络【附代码】

简介: 笔记

在平常看一些卷积神经网络的时候,大多数都是直接通过写一个Model类来定义的,这样写的代码其实是比较好懂的,特别是在魔改网络的时候也很方便。然后也有一些会通过cfg配置文件进行模型的定义。在yolov5中可以看到是通过yaml文件进行网络的定义【个人感觉通过配置文件魔改网络有些不方便,当然每个人习惯不同】,可能很多人也用过,如果自己去写一个yaml文件,自己能不能定义出来呢?很多人不知道是如何具体通过yaml文件将里面的参数传入自己定义的网络中,这也就给自己修改网络带来了不便。这篇文章将仿照yolov5的方式,利用yaml定义一个自己的网络。


定义卷积块


我们可以先定义一个卷积块CBL,C指卷积Conv,B指BN层,L为激活函数,这里我用ReLu.

class BaseConv(nn.Module):
    def __init__(self, in_channels, out_channels, k=1, s=1, p=None):
        super().__init__()
        self.in_channels = in_channels
        self.out_channels = out_channels
        self.conv = nn.Conv2d(in_channels, out_channels, k, s, autopad(k, p))
        self.bn = nn.BatchNorm2d(out_channels)
        self.act_fn = nn.ReLU(inplace=True)
    def forward(self, x):
        return self.act_fn(self.bn(self.conv(x)))

卷积中的autopad是自动补充pad,代码如下:

def autopad(k, p=None):
    if p is None:
        p = k // 2 if isinstance(k, int) else [x // 2 for x in k]
    return p

定义一个Bottleneck


可以仿照yolov5定义一个Bottleneck,参考了残差块的思想。

class Bottleneck(nn.Module):
    def __init__(self, in_channels, out_channels, shortcut=True):
        super(Bottleneck, self).__init__()
        self.conv1 = BaseConv(in_channels, out_channels, k=1, s=1)
        self.conv2 = BaseConv(out_channels, out_channels, k=3, s=1)
        self.add = shortcut and in_channels == out_channels
    def forward(self, x):
        """
        x-->conv1-->conv2-->add
          |_________________|
        """
        return x + self.conv2(self.conv1(x)) if self.add else self.conv2(self.conv1(x))

攥写yaml配置文件


然后我们来写一下yaml配置文件,网络不要很复杂,就由两个卷积和两个Bottleneck组成就行。同理,仿v5的方法,我们的网络中的backone也是个列表,每行为一个卷积层,每列有4个参数,分别代表from(指该层的输入通道数为上一层的输出通道数,所以是-1),number【yaml中的1,1,2指该层的深度,或者说是重复几次】,Module_nams【该层的名字】,args【网络参数,包含输出通道数,k,s,p等设置】

# define own model
backbone:
  [[-1, 1, BaseConv, [32, 3, 1]],  # out_channles=32, k=3, s=1
   [-1, 1, BaseConv, [64, 1, 1]],
   [-1, 2, Bottleneck, [64]]
  ]

我们现在用yaml工具来打开我们的配置文件,看看都有什么内容

    import yaml
    # 获得yaml文件名字
    yaml_file = Path('Model.yaml').name
    with open(yaml_file,errors='ignore') as f:
        yaml_ = yaml.safe_load(f)
    print(yaml_)

输出:


{'backbone': [[-1, 1, 'BaseConv', [32, 3, 1]], [-1, 1, 'BaseConv', [64, 1, 1]], [-1, 2, 'Bottleneck', [64]]]}


然后我们可以定义下自己Model类,也就是定义自己的网络。可以看到与前面读取yaml文件相比,多了一行    ch = self.yaml["ch"] = self.yaml["ch"] = 3   这个是在原yaml内容中加入一个key和valuse,3指的3通道,因为我们的图像是3通道。parse_model是下面要说的传参过程。

class Model(nn.Module):
    def __init__(self, cfg='./Model.yaml', ch=3, ):
        super().__init__()
        self.yaml = cfg
        import yaml
        yaml_file = Path(cfg).name
        with open(yaml_file, errors='ignore')as f:
            self.yaml = yaml.safe_load(f)
        ch = self.yaml["ch"] = self.yaml["ch"] = 3
        self.backbone = parse_model(deepcopy(self.yaml), ch=[ch])
    def forward(self, x):
        output = self.backbone(x)
        return output

传入参数


这一步也是最关键的一步,我们需要定义传参的函数,将yaml中的卷积参数传入我们定义的网络中,这里会用的一个非常非常重要的函数eval(),后面也会介绍到这个函数的用法。

这里先附上完整代码:

def parse_model(yaml_cfg, ch):
    """
    :param yaml_cfg: yaml file
    :param ch: init in_channels default is 3
    :return: model
    """
    layer, out_channels = [], ch[-1]
    for i, (f, number, Module_name, args) in enumerate(yaml_cfg['backbone']):
        """
        f:上一层输出通道
        number:该模块有几层,就是该模块要重复几次
        Mdule_name:卷积层名字
        args:参数,包含输出通道数,k,s,p等
        """
        # 通过eval,将str类型转自己定义的BaseConv
        m = eval(Module_name) if isinstance(Module_name, str) else Module_name
        for j, a in enumerate(args):
            # 通过eval,将str转int,获得输出通道数
            args[j] = eval(a) if isinstance(a, str) else a
        # 更新通道
        # args[0]是输出通道
        if m in [BaseConv, Bottleneck]:
            in_channels, out_channels = ch[f], args[0]
            args = [in_channels, out_channels, *args[1:]]  # args=[in_channels, out_channels, k, s, p]
        # 将参数传入模型
        model_ = nn.Sequential(*[m(*args) for _ in range(number)]) if number > 1 else m(*args)
        # 更新通道列表,每次获取输出通道
        ch.append(out_channels)
        layer.append(model_)
    return nn.Sequential(*layer)

下面开始分析代码 。


这行代码是通过列表用来存放每层内容以及输出通道数。


# 这行代码是通过列表用来存放每层内容以及输出通道数
layer, out_channels = [], ch[-1]

然后进入我们的for循环,在每一次循环中可以获得我们yaml文件中的每一层网络:f是上一层网络的输出通道【用来作为本层的输入通道】,number【网络深度,也就是该层重复几次而已】,Module_name是该层的名字,args是该层的一些参数。


for i, (f, number, Module_name, args) in enumerate(yaml_cfg['backbone']):

接下来会碰到一个很重要的函数eval()。下行的代码首先需要判断一下我们的Module_name类型是不是字符串类型,也就是判断一下yaml中“BaseConv”是不是字符串类型,如果是,则用eval进行对应类型的转化,转成我们的BaseConv类型。


m = eval(Module_name) if isinstance(Module_name, str) else Module_name

这里我将对eval函数在深入点,如果知道这个函数用法的,就可以略去这部分。


我们先举个例子,比如我现在有个变量a="123",这个a的类型是什么呢?他是一个str类型,不是int类型。 现在我们用eval函数转一下,看看会变成什么样子。


>>> b = eval(a) if isinstance(a,str) else a
>>> b
123
>>> type(b)
<class 'int'>

我们可以看到,经过eval函数以后,会自动识别并转为int类型。那么我继续举例子,如果现在a="BaseConv",经过eval以后会变成什么?可以看到,这里报错了!这是为什么?这是因为我们没有导入BaseConv这个类,所以eval函数并不知道我们希望转为什么类型。所以我们需要用import导入BaseConv这个类才可以。


>>> a="BaseConv"
>>> b = eval(a) if isinstance(a,str) else a
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<string>", line 1, in <module>
NameError: name 'BaseConv' is not defined

当我们导入BaseConv以后,在经过eval就可以获得:


<class 'models.BaseConv'>


接下来是获得args中的网络参数,也是通过eval进行转化


   

for j, a in enumerate(args):
            # 通过eval,将str转int,获得输出通道数
            args[j] = eval(a) if isinstance(a, str) else a

获取通道数,并在每次循环中对通道进行更新:可以仔细看一下ch[f]指的上一层输出通道,刚开始默认为[3],那么ch[-1]=3,我们yaml中第一层的BaseConv args[0]为32,表示输出32通道。因此在第一次循环中有in_channels = 3,out_channels=32。args也要更新,*args前面的"*"并不是指针的意思,也不是乘的意思,而是解压操作,因此我们第一次循环中得到的args=[3,32,3,1]。


# 更新通道
# args[0]是输出通道
if m in [BaseConv, Bottleneck]:
    in_channels, out_channels = ch[f], args[0]
    args = [in_channels, out_channels, *args[1:]]  # args=[in_channels, out_channels, k, s, p]

将参数传入模型


这里用for _ in range(number)来判断网络的深度【或者说该模块重复几次】,这里的m就是前面经过eval转化的 <class 'models.BaseConv'>。通过*args解压操作将args列表中的内容放入m中,再通过*解压操作放入nn.Sequential。


model_ = nn.Sequential(*[m(*args) for _ in range(number)]) if number > 1 else m(*args)

这样就可以获得我们第一次循环BaseConv了。后面的循环也是同样的反复操作而已。


BaseConv(
  (conv): Conv2d(3, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
  (bn): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (act_fn): ReLU(inplace=True)
)

然后是更新通道列表和layer列表,为的是获取每次循环的输出通道,没有这一步,再下一次循环的时候将不能正确得到通道数。


# 更新通道列表,每次获取输出通道
ch.append(out_channels)
layer.append(model_)

然后我们就可以对模型调用进行实例化了,可以打印下模型:


Model(
  (backbone): Sequential(
    (0): BaseConv(
      (conv): Conv2d(3, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
      (bn): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (act_fn): ReLU(inplace=True)
    )
    (1): BaseConv(
      (conv): Conv2d(32, 64, kernel_size=(1, 1), stride=(1, 1))
      (bn): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (act_fn): ReLU(inplace=True)
    )
    (2): Sequential(
      (0): Bottleneck(
        (conv1): BaseConv(
          (conv): Conv2d(64, 64, kernel_size=(1, 1), stride=(1, 1))
          (bn): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
          (act_fn): ReLU(inplace=True)
        )
        (conv2): BaseConv(
          (conv): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
          (bn): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
          (act_fn): ReLU(inplace=True)
        )
      )
      (1): Bottleneck(
        (conv1): BaseConv(
          (conv): Conv2d(64, 64, kernel_size=(1, 1), stride=(1, 1))
          (bn): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
          (act_fn): ReLU(inplace=True)
        )
        (conv2): BaseConv(
          (conv): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
          (bn): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
          (act_fn): ReLU(inplace=True)
        )
      )
    )
  )
)


同时我们也可以对模型每层可视化看一下。可以看到和我们定义的模型是一样的。

8.png

上述完整的代码:

from copy import deepcopy
from models import BaseConv, Bottleneck
import torch.nn as nn
import os
path = os.getcwd()
from pathlib import Path
import torch
def parse_model(yaml_cfg, ch):
    """
    :param yaml_cfg: yaml file
    :param ch: init in_channels default is 3
    :return: model
    """
    layer, out_channels = [], ch[-1]
    for i, (f, number, Module_name, args) in enumerate(yaml_cfg['backbone']):
        """
        f:上一层输出通道
        number:该模块有几层,就是该模块要重复几次
        Mdule_name:卷积层名字
        args:参数,包含输出通道数,k,s,p等
        """
        # 通过eval,将str类型转自己定义的BaseConv
        m = eval(Module_name) if isinstance(Module_name, str) else Module_name
        for j, a in enumerate(args):
            # 通过eval,将str转int,获得输出通道数
            args[j] = eval(a) if isinstance(a, str) else a
        # 更新通道
        # args[0]是输出通道
        if m in [BaseConv, Bottleneck]:
            in_channels, out_channels = ch[f], args[0]
            args = [in_channels, out_channels, *args[1:]]  # args=[in_channels, out_channels, k, s, p]
        # 将参数传入模型
        model_ = nn.Sequential(*[m(*args) for _ in range(number)]) if number > 1 else m(*args)
        # 更新通道列表,每次获取输出通道
        ch.append(out_channels)
        layer.append(model_)
    return nn.Sequential(*layer)
class Model(nn.Module):
    def __init__(self, cfg='./Model.yaml', ch=3, ):
        super().__init__()
        self.yaml = cfg
        import yaml
        yaml_file = Path(cfg).name
        with open(yaml_file, errors='ignore')as f:
            self.yaml = yaml.safe_load(f)
        ch = self.yaml["ch"] = self.yaml["ch"] = 3
        self.backbone = parse_model(deepcopy(self.yaml), ch=[ch])
    def forward(self, x):
        output = self.backbone(x)
        return output
if __name__ == "__main__":
    cfg = path + '/Model.yaml'
    model = Model()
    model.eval()
    print(model)
    x = torch.ones(1, 3, 512, 512)
    output = model(x)
    torch.save(model, "model.pth")
    # model = torch.load('model.pth')
    # model.eval()
    # x = torch.ones(1,3,512,512)
    # input_name = ['input']
    # output_name = ['output']
    # torch.onnx.export(model, x, 'myonnx.onnx', verbose=True)
目录
相关文章
|
2月前
|
机器学习/深度学习 PyTorch TensorFlow
卷积神经网络深度解析:从基础原理到实战应用的完整指南
蒋星熠Jaxonic,深度学习探索者。深耕TensorFlow与PyTorch,分享框架对比、性能优化与实战经验,助力技术进阶。
|
3月前
|
机器学习/深度学习 人工智能 算法
卷积神经网络深度解析:从基础原理到实战应用的完整指南
蒋星熠Jaxonic带你深入卷积神经网络(CNN)核心技术,从生物启发到数学原理,详解ResNet、注意力机制与模型优化,探索视觉智能的演进之路。
405 11
|
10月前
|
机器学习/深度学习 编解码 自动驾驶
RT-DETR改进策略【模型轻量化】| 替换骨干网络为MoblieNetV1,用于移动视觉应用的高效卷积神经网络
RT-DETR改进策略【模型轻量化】| 替换骨干网络为MoblieNetV1,用于移动视觉应用的高效卷积神经网络
400 3
RT-DETR改进策略【模型轻量化】| 替换骨干网络为MoblieNetV1,用于移动视觉应用的高效卷积神经网络
|
6月前
|
机器学习/深度学习 算法 数据安全/隐私保护
基于PSO粒子群优化TCN-LSTM时间卷积神经网络时间序列预测算法matlab仿真
本内容展示了一种基于粒子群优化(PSO)与时间卷积神经网络(TCN)的时间序列预测方法。通过 MATLAB2022a 实现,完整程序运行无水印,核心代码附详细中文注释及操作视频。算法利用 PSO 优化 TCN 的超参数(如卷积核大小、层数等),提升非线性时间序列预测性能。TCN 结构包含因果卷积层与残差连接,结合 LSTM 构建混合模型,经多次迭代选择最优超参数,最终实现更准确可靠的预测效果,适用于金融、气象等领域。
|
3月前
|
机器学习/深度学习 传感器 数据采集
【故障识别】基于CNN-SVM卷积神经网络结合支持向量机的数据分类预测研究(Matlab代码实现)
【故障识别】基于CNN-SVM卷积神经网络结合支持向量机的数据分类预测研究(Matlab代码实现)
235 0
|
5月前
|
机器学习/深度学习 人工智能 PyTorch
零基础入门CNN:聚AI卷积神经网络核心原理与工业级实战指南
卷积神经网络(CNN)通过局部感知和权值共享两大特性,成为计算机视觉的核心技术。本文详解CNN的卷积操作、架构设计、超参数调优及感受野计算,结合代码示例展示其在图像分类、目标检测等领域的应用价值。
297 7
|
7月前
|
机器学习/深度学习 人工智能 算法
深度解析:基于卷积神经网络的宠物识别
宠物识别技术随着饲养规模扩大而兴起,传统手段存在局限性,基于卷积神经网络的宠物识别技术应运而生。快瞳AI通过优化MobileNet-SSD架构、多尺度特征融合及动态网络剪枝等技术,实现高效精准识别。其在智能家居、宠物医疗和防走失领域展现广泛应用前景,为宠物管理带来智能化解决方案,推动行业迈向新高度。
|
7月前
|
机器学习/深度学习 算法 数据安全/隐私保护
基于PSO粒子群优化TCN时间卷积神经网络时间序列预测算法matlab仿真
本内容介绍了一种基于PSO(粒子群优化)改进TCN(时间卷积神经网络)的时间序列预测方法。使用Matlab2022a运行,完整程序无水印,附带核心代码中文注释及操作视频。TCN通过因果卷积层与残差连接处理序列数据,PSO优化其卷积核权重等参数以降低预测误差。算法中,粒子根据个体与全局最优位置更新速度和位置,逐步逼近最佳参数组合,提升预测性能。
|
6月前
|
机器学习/深度学习 数据采集 监控
基于CNN卷积神经网络和GEI步态能量提取的步态识别算法matlab仿真,对比不同角度下的步态识别性能
本项目基于CNN卷积神经网络与GEI步态能量提取技术,实现高效步态识别。算法使用不同角度(0°、45°、90°)的步态数据库进行训练与测试,评估模型在多角度下的识别性能。核心流程包括步态图像采集、GEI特征提取、数据预处理及CNN模型训练与评估。通过ReLU等激活函数引入非线性,提升模型表达能力。项目代码兼容Matlab2022a/2024b,提供完整中文注释与操作视频,助力研究与应用开发。
|
8月前
|
机器学习/深度学习 算法 数据安全/隐私保护
基于GA遗传优化TCN-GRU时间卷积神经网络时间序列预测算法matlab仿真
本项目基于MATLAB2022a开发,提供无水印算法运行效果预览及核心程序(含详细中文注释与操作视频)。通过结合时间卷积神经网络(TCN)和遗传算法(GA),实现复杂非线性时间序列的高精度预测。TCN利用因果卷积层与残差连接提取时间特征,GA优化超参数(如卷积核大小、层数等),显著提升模型性能。项目涵盖理论概述、程序代码及完整实现流程,适用于金融、气象、工业等领域的时间序列预测任务。