简介

在前一篇文章提到了关于数据的一系列操作,数据读入之后就是将数据“喂”给深度模型,所以构建一个深度学习模型是很重要的,本文主要讲解PyTorch中模型的相关操作,包括模型的定义、模型参数的初始化、模型的保存及加载。

模型构建

在PyTorch中,想要让PyTorch的后续接口认为这是一个模型的关键需要满足下面三个条件。

  • 自定义的模型以类的形式存在,且该类必须继承自torch.nn.Module,该继承操作可以让PyTorch识别该类为一个模型。
  • 在自定义的类中的__init__方法中声明模型使用到的组件,一般是封装好的张量操作,如卷积。池化、批量标准化、全连接等。
  • forward方法中使用__init__方法中的组件进行组装,构成网络的前向传播逻辑。

该模型的前向运算通过调用模型的call方法即可,通过实例化的对象加括号即可默认调用该方法,如net(input_tensor)。包括很多nn中定义的张量运算也可以通过该方法进行运算。

下面的代码演示了一个比较简单的卷积神经网络分类器,其中涉及到的张量操作如卷积等均为计算机视觉中的基本知识,参数也比较好理解,不多赘述,可以查看nn模块的文档。**这里需要注意,PyTorch不同于TensorFlow,PyTorch期待图片的张量格式输入是(batch, c, h, w ),这点需要注意,当然,如果按照上篇文章中PyTorch的数据接口读入数据,那不需要自行控制。**下面的代码输出如期待的是[batch, 101]的维度,通过softmax激活后就是常见的分类器输出。

import torch
import torch.nn as nn
import torch.nn.functional as Fclass Net(nn.Module):def __init__(self):super(Net, self).__init__()self.conv1 = nn.Conv2d(in_channels=3, out_channels=32, kernel_size=(3, 3))self.pool1 = nn.MaxPool2d(kernel_size=2, stride=2)self.conv2 = nn.Conv2d(in_channels=32, out_channels=64, kernel_size=3)self.pool2 = nn.MaxPool2d(2, 2)self.fc1 = nn.Linear(64*54*54, 256)self.fc2 = nn.Linear(256, 128)self.fc3 = nn.Linear(128, 101)def forward(self, x):x = self.pool1(F.relu(self.conv1(x)))x = self.pool2(F.relu(self.conv2(x)))x = x.view(-1, 64*54*54)x = F.relu(self.fc1(x))x = F.relu(self.fc2(x))x = self.fc3(x)return xif __name__ == '__main__':net = Net()data = torch.randn((32, 3, 224, 224))label = torch.zeros((32, ))pred = net(data)print(pred.shape)

实际上,真正设计模型的时候,模型都是比较复杂的,远不是一个模块就能完成的,这时候就需要组合多个模型,也需要组合多个张量运算,将多个张量运算堆叠或者多个模型堆叠需要使用一个容器—torch.nn.Sequential,该容器将一系列操作按照前后顺序堆叠起来,如下面的例子,该容器接受一系列的张量操作或者模型操作为参数。

nn.Sequential(nn.Conv2d(in_channels, out_channels, 3, stride, 1, bias=False),nn.BatchNorm2d(out_channels),nn.ReLU(inplace=True),nn.Conv2d(out_channels, out_channels, 3, 1, 1, bias=False),nn.BatchNorm2d(out_channels))

下面的例子就演示了构建较为复杂的深度模型的方法,该模型为ResNet34,参考了这个思路。

