从零搭建Pytorch模型教程(二)搭建网络

本文涉及的产品
函数计算FC,每月15万CU 3个月
简介: 上一篇《从零搭建Pytorch模型教程(一)数据读取》中介绍了classdataset的几个要点,由哪些部分组成,每个部分需要完成哪些事情,如何进行数据增强,如何实现自己设计的数据增强。然后,介绍了分布式训练的数据加载方式,数据读取的整个流程,当面对超大数据集时,内存不足的改进思路。欢迎关注公众号CV技术指南,专注于计算机视觉的技术总结、最新技术跟踪、最新论文解读、各种技术教程、CV招聘信息发布等。关注公众号可邀请加入免费版的知识星球和技术交流群。

搭建CNN网络


首先来看一个CNN网络 (以YOLO_v1的一部分层为例)。


class Flatten(nn.Module):
   def __init__(self):
       super(Flatten,self).__init__()
   def forward(self,x):
       return x.view(x.size(0),-1)
class Yolo_v1(nn.Module):
   def __init__(self, num_class):
       super(Yolo_v1,self).__init__()
       C = num_class
       self.conv_layer1=nn.Sequential(
           nn.Conv2d(in_channels=3,out_channels=64,kernel_size=7,stride=1,padding=7//2),
           nn.BatchNorm2d(64),
           nn.LeakyReLU(0.1),
           nn.MaxPool2d(kernel_size=2,stride=2)
      )
       self.conv_layer2=nn.Sequential(
           nn.Conv2d(in_channels=64,out_channels=192,kernel_size=3,stride=1,padding=3//2),
           nn.BatchNorm2d(192),
           nn.LeakyReLU(0.1),
           nn.MaxPool2d(kernel_size=2,stride=2)
      )
       #为了简便,这里省去了很多层
       self.flatten = Flatten()
       self.conn_layer1 = nn.Sequential(
           nn.Linear(in_features=7*7*1024,out_features=4096),
           nn.Dropout(0.5),nn.LeakyReLU(0.1))
       self.conn_layer2 = nn.Sequential(nn.Linear(in_features=4096,out_features=7*7*(2*5 + C)))
self._initialize_weights()
   def forward(self,input):
       conv_layer1 = self.conv_layer1(input)
       conv_layer2 = self.conv_layer2(conv_layer1)
       flatten = self.flatten(conv_layer2)
       conn_layer1 = self.conn_layer1(flatten)
       output = self.conn_layer2(conn_layer1)
       return output
   def _initialize_weights(self):
       for m in self.modules():
           if isinstance(m, nn.Conv2d):
               n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels
               m.weight.data.normal_(0, math.sqrt(2. / n))
               if m.bias is not None:
                   m.bias.data.zero_()
           elif isinstance(m, nn.BatchNorm2d):
               m.weight.data.fill_(1)
               m.bias.data.zero_()
           elif isinstance(m, nn.Linear):
               m.weight.data.normal_(0, 0.01)
               m.bias.data.zero_()

搭建网络有几个要点:


  1. 自定义类要继承torch.nn.Module。有时候自己设计了一些模块,为了使用更方便,通常额外定义一个类,就像这里的Flatten,自定义的类也要继承torch.nn.Module。


  1. 完成init函数和forward函数。其中__init__函数完成网络的搭建,forward函数完成网络的前传路径


  1. 完成所有层的参数初始化,一般只有卷积层,归一化层,全连接层要初始化,池化层没有参数。

__init__函数


构建网络层有几种方式,一种是pytorch官方已经有了定义的网络,如resnet,vgg,Inception等。一种是自定义层,例如自己设计了一个新的模块。


首先是使用pytorch官方库已经支持的网络,这些网络放在了torchvision.models中,下面选择自己需要的一个。


以下只列举了2D 模型的一部分,还有视频类的3D 模型。


import torchvision.models as models
resnet18 = models.resnet18(pretrained = True)
alexnet = models.alexnet()
vgg16 = models.vgg16()
squeezenet = models.squeezenet1_0()
densenet = models.densenet161()
inception = models.inception_v3()
googlenet = models.googlenet()
shufflenet = models.shufflenet_v2_x1_0()
mobilenet_v2 = models.mobilenet_v2()
mobilenet_v3_large = models.mobilenet_v3_large()
mobilenet_v3_small = models.mobilenet_v3_small()
resnext50_32x4d = models.resnext50_32x4d()
wide_resnet50_2 = models.wide_resnet50_2()
mnasnet = models.mnasnet1_0()
efficientnet_b0 = models.efficientnet_b0()
efficientnet_b1 = models.efficientnet_b1()
efficientnet_b2 = models.efficientnet_b2()
regnet_y_400mf = models.regnet_y_400mf()
regnet_y_800mf = models.regnet_y_800mf()
vit_b_16 = models.vit_b_16()
vit_b_32 = models.vit_b_32()
vit_l_16 = models.vit_l_16()
vit_l_32 = models.vit_l_32()
convnext_tiny = models.convnext_tiny()
convnext_small = models.convnext_small()
convnext_base = models.convnext_base()
convnext_large = models.convnext_large()


若需要加载该网络在ImageNet上预训练的模型,则在括号内设置参数pretrained=True即可。但这种方式有个不好的问题在于这些预训练模型并不是在本地,因此每次运行都会从网上读取加载模型,非常浪费时间。因此,可以去它官网(https://pytorch.org/)上把那个模型下载到本地,通过下面指令完成加载。

resnet50.load_state_dict(torch.load('/path/to/resnet50.pth'))

另一种自定义层的,一般可以通过torch.nn.Sequential()来构建,在中间插入卷积层、归一化层、激活函数层、池化层即可。


例如下方这种是最常用的。

self.conv_layer1=nn.Sequential(
          nn.Conv2d(in_channels=3,out_channels=64,kernel_size=7,stride=1,padding=7//2),
          nn.BatchNorm2d(64),
          nn.LeakyReLU(0.1),
          nn.MaxPool2d(kernel_size=2,stride=2),
          nn.Conv2d(in_channels=3,out_channels=64,kernel_size=7,stride=1,padding=7//2),
          nn.BatchNorm2d(64),
          nn.LeakyReLU(0.1),
          nn.MaxPool2d(kernel_size=2,stride=2),
      )

当网络很深时,上面这种方式构建比较麻烦,例如resnet,总不可能就按找上面这种方式这么写50层。就把它们共同的部分给构建出来,然后通过传参来设置不同的层。

例如:


1.下面这里先构建一个基本的几层作为一个类,每一层的参数(不同输入输出通道数,卷积核大小,有无池化)都通过传参来设置。


class BasicBlock(nn.Module):
  expansion = 1
  def __init__(self, inplanes, planes, stride=1, downsample=None, groups=1,
                base_width=64, dilation=1, norm_layer=None):
      super(BasicBlock, self).__init__()
      self.conv1 = conv3x3(inplanes, planes, stride)
      self.bn1 = norm_layer(planes)
      self.relu = nn.ReLU(inplace=True)
      self.conv2 = conv3x3(planes, planes)
      self.bn2 = norm_layer(planes)
      self.downsample = downsample
      self.stride = stride
  def forward(self, x):
      identity = x
      out = self.conv1(x)
      out = self.bn1(out)
      out = self.relu(out)
      out = self.conv2(out)
      out = self.bn2(out)
      if self.downsample is not None:
          identity = self.downsample(x)
      out += identity
      out = self.relu(out)
      return out

2.下面是设置不同的层。注:上面和下面都不是一个完整的代码,只是用来说明这种很多层的构建方式。

layers = []
layers.append(block(self.inplanes, planes, stride, downsample, self.groups,
self.base_width, previous_dilation, norm_layer))
self.inplanes = planes * block.expansion
for _ in range(1, blocks):
    layers.append(block(self.inplanes, planes, groups=self.groups,
    base_width=self.base_width, dilation=self.dilation,
    norm_layer=norm_layer))
return nn.Sequential(*layers)

forward函数


这里就是网络的传播路径了,一般就是一路往下传就是。return的内容就是网络的输出。


def forward(self,x):
    x = self.conv_layer1(x)
    x = self.conv_layer2(x)
    x = sexlf.flatten(x)
    x = self.conn_layer1(x)
    output = self.conn_layer2(x)
return output

如果想将中间某几层的输出拿出来,做一下特征金字塔,可以像下面这么写。

def forward(self,x):
    conv_layer1 = self.conv_layer1(x)
    conv_layer2 = self.conv_layer2(conv_layer1)
    conv_layer3 = self.conv_layer2(conv_layer2)
    conv_layer4 = self.conv_layer2(conv_layer3)
    FP = self.YourModule(conv_layer1,conv_layer2,conv_layer3,conv_layer4)
    flatten = self.flatten(FN)
    conn_layer1 = self.conn_layer1(flatten)
    output = self.conn_layer2(conn_layer1)
    return output

像可视化特征图里,想要可视化某一层的特征图,就可以像下面这么写。

def forward(self,x):
    x = self.conv_layer1(x)
    feature = self.conv_layer2(x)
    x = sexlf.flatten(feature)
    x = self.conn_layer1(x)
    output = self.conn_layer2(x)
    return feature,output


初始化网络


初始化网络是要放在init函数里完成,分为两类,一类是随机初始化,一类是加载预训练模型。


随机初始化


关于随机初始化,目前主要有多种方式:Normal Initialization, Uniform Initialization,Xavier Initialization,He Initialization (也称 kaiming Initialization),LeCun Initialization。


关于这些初始化方法,可以看这篇文章《神经网络的初始化方法总结 | 又名“如何选择合适的初始化方法”》。我们一般使用Kaiming Initialization。

下面是一种方式,直接按自定义的方式初始化。

def _initialize_weights(self):
    for m in self.modules():
    if isinstance(m, nn.Conv2d):
        n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels
        m.weight.data.normal_(0, math.sqrt(2. / n))
    if m.bias is not None:
        m.bias.data.zero_()
    elif isinstance(m, nn.BatchNorm2d):
        m.weight.data.fill_(1)
        m.bias.data.zero_()
    elif isinstance(m, nn.Linear):
        m.weight.data.normal_(0, 0.01)
        m.bias.data.zero_()

也可以选择pytorch实现了的初始化。


for m in self.modules():
    if isinstance(m, nn.Conv2d):
        nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
    elif isinstance(m, (nn.BatchNorm2d, nn.GroupNorm)):
        nn.init.constant_(m.weight, 1)
        nn.init.constant_(m.bias, 0)

还可以像下面这么写:


from torch.nn import init
def weights_init_kaiming(m):
  classname = m.__class__.__name__
  # print(classname)
  if classname.find('Conv') != -1:
      init.kaiming_normal_(m.weight.data, a=0, mode='fan_in') # For old pytorch, you may use kaiming_normal.
  elif classname.find('Linear') != -1:
      init.kaiming_normal_(m.weight.data, a=0, mode='fan_out')
      init.constant_(m.bias.data, 0.0)
  elif classname.find('BatchNorm2d') != -1:
      init.normal_(m.weight.data, 1.0, 0.02)
      init.constant_(m.bias.data, 0.0)
def weights_init_classifier(m):
  classname = m.__class__.__name__
  if classname.find('Linear') != -1:
      init.normal_(m.weight.data, std=0.001)
      init.constant_(m.bias.data, 0.0)
self.conv_layer1=nn.Sequential(
          nn.Conv2d(in_channels=3,out_channels=64,kernel_size=7,stride=1,padding=7//2),
          nn.BatchNorm2d(64),
          nn.LeakyReLU(0.1),
          nn.MaxPool2d(kernel_size=2,stride=2)
      )
self.conv_layer1.apply(weights_init_kaiming)

反正随便选择一种就好。


加载预训练模型初始化


加载预训练模型一般是在train文件里写,但有些网络由于是使用现成的backbone网络,例如使用了resnet50,然后后面加了自定义的模块,所以它想要resnet50预训练模型初始化backbone,而其它层做随机初始化,那加载预训练模型就是在网络定义中做的。因此,既然这里提到了初始化,就干脆写在这里。

最简单的就是直接整个模型都加载。

resnet50.load_state_dict(torch.load('/path/to/resnet50.pth'))

但也有一些情况下,我只想加载其中一部分层的参数。剩下一部分由于已经改变参数了,无法加载预训练模型,所以要选择上面的随机初始化。


这里有必要来说明网络的每一层是如何表示的。下面以一个例子来说明。


class Flatten(nn.Module):
  def __init__(self):
      super(Flatten,self).__init__()
  def forward(self,x):
      return x.view(x.size(0),-1)
class YourNet(nn.Module):
  def __init__(self,stride=2, pool='avg'):
      super(YourNet, self).__init__()
      self.resnet50 = models.resnet50(pretrained=False)
      self.model.load_state_dict(torch.load('/path/to/resnet50.pth'))
      self.flatten = Flatten()
      self.conn_layer1 = nn.Sequential(
          nn.Linear(in_features=7 * 7 * 1024, out_features=4096),
          nn.Dropout(0.5),
          nn.LeakyReLU(0.1)
      )
      self.conn_layer2 = nn.Sequential(nn.Linear(in_features=4096, out_features=7 * 7 * (2 * 5 + 20)))
  def forward(self,x):
      #这里省略
if __name__ == "__main__":
  model = YourNet()
  for name, value in model.named_parameters():
      print(name)

这里简单定义了一个网络。在最后面有这两行:

for name, value in model.named_parameters():
    print(name)

这两行的输出就是打印网络层的名字,实际上加载预训练模型时,也是按照这个名字来加载的。下面是一部分输出。


resnet50.conv1.weight
resnet50.bn1.weight
resnet50.bn1.bias
resnet50.layer1.0.conv1.weight
resnet50.layer1.0.bn1.weight
resnet50.layer1.0.bn1.bias
resnet50.layer1.0.conv2.weight
resnet50.layer1.0.bn2.weight
resnet50.layer1.0.bn2.bias
resnet50.layer1.0.conv3.weight
resnet50.layer1.0.bn3.weight
resnet50.layer1.0.bn3.bias
resnet50.layer1.0.downsample.0.weight
resnet50.layer1.0.downsample.1.weight
resnet50.layer1.0.downsample.1.bias
...
...
resnet50.layer4.2.bn3.weight
resnet50.layer4.2.bn3.bias
resnet50.fc.weight
resnet50.fc.bias
conn_layer1.0.weight
conn_layer1.0.bias
conn_layer2.0.weight
conn_layer2.0.bias

在预训练模型中就是这样,key即为网络层的名字,value即为它们对应的参数。因此,加载预训练模型可以按照下面这种方式加载。


pretrained_dict = torch.load('/path/to/resnet50.pth')
pretrained_dict.pop('fc.weight')
pretrained_dict.pop('fc.bias')
#自己的模型参数变量
model_dict = model.state_dict()
#去除一些不需要的参数
pretrained_dict = {k: v for k, v in pretrained_dict.items() if k in model_dict}
#参数更新
model_dict.update(pretrained_dict)
# 加载我们真正需要的state_dict
model.load_state_dict(model_dict)

自己定义的一些层是不会出现在pretrained_dict中,因此会将其剔除,从而只加载了pretrained_dict中有的层。

 

本文介绍了如何搭建神经网络,构建网络的几种方式,前向传播的过程,几种初始化方式,如何加载预训练模型的指定层等内容。


下一篇我们将介绍如何写train函数,以及包括设置优化方式,设置学习率,不同层设置不同学习率,解析参数等。


欢迎关注公众号CV技术指南,专注于计算机视觉的技术总结、最新技术跟踪、最新论文解读、各种技术教程、CV招聘信息发布等。关注公众号可邀请加入免费版的知识星球和技术交流群。

相关实践学习
【文生图】一键部署Stable Diffusion基于函数计算
本实验教你如何在函数计算FC上从零开始部署Stable Diffusion来进行AI绘画创作,开启AIGC盲盒。函数计算提供一定的免费额度供用户使用。本实验答疑钉钉群:29290019867
建立 Serverless 思维
本课程包括: Serverless 应用引擎的概念, 为开发者带来的实际价值, 以及让您了解常见的 Serverless 架构模式
相关文章
|
2月前
|
存储 物联网 PyTorch
基于PyTorch的大语言模型微调指南:Torchtune完整教程与代码示例
**Torchtune**是由PyTorch团队开发的一个专门用于LLM微调的库。它旨在简化LLM的微调流程,提供了一系列高级API和预置的最佳实践
183 59
基于PyTorch的大语言模型微调指南:Torchtune完整教程与代码示例
|
29天前
|
机器学习/深度学习 人工智能 PyTorch
Transformer模型变长序列优化:解析PyTorch上的FlashAttention2与xFormers
本文探讨了Transformer模型中变长输入序列的优化策略,旨在解决深度学习中常见的计算效率问题。文章首先介绍了批处理变长输入的技术挑战,特别是填充方法导致的资源浪费。随后,提出了多种优化技术,包括动态填充、PyTorch NestedTensors、FlashAttention2和XFormers的memory_efficient_attention。这些技术通过减少冗余计算、优化内存管理和改进计算模式,显著提升了模型的性能。实验结果显示,使用FlashAttention2和无填充策略的组合可以将步骤时间减少至323毫秒,相比未优化版本提升了约2.5倍。
43 3
Transformer模型变长序列优化:解析PyTorch上的FlashAttention2与xFormers
|
14天前
|
机器学习/深度学习 算法 PyTorch
基于Pytorch Gemotric在昇腾上实现GraphSage图神经网络
本文详细介绍了如何在昇腾平台上使用PyTorch实现GraphSage算法,在CiteSeer数据集上进行图神经网络的分类训练。内容涵盖GraphSage的创新点、算法原理、网络架构及实战代码分析,通过采样和聚合方法高效处理大规模图数据。实验结果显示,模型在CiteSeer数据集上的分类准确率达到66.5%。
|
9天前
|
网络协议 安全 网络安全
探索网络模型与协议:从OSI到HTTPs的原理解析
OSI七层网络模型和TCP/IP四层模型是理解和设计计算机网络的框架。OSI模型包括物理层、数据链路层、网络层、传输层、会话层、表示层和应用层,而TCP/IP模型则简化为链路层、网络层、传输层和 HTTPS协议基于HTTP并通过TLS/SSL加密数据,确保安全传输。其连接过程涉及TCP三次握手、SSL证书验证、对称密钥交换等步骤,以保障通信的安全性和完整性。数字信封技术使用非对称加密和数字证书确保数据的机密性和身份认证。 浏览器通过Https访问网站的过程包括输入网址、DNS解析、建立TCP连接、发送HTTPS请求、接收响应、验证证书和解析网页内容等步骤,确保用户与服务器之间的安全通信。
53 1
|
14天前
|
监控 安全 BI
什么是零信任模型?如何实施以保证网络安全?
随着数字化转型,网络边界不断变化,组织需采用新的安全方法。零信任基于“永不信任,永远验证”原则,强调无论内外部,任何用户、设备或网络都不可信任。该模型包括微分段、多因素身份验证、单点登录、最小特权原则、持续监控和审核用户活动、监控设备等核心准则,以实现强大的网络安全态势。
|
2月前
|
存储 数据可视化 API
重磅干货,免费三方网络验证[用户系统+CDK]全套API接口分享教程。
本套网络验证系统提供全面的API接口,支持用户注册、登录、数据查询与修改、留言板管理等功能,适用于不想自建用户系统的APP开发者。系统还包含CDK管理功能,如生成、使用、查询和删除CDK等。支持高自定义性,包括20个自定义字段,满足不同需求。详细接口参数及示例请参考官方文档。
|
2月前
|
并行计算 监控 搜索推荐
使用 PyTorch-BigGraph 构建和部署大规模图嵌入的完整教程
当处理大规模图数据时,复杂性难以避免。PyTorch-BigGraph (PBG) 是一款专为此设计的工具,能够高效处理数十亿节点和边的图数据。PBG通过多GPU或节点无缝扩展,利用高效的分区技术,生成准确的嵌入表示,适用于社交网络、推荐系统和知识图谱等领域。本文详细介绍PBG的设置、训练和优化方法,涵盖环境配置、数据准备、模型训练、性能优化和实际应用案例,帮助读者高效处理大规模图数据。
56 5
|
2月前
|
存储 网络协议 安全
30 道初级网络工程师面试题,涵盖 OSI 模型、TCP/IP 协议栈、IP 地址、子网掩码、VLAN、STP、DHCP、DNS、防火墙、NAT、VPN 等基础知识和技术,帮助小白们充分准备面试,顺利踏入职场
本文精选了 30 道初级网络工程师面试题,涵盖 OSI 模型、TCP/IP 协议栈、IP 地址、子网掩码、VLAN、STP、DHCP、DNS、防火墙、NAT、VPN 等基础知识和技术,帮助小白们充分准备面试,顺利踏入职场。
97 2
|
2月前
|
运维 网络协议 算法
7 层 OSI 参考模型:详解网络通信的层次结构
7 层 OSI 参考模型:详解网络通信的层次结构
258 1
|
2月前
|
网络协议 算法 网络性能优化
计算机网络常见面试题(一):TCP/IP五层模型、TCP三次握手、四次挥手,TCP传输可靠性保障、ARQ协议
计算机网络常见面试题(一):TCP/IP五层模型、应用层常见的协议、TCP与UDP的区别,TCP三次握手、四次挥手,TCP传输可靠性保障、ARQ协议、ARP协议