自己动手学深度学习(P3)NDArray处理数据与autograd求导

参考网站:http://zh.gluon.ai/

使用NDArray来处理数据

对于机器学习来说,处理数据往往是万事之开头。它包含两个部分:数据读取和当数据已经在内存里时如何处理。本章将关注后者。我们首先介绍NDArray,这是MXNet储存和变化数据的主要工具。如果你之前用过NumPy,你会发现NDArrayNumPy的多维数组非常类似。当然,NDArray提供更多的功能,首先是CPU和GPU的异步计算,其次是自动求导。这两点是的NDArray能更好地支持机器学习。

让我们开始

我们先介绍最基本的功能。不用担心如果你不懂我们用到的数学操作,例如按元素加法,或者正态分布,我们会在之后的章节分别详细介绍。

我们首先从mxnet导入ndarray这个包

from mxnet import ndarray as nd

然后我们创建一个有3行和4列的2D数组(通常也叫矩阵),并且把每个元素初始化成0

nd.zeros((3, 4))

[[ 0.  0.  0.  0.]
 [ 0.  0.  0.  0.]
 [ 0.  0.  0.  0.]]

类似的,我们可以创建数组每个元素被初始化成1。

x = nd.ones((3, 4))
x

[[ 1.  1.  1.  1.]
 [ 1.  1.  1.  1.]
 [ 1.  1.  1.  1.]]

或者从python的数组直接构造

nd.array([[1,2],[2,3]])

[[ 1.  2.]
 [ 2.  3.]]

我们经常需要创建随机数组,就是说每个元素的值都是随机采样而来,这个经常被用来初始化模型参数。下面创建数组,它的元素服从均值0方差1的正态分布。

y = nd.random_normal(0, 1, shape=(3, 4))
y

[[ 2.21220636  1.16307867  0.7740038   0.48380461]
 [ 1.04344046  0.29956347  1.18392551  0.15302546]
 [ 1.89171135 -1.16881478 -1.23474145  1.55807114]]

NumPy一样,每个数组的形状可以通过.shape来获取

y.shape

(3L, 4L)

它的大小,就是总元素个数,是形状的累乘。

y.size

12L

操作符

NDArray支持大量的数学操作符,例如按元素加法:

x + y

[[ 3.21220636  2.16307878  1.77400374  1.48380458]
 [ 2.04344034  1.29956341  2.18392563  1.15302551]
 [ 2.89171124 -0.16881478 -0.23474145  2.55807114]]

乘法:

x * y

[[ 2.21220636  1.16307867  0.7740038   0.48380461]
 [ 1.04344046  0.29956347  1.18392551  0.15302546]
 [ 1.89171135 -1.16881478 -1.23474145  1.55807114]]

指数运算:

nd.exp(y)

[[ 9.13585091  3.19976926  2.16843081  1.6222347 ]
 [ 2.83896756  1.34926963  3.26717448  1.16535461]
 [ 6.63070631  0.31073502  0.29090998  4.74965096]]

也可以转秩一个矩阵然后计算矩阵乘法:

nd.dot(x, y.T)

[[ 4.63309383  2.67995477  1.04622626]
 [ 4.63309383  2.67995477  1.04622626]
 [ 4.63309383  2.67995477  1.04622626]]

广播

当二元操作符左右两边ndarray形状不一样时,系统会尝试将其复制到一个共同的形状。例如a的第0维是3, b的第0维是1,那么a+b时会将b沿着第0维复制3遍:

a = nd.arange(3).reshape((3,1))
b = nd.arange(2).reshape((1,2))
print('a:', a)
print('b:', b)
print('a+b:', a+b)

a:
[[ 0.]
 [ 1.]
 [ 2.]]
b:
[[ 0.  1.]]
a+b:
[[ 0.  1.]
 [ 1.  2.]
 [ 2.  3.]]

跟NumPy的转换

ndarray可以很方便同numpy进行转换

import numpy as np
x = np.ones((2,3))
y = nd.array(x) # numpy -> mxnet
z = y.asnumpy() # mxnet -> numpy
print([z, y])

