当前位置:   article > 正文

ONNX简介_onnx是什么文件

onnx是什么文件

ONNX简介

Open Neural Network ExchangeONNX,开放神经网络交换)格式,是一个用于表示深度学习模型的标准,可使模型在不同框架之间进行转移。ONNX是一种针对机器学习所设计的开放式的文件格式,用于存储训练好的模型。它使得不同的人工智能框架(如Pytorch, MXNet)可以采用相同格式存储模型数据并交互。ONNX的规范及代码主要由微软,亚马逊,FacebookIBM等公司共同开发,以开放源代码的方式托管在Github上。目前官方支持加载ONNX模型并进行推理的深度学习框架有:Caffe2, PyTorch, MXNetML.NETTensorRTMicrosoft CNTK,并且TensorFlow也非官方的支持ONNX

ONNX

开放式神经网络交换(ONNX)是迈向开放式生态系统的第一步,它使AI开发人员能够随着项目的发展选择合适的工具。ONNXAI模型提供开源格式。它定义了可扩展的计算图模型,以及内置运算符和标准数据类型的定义。最初的ONNX专注于推理(评估)所需的功能。ONNX解释计算图的可移植,它使用graph的序列化格式。它不一定是框架选择在内部使用和操作计算的形式。例如,如果在优化过程中操作更有效,则实现可以在存储器中以不同方式表示模型。

ONNX是一个开放式规范,由以下组件组成:

  1. 可扩展计算图模型的定义。
  2. 标准数据类型的定义。
  3. 内置运算符的定义。

现如今,各大主流深度学习框架都有着自己独有的特点与魅力,吸引着广大科研与开发人员,例如:

Caffe2:方便机器学习算法和模型大规模部署在移动设备。

PyTorchPyTorch是一个快速便于实验深度学习框架。但是由于其高度封装,导致部分function不够灵活。

TensorFlowTensorFlow是一个开放源代码软件库,是很多主流框架的基础或者依赖。几乎能满足所有机器学习开发的功能,但是也有由于其功能代码过于底层,学习成本高,代码冗繁,编程逻辑与常规不同等缺点。

此外还有:Cognitive Toolkit (CNTK)Apache MXNetChainerApple CoreMLSciKit-LearnML.NET

深度学习算法大多通过计算数据流图来完成神经网络的深度学习过程。一些框架(例如CNTKCaffe2TheanoTensorFlow)使用静态图形,而其他框架(例如PyTorchChainer)使用动态图形。但是这些框架都提供了接口,使开发人员可以轻松构建计算图和运行时,以优化的方式处理图。这些图用作中间表示(IR),捕获开发人员源代码的特定意图,有助于优化和转换在特定设备(CPUGPUFPGA等)上运行。

假设一个场景:现在某组织因为主要开发用TensorFlow为基础的框架,现在有一个深度算法,需要将其部署在移动设备上,以观测变现。传统地我们需要用caffe2重新将模型写好,然后再训练参数;试想下这将是一个多么耗时耗力的过程。

此时,ONNX便应运而生,Caffe2PyTorchMicrosoft Cognitive ToolkitApache MXNet等主流框架都对ONNX有着不同程度的支持。这就便于了我们的算法及模型在不同的框架之间的迁移。

ONNX model

The top-level ONNX construct is a ‘Model.’

模型结构的主要目的是将元数据(meta data)与图形(graph)相关联,图形包含所有可执行元素。首先,读取模型文件时使用元数据,为实现提供所需的信息,以确定它是否能够:执行模型,生成日志消息,错误报告等功能。此外元数据对工具很有用,例如IDE和模型库,它需要它来告知用户给定模型的目的和特征。

每个model具有以下组件:

https://pic1.zhimg.com/v2-03c407241a9eb1335b97d23f62b32bc0_r.jpg

ONNX Operator Sets

