攻略

利用yaml定义卷积网络[附代码]

  • 来源:阿里云
  • 时间:2023-02-15 10:59:38

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

定义卷积块


(资料图片仅供参考)

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

classBaseConv(nn.Module):def__init__(self,in_channels,out_channels,k=1,s=1,p=None):super.__init__self.in_channels=in_channelsself.out_channels=out_channelsself.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)defforward(self,x):returnself.act_fn(self.bn(self.conv(x)))

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

defautopad(k,p=None):ifpisNone:p=k//2ifisinstance(k,int)else[x//2forxink]returnp

定义一个Bottleneck

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

classBottleneck(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=shortcutandin_channels==out_channelsdefforward(self,x):"""x-->conv1-->conv2-->add|_________________|"""returnx+self.conv2(self.conv1(x))ifself.addelseself.conv2(self.conv1(x))

攥写yaml配置文件

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

#defineownmodelbackbone:[[-1,1,BaseConv,[32,3,1]],#out_channles=32,k=3,s=1[-1,1,BaseConv,[64,1,1]],[-1,2,Bottleneck,[64]]]

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

importyaml#获得yaml文件名字yaml_file=Path("Model.yaml").namewithopen(yaml_file,errors="ignore")asf: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是下面要说的传参过程。

classModel(nn.Module):def__init__(self,cfg="./Model.yaml",ch=3,):super.__init__self.yaml=cfgimportyamlyaml_file=Path(cfg).namewithopen(yaml_file,errors="ignore")asf:self.yaml=yaml.safe_load(f)ch=self.yaml["ch"]=self.yaml["ch"]=3self.backbone=parse_model(deepcopy(self.yaml),ch=[ch])defforward(self,x):output=self.backbone(x)returnoutput

传入参数

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

这里先附上完整代码:

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

fori,(f,number,Module_name,args)inenumerate(yaml_cfg["backbone"]):

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

m=eval(Module_name)ifisinstance(Module_name,str)elseModule_name

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

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

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

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

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

forj,ainenumerate(args):#通过eval,将str转int,获得输出通道数args[j]=eval(a)ifisinstance(a,str)elsea

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

#更新通道#args[0]是输出通道ifmin[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_inrange(number)来判断网络的深度[或者说该模块重复几次],这里的m就是前面经过eval转化的。通过*args解压操作将args列表中的内容放入m中,再通过*解压操作放入nn.Sequential。

model_=nn.Sequential(*[m(*args)for_inrange(number)])ifnumber>1elsem(*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))))))

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

上述完整的代码:

fromcopyimportdeepcopyfrommodelsimportBaseConv,Bottleneckimporttorch.nnasnnimportospath=os.getcwdfrompathlibimportPathimporttorchdefparse_model(yaml_cfg,ch):""":paramyaml_cfg:yamlfile:paramch:initin_channelsdefaultis3:return:model"""layer,out_channels=[],ch[-1]fori,(f,number,Module_name,args)inenumerate(yaml_cfg["backbone"]):"""f:上一层输出通道number:该模块有几层,就是该模块要重复几次Mdule_name:卷积层名字args:参数,包含输出通道数,k,s,p等"""#通过eval,将str类型转自己定义的BaseConvm=eval(Module_name)ifisinstance(Module_name,str)elseModule_nameforj,ainenumerate(args):#通过eval,将str转int,获得输出通道数args[j]=eval(a)ifisinstance(a,str)elsea#更新通道#args[0]是输出通道ifmin[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_inrange(number)])ifnumber>1elsem(*args)#更新通道列表,每次获取输出通道ch.append(out_channels)layer.append(model_)returnnn.Sequential(*layer)classModel(nn.Module):def__init__(self,cfg="./Model.yaml",ch=3,):super.__init__self.yaml=cfgimportyamlyaml_file=Path(cfg).namewithopen(yaml_file,errors="ignore")asf:self.yaml=yaml.safe_load(f)ch=self.yaml["ch"]=self.yaml["ch"]=3self.backbone=parse_model(deepcopy(self.yaml),ch=[ch])defforward(self,x):output=self.backbone(x)returnoutputif__name__=="__main__":cfg=path+"/Model.yaml"model=Modelmodel.evalprint(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)

关键词:

推荐内容

Copyright @  2015-2022 每日教育装备网版权所有  

备案号:浙ICP备2022016517号-15

  

联系邮箱:5 146 761 13 @qq.com