《动手学习深度学习》

Published: 16 Sep 2019 Category: DL

访问https://zh.d2l.ai,获取本书的最新版本或正式版本。

一、写在前面

本书的代码基于Apache MXNet实现。MXNet是一个开源的深度学习框架。它是AWS(亚⻢逊云 计算服务)首选的深度学习框架,也被众多学校和公司使用。

目前的机器学习和深度学习应用共同的核心思想:我们可以 称其为“用数据编程”。

深度学习框架也在传播深度学习思想的过程中扮演了重要⻆色。Caffe、Torch和Theano这 样的第一代框架使建模变得更简单。许多开创性的论文都用到了这些框架。如今它们已经被TensorFlow(经常是以高层API Keras的形式被使用)、CNTK、Caffe2 和Apache MXNet所 取代。第三代,即命令式深度学习框架,是由用类似NumPy的语法来定义模型的Chainer所 开创的。这样的思想后来被PyTorch和MXNet的Gluon API 采用,后者也正是本书用来教学深度学习的工具。

深度学习的应用

  • 字体识别
  • 物体识别(皮肤癌诊断、鸟类识别、猫狗识别等)
  • 语言识别
  • 对话(苹果的siri,亚⻢逊的Alexa和谷歌助手等)
  • 博弈(下棋、星际争霸游戏等)
  • 自动驾驶
  • 自然语言处理

二、安装

2.1 启动环境

$ cd ~/Code/mime/d2l/d2l-zh
$ conda activate gluon
$ jupyter notebook

$ conda deactivate

访问 http://localhost:8888

2.2 尝试jupyter notebook

能写一行代码出一次结果,体验不错。

2.2.1 NDArray

NDArray是MXNet中存储和变换数据的主要工具。

修改向量形状:x.reshape((3,4))

由于x的元素个数是已知的12, 上面x.reshape((3, 4))也可写成x.reshape((-1, 4))或x.reshape((3, -1))。这里的-1是能够通过元素个数和其他维度的大小推断出来的。

创建各元素为1的张量: nd.ones((3, 4))

创建各元素为0的张量: nd.zeros((2, 3, 4))

随机生成NDArray中每个元素的值: nd.random.normal(0, 1, shape=(3, 4))

按元素做指数运算:X.exp()

使用dot函数做矩阵乘法: nd.dot(X, Y.T)

上面将X与Y的转置做矩阵乘法。由于X是3行4列的矩阵,Y转置为4行3列的矩阵,因此两个矩阵相乘得到3行3列的矩阵。

将多个NDArray连结(concatenate):nd.concat(X, Y, dim=0)
在行上(维度0,即形状中的最左边元素)连结两个矩阵。

nd.concat(X, Y, dim=1) 在列上(维度1,即形状中左起第二个元素)连结两个矩阵。

对NDArray中的所有元素求和得到只有一个元素的NDArray:X.sum()

使用条件判断式可以得到元素为0或1的新的NDArray。以X == Y为例,如果X和Y在相同位置的条件判断为真(值相等),那么新的NDArray在相同位置的值为1;反之为0。

可以通过asscalar函数将结果变换为Python中的标量: X.norm().asscalar()

也可以把Y.exp()、X.sum()、X.norm()等分别改写为nd.exp(Y)、nd.sum(X)、nd.norm(X)等。

2.2.2 广播机制

当对两个形状不同的NDArray按元素运算时,可能会触发广播(broadcasting)机制:先适当复制元素使这两个NDArray形状相同后再按元素运算。

定义两个NDArray:

In [19]:
    A = nd.arange(3).reshape((3, 1))
    B = nd.arange(2).reshape((1, 2))
    A, B
Out[19]:
    (
     [[0.]
      [1.]
      [2.]]
     <NDArray 3x1 @cpu(0)>,
     [[0. 1.]]
     <NDArray 1x2 @cpu(0)>)

In [20]:
    A + B
Out[20]:
    [[0. 1.]
     [1. 2.]
     [2. 3.]]

