本文旨在记录阅读本书过程中遇到的在以往学习和使用中忽略的知识点,也包括一些需要加强记忆的重点。
此外,原书中主要基于深度学习框架mxnet,但官方网站和对应代码中也提供了pytorch对应的版本,本文主要基于pytorch对应部分进行汇总和整理。(持续更新中)
2.2.5 运算的内存开销
运行一些操作可能会导致为新结果分配内存。
例如,如果我们用Y = X + Y
,我们将取消引用Y
指向的张量,而是指向新分配的内存处的张量。
在下面的例子中,我们用Python的id()
函数演示了这一点,
它给我们提供了内存中引用对象的确切地址。
运行Y = Y + X
后,我们会发现id(Y)
指向另一个位置。
这是因为Python首先计算Y + X
,为结果分配新的内存,然后使Y
指向内存中的这个新位置。
before = id(Y)
Y = Y + X
id(Y) == before # False
这可能是不可取的,原因有两个:
- 首先,我们不想总是不必要地分配内存。在机器学习中,我们可能有数百兆的参数,并且在一秒内多次更新所有参数。通常情况下,我们希望原地执行这些更新;
- 如果我们不原地更新,其他引用仍然会指向旧的内存位置,这样我们的某些代码可能会无意中引用旧的参数。
幸运的是,执行原地操作非常简单。我们可以使用切片表示法将操作的结果分配给先前分配的数组,例如Y[:] = <expression>
。
如果在后续计算中没有重复使用X
,
我们也可以使用X[:] = X + Y
或X += Y
来减少操作的内存开销。
before = id(X)
X += Y
id(X) == before # True
2.3.10 范数
线性代数中最有用的一些运算符是范数(norm)。
非正式地说,向量的范数是表示一个向量有多大。
这里考虑的大小(size)概念不涉及维度,而是分量的大小。
在线性代数中,向量范数是将向量映射到标量的函数
f
f
f。
给定任意向量
x
\mathbf{x}
x,向量范数要满足一些属性。
第一个性质是:如果我们按常数因子
α
\alpha
α缩放向量的所有元素,
其范数也会按相同常数因子的绝对值缩放:
f ( α x ) = ∣ α ∣ f ( x ) . f(\alpha \mathbf{x}) = |\alpha| f(\mathbf{x}). f(αx)=∣α∣f(x).
第二个性质是熟悉的三角不等式:
f ( x + y ) ≤ f ( x ) + f ( y ) . f(\mathbf{x} + \mathbf{y}) \leq f(\mathbf{x}) + f(\mathbf{y}). f(x+y)≤f(x)+f(y).
第三个性质简单地说范数必须是非负的:
f ( x ) ≥ 0. f(\mathbf{x}) \geq 0. f(x)≥0.
最后一个性质要求范数最小为0,当且仅当向量全由0组成。
∀ i , [ x ] i = 0 ⇔ f ( x ) = 0. \forall i, [\mathbf{x}]_i = 0 \Leftrightarrow f(\mathbf{x})=0. ∀i,[x]i=0⇔f(x)=0.
范数听起来很像距离的度量。
欧几里得距离和毕达哥拉斯定理中的非负性概念和三角不等式可能会给出一些启发。事实上,欧几里得距离是一个
L
2
L_2
L2范数:
假设
n
n
n维向量
x
\mathbf{x}
x中的元素是
x
1
,
…
,
x
n
x_1,\ldots,x_n
x1,…,xn,其**
L
2
L_2
L2范数是向量元素平方和的平方根:
∥
x
∥
2
=
∑
i
=
1
n
x
i
2
,
\|\mathbf{x}\|_2 = \sqrt{\sum_{i=1}^n x_i^2},
∥x∥2=i=1∑nxi2,
其中,在
L
2
L_2
L2范数中常常省略下标
2
2
2,也就是说
∥
x
∥
\|\mathbf{x}\|
∥x∥等同于
∥
x
∥
2
\|\mathbf{x}\|_2
∥x∥2。在代码中,我们可以按如下方式计算向量的
L
2
L_2
L2范数。
u = torch.tensor([3.0, -4.0])
torch.norm(u)
# tensor(5.)
深度学习中更经常地使用
L
2
L_2
L2范数的平方,也会经常遇到
L
1
L_1
L1范数,它表示为向量元素的绝对值之和:
∥
x
∥
1
=
∑
i
=
1
n
∣
x
i
∣
.
\|\mathbf{x}\|_1 = \sum_{i=1}^n \left|x_i \right|.
∥x∥1=i=1∑n∣xi∣.
与 L 2 L_2 L2范数相比, L 1 L_1 L1范数受异常值的影响较小。为了计算 L 1 L_1 L1范数,我们将绝对值函数和按元素求和组合起来。
torch.abs(u).sum()
# tensor(7.)
L 2 L_2 L2范数和 L 1 L_1 L1范数都是更一般的 L p L_p Lp范数的特例:
∥ x ∥ p = ( ∑ i = 1 n ∣ x i ∣ p ) 1 / p . \|\mathbf{x}\|_p = \left(\sum_{i=1}^n \left|x_i \right|^p \right)^{1/p}. ∥x∥p=(i=1∑n∣xi∣p)1/p.
类似于向量的 L 2 L_2 L2范数,矩阵 X ∈ R m × n \mathbf{X} \in \mathbb{R}^{m \times n} X∈Rm×n(的Frobenius范数(Frobenius norm)是矩阵元素平方和的平方根:)
∥ X ∥ F = ∑ i = 1 m ∑ j = 1 n x i j 2 . \|\mathbf{X}\|_F = \sqrt{\sum_{i=1}^m \sum_{j=1}^n x_{ij}^2}. ∥X∥F=i=1∑mj=1∑nxij2.
Frobenius范数满足向量范数的所有性质,它就像是矩阵形向量的
L
2
L_2
L2范数。
调用以下函数将计算矩阵的Frobenius范数。
torch.norm(torch.ones((4, 9)))
# tensor(6.)
范数和目标
在深度学习中,我们经常试图解决优化问题:最大化分配给观测数据的概率;
最小化预测和真实观测之间的距离。用向量表示物品(如单词、产品或新闻文章),以便最小化相似项目之间的距离,最大化不同项目之间的距离。目标,或许是深度学习算法最重要的组成部分(除了数据),通常被表达为范数。
2.5 自动微分
求导是几乎所有深度学习优化算法的关键步骤。虽然求导的计算很简单,只需要一些基本的微积分。但对于复杂的模型,手工进行更新是一件很痛苦的事情(而且经常容易出错)。深度学习框架通过自动计算导数,即自动微分(automatic differentiation)来加快求导。实际中,根据设计好的模型,系统会构建一个计算图(computational graph),来跟踪计算是哪些数据通过哪些操作组合起来产生输出。自动微分使系统能够随后反向传播梯度。这里,反向传播(backpropagate)意味着跟踪整个计算图,填充关于每个参数的偏导数。
2.7 查阅文档
查找模块中的所有函数和类。
为了知道模块中可以调用哪些函数和类,可以调用dir
函数。例如,我们可以查询随机数生成模块中的所有属性:
import torch
print(dir(torch.distributions))
通常可以忽略以“_ ”(双下划线)开始和结束的函数,它们是python中的特殊对象,或以单个“”(单下划线)开始的函数,它们通常是内部函数。根据剩余的函数名或属性名,大概可以猜测到各方法的用途。
有关如何使用给定函数或类的更具体说明,可以调用help函数。例如:
help(torch.ones)
在jupyter记事本中,可以使用?
指令在另一个浏览器窗口中显示文档。例如,list?
指令将创建和help(list)指令几乎相同的内容,并在新的浏览器窗口中显示它。此外,如果我们使用两个问号,如list??
,将显示实现该函数的python代码。