Pytorch1.0.1导出ONNX模型

TORCH.ONNX

Example: End-to-end AlexNet from PyTorch to Caffe2

这是一个简单的脚本,它将torchvision中定义的预训练的AlexNet导出到ONNX中。它运行一轮推理,然后将生成的跟踪模型保存到alexnet.onnx:

mport torch
import torchvision

dummy_input = torch.randn(10, 3, 224, 224, device='cuda')
model = torchvision.models.alexnet(pretrained=True).cuda()

# Providing input and output names sets the display names for values
# within the model's graph. Setting these does not change the semantics
# of the graph; it is only for readability.
#
# The inputs to the network consist of the flat list of inputs (i.e.
# the values you would pass to the forward() method) followed by the
# flat list of parameters. You can partially specify names, i.e. provide
# a list here shorter than the number of inputs to the model, and we will
# only set that subset of names, starting from the beginning.
input_names = [ "actual_input_1" ] + [ "learned_%d" % i for i in range(16) ]
output_names = [ "output1" ]

torch.onnx.export(model, dummy_input, "alexnet.onnx", verbose=True, input_names=input_names, output_names=output_names)

结果是二进制protobuf文件,其中包含您导出的模型的网络结构和参数(在本例中为AlexNet)。关键字参数使导出器打印出人类可读的网络表示:alexnet.onnxverbose=True

# These are the inputs and parameters to the network, which have taken on
# the names we specified earlier.
graph(%actual_input_1 : Float(10, 3, 224, 224)
      %learned_0 : Float(64, 3, 11, 11)
      %learned_1 : Float(64)
      %learned_2 : Float(192, 64, 5, 5)
      %learned_3 : Float(192)
      # ---- omitted for brevity ----
      %learned_14 : Float(1000, 4096)
      %learned_15 : Float(1000)) {
  # Every statement consists of some output tensors (and their types),
  # the operator to be run (with its attributes, e.g., kernels, strides,
  # etc.), its input tensors (%actual_input_1, %learned_0, %learned_1)
  %17 : Float(10, 64, 55, 55) = onnx::Conv[dilations=[1, 1], group=1, kernel_shape=[11, 11], pads=[2, 2, 2, 2], strides=[4, 4]](%actual_input_1, %learned_0, %learned_1), scope: AlexNet/Sequential[features]/Conv2d[0]
  %18 : Float(10, 64, 55, 55) = onnx::Relu(%17), scope: AlexNet/Sequential[features]/ReLU[1]
  %19 : Float(10, 64, 27, 27) = onnx::MaxPool[kernel_shape=[3, 3], pads=[0, 0, 0, 0], strides=[2, 2]](%18), scope: AlexNet/Sequential[features]/MaxPool2d[2]
  # ---- omitted for brevity ----
  %29 : Float(10, 256, 6, 6) = onnx::MaxPool[kernel_shape=[3, 3], pads=[0, 0, 0, 0], strides=[2, 2]](%28), scope: AlexNet/Sequential[features]/MaxPool2d[12]
  # Dynamic means that the shape is not known. This may be because of a
  # limitation of our implementation (which we would like to fix in a
  # future release) or shapes which are truly dynamic.
  %30 : Dynamic = onnx::Shape(%29), scope: AlexNet
  %31 : Dynamic = onnx::Slice[axes=[0], ends=[1], starts=[0]](%30), scope: AlexNet
  %32 : Long() = onnx::Squeeze[axes=[0]](%31), scope: AlexNet
  %33 : Long() = onnx::Constant[value={9216}](), scope: AlexNet
  # ---- omitted for brevity ----
  %output1 : Float(10, 1000) = onnx::Gemm[alpha=1, beta=1, broadcast=1, transB=1](%45, %learned_14, %learned_15), scope: AlexNet/Sequential[classifier]/Linear[6]
  return (%output1);
}

您还可以使用onnx库验证protobuf 。你可以用conda 安装:onnx

conda install -c conda-forge onnx

然后,您可以运行:

import onnx

# Load the ONNX model
model = onnx.load("alexnet.onnx")

# Check that the IR is well formed
onnx.checker.check_model(model)

