Skip to content

Apache MXNet - Python API autograd 和 initializer

MXNet Python API:Autograd 和 Initializer

Section titled “MXNet Python API:Autograd 和 Initializer”

本章深入探讨 MXNet 的 autograd API 用于自动微分以及 initializer API 用于设置神经网络中的初始参数值。

mxnet.autograd 模块提供用于对 NDArray 进行自动微分的工具。

mxnet.autograd.Function 允许用户定义自定义的可微分操作。如果您需要 MXNet 内置运算符未覆盖的非标准梯度计算,您可以继承 Function 并实现自定义的 forward 和 backward 方法。

自定义 autograd 函数的工作流程:

  • 在 forward 方法中定义前向计算。此方法接受输入 NDArray 并返回输出 NDArray。
  • 在 backward 方法中实现自定义的梯度计算。此方法接收后续函数输出的梯度(通常表示为 dy 或 head_grads),并且必须返回相对于 forward 方法的每个输入的梯度。
  • 在反向传播期间,mxnet.autograd 将调用您的自定义 backward 方法,而不是依赖于预定义操作的链式法则。
  • 您可以在 forward 中使用 self.save_for_backward(*tensors) 缓存 backward 计算所需的任何张量(Tensors),这些张量可以通过 backward 中的 self.saved_tensors 检索。

示例:定义一个具有自定义前向和反向传播的稳定 sigmoid 函数。

import mxnet as mx
from mxnet import nd, autograd
class StableSigmoid(autograd.Function):
def forward(self, x):
# 数值稳定的 sigmoid: y = 1 / (1 + exp(-x))
y = 1 / (1 + nd.exp(-x))
self.save_for_backward(y) # 保存 y 以在反向传播中使用
return y
def backward(self, dy):
# sigmoid 的梯度: dy * y * (1 - y)
y, = self.saved_tensors # 检索保存的 y
return dy * y * (1 - y)
# 使用自定义函数
custom_sigmoid_op = StableSigmoid()
x_input = nd.random.uniform(shape=(5,))
x_input.attach_grad() # 标记 x_input 以计算其梯度
with autograd.record():
output_y = custom_sigmoid_op(x_input)
# 从一个虚拟头部梯度(例如,全为 1)反向传播
output_y.backward(nd.ones_like(output_y))
dx_gradient = x_input.grad
print("输入 x:")
print(x_input.asnumpy())
print("\n输出 y (sigmoid(x)):")
print(output_y.asnumpy())
print("\n梯度 dx (自定义):")
print(dx_gradient.asnumpy())

输出(值会因随机输入而异):

Input x:
[0.6288061 0.03209561 0.9043807 0.07945406 0.3247892 ]
Output y (sigmoid(x)):
[0.6521595 0.5080225 0.711851 0.5198517 0.58049846]
Gradient dx (custom):
[0.22686191 0.24993561 0.20499429 0.2496056 0.24351722]

mxnet.autograd 中常用的函数和上下文管理器:

方法/上下文管理器描述
tagNametds
tagNametds
tagNametds
tagNametds
tagNametds
tagNametds

使用 mxnet.autograd.grad() 直接计算梯度:

x = nd.ones((2,))
x.attach_grad() # 如果 x 是我们需要梯度的叶子变量,则需要为其附加梯度
with autograd.record():
# y = x * x
# z = y + x
# 我们来做类似于 z = exp(x) + x 的计算
z = nd.exp(x) + x
# 计算 dz/dx
dx_grads_list = autograd.grad(heads=z, variables=[x], create_graph=False)
dx_grad = dx_grads_list[0]
print("x:", x.asnumpy())
print("z:", z.asnumpy())
print("dz/dx:", dx_grad.asnumpy()) # 应该等于 exp(x) + 1。对于 x=1,e^1+1 ≈ 2.718+1 = 3.718

输出:

x: [1. 1.]
z: [3.7182817 3.7182817]
dz/dx: [3.7182817 3.7182817]

在推理(Inference)部分使用 predict_mode()(或在 record 或 pause 中使用 train_mode=False):

# model = 某个 Gluon 模型()
# x_input = nd.random.normal(shape=(1,10))
# with autograd.record(train_mode=True): # 训练模式,用于像 dropout/batchnorm 这样的部分
# intermediate_y = model.feature_extractor(x_input)
# with autograd.predict_mode(): # 推理模式,用于不需要梯度或特殊训练行为的部分
# final_output_logits = model.classifier(intermediate_y)
# # 或者如果只需要预测:
# # with autograd.predict_mode():
# # predictions = model(x_input)
# # loss.backward() # 如果损失在 record() 作用域内计算
print("predict_mode() 用于确保网络的部分在推理模式下运行。")

mxnet.initializer 模块(通常通过 mx.init 访问)提供了各种初始化神经网络层权重和偏置的方案。恰当的初始化对于有效训练至关重要。

关键的 Initializer 类及其用途:

Initializer 类描述
tagNametds
tagNametds
tagNametds
tagNametds
tagNametds
tagNametds
tagNametds
tagNametds
tagNametds

在定义 Gluon 层或模型时应用 Initializer:

from mxnet import gluon, init
# 示例 1:使用 Xavier 初始化 Dense 层
layer_xavier = gluon.nn.Dense(units=64, weight_initializer=init.Xavier(), bias_initializer=init.Zero())
layer_xavier.initialize() # 实际的初始化在这里发生
# print("Xavier 初始化后的权重示例:", layer_xavier.weight.data()[0,0:5].asnumpy())
# 示例 2:对 Sequential 模型使用 Mixed initializer
net = gluon.nn.Sequential()
with net.name_scope():
net.add(gluon.nn.Dense(256, activation='relu')) # 如果在 net.initialize 中未指定,将应用默认初始化
net.add(gluon.nn.Dense(10))
# 定义混合 Initializer:偏置为零,权重使用 Normal(0.02)
mixed_init = init.Mixed(
['.*bias', '.*gamma', '.*beta', '.*running_mean', '.*running_var'], # 偏置、Batchnorm 缩放/偏移、运行统计量的模式
[init.Zero(), init.One(), init.Zero(), init.Zero(), init.One()] # 对应的 Initializer
)
# 可以为其他权重(例如,'.*weight')指定回退或使用默认值。
# 为权重指定:net.initialize(init.Normal(0.02), ctx=mx.cpu())
# 对于混合:net.initialize(mixed_init, ctx=mx.cpu())
# 一个更常见的混合初始化模式:
net.initialize(init.Mixed(['.*weight', '.*bias'],
[init.Normal(0.02), init.Zero()]),
ctx=mx.cpu())
print("已初始化的 net[0].bias(Dense 层 0 的偏置):")
print(net[0].bias.data().asnumpy())
print("\nnet[0].weight(Dense 层 0 的权重)示例 - 第一行的前 5 个值:")
print(net[0].weight.data()[0,:5].asnumpy())

输出(偏置应为零,权重将是小的随机数):

Initialized net[0].bias (Dense layer 0 bias):
[0. 0. 0. ... 0. 0. 0.] (形状将是 (256,))
Sample of net[0].weight (Dense layer 0 weight) - first 5 values of first row:
[-0.00890991 0.01314114 -0.00038932 0.02028422 0.0158705 ]