文章目录

  • 在pytorch中创建operation
    • 根据步骤定义了自己的LinearFunction
    • 直接使用LinearFunction
    • 扩展LinearFunction到module
  • 让torch.onnx能够识别自定义op
    • 自定义的op在转onnx的时候报错
      • 被op运算符已经在ONNX标准化
      • op运算符没有被标准化
    • 解决自定义的LinearFunction操作
    • pytorch转onnx代码实现
    • 对应的输出的onnx结构的部分也就是如下的

在pytorch中创建operation

检查pytorch是否包含operation。pytorch可以实现自定义层,可以拓展一些特殊的算子,同时提供了不可导operation的backward写法。例如,虽然pytorch可以自动求导,但是有时候一些操作是不可导的,这时候你需要自定义求导方式。也就是所谓的 “Extending torch.autograd”.

  • 自定义一个pytorch的op,即对pytorch进行扩展。

  • 扩展方法:通过继承 autograd.Function

  • 继承 autograd.Function 的 子类 只需要 实现两个 静态方法:

    • forward : 计算 op 的前向过程.

      • 在执行 forward 之前,Variable 参数已经被转换成了 Tensor
      • forward 的形参可以有默认参数,默认参数可以是任意 python 对象。
      • 可以返回任意多个 Tensor
      • 里面可以使用任何 python 操作,但是 return 的值必须是 Tensor
    • backward : 计算 梯度,
      • forward 返回几个值, 这里就需要几个形参,还得外加一个 ctx。
      • forward 有几个 形参(不包含 ctx) ,backward 就得返回几个值。
      • backward 实参也是 Variable 。
      • backward 返回的得是 Variable。

根据步骤定义了自己的LinearFunction

import torch
from torch.autograd import gradcheck
from torch.autograd import Variable
from torch.autograd import Function'''symbolic可以认为规定了,pytorch->onnx这个过程中的输出规范。简单的来说我们就是在自己创造,onnx非标准化的非ATen操作符(op),我的代码中对应的symbolic是这样的
'''
class LinearFunction(Function):# 这里的beta和alpha没有实际用处,只是证明使用自定义的op,在torch->onnx过程中,是可以传递网络参数的。@staticmethoddef symbolic(g, self, mat1, mat2, beta, alpha):#return g.op("nonentity", mat1, mat2, self, beta_f=beta, alpha_f=alpha)return g.op("nonentity", self,mat1, mat2,  beta_f=beta, alpha_f=alpha)# forward 和 backward 都得是 静态方法!!!!!@staticmethod# bias 是个可选参数,有个 默认值 Nonedef forward(ctx, input, weight, bias=None):# input,weight 都已经变成了 Tensor# 用 ctx 把该存的存起来,留着 backward 的时候用# ctx.save_for_backward 只能存 tensor, None, 其余都不能存。# ctx.save_for_backward 只保存 forward 的实参,或者 forward 的返回值。ctx.save_for_backward(input, weight, bias)output = input.mm(weight.t())if bias is not None:output += bias.unsqueeze(0).expand_as(output)return output# 由于 forward 只有一个 返回值,所以 backward 只需要一个参数 接收 梯度。@staticmethoddef backward(ctx, grad_output):# 此方法猜测是 torch.no_grad() 上下文中运行的. #grad_output 是 Variable 类型。# 在开头的地方将保存的 tensor 给 unpack 了# 然后 给 所有应该返回的 梯度 以 None 初始化。input, weight, bias = ctx.saved_tensorsgrad_input = grad_weight = grad_bias = None# needs_input_grad 检查是可选的。如果想使得 代码更简单的话,可以忽略。# 给不需要梯度的 参数返回梯度 不是一个错误。# 返回值 的个数 需要和 forward 形参的个数(不包含 ctx)一致if ctx.needs_input_grad[0]:grad_input = grad_output.mm(weight)if ctx.needs_input_grad[1]:grad_weight = grad_output.t().mm(input)if bias is not None and ctx.needs_input_grad[2]:grad_bias = grad_output.sum(0).squeeze(0)# 梯度的顺序和 forward 形参的顺序要对应。return grad_input, grad_weight, grad_bias

上面就是继承 Function 的全过程,pytorch封装有 Function 和 Module, linear 可以当成函数直接调用,像 F.conv2d 一样, 也可以封装进 Module 像 nn.Conv2d 那样使用.

直接使用LinearFunction

