MXNet中的autograd 自动求导/梯度
想要跑程序可以参考这里
从例子出发,对于一个函数z = 2 * x * x,求z对x的导数
为了求有关变量x的梯度(即是对x求导),我们需要先调用attach_grad()函数来申请存储梯度所需要的内存。
import mxnet.ndarray as nd
import mxnet.autograd as ag #这两句也可以等价于from mxnet import autograd, nd
x = nd.array([[1,2],[3,4]]) #定义一个2*2的矩阵
x.attach_grad() #当我们求导时,需要一个地方来存x的导数
接下来要定义有关变量x的函数。为了减少计算和内存开销,默认条件下MXNet不会记录用于求梯度的计算。我们需要调用record()函数来要求MXNet记录与求梯度有关的计算,即用record()函数来记录需要求导的程序。
with ag.record(): #record()函数用来记录我们需要求导的程序
y = x*2
z = y*x # z = 2*x*x
z
[[ 2. 8.]
[18. 32.]]
<NDArray 2x2 @cpu(0)>
接下来我们可以通过调用backward()函数自动求梯度。需要注意的是,如果z不是一个标量,MXNet将默认先对z中元素求和得到新的变量,再求该变量有关x的梯度,即等价于nd.sum(z).backward()。(这里是对z求导,但z本身也是个矩阵,难道矩阵算作标量吗?这点不是很清楚。这里的结果好像是对z中每个元素进行求导。)
z.backward() #进行求导
x.grad #z对x的导数
[[ 4. 8.]
[12. 16.]]
<NDArray 2x2 @cpu(0)>
验证一波:
x.grad == 4*x #验证
[[1. 1.]
[1. 1.]]
<NDArray 2x2 @cpu(0)>
使用MXNet的一个便利之处是,即使函数的计算图包含了Python的控制流(如条件和循环控制),我们也有可能对变量求梯度。考虑下面程序,其中包含Python的条件和循环控制。需要强调的是,这里循环(while循环)迭代的次数和条件判断(if语句)的执行都取决于输入a的值。
def f(a):
b = a * 2
while b.norm().asscalar() < 1000: #norm()将b中的元素平方后求和,用asscalar()转换成标量
b = b * 2
if b.sum().asscalar() > 0:
c = b
else:
c = 100 * b
return c #可以看出,f(a) = x * a,x的值取决于输入a
我们像之前一样使用record()函数记录计算,并调用backward()函数求梯度。
a = nd.random.normal(0,1,shape=3) #a的每个元素都随机采样于均值为0、标准差为1的正态分布
a.attach_grad()
with ag.record():
c = f(a)
c.backward()
对于上面定义的f函数,不难看出,给定任意输入a,其输出必然是 f(a) = x * a的形式,其中标量系数x的值取决于输入a。由于c = f(a)有关a的导数为x,且值为c / a,我们可以像下面这样验证对本例中控制流求梯度的结果的正确性:
a.grad == c/a
[1. 1. 1.]
<NDArray 3 @cpu(0)>
总结:对谁求导,对谁用attach_grad()函数;with ag.record():后面跟需要求导的程序;backward()计算导数;grad查看结果。