import torch
from torch import nn
from torch.nn import functional as Fclass ResidualBlock(nn.Module):"""残差模块"""def __init__(self, in_channels, out_channels, stride=1, shortcut=None):super(ResidualBlock, self).__init__()self.left = nn.Sequential(nn.Conv2d(in_channels, out_channels, 3, stride, 1, bias=False),nn.BatchNorm2d(out_channels),nn.ReLU(inplace=True),nn.Conv2d(out_channels, out_channels, 3, 1, 1, bias=False),nn.BatchNorm2d(out_channels))self.right = shortcutdef forward(self, x):out = self.left(x)residual = x if self.right is None else self.right(x)out += residualreturn F.relu(out)class ResNet34(nn.Module):def __init__(self, num_classes=101):super(ResNet34, self).__init__()self.model_name = 'ResNet34'self.pre = nn.Sequential(nn.Conv2d(3, 64, 7, 2, 3, bias=False),nn.BatchNorm2d(64),nn.ReLU(inplace=True),nn.MaxPool2d(3, 2, 1))self.layer1 = self.make_layer(64, 128, 3)self.layer2 = self.make_layer(128, 256, 4, stride=2)self.layer3 = self.make_layer(256, 512, 6, stride=2)self.layer4 = self.make_layer(512, 512, 3, stride=2)self.fc = nn.Linear(512, num_classes)def make_layer(self, in_channels, out_channels, block_num, stride=1):shortcut = nn.Sequential(nn.Conv2d(in_channels, out_channels, 1, stride, bias=False),nn.BatchNorm2d(out_channels))layers = list()layers.append(ResidualBlock(in_channels, out_channels, stride, shortcut))for i in range(1, block_num):layers.append(ResidualBlock(out_channels, out_channels))return nn.Sequential(*layers)def forward(self, x):x = self.pre(x)x = self.layer1(x)x = self.layer2(x)x = self.layer3(x)x = self.layer4(x)x = F.avg_pool2d(x, 7)x = x.view(x.size(0), -1)return self.fc(x)if __name__ == '__main__':resnet = ResNet34(num_classes=101)data = torch.randn((32, 3, 224, 224))label = torch.zeros((32,))pred = resnet(data)print(pred.shape)

权重初始化

在上一节的介绍中,详细叙述了模型的构建细节,但是有一个很重要的点没有提及,就是权重初始化,如上面案例中的conv2d和Linear,这些运算都有可训练的参数需要初始化(当然,不设定也有默认初始化方法),这些参数权重也是模型训练的目的,参数初始化的效果在深度学习中尤为重要,它会直接影响模型的收敛效果。

当然,一般需要对不同的运算层采用不同的初始化方法,在PyTorch中初始化方法均在torch.nn.init中封装。在上一节自定义网络的类中,添加一个权重初始化方法如下(针对不同的结构采用不同的初始化方法,如Xavier、Kaiming正太分布等),修改后的代码如下。

import torch
import torch.nn as nn
import torch.nn.functional as Fclass Net(nn.Module):def __init__(self):super(Net, self).__init__()self.conv1 = nn.Conv2d(in_channels=3, out_channels=32, kernel_size=(3, 3))self.pool1 = nn.MaxPool2d(kernel_size=2, stride=2)self.conv2 = nn.Conv2d(in_channels=32, out_channels=64, kernel_size=3)self.pool2 = nn.MaxPool2d(2, 2)self.fc1 = nn.Linear(64*54*54, 256)self.fc2 = nn.Linear(256, 128)self.fc3 = nn.Linear(128, 101)def forward(self, x):x = self.pool1(F.relu(self.conv1(x)))x = self.pool2(F.relu(self.conv2(x)))x = x.view(-1, 64*54*54)x = F.relu(self.fc1(x))x = F.relu(self.fc2(x))x = self.fc3(x)return xdef init_weights(self):for m in self.modules():if isinstance(m, nn.Conv2d):nn.init.xavier_normal_(m.weight.data)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):nn.init.normal_(m.weight.data, 0, 0.01)m.bias.data.zero_()if __name__ == '__main__':net = Net()net.init_weights()data = torch.randn((32, 3, 224, 224))label = torch.zeros((32,))pred = net(data)print(pred.shape)

初始化方法

在上一节,主要讲述如何对模型各层进行初始化,事实上常用的初始化方法PyTorch都进行了封装于torch.nn.init模块下,有Xavier初始化和Kaiming初始化这两种效果比较显著的初始化方法,也有一些比较传统的方法。

Xavier初始化

这是依据“方差一致性”推导得到的初始化方法,有均匀分布和正态分布两种。

  • Xavier均匀分布

    • nn.init.xavier_uniform_(tensor, gain=1)
    • Xavier初始化方法服从均匀分布(-a, a),其中a=gain*sqrt(6/fan_in+fan_out),其中gain是依据激活函数类型设定的。
  • Xavier正态分布
    • nn.init.xavier_normal_(tensor, gain=1)
    • Xavier初始化方法服从正太分布,mean=0, std=gain*sqrt(2/fan_in+fan_out)

