2.3. 自动求梯度
在深度学习中,我们经常需要对函数求梯度(gradient)。本节将介绍如何使用MXNet提供的autograd模块来自动求梯度。
通过下面的代码导入autograd模块
from mxnet import autograd
2.3.1. 一个简单的例子
我们先来看一个简单的例子,如何使用MXNet来求解 y = 2 x T x y=2x^T x y=2xTx这个函数关于 x x x的梯度的方法。
print('简单例子')
x=nd.arange(4).reshape((4,1))#创建一个4*1的向量,元素值为从0到3
print(x)
# 1. 为了求有关变量x的梯度,我们需要先调用attach_grad函数来申请存储梯度所需要的内存。
x.attach_grad()
# 2. 我们需要调用record函数来要求MXNet记录与求梯度有关的计算。
with autograd.record():
y=2*nd.dot(x.T,x)#定义有关变量x的函数。
# 3. 通过调用backward函数自动求梯度。当y不是一个标量,MXNet将默认先对y中元素求和得到新变量,再求该变量有关x的梯度。
y.backward()
assert (x.grad-4*x).norm().asscalar()==0 #判断函数y关于x的梯度是否正确
# 4. 输出梯度
print(x.grad)
输出:
简单例子
[[0.]
[1.]
[2.]
[3.]]
<NDArray 4x1 @cpu(0)>
[[ 0.]
[ 4.]
[ 8.]
[12.]]
<NDArray 4x1 @cpu(0)>
在上面的例子中,仅定义y=2*nd.dot(x.T,x)这个表达式不会记录用于求梯度的计算,这是为了减少计算和内存开销。通过调用record函数来要求MXNet记录与求梯度有关的计算。
2.4.2. 训练模式和预测模式
从上面可以看出,在调用record函数后,MXNet会记录并计算梯度。此外,默认情况下autograd还会将运行模式从预测模式转为训练模式。这可以通过调用is_training函数来查看。
print('训练模式和预测模式')
print(autograd.is_training())
with autograd.record():
print(autograd.is_training())
在有些情况下,同一个模型在训练模式和预测模式下的行为并不相同。
2.4.3. 对python控制流求梯度
使用MXNet的一个便利之处是,即使函数的计算图包含了Python的控制流(如条件和循环控制),我们也有可能对变量求梯度。
本节将提供一个包含python条件和循环控制的函数,输入值为a,输出为c。我们将对a求解梯度。
print('对Python控制流求梯度')
# 定义一个函数,待会将对这个函数求关于a的梯度
def f(a):
b=a*2
while b.norm().asscalar()<1000:
b=b*2
if b.sum().asscalar()>0:
c=b
else:
c=100*b
return c
a=nd.random.normal(shape=1)
# 1. 为了求有关变量a的梯度,我们需要先调用attach_grad函数来申请存储梯度所需要的内存。
a.attach_grad()
# 2. 我们需要调用record函数来要求MXNet记录与求梯度有关的计算。
with autograd.record():
c=f(a)
# 3. 通过调用backward函数自动求梯度。
c.backward()
# 4. 验证梯度求解的是否正确
print(a.grad==c/a)
输出:
对Python控制流求梯度
[1.]
<NDArray 1 @cpu(0)>
如何判断梯度正确性:事实上,给定任意输入a,其输出必然是 f(a) = x * a的形式,其中标量系数x的值取决于输入a。由于c = f(a)有关a的梯度为x,且值为c / a,我们可以像下面这样验证对本例中控制流求梯度的结果的正确性。
练习
-
在本节对控制流求梯度的例子中,把变量a改成一个随机向量或矩阵。此时计算结果c不再是标量,运行结果将有何变化?该如何分析该结果?
print('练习1') a=nd.random.normal(shape=2) print(a) a.attach_grad() with autograd.record(): c=f(a) c.backward() print(a.grad) print(c)
输出:
练习1 [0.4838046 0.29956347] <NDArray 2 @cpu(0)> [2048. 2048.] <NDArray 2 @cpu(0)> [990.83185 613.506 ] <NDArray 2 @cpu(0)>
从上面可以发现,将变量a改为一个随机的包含两个元素的向量。此时计算结果c也为包含两个元素的向量,得到a的梯度也为包含两个元素的向量。运算的结果的判断方式还是a.grad==c/a,这里的c/a为对应元素相除。
-
重新设计一个对控制流求梯度的例子。运行并分析结果。