每个模型必须明确命名它依赖于其功能的运算符集。操作员集定义可用的操作符,其版本和状态。每个模型按其域定义导入的运算符集。所有模型都隐式导入默认的ONNX运算符集。

运算符集的属性是:

https://pic3.zhimg.com/v2-19437f016e6bde73c90d340e49241b3e_r.jpg

ONNX Operator

(graph)中使用的每个运算符必须由模型(model)导入的一个运算符集明确声明。

运算符定义的属性是:

https://pic1.zhimg.com/v2-350ef01471a7b05c17b7edbf5ce57bbc_r.jpg

ONNX Graph

序列化图由一组元数据字段(metadata),模型参数列表(a list of model parameters,)和计算节点列表组成(a list of computation nodes)

每个计算数据流图被构造为拓扑排序的节点列表,这些节点形成图形,其必须没有周期。每个节点代表对运营商的呼叫。每个节点具有零个或多个输入以及一个或多个输出。

图表具有以下属性:

https://pic2.zhimg.com/v2-5f412421ad4f69f68062b45007dbd709_r.jpg

Each graph MUST define the names and types of its inputs and outputs, which are specified as ‘value info’ structures, having the following properties:

https://pic2.zhimg.com/v2-5363f02eee440c97ec8de62d82f72ee1_r.jpg

Names Within a Graph

所有名称必须遵守C标识符语法规则。

节点,输入,输出,初始化器和属性的名称被组织到多个名称空间中。在命名空间内,每个给定图形的每个名称必须是唯一的。

The namespaces are:

https://pic2.zhimg.com/v2-8e697aca171768de2185aa41f16376e5_r.jpg

Node

计算节点由名称,它调用的运算符的名称,命名输入列表,命名输出列表和属性列表组成。输入和输出在位置上与operator输入和输出相关联。属性按名称与运算符属性相关联。

它们具有以下属性:

https://pic1.zhimg.com/v2-490b564fdd342efa1c4fe476f63cae4c_r.jpg

计算图中的边缘由后续节点的输入中由名称引用的一个节点的输出建立。

给定节点的输出将新名称引入图中。节点输出的值由节点的运算符计算。节点输入可以指定节点输出,图形输入和图形初始化器。当节点输出的名称与图形输出的名称一致时,图形输出的值是该节点计算的相应输出值。

ONNX Python API

  1. Loading an ONNX Model
  2. Saving an ONNX Model
  3. ManipulatingTensorProtoandNumpyArray
  4. Creating an ONNX Model Using HelperFunctions
  5. Checking an ONNX Model
  6. Optimizing an ONNX Model
  7. Running Shape Inference on an ONNX Model
  8. UtilityFunctions

1. Loading an ONNX Model

import onnx

 

onnx_model = onnx.load('path/to/the/model.onnx')

# `onnx_model` is a ModelProto struct

2. Saving an ONNX Model

import onnx

 

onnx_model = ... # Your model in memory as ModelProto

 

# Save the ONNX model

onnx.save(onnx_model, 'path/to/the/model.onnx')

3. ManipulatingTensorProtoandNumpyArray

import numpy

import onnx

from onnx import numpy_helper

 

# Preprocessing: create a Numpy array

numpy_array = numpy.array([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]], dtype=float)

print('Original Numpy array:\n{}\n'.format(numpy_array))

 

# Convert the Numpy array to a TensorProto

tensor = numpy_helper.from_array(numpy_array)

print('TensorProto:\n{}'.format(tensor))

 

# Convert the TensorProto to a Numpy array

new_array = numpy_helper.to_array(tensor)

print('After round trip, Numpy array:\n{}\n'.format(numpy_array))

 

# Save the TensorProto

with open('tensor.pb', 'wb') as f:

    f.write(tensor.SerializeToString())

 

# Load a TensorProto

new_tensor = onnx.TensorProto()

with open('tensor.pb', 'rb') as f:

    new_tensor.ParseFromString(f.read())

print('After saving and loading, new TensorProto:\n{}'.format(new_tensor))