# Print a human readable representation of the graph
onnx.helper.printable_graph(model.graph)

要使用caffe2运行导出的脚本,您需要安装caffe2:如果您还没有caffe2,请按照安装说明进行操作。

安装完成后,您可以将后端用于Caffe2:

# ...continuing from above
import caffe2.python.onnx.backend as backend
import numpy as np

rep = backend.prepare(model, device="CUDA:0") # or "CPU"
# For the Caffe2 backend:
#     rep.predict_net is the Caffe2 protobuf for the network
#     rep.workspace is the Caffe2 workspace for the network
#       (see the class caffe2.python.onnx.backend.Workspace)
outputs = rep.run(np.random.randn(10, 3, 224, 224).astype(np.float32))
# To run networks with more than one input, pass a tuple
# rather than a single numpy ndarray.
print(outputs[0])

将来,还会有其他框架的后端。

限制

  • 该ONNX导出是一个基于Trace的出口国,这意味着它通过执行模型一次,导出此运行期间实际运行操作人员进行操作。这意味着如果您的模型是动态的,例如,根据输入数据更改行为,则导出将不准确。类似地,跟踪可能仅对特定输入大小有效(这是我们在跟踪时需要显式输入的一个原因。)我们建议检查模型跟踪并确保跟踪的运算符看起来合理。

  • PyTorch和Caffe2经常有运算符的实现,但有一些数字差异。根据模型结构,这些差异可以忽略不计,但它们也会导致行为的主要差异(特别是在未经训练的模型上)。在未来的版本中,我们计划允许Caffe2直接调用Torch实现的运算符,以帮助您平滑当精确度很重要时,这些差异,并记录这些差异。

Supported operators

The following operators are supported:

add (nonzero alpha not supported)

sub (nonzero alpha not supported)

mul

div

cat

mm

addmm

neg

sqrt

tanh

sigmoid

mean

sum

prod

t

expand (only when used before a broadcasting ONNX operator; e.g., add)

transpose

view

split

squeeze

prelu (single weight shared among input channels not supported)

threshold (non-zero threshold/non-zero value not supported)

leaky_relu

glu

softmax (only dim=-1 supported)

avg_pool2d (ceil_mode not supported)

log_softmax

unfold (experimental support with ATen-Caffe2 integration)

elu

concat

abs

index_select

pow

clamp

max

min

eq

gt

lt

ge

le

exp

sin

cos

tan

asin

acos

atan

permute

Conv

BatchNorm

MaxPool1d (ceil_mode not supported)

MaxPool2d (ceil_mode not supported)

MaxPool3d (ceil_mode not supported)

Embedding (no optional arguments supported)

RNN

ConstantPadNd

Dropout

FeatureDropout (training mode not supported)

Index (constant integer and tuple indices supported)

The operator set above is sufficient to export the following models:

AlexNet

DCGAN

DenseNet

Inception (warning: this model is highly sensitive to changes in operator implementation)

ResNet

SuperResolution

VGG

word_language_model

增加导出额外的operators是一种高级应用。为此,开发人员需要接触PyTorch的源代码。请按照 从源安装PyTorch 的说明进行操作。如果想要的运算符在ONNX中是标准化的存在,则应该很容易添加对导出此类运算符的支持(为运算符添加符号函数)。要确认operators是否标准化,请检查 ONNX 目前支持的operators列表。

如果运算符是ATen运算符,这意味着您可以找到函数的声明torch/csrc/autograd/generated/VariableType.h (在PyTorch install目录中的生成代码中),您应该添加符号函数并按照下面列出的说明操作:torch/onnx/symbolic.py

  • torch / onnx /symbolic.py中定义符号函数 。确保该函数与在中定义的ATen运算符/函数同名VariableType.h。

  • 第一个参数始终是导出的ONNX图。参数名称必须与名称完全匹配VariableType.h,因为调度是用关键字参数完成的。

  • 参数排序不一定匹配VariableType.h,tensors(输入)始终是第一个,然后是non tensors参数。

  • 在符号函数中,如果运算符已经在ONNX中标准化,我们只需要创建一个节点来表示图中的ONNX运算符。

  • 如果输入参数是张量,但是ONNX要求标量,我们必须明确地进行转换。辅助函数_scalar可以将标量张量转换为python标量,并_if_scalar_type_as可以将Python标量转换为PyTorch张量。