2.2.3 运算的内存开销

python知识 Python自带的id函数:如果两个实例的ID一致,那么它们所对应的内存地址相同;反之则不同。

In [24]:
before = id(Y)
Y = Y + X
id(Y) == before

Out[24]:
False

如果想指定结果到特定内存,我们可以使用前面介绍的索引来进行替换操作。在下面的例子中,我们先通过zeros_like创建和Y形状相同且元素为0的NDArray,记为Z。接下来,我们把X + Y的结果通过[:]写进Z对应的内存中。

In [25]:
Z = Y.zeros_like()
before = id(Z)
Z[:] = X + Y
id(Z) == before

Out[25]:
True

实际上,上例中我们还是为X + Y开了临时内存来存储计算结果,再复制到Z对应的内存。如果想避免这个临时内存开销,我们可以(1)使用运算符全名函数中的out参数

In [26]:
nd.elemwise_add(X, Y, out=Z)
id(Z) == before

Out[26]:
True

如果X的值在之后的程序中不会复用,我们也(2)可以用 X[:] = X + Y 或者 X += Y 来减少运算的内存开销。

In [27]:
before = id(X)
X += Y
id(X) == before

Out[27]:
True

上述(1)(2)可以减少内存开销。

2.3 自动求梯度 (未完)

hold,这里有些基础看不懂,跳到最后看看『数学基础』。

三、数学基础

3.1 运算

向量a和b的点乘(内积)是一个标量。

矩阵乘法不同,每个元素是原位置的行*列之和。一个m*p的矩阵和p*n的矩阵相乘,得到一个m*n的矩阵。

3.2 范数

向量的范数是各元素的绝对值的p次方之和的根号p的值。

例如,向量x的L1 范数是该向量元素绝对值之和。 向量x的L2 范数是该向量元素平方和的平方根。

我们通常用∥x∥指代∥x∥2 。

矩阵X的Frobenius范数为该矩阵元素平方和的平方根。

3.3 微分 && 偏导数

(看书上的公式)

3.4 梯度 && 海森矩阵

看不懂。。 为表示简洁,我们有时用∇f(x)代替∇xf(x)。

3.5 和数学不相关的基础

NVIDIA有面向个人用戶(如GTX系列)和企业用戶(如Tesla系列)的两类GPU。这两类GPU的 计算能力相当。然而,面向企业用戶的GPU通常使用被动散热并增加了显存校验,从而更适合数据中心,并通常要比面向个人用戶的GPU贵上10倍。

GPU的性能主要由以下3个参数构成。

  1. 计算能力。通常我们关心的是32位浮点计算能力。16位浮点训练也开始流行,如果只做预测的话也可以用8位整数。
  2. 显存大小。当模型越大或者训练时的批量越大时,所需要的显存就越多。
  3. 显存带宽。只有当显存带宽足够时才能充分发挥计算能力。

2.3 自动求梯度(继续)

attach_grad函数用来申请存储梯度所需要的内存。

2.4 查阅文档

当我们想知道一个模块里面提供了哪些可以调用的函数和类的时候,可以使用dir函数。下面我们打印nd.random模块中所有的成员或属性。

In [1]:
from mxnet import nd

print(dir(nd.random))
 
['NDArray', '_Null', '__all__', '__builtins__', '__cached__', '__doc__', '__file__', ... 'normal_like', 'numeric_types', 'poisson', 'poisson_like', 'randint', 'randn', 'shuffle', 'uniform', 'uniform_like']

通常我们可以忽略掉由__开头和结尾的函数(Python的特别对象)或者由_开头的函数(一般为内部函数)。通过其余成员的名字我们大致猜测出这个模块提供了各种随机数的生成方法,包括从均匀分布采样(uniform)、从正态分布采样(normal)、从泊松分布采样(poisson)等。

查找特定函数和类的使用:想了解某个函数或者类的具体用法时,可以使用help函数

help(nd.ones_like)