NDArray与NumPy的多维数组类似,但NDArray提供了更多的功能:GPU和CPU的异步计算;自动求导。这使得NDArray能更好地支持机器学习。
初始化
from mxnet import ndarray as ndnd.zeros((3,4))nd.ones((3,4))nd.array([[1,2],[3,4]])out:[[1. 2.][3. 4.]]nd.random_normal(0,1,shape=(3,4)) #标准正态分布# 输出信息y.shapey.size
操作符
按照相应元素运算
x+yx*ynd.exp(x)
矩阵的乘法
nd.dot(x, y.T)
广播(Beoadcasting)
当二元操作符左右两边ndarray形状不一样时,系统会尝试将它们扩充到共同的形状。
a=nd.arange(3).reshape((3,1))b=nd.arange(2),reshape((1,2))print ('a+b', a+b)out:a+b:[[ 0. 1.] [ 1. 2.] [ 2. 3.]]
与NumPy的转换
x=np.ones((2,3))y=nd.array(x) # numpy->mxnetz=y.asnumpy() # mxnet->numpy
替换操作
如果我们写y=x+y,会开辟新的内存来存储计算结果,如:
x=nd.ones((3,4))y=nd.ones((3,4))before = id(y)y=y+xid(y)==before # False
可以通过[:]写到之间建立好的数组中
z=nd.zeros_like(y)before=id(z)z[:]=x+yid(z)==before # True
上述,系统还是为x+y创建了临时空间,再复制给了z。为了避免这个开销,可以使用操作符的全名版本并指定out参数
nd.elemwise_add(x,y,out=z)id(z)==before # True
截取(Slicing)
x=nd.arange(0,9).reshape((3,3))x[1:3]out:[[ 3. 4. 5.] [ 6. 7. 8.]]#改变指定位置的值x[1,2]=9.#多维截取x[1:2,1:3]#多维写入x[1:2,1:3]=9.out:[[ 0. 1. 2.] [ 3. 9. 9.] [ 6. 7. 8.]]
使用autograd自动求导
import mxnet.ndarray as ndimport mxnet.autograd as ag
为变量附上梯度
x=nd.array([[1,2],[3,4]])x.attach_grad() # ndarray的方法申请相应的空间# 定义函数f=2x*x,显式要求mxnet记录我们要求导的程序with ag.record(): y=x*2 z=y*x# 通过z.backword()来进行求导,如果z不是一个标量,z.backward()等价于nd.sum(z).backward().z.backward()print('x.grad: ',x.grad)x.grad == 4*x# outputx.grad:[[4., 8.] [12., 16.]][[ 1. 1.] [ 1. 1.]]
对控制流求导
命令式的编程的一个便利之处是几乎可以对任意的可导程序进行求导,即使里面包含了 Python 的 控制流。对于计算图框架来说,这个对应于动态图,就是图的结构会根据输入数据不同而改变。
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()
头梯度和链式法则
基于链式法则:
\[\frac{dz}{dx} = \frac{dz}{dy}\frac{dy}{dx}\]\(\frac{dz}{dy}\)就是\(\frac{dy}{dx}\)的头梯度,而计算\(\frac{dz}{dy}\),头梯度则为默认值,即nd.ones_like(y)。我们也可以手动指定头梯度。with ag.record(): y = x * 2 z = y * xhead_gradient = nd.array([[10, 1.], [.1, .01]]) z.backward(head_gradient) print(x.grad)# outx=[[1.,2.] [3.,4.]]x.grad=[[40., 8.] [12., 0.16]]