4. Creating an ONNX Model Using HelperFunctions

import onnx

from onnx import helper

from onnx import AttributeProto, TensorProto, GraphProto

 

# The protobuf definition can be found here:

# https://github.com/onnx/onnx/blob/master/onnx/onnx.proto

 

# Create one input (ValueInfoProto)

X = helper.make_tensor_value_info('X', TensorProto.FLOAT, [1, 2])

 

# Create one output (ValueInfoProto)

Y = helper.make_tensor_value_info('Y', TensorProto.FLOAT, [1, 4])

 

# Create a node (NodeProto)

node_def = helper.make_node(

    'Pad', # node name

    ['X'], # inputs

    ['Y'], # outputs

    mode='constant', # attributes

    value=1.5,

    pads=[0, 1, 0, 1],

)

 

# Create the graph (GraphProto)

graph_def = helper.make_graph(

    [node_def],

    'test-model',

    [X],

    [Y],

)

 

# Create the model (ModelProto)

model_def = helper.make_model(graph_def, producer_name='onnx-example')

 

print('The model is:\n{}'.format(model_def))

onnx.checker.check_model(model_def)

print('The model is checked!')

5. Checking an ONNX Model

import onnx

 

# Preprocessing: load the ONNX model

model_path = 'path/to/the/model.onnx'

onnx_model = onnx.load(model_path)

 

print('The model is:\n{}'.format(onnx_model))

 

# Check the model

onnx.checker.check_model(onnx_model)

print('The model is checked!')

6. Optimizing an ONNX Model

import onnx

from onnx import optimizer

 

# Preprocessing: load the model to be optimized.

model_path = 'path/to/the/model.onnx'

original_model = onnx.load(model_path)

 

print('The model before optimization:\n{}'.format(original_model))

 

# A full list of supported optimization passes can be found here:

# https://github.com/onnx/onnx/blob/master/onnx/optimizer.py#L27

passes = ['fuse_consecutive_transposes']

 

# Apply the optimization on the original model

optimized_model = optimizer.optimize(original_model, passes)

 

print('The model after optimization:\n{}'.format(optimized_model))

 

# One can also apply the default passes on the (serialized) model

# Check the default passes here: https://github.com/onnx/onnx/blob/master/onnx/optimizer.py#L41

optimized_model = optimizer.optimize(original_model)

7. Running Shape Inference on an ONNX Model

import onnx

from onnx import helper, shape_inference

from onnx import TensorProto

 

# Preprocessing: create a model with two nodes, Y's shape is unknown

node1 = helper.make_node('Transpose', ['X'], ['Y'], perm=[1, 0, 2])

node2 = helper.make_node('Transpose', ['Y'], ['Z'], perm=[1, 0, 2])

 

graph = helper.make_graph(

    [node1, node2],

    'two-transposes',

    [helper.make_tensor_value_info('X', TensorProto.FLOAT, (2, 3, 4))],

    [helper.make_tensor_value_info('Z', TensorProto.FLOAT, (2, 3, 4))],

)

 

original_model = helper.make_model(graph, producer_name='onnx-examples')

 

# Check the model and print Y's shape information

onnx.checker.check_model(original_model)

print('Before shape inference, the shape info of Y is:\n{}'.format(original_model.graph.value_info))

 

# Apply shape inference on the model

inferred_model = shape_inference.infer_shapes(original_model)

 

# Check the model and print Y's shape information

onnx.checker.check_model(inferred_model)

print('After shape inference, the shape info of Y is:\n{}'.format(inferred_model.graph.value_info))

8. UtilityFunctions

import onnx

import onnx.utils

 

model = onnx.load('path/to/the/model.onnx')

polished_model = onnx.utils.polish_model(model)

目前ONNX已经支持大多数框架,使用这些框架构建的模型可以转换为通用的ONNX计算图和OP。现阶段ONNX只支持推理,所以导入的模型都需要在原框架完成训练。