Kaiming初始化

同样根据“方差一致性”推导得到,针对xavier初始化方法在relu这一类激活函数上效果不佳而提出的。

  • Kaiming均匀分布

    • nn.init.kaiming_uniform_(tensor, a=0, mode='fan_in', nonlinearity='leaky_relu')
    • 服从(-b, b)的均匀分布,其中b=sqrt(6/(1+a^2)*fan_in),其中a为激活函数负半轴的斜率,relu对应的a为0。
    • mode参数为fan_in或者fan_out,前者使正向传播时方差一致,后者使反向传播时方差一致。
    • nonlinearity参数表示是否非线性,relu表示线性,leaky_relu表示非线性。
  • Kaiming正态分布
    • nn.init.kaiming_normal_(tensor, a=0, mode='fan_in', nonlinearity='leaky_relu')
    • 服从(0, std)的正态分布,其中std=sqrt(2/(1+a^2)*fan_in)
    • 参数同上。

其他初始化

  • 均匀分布初始化

    • nn.init.uniform_(tensor, a=0, b=1)
    • 服从U(a,b)的均匀分布。
  • 正态分布初始化
    • nn.init.normal_(tensor, mean=0, std=1)
    • 服从N(mean,std)的正态分布。
  • 常数初始化
    • nn.init.constant_(tensor, val)
    • 使值为常数val的初始化方法。
  • 单位矩阵初始化
    • nn.init.eye_(tensor)
    • 将二维Tensor初始化为单位矩阵。
  • 正交初始化
    • nn.init.orthogonal_(tensor, gain=1)
    • 使得tensor是正交的。
  • 稀疏初始化
    • nn.init.sparse_(tensor, sparsity, std=0.01)
    • 从正态分布N(0,std)中进行稀疏化,使得每一列有一部分为0,其中sparsity控制列中为0的比例。
  • 增益计算
    • nn.init.calculate_gain(nonlinearity, param=None)
    • 用于计算不同激活函数的gain值。

迁移学习

上一节,介绍了很多权重初始化的方法,可以知道良好的初始化可以加速模型收敛、获得更好的精度,实际使用中,通常采用一个预训练的模型的权重作为模型的初始化参数,这个方法称为Finetune,更广泛的就是指迁移学习,Finetune技术本质上就是使得模型具有更好的权重初始化。

一般,需要如下三个步骤。

  1. 训练模型,保存模型参数。
  2. 加载预训练的模型参数。
  3. 构建新模型,将获得的预训练模型参数放到新模型中。

上述过程有两种模型的保存方法,一种是保存整个模型,另一种是保存模型的参数,推荐后者。

下面的代码演示如何进行Finetune的初始化,当然,实际进行迁移学习时对于不同的层需要采用不同的学习速率,一般希望前面的层学习率低,后层(如全连接层)学习率高一些,需要对不同的层设置不同的学习率,这里不多提及了。

net = Net()
# 假设训练完成了
# 保存模型参数
torch.save(net.state_dict(), 'net_weights.pkl')
# 加载模型参数
pretrained_params = torch.load('net_weights.pkl')
# 构建模型,预训练参数初始化
net = Net()
net_state_dict = net.state_dict()
pre_dict = {k: v for k, v in pretrained_params.items() if k in net_state_dict}
net_state_dict.update(pre_dict)

补充说明

本文介绍了PyTorch中模型构建的方法以及权重初始化技巧,进一步提到了迁移学习,这包括模型的保存与加载、使用预训练模型的参数作为网络的初始化参数(finetune技巧)。本文的所有代码均开源于我的Github,欢迎star或者fork。