# input, weight, 是 Variable
def linear(input, weight, bias=None):# 一定是要 通过调用 apply 来用的。 Function.apply 中估计做了不少事情。return LinearFunction.apply(input, weight, bias)if __name__ == '__main__':in_ = torch.randn((20, 20), requires_grad=True, dtype=torch.double)weight_ = torch.randn((20, 20), requires_grad=True, dtype=torch.double)res= linear(in_, weight_)loss = res.sum()loss.backward()                      # 转成标量# 反向传播:因为 loss = sum(y),故grad_outputs = dloss/dy = 1,可以省略不写# print(in_.grad)# print(weight_.grad)input = (torch.randn((20, 20), requires_grad=True, dtype=torch.double) ,torch.randn((30, 20), requires_grad=True, dtype=torch.double))test = gradcheck(LinearFunction.apply, input, eps=1e-6, atol=1e-4)# 如果通过,最后会打印一个 Trueprint(test)

扩展LinearFunction到module

扩展module就很简单,需要重载 nn.Module中的__init__和__forward__

class Linear(nn.Module):def __init__(self, input_features, output_features, bias=True):super(Linear, self).__init__()self.input_features = input_featuresself.output_features = output_features# nn.Parameter is a special kind of Variable, that will get# automatically registered as Module's parameter once it's assigned# 这个很重要! Parameters是默认需要梯度的!# as an attribute. Parameters and buffers need to be registered, or# they won't appear in .parameters() (doesn't apply to buffers), and# won't be converted when e.g. .cuda() is called. You can use# .register_buffer() to register buffers.# nn.Parameters can never be volatile and, different than Variables,# they require gradients by default.self.weight = nn.Parameter(torch.Tensor(output_features, input_features))if bias:self.bias = nn.Parameter(torch.Tensor(output_features))else:# You should always register all possible parameters, but the# optional ones can be None if you want.self.register_parameter('bias', None)# Not a very smart way to initialize weightsself.weight.data.uniform_(-0.1, 0.1)if bias is not None:self.bias.data.uniform_(-0.1, 0.1)def forward(self, input):# See the autograd section for explanation of what happens here.return LinearFunction.apply(input, self.weight, self.bias)

让torch.onnx能够识别自定义op

自定义的op在转onnx的时候报错

在尝试利用自定义的op执行torch.nn.export想要输出protobuf二值文件的时候,读到自定义op,会报错:

...%19 : Float(64, 64, 3, 3) = onnx::MaxPool[kernel_shape=[2, 2], pads=[0, 0, 0, 0], strides=[2, 2]](%18), scope: Net/Sequential[conv3]/MaxPool2d[2]%20 : Float(64, 576) = onnx::Flatten[axis=1](%19), scope: Net%input.5 : Float(64, 128) = ^LinearFunction()(%20, %dense.0.weight, %dense.0.bias), scope: Net/Sequential[dense]/Linear[0]%22 : Float(64, 128) = onnx::Relu(%input.5), scope: Net/Sequential[dense]/ReLU[1]%23 : Float(64, 10) = ^LinearFunction()(%22, %dense.2.weight, %dense.2.bias), scope: Net/Sequential[dense]/Linear[2]

显示未定义的操作operator LinearFunction
解决办法就是想办法让torch.onnx能读懂我自定义的op:LinearFunction。

被op运算符已经在ONNX标准化

现今onnx支持的运算符,一般最新版本的支持的运算符信息会在github的onnx源码工程中的Operators.md中写出Operators.md.
如果,运算符已经被标准化,即在上边的列表中能找到,且在该版本的torch中,这个操作是一个ATen操作符,即在 torch/csrc/autograd/generated/VariableType.h能找到它的定义。
那就在torch/onnx/symbolic.py里面加上符号并且遵循下面的指令:

  1. 在 torch/onnx/symbolic.py里面定义符号。确保该功能与在ATen操作符在VariableType.h的功能相同。
  2. 第一个参数总是ONNX图形参数,参数的名字必须与 VariableType.h里的匹配,因为调度是依赖于关键字参数完成的。
  3. 参数排序不需要严格与VariableType.h匹配,首先的张量一定是输入的张量,然后是非张量参数。
  4. 在符号功能里,如果操作符已经在ONNX标准化了,我们只需要创建一个代码去表示在图形里面的ONNX操作符。
  5. 如果输入参数是一个张量,但是ONNX需要的是一个标量形式的输入,我们需要做个转化。_scalar可以帮助我们将一个张量转化为一个python标量,并且_if_scalar_type_as函数可以将python标量转化为PyTorch张量。