其中 Frameworks 下的框架表示它们已经内嵌了 ONNX,开发者可以直接通过这些框架的内置 API 将模型导出为 ONNX 格式,或采用它们作为推理后端。而 Converters 下的框架并不直接支持 ONNX 格式,但是可以通过转换工具导入或导出这些框架的模型。

其实并不是所有框架都支持导入和导出 ONNX 格式的模型,有一些并不支持导入 ONNX 格式的模型,例如 PyTorch Chainer 等,TensorFlow ONNX 导入同样也正处于实验阶段。下图展示了各框架对 ONNX 格式的支持情况:

 

怎样使用 ONNX

对于内建了 ONNX 的框架而言,使用非常简单,只需要调用 API 导出或导入已训练模型就可以了。例如对 PyTorch 而言,只需要几个简单的步骤就能完成模型的导出和导入。简单而言,首先加载 torch.onnx 模块,然后导出预训练模型并查看模型结构信息,最后再将导出的 ONNX 模型加载到另外的框架就能执行推理了。

from torch.autograd import Variable

import torch.onnx

import torchvision

 

dummy_input = Variable(torch.randn(10, 3, 224, 224)).cuda()

model = torchvision.models.alexnet(pretrained=True).cuda()

 

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)

 

如上所示将导出 ONNX 格式的 AlexNet 模型,其中"alexnet.onnx"为保存的模型,input_namesoutput_names verbose=True 都是为了打印出模型结构信息。同样随机产生的「图像」dummy_input 也是为了了解模型结构,因为我们可以通过它理解输入与每一层具体的参数维度。以下展示了 ONNX 输出的简要模型信息:

