😍用python操作ONNX

接下来的章节将重点介绍如何使用onnx提供的Python API构建 ONNX 计算图。

一个简单的例子:线性回归

线性回归是机器学习中最简单的模型,其表达式如下.我们可以将其视为三个变量的函数 分解为y = Add(MatMul(X, A), B)。这就是我们需要用 ONNX 运算符表示的内容。首先是用ONNX 运算符实现函数。 ONNX 是强类型的。必须为函数的输入和输出定义形状和类型。也就是说,在make 函数中,我们需要四个函数来构建计算图:

  • make_tensor_value_info:声明变量(输入或输出)的形状和类型

  • make_node:创建一个由操作符类型(算子名称)、输入和输出定义的节点

  • make_graph:利用前两个函数创建的对象创建 ONNX 计算图

  • make_model:将计算图和额外的元数据进行合并

在创建过程中,我们需要为图中每个节点的输入和输出命名。计算图的输入和输出由 onnx 对象定义,字符串用于指代中间结果。

# imports

from onnx import TensorProto
from onnx.helper import (
    make_model, make_node, make_graph,
    make_tensor_value_info)
from onnx.checker import check_model

# inputs

# 'X' is the name, TensorProto.FLOAT the type, [None, None] the shape
X = make_tensor_value_info('X', TensorProto.FLOAT, [None, None])
A = make_tensor_value_info('A', TensorProto.FLOAT, [None, None])
B = make_tensor_value_info('B', TensorProto.FLOAT, [None, None])

# outputs, the shape is left undefined

Y = make_tensor_value_info('Y', TensorProto.FLOAT, [None])

# nodes

# It creates a node defined by the operator type MatMul,
# 'X', 'A' are the inputs of the node, 'XA' the output.
node1 = make_node('MatMul', ['X', 'A'], ['XA'])
node2 = make_node('Add', ['XA', 'B'], ['Y'])

# from nodes to graph
# the graph is built from the list of nodes, the list of inputs,
# the list of outputs and a name.

graph = make_graph([node1, node2],  # nodes
                    'lr',  # a name
                    [X, A, B],  # inputs
                    [Y])  # outputs

# onnx graph
# there is no metadata in this case.

onnx_model = make_model(graph)

# Let's check the model is consistent,
# this function is described in section
# Checker and Shape Inference.
check_model(onnx_model)

# the work is done, let's display it...
print(onnx_model)

shape定义为[None, None]表示该对象是一个两维张量,没有任何形状信息。 通过查看图中每个对象的字段,也可以检查 ONNX 计算图。

张量类型是浮点数(= 1)。函数onnx.helper.tensor_dtype_to_np_dtype()会给出与 numpy 对应的数据类型。

%r同样用于格式化字符串,但它表示将一个值转换为它的“原始”字符串表示形式。这意味着,它会保留字符串中的所有特殊字符,包括空格和换行符等,而不会尝试对它们进行任何转换或解释。这对于调试或者需要保留原始格式的字符串非常有用。

例如:

输出将会是:

序列化

ONNX 建立在 protobuf 的基础之上。它为描述机器学习模型添加了必要的定义,大多数情况下,ONNX 是用来序列化或反序列化模型的。第二部分将介绍数据的序列化和反序列化,如张量、稀疏张量......

模型序列化

ONNX 基于 protobuf。它最大限度地减少了在磁盘上保存图形所需的空间。onnx 中的每个对象(参见Protos)都可以通过SerializeToString 方法序列化。

ONNX计算图可通过函数load恢复:

任何模型都可以用这种方式序列化,除非它们的大小超过 2 Gb。接下来的章节将介绍如何克服模型大小这一限制。

张量序列化

张量的序列化过程通常如下:

还有反序列化:

数据类型不限于TensorProto

使用函数load_tensor_from_string可以简化这段代码。

Initializer,默认值

之前的模型假定线性回归的系数也是模型的输入,这不是很方便。为了遵循 onnx 语义,它们应该作为常量或initializer成为模型本身的一部分。这个示例修改了上一个示例,将输入AB变为initializer。以下两个函数,可以将 numpy 转换为 onnx,也可以反过来转换(参见数组)。

  • onnx.numpy_helper.to_array:从 onnx 转换到 numpy

  • onnx.numpy_helper.from_array: 从 numpy 转换到 onnx

同样,也可以通过 onnx API来查看initializers

Attributes