op运算符没有被标准化

如果没有被标准化,也就代表torch.onnx模块下,也没有这个op的定义,是个非ATen操作符,那么符号功能需要加在相应的PyTorch函数类中。请阅读下面的指示:

  1. 在相应的函数类中创建一个符号函数命名为symbolic。
  2. 第一个参数总是导出ONNX图形参数。
  3. 参数的名字除了第一个必须与前面的形式严格匹配。
  4. 输出元组大小必须与前面的形式严格匹配。
  5. 在符号功能中,如果操作符已经在ONNX标准化了,我们只需要创建一个代码去表示在图形里面的ONNX操作符。

解决自定义的LinearFunction操作

在Pytorch1.1.0 入门 自定义op(python)中提到过,早LinearFunction的定义中定义了一个@staticmethod的函数symbolic(),这个被叫做符号函数,经过后来的尝试,就是用torch.onnx.export进行向onnx格式转换的过程中,帮助识别自定义操作的函数。

  1. 最开始苦于不知道具体使用方法,观察了一下torch/onnx/symbolic.py下的操作,很多都是以g.op()作为返回对象的,而这个函数的第一个参数都能最后输出的onnx格式的模型的名字一样,例如:
def stack(g, tensor_list, dim):unsqueezed = [g.op("Unsqueeze", t, axes_i=[dim]) for t in _unpack_list(tensor_list)]return g.op("Concat", *unsqueezed, axis_i=dim)def mm(g, self, other):# Create a dummy C tensor. Only needed for API purposes, the value is# since beta = 0ty = _try_get_scalar_type(self, other).lower()C = g.constant(0, [1], ty)return g.op("Gemm", self, other, C, beta_f=0.0, alpha_f=1.0)

最后,对应的onnx层名字就是"Concat"和“Gemm”等。

  1. 标准torch.nn.Linear()方法输出的onnx的格式之后,发现全连接层的表示是“Gemm”:
    去torch/onnx/symbolic.py扒了扒已经被定义的op的写法, addmm只会返回一个,所以torch.nn.Linear()调用的应该是addmm。
def addmm(g, self, mat1, mat2, beta, alpha):return g.op("Gemm", mat1, mat2, self, beta_f=_scalar(beta), alpha_f=_scalar(alpha))
  1. 所以,在symbolic()函数下照猫画虎定义了和addmm几乎一样的结构。
    def symbolic(g, self, mat1, mat2, beta, alpha):return g.op("nonentity", self,mat1, mat2,  beta_f=beta, alpha_f=alpha)