[array([[ 1.,  1.,  1.],
       [ 1.,  1.,  1.]], dtype=float32),
[[ 1.  1.  1.]
 [ 1.  1.  1.]]

替换操作

在前面的样例中,我们为每个操作新开内存来存储它的结果。例如,如果我们写y = x + y, 我们会把y从现在指向的实例转到新建的实例上去。我们可以用Python的id()函数来看这个是怎么执行的:

x = nd.ones((3, 4))
y = nd.ones((3, 4))

before = id(y)
y = y + x
id(y) == before

False

我们可以吧结果通过[:]写到一个之前开好的数组里:

z = nd.zeros_like(x)
before = id(z)
z[:] = x + y
id(z) == before

True

但是这里我们还是为x+y创建了临时空间,然后在复制到z. 需要避免这个开销,我们可以使用操作符的全名版本中的out参数:

nd.elemwise_add(x, y, out=z)
id(z) == before

True

如果可以现有的数组之后不会再用,我们也可以用复制操作符达到这个目的

before = id(x)
x += y
id(x) == before

True

使用autograd来自动求导

在机器学习中,我们通常使用梯度下降来更新模型参数来求解。损失函数关于模型参数的梯度指向一个可以降低损失函数值的方向,我们不断的沿着梯度的方向更新模型从而最小化损失函数。虽然梯度计算比较直观,但对于复杂的模型,例如多达数十层的神经网络,手动计算梯度非常困难。

为此MXNet提供autograd包来自动化求导过程。虽然大部分的深度学习框架要求编译计算图来自动求导,mxnet.autograd可以对正常的命令式程序进行求导,它每次在后端实时创建计算图从而可以立即得到梯度的计算方法。

下面让我们一步步介绍这个包。我们先导入autograd.

import mxnet.ndarray as nd
import mxnet.autograd as ag

为变量附上梯度

假设我们想对函数f = 2 * (x ^ 2)求关于x的导数。我们先创建变量x,并赋初值。

x = nd.array([[1, 2], [3, 4]])

当进行求导的时候,我们需要一个地方来存x的导数,这个可以通过NDArray的方法attach_grad()来要求系统申请对应的空间。

x.attach_grad()

下面定义f。默认MXNet不会自动记录和构建用于求导的计算图,我们需要使用autograd里的record()函数来显式的要求MXNet记录我们需要求导的程序。

with ag.record():
    y = x * 2
    z = y * x

接下来我们可以通过z.backward()来进行求导。如果z不是一个标量,那么z.backward()等价于nd.sum(z).backward().

z.backward()

现在我们来看求出来的导数是不是正确的。注意到y = x * 2z = x * y,所以z等价于2 * x * x。它的导数那么就是dz/dx = 4 * x

x.grad == 4*x

[[ 1.  1.]
 [ 1.  1.]]

对控制流求导

命令式的编程的一个便利之处是几乎可以对任意的可导程序进行求导,即使里面包含了Python的控制流。考虑下面程序,里面包含控制流forif,但循环迭代的次数和判断语句的执行都是取决于输入的值。不同的输入会导致这个程序的执行不一样。(对于计算图框架来说,这个对应于动态图,就是图的结构会根据输入数据不同而改变)。

def f(a):
    b = a * 2
    while nd.norm(b).asscalar() < 1000:
        b = b * 2
    if nd.sum(b).asscalar() > 0:
        c = b
    else:
        c = 100 * b
    return c

我们可以跟之前一样使用record记录和backward求导。

a = nd.random_normal(shape=3)
a.attach_grad()
with ag.record():
    c = f(a)
c.backward()

注意到给定输入a,其输出f(a)=xax的值取决于输入a。所以有df/da = x,我们可以很简单地评估自动求导的导数:

a.grad == c/a

[ 1.  1.  1.]

到这里,我们已经熟悉了数据的处理和求导的初步使用,下一Part我们将开始回归方法的学习。

你可能感兴趣的:(自己动手学深度学习(P3)NDArray处理数据与autograd求导)