2. 预备知识
2.1 数据操作
import torch
:导入PyTorchx = torch.arange(12)
:创建一个行向量x
,这个行向量包含从0开始的前12个整数x.shape
:查看张量的形状(维度)x.numel()
:查看张量的元素总数(给出各维度相乘的一个值)x = x.reshape((2,4))
:改变张量的形状而不改变元素数量和元素值torch.zeros((2,3,4))
:创建一个形状为(2,3,4)
的全零向量torch.ones((2,3,4))
:创建一个形状为(2,3,4)
的全一向量torch.randn(3, 4)
:创建一个形状为(3, 4)
的张量,元素值服从均值为0,标准差为1的标准高斯分布的随机采样torch.tensor([1, 3, 4])
:根据list
列表创建一个张量- 运算符:加(
+
)减(-
)乘(*
)除(/
)幂(**
),指数(torch.exp(x)
) torch.cat((X, Y), dim=0)
:在沿轴-0
连接张量X == Y
:按每个位置进行逻辑运算X.sum()
:对张量中的所有元素进行求和,产生一个单元素的张量- 索引和切片:X[-1], X[1:3],X[0:2, :]
A = X.numpy()
:将张量转换为Numpy的ndarray类型B = torch.tensor(A)
:将Numpy的数组类型转换成张量Y = Y + X
会为Y
重新分配一个地址,可以采用以下方法避免不必要地分配内存以减少内存开销:Z[:] = X + Y
X += Y
2.2 数据预处理
os.makedirs()
:创建文件夹。exist_true
: 在目标目录已存在的情况下,不会引发错误而是忽略该错误。pd.read_csv(data_file)
:采用pandas
包读取csv
文件NaN
:即not a number
代表缺失值,为了处理缺失的数据,典型的方法包括插值法和删除法。df.iloc[ : , 0:2]
:对pandas
的DataFrame
数据结构进行位置索引pd.get_dummies(data, dummy_na=True)
:将DataFrame
中分类变量(字符串型)转换称虚拟变量(数值型)
2.3 线性代数
len(x)
:查看张量x
的长度A.T
:矩阵A
的转置B = A.clone()
:创建张量A
的副本B
。A与B具有相同的数据和属性,二者相互独立(有各自的内存地址),修改其中一个不会影响另一个。A.sum(axis=0)
:沿x
轴求和并降低维度,可以通过设置keepdims=True
使得计算总和或均值时保持轴数不变A.mean()
:求张量平均值A.cumsum(axis=0)
:沿轴-0
计算张量A
的累积总和,对于一个tensor,从左到右从外到内依次是轴-0
、轴-1
、轴-2
以此类推。torch.dot(x, y)
:向量点积torch.mv(A, x)
:矩阵A
和向量x
的矩阵-向量积torch.mm(A, B)
:矩阵乘法
范数:范数是一种用来度量向量或矩阵的大小的函数。在数学中,范数是对向量空间中的元素进行度量的一种方式,它将向量的长度或大小映射到非负实数上。范数被称为"范数"是因为它们满足一些特定的性质,这些性质在拉丁语中被称为"norma"(规范、标准)。
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
len():对于 Tensor
类型, len()
函数的输出是该张量的第一个维度的大小(即张量的长度)。如果要获取张量的完整形状信息,可以使用 tensor.size()
或 tensor.shape
属性。
2.4 微积分
假设有一个函数
f
:
R
→
R
f: \mathbb{R} \rightarrow \mathbb{R}
f:R→R,其输入输出都是标量。如果
f
f
f 的导数存在,这个极限被定义为:
f
′
(
x
)
=
lim
h
→
0
f
(
x
+
h
)
−
f
(
x
)
h
f^{\prime}(x)=\lim _{h \rightarrow 0} \frac{f(x+h)-f(x)}{h}
f′(x)=h→0limhf(x+h)−f(x)
导数可以被解释为函数相对于其变量的瞬时变化率,它也是函数曲线的切线的斜率。
f-string:python 3.6 引入的一种格式化字符串的方法, f-string
在形式上以 f
或 F
修饰符引领字符串,形如: f'xxx,{}:.3'
,其中 {}
中为被替换的字段(变量), :
后指定被替换的字段的格式,例如 :.3
代表保留3位浮点数。
微分法则(假设函数 f f f 和 g g g 都是可微的, C C C 是一个常数):
- 常数相乘法则: d d x [ C f ( x ) ] = C d d x f ( x ) \dfrac{d}{d x}[C f(x)]=C \dfrac{d}{d x} f(x) dxd[Cf(x)]=Cdxdf(x)
- 加法法则: d d x [ f ( x ) + g ( x ) ] = d d x f ( x ) + d d x g ( x ) \dfrac{d}{d x}[f(x)+g(x)]=\dfrac{d}{d x} f(x)+\dfrac{d}{d x} g(x) dxd[f(x)+g(x)]=dxdf(x)+dxdg(x) ==> ( u + v ) ′ = u ′ + v ′ (u+v)'=u'+v' (u+v)′=u′+v′
- 乘法法则: d d x [ f ( x ) g ( x ) ] = f ( x ) d d x [ g ( x ) ] + g ( x ) d d x [ f ( x ) ] \dfrac{d}{d x}[f(x) g(x)]=f(x) \dfrac{d}{d x}[g(x)]+g(x) \dfrac{d}{d x}[f(x)] dxd[f(x)g(x)]=f(x)dxd[g(x)]+g(x)dxd[f(x)] ==> ( u ⋅ v ) ′ = u ⋅ v ′ + v ⋅ u ′ (u·v)'=u·v'+v·u' (u⋅v)′=u⋅v′+v⋅u′
- 除法法则: d d x [ f ( x ) g ( x ) ] = g ( x ) d d x [ f ( x ) ] − f ( x ) d d x [ g ( x ) ] [ g ( x ) ] 2 \dfrac{d}{d x}\left[\dfrac{f(x)}{g(x)}\right]=\dfrac{g(x) \dfrac{d}{d x}[f(x)]-f(x) \dfrac{d}{d x}[g(x)]}{[g(x)]^{2}} dxd[g(x)f(x)]=[g(x)]2g(x)dxd[f(x)]−f(x)dxd[g(x)] ==> ( u v ) ′ = v ⋅ u ′ − u ⋅ v ′ v 2 (\dfrac{u}{v})'=\dfrac{v·u'-u·v'}{v^2} (vu)′=v2v⋅u′−u⋅v′
赋值条件语句:语法形如: 变量 = 真值部分 if 条件 else 假值部分
,如果条件为真,则变量将被赋值为真值部分;如果条件为假,则变量将被赋值为假值部分。
plt.gca():gca:get current axes,通过调用 plt.gca()
用于获取当前的坐标轴(Axes)对象。
# 如果X有一个轴,输出True
def has_one_axis(X):
return (hasattr(X, "ndim") and X.ndim == 1 or isinstance(X, list)
and not hasattr(X[0], "__len__"))
hasattr(X, "ndim")
:检查对象X
是否具有ndim
属性。ndim
属性通常用于表示对象的维度(轴的数量)。- (
isinstance(X, list)
):判断X
是否为列表。isinstance()
是一个 Python 内置函数,用于检查一个对象是否属于指定的类型。isinstance(object, classinfo)
接受两个参数:object
:要检查的对象。classinfo
:可以是一个类型对象(如int
、str
)或一个类型元组(如(int, float)
)
- 列表具有一个
__len__
属性,即代表列表第一维的长度。如果X
是一个一维列表,X[0]
将是一个标量,即不存在__len__
属性 axes.cla()
:清除坐标轴上的当前图形。
梯度(gradient):我们可以连结一个多元函数对其所有变量的偏导数,以得到该函数的梯度 (gradient) 向量。具体而言,设函数
f
:
R
n
→
R
f: \mathbb{R}^{n} \rightarrow \mathbb{R}
f:Rn→R 的输入是一个
n
n
n 维向量
x
=
[
x
1
,
x
2
,
…
,
x
n
]
⊤
\mathbf{x}=\left[x_{1}, x_{2}, \ldots, x_{n}\right]^{\top}
x=[x1,x2,…,xn]⊤,并且输出是一个标量。函数
f
(
x
)
f(\mathbf{x})
f(x) 相对于
x
\mathbf{x}
x 的梯度是一个包含
n
n
n 个偏导数的向量:
∇
x
f
(
x
)
=
[
∂
f
(
x
)
∂
x
1
,
∂
f
(
x
)
∂
x
2
,
…
,
∂
f
(
x
)
∂
x
n
]
⊤
\nabla_{\mathbf{x}} f(\mathbf{x})=\left[\frac{\partial f(\mathbf{x})}{\partial x_{1}}, \frac{\partial f(\mathbf{x})}{\partial x_{2}}, \ldots, \frac{\partial f(\mathbf{x})}{\partial x_{n}}\right]^{\top}
∇xf(x)=[∂x1∂f(x),∂x2∂f(x),…,∂xn∂f(x)]⊤
其中
∇
x
f
(
x
)
\nabla_{\mathbf{x}} f(\mathbf{x})
∇xf(x) 通常在没有歧义时被
∇
f
(
x
)
\nabla f(\mathbf{x})
∇f(x) 取代。
链式法则: d y d x = d y d u d u d x \dfrac{d y}{d x}=\dfrac{d y}{d u} \dfrac{d u}{d x} dxdy=dudydxdu
对于函数具有任意数量的情况,假设可微分函数
y
y
y 有变量
u
1
,
u
2
,
…
,
u
m
u_{1}, u_{2}, \ldots, u_{m}
u1,u2,…,um,其中每个 可微分函数
u
i
u_{i}
ui 都有变量
x
1
,
x
2
,
…
,
x
n
x_{1}, x_{2}, \ldots, x_{n}
x1,x2,…,xn 。注意,
y
y
y 是
x
1
,
x
2
,
…
,
x
n
x_{1}, x_{2}, \ldots, x_{n}
x1,x2,…,xn 的函数。对于任意
i
=
1
,
2
,
…
,
n
i=1,2, \ldots, n
i=1,2,…,n,链式法则给出:
∂
y
∂
x
i
=
∂
y
∂
u
1
∂
u
1
∂
x
i
+
∂
y
∂
u
2
∂
u
2
∂
x
i
+
⋯
+
∂
y
∂
u
m
∂
u
m
∂
x
i
\dfrac{\partial y}{\partial x_{i}}=\dfrac{\partial y}{\partial u_{1}} \dfrac{\partial u_{1}}{\partial x_{i}}+\frac{\partial y}{\partial u_{2}} \dfrac{\partial u_{2}}{\partial x_{i}}+\cdots+\frac{\partial y}{\partial u_{m}} \dfrac{\partial u_{m}}{\partial x_{i}}
∂xi∂y=∂u1∂y∂xi∂u1+∂u2∂y∂xi∂u2+⋯+∂um∂y∂xi∂um
常见函数的导数(梯度):(其中 x \mathbf{x} x 为 n n n 维向量)
- 对于所有 A ∈ R m × n \mathbf{A} \in \mathbb{R}^{m \times n} A∈Rm×n,都有 ∇ x A x = A ⊤ \nabla_{\mathbf{x}} \mathbf{A} \mathbf{x}=\mathbf{A}^{\top} ∇xAx=A⊤
- 对于所有 A ∈ R n × m \mathbf{A} \in \mathbb{R}^{n \times m} A∈Rn×m,都有 ∇ x x ⊤ A = A \nabla_{\mathbf{x}} \mathbf{x}^{\top} \mathbf{A}=\mathbf{A} ∇xx⊤A=A
- 对于所有 A ∈ R n × n \mathbf{A} \in \mathbb{R}^{n \times n} A∈Rn×n,都有 ∇ x x ⊤ A x = ( A + A ⊤ ) x \nabla_{\mathbf{x}} \mathbf{x}^{\top} \mathbf{A} \mathbf{x}=\left(\mathbf{A}+\mathbf{A}^{\top}\right) \mathbf{x} ∇xx⊤Ax=(A+A⊤)x
- ∇ x ∥ x ∥ 2 = ∇ x x ⊤ x = 2 x \nabla_{\mathbf{x}}\|\mathbf{x}\|^{2}=\nabla_{\mathbf{x}} \mathbf{x}^{\top} \mathbf{x}=2 \mathbf{x} ∇x∥x∥2=∇xx⊤x=2x
2.5 自动求导
深度学习框架通过自动计算导数,即自动微分(automatic differentiation)来加快求导。 实际中,根据设计好的模型,系统会构建一个计算图(computational graph), 来跟踪计算是哪些数据通过哪些操作组合起来产生输出。 自动微分使系统能够随后反向传播梯度。 这里,反向传播(backpropagate)意味着跟踪整个计算图,填充关于每个参数的偏导数。
自动求导的两种模式:正向累积和反向累积
- 链式法则: ∂ y ∂ x = ∂ y ∂ u n ∂ u n ∂ u n − 1 … ∂ u 2 ∂ u 1 ∂ u 1 ∂ x \dfrac{\partial y}{\partial x}=\dfrac{\partial y}{\partial u_{n}} \dfrac{\partial u_{n}}{\partial u_{n-1}} \ldots \dfrac{\partial u_{2}}{\partial u_{1}} \dfrac{\partial u_{1}}{\partial x} ∂x∂y=∂un∂y∂un−1∂un…∂u1∂u2∂x∂u1
- 正向累积: ∂ y ∂ x = ∂ y ∂ u n ( ∂ u n ∂ u n − 1 ( … ( ∂ u 2 ∂ u 1 ∂ u 1 ∂ x ) ) ) \dfrac{\partial y}{\partial x}=\dfrac{\partial y}{\partial u_{n}}\left(\dfrac{\partial u_{n}}{\partial u_{n-1}}\left(\ldots\left(\dfrac{\partial u_{2}}{\partial u_{1}} \dfrac{\partial u_{1}}{\partial x}\right)\right)\right) ∂x∂y=∂un∂y(∂un−1∂un(…(∂u1∂u2∂x∂u1)))
- 反向累积(又称反向传递): ∂ y ∂ x = ( ( ( ∂ y ∂ u n ∂ u n ∂ u n − 1 ) … ) ∂ u 2 ∂ u 1 ) ∂ u 1 ∂ x \dfrac{\partial y}{\partial x}=\left(\left(\left(\dfrac{\partial y}{\partial u_{n}} \dfrac{\partial u_{n}}{\partial u_{n-1}}\right) \ldots\right) \dfrac{\partial u_{2}}{\partial u_{1}}\right) \dfrac{\partial u_{1}}{\partial x} ∂x∂y=(((∂un∂y∂un−1∂un)…)∂u1∂u2)∂x∂u1
反向累积计算梯度的过程示例(首先需要执行一次正向计算存储中间结果,接下来执行反向计算):
前向:执行计算图,存储中间结果(例如 b b b、 a a a 的值 )
反向:从相反的方向执行图,利用正向计算的中间结果以及链式法则,直接得出梯度值。
反向累积复杂度
- 计算复杂度: O ( n ) O(n) O(n) , n n n 是操作子个数,通常正向与反向的代价类似
- 内存复杂度: O ( n ) O(n) O(n) ,因为需要存储正向的所有中间结果。(因此深度神经网络特别消耗GPU资源)
正向累积复杂度
- 计算复杂度:计算一个变量的梯度的计算复杂度为 O ( n ) O(n) O(n) ,每一层的梯度都需要单独计算
- 内存复杂度: O ( 1 ) O(1) O(1),即不需要存储中间结果。
控制流梯度计算
使用自动微分的一个好处是: 即使构建函数的计算图需要通过Python控制流(例如,条件、循环或任意函数调用),我们仍然可以计算得到的变量的梯度。
相关代码
x.requires_grad_(True)
:用于指示是否需要计算梯度,当True
时,PyTorch 会自动跟踪该张量的所有操作。y.backward()
:计算一个标量(scalar)相对于计算图中所有具有requires_grad=True
的张量的梯度,并将结果存储在相应张量的grad
属性中。例如可以用x.grad
获取张量x
的梯度值。当调用backward()
方法之后,计算图中的梯度信息会被清除(此时若再一次执行y.backward()
将会报错)。我们可以通过设置retain_graph=True
参数来保留计算图的梯度信息,例如y.backward(retain_graph=True)
。y.sum()
:计算张量中的所有元素进行求和,产生一个单元素的张量(scalar)。因为y.backward()
只有在y
是标量(scalar)时才能够正常执行,因此,若y
是一个向量,通常需要进行y.sum()
操作。 我们通常一次会计算一批样本的loss,但是,我们并不需要计算微分矩阵,而是单独计算批量中每个样本的偏导数之和。即假设有样本 $ S_1, S_2 ,S_3 $ ,网络中间变量为 x 1 , x 2 , x 3 x_1,x_2,x_3 x1,x2,x3 ,可得到每一个样本相对应的 loss 为 y 1 , y 2 , y 3 y_1,y_2,y_3 y1,y2,y3 ,最终我们计算的变量 x 1 x_1 x1 的梯度信息将是 ∂ ( y 1 + y 2 + y 3 ) ∂ x 1 = ∂ y 1 + ∂ y 2 + ∂ y 3 ∂ x 1 \dfrac{\partial{(y_1+y_2+y_3)}}{\partial{x_1}} = \dfrac{\partial{y_1}+\partial{y_2}+\partial{y_3}}{\partial{x_1}} ∂x1∂(y1+y2+y3)=∂x1∂y1+∂y2+∂y3x.grad.zero_()
:由于 PyTorch 中张量的梯度是依次累加的,故有时需要手动清除梯度信息。u = y.detach()
:有时我们需要将张量 y y y 视为一个常数,此时需要从计算图中将该张量分离出来。即用u = y.detach()
实现,将 y y y 分离出来返回一个新的变量 u u u ,该变量 u u u 具有与 y y y 相同的值,但丢弃计算图中如何计算 y y y 的任何信息(不再与计算图相关联)。
2.6 概率
联合概率(joint probability): P ( A = a , B = b ) P(A=a, B=b) P(A=a,B=b) A = a A=a A=a 和 B = b B=b B=b 同时发生的概率。
条件概率(conditional probability): P ( B = b ∣ A = a ) P(B=b \mid A=a) P(B=b∣A=a)在 A = a A=a A=a 发生的前提下, B = b B=b B=b 发生的概率。
贝叶斯定理(Bayes’ theorem): P ( A , B ) = P ( B ∣ A ) P ( A ) = P ( A ∣ B ) P ( B ) P(A, B)=P(B \mid A) P(A) = P(A \mid B) P(B) P(A,B)=P(B∣A)P(A)=P(A∣B)P(B) P ( A ∣ B ) = P ( B ∣ A ) P ( A ) P ( B ) P(A \mid B)=\dfrac{P(B \mid A) P(A)}{P(B)} P(A∣B)=P(B)P(B∣A)P(A)
边际化: P ( B ) = ∑ A P ( A , B ) P(B)=\sum_{A} P(A, B) P(B)=A∑P(A,B) B的概率相当于在 A A A 的所有可能下,他们的联合概率之和。边际化结果的概率或分布称为边际概率(marginal probability) 或边际分布(marginal distribution)
独立与依赖: A ⊥ B A \perp B A⊥B 表示两个随机变量 A A A 和 B B B 是独立的,意味着事件 A A A 的发生跟 B B B 事件的发生无关。
2.7 查阅文档
dir()
:获取对象的属性列表(包括函数名)help()
:获取对象的帮助文档