如果运算符是非ATen运算符,则必须在相应的PyTorch Function类中添加符号函数。请阅读以下说明:

  • 创建symbolic在相应的Function类中命名的符号函数。

  • 第一个参数始终是导出的ONNX图。

  • 除第一个之外的参数名称必须与名称完全匹配forward。

  • 输出元组大小必须与输出相匹配forward。

  • 在符号函数中,如果运算符已经在ONNX中标准化,我们只需要创建一个节点来表示图中的ONNX运算符。

符号函数应该在Python中实现。所有这些函数都与Python方法交互,这些方法是通过C ++实现的 - Python绑定,但直观地说它们提供的接口如下所示:

def operator/symbolic(g, *inputs):
  """
  Modifies Graph (e.g., using "op"), adding the ONNX operations representing
  this PyTorch function, and returning a Value or tuple of Values specifying the
  ONNX outputs whose values correspond to the original PyTorch return values
  of the autograd Function (or None if an output is not supported by ONNX).

  Arguments:
    g (Graph): graph to write the ONNX representation into
    inputs (Value...): list of values representing the variables which contain
        the inputs for this function
  """

class Value(object):
  """Represents an intermediate tensor value computed in ONNX."""
  def type(self):
    """Returns the Type of the value."""

class Type(object):
  def sizes(self):
    """Returns a tuple of ints representing the shape of a tensor this describes."""

class Graph(object):
  def op(self, opname, *inputs, **attrs):
    """
    Create an ONNX operator 'opname', taking 'args' as inputs
    and attributes 'kwargs' and add it as a node to the current graph,
    returning the value representing the single output of this
    operator (see the `outputs` keyword argument for multi-return
    nodes).

    The set of operators and the inputs/attributes they take
    is documented at https://github.com/onnx/onnx/blob/master/docs/Operators.md

    Arguments:
        opname (string): The ONNX operator name, e.g., `Abs` or `Add`.
        args (Value...): The inputs to the operator; usually provided
            as arguments to the `symbolic` definition.
        kwargs: The attributes of the ONNX operator, with keys named
            according to the following convention: `alpha_f` indicates
            the `alpha` attribute with type `f`.  The valid type specifiers are
            `f` (float), `i` (int), `s` (string) or `t` (Tensor).  An attribute
            specified with type float accepts either a single float, or a
            list of floats (e.g., you would say `dims_i` for a `dims` attribute
            that takes a list of integers).
        outputs (int, optional):  The number of outputs this operator returns;
            by default an operator is assumed to return a single output.
            If `outputs` is greater than one, this functions returns a tuple
            of output `Value`, representing each output of the ONNX operator
            in positional.
    """

所述ONNX图C ++定义在torch/csrc/jit/ir.h

以下是为elu操作员处理缺少的符号功能的示例。我们尝试导出模型并查看错误消息,如下所示:

UserWarning: ONNX export failed on elu because torch.onnx.symbolic.elu does not exist
RuntimeError: ONNX export failed: Couldn't export operator elu

导出失败,因为PyTorch不支持导出elu运算符。我们找到 了

virtual Tensor elu(const Tensor & input, Scalar alpha, bool inplace) const override;

VariableType.h。这意味着是一个ATen op。我们检查ONNX op 列表,并确认在ONNX中是标准化的。我们将以下行添加到symbolic.py


def elu(g, input, alpha, inplace=False):
    return g.op("Elu", input, alpha_f=_scalar(alpha))

现在PyTorch能够导出elu op。

symbolic.pytensor.py, padding.py中有更多示例 。

用于指定运算符定义的接口是实验性的; 喜欢冒险的用户应该注意,API可能会在未来的界面中发生变化。

功能

torch.onnx.export(* args,** kwargs )
发布了45 篇原创文章 · 获赞 21 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/xxradon/article/details/93767462