pytorch转onnx代码实现

 torch.onnx.export(model,(data,indices,updates),"vfe.onnx",#   export_params=True,opset_version=13,#   do_constant_folding=True,#   keep_initializers_as_inputs=True,input_names=["data","indices","updates"],output_names=["output"],operator_export_type=torch.onnx.OperatorExportTypes.ONNX_ATEN_FALLBACK)

对应的输出的onnx结构的部分也就是如下的

...%19 : Float(64, 64, 3, 3) = onnx::MaxPool[kernel_shape=[2, 2], pads=[0, 0, 0, 0], strides=[2, 2]](%18), scope: Net_LinearFunction/Sequential[conv3]/MaxPool2d[2]%20 : Float(64, 576) = onnx::Flatten[axis=1](%19), scope: Net_LinearFunction%21 : Float(64, 128) = onnx::nonentity[alpha=1.3, beta=1.2](%20, %dense.0.weight, %dense.0.bias), scope: Net_LinearFunction/Sequential[dense]/Linear[0]%22 : Float(64, 128) = onnx::Relu(%21), scope: Net_LinearFunction/Sequential[dense]/ReLU[1]%23 : Float(64, 10) = onnx::nonentity[alpha=1.33, beta=1.22](%22, %dense.2.weight, %dense.2.bias), scope: Net_LinearFunction/Sequential[dense]/Linear[2]return (%23)

%21和%23都是自定义的op,“nonentity”来执行运算的,“[]”中代表的是网络参数,"()"中代表的权重

原文链接:https://blog.csdn.net/u012436149/article/details/78829329

原文链接:https://blog.csdn.net/qq_33120609/article/details/99429967

模型量化 pytorch2onnx相关推荐

  1. CUDA上深度学习模型量化的自动化优化

    CUDA上深度学习模型量化的自动化优化 深度学习已成功应用于各种任务.在诸如自动驾驶汽车推理之类的实时场景中,模型的推理速度至关重要.网络量化是加速深度学习模型的有效方法.在量化模型中,数据和模型参数 ...

  2. deeplearning模型量化实战

    deeplearning模型量化实战 MegEngine 提供从训练到部署完整的量化支持,包括量化感知训练以及训练后量化,凭借"训练推理一体"的特性,MegEngine更能保证量化 ...

  3. 基于YOLOv5模型压缩、模型量化、模型剪枝

    基于YOLOv5模型压缩.模型量化.模型剪枝 代码下载地址:下载地址 Requirements pip install -r requirements.txt Pruning for YOLOs Mo ...

  4. 一次失败的Pytorch模型量化尝试

    我的原工程模型是blazeface学习笔记_zhqh100的博客-CSDN博客完整的应该是一个人脸识别项目,人脸识别,大言不惭的说,我之前其实也做过,比如用dlib来做人脸识别,就是用opencv那一 ...

  5. 最新组合式模型量化方法,实现FPGA最高硬件利用率,准确率-推理速度达到SOTA...

    作者 | 王言治 来源 | AI科技大本营(ID:rgznai100) 深度神经网络(DNN)在图像.语言处理等领域获得了巨大成功,而如何将这些网络部署在ASIC.FPGA等嵌入式设备仍是热门研究方向 ...

  6. 神经网络压缩方法:模型量化的概念简介

    来源:DeepHub IMBA 本文约3200字,建议阅读6分钟 本文为你介绍如何使用量化的方法优化重型深度神经网络模型. 在过去的十年中,深度学习在解决许多以前被认为无法解决的问题方面发挥了重要作用 ...

  7. AI视觉组仙人一步之模型量化

    开心的程序猿@NXP 2021-01-28 Thursday 本文专为参加今年大学生智能车竞赛AI视觉组的同学们而写,也很适合其他对MCU上AI应用感兴趣的朋友. 神经网络模型最大的一个特点就是拥有宰 ...

  8. PyTorch模型量化工具学习

    官方教程(英文): https://pytorch.org/docs/stable/quantization.html​pytorch.org 官方教程(中文): https://pytorch.ap ...

  9. 【视频课】模型优化拆分!分别学习模型剪枝与模型量化理论与实践

    前言 好的模型结构是深度学习成功的关键因素之一,不仅是非常重要的学术研究方向,在工业界实践中也是模型是否能上线的关键.对各类底层深度学习模型设计和优化技术理解的深度是决定我们能否在深度学习项目中游刃有 ...

最新文章

  1. nowcoder 提高组模拟赛 选择题 解题报告
  2. django-admin.py 不是内部或外部命令
  3. 启动定时器t0的工作指令是_第六章 习题
  4. MySQL之最基本命令
  5. 【Java报错】Greenplum数据库报错 could not determine data type of parameter $2. 问题解决(踩坑分享)
  6. canvas入门实战--邀请卡生成与下载
  7. vue中指令的编译过程
  8. Redis 安装,主从配置及Sentinel配置自动Failover
  9. alook浏览器_Alook浏览器
  10. 怎么查违章?查违章哪个软件最好?交管12123处理电子眼不用再跑交警队!
  11. oracle rman迁移spfile,RMAN 异机迁移实战操作-附加常用命令
  12. 设计模式-中介者模式
  13. 简述python 的模块的分析
  14. 《测绘综合能力》——海洋测绘
  15. SQL Server 2008 Database Mirroring
  16. 头条限流是什么原因_今日头条限流吗 头条号限流是什么状态
  17. IDEA新手使用手册
  18. 一点点对WebResource.axd的配置及使用[原创]
  19. 进程间的几种通信方式的比较和线程间的几种通信方式
  20. 记一次聚拢Android线程实操

热门文章

  1. linux自动同步onedrive,Linux下同步onedrive
  2. 植物识别小系统:“ 花草树木 皆有名“一热爱自然,从认识自然开始 ~
  3. 微信服务号解决开启服务配置后自定义菜单失效的方法
  4. 区块链学习笔记(初识区块链)
  5. 网络线综合布线接地注意事项
  6. SOM-TL437x是基于TI Sitara系列AM4376/AM4379 ARM Cortex-A9高性能低功耗处理器设计的工业级核心板
  7. macos 打开终端弹出:(eval):export:1: not valid in this context: Fusion.app/Contents/Public
  8. goto加密php,PHP文件解密求大神思路,PHP加密后有大量goto语句
  9. 一个简单的JS(盒子移动)
  10. 婚庆行业发展报告,2021怎么精准引流?