PyTorch-模型相关推荐

  1. TensorFlow与PyTorch模型部署性能比较

    TensorFlow与PyTorch模型部署性能比较 前言 2022了,选 PyTorch 还是 TensorFlow?之前有一种说法:TensorFlow 适合业界,PyTorch 适合学界.这种说 ...

  2. PyTorch 深度剖析:如何保存和加载PyTorch模型?

    点击上方"视学算法",选择加"星标"或"置顶" 重磅干货,第一时间送达 作者丨科技猛兽 编辑丨极市平台 导读 本文详解了PyTorch 模型 ...

  3. TensorRT和PyTorch模型的故事

    点击上方"视学算法",选择加"星标"或"置顶" 重磅干货,第一时间送达 作者丨伯恩legacy 来源丨https://zhuanlan.zh ...

  4. 手把手教你洞悉 PyTorch 模型训练过程,彻底掌握 PyTorch 项目实战!(文末重金招聘导师)...

    (文末重金招募导师) 在CVPR 2020会议接收中,PyTorch 使用了405次,TensorFlow 使用了102次,PyTorch使用数是TensorFlow的近4倍. 自2019年开始,越来 ...

  5. 基于C++的PyTorch模型部署

    点击上方"小白学视觉",选择加"星标"或"置顶" 重磅干货,第一时间送达 引言 PyTorch作为一款端到端的深度学习框架,在1.0版本之后 ...

  6. 在C++平台上部署PyTorch模型流程+踩坑实录

    点击上方"小白学视觉",选择加"星标"或"置顶" 重磅干货,第一时间送达 导读 本文主要讲解如何将pytorch的模型部署到c++平台上的模 ...

  7. 如何使用TensorRT对训练好的PyTorch模型进行加速?

    点击上方"3D视觉工坊",选择"星标" 干货第一时间送达 作者丨伯恩legacy@知乎 来源丨https://zhuanlan.zhihu.com/p/8831 ...

  8. pytorch模型转onnx-量化rknn(bisenet)

    1.pytorch模型转化onnx 先把pytorch的.pth模型转成onnx,例如我这个是用Bisenet转的,执行export_onnx.py import argparse import os ...

  9. sklearn与pytorch模型的保存与读取

    当我们花了很长时间训练了一个模型,需要用该模型做其他事情(比如迁移学习),或者我们想把自己的机器学习模型分享出去的时候,我们这时候需要将我们的ML模型持久化到硬盘中去. 1.sklearn中模型的保存 ...

  10. 浅谈pytorch 模型 .pt, .pth, .pkl的区别及模型保存方式 pth中的路径加载使用

    首先xxx.pth文件里面会书写一些路径,一行一个. 将xxx.pth文件放在特定位置,则可以让python在加载模块时,读取xxx.pth中指定的路径. Python客栈送红包.纸质书 有时,在用i ...

最新文章

  1. iOS App 崩溃报告符号化
  2. sketchup 图片转模型_你应该知道的那些 Sketchup 实用快捷键和使用技巧!
  3. Vector源码分析
  4. java collection api_Java Stream和Collection比较:何时以及如何从Java API返回?
  5. 这样写代码,真是帅到没有朋友
  6. Pandas-Series知识点总结
  7. 爱情七十一课,低调恋爱
  8. centos7进入单用户模式
  9. 惠普谢少毅:网络攻击威胁在线交易
  10. 腾讯云直播开发日记 (二)附近直播-直播礼物-直播回放
  11. 儿童python编程书籍推荐_推荐给家长们的《趣学Python——教孩子学编程》书
  12. QTreeView设置branch图标大小
  13. 跟着“不睡觉的怪叔叔”一起学习openlayers入门
  14. xyplorer设置备忘
  15. CentOS + Mongodb 搭建NodeBB [转载翻译]
  16. 如果有10个词,我想从中取3个词,然后把所有的10选3的可能统计记录下来,该怎么做?...
  17. python 公司名称 相似度分析_Python文本相似度分析
  18. 苹果保修期查询_查询iPhone的保修日期和激活日期
  19. HTML(表结构,关联三个小练习)
  20. 【windows10】使用pytorch版本deeplabv3+训练自己数据集

热门文章

  1. 用户认证-什么是认证
  2. 文件上传功能-本地存储、阿里OSS、七牛云
  3. 常用的函数式接口_Supplier接口练习_求数组元素最大值
  4. spring事务管理-xml配置aop事务(重点)
  5. 泛微文档存放在服务器哪个地址,泛微OA根据文档的docid查询文档附件存放的路径...
  6. java 阻塞 wait_java交替打印奇偶数问题,会出现2个线程都wait阻塞了
  7. pythonfor循环range_python之for循环与range()函数
  8. 学习oop知识之OOP的封装
  9. Windows环境下使用 Caffe在ImageNet上训练网络
  10. 双系统Ubuntu无法访问windows磁盘分区解决方法