参考引用
- 动手学深度学习
- 利用 Anaconda 安装 pytorch 和 paddle 深度学习环境 + pycharm 安装
假如需要编写程序来响应⼀个 “唤醒词”,比如 “Alexa”
机器学习中,学习 (learning) 是一个训练模型的过程。通过这个过程,可以发现正确的参数集,从而使模型强制执行所需的行为。换句话说,用数据训练 (train) 模型。训练过程通常包含如下步骤
每个数据集由一个个样本 (sample) 组成,大多时候,它们遵循独立同分布。样本有时也叫做数据点 (data point) 或数据实例 (data instance),通常每个样本由一组称为特征 (features,或协变量 (covariates)) 的属性组成,机器学习模型会根据这些属性进行预测。假设要预测的是一个特殊的属性,它被称为标签 (label,或目标 (target))
当每个样本的特征类别数量相同时,其特征向量是固定长度的,这个长度被称为数据的维数 (dimensionality)。然而,并不是所有的数据都可以用 “固定长度” 的向量表示,与传统机器学习方法相比,深度学习的一个主要优势是可以处理不同长度的数据
更多的数据可以被用来训练出更强大的模型,仅仅拥有海量的数据是不够的,还需要正确的数据。如果数据中充满了错误,或者如果数据的特征不能预测任务目标,那么模型很可能无效
深度学习与经典方法的区别主要在于:前者关注功能强大的模型,这些模型由神经网络错综复杂的交织在一起,包含层层数据转换,因此被称为深度学习 (deep learning)
在机器学习中需要定义模型的优劣程度的度量,这个度量在大多数情况是 “可优化” 的,这被称之为目标函数 (obiective function)。通常定义一个目标函数,并希望优化它到最低点。因为越低越好,所以这些函数有时被称为损失函数 (loss function,或 cost function)
通常,损失函数是根据模型参数定义的,并取决于数据集。在一个数据集上,可以通过最小化总损失来学习模型参数的最佳值
可用数据集通常可以分成两部分:训练数据集用于拟合模型参数,测试数据集用于评估拟合的模型
监督学习 (supervised learning) 擅长在 “给定输入特征” 的情况下预测标签。每个 “特征-标签” 对都称为一个样本 (example)。有时即使标签是未知的,样本也可以指代输入特征。目标是生成一个模型,能够将任何输入特征映射到标签 (即预测)
监督学习之所以能发挥作用,是因为在训练参数时为模型提供了一个数据集,其中每个样本都有真实的标签。工业中,大部分机器学习的成功应用都使用了监督学习。这是因为许多重要的任务可描述为:在给定一组特定的可用数据的情况下,估计未知事物的概率。比如:
监督学习的学习过程一般可以分为三大步骤:
设计一款应用程序能够自动理解从图像中看到的文本,并将手写字符映射到对应的已知字符之上。这种 “哪一个” 的问题叫做分类 (classification) 问题。分类问题希望模型能够预测样本属于哪个类别 (category,类(class))
用概率语言来理解模型:给定一个样本特征,模型为每个可能的类分配一个概率。比如,猫狗分类例子中,分类器可能会输出图像是猫的概率为 0.9:分类器 90% 确定图像描绘的是一只猫。预测类别的概率大小传达了一种模型的不确定性
分类可能变得比二项分类、多项分类复杂得多。例如,有一些分类任务的变体可以用于寻找层次结构,层次结构假定在许多类之间存在某种关系。因此,并不是所有的错误都是均等的。人们宁愿错误地分入一个相关的类别,也不愿错误地分入一个遥远的类别,这通常被称为层次分类 (hierarchical classifcation)
不管是监督学习还是无监督学习,都会预先获取大量数据,然后启动模型,不再与环境交互。这里所有学习都是在算法与环境断开后进行的,被称为离线学习 (offline learning)
强化学习不需要输入数据集,并能与环境交互且采取行动。这可能包括应用到机器人、对话系统,甚至开发视频游戏的人工智能。深度强化学习(deep reinforcement learning) 将深度学习应用于强化学习的问题
在强化学习问题中,智能体 (agent) 在一系列的时间步骤上与环境交互。在每个特定时间点,智能体从环境接收一些观察 (observation),并且必须选择一个动作 (action),然后通过某种机制 (有时称为执行器) 将其传输回环境,最后智能体从环境中获得奖励 (reward)。此后新一轮循环开始,智能体接收后续观察,并选择后续操作,依此类推
强化学习的目标是产生一个好的策略 (policy)。强化学习智能体选择的 “动作” 受策略控制,即一个从环境观察映射到行动的功能
为了解决各种各样的机器学习问题,深度学习提供了强大的工具。虽然许多深度学习方法都是最近才有重大突破,但使用数据和神经网络编程的核心思想已经研究了几个世纪
神经网络 (neural networks) 的得名源于生物灵感。研究人员一直试图组装类似于相互作用的神经元网络的计算电路,其核心是当今大多数网络中都可以找到的几个关键原则:
机器学习是人工智能的一个分支/方法,而深度学习是机器学习的一个子集
机器学习可以使用数据来学习输入和输出之间的转换,例如在语音识别中将音频转换为文本。在这样做时,通常需要以适合算法的方式表示数据,以便将这种表示转换为输出
深度学习方法中最显著的共同点是使用端到端训练:与其基于单独调整的组件组装系统,不如构建系统,然后联合调整它们的性能。因此,深度学习的一个关键优势:不仅取代了传统学习管道末端的浅层模型,还取代了劳动密集型的特征工程过程
import torch
x = torch.arange(12) # 使用 arange 创建一个行向量,包含以 0 开始的前 12 个整数
print(x)
print(x.shape) # 通过张量的 shape 属性来访问张量 (沿每个轴的长度) 的形状
# 如果只想知道张量中元素的总数,即形状的所有元素乘积,可以检查它的大小 (size)
# 因为这里在处理的是一个向量,所以 shape 与 size 相同
print(x.numel())
# 要想改变一个张量的形状而不改变元素数量和元素值,可以调用 reshape 函数
# 虽然张量的形状发生了改变,但其元素值并没有变。通过改变张量的形状张量的大小不会改变
X = x.reshape(3, 4) # 把张量 x 从形状为 (12,) 行向量转换为形状为 (3, 4) 矩阵
# 可以通过 -1 来⾃动计算出维度的功能
# 用 x.reshape(-1,4) 或 x.reshape(3,-1) 来取代 x.reshape(3,4)
print(X)
# 创建全 0 或全 1 常量
y = torch.zeros((2, 3, 4))
y = torch.ones((2, 3, 4))
print(y)
# 每个元素都从均值为 0、标准差为 1 的标准高斯分布 (正态分布) 中随机采样
z = torch.randn(3, 4)
print(z)
# 提供包含数值的 Python 列表 (或嵌套列表),为所需张量中的每个元素赋予确定值
# 最外层的列表对应于轴 0,内层的列表对应于轴 1
w = torch.tensor([[2, 1, 4, 3], [1, 2, 3, 4], [4, 3, 2, 1]])
print(w)
import torch
x = torch.tensor([1.0, 2, 4, 8])
y = torch.tensor([2, 2, 2, 2, ])
print(x + y, x - y, x * y, x / y, x ** y) # ** 运算符是求幂运算
print(torch.exp(x))
x = torch.arange(12, dtype=torch.float32).reshape(3, 4) # 创建一个形状为 (3,4) 的张量 x,其中包含了从 0 到 11 的连续数字,其数据类型为 torch.float32
y = torch.tensor([[2.0, 1, 4, 3], [1, 2, 3, 4], [4, 3, 2, 1]])
print(torch.cat((x, y), dim=0)) # 将张量x和y在维度0上进行拼接,即将y拼接在x的下方,拼接后的张量形状为(6,4)
print(torch.cat((x, y), dim=1)) # 将张量x和y在维度1上进行拼接,即将y拼接在x的右侧,拼接后的张量形状为(3,8)
# 通过逻辑运算符构建二元张量
# 如果 x 和 y 在该位置相等,则新张量中相应项的值为 1,否则该位置为 0
print(x == y)
# 对张量中的所有元素进⾏求和,会产⽣⼀个单元素张量
print(x.sum())
import torch
a = torch.arange(3).reshape((3, 1))
b = torch.arange(2).reshape((1, 2))
print(a)
print(b)
# 将两个矩阵⼴播为⼀个更⼤的 3×2 矩阵:矩阵 a 复制列,矩阵 b 复制⾏,然后再按元素相加
print(a + b)
import torch
x = torch.arange(12, dtype=torch.float32).reshape(3, 4)
print(x[-1]) # 用 [-1] 选择最后一个元素
print(x[1:3]) # 用 [1:3] 选择第二个和第三个元素
# 指定索引来将元素写入矩阵
x[1, 2] = 9
print(x)
# 为多个元素赋值相同的值:只需索引所有元素,然后为它们赋值
x[0:2, :] = 12 # : 表示沿轴 1(列)的所有元素
print(x)
import torch
x = torch.arange(12, dtype=torch.float32).reshape(3, 4)
y = torch.tensor([[2.0, 1, 4, 3], [1, 2, 3, 4], [4, 3, 2, 1]])
# 使用切片表示法将操作的结果分配给先前分配的数组,例如 Y[:]=
z = torch.zeros_like(y) # 使用 zeros_like 来分配一个全 0 的块
print('id(z):', id(z))
z[:] = x + y
print('id(z):', id(z))
将深度学习框架定义的张量转换为 NumPy 张量 (ndarray) 很容易,反之也同样。torch 张量和 numpy 数组将共享它们的底层内存,就地操作更改一个张量也会同时更改另一个张量
import torch
x = torch.arange(12, dtype=torch.float32).reshape(3, 4)
A = x.numpy()
B = torch.tensor(A)
print(type(A), type(B))
要将大小为 1 的张量转换为 Python 标量,可以调用 item 函数或 Python 的内置函数
import torch
a = torch.tensor([3.5])
print((a, a.item(), float(a), int(a)))
import os
import pandas as pd
os.makedirs(os.path.join('..', 'data'), exist_ok=True)
data_file = os.path.join('..', 'data', 'house_tiny.csv')
with open(data_file, 'w') as f:
f.write('NumRooms,Alley,Price\n')
f.write('NA,Pave,127500\n')
f.write('2,NA,106000\n')
f.write('4,NA,178100\n')
f.write('NA,NA,140000\n')
data = pd.read_csv(data_file)
print(data)
# 输出
NumRooms Alley Price
0 NaN Pave 127500
1 2.0 NaN 106000
2 4.0 NaN 178100
3 NaN NaN 140000
import os
import pandas as pd
os.makedirs(os.path.join('..', 'data'), exist_ok=True)
data_file = os.path.join('..', 'data', 'house_tiny.csv')
with open(data_file, 'w') as f:
f.write('NumRooms,Alley,Price\n')
f.write('NA,Pave,127500\n')
f.write('2,NA,106000\n')
f.write('4,NA,178100\n')
f.write('NA,NA,140000\n')
data = pd.read_csv(data_file)
# 通过位置索引 iloc,将 data 分成 inputs 和 outputs
# inputs 为 data 的前两列,outputs 为 data 的最后一列
inputs, outputs = data.iloc[:, 0:2], data.iloc[:, 2]
inputs = inputs.fillna(inputs.mean())
print(inputs)
# 输出
NumRooms Alley
0 3.0 Pave
1 2.0 NaN
2 4.0 NaN
3 3.0 NaN
import torch
x, y = torch.tensor(inputs.values), torch.tensor(outputs.values)
print(x, y)
import torch
x = torch.tensor(3.0)
y = torch.tensor(2.0)
print(x + y, x * y, x / y, x**y)
import torch
x = torch.arange(4)
print(x)
import torch
x = torch.arange(4)
print(len(x)) # 调用 Python 的内置 len() 函数来访问张量的长度
# 当用张量表示一个向量(只有一个轴) 时,也可通过 .shape 属性访问向量长度
# 形状(shape)是⼀个元素组,列出了张量沿每个轴的⻓度(维数)
print(x.shape)
维度(dimension):向量或轴的维度被用来表示向量或轴的长度,即向量或轴的元素数量。然而,张量的维度用来表示张量具有的轴数。因此,在这个意义上,张量的某个轴的维度就是这个轴的长度
A = [ a 11 a 12 ⋯ a 1 n a 21 a 22 ⋯ a 2 n ⋮ ⋮ ⋱ ⋮ a m 1 a m 2 ⋯ a m n ] \mathbf{A}=\begin{bmatrix}a_{11}&a_{12}&\cdots&a_{1n}\\a_{21}&a_{22}&\cdots&a_{2n}\\\vdots&\vdots&\ddots&\vdots\\a_{m1}&a_{m2}&\cdots&a_{mn}\end{bmatrix} A= a11a21⋮am1a12a22⋮am2⋯⋯⋱⋯a1na2n⋮amn
import torch
A = torch.arange(20).reshape(5, 4)
B = torch.tensor([[1, 2, 3], [2, 0, 4], [3, 4, 5]]) # 对称矩阵
print(A)
print(A.T) # 访问矩阵的转置
print(B == B.T)
import torch
a = 2
X = torch.arange(24).reshape(2, 3, 4)
print(X)
print(a + X)
print(a * X)
import torch
A = torch.arange(20, dtype=torch.float32).reshape(5, 4)
B = A.clone() # 通过分配新内存,将 A 的一个副本分配给B
# print(A + B)
print(A * B)
import torch
A = torch.arange(20, dtype=torch.float32).reshape(5, 4)
A_sum_axis0 = A.sum(axis=0)
A_sum_axis1 = A.sum(axis=1)
print(A.sum()) # 矩阵 A 中元素和
print(A_sum_axis0)
print(A_sum_axis1)
print(A.mean())
print(A.mean(axis=0))
import torch
A = torch.arange(20, dtype=torch.float32).reshape(5, 4)
sum_A = A.sum(axis=1, keepdims=True) # 非降维(列)求矩阵 A 中元素和
print(sum_A)
print(A.cumsum(axis=0))
import torch
x = torch.arange(4, dtype=torch.float32)
y = torch.ones(4, dtype=torch.float32)
print(torch.dot(x, y))
import torch
A = torch.arange(20, dtype=torch.float32).reshape(5, 4)
x = torch.arange(4, dtype=torch.float32)
print(torch.mv(A, x)) # 使⽤ mv 函数执行矩阵-向量积
C = A B = [ a 1 ⊤ a 2 ⊤ ⋮ a n ⊤ ] [ b 1 b 2 ⋯ b m ] = [ a 1 ⊤ b 1 a 1 ⊤ b 2 ⋯ a 1 ⊤ b m a 2 ⊤ b 1 a 2 ⊤ b 2 ⋯ a 2 ⊤ b m ⋮ ⋮ ⋱ ⋮ a n ⊤ b 1 a n ⊤ b 2 ⋯ a n ⊤ b m ] \mathbf{C}=\mathbf{A}\mathbf{B}=\begin{bmatrix}\mathbf{a}_1^\top\\\mathbf{a}_2^\top\\\vdots\\\mathbf{a}_n^\top\end{bmatrix}\begin{bmatrix}\mathbf{b}_1&\mathbf{b}_2&\cdots&\mathbf{b}_m\end{bmatrix}=\begin{bmatrix}\mathbf{a}_1^\top\mathbf{b}_1&\mathbf{a}_1^\top\mathbf{b}_2&\cdots&\mathbf{a}_1^\top\mathbf{b}_m\\\mathbf{a}_2^\top\mathbf{b}_1&\mathbf{a}_2^\top\mathbf{b}_2&\cdots&\mathbf{a}_2^\top\mathbf{b}_m\\\vdots&\vdots&\ddots&\vdots\\\mathbf{a}_n^\top\mathbf{b}_1&\mathbf{a}_n^\top\mathbf{b}_2&\cdots&\mathbf{a}_n^\top\mathbf{b}_m\end{bmatrix} C=AB= a1⊤a2⊤⋮an⊤ [b1b2⋯bm]= a1⊤b1a2⊤b1⋮an⊤b1a1⊤b2a2⊤b2⋮an⊤b2⋯⋯⋱⋯a1⊤bma2⊤bm⋮an⊤bm
import torch
A = torch.arange(20, dtype=torch.float32).reshape(5, 4)
B = torch.ones(4, 3)
print(torch.mm(A, B)) # 使⽤ mm 函数执行矩阵-矩阵乘法
非正式地说,向量的范数 (norm) 是表示一个向量有多大。这里考虑的大小 (size) 概念不涉及维度,而是分量的大小
在线性代数中,向量范数是将向量映射到标量的函数 f f f。给定任意向量 x \mathbf{x} x,向量范数要满足一些属性
欧几里得距离是一个 L 2 L_2 L2 范数,假设 n 维向量 x \mathbf{x} x 中的元素是 x 1 , . . . , x n x_1,...,x_n x1,...,xn,其 L 2 L_2 L2 范数是向量元素平方和的平方根
深度学习中更经常地使用 L 2 L_2 L2 范数的平方,也会经常遇到 L 1 L_1 L1 范数,它表示为向量元素的绝对值之和
∥ x ∥ 1 = ∑ i = 1 n ∣ x i ∣ \|\mathbf{x}\|_1=\sum_{i=1}^n|x_i| ∥x∥1=i=1∑n∣xi∣
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|x_i|^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 范数是矩阵元素平方和的平方根
import torch
u = torch.tensor([3.0, 4.0])
print(torch.norm(u)) # 计算 L2 范数
print(torch.abs(u).sum) # 计算 L1 范数
print(torch.norm(torch.ones((4, 9)))) # 计算矩阵范数
在深度学习中,经常试图解决优化问题:最大化分配给观测数据的概率,最小化预测和真实观测之间的距离。用向量表示物品(如单词、产品或新闻文章),以便最小化相似项目之间的距离,最大化不同项目之间的距离。目标,通常被表达为范数
在深度学习中,通常选择对于模型参数可微的损失函数。简而言之,对于每个参数,如果把这个参数增加或减少一个无穷小的量,可以知道损失会以多快的速度增加或减少
假设有一个函数 f : R → R f:\mathbb{R}\to\mathbb{R} f:R→R,其输入和输出都是标量。如果 f f f 的导数存在,这个极限被定义为
f ′ ( x ) = lim h → 0 f ( x + h ) − f ( x ) h f'(x)=\lim_{h\to0}\frac{f(x+h)-f(x)}h f′(x)=h→0limhf(x+h)−f(x)
如果 f ′ ( a ) f^{\prime}(a) f′(a) 存在,则称 f f f 在 a a a 处是可微 (differentiable) 的。如果 f f f 在一个区间内的每个数上都是可微的,则此函数在此区间中是可微的。可将上式中的导数 f ′ ( a ) f^{\prime}(a) f′(a) 解释为 f ( a ) f(a) f(a) 相对于 x x x 的瞬时变化率。所谓瞬时变化率是基于 x x x 中的变化 h h h,且 h h h 接近 0
导数的几个等价符号
f ′ ( x ) = y ′ = d y d x = d f d x = d d x f ( x ) = D f ( x ) = D x f ( x ) f'(x)=y'=\frac{dy}{dx}=\frac{df}{dx}=\frac{d}{dx}f(x)=Df(x)=D_xf(x) f′(x)=y′=dxdy=dxdf=dxdf(x)=Df(x)=Dxf(x)
其中 d d x \frac{d}{dx} dxd 和 D D D 是微分运算符,表示微分操作,常见微分法则如下
d d x [ C f ( x ) ] = C d d x f ( x ) \frac d{dx}[Cf(x)]=C\frac d{dx}f(x) dxd[Cf(x)]=Cdxdf(x)
d d x [ f ( x ) + g ( x ) ] = d d x f ( x ) + d d x g ( x ) \frac d{dx}[f(x)+g(x)]=\frac d{dx}f(x)+\frac d{dx}g(x) dxd[f(x)+g(x)]=dxdf(x)+dxdg(x)
d d x [ f ( x ) g ( x ) ] = f ( x ) d d x [ g ( x ) ] + g ( x ) d d x [ f ( x ) ] \frac{d}{dx}[f(x)g(x)]=f(x)\frac{d}{dx}[g(x)]+g(x)\frac{d}{dx}[f(x)] dxd[f(x)g(x)]=f(x)dxd[g(x)]+g(x)dxd[f(x)]
d d x [ f ( x ) g ( x ) ] = g ( x ) d d x [ f ( x ) ] − f ( x ) d d x [ g ( x ) ] [ g ( x ) ] 2 \frac{d}{dx}\left[\frac{f(x)}{g(x)}\right]=\frac{g(x)\frac{d}{dx}[f(x)]-f(x)\frac{d}{dx}[g(x)]}{[g(x)]^2} dxd[g(x)f(x)]=[g(x)]2g(x)dxd[f(x)]−f(x)dxd[g(x)]
import numpy as np
import matplotlib.pyplot as plt
def f(x):
return 3 * x ** 2 - 4 * x
def numerical_lim(f, x, h):
return (f(x + h) - f(x)) / h
# 通过令 x = 1 并让 h 接近 0,则 numerical_lim 结果接近 2
h = 0.1
for i in range(5):
# 将变量 h 格式化为浮点数,并保留小数点后五位
print(f'h={h:.5f}, numerical limit={numerical_lim(f, 1, h):.5f}')
h *= 0.1
# 定义 set_figsize 函数来设置图表大小
# 参数 figsize 是一个长度为 2 的元组,用于指定图形的宽度和高度
# plt.rcParams 是 Matplotlib 库中的一个全局变量,它是一个字典对象,存储了一些全局默认值
# figure.figsize 是其中一个键值对,用于设置图形的尺寸,默认值为 (6.0, 4.0)
def set_figsize(figsize=(3.5, 2.5)):
plt.rcParams['figure.figsize'] = figsize # 修改全局默认值 figsize
# 用于设置由 matplotlib 生成图表的轴的属性
# axes:图形的轴对象(坐标轴) legend:图例
def set_axes(axes, xlabel, ylabel, xlim, ylim, xscale, yscale, legend):
axes.set_xlabel(xlabel) # x 轴的标签
axes.set_ylabel(ylabel)
axes.set_xscale(xscale) # x 轴的刻度缩放方式,可以是线性(linear)或对数(log)
axes.set_yscale(yscale)
axes.set_xlim(xlim) # x 轴的范围
axes.set_ylim(ylim)
if legend: # 如果 legend 参数不为空
axes.legend(legend) # 则调用轴对象的 legend() 方法设置图例
axes.grid() # 使用 grid() 方法在图形中加入网格
# 定义一个 plot 函数来简洁地绘制多条曲线
# fmts:曲线的样式,可以是一条曲线的样式(字符串)或多条曲线的样式(字符串组成的列表)
# ('-','m--'):这个字符串元组表示一个紫色 (m) 的虚线 (--)
# ('g-.'):这个字符串元组表示一个绿色 (g) 的点划线 (-.)
# ('r:'):这个字符串元组表示一个红色 (r) 的点线 (:)
def plot(X, Y=None, xlabel=None, ylabel=None, legend=None, xlim=None,
ylim=None, xscale='linear', yscale='linear',
fmts=('-', 'm--', 'g-.', 'r:'), figsize=(3.5, 2.5), axes=None):
# 绘制数据点
if legend is None:
legend = [] # 设置图例为空列表
set_figsize(figsize) # 设置图标大小
# 如果 X 只有⼀个轴,则输出 True
def has_one_axis(X):
# 判断 X 对象是否有一个属性 ndim,并且是否等于 1(即 X 是否为一维对象)
# 判断 X 是否为 list 类型的对象,并且第一个元素是否没有属性 __len__(即判断 X 的第一个元素是否是一维对象)
return (hasattr(X, "ndim") and X.ndim == 1 or isinstance(X, list)
and not hasattr(X[0], "__len__"))
if has_one_axis(X): # 如果 X 只有一个轴
X = [X] # 则将 X 放入一个列表中
if Y is None:
X, Y = [[]] * len(X), X # X、Y 的初始值都是包含有 len(X) 个空列表的列表
elif has_one_axis(Y): # 如果 Y 只有一个轴
Y = [Y]
if len(X) != len(Y): # 如果 X 和 Y 长度不等,则将 X 复制多份以匹配 Y 的长度
X = X * len(Y)
fig, ax = plt.subplots() # 创建一个新的图形窗口,并返回一个包含 FigFig 对象和 Axes 对象的元组(fig, ax)
# 遍历 X、Y 和 fmts 三个列表,并使用 zip 函数将它们中的元素一一对应起来
for x, y, fmt in zip(X, Y, fmts):
if len(x): # x 列表不为空
ax.plot(x, y, fmt) # 则调用 ax.plot() 函数绘制 x 和 y 之间的数据点,并使用 fmt 参数指定样式
else: # 如果 x 列表为空
ax.plot(y, fmt) # 则说明传入的是一个一维的 y 数组
set_axes(ax, xlabel, ylabel, xlim, ylim, xscale, yscale, legend)
plt.show() # 显示图形窗口
# 绘制函数 f(x) 及其在 x = 1 处的切线 y = 2x - 3,其中系数 2 是切线的斜率
x = np.arange(0, 3, 0.1) # 定义一个从 0 到 3,步长为 0.1 的数组 x
plot(x, [f(x), 2 * x - 3], 'x', 'f(x)', legend=['f(x)', 'Tangent line (x=1)'])
# 输出
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
在深度学习中,函数通常依赖于许多变量。因此,需要将微分的思想推广到多元函数
设 y = f ( x 1 , x 2 , … , x n ) y=f(x_{1},x_{2},\ldots,x_{n}) y=f(x1,x2,…,xn) 是一个具有 n n n个变量的函数。 y y y 关于第 i i i 个参数 x i x_i xi 的偏导数 (partial derivative) 为
∂ y ∂ x i = lim h → 0 f ( x 1 , … , x i − 1 , x i + h , x i + 1 , … , x n ) − f ( x 1 , … , x i , … , x n ) h \begin{aligned}\frac{\partial y}{\partial x_i}=\lim\limits_{h\to0}\frac{f(x_1,\ldots,x_{i-1},x_i+h,x_{i+1},\ldots,x_n)-f(x_1,\ldots,x_i,\ldots,x_n)}{h}\end{aligned} ∂xi∂y=h→0limhf(x1,…,xi−1,xi+h,xi+1,…,xn)−f(x1,…,xi,…,xn)
偏导数的几个等价符号
∂ y ∂ x i = ∂ f ∂ x i = f x i = f i = D i f = D x i f \begin{aligned}\frac{\partial y}{\partial x_i}=\frac{\partial f}{\partial x_i}=f_{x_i}=f_i=D_if=D_{x_i}f\end{aligned} ∂xi∂y=∂xi∂f=fxi=fi=Dif=Dxif
∇ x x ⊤ A = A \nabla_{\mathbf{x}}\mathbf{x}^{\top}\mathbf{A}=\mathbf{A} ∇xx⊤A=A
∇ x x ⊤ A x = ( A + A ⊤ ) x \nabla_{\mathbf{x}}\mathbf{x}^\top\mathbf{A}\mathbf{x}=(\mathbf{A}+\mathbf{A}^\top)\mathbf{x} ∇xx⊤Ax=(A+A⊤)x
∇ x ∥ x ∥ 2 = ∇ x x ⊤ x = 2 x \nabla_x\|\mathbf{x}\|^2=\nabla_x\mathbf{x}^\top\mathbf{x}=2\mathbf{x} ∇x∥x∥2=∇xx⊤x=2x
import torch
# 计算 y 关于 x 的梯度之前,需要⼀个地方来存储梯度,不会在每次对一个参数求导时都分配新的内存
# 一个标量函数关于向量 x 的梯度是向量,并且与 x 具有相同的形状
x = torch.arange(4.0, requires_grad=True) # 等价于 x.requires_grad_(True)
y = 2 * torch.dot(x, x)
y.backward() # 调用反向传播函数来自动计算 y 关于 x 每个分量的梯度
print(y)
print(x.grad) # 默认值是 None
print(x.grad == 4 * x) # y = 2(x^T)x 关于 x 的梯度应为 4x,此行代码为验证梯度计算是否正确
# 输出
tensor(28., grad_fn=<MulBackward0>)
tensor([ 0., 4., 8., 12.])
tensor([True, True, True, True])
import torch
x = torch.arange(4.0, requires_grad=True)
y = 2 * torch.dot(x, x)
y.backward()
# 对⾮标量调⽤ backward 需要传⼊⼀个 gradient 参数,该参数指定微分函数关于 self 的梯度
x.grad.zero_() # 在默认情况下,PyTorch 会累积梯度,因此需要清除之前的值
y = x * x
# 给定 torch.ones(len(x)) 作为反向传播的输入,意味着将一个与 x 的长度相同的全 1 向量作为 y 对于自己的导数进行反向传播
# 根据链式法则,这相当于求 dy/dy,即对 y 求导。由于 y 是一个函数,因此返回值将是与 x 的导数相关的向量
y.sum().backward() # 等价于 y.backward(torch.ones(len(x)))
print(x.grad)
# 输出
tensor([0., 2., 4., 6.])
import torch
def f(a):
b = a * 2
while b.norm() < 1000:
b = b * 2
if b.sum() > 0:
c = b
else:
c = 100 * b
return c
a = torch.randn(size=(), requires_grad=True)
d = f(a)
d.backward()
print(a.grad == d / a)
# 输出
tensor(True)
import torch
from torch.distributions import multinomial
fair_probs = torch.ones([6]) / 6
counts = multinomial.Multinomial(1000, fair_probs).sample()
print(counts / 1000) # 相对频率作为估计值
# 输出(每个结果的真实概率约为 0.167)
tensor([0.1600, 0.1840, 0.1700, 0.1650, 0.1670, 0.1540])
import torch
import matplotlib.pyplot as plt
from torch.distributions import multinomial
fair_probs = torch.ones([6]) / 6
counts = multinomial.Multinomial(10, fair_probs).sample((500,))
cum_counts = counts.cumsum(dim=0)
estimates = cum_counts / cum_counts.sum(dim=1, keepdim=True)
def set_figsize(figsize=(6, 4.5)):
plt.rcParams['figure.figsize'] = figsize
for i in range(6):
plt.plot(estimates[:, i].numpy(), label=("P(die=" + str(i + 1) + ")"))
set_figsize((6, 4.5))
plt.axhline(y=0.167, color='black', linestyle='dashed')
plt.xlabel('Groups of experiments')
plt.ylabel('Estimated probability')
plt.legend()
plt.show() # plt.legend() 需要调用 plt.show() 来显示图例
在掷散子的随机实验中,引入了随机变量 (random variable) 概念。随机变量几乎可以是任何数量,并且它可以在随机实验的一组可能性中取一个值。考虑一个随机变量 X X X,其值在掷骰子的样本空间 S = { 1 , 2 , 3 , 4 , 5 , 6 } \mathcal{S}=\{1,2,3,4,5,6\} S={1,2,3,4,5,6} 中。可以将事件 “看到一个5” 表示为 { X = 5 } {\{X = 5\}} {X=5} 或 X = 5 X = 5 X=5,其概率表示为 P ( { X = 5 } ) P(\{X=5\}) P({X=5}) 或 P ( X = 5 ) P(X=5) P(X=5)
由于概率论中的事件是来自样本空间的一组结果,因此可以为随机变量指定值的可取范围。例如, P ( 1 ≤ X ≤ 3 ) P(1\leq X\leq3) P(1≤X≤3) 表示事件 { 1 ≤ X ≤ 3 } \{1\leq X\leq3\} {1≤X≤3},即 { X = 1 , 2 , o r , 3 } \{X=1,2,\mathrm{or},3\} {X=1,2,or,3} 的概率。等价地, P ( 1 ≤ X ≤ 3 ) P(1\leq X\leq3) P(1≤X≤3) 表示随机变量 X X X 从 { 1 , 2 , 3 } \{1,2,3\} {1,2,3} 中取值的概率
离散(discrete)随机变量(如骰⼦的每⼀⾯)和连续(continuous)随机变量(如人的体重和身高)之间存在微妙的区别,以下小节主要考虑离散空间中的概率,连续随机变量的概率参考:连续随机变量
P ( A , B ) = P ( A ∣ B ) P ( B ) P(A,B)=P(A\mid B)P(B) P(A,B)=P(A∣B)P(B)
P ( A , B ) P(A,B) P(A,B) 是一个联合分布, P ( A ∣ B ) P(A\mid B) P(A∣B) 是一个条件分布
为了概括概率分布的关键特征,需要一些测量方法。一个随机变量 X X X 的期望 (expectation,或平均值 (average))
E [ X ] = ∑ x x P ( X = x ) E[X]=\sum_xxP(X=x) E[X]=x∑xP(X=x)
当函数 f ( x ) f(x) f(x) 的输入是从分布 P P P 中抽取的随机变量时, f ( x ) f(x) f(x) 的期望值为
E x ∼ P [ f ( x ) ] = ∑ x f ( x ) P ( x ) E_{x\sim P}[f(x)]=\sum_xf(x)P(x) Ex∼P[f(x)]=x∑f(x)P(x)
在许多情况下,希望衡量随机变量 X X X 与其期望值的偏置,这可以通过方差来量化
V a r [ X ] = E [ ( X − E [ X ] ) 2 ] = E [ X 2 ] − E [ X ] 2 \mathrm{Var}[X]=E\left[(X-E[X])^2\right]=E[X^2]-E[X]^2 Var[X]=E[(X−E[X])2]=E[X2]−E[X]2
方差的平方根被称为标准差 (standard deviation)
随机变量函数的方差衡量的是:当从该随机变量分布中采样不同值 x x x 时,函数值偏离该函数的期望的程度
Var [ f ( x ) ] = E [ ( f ( x ) − E [ f ( x ) ] ) 2 ] \operatorname{Var}[f(x)]=E\left[\left(f(x)-E[f(x)]\right)^2\right] Var[f(x)]=E[(f(x)−E[f(x)])2]