Apache MXNet - Python API Symbol
Apache MXNet - Python API: Symbol
Section titled “Apache MXNet - Python API: Symbol”本章介绍 mxnet.symbol API (通常导入为 mx.sym),它使得 Apache MXNet 能够进行符号式编程。符号式编程涉及首先定义一个计算图,然后可以对其进行编译、优化和执行。
理解 mxnet.symbol
Section titled “理解 mxnet.symbol”MXNet 的 Symbol API 允许您通过组合符号操作来构建计算图。与命令式编程(例如 NDArray,操作立即执行)不同,符号式编程将图定义与执行分离。
关键特性和优势:
- 计算图:符号代表图中的节点(操作或变量)。此图定义了数据和计算的流向。
- 优化:定义后,图可以由 MXNet 的引擎分析和优化(例如,内存优化、操作融合),然后再执行。
- 部署:符号图可以轻松地序列化并部署到各种环境(例如,移动设备、服务器),而无需定义它们的 Python 代码。
- 声明式特性:您声明要计算 什么,而不是如何一步步计算。
在现代 MXNet 中,特别是在使用 Gluon API 时,用户通常不会广泛地从头构建纯符号图。相反,他们使用 gluon.HybridBlock,它允许以命令式方式定义模型。然后,通过调用 .hybridize(),HybridBlock 的前向传播在内部被转换为符号图,以获得性能优势。然而,理解 mx.symbol 对于理解这种混合化如何工作以及用于高级用例至关重要。
简单的符号表达式 z = x + y 示例:
import mxnet as mx
# 创建符号变量(数据的占位符)x = mx.sym.Variable('x')y = mx.sym.Variable('y')
# 通过对 'x' 和 'y' 应用 '+' 操作符构建新的符号 'z'z = x + y
print(f"Symbol x: {x}")print(f"Symbol y: {y}")print(f"Symbol z (x+y): {z}")输出显示了符号的名称和 z 的操作:
<html> <body> <p>Symbol x: <Symbol x>Symbol y: <Symbol y>Symbol z (x+y): <Symbol _plus0> </p> </body></html>要执行此符号图,您通常会将其与实际数据 (NDArray) 和执行器绑定,或者更常见的是在 Gluon HybridBlock 中使用它。
核心类:Symbol
Section titled “核心类:Symbol”| 类 | 定义 |
|---|---|
| Symbol(handle) | MXNet 符号 API 中表示节点或整个计算图的主要类。它可以表示变量、常量或操作。 |
mx.symbol 中的常用函数和参数
Section titled “mx.symbol 中的常用函数和参数”mxnet.symbol 中的许多函数与 mxnet.ndarray 中的函数相似,但它们作用于 Symbol 对象来构建图。它们不立即执行计算,而是返回表示操作结果的新 Symbol 对象。
| 函数及参数 | 定义 |
|---|---|
| Activation(data, act_type=‘relu’, name=None, attr=None, out=None, **kwargs) | 创建一个应用激活函数的符号。data 是输入 Symbol。 |
| BatchNorm(data, gamma, beta, moving_mean, moving_var, eps=0.001, momentum=0.9, fix_gamma=False, use_global_stats=False, output_mean_var=False, axis=1, cudnn_off=False, min_calib_range=None, max_calib_range=None, name=None, attr=None, out=None, **kwargs) | 创建用于批标准化的符号。 |
| BilinearSampler(data, grid, cudnn_off=False, name=None, attr=None, out=None, **kwargs) | 创建用于双线性采样的符号。 |
| BlockGrad(data, name=None, attr=None, out=None, **kwargs) | 创建一个符号,该符号停止对其输入 data 符号进行梯度计算。 |
| cast(data, dtype, name=None, attr=None, out=None, **kwargs) | 创建一个符号,该符号将其输入 data 符号转换为新的 dtype。 |
| zeros(shape, dtype=‘float32’, ctx_group=None, **kwargs) | 创建一个符号,表示给定 shape 和 dtype 的零数组。 |
| ones(shape, dtype=‘float32’, ctx_group=None, **kwargs) | 创建一个符号,表示由一组成的数组。 |
| full(shape, val, dtype=‘float32’, ctx_group=None, **kwargs) | 创建一个符号,表示填充了标量 val 的数组。 |
| arange(start, stop=None, step=1.0, repeat=1, infer_range=True, dtype=‘float32’, ctx_group=None, **kwargs) | 创建一个符号,表示一系列等间距的值。 |
| linspace(start, stop, num, endpoint=True, name=None, attr=None, out=None, **kwargs) | 创建一个符号,表示在指定区间 [start, stop] 内的 num 个等间距数字。 |
| histogram(data, bins, range=None, name=None, attr=None, out=None, **kwargs) | 创建一个符号,该符号计算输入 data 符号的直方图。 |
| power(base, exp, name=None, attr=None, out=None, **kwargs) | 按元素求幂。base 和 exp 可以是 Symbols 或标量。 |
| softmax(data, axis=-1, temperature=None, dtype=None, name=None, attr=None, out=None, **kwargs) | 应用 softmax 函数,通常用作多类分类的输出激活。(注意:SoftmaxActivation 已过时;softmax 是首选)。 |
实现示例 (符号操作)
Section titled “实现示例 (符号操作)”符号式使用 mx.sym.power:
import mxnet as mximport numpy as np
# 标量幂(由 Python 在定义时计算,而不是符号操作)# print(mx.sym.power(3, 5)) # 这将导致错误,因为 mx.sym.power 期望符号,或者 base/exp 是符号
# 符号变量的幂x_sym = mx.sym.Variable('x_data')y_sym = mx.sym.Variable('y_data')
# x^3 的符号z_pow_x_cubed = mx.sym.power(x_sym, 3)# 4^y 的符号z_pow_4_y = mx.sym.power(4, y_sym) # 标量底数,符号指数# x^y 的符号z_pow_x_y = mx.sym.power(x_sym, y_sym)
print(f"Symbol for x^3: {z_pow_x_cubed}")print(f"Symbol for 4^y: {z_pow_4_y}")print(f"Symbol for x^y: {z_pow_x_y}")
# 要执行,绑定数据并使用执行器(或在 Gluon HybridBlock 中使用)# 评估示例(旧式风格,仅用于说明绑定)x_nd = mx.nd.array([1, 2, 3], ctx=mx.cpu())executor_x_cubed = z_pow_x_cubed.simple_bind(ctx=mx.cpu(), x_data=x_nd.shape)output_x_cubed = executor_x_cubed.forward(x_data=x_nd)[0]print(f"x={x_nd.asnumpy()} 的 x^3 结果:{output_x_cubed.asnumpy()}")输出:
<html> <body> <p>Symbol for x^3: <Symbol _power_scalar0>Symbol for 4^y: <Symbol _rpower_scalar0>Symbol for x^y: <Symbol _power0>Result of x^3 for x=[1. 2. 3.]: [ 1. 8. 27.] </p> </body></html>使用 mx.sym.softmax(优于 SoftmaxActivation)的示例:
data_sym = mx.sym.Variable('input_data')softmax_output_sym = mx.sym.softmax(data_sym, axis=1) # 对每一行的列应用 Softmaxprint(f"Softmax 符号: {softmax_output_sym}")
# 评估示例input_nd = mx.nd.array([[2., 0.9, -0.5, 4., 8.], [4., -.7, 9., 2., 0.9]], ctx=mx.cpu())
# 使用 simple_bind 进行评估(说明性示例)# 实际上,Gluon 在其框架内处理执行。executor_softmax = softmax_output_sym.simple_bind(ctx=mx.cpu(), input_data=input_nd.shape)output_softmax = executor_softmax.forward(input_data=input_nd)[0]print(f"Softmax 输出:\n{output_softmax.asnumpy()}")输出:
<html> <body> <p>Softmax symbol: <Symbol softmax0>Softmax output:[[2.4258138e-03 8.0748333e-04 1.9912292e-04 1.7924475e-02 9.7864312e-01] [6.6843745e-03 6.0796250e-05 9.9204916e-01 9.0463174e-04 3.0112563e-04]] </p> </body></html>symbol.contrib:实验性符号功能
Section titled “symbol.contrib:实验性符号功能”与 ndarray.contrib 类似,mxnet.symbol.contrib 提供实验性符号操作。这些操作可能会在未来的 MXNet 版本中更改或移除。
symbol.contrib 中的精选函数
Section titled “symbol.contrib 中的精选函数”| 函数及参数 | 定义 |
|---|---|
| rand_zipfian(true_classes, num_sampled, range_max, name=None, attr=None, out=None, **kwargs) | rand_zipfian 的符号版本,用于在计算图中从近似的 Zipfian 分布中抽取样本。 |
| foreach(body, data, init_states, name=None) | foreach 的符号版本,用于在图中定义类似循环的计算。 |
| while_loop(cond, func, loop_vars, max_iterations=None, name=None) | while_loop 的符号版本,用于在图中定义条件迭代计算。 |
| cond(pred, then_branch, else_branch, name=None) | cond 的符号版本,用于在图中定义条件分支。then_branch 和 else_branch 是返回 Symbols 的函数。 |
| getnnz(data, axis=None, name=None, attr=None, out=None, **kwargs) | 计算稀疏张量 Symbol 中非零元素的符号操作符。 |
| requantize(data, min_range, max_range, min_calib_range=None, max_calib_range=None, out_type=‘int8’, name=None, attr=None, out=None, **kwargs) | 用于数据重新量化的符号操作符,通常用于量化图推理。 |
| index_copy(old_tensor, index_vector, new_tensor, name=None, attr=None, out=None, **kwargs) | 符号式地将元素从 new_tensor 复制到 old_tensor 的指定 index_vector 位置。 |
| interleaved_matmul_encdec_qk(queries, keys, num_heads, name=None, attr=None, out=None, **kwargs) | 计算多头注意力(encoder-decoder)中 query-key 投影的矩阵乘法。这是 Transformer 模型的一种专用操作。 |
实现示例 (symbol.contrib)
Section titled “实现示例 (symbol.contrib)”符号式使用 mx.sym.contrib.rand_zipfian:
from mxnet.symbol import contrib as contrib_sym
true_cls_sym = mx.sym.Variable('true_cls_data')num_sampled = 5range_max = 10
zipfian_group = contrib_sym.rand_zipfian(true_cls_sym, num_sampled, range_max)# rand_zipfian 返回一组符号:samples, expected_count_true, expected_count_samplesamples_sym = zipfian_group[0]
print(f"Zipfian 样本的符号: {samples_sym}")
# 评估示例true_cls_nd = mx.nd.array([3, 1], ctx=mx.cpu())# 对于分组符号,绑定每个元素或使用处理分组的包装器。# 为了简单起见,这里仅展示对 'samples' 部分的绑定。# 注意:正确绑定分组符号可能更复杂。executor_zipfian = samples_sym.simple_bind(ctx=mx.cpu(), true_cls_data=true_cls_nd.shape)output_zipfian = executor_zipfian.forward(true_cls_data=true_cls_nd)[0]print(f"评估的 Zipfian 样本(会因运行而异):\n{output_zipfian.asnumpy()}")输出(实际样本会因运行而异):
<html> <body> <p>Symbol for Zipfian samples: <Symbol rand_zipfian0_output0>Evaluated Zipfian samples (will vary):[[0 1 2 4 5] [0 2 3 4 5]]<NDArray 2x5 @cpu(0)> </p> </body></html>symbol.image:符号式图像操作
Section titled “symbol.image:符号式图像操作”mxnet.symbol.image 模块提供图像处理函数的符号版本,类似于 mxnet.ndarray.image。这些函数用于在计算图内定义图像增强和预处理步骤,这有利于性能,因为这些操作可以由图执行器进行融合或优化。
symbol.image 中的精选函数
Section titled “symbol.image 中的精选函数”提供了诸如 adjust_lighting、crop、normalize、random_crop、random_lighting、random_resized_crop、resize、to_tensor 等函数,它们存在于 mx.sym.image 中,其行为与其在 mx.nd.image 中的对应函数类似,但它们作用于 Symbols 来构建图。
实现示例 (symbol.image)
Section titled “实现示例 (symbol.image)”img_h, img_w, img_c = 224, 224, 3
# 图像批量(许多图像操作采用 N H W C 格式)的符号占位符image_data_sym = mx.sym.Variable('image_batch_data', shape=(None, img_h, img_w, img_c))
# 符号式 to_tensor (HWC -> CHW, 缩放 0-255 到 0-1)# 注意:symbol API 中的 to_tensor 可能根据特定版本/操作符期望 NCHW 或 NHWC 格式。# 对于此示例,假设它处理 NHWC -> NCHW 并进行缩放。tensor_sym = mx.sym.image.to_tensor(image_data_sym)
# 符号式标准化mean_rgb = (0.485*255, 0.456*255, 0.406*255) # 0-255 范围内数据的均值std_rgb = (0.229*255, 0.224*255, 0.225*255) # 0-255 范围内数据的标准差# to_tensor 后,数据范围是 0-1,因此均值/标准差应针对 0-1 范围。normalized_sym = mx.sym.image.normalize(tensor_sym, mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225))
print(f"标准化图像数据的符号: {normalized_sym}")
# 这个 `normalized_sym` 随后可以输入到符号式 CNN 定义中。# 为了评估,需要将其与实际图像数据绑定。# dummy_image_batch_nd = mx.nd.random.uniform(0, 255, (1, img_h, img_w, img_c), ctx=mx.cpu())# exec_norm = normalized_sym.simple_bind(ctx=mx.cpu(), image_batch_data=dummy_image_batch_nd.shape)# output_norm = exec_norm.forward(image_batch_data=dummy_image_batch_nd)[0]# print(f"标准化输出的形状:{output_norm.shape}") # 期望形状:(批量大小, 序列长度, 输出维度)输出:
<html> <body> <p>Symbol for normalized image data: <Symbol normalize0> </p> </body></html>symbol.random:符号式随机数生成
Section titled “symbol.random:符号式随机数生成”mxnet.symbol.random 模块提供了创建 Symbols 的函数,这些 Symbols 在执行时会生成随机数。这对于向模型添加随机性(例如 dropout)或在非常特定的场景下符号式初始化参数很有用(尽管通常参数初始化为 NDArray)。
symbol.random 中的精选函数
Section titled “symbol.random 中的精选函数”提供了诸如 uniform、normal、randn、poisson、exponential、gamma、multinomial、negative_binomial、generalized_negative_binomial、shuffle、randint 以及各种 *_like 函数。它们接受形状参数和分布参数,返回表示随机采样操作的 Symbol。
实现示例 (symbol.random)
Section titled “实现示例 (symbol.random)”# 符号式随机均匀生成rand_unif_sym = mx.sym.random.uniform(low=-1, high=1, shape=(2, 3))print(f"均匀随机数的符号: {rand_unif_sym}")
# 变量的符号式打乱data_to_shuffle = mx.sym.Variable('shuffle_input')shuffled_data_sym = mx.sym.random.shuffle(data_to_shuffle)print(f"打乱数据的符号: {shuffled_data_sym}")
# 打乱操作的评估示例input_nd_for_shuffle = mx.nd.array([[1,2,3],[4,5,6],[7,8,9]], ctx=mx.cpu())exec_shuffle = shuffled_data_sym.simple_bind(ctx=mx.cpu(), shuffle_input=input_nd_for_shuffle.shape)output_shuffled = exec_shuffle.forward(shuffle_input=input_nd_for_shuffle)[0]print(f"原始数据:\n{input_nd_for_shuffle.asnumpy()}")print(f"打乱的数据(会因运行而异):\n{output_shuffled.asnumpy()}")输出(打乱的数据会因运行而异):
<html> <body> <p>Symbol for uniform randoms: <Symbol random_uniform0>Symbol for shuffled data: <Symbol random_shuffle0>Original data:[[1. 2. 3.] [4. 5. 6.] [7. 8. 9.]]Shuffled data (will vary):[[4. 5. 6.] [7. 8. 9.] [1. 2. 3.]] </p> </body></html>symbol.sparse:符号式稀疏操作
Section titled “symbol.sparse:符号式稀疏操作”MXNet 通过 mxnet.symbol.sparse(以及相应的 mxnet.ndarray.sparse 和 mxnet.numpy.sparse 模块)支持稀疏矩阵的操作。这对于处理高维稀疏数据的应用程序至关重要,例如推荐系统或具有大词汇表的自然语言处理。
symbol.sparse 中的精选函数
Section titled “symbol.sparse 中的精选函数”稀疏 API 提供了用于常见任务的符号操作符:
| 函数及参数 | 定义 |
|---|---|
| ElementWiseSum(*args, **kwargs) | 符号式地按元素相加多个输入的稀疏或稠密 Symbols。比重复相加更高效。 |
| Embedding(data, weight, input_dim, output_dim, dtype=‘float32’, sparse_grad=False, name=None, attr=None, out=None, **kwargs) | 创建 Embedding 层 Symbol。使用 weight 矩阵(即 Embedding 矩阵)将整数索引(通常来自稀疏输入 data)映射到稠密向量表示。sparse_grad=True 为权重启用稀疏梯度更新,这对于大型 Embedding 非常节省内存。 |
| LinearRegressionOutput(data, label, grad_scale=1.0, name=None, attr=None, out=None, **kwargs) | 计算线性回归的平方损失。data 是预测值,label 是目标值。通常用作符号图中回归任务的最终层。 |
| LogisticRegressionOutput(data, label, grad_scale=1.0, name=None, attr=None, out=None, **kwargs) | 应用 sigmoid 函数并计算逻辑回归损失。用于二分类。 |
| MAERegressionOutput(data, label, grad_scale=1.0, name=None, attr=None, out=None, **kwargs) | 计算平均绝对误差 (Mean Absolute Error) 损失。 |
| abs(data, name=None, attr=None, out=None, **kwargs) | 稀疏/稠密 Symbols 的按元素绝对值。 |
| dot(lhs, rhs, transpose_a=False, transpose_b=False, name=None, attr=None, out=None, **kwargs) | 计算两个 Symbols 的点积,它们可以是稀疏或稠密的。支持对稀疏输入进行优化的矩阵乘法。 |
| elemwise_add/sub/mul/div(lhs, rhs, name=None, attr=None, out=None, **kwargs) | 支持稀疏 Symbols 的按元素算术运算。 |
| exp, sin, sign, sigmoid, etc. | 适用于稀疏/稠密 Symbols 的各种数学函数。 |
| Optimizer-specific update functions (e.g., adagrad_update, adam_update, sgd_update) | 优化器更新规则的符号表示,在处理稀疏梯度时常由训练机制内部使用。 |
实现示例 (symbol.sparse Embedding)
Section titled “实现示例 (symbol.sparse Embedding)”定义符号式 Embedding 层:
input_dim = 10000 # 词汇表大小output_dim = 100 # Embedding 维度batch_size = 4sequence_length = 10
# 符号输入数据(例如,整数词索引)# 形状:(批量大小, 序列长度) 或其他合适的形状input_indices_sym = mx.sym.Variable('input_indices', shape=(batch_size, sequence_length))
# 符号式 Embedding 权重矩阵embedding_weight_sym = mx.sym.Variable('embedding_weight', shape=(input_dim, output_dim))
# 创建 Embedding 符号embedded_features_sym = mx.sym.Embedding(data=input_indices_sym, weight=embedding_weight_sym, input_dim=input_dim, output_dim=output_dim, sparse_grad=False) # 如果 input_dim 非常大,设置为 True 以进行稀疏更新
print(f"嵌入特征的符号: {embedded_features_sym}")
# 要使用它,需要将其与实际的 NDArray 数据(用于索引和权重)绑定。# e.g., input_indices_nd = mx.nd.random.randint(0, input_dim, (batch_size, sequence_length), ctx=mx.cpu())# embedding_weight_nd = mx.nd.random.uniform(-0.01, 0.01, (input_dim, output_dim), ctx=mx.cpu())
# executor = embedded_features_sym.simple_bind(ctx=mx.cpu(), \# input_indices=input_indices_nd.shape, \# embedding_weight=embedding_weight_nd.shape)# output_embedded = executor.forward(is_train=False, \# input_indices=input_indices_nd, \# embedding_weight=embedding_weight_nd)[0]# print(f"嵌入输出的形状:{output_embedded.shape}") # 期望形状:(批量大小, 序列长度, 输出维度)这个符号式 embedded_features_sym 随后将连接到神经网络定义中的后续层(例如 RNN、Transformer 模型)。