动手学深度学习1--预备知识

动手学深度学习1--预备知识
山河忽晚学习内容
- 深度学习基础——线性神经网络,多层感知机
- 卷积神经网络——LeNet,AlexNet,VGG,Inception,ResNet
- 循环神经网络——RNN,GRU,LSTM,seq2seq
- 注意力机制——Attention,Transformer
- 优化算法——SGD,Momentum,Adam
- 高性能计算——并行,多GPU,分布式
- 计算机视觉——目标检测,语义分割
- 自然语言处理——词嵌入,BERT
数据操作 + 预处理
N 维数组是机器学习和神经网络的主要数据结构。
- 0-d(标量)——
1.0
- 表示一个类别
- 1-d(向量)——
[1.0, 2.7, 3.4]
- 一个特征向量
- 2-d(矩阵)——
[[1.0,2.7,3.4], [5.0,0.2,4.6], [4.3,8.5,0.2]]
- 一个样本的特征矩阵,每一行表示一个样本,每一列表示样本的一个特征
- 3-d
——
[[[0.1,2.7,3.4], [5.0,0.2,4.6], [4.3,8.5,(0.2]], [[3.2,5.7,3.4], [5.4,6.2,3.2], [4.1,3.5,6.2]]]
- RGB图片(宽 x 高 x 通道)
- 宽是列的个数(有多少列),高是行的个数(有多少行),通道channel包括RGB三通道
- 4-d ——
[[[[....]]]]
- 一个RGB图片批量(批量大小 x 宽 x 高 x 通道)
- 批量大小:batch_size
- 5-d ——
[[[[[....]]]]]
- 一个视频批量(批量大小 x 时间 x 宽 x 高 x 通道)
数组操作
操作原理
创建数组需要:
- 形状:例如 3 x 4 矩阵
- 每个元素的数据类型:例如32位浮点数
- 每个元素的值,例如全是0,或者随机数
访问元素:
创建数组
导入torch
1 | import torch |
张量表示一个由数值组成的数组,这个数组可能有多个维度
1 | x = torch.arange(12) |
tensor([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
可以通过张量的shape
属性来访问张量(沿每个轴的长度)的形状和张量中元素的总数
1 | x.shape |
torch.Size([12]) # 只有一个维度,这个维度的长度是12
1 | x.numel() # x中元素的种类数 |
12 # 结果为一个标量数据值
要想改变一个张量的形状而不改变元素数量和元素值,可以调用reshape
函数
1 | X = x.reshape(3, 4) # 改为3行4列赋值给X |
tensor([[ 0, 1, 2, 3], [ 4, 5, 6, 7], [ 8, 9, 10, 11]])
使用全0、全1、其他常量,或者从特定分布中随机采样的数字
1 | torch.zeros((2, 3, 4)) # 生成全0元素 |
通过提供包含数值的Python列表(或嵌套列表),来为所需张量中的每个元素赋予确定值
1 | torch.tensor([[2, 1, 4, 3], [1, 2, 3, 4], [4, 3, 2, 1]]) |
tensor([[2, 1, 4, 3], [1, 2, 3, 4], [4, 3, 2, 1]])
数组运算
常见的标准算术运算符(+
、-
、*
、/
和**
)都可以被升级为按元素运算
1 | x = torch.tensor([1.0, 2, 4, 8]) |
tensor([ 3., 4., 6., 10.]),
tensor([-1., 0., 2., 6.]),
tensor([ 2., 4., 8., 16.]),
tensor([0.5000, 1.0000, 2.0000, 4.0000]),
tensor([ 1., 4., 16., 64.]))
“按元素”方式可以应用更多的计算
1 | torch.exp(x) |
tensor([2.7183e+00, 7.3891e+00, 5.4598e+01, 2.9810e+03])
我们也可以把多个张量连结(concatenate)在一起
1 | X = torch.arange(12, dtype=torch.float32).reshape((3,4)) |
(tensor([[ 0., 1., 2., 3.], [ 4., 5., 6., 7.], [ 8., 9., 10., 11.], [ 2., 1., 4., 3.], [ 1., 2., 3., 4.], [ 4., 3., 2., 1.]]), tensor([[ 0., 1., 2., 3., 2., 1., 4., 3.], [ 4., 5., 6., 7., 1., 2., 3., 4.], [ 8., 9., 10., 11., 4., 3., 2., 1.]]))
通过逻辑运算符构建二元张量
1 | X == Y |
tensor([[False, True, False, True], [False, False, False, False], [False, False, False, False]])
对张量中的所有元素进行求和,会产生一个单元素张量
1 | X.sum() |
tensor(66.)
数组广播
即使形状不同,我们仍然可以通过调用广播机制(broadcasting mechanism)来执行按元素操作
1 | a = torch.arange(3).reshape((3, 1)) |
(tensor([[0], [1], [2]]),
tensor([[0, 1]]))
1 | a + b # 广播机制自动复制自身 |
[[0, 0], [[0, 1], [[0, 1], [1, 1], + [0, 1], = [1, 2], [2, 2]] [0, 1]] [2, 3],
可以用[-1]
选择最后一个元素,可以用[1:3]
选择第二个和第三个元素
1 | X[-1], X[1:3] |
(tensor([ 8., 9., 10., 11.]), tensor([[ 4., 5., 6., 7.], [ 8., 9., 10., 11.]]))
除读取外,我们还可以通过指定索引来将元素写入矩阵
1 | X[1, 2] = 9 |
tensor([[ 0., 1., 2., 3.], [ 4., 5., 9., 7.], [ 8., 9., 10., 11.]])
为多个元素赋值相同的值,我们只需要索引所有元素,然后为它们赋值
1 | X[0:2, :] = 12 |
tensor([[12., 12., 12., 12.], [12., 12., 12., 12.], [ 8., 9., 10., 11.]])
内存分配
运行一些操作可能会导致为新结果分配内存
1 | before = id(Y) |
False
执行原地操作
1 | Z = torch.zeros_like(Y) |
id(Z): 140327634811696 id(Z): 140327634811696
如果在后续计算中没有重复使用X
,我们也可以使用X[:] = X + Y
或X += Y
来减少操作的内存开销
1 | before = id(X) |
True
转换为NumPy张量(ndarray
)
1 | A = X.numpy() |
(numpy.ndarray, torch.Tensor)
将大小为1的张量转换为Python标量
1 | a = torch.tensor([3.5]) |
(tensor([3.5000]), 3.5, 3.5, 3)
数据预处理
是指如果我有一个原始数据,我怎么把它读取进来,使得我们通过机器学习的方法能够处理。
创建数据集
创建一个人工数据集,并存储在CSV(逗号分隔值)文件
1 | import os |
读取数据
从创建的CSV文件中加载原始数据集
一般读取CSV文件使用pandas库
1 | import pandas as pd |
NumRooms Alley Price 0 NaN Pave 127500 1 2.0 NaN 106000 2 4.0 NaN 178100 3 NaN NaN 140000
处理数据
为了处理缺失的数据,典型的方法包括插值法和删除法,
这里,我们将考虑插值法
1 | inputs, outputs = data.iloc[:, 0:2], data.iloc[:, 2] |
NumRooms Alley 0 3.0 Pave 1 2.0 NaN 2 4.0 NaN 3 3.0 NaN
对于inputs
中的类别值或离散值,我们将“NaN”视为一个类别(Not
a Number)
对于字符型数值,最好转变为数值类型
1 | # 把列里面所有出现的不同种类的值都变成一个特征 |
NumRooms Alley_Pave Alley_nan 0 3.0 1 0 1 2.0 0 1 2 4.0 0 1 3 3.0 0 1
现在inputs
和outputs
中的所有条目都是数值类型,它们可以转换为张量格式
1 | import torch |
(tensor([[3., 1., 0.], [2., 0., 1.], [4., 0., 1.], [3., 0., 1.]], dtype=torch.float64), tensor([127500., 106000., 178100., 140000.], dtype=torch.float64))
传统python计算会用32位或64位浮点数,但是64位浮点数对深度学习来讲计算比较慢,一般指定数据类型为32位浮点数。
线性代数
理论基础
标量—-一个值
简单操作
- c = a + b
- c = a ⋅ b
- c = sin a
长度
$$ |a| = \begin{cases} a & \text{if } a > 0 \\ -a & \text{otherwise} \end{cases} $$
|a + b| ≤ |a|+|b|
|a ⋅ b| = |a|⋅|b|
向量 —-一行值
向量的简单操作
- $ c = a + b c_i = a_i + b_i $
- $ c = b c_i = b_i $
- $ c = a c_i = a_i $
- $ c = a + b c_i = a_i + b_i $
长度(范数)—-向量的长度就是向量的每个元素的平方求和再开根号
范数(以 L2 范数为例) $$ \lVert a \rVert_2 = \left[ \sum_{i=1}^m a_i^2 \right]^{\frac{1}{2}} $$
$ a a $
$ a + b a + b $
$ a b = |a| b $
点乘
- a⊤b = ∑iaibi
正交
- a⊤b = ∑iaibi = 0
矩阵
- 简单操作
- 矩阵加法:$ C = A + B C_{ij} = A_{ij} + B_{ij} $
- 矩阵数乘:$ C = B C_{ij} = B_{ij} $
- 矩阵逐元素正弦运算:$ C = A C_{ij} = A_{ij} $
- 矩阵加法:$ C = A + B C_{ij} = A_{ij} + B_{ij} $
- 特征向量和特征值
- 不被矩阵改变方向的向量
- 对称矩阵总是可以找到特征向量
- 乘法(矩阵乘以向量)
- c = Ab where ci = ∑jAijbj
- c = Ab where ci = ∑jAijbj
- 乘法(矩阵乘以矩阵)
- C = AB where Cik = ∑jAijBjk
- 范数
- c = A ⋅ b hence ‖c‖ ≤ ‖A‖ ⋅ ‖b‖
- 取决于如何衡量(b)和(c)的长度
- c = A ⋅ b hence ‖c‖ ≤ ‖A‖ ⋅ ‖b‖
- 常见范数
- 矩阵范数:最小的满足上面公式的值
- Frobenius 范数:
$$ \lVert A \rVert_{\text{Frob}} = \left[ \sum_{ij} A_{ij}^2 \right]^{\frac{1}{2}} $$
- 矩阵范数:最小的满足上面公式的值
特殊矩阵
- 对称和反对称
- $ A_{ij} = A_{ji} $(对称矩阵,元素关于主对角线对称)
- $ A_{ij} = -A_{ji} $(反对称矩阵,主对角线元素为 0,( (i,j) ) 与 (
(j,i) ) 元素互反 )
- $ A_{ij} = A_{ji} $(对称矩阵,元素关于主对角线对称)
- 正定—-如果一个矩阵是正定的,那么它乘以任何一个列向量或行向量都大于等于0
- 向量范数平方: ‖x‖2 = x⊤x ≥ 0
(欧几里得范数平方,内积定义,结果非负 )
- 二次型推广: x⊤Ax ≥ 0
(刻画半正定/正定矩阵,( A ) 对称时,非零 ( x ) 代入结果非负则 ( A ) 半正定 )
- 向量范数平方: ‖x‖2 = x⊤x ≥ 0
- 正交矩阵
- 所有行都相互正交
- 所有行都有单位长度 U with ∑jUijUkj = δik
- 可以写成 UU⊤ = 1
- 所有行都相互正交
- 置换矩阵
- P where
Pij = 1 if and only if
j = π(i)
- 置换矩阵是正交矩阵
- P where
Pij = 1 if and only if
j = π(i)
代码实现
标量计算
标量由只有一个元素的张量表示
1 | import torch |
(tensor(5.), tensor(6.), tensor(1.5000), tensor(9.))
向量基础
向量可以被视为标量值组成的列表
1 | x = torch.arange(4) |
tensor([0, 1, 2, 3])
通过张量的索引来访问任一元素
1 | x[3] |
tensor(3)
访问张量的长度
1 | len(x) |
4
只有一个轴的张量,形状只有一个元素
1 | x.shape |
torch.Size([4])
矩阵基础
通过指定两个分量m和n来创建一个形状为m × n的矩阵
1 | A = torch.arange(20).reshape(5, 4) |
tensor([[ 0, 1, 2, 3], [ 4, 5, 6, 7], [ 8, 9, 10, 11], [12, 13, 14, 15], [16, 17, 18, 19]])
矩阵的转置
1 | A.T |
tensor([[ 0, 4, 8, 12, 16], [ 1, 5, 9, 13, 17], [ 2, 6, 10, 14, 18], [ 3, 7, 11, 15, 19]])
对称矩阵(symmetric matrix)A等于其转置:A = A⊤
1 | B = torch.tensor([[1, 2, 3], [2, 0, 4], [3, 4, 5]]) |
tensor([[1, 2, 3], [2, 0, 4], [3, 4, 5]])
1 | B == B.T |
tensor([[True, True, True], [True, True, True], [True, True, True]])
矩阵形状改变
就像向量是标量的推广,矩阵是向量的推广一样,我们可以构建具有更多轴的数据结构
只要元素个数不发生变化,就可以根据个数改变矩阵的形状
行是最后一维(有几列),列是倒数第二维(有几行)
1 | X = torch.arange(24).reshape(2, 3, 4) |
tensor([[[ 0, 1, 2, 3], [ 4, 5, 6, 7], [ 8, 9, 10, 11]],
[[12, 13, 14, 15], [16, 17, 18, 19], [20, 21, 22, 23]]])
复制矩阵
给定具有相同形状的任意两个张量,任何按元素二元运算的结果都将是相同形状的张量
1 | A = torch.arange(20, dtype=torch.float32).reshape(5, 4) |
(tensor([[ 0., 1., 2., 3.], [ 4., 5., 6., 7.], [ 8., 9., 10., 11.], [12., 13., 14., 15.], [16., 17., 18., 19.]]),
tensor([[ 0., 2., 4., 6.], [ 8., 10., 12., 14.], [16., 18., 20., 22.], [24., 26., 28., 30.], [32., 34., 36., 38.]]))
矩阵元素积
两个矩阵的按元素乘法称为Hadamard积(Hadamard product)(数学符号⊙)
1 | A * B |
tensor([[ 0., 1., 4., 9.], [ 16., 25., 36., 49.], [ 64., 81., 100., 121.], [144., 169., 196., 225.], [256., 289., 324., 361.]])
1 | a = 2 |
(tensor([[[ 2, 3, 4, 5], [ 6, 7, 8, 9], [10, 11, 12, 13]],
[[14, 15, 16, 17], [18, 19, 20, 21], [22, 23, 24, 25]]]),
torch.Size([2, 3, 4]))
求和运算
计算向量元素的和
1 | x = torch.arange(4, dtype=torch.float32) |
(tensor([0., 1., 2., 3.]), tensor(6.))
表示任意形状张量的元素和
1 | A.shape, A.sum() |
(torch.Size([5, 4]), tensor(190.))
指定轴求和
指定张量沿哪一个轴来通过求和降低维度
1 | A_sum_axis0 = A.sum(axis=0) # 对A.shape的第0维进行求和运算,剩下的矩阵是A.shape去掉第0维后的形状 |
(tensor([40., 45., 50., 55.]), torch.Size([4]))
1 | A_sum_axis1 = A.sum(axis=1) # 对A.shape的第1维进行求和运算,剩下的矩阵是A.shape去掉第1维后的形状 |
(tensor([ 6., 22., 38., 54., 70.]), torch.Size([5]))
1 | A.sum(axis=[0, 1]) # 对A.shape的第0维和第1维都进行求和运算,剩下的矩阵是A.shape去掉第0维和第1维后的形状 |
tensor(190.)
平均值计算
一个与求和相关的量是平均值(mean或average)
1 | A.mean(), A.sum() / A.numel() |
(tensor(9.5000), tensor(9.5000))
1 | A.mean(axis=0), A.sum(axis=0) / A.shape[0] # 按某维度来计算 |
(tensor([ 8., 9., 10., 11.]), tensor([ 8., 9., 10., 11.]))
沿轴求和时维度不变
计算总和或均值时保持轴数不变
keepdims=True时,指不会把要求和的维度去掉而是把shape中对应的那个维度变成1
1 | sum_A = A.sum(axis=1, keepdims=True) |
tensor([[ 6.], [22.], [38.], [54.], [70.]])
通过广播将A
除以sum_A
1 | A / sum_A |
tensor([[0.0000, 0.1667, 0.3333, 0.5000], [0.1818, 0.2273, 0.2727, 0.3182], [0.2105, 0.2368, 0.2632, 0.2895], [0.2222, 0.2407, 0.2593, 0.2778], [0.2286, 0.2429, 0.2571, 0.2714]])
沿轴累加求和
某个轴计算A
元素的累积总和
1 | A.cumsum(axis=0) |
tensor([[ 0., 1., 2., 3.], [ 4., 6., 8., 10.], [12., 15., 18., 21.], [24., 28., 32., 36.], [40., 45., 50., 55.]])
点积是相同位置的按元素乘积的和
1 | y = torch.ones(4, dtype = torch.float32) |
(tensor([0., 1., 2., 3.]), tensor([1., 1., 1., 1.]), tensor(6.))
向量点积计算
我们可以通过执行按元素乘法,然后进行求和来表示两个向量的点积
1 | torch.sum(x * y) |
tensor(6.)
矩阵与向量积计算
矩阵向量积Ax是一个长度为m的列向量,其第i个元素是点积ai⊤x
1 | A.shape, x.shape, torch.mv(A, x) |
(torch.Size([5, 4]), torch.Size([4]), tensor([ 14., 38., 62., 86., 110.]))
矩阵x矩阵计算
我们可以将矩阵-矩阵乘法AB看作简单地执行m次矩阵-向量积,并将结果拼接在一起,形成一个n × m矩阵
1 | B = torch.ones(4, 3) |
tensor([[ 6., 6., 6.], [22., 22., 22.], [38., 38., 38.], [54., 54., 54.], [70., 70., 70.]])
向量范数
L2范数是向量元素平方和的平方根:
$$\|\mathbf{x}\|_2 = \sqrt{\sum_{i=1}^n x_i^2}$$
1 | u = torch.tensor([3.0, -4.0]) |
tensor(5.)
L1范数,它表示为向量元素的绝对值之和:
$$\|\mathbf{x}\|_1 = \sum_{i=1}^n \left|x_i \right|$$
1 | torch.abs(u).sum() |
tensor(7.)
矩阵范数
矩阵的Frobenius范数(Frobenius norm)是矩阵元素平方和的平方根:
$$\|\mathbf{X}\|_F = \sqrt{\sum_{i=1}^m \sum_{j=1}^n x_{ij}^2}$$
1 | torch.norm(torch.ones((4, 9))) |
tensor(6.)
微积分
矩阵的计算部分要清楚如何求导数!
理论基础
标量导数
- 导数是切线的斜率
y | a | xn | exp (x) | log (x) | sin (x) |
---|---|---|---|---|---|
$\dfrac{dy}{dx}$ | 0 | nxn − 1 | exp (x) | $\dfrac{1}{x}$ | cos (x) |
注:a 不是 x 的函数
y | u + v | uv | y = f(u), u = g(x) |
---|---|---|---|
$\dfrac{dy}{dx}$ | $\dfrac{du}{dx} + \dfrac{dv}{dx}$ | $\dfrac{du}{dx} v + \dfrac{dv}{dx} u$ | $\dfrac{dy}{du} \dfrac{du}{dx}$ |
亚导数
当不存在导数时怎么办?
将导数拓展到不可微的函数
$ y = |x| $
绝对值函数的导数
$$ \frac{\partial |x|}{\partial x} = \begin{cases} 1 & \text{if } x > 0 \\ -1 & \text{if } x < 0 \\ a & \text{if } x = 0, \ a \in [-1,1] \end{cases} $$
ReLU 函数的导数(分段形式):
$$ \frac{\partial}{\partial x} \text{max}(x, 0) = \begin{cases} 1 & \text{if } x > 0 \\ 0 & \text{if } x < 0 \\ a & \text{if } x = 0, \ a \in [0,1] \end{cases} $$
向量梯度
- 将导数拓展到向量,需要注意向量或矩阵的形状!!
- 求导后梯度的形状,按照分子布局符号,如上图
- y是列向量,x是标量,求导后的结果是y的形状(列向量)
- x是列向量,y是标量,求导后的结果行向量
- x是向量,y是向量,求导后的结果是矩阵
- 如果是反过来的情况,则称为分母布局符号
单向量梯度样例
x是列向量,y是标量
y | 表达式 | $\dfrac{\partial y}{\partial \boldsymbol{x}}$ | 补充说明 |
---|---|---|---|
y = a | 标量(与 x 无关) | 0⊤ | a 不是 x 的函数 |
y = au | 标量(u 是 x 的函数) | $a \dfrac{\partial u}{\partial \boldsymbol{x}}$ | 数乘求导法则 |
y = sum(x) | 向量元素和 | 1⊤ | 对向量各元素求和后求导 |
y = ‖x‖2 | 向量 L2 范数平方 | 2x⊤ | 由 ‖x‖2 = x⊤x 求导 |
符号说明
- 0:零向量(元素全
0 ),0⊤
是其转置(行向量);
- 1:全 1
向量(元素全 1 ),1⊤
是其转置(行向量);
- 用途:向量微积分、机器学习梯度推导基础规则。
y | 表达式 | $\dfrac{\partial y}{\partial \boldsymbol{x}}$ | 说明 |
---|---|---|---|
y = u + v | 和(u, v 是标量/向量函数) | $\dfrac{\partial u}{\partial \boldsymbol{x}} + \dfrac{\partial v}{\partial \boldsymbol{x}}$ | 加法求导法则 |
y = uv | 积(u, v 是标量/向量函数) | $\dfrac{\partial u}{\partial \boldsymbol{x}} v + \dfrac{\partial v}{\partial \boldsymbol{x}} u$ | 乘积求导法则 |
y = ⟨u, v⟩ | 内积(u, v 是向量函数) | $\boldsymbol{u}^\top \dfrac{\partial \boldsymbol{v}}{\partial \boldsymbol{x}} + \boldsymbol{v}^\top \dfrac{\partial \boldsymbol{u}}{\partial \boldsymbol{x}}$ | 内积求导法则 |
用途说明:向量微积分基础规则,用于机器学习(如反向传播)、优化理论中推导梯度,计算复杂函数对参数向量的导数。
向量对向量的导数
(雅可比矩阵,Jacobian Matrix)
给定: $$ \boldsymbol{x} = \begin{bmatrix} x_1 \\ x_2 \\ \vdots \\ x_n \end{bmatrix}, \quad \boldsymbol{y} = \begin{bmatrix} y_1 \\ y_2 \\ \vdots \\ y_m \end{bmatrix} $$
雅可比矩阵定义:
$$
\frac{\partial \boldsymbol{y}}{\partial \boldsymbol{x}} =
\begin{bmatrix}
\frac{\partial y_1}{\partial \boldsymbol{x}} \\
\frac{\partial y_2}{\partial \boldsymbol{x}} \\
\vdots \\
\frac{\partial y_m}{\partial \boldsymbol{x}}
\end{bmatrix} = \begin{bmatrix}
\frac{\partial y_1}{\partial x_1}, \frac{\partial y_1}{\partial x_2},
\dots, \frac{\partial y_1}{\partial x_n} \\
\frac{\partial y_2}{\partial x_1}, \frac{\partial y_2}{\partial x_2},
\dots, \frac{\partial y_2}{\partial x_n} \\
\vdots \\
\frac{\partial y_m}{\partial x_1}, \frac{\partial y_m}{\partial x_2},
\dots, \frac{\partial y_m}{\partial x_n}
\end{bmatrix}
$$
首先把y拆分后对向量x求导,再计算单独求导后的结果。
雅可比矩阵
y | $\dfrac{\partial \boldsymbol{y}}{\partial \boldsymbol{x}}$ | 维度与说明 |
---|---|---|
y = a(常向量,与 x 无关) | 0(零矩阵) | a 不随 x 变化 |
y = x(恒等映射) | I(单位矩阵) | 自身对自身求导为单位矩阵 |
y = Ax(矩阵-向量乘法) | A(原矩阵) | 线性变换导数是变换矩阵本身 |
y = x⊤A(向量-矩阵乘法) | A⊤(矩阵转置) | 转置保证导数维度 $^{m n} $ |
补充条件
- 维度: x ∈ ℝn
, $ ^m $,故 $ ^{m n} $;
- 常量: $a, , $均与 $ $无关;
- 符号:0 (零矩阵)、 I (单位矩阵) 。
用途:向量微积分基础,机器学习(如反向传播)、控制理论中线性变换梯度推导的核心规则。
y | $\dfrac{\partial \boldsymbol{y}}{\partial \boldsymbol{x}}$ | 说明 |
---|---|---|
y = au(标量-向量数乘) | $a \dfrac{\partial \boldsymbol{u}}{\partial \boldsymbol{x}}$ | 数乘法则(标量提至导数外) |
y = Au(矩阵-向量乘法) | $\boldsymbol{A} \dfrac{\partial \boldsymbol{u}}{\partial \boldsymbol{x}}$ | 线性变换法则(矩阵提至导数外) |
y = u + v(向量和) | $\dfrac{\partial \boldsymbol{u}}{\partial \boldsymbol{x}} + \dfrac{\partial \boldsymbol{v}}{\partial \boldsymbol{x}}$ | 加法法则(和的导数拆为导数和) |
用途说明
向量微积分基础规则,用于机器学习(如神经网络反向传播)、优化理论中,推导线性/仿射变换的梯度,计算损失函数对参数的导数。
向量链式法制
- 标量链式法则
若 $y = f(u) ,u = g(x) $(复合函数, y 经 u 依赖 x ),则:
$$ \frac{dy}{dx} = \frac{dy}{du} \cdot \frac{du}{dx} $$
说明:微积分核心求导法则,是神经网络反向传播的数学基础(分解复杂梯度为多层简单梯度乘积)。
标量中间变量 u
若y = f(u)(y标量),u = g(x)(u标量,x ∈ ℝn):$ = $
维度:$$ \frac{dy}{\partial \boldsymbol{x}} (1,n) = \frac{dy}{du} (1,) \cdot \frac{\partial u}{\partial \boldsymbol{x}} (1,n) $$
拓展到向量,向量中间变量 u
- 若 y = f(u)(y标量),u = g(x)(u ∈ ℝk,x ∈ ℝn):
$$ \frac{dy}{\partial \boldsymbol{x}} =
\frac{dy}{\partial \boldsymbol{u}} \cdot \frac{\partial
\boldsymbol{u}}{\partial \boldsymbol{x}} $$
- 维度:$$ \frac{dy}{\partial \boldsymbol{x}} (1,n) = \frac{dy}{\partial \boldsymbol{u}} (1,k) \cdot \frac{\partial \boldsymbol{u}}{\partial \boldsymbol{x}} (k,n) $$
- 若 y = f(u)(y标量),u = g(x)(u ∈ ℝk,x ∈ ℝn):
$$ \frac{dy}{\partial \boldsymbol{x}} =
\frac{dy}{\partial \boldsymbol{u}} \cdot \frac{\partial
\boldsymbol{u}}{\partial \boldsymbol{x}} $$
向量对向量的链式求导法则
- 若 $ 经向量 依赖于 $,则: $$ \frac{\partial \boldsymbol{y}}{\partial \boldsymbol{x}} = \frac{\partial \boldsymbol{y}}{\partial \boldsymbol{u}} \cdot \frac{\partial \boldsymbol{u}}{\partial \boldsymbol{x}} $$
维度说明
$\frac{\partial \boldsymbol{y}}{\partial \boldsymbol{x}} :(m, n) 矩阵( \boldsymbol{y} \in \mathbb{R}^m , \boldsymbol{x} \in \mathbb{R}^n )$
$$ \frac{\partial \boldsymbol{y}}{\partial \boldsymbol{u}} : (m, k) 矩阵( \boldsymbol{u} \in \mathbb{R}^k ) $$
$ : (k, n) 矩阵 $
合法性:需满足 ( k ) 维度匹配(前导矩阵列数 = 后续矩阵行数)。
代码实现
如果f的导数存在,这个极限被定义为:
$$f'(x) = \lim_{h \rightarrow 0} \frac{f(x+h) - f(x)}{h}$$
定义u = f(x) = 3x2 − 4x
1 | # %matplotlib inline |
通过令x = 1并让h接近0,$\frac{f(x+h)-f(x)}{h}$的数值结果接近2
1 | def numerical_lim(f, x, h): |
h=0.10000, numerical limit=2.30000 h=0.01000, numerical limit=2.03000 h=0.00100, numerical limit=2.00300 h=0.00010, numerical limit=2.00030 h=0.00001, numerical limit=2.00003
为了对导数的这种解释进行可视化,我们将使用matplotlib
定义几个函数
1 | def use_svg_display(): |
绘制函数u = f(x)及其在x = 1处的切线y = 2x − 3
1 | x = np.arange(0, 3, 0.1) |
自动求导
自动求导计算一个函数在指定值上的导数
它有别于符号求导和数值求导
理论基础
计算图
将代码分解成操作子
将计算表示成一个无环图
显示构造,可实现框架:Tensorflow、Theano、MXNet
1 | from mxnet import sym |
隐式构造,可实现框架:PyTorch、MXNet
1 | from mxnet import autograd, nd |
两种模式
核心原理链式法则:$$\frac{\partial y}{\partial x} = \frac{\partial y}{\partial u_n} \frac{\partial u_n}{\partial u_{n - 1}} \dots \frac{\partial u_2}{\partial u_1} \frac{\partial u_1}{\partial x}$$
正向积累:$$\frac{\partial y}{\partial x} = \frac{\partial y}{\partial u_n} \left( \frac{\partial u_n}{\partial u_{n - 1}} \left( \dots \left( \frac{\partial u_2}{\partial u_1} \frac{\partial u_1}{\partial x} \right) \right) \right)$$
反向累积、又称反向传递 $$\frac{\partial y}{\partial x} = \left( \left( \left( \frac{\partial y}{\partial u_n} \frac{\partial u_n}{\partial u_{n - 1}} \right) \dots \right) \frac{\partial u_2}{\partial u_1} \right) \frac{\partial u_1}{\partial x}$$
反向累积总结
- 构造计算图
- 前向:执行图,存储中间结果
- 反向:从相反方向执行图
- 去除不需要的枝
复杂度
- 计算复杂度:O(n),
n 是操作子个数
- 通常正向和反向的代价类似
- 通常正向和反向的代价类似
- 内存复杂度: O(n),因为需要存储正向的所有中间结果 。(耗费计算资源的主要原因)
- 跟正向累积对比:
- O(n)
计算复杂度用来计算一个变量的梯度
- O(1) 内存复杂度
- O(n)
计算复杂度用来计算一个变量的梯度
代码实现
假设我们想对函数y = 2x⊤x关于列向量x求导
1 | import torch |
在我们计算y关于x的梯度之前,需要一个地方来存储梯度
1 | x.requires_grad_(True) # 等价于x = torch.arange(4.0, requires_grad=True),默认值是None |
现在计算y
1 | y = 2 * torch.dot(x, x) |
tensor(28., grad_fn=
)
通过调用反向传播函数来自动计算y
关于x
每个分量的梯度
1 | y.backward() # 求导 |
tensor([ 0., 4., 8., 12.])
1 | x.grad == 4 * x |
tensor([True, True, True, True])
现在计算x
的另一个函数
1 | x.grad.zero_() |
tensor([1., 1., 1., 1.])
深度学习中,我们的目的不是计算微分矩阵,而是单独计算批量中每个样本的偏导数之和
1 | x.grad.zero_() |
tensor([0., 2., 4., 6.])
将某些计算移动到记录的计算图之外
1 | x.grad.zero_() |
tensor([True, True, True, True])
1 | x.grad.zero_() |
tensor([True, True, True, True])
即使构建函数的计算图需要通过Python控制流(例如,条件、循环或任意函数调用),我们仍然可以计算得到的变量的梯度
1 | def f(a): |
tensor(True)