graph(%actual_input_1 : Float(10, 3, 224, 224)

      %learned_0 : Float(64, 3, 11, 11)

      %learned_1 : Float(64)

      # ---- omitted for brevity ----

      %learned_14 : Float(1000, 4096)

      %learned_15 : Float(1000)) {

  %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 ----

  %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 检查中间表征,不过这里并不介绍。后面加载另外一个框架并执行推理同样非常简单。如下所示,我们可以从 caffe2 中加载 ONNX 的后端,并将前面保存的模型加载到该后端,从而在新框架下进行推理。这里我们能选择执行推理的硬件,并直接推理得出输出结果。

import caffe2.python.onnx.backend as backend

import numpy as np

import onnx

 

model = onnx.load("alexnet.onnx")

rep = backend.prepare(model, device="CUDA:0") # or "CPU"

outputs = rep.run(np.random.randn(10, 3, 224, 224).astype(np.float32))

其实也就两三行代码涉及 ONNX 的核心操作,即导出模型、加载模型和加载另一个框架的后端。TensorFlow CNTK 等其它框架的具体 API 可能不一样,但主要过程也就这简单的几步。

怎样优化 ONNX

前面就已经介绍了 Model ZooONNX Runtime ONNX.JS,现在,我们可以具体看看它们都是什么,它们怎样才能帮助我们优化 ONNX 模型的选择与推理速度。

Model Zoo

ONNX Model Zoo 包含了一系列预训练模型,它们都是 ONNX 格式,且能获得当前最优的性能。因此只要下载这样的模型,我们本地不论是 TensorFlow 还是 MXNet,只要是只是能加载模型的框架,就能运行这些预训练模型。

项目地址:https://github.com/onnx/models

更重要的是,这个 Model Zoo 不仅有调用预训练模型的代码,它还为每个预训练模型开放了对应的训练代码。训练和推理代码都是用 Jupyter Notebook 写的,数据和模型等都有对应的链接。

目前该 Model Zoo 主要从图像分类、检测与分割、图像超分辨、机器翻译和语音识别等 14 个方向包含19 种模型,还有更多的模型还在开发中。如下展示了图像分类中已经完成的模型,它们都是通用的 ONNX 格式。

Model Zoo目前面临的问题及解决方法,例如目前的预训练模型主要集中在计算机视觉方面、ONNX 缺少一些特定的 OP、权重计算图下载慢等。因此 Model Zoo 接下来也会更关注其它语音和语言等模型,优化整个 GitHub 项目的下载结构。

ONNX Runtime

微软开源的 ONNX Runtime 推理引擎支持 ONNX 中定义的所有运算单元,它非常关注灵活性和推理性能。因此不论我们的开发环境是什么,Runtime 都会基于各种平台与硬件选择不同的自定义加速器,并希望以最小的计算延迟和资源占用完成推理。

文档地址:https://docs.microsoft.com/en-us/python/api/overview/azure/onnx/intro

ONNX Runtime 可以自动调用各种硬件加速器,例如英伟达的 CUDATensorRT 和英特尔的 MKL-DNNnGraph。如下所示,ONNX 格式的模型可以传入到蓝色部分的 Runtime,并自动完成计算图分割及并行化处理,最后我们只需要如橙色所示的输入数据和输出结果就行了。

其实在实际使用的时候,开发者根本不需要考虑蓝色的部分,不论是编译还是推理,代码都贼简单。如下所示,导入 onnxruntime 模块后,调用 InferenceSession() 方法就能导入 ONNX 格式的模型,并完成上图一系列复杂的优化。最后只需要 session.run() 就可以进行推理了,所有的优化过程都隐藏了细节。

import onnxruntime

 

session = onnxruntime.InferenceSession("your_model.onnx")

prediction = session.run(None, {"input1": value})

Runtime 的目标是构建高性能推理引擎,它需要利用最好的加速器和完整的平台支持。只需要几行代码就能把计算图优化一遍,这对 ONNX 格式的模型是个大福利。

ONNX.JS

ONNX.js 是一个在浏览器上运行 ONNX 模型的库,它采用了 WebAssembly WebGL 技术,并在 CPU GPU 上推理 ONNX 格式的预训练模型。

项目地址:https://github.com/Microsoft/onnxjs

Demo 展示地址:https://microsoft.github.io/onnxjs-demo

通过 ONNX.js,开发者可以直接将预训练的 ONNX 模型部署到浏览器,这些预训练模型可以是 Model Zoo 中的,也可以是自行转换的。部署到浏览器有很大的优势,它能减少服务器与客户端之间的信息交流,并获得免安装和跨平台的机器学习模型体验。如下所示为部署到网页端的 SqueezeNet

如上若是选择 GPU,它会采用 WebGL 访问 GPU。如果选择 CPU,那么其不仅会采用 WebAssembly 以接近原生的速度执行模型,同时也会采用 Web Workers 提供的「多线程」环境来并行化数据处理。该项目表明,通过充分利用 WebAssembly Web WorkersCPU 可以获得很大的性能提升。这一点在项目提供的 Benchmarks 中也有相应的展示:

以上微软在 Chrome Edge 浏览器中测试了 ResNet-50 的推理速度,其中比较显著的是 CPU 的推理速度。这主要是因为 Keras.js TensorFlow.js 在任何浏览器中都不支持 WebAssembly

最后,从 ONNXIFI ONNX.js,开源社区已经为 ONNX 格式构建出众多的优化库、转换器和资源。很多需要支持多框架的场景也都将其作为默认的神经网络格式,也许以后,ONNX 真的能统一神经网络之间的江湖。

 

【转载】https://zhuanlan.zhihu.com/p/41255090

【转载】https://mp.weixin.qq.com/s?__biz=MzA3MzI4MjgzMw==&mid=2650752891&idx=2&sn=ce805bcd4574fcc90ad62f9a03c25116&chksm=871a8305b06d0a13eb6f1f327269ccf4f9dfd8e1053378fc021dc6bf2a999c78313ce429d883&scene=27#wechat_redirect

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/繁依Fanyi0/article/detail/949025
推荐阅读
  

闽ICP备14008679号