自己动手学深度学习(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我们将开始回归方法的学习。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,830评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,992评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,875评论 0 331
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,837评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,734评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,091评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,550评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,217评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,368评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,298评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,350评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,027评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,623评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,706评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,940评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,349评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,936评论 2 341

推荐阅读更多精彩内容