有些算子需要属性,如Transpose算子。 让我们为表达式y = Add(MatMul(X, Transpose(A))+ B)创建一个ONNX计算图。Transpose 需要一个定义了坐标轴排列顺序的属性:perm=[1, 0]。它在函数make_node 中作为命名属性添加进去。

make函数的整个列表如下,具体使用方法在make 函数中有介绍。

Opset 和元数据

让我们加载之前创建好的 ONNX文件,看看它有哪些元数据。

其中大部分是空的,因为在创建 ONNX计算图时没有填充,其中只有两个有数值的变量:

IR定义了 ONNX语言的版本。 Opset 定义了所用算子的版本。 ONNX 默认使用最新的版本。 也可以使用另一个版本。

算子Reshape的第 5 版将形状定义为输入,但是在第 1 版中却将属性定义为输入。opset定义了在描述计算图时所遵循的规范。

元数据可用于存储任何信息,如有关模型生成方式的信息、也可以用版本号区分不同的模型等。

字段training_info可用于存储其他计算图。 参见training_tool_test.py,了解其工作原理。

Functions

函数可以用来缩短构建模型的代码,并为runtime更快地运行预测提供更多可能性。如果没有使用函数,runtime只能使用基于现有的算子进行默认地实现。

无属性(attribute)的函数

这是一种简单的情况,函数的每个输入都是执行时已知的动态对象。

带有属性(attribute)的函数

下面的函数与前面的函数相同,只是将一个输入变量B转换成了一个名为bias的参数。 代码几乎相同,只是 bias 现在是一个常量。 在函数定义中,创建了一个节点Constant,用于将参数作为结果插入。它通过ref_attr_name 属性与参数相连。

解析(Parsing)

onnx 模块提供了一种定义计算图更快的方法,而且更易于阅读。如果计算图是在单个函数中构建的,那么使用起来就很容易。

这种方法常用于创建小型模型。

检查器和形状推理(Checker and Shape Inference)

onnx 提供了一个检查模型是否有效的函数。 该函数会检查输入变量的类型和维度是否一致。 下面的示例添加了两个不同类型的矩阵,这是不允许的。

check_model会由于这种类型不一致而引发错误。 但是对于没有指定域的自定义算子来说,check_model不会检查数据的类型和形状。

形状推理的目的只有一个:估计中间结果的形状和类型。 运行时就可以事先估计内存消耗,优化计算。它可以融合某些运算符,可以就地进行计算。

有一个新属性value_info,用于存储推断出的形状。dim_param中的字母I"I"可以看作一个变量。这取决于输入,但函数能够判断出哪些中间结果将共享相同的维度。 形状推理并非对所有算子都有效,例如,reshape算子。形状推理只有在形状恒定的情况下才会起作用。 如果形状不恒定,则无法推理出形状。

Evaluation and Runtime

ONNX标准允许框架以 ONNX格式导出模型,并允许使用任何支持ONNX格式的后端进行推理。它可用于多种平台,并针对快速推理进行了优化。ONNX实现了一个 Python runtime,有助于理解模型。 它不用于实际生产中,性能也不是它的目标。

评估线性回归模型

完整的 API 描述请参见onnx.reference。 它接收一个模型(一个ModelProto、一个文件名......)。 方法run返回字典中指定的一组输入的输出。

评估节点

评估器还可以评估一个简单的节点,以检查算子在特定输入时的表现。

类似的代码也适用于GraphProtoFunctionProto

Evaluation Step by Step

参数verbose会显示中间结果信息。

评估自定义的节点

下面的示例仍然实现了线性回归,但在矩阵A 中加入了单位矩阵

Y=X(A+I)+BY = X(A+I)+B

如果将运算符EyeLikeAdd合并为AddEyeLike,效率会更高。下一个示例将这两个运算符替换为"optimized"域中的一个运算符。

我们需要评估这个模型是否等同于第一个模型,这就需要对AddEyeLike这个特定节点进行实现。

预测结果是一样的。让我们在一个足够大的矩阵上比较一下性能,看看是否有显著差异。

在这种情况下,似乎值得添加一个优化节点。 这种优化通常被称为融合。 两个连续的算子被融合,成为一个经过优化后的算子。

Implementation details

Attributes and inputs

两者之间有明显的区别。输入是动态的,每次执行都可能发生变化。属性永远不会改变,优化器据此可以改进计算图。 因此,不可以将输入转化为属性。 而Constant是唯一能将属性转化为输入的算子。

Shape or no shape

在处理具有可变维度的深度学习模型时,如何在ONNX格式中有效地表示和处理这些模型,以便它们可以在不同的深度学习框架中使用?

Last updated

Was this helpful?