模型量化 pytorch2onnx

在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过程中,是可以传递网络参数的。
	
    @staticmethod
    def 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 是个可选参数,有个 默认值 None
    def 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 只需要一个参数 接收 梯度。
    @staticmethod
    def backward(ctx, grad_output):
        # 此方法猜测是 torch.no_grad() 上下文中运行的. 
        #grad_output 是 Variable 类型。
        # 在开头的地方将保存的 tensor 给 unpack 了
        # 然后 给 所有应该返回的 梯度 以 None 初始化。
        input, weight, bias = ctx.saved_tensors
        grad_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)
    # 如果通过,最后会打印一个 True
    print(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_features
        self.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 weights
        self.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 = 0
    ty = _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)

torch.onnx.export详细介绍

operator_export_type (enum, default None)

默认为OperatorExportTypes.ONNX, 如果Pytorch built with DPYTORCH_ONNX_CAFFE2_BUNDLE,则默认为OperatorExportTypes.ONNX_ATEN_FALLBACK。

枚举类型包括:

OperatorExportTypes.ONNX - 将所有操作导出为ONNX操作。

OperatorExportTypes.ONNX_FALLTHROUGH - 试图将所有操作导出为ONNX操作,但碰到无法转换的操作(如onnx未实现的操作),则将操作导出为“自定义操作”,为了使导出的模型可用,运行时必须支持这些自定义操作。

OperatorExportTypes.ONNX_ATEN - 所有ATen操作导出为ATen操作,ATen是Pytorch的内建tensor库,所以这将使得模型直接使用Pytorch实现。(此方法转换的模型只能被Caffe2直接使用)

OperatorExportTypes.ONNX_ATEN_FALLBACK - 试图将所有的ATen操作也转换为ONNX操作,如果无法转换则转换为ATen操作(此方法转换的模型只能被Caffe2直接使用)。

对应的输出的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

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: MobileNetV2是一种用于图像分类和目标检测的轻量级卷积神经网络模型PyTorch是一种常用的深度学习框架,而ONNX是一种用于模型的开放式神经网络交换格式。 在PyTorch中使用MobileNetV2进行训练,可以通过加载预训练的模型,并进行微调来实现。我们可以使用PyTorch提供的torchvision模块来加载MobileNetV2模型的预训练权重,然后将数据集导入模型进行训练。 训练过程中,我们可以使用交叉熵损失函数和随机梯度下降(SGD)优化器。通过迭代训练数据集,不断更新模型的权重参数,使模型能够应对新的输入数据。 训练完成后,我们可以将PyTorch模型转换为ONNX格式,以便在其他平台上使用。在PyTorch中,可以使用torch.onnx.export()函数将模型转换为ONNX格式。此函数需要指定输入张量的形状和文件路径,以保存转换后的模型。 使用ONNX格式的模型,可以在不同的深度学习框架(如TensorFlow)或硬件平台上进行推理和部署。通过将模型转换为ONNX格式,可以实现更好的跨平台兼容性,并加速模型的部署过程。 总之,使用PyTorch训练MobileNetV2模型,并将其转换为ONNX格式,可以提供一种灵活而高效的方式,用于图像分类和目标检测任务,并实现跨平台部署的便利性。 ### 回答2: MobileNetV2是一种轻量级的卷积神经网络,适用于移动设备和嵌入式系统。PyTorch是一个流行的深度学习框架,提供了训练和部署模型的功能。而ONNX是一种开放的中间表示格式,可以在不同的深度学习框架之间共享模型。 要使用PyTorch训练MobileNetV2模型并将其转换为ONNX格式,可以按照以下步骤进行。 首先,需要导入所需的PyTorchONNX库: ```python import torch import torchvision.models as models import onnx ``` 然后,加载MobileNetV2模型并进行训练,可以使用PyTorch提供的预训练模型或自定义训练数据集来进行训练。训练过程可以根据具体任务进行配置,包括选择优化器、损失函数和训练迭代次数等。 训练完成后,可以将模型保存为PyTorch的.pth文件: ```python torch.save(model.state_dict(), 'mobilenetv2.pth') ``` 接下来,使用ONNX库将.pth文件转换为ONNX格式: ```python dummy_input = torch.randn(1, 3, 224, 224) # 定义一个虚拟输入作为示例 model = models.mobilenet_v2(pretrained=True) # 加载预训练模型 model.load_state_dict(torch.load('mobilenetv2.pth')) # 加载训练权重 torch.onnx.export(model, dummy_input, 'mobilenetv2.onnx', verbose=True) # 导出为ONNX模型 ``` 最后,将训练和转换得到的.onnx文件用于推理和部署。可以使用ONNX Runtime或其他支持ONNX格式的推理框架加载和运行模型。 通过以上步骤,我们可以使用PyTorch训练MobileNetV2模型,并将其转换为ONNX格式,以实现模型的跨框架和跨平台应用。 ### 回答3: MobileNetV2是一种轻量级的神经网络架构,适用于移动设备等资源受限的环境下进行图像分类任务。PyTorch是一种深度学习框架,具有易用性和高效性,训练神经网络模型时是使用PyTorch进行的。 ONNX是一种开放的深度学习模型格式,能够在不同的深度学习框架之间进行模型的互操作性。将MobileNetV2模型训练为ONNX格式,可以使得该模型能够运行在不同的深度学习框架中,而不仅仅局限于PyTorch。 要将MobileNetV2模型训练为ONNX格式,可以按照以下步骤进行: 1. 准备训练数据集:使用包含图像和对应标签的数据集进行训练,例如ImageNet数据集。 2. 定义并训练MobileNetV2模型:使用PyTorch定义MobileNetV2模型,并使用训练数据集进行模型训练。 3. 导出模型ONNX格式:在模型训练完成后,使用PyTorch提供的导出函数将训练好的模型转换为ONNX格式。这可以通过调用`torch.onnx.export()`函数完成,将模型定义、训练好的参数和输入的形状等信息导出为ONNX模型文件。 4. 验证导出的ONNX模型:载入导出的ONNX模型,并使用测试数据进行验证,以确保模型导出正确无误。 通过将MobileNetV2模型训练为ONNX格式,可以使得该模型能够在其他深度学习框架中进行部署和应用。此外,ONNX格式还支持模型量化和优化等功能,有助于进一步减小模型的体积和提高模型的执行效率。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值