首先介绍一些核心组件。无论什么类型的机器学习问题,都会遇到这些组件:
可以用来学习的数据(data);
如何转换数据的模型(model);
一个目标函数(objective function),用来量化模型的有效性;
调整模型参数以优化目标函数的算法(algorithm)。
仅仅拥有海量的数据是不够的,我们还需要正确的数据。 如果数据中充满了错误,或者如果数据的特征不能预测任务目标,那么模型很可能无效。 有一句古语很好地反映了这个现象:“输入的是垃圾,输出的也是垃圾。”
大多数机器学习会涉及到数据的转换。 比如一个“摄取照片并预测笑脸”的系统。再比如通过摄取到的一组传感器读数预测读数的正常与异常程度。 虽然简单的模型能够解决如上简单的问题,但本书中关注的问题超出了经典方法的极限。 深度学习与经典方法的区别主要在于:前者关注的功能强大的模型,这些模型由神经网络错综复杂的交织在一起,包含层层数据转换,因此被称为深度学习(deep learning)。 在讨论深度模型的过程中,本书也将提及一些传统方法。
前面的内容将机器学习介绍为“从经验中学习”。 这里所说的“学习”,是指自主提高模型完成某些任务的效能。 但是,什么才算真正的提高呢? 在机器学习中,我们需要定义模型的优劣程度的度量,这个度量在大多数情况是“可优化”的,这被称之为目标函数(objective function)。 我们通常定义一个目标函数,并希望优化它到最低点。 因为越低越好,所以这些函数有时被称为损失函数(loss function,或cost function)。 但这只是一个惯例,我们也可以取一个新的函数,优化到它的最高点。 这两个函数本质上是相同的,只是翻转一下符号。
当任务在试图预测数值时,最常见的损失函数是平方误差(squared error),即预测值与实际值之差的平方。 当试图解决分类问题时,最常见的目标函数是最小化错误率,即预测与实际情况不符的样本比例。 有些目标函数(如平方误差)很容易被优化,有些目标(如错误率)由于不可微性或其他复杂性难以直接优化。 在这些情况下,通常会优化替代目标。
通常,损失函数是根据模型参数定义的,并取决于数据集。 在一个数据集上,我们可以通过最小化总损失来学习模型参数的最佳值。 该数据集由一些为训练而收集的样本组成,称为训练数据集(training dataset,或称为训练集(training set))。 然而,在训练数据上表现良好的模型,并不一定在“新数据集”上有同样的性能,这里的“新数据集”通常称为测试数据集(test dataset,或称为测试集(test set))。
综上所述,可用数据集通常可以分成两部分:训练数据集用于拟合模型参数,测试数据集用于评估拟合的模型。 然后我们观察模型在这两部分数据集的性能。 “一个模型在训练数据集上的性能”可以被想象成“一个学生在模拟考试中的分数”。 这个分数用来为一些真正的期末考试做参考,即使成绩令人鼓舞,也不能保证期末考试成功。 换言之,测试性能可能会显著偏离训练性能。 当一个模型在训练集上表现良好,但不能推广到测试集时,这个模型被称为过拟合(overfitting)的。 就像在现实生活中,尽管模拟考试考得很好,真正的考试不一定百发百中。
当我们获得了一些数据源及其表示、一个模型和一个合适的损失函数,接下来就需要一种算法,它能够搜索出最佳参数,以最小化损失函数。 深度学习中,大多流行的优化算法通常基于一种基本方法–梯度下降(gradient descent)。 简而言之,在每个步骤中,梯度下降法都会检查每个参数,看看如果仅对该参数进行少量变动,训练集损失会朝哪个方向移动。 然后,它在可以减少损失的方向上优化参数。
监督学习(supervised learning)擅长在“给定输入特征”的情况下预测标签。 每个“特征-标签”对都称为一个样本(example)。 有时,即使标签是未知的,样本也可以指代输入特征。 我们的目标是生成一个模型,能够将任何输入特征映射到标签(即预测)。
举一个具体的例子: 假设我们需要预测患者的心脏病是否会发作,那么观察结果“心脏病发作”或“心脏病没有发作”将是样本的标签。 输入特征可能是生命体征,如心率、舒张压和收缩压等。
监督学习之所以能发挥作用,是因为在训练参数时,我们为模型提供了一个数据集,其中每个样本都有真实的标签。 用概率论术语来说,我们希望预测“估计给定输入特征的标签”的条件概率。 虽然监督学习只是几大类机器学习问题之一,但是在工业中,大部分机器学习的成功应用都使用了监督学习。 这是因为在一定程度上,许多重要的任务可以清晰地描述为,在给定一组特定的可用数据的情况下,估计未知事物的概率。
监督学习的学习过程一般可以分为三大步骤:
从已知大量数据样本中随机选取一个子集,为每个样本获取真实标签。有时,这些样本已有标签(例如,患者是否在下一年内康复?);有时,这些样本可能需要被人工标记(例如,图像分类)。这些输入和相应的标签一起构成了训练数据集;
选择有监督的学习算法,它将训练数据集作为输入,并输出一个“已完成学习的模型”;
将之前没有见过的样本特征放到这个“已完成学习的模型”中,使用模型的输出作为相应标签的预测。
回归:回归问题是预测连续值的问题。例如,预测房价、股票价格或者人的身高等。这些都是连续的数值,我们的目标是找到输入特征和连续目标值之间的关系。例如,我们可能会使用房屋的面积、位置、建造年份等特征来预测房价。
分类:分类问题是预测离散值的问题。例如,判断一封电子邮件是垃圾邮件还是非垃圾邮件,或者判断一张图片是猫还是狗。在这些情况下,我们的目标是根据输入特征将样本分到两个或更多的类别中。
标记问题:标记问题是关于对象的多个属性的预测,这些属性并不是互斥的。例如,在自然语言处理中,我们可能需要标记句子中的每个词的词性(名词、动词、形容词等)。在这种情况下,每个词可以有多个标签。
搜索:在监督学习中,搜索可以被看作是学习一种策略,以在大量可能的解决方案中找到最好的一个。例如,棋类游戏的AI,它需要在每一步中决定最佳的移动。
推荐系统:推荐系统是一种信息过滤系统,用于预测用户对项目的“评分”或“偏好”。例如,根据用户过去的购买历史、浏览历史等信息,预测用户可能喜欢哪些新产品或服务。例如,Netflix推荐系统会根据用户过去观看的电影来推荐可能喜欢的新电影。
到目前为止,所有的例子都与监督学习有关,即需要向模型提供巨大数据集:每个样本包含特征和相应标签值。 打趣一下,“监督学习”模型像一个打工仔,有一份极其专业的工作和一位极其平庸的老板。 老板站在身后,准确地告诉模型在每种情况下应该做什么,直到模型学会从情况到行动的映射。 取悦这位老板很容易,只需尽快识别出模式并模仿他们的行为即可。
相反,如果工作没有十分具体的目标,就需要“自发”地去学习了。 比如,老板可能会给我们一大堆数据,然后要求用它做一些数据科学研究,却没有对结果有要求。 这类数据中不含有“目标”的机器学习问题通常被为无监督学习(unsupervised learning), 本书后面的章节将讨论无监督学习技术。 那么无监督学习可以回答什么样的问题呢?来看看下面的例子。
聚类(clustering)问题:没有标签的情况下,我们是否能给数据分类呢?比如,给定一组照片,我们能把它们分成风景照片、狗、婴儿、猫和山峰的照片吗?同样,给定一组用户的网页浏览记录,我们能否将具有相似行为的用户聚类呢?
主成分分析(principal component analysis)问题:我们能否找到少量的参数来准确地捕捉数据的线性相关属性?比如,一个球的运动轨迹可以用球的速度、直径和质量来描述。再比如,裁缝们已经开发出了一小部分参数,这些参数相当准确地描述了人体的形状,以适应衣服的需要。另一个例子:在欧几里得空间中是否存在一种(任意结构的)对象的表示,使其符号属性能够很好地匹配?这可以用来描述实体及其关系,例如“罗马” − “意大利” + “法国” = “巴黎”。
因果关系(causality)和概率图模型(probabilistic graphical models)问题:我们能否描述观察到的许多数据的根本原因?例如,如果我们有关于房价、污染、犯罪、地理位置、教育和工资的人口统计数据,我们能否简单地根据经验数据发现它们之间的关系?
生成对抗性网络(generative adversarial networks):为我们提供一种合成数据的方法,甚至像图像和音频这样复杂的非结构化数据。潜在的统计机制是检查真实和虚假数据是否相同的测试,它是无监督学习的另一个重要而令人兴奋的领域。
有人一直心存疑虑:机器学习的输入(数据)来自哪里?机器学习的输出又将去往何方? 到目前为止,不管是监督学习还是无监督学习,我们都会预先获取大量数据,然后启动模型,不再与环境交互。 这里所有学习都是在算法与环境断开后进行的,被称为离线学习(offline learning)。 对于监督学习,从环境中收集数据的过程类似于 图1.3.6。
如果你对使用机器学习开发与环境交互并采取行动感兴趣,那么最终可能会专注于强化学习(reinforcement learning)。 这可能包括应用到机器人、对话系统,甚至开发视频游戏的人工智能(AI)。 深度强化学习(deep reinforcement learning)将深度学习应用于强化学习的问题,是非常热门的研究领域。 突破性的深度Q网络(Q-network)在雅达利游戏中仅使用视觉输入就击败了人类, 以及 AlphaGo 程序在棋盘游戏围棋中击败了世界冠军,是两个突出强化学习的例子。
在强化学习问题中,智能体(agent)在一系列的时间步骤上与环境交互。 在每个特定时间点,智能体从环境接收一些观察(observation),并且必须选择一个动作(action),然后通过某种机制(有时称为执行器)将其传输回环境,最后智能体从环境中获得奖励(reward)。 此后新一轮循环开始,智能体接收后续观察,并选择后续操作,依此类推。 强化学习的过程在 图1.3.7 中进行了说明。 请注意,强化学习的目标是产生一个好的策略(policy)。 强化学习智能体选择的“动作”受策略控制,即一个从环境观察映射到行动的功能。
强化学习框架的通用性十分强大。 例如,我们可以将任何监督学习问题转化为强化学习问题。 假设我们有一个分类问题,可以创建一个强化学习智能体,每个分类对应一个“动作”。 然后,我们可以创建一个环境,该环境给予智能体的奖励。 这个奖励与原始监督学习问题的损失函数是一致的。
当然,强化学习还可以解决许多监督学习无法解决的问题。 例如,在监督学习中,我们总是希望输入与正确的标签相关联。 但在强化学习中,我们并不假设环境告诉智能体每个观测的最优动作。 一般来说,智能体只是得到一些奖励。 此外,环境甚至可能不会告诉是哪些行为导致了奖励。
以强化学习在国际象棋的应用为例。 唯一真正的奖励信号出现在游戏结束时:当智能体获胜时,智能体可以得到奖励1;当智能体失败时,智能体将得到奖励-1。 因此,强化学习者必须处理学分分配(credit assignment)问题:决定哪些行为是值得奖励的,哪些行为是需要惩罚的。 就像一个员工升职一样,这次升职很可能反映了前一年的大量的行动。 要想在未来获得更多的晋升,就需要弄清楚这一过程中哪些行为导致了晋升。
强化学习可能还必须处理部分可观测性问题。 也就是说,当前的观察结果可能无法阐述有关当前状态的所有信息。 比方说,一个清洁机器人发现自己被困在一个许多相同的壁橱的房子里。 推断机器人的精确位置(从而推断其状态),需要在进入壁橱之前考虑它之前的观察结果。
最后,在任何时间点上,强化学习智能体可能知道一个好的策略,但可能有许多更好的策略从未尝试过的。 强化学习智能体必须不断地做出选择:是应该利用当前最好的策略,还是探索新的策略空间(放弃一些短期回报来换取知识)。
机器学习研究计算机系统如何利用经验(通常是数据)来提高特定任务的性能。它结合了统计学、数据挖掘和优化的思想。通常,它是被用作实现人工智能解决方案的一种手段。
表示学习作为机器学习的一类,其研究的重点是如何自动找到合适的数据表示方式。深度学习是通过学习多层次的转换来进行的多层次的表示学习。
深度学习不仅取代了传统机器学习的浅层模型,而且取代了劳动密集型的特征工程。
最近在深度学习方面取得的许多进展,大都是由廉价传感器和互联网规模应用所产生的大量数据,以及(通过GPU)算力的突破来触发的。
整个系统优化是获得高性能的关键环节。有效的深度学习框架的开源使得这一点的设计和实现变得非常容易。
为了能够完成各种数据操作,我们需要某种方法来存储和操作数据。 通常,我们需要做两件重要的事:(1)获取数据;(2)将数据读入计算机后对其进行处理。 如果没有某种方法来存储数据,那么获取数据是没有意义的。
首先,我们介绍n维数组,也称为张量(tensor)。 使用过Python中NumPy计算包的读者会对本部分很熟悉。 无论使用哪个深度学习框架,它的张量类(在MXNet中为ndarray
, 在PyTorch和TensorFlow中为Tensor
)都与Numpy的ndarray
类似。 但深度学习框架又比Numpy的ndarray
多一些重要功能: 首先,GPU很好地支持加速计算,而NumPy仅支持CPU计算; 其次,张量类支持自动微分。 这些功能使得张量类更适合深度学习。 如果没有特殊说明,本书中所说的张量均指的是张量类的实例。
自动微分(Automatic Differentiation,AD)是一种计算导数的技术
假设我们有以下函数:
f(x) = x^2 + 2x + 1
我们想要计算 f(x) 在 x=2 处的导数。手动计算的结果为:
f'(x) = 2x + 2
f'(2) = 6
现在我们可以使用自动微分来计算导数。以下是使用 TensorFlow 中的张量类来实现自动微分的示例代码:
import tensorflow as tf
# 定义变量 x,赋值为 2
x = tf.Variable(2.0)
# 定义函数 f(x)
def f(x):
return x**2 + 2*x + 1
# 使用 TensorFlow 中的 GradientTape 记录梯度信息
with tf.GradientTape() as tape:
# 计算函数值
y = f(x)
# 计算导数
dy_dx = tape.gradient(y, x)
# 打印导数值
print(dy_dx)
运行上述代码,输出结果为:
tf.Tensor(6.0, shape=(), dtype=float32)
导入torch
import torch
x = torch.arange(12)
x
tensor([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
print(x.shape)
print(x.numel())
x=x.reshape(3, 4)
print(x.shape)
print(x.numel())
torch.Size([12])
12
torch.Size([3, 4])
12
随机值
torch.randn(3,4)
tensor([[ 0.5627, -0.0208, 0.7325, 0.4197],
[ 1.6485, -2.6882, -2.7821, 0.7676],
[ 0.8092, 0.0832, 1.0177, 0.6758]])
x = torch.tensor([1.0, 2, 4, 8])
y = torch.tensor([2, 2, 2, 2])
x + y, x - y, x * y, x / y, x ** y # **运算符是求幂运算
torch.exp(x)
除了按元素计算外,我们还可以执行线性代数运算,包括向量点积和矩阵乘法。 我们将在 2.3节中解释线性代数的重点内容。
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]])
print(X.shape,Y.shape)
print(torch.cat((X, Y), dim=0).shape, torch.cat((X, Y), dim=1).shape)
torch.Size([3, 4]) torch.Size([3, 4])
torch.Size([6, 4]) torch.Size([3, 8])
广播机制:
x=torch.arange(6).reshape(1,6)
y=torch.arange(6).reshape(6,1)
x,y
(tensor([[0, 1, 2, 3, 4, 5]]),
tensor([[0],
[1],
[2],
[3],
[4],
[5]]))
tensor([[ 0, 1, 2, 3, 4, 5],
[ 1, 2, 3, 4, 5, 6],
[ 2, 3, 4, 5, 6, 7],
[ 3, 4, 5, 6, 7, 8],
[ 4, 5, 6, 7, 8, 9],
[ 5, 6, 7, 8, 9, 10]])
索引与切片:起始:结束:步长;逗号区分维度
x[0,0:6:2]
tensor([0, 2, 4])
节省内存:x+=y
转换为其他Python对象
x=torch.arange(6).reshape(1,6)
y=x.numpy()
type(x),type(y)
(torch.Tensor, numpy.ndarray)
2.2.1. 读取数据集
创建数据集:
import os
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')
# 如果没有安装pandas,只需取消对以下行的注释来安装pandas,在Jupyter Notebook
# !pip install pandas
import pandas as pd
data = pd.read_csv(data_file)
data
NumRooms Alley Price
0 NaN Pave 127500
1 2.0 NaN 106000
2 4.0 NaN 178100
3 NaN NaN 140000
处理缺失值
注意,“NaN”项代表缺失值。 为了处理缺失的数据,典型的方法包括插值法和删除法, 其中插值法用一个替代值弥补缺失值,而删除法则直接忽略缺失值。 在这里,我们将考虑插值法。
inputs, outputs = data.iloc[:, 0:2], data.iloc[:, 2]
inputs = inputs.fillna(inputs.mean())
inputs
NumRooms Alley
0 3.0 Pave
1 2.0 NaN
2 4.0 NaN
3 3.0 NaN
对于inputs
中的类别值或离散值,我们将“NaN”视为一个类别。 由于“巷子类型”(“Alley”)列只接受两种类型的类别值“Pave”和“NaN”, pandas
可以自动将此列转换为两列“Alley_Pave”和“Alley_nan”。 巷子类型为“Pave”的行会将“Alley_Pave”的值设置为1,“Alley_nan”的值设置为0。 缺少巷子类型的行会将“Alley_Pave”和“Alley_nan”分别设置为0和1。
inputs = pd.get_dummies(inputs, dummy_na=True)
print(inputs)
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
转换为张量格式
import torch
X, y = torch.tensor(inputs.values), torch.tensor(outputs.values)
X, y
向量点乘:
A·B = A1B1 + A2B2 + ... + AnBn
正交 A·B=0
范数:
import torch
x = torch.tensor(3.0)
y = torch.tensor(2.0)
x = torch.arange(4)
tensor([0, 1, 2, 3])
a=torch.arange(10).reshape(2,5)
tensor([[0, 1, 2, 3, 4],
[5, 6, 7, 8, 9]])
转置:行列互换
a.T
tensor([[0, 5],
[1, 6],
[2, 7],
[3, 8],
[4, 9]])
对称矩阵:
降维:默认情况下,调用求和函数会沿所有的轴降低张量的维度,使它变为一个标量。 我们还可以指定张量沿哪一个轴来通过求和降低维度。 以矩阵为例,为了通过求和所有行的元素来降维(轴0),可以在调用函数时指定axis=0
。 由于输入矩阵沿0轴降维以生成输出向量,因此输入轴0的维数在输出形状中消失。
A = torch.arange(20, dtype=torch.float32).reshape(5, 4)
A_sum_axis0 = A.sum(axis=0)
A_sum_axis0, A_sum_axis0.shape
tensor([[ 0., 1., 2., 3.],
[ 4., 5., 6., 7.],
[ 8., 9., 10., 11.],
[12., 13., 14., 15.],
[16., 17., 18., 19.]])
(tensor([40., 45., 50., 55.]), torch.Size([4]))
指定axis=1将通过汇总所有列的元素降维(轴1)。因此,输入轴1的维数在输出形状中消失。
A_sum_axis1 = A.sum(axis=1)
A_sum_axis1, A_sum_axis1.shape
(tensor([ 6., 22., 38., 54., 70.]), torch.Size([5]))
一个与求和相关的量是平均值(mean或average)
(tensor(9.5000), tensor(9.5000))
同样,计算平均值的函数也可以沿指定轴降低张量的维度。
A.mean(axis=0), A.sum(axis=0) / A.shape[0]
非降维求和
A.sum(axis=1, keepdims=True),A.sum(axis=1)
(tensor([[ 6.],
[22.],
[38.],
[54.],
[70.]]),
tensor([ 6., 22., 38., 54., 70.]))
沿某个轴计算的累积总和
A.cumsum(axis=1)
tensor([[ 0., 1., 3., 6.],
[ 4., 9., 15., 22.],
[ 8., 17., 27., 38.],
[12., 25., 39., 54.],
[16., 33., 51., 70.]])
补充,axis选哪个维度,哪个维度就没了、keepdim
x = torch.arange(24).reshape(2,3,4)
x0=x.sum(axis=0)
x1=x.sum(axis=1)
x.shape,x0.shape,x1.shape,x0,x1
(torch.Size([2, 3, 4]),
torch.Size([3, 4]),
torch.Size([2, 4]),
tensor([[12, 14, 16, 18],
[20, 22, 24, 26],
[28, 30, 32, 34]]),
tensor([[12, 15, 18, 21],
[48, 51, 54, 57]]))
x0=x.sum(axis=0,keepdim=True)
x1=x.sum(axis=1,keepdim=True)
torch.Size([1, 3, 4]),
torch.Size([2, 1, 4]),
x0=x.sum(axis=[0,1])
x1=x.sum(axis=[1,2])
torch.Size([4]),
torch.Size([2]),
x = torch.arange(5, dtype = torch.float32)
y = torch.arange(5, dtype = torch.float32)
x, y, torch.dot(x, y) #点积
(tensor([0., 1., 2., 3., 4.]), tensor([0., 1., 2., 3., 4.]), tensor(30.))
矩阵-向量积(matrix-vector product)
x = torch.arange(5)
A=torch.arange(15).reshape(3,5)
x.shape,A.shape,torch.mv(A, x)
(torch.Size([5]), torch.Size([3, 5]), tensor([ 30, 80, 130]))
矩阵-矩阵乘法(matrix-matrix multiplication)
A=torch.arange(16).reshape(4,4)
torch.mm(A, A)
tensor([[ 56, 62, 68, 74],
[152, 174, 196, 218],
[248, 286, 324, 362],
[344, 398, 452, 506]])
线性代数中最有用的一些运算符是范数(norm)。 非正式地说,向量的范数是表示一个向量有多大。 这里考虑的大小(size)概念不涉及维度,而是分量的大小。
在线性代数中,向量范数是将向量映射到标量的函数f。 给定任意向量x,向量范数要满足一些属性。 第一个性质是:如果我们按常数因子缩放向量的所有元素, 其范数也会按相同常数因子的绝对值缩放:
第二个性质是熟悉的三角不等式:
第三个性质简单地说范数必须是非负的:
这是有道理的。因为在大多数情况下,任何东西的最小的大小是0。 最后一个性质要求范数最小为0,当且仅当向量全由0组成。
范数听起来很像距离的度量。欧几里得距离和毕达哥拉斯定理中的非负性概念和三角不等式可能会给出一些启发。事实上,欧几里得距离是一个L2范数:假设n维向量x中的元素是x1,...,xn,其L2范数是向量元素平方和的平方根。
深度学习中更经常地使用L2范数的平方,也会经常遇到L1范数,它表示为向量元素的绝对值之和:
torch.abs(x).sum()
L2范数和L1范数都是更一般的Lp范数的特例:
类似于向量的L2范数,矩阵的Frobenius范数(Frobenius norm)是矩阵元素平方和的平方根:
Frobenius范数满足向量范数的所有性质,它就像是矩阵形向量的L2范数。 调用以下函数将计算矩阵的Frobenius范数。
范数和目标
在深度学习中,我们经常试图解决优化问题: 最大化分配给观测数据的概率; 最小化预测和真实观测之间的距离。 用向量表示物品(如单词、产品或新闻文章),以便最小化相似项目之间的距离,最大化不同项目之间的距离。 目标,或许是深度学习算法最重要的组成部分(除了数据),通常被表达为范数。
小结
标量、向量、矩阵和张量是线性代数中的基本数学对象。
向量泛化自标量,矩阵泛化自向量。
标量、向量、矩阵和张量分别具有零、一、二和任意数量的轴。
一个张量可以通过sum
和mean
沿指定的轴降低维度。
两个矩阵的按元素乘法被称为他们的Hadamard积。它与矩阵乘法不同。
在深度学习中,我们经常使用范数,如L1范数、L2范数和Frobenius范数。
我们可以对标量、向量、矩阵和张量执行各种操作。
练习
证明一个矩阵的转置的转置是它本身,即(A^T)^T=A。
a = torch.arange(8).reshape(2,4)
a,a.T,a.T.T
(tensor([[0, 1, 2, 3],
[4, 5, 6, 7]]),
tensor([[0, 4],
[1, 5],
[2, 6],
[3, 7]]),
tensor([[0, 1, 2, 3],
[4, 5, 6, 7]]))
给出两个矩阵A和B,证明“它们转置的和”等于“它们和的转置”,即(A^T+B^T)=(A+B)^T。
a = torch.arange(6).reshape(2,3)
b = torch.arange(6,0,-1).reshape(2,3)
a,b,(a+b).T,a.T+b.T
(tensor([[0, 1, 2],
[3, 4, 5]]),
tensor([[6, 5, 4],
[3, 2, 1]]),
tensor([[6, 6],
[6, 6],
[6, 6]]),
tensor([[6, 6],
[6, 6],
[6, 6]]))
给定任意方阵A,A+A^T总是对称的吗?为什么?
不一定,因为广播机制
a = torch.arange(6).reshape(1,6)
b = torch.arange(6,0,-1).reshape(6,1)
a,a.T,a+a.T
本节中定义了形状(2,3,4)的张量X。len(X)的输出结果是什么?
a = torch.arange(24).reshape(2,3,4)
len(a)
2
竟然是第一个维度
对于任意形状的张量X,len(X)是否总是对应于X特定轴的长度?这个轴是什么?
是
运行A/A.sum(axis=1),看看会发生什么。请分析一下原因?
报错了:RuntimeError: The size of tensor a (4) must match the size of tensor b (3) at non-singleton dimension 1
考虑一个具有形状(2,3,4)的张量,在轴0、1、2上的求和输出是什么形状?
x = torch.arange(24).reshape(2,3,4)
x[0],x[0,0],x[0,0,0]
(tensor([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]]),
tensor([0, 1, 2, 3]),
tensor(0))
为linalg.norm函数提供3个或更多轴的张量,并观察其输出。对于任意形状的张量这个函数计算得到什么?
范数 norm
torch.linalg.norm(A, ord=None, dim=None, keepdim=False, *, out=None, dtype=None)
A是要计算范数的张量,ord是范数的阶数,dim是要沿着哪些维度计算范数,keepdim表示是否保持维度不变,out表示输出张量,dtype表示输出张量的数据类型。如果ord和dim都不指定,则默认计算2范数。
# 计算向量的2范数
x = torch.randn(3)
norm_x = torch.linalg.norm(x)
# 计算矩阵的Frobenius范数
A = torch.randn(3, 4)
norm_A = torch.linalg.norm(A)
norm_x,norm_A
导数
总结一下:维度不变,分子不转分母转
假设我们有一个函数 f: R → R,其输入和输出都是标量。 如果 f 的导数存在,这个极限被定义为
让我们熟悉一下导数的几个等价符号。
给定 y=f(x),其中 x 和 y 分别是函数 f 的自变量和因变量。以下表达式是等价的:
为了对导数的这种解释进行可视化,我们将使用matplotlib
, 这是一个Python中流行的绘图库。 要配置matplotlib
生成图形的属性,我们需要定义几个函数。 在下面,use_svg_display
函数指定matplotlib
软件包输出svg图表以获得更清晰的图像。
注意,注释#@save
是一个特殊的标记,会将对应的函数、类或语句保存在d2l
包中。 因此,以后无须重新定义就可以直接调用它们(例如,d2l.use_svg_display()
)。
通过这三个用于图形配置的函数,定义一个plot
函数来简洁地绘制多条曲线, 因为我们需要在整个书中可视化许多曲线。
def use_svg_display(): #@save
"""使用svg格式在Jupyter中显示绘图"""
backend_inline.set_matplotlib_formats('svg')
def set_figsize(figsize=(4, 3)): #@save
"""设置matplotlib的图表大小"""
use_svg_display()
d2l.plt.rcParams['figure.figsize'] = figsize
#@save
def set_axes(axes, xlabel, ylabel, xlim, ylim, xscale, yscale, legend):
"""设置matplotlib的轴"""
axes.set_xlabel(xlabel)
axes.set_ylabel(ylabel)
axes.set_xscale(xscale)
axes.set_yscale(yscale)
axes.set_xlim(xlim)
axes.set_ylim(ylim)
if legend:
axes.legend(legend)
axes.grid()
#@save
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)
axes = axes if axes else d2l.plt.gca()
# 如果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__"))
if has_one_axis(X):
X = [X]
if Y is None:
X, Y = [[]] * len(X), X
elif has_one_axis(Y):
Y = [Y]
if len(X) != len(Y):
X = X * len(Y)
axes.cla()
for x, y, fmt in zip(X, Y, fmts):
if len(x):
axes.plot(x, y, fmt)
else:
axes.plot(y, fmt)
set_axes(axes, xlabel, ylabel, xlim, ylim, xscale, yscale, legend)
x = np.arange(0, 3, 0.1)
plot(x, [f(x), 2 * x - 3], 'x', 'f(x)', legend=['f(x)', 'Tangent line (x=1)'])
偏导数
设y=f(x₁,x₂,...,xₙ)是一个具有n个变量的函数。y关于第i个参数xi的偏导数(partial derivative)为:
为了计算 ∂y/∂xi,我们可以简单地将 xi 看作常数,并计算 dy/dxi。(只有xi+h,其他不变,再除以h)
对于偏导数的表示,以下是等价的:
梯度
我们可以连结一个多元函数对其所有变量的偏导数,以得到该函数的梯度(gradient)向量。具体而言,设函数 f: ℝ^n → ℝ 的输入是一个 n 维向量 x = [x₁,x₂,…,xₙ]ᵀ,并且输出是一个标量。函数 f 相对于 x 的梯度是一个包含 n 个偏导数的向量:
链式法则
然而,上面方法可能很难找到梯度。 这是因为在深度学习中,多元函数通常是复合(composite)的, 所以难以应用上述任何规则来微分这些函数。 幸运的是,链式法则可以被用来微分复合函数。
让我们先考虑单变量函数。假设函数 y = f(u) 和 u = g(x) 都是可微的,根据链式法则:
现在考虑一个更一般的场景,即函数具有任意数量的变量的情况。 假设可微分函数y有变量u₁,u₂,…,uₘ,其中每个可微分函数uᵢ都有变量x₁,x₂,…,xₙ。 注意,y是x₁,x₂,…,xₙ的函数。 对于任意i=1,2,…,n,链式法则给出:
小结
微分和积分是微积分的两个分支,前者可以应用于深度学习中的优化问题。
导数可以被解释为函数相对于其变量的瞬时变化率,它也是函数曲线的切线的斜率。
梯度是一个向量,其分量是多变量函数相对于其所有变量的偏导数。
链式法则可以用来微分复合函数。
作业
x = np.arange(0, 2, 0.1)
plot(x, [f(x), x **3 - 1/x], 'x', 'f(x)', legend=['f(x)', 'Tangent line (x=1)'])
6x1,5ex2
2x
深度学习框架通过自动计算导数,即自动微分(automatic differentiation)来加快求导。 实际中,根据设计好的模型,系统会构建一个计算图(computational graph), 来跟踪计算是哪些数据通过哪些操作组合起来产生输出。 自动微分使系统能够随后反向传播梯度。 这里,反向传播(backpropagate)意味着跟踪整个计算图,填充关于每个参数的偏导数。
例子
首先,我们创建变量x
并为其分配一个初始值。在我们计算y关于x的梯度之前,需要一个地方来存储梯度。 重要的是,我们不会在每次对一个参数求导时都分配新的内存。 因为我们经常会成千上万次地更新相同的参数,每次都分配新的内存可能很快就会将内存耗尽。 注意,一个标量函数关于向量x的梯度是向量,并且与x具有相同的形状。
import torch
x = torch.arange(4.0)
x.requires_grad_(True) # 等价于x=torch.arange(4.0,requires_grad=True)
x.grad,x # 默认值是None
(None, tensor([0., 1., 2., 3.], requires_grad=True))
# x是一个长度为4的向量,计算x和x的点积,得到了我们赋值给y的标量输出。 接下来,通过调用反向传播函数来自动计算y关于x每个分量的梯度,并打印这些梯度。 梯度 gradient; grad
y = 2 * torch.dot(x, x)
y
tensor(28., grad_fn=)
y.backward()
x.grad
tensor([ 0., 4., 8., 12.])
y.backward()
x.grad
# tensor([ 0., 8., 16., 24.])
# 函数y=2xᵀx关于向量x的梯度应为4x。
# 让我们快速验证这个梯度是否计算正确。
x.grad == 4 * x
# tensor([True, True, True, True])
# 现在计算x的另一个函数。
# 在默认情况下,PyTorch会累积梯度,我们需要清除之前的值
x.grad.zero_()
y = x.sum()
y.backward()
x.grad
# tensor([1., 1., 1., 1.])
非标量变量的反向传播
当y
不是标量时,向量y
关于向量x
的导数的最自然解释是一个矩阵。 对于高阶和高维的y
和x
,求导的结果可以是一个高阶张量。
然而,虽然这些更奇特的对象确实出现在高级机器学习中(包括深度学习中), 但当调用向量的反向计算时,我们通常会试图计算一批训练样本中每个组成部分的损失函数的导数。 这里,我们的目的不是计算微分矩阵,而是单独计算批量中每个样本的偏导数之和。
# 对非标量调用backward需要传入一个gradient参数,该参数指定微分函数关于self的梯度。
# 本例只想求偏导数的和,所以传递一个1的梯度是合适的
x.grad.zero_()
y = x * x
# 等价于y.backward(torch.ones(len(x)))
y.sum().backward()
x.grad
tensor([0., 2., 4., 6.])
分离计算
有时,我们希望将某些计算移动到记录的计算图之外。 例如,假设y
是作为x
的函数计算的,而z
则是作为y
和x
的函数计算的。 想象一下,我们想计算z
关于x
的梯度,但由于某种原因,希望将y
视为一个常数, 并且只考虑到x
在y
被计算后发挥的作用。
这里可以分离y
来返回一个新变量u
,该变量与y
具有相同的值, 但丢弃计算图中如何计算y
的任何信息。 换句话说,梯度不会向后流经u
到x
。 因此,下面的反向传播函数计算z=u*x
关于x
的偏导数,同时将u
作为常数处理, 而不是z=x*x*x
关于x
的偏导数。
x.grad.zero_()
y = x * x
u = y.detach()
z = u * x
z.sum().backward()
x,x.grad,u,x.grad == u
(tensor([0., 1., 2., 3.], requires_grad=True),
tensor([0., 1., 4., 9.]),
tensor([0., 1., 4., 9.]),
tensor([True, True, True, True]))
由于记录了y
的计算结果,我们可以随后在y
上调用反向传播, 得到y=x*x
关于的x
的导数,即2*x
。
x.grad.zero_()
y.sum().backward()
x.grad,x,x.grad == 2 * x
(tensor([0., 2., 4., 6.]),
tensor([0., 1., 2., 3.], requires_grad=True),
tensor([True, True, True, True]))
Python控制流的梯度计算
使用自动微分的一个好处是: 即使构建函数的计算图需要通过Python控制流(例如,条件、循环或任意函数调用),我们仍然可以计算得到的变量的梯度。 在下面的代码中,while
循环的迭代次数和if
语句的结果都取决于输入a
的值。
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()
a,a.grad ,d,a.grad == d / a
(tensor(0.1172, requires_grad=True),
tensor(16384.),
tensor(1919.7633, grad_fn=),
tensor(True))
深度学习框架可以自动计算导数:我们首先将梯度附加到想要对其计算偏导数的变量上,然后记录目标值的计算,执行它的反向传播函数,并访问得到的梯度。
练习
为什么计算二阶导数比一阶导数的开销要更大?
二阶导数是求一阶导数的导数
在运行反向传播函数之后,立即再次运行它,看看会发生什么。
# 练习
import torch
x = torch.arange(5.,requires_grad=True)
y = 2 * torch.dot(x**2,torch.ones_like(x))
y.backward()
print(x.grad,x)
y.backward()
RuntimeError: Trying to backward through the graph a second time (or directly access saved tensors after they have already been freed). Saved intermediate values of the graph are freed when you call .backward() or autograd.grad(). Specify retain_graph=True if you need to backward through the graph a second time or if you need to access saved tensors after calling backward.
这个错误通常出现在使用PyTorch进行自动求导(Autograd)时,它表示在计算反向传播时,尝试第二次遍历计算图或访问已经被释放的中间变量。这通常是由于在计算图中的某些操作被计算了多次,或者在计算图中的某些节点被多次使用时出现的。
torch.dot()
函数时,需要传递两个张量作为参数,这两个张量必须具有相同的形状
在控制流的例子中,我们计算d
关于a
的导数,如果将变量a
更改为随机向量或矩阵,会发生什么?
import torch
x = torch.randn(3, requires_grad=True)
y = torch.dot(x,x)
y.backward()
x.grad
import torch:导入PyTorch库,使得可以使用其中提供的函数和类。
x = torch.randn(3, requires_grad=True):创建一个形状为(3,)的随机向量x,并将requires_grad设置为True,表示需要对x进行自动求导。
y = torch.dot(x,x):计算向量x的平方和,并将结果赋值给变量y。
y.backward():对变量y进行自动求导,计算出对x的梯度,并将梯度值保存在x.grad中。
x.grad:输出x的梯度值。
因为y是一个标量,所以可以对它进行自动求导。在这个例子中,y的值是向量x的平方和,即x[0]^2 + x[1]^2 + x[2]^2。对y求导后,得到的梯度值就是一个和x具有相同形状的向量,其中每个元素的导数值等于对应的x元素的两倍。因此,x.grad的值就是一个长度为3的张量,其中每个元素的值等于对应的x元素的两倍。
在这段代码中,x是一个形状为(3,)的张量,它具有三个元素。y是一个标量,它的值是向量x的平方和,即:
y = x[0]^2 + x[1]^2 + x[2]^2
在进行自动求导之后,x.grad保存了y对每个元素x[i]的梯度值,即:
x.grad[i] = dy/dx[i]
其中,dy/dx[i]表示y对x[i]的偏导数。根据向量求导的规则,我们可以计算出:
dy/dx[i] = 2 * x[i]
因此,x.grad的值等于向量x中每个元素的两倍,即:
x.grad = [2x[0], 2x[1], 2*x[2]]
计算的梯度会得到不同的结果
重新设计一个求控制流梯度的例子,运行并分析结果。
一个简单的控制流例子是计算条件语句的加权和。假设有两个向量x和y,我们要计算它们的加权和,如果x中的元素大于y中的元素,则权重为1,否则为2。可以使用以下代码实现:
import torch
x = torch.tensor([1, 3, 2], dtype=torch.float32, requires_grad=True)
y = torch.tensor([2, 2, 1], dtype=torch.float32, requires_grad=True)
接着,我们定义变量s并初始化为0,然后使用一个循环遍历x和y中的元素,并根据条件语句计算加权和。在这个例子中,如果x[i]大于y[i],则将x[i]加入到s中,否则将2*y[i]加入到s中。
s = 0
for i in range(len(x)):
if x[i] > y[i]:
s = s + x[i]
else:
s = s + 2*y[i]
s.backward()
print(x.grad)
print(y.grad)
这些值表示x和y对应元素的梯度。例如,x.grad[0]的值为1,表示当x[0]增加一个很小的量时,加权和s也会增加一个很小的量。反之,如果将x[0]减少一个很小的量,则加权和s也会减少一个很小的量。同样地,y.grad[0]的值为2,表示当y[0]增加一个很小的量时,加权和s会减少两倍的这个量。这些梯度信息可以用来优化模型,以使得加权和最小化。
导数
tensor([0., 1., 1.])
tensor([2., 0., 0.])
使f(x)=sin(x),绘制f(x)和的图像,其中后者不使用f'(x)=\cos(x)
import torch
import matplotlib.pyplot as plt
import numpy as np
# 定义函数 f(x) = sin(x)
def f(x):
return torch.sin(x)
# 构造输入张量 x,并将 requires_grad 属性设置为 True
x = torch.tensor(np.linspace(-3*np.pi, 3*np.pi, 100), requires_grad=True)
# 计算函数 f(x) 的值
y = f(x)
# 计算函数 f(x) 对 x 的导数
y.backward(torch.ones_like(x))
# 绘制函数 f(x) 和其导数的图像
plt.plot(x.detach().numpy(), y.detach().numpy(), label="f(x)")
plt.plot(x.detach().numpy(), x.grad.detach().numpy(), label="f'(x)")
plt.legend()
plt.show()
或者
%matplotlib inline
import matplotlib.pylab as plt
from matplotlib.ticker import FuncFormatter, MultipleLocator
import numpy as np
import torch
f,ax=plt.subplots(1)
x = np.linspace(-3*np.pi, 3*np.pi, 100)
x1= torch.tensor(x, requires_grad=True)
y1= torch.sin(x1)
y1.sum().backward()
ax.plot(x,np.sin(x),label='sin(x)')
ax.plot(x,x1.grad,label="gradient of sin(x)")
ax.legend(loc='upper center', shadow=True)
ax.xaxis.set_major_formatter(FuncFormatter(
lambda val,pos: '{:.0g}\pi'.format(val/np.pi) if val !=0 else '0'
))
ax.xaxis.set_major_locator(MultipleLocator(base=np.pi))
plt.show()
大数定律(law of large numbers)告诉我们: 随着投掷次数的增加,这个估计值会越来越接近真实的潜在概率。
基本概率论
在统计学中,我们把从概率分布中抽取样本的过程称为抽样(sampling)。 笼统来说,可以把分布(distribution)看作对事件的概率分配, 稍后我们将给出的更正式定义。 将概率分配给一些离散选择的分布称为多项分布(multinomial distribution)。
为了抽取一个样本,即掷骰子,我们只需传入一个概率向量。 输出是另一个相同长度的向量:它在索引i处的值是采样结果中i出现的次数。
%matplotlib inline
import torch
from torch.distributions import multinomial
from d2l import torch as d2l
fair_probs = torch.ones([6]) / 6
torch.ones([6]) / 6 , multinomial.Multinomial(1, fair_probs).sample()
# multinomial.Multinomial函数创建了一个多项式分布的随机变量。该函数的第一个参数1表示我们只想要生成一个样本,第二个参数fair_probs是一个长度为6的张量,代表了多项式分布中每个可能的取值的概率。函数的返回值是一个长度为6的张量,其中只有一个元素为1,其余为0。这个张量表示了我们生成的随机变量的取值,即在6个可能的取值中随机选择一个。
(tensor([0.1667, 0.1667, 0.1667, 0.1667, 0.1667, 0.1667]),
tensor([1., 0., 0., 0., 0., 0.]))
# 在估计一个骰子的公平性时,我们希望从同一分布中生成多个样本。 如果用Python的for循环来完成这个任务,速度会慢得惊人。 因此我们使用深度学习框架的函数同时抽取多个样本,得到我们想要的任意形状的独立样本数组。
multinomial.Multinomial(100, fair_probs).sample()
# tensor([22., 16., 15., 18., 18., 11.])
# 现在我们知道如何对骰子进行采样,我们可以模拟1000次投掷。 然后,我们可以统计1000次投掷后,每个数字被投中了多少次。 具体来说,我们计算相对频率,以作为真实概率的估计。
# 将结果存储为32位浮点数以进行除法
counts = multinomial.Multinomial(1000, fair_probs).sample()
counts / 1000 # 相对频率作为估计值
# tensor([0.1760, 0.1650, 0.1690, 0.1640, 0.1560, 0.1700])
# 因为我们是从一个公平的骰子中生成的数据,我们知道每个结果都有真实的概率1/6, 大约是0.1667,所以上面输出的估计值看起来不错。
# 我们也可以看到这些概率如何随着时间的推移收敛到真实概率。 让我们进行500组实验,每组抽取10个样本。
counts = multinomial.Multinomial(10, fair_probs).sample((500,))
cum_counts = counts.cumsum(dim=0)
estimates = cum_counts / cum_counts.sum(dim=1, keepdims=True)
d2l.set_figsize((6, 4.5))
for i in range(6):
d2l.plt.plot(estimates[:, i].numpy(),
label=("P(die=" + str(i + 1) + ")"))
d2l.plt.axhline(y=0.167, color='black', linestyle='dashed')
d2l.plt.gca().set_xlabel('Groups of experiments')
d2l.plt.gca().set_ylabel('Estimated probability')
d2l.plt.legend();
概率论公理
在处理骰子掷出时,我们将集合S={1, 2, 3, 4, 5, 6}称为样本空间(sample space)或结果空间(outcome space),其中每个元素都是结果(outcome)。事件(event)是一组给定样本空间的随机结果。例如,“看到5”({5})和“看到奇数”({1, 3, 5})都是掷出骰子的有效事件。注意,如果一个随机实验的结果在A中,则事件A已经发生。也就是说,如果投掷出3点,因为3∈{1, 3, 5},我们可以说,“看到奇数”的事件发生了。
概率(probability)可以被认为是将集合映射到真实值的函数。在给定的样本空间S中,事件A的概率,表示为P(A),满足以下属性:
以上也是概率论的公理,由科尔莫戈罗夫于1933年提出。有了这个公理系统,我们可以避免任何关于随机性的哲学争论;相反,我们可以用数学语言严格地推理。例如,假设事件A1为整个样本空间,且当所有i>1时的Ai=∅,那么我们可以证明P(∅)=0,即不可能发生事件的概率是0。
随机变量
在我们掷骰子的随机实验中,我们引入了随机变量(random variable)的概念。随机变量几乎可以是任何数量,并且它可以在随机实验的一组可能性中取一个值。考虑一个随机变量X,其值在掷骰子的样本空间S={1, 2, 3, 4, 5, 6}中。我们可以将事件“看到一个5”表示为{X=5}或X=5,其概率表示为P({X=5})或P(X=5)。通过P(X=a),我们区分了随机变量X和X可以采取的值(例如a)。
然而,这可能会导致繁琐的表示。为了简化符号,一方面,我们可以将P(X)表示为随机变量X上的分布:分布告诉我们X获得某一值的概率。另一方面,我们可以简单用P(a)表示随机变量取值a的概率。由于概率论中的事件是来自样本空间的一组结果,因此我们可以为随机变量指定值的可取范围。例如,P(1≤X≤3)表示事件{1≤X≤3},即{X=1, 2, or, 3}的概率。等价地,P(1≤X≤3)表示随机变量X从{1, 2, 3}中取值的概率。
请注意,离散(discrete)随机变量(如骰子的每一面)和连续(continuous)随机变量(如人的体重和身高)之间存在微妙的区别。现实生活中,测量两个人是否具有完全相同的身高没有太大意义。如果我们进行足够精确的测量,最终会发现这个星球上没有两个人具有完全相同的身高。在这种情况下,询问某人的身高是否落入给定的区间,比如是否在1.79米和1.81米之间更有意义。在这些情况下,我们将这个看到某个数值的可能性量化为密度(density)。高度恰好为1.80米的概率为0,但密度不是0。在任何两个不同高度之间的区间,我们都有非零的概率。在本节的其余部分中,我们将考虑离散空间中的概率。连续随机变量的概率可以参考深度学习数学附录中随机变量的一节。
处理多个随机变量
很多时候,我们会考虑多个随机变量。 比如,我们可能需要对疾病和症状之间的关系进行建模。 给定一个疾病和一个症状,比如“流感”和“咳嗽”,以某个概率存在或不存在于某个患者身上。 我们需要估计这些概率以及概率之间的关系,以便我们可以运用我们的推断来实现更好的医疗服务。
再举一个更复杂的例子:图像包含数百万像素,因此有数百万个随机变量。 在许多情况下,图像会附带一个标签(label),标识图像中的对象。 我们也可以将标签视为一个随机变量。 我们甚至可以将所有元数据视为随机变量,例如位置、时间、光圈、焦距、ISO、对焦距离和相机类型。 所有这些都是联合发生的随机变量。 当我们处理多个随机变量时,会有若干个变量是我们感兴趣的。
2.6.2.1. 联合概率
第一个被称为联合概率(joint probability)P(A=a, B=b)。给定任意值a和b,联合概率可以回答:A=a和B=b同时满足的概率是多少?请注意,对于任何a和b的取值,P(A=a, B=b)≤P(A=a)。这点是确定的,因为要同时发生A=a和B=b,A=a就必须发生,B=b也必须发生(反之亦然)。因此,A=a和B=b同时发生的可能性不大于A=a或是B=b单独发生的可能性。
2.6.2.2. 条件概率
联合概率的不等式带给我们一个有趣的比率:0≤P(A=a, B=b)/P(A=a)≤1。我们称这个比率为条件概率(conditional probability),并用P(B=b|A=a)表示它:它是B=b的概率,前提是A=a已发生。
2.6.2.3. 贝叶斯定理
使用条件概率的定义,我们可以得出统计学中最有用的方程之一:Bayes定理(Bayes’ theorem)。 根据乘法法则(multiplication rule )可得到P(A, B)=P(B|A)P(A)。根据对称性,可得到P(A, B)=P(A|B)P(B)。假设P(B)>0,求解其中一个条件变量,我们得到
P(A|B) = P(B|A)P(A)/P(B).
请注意,这里我们使用紧凑的表示法:其中P(A, B)是一个联合分布(joint distribution),P(A∣B)是一个条件分布(conditional distribution)。这种分布可以在给定值A=a, B=b上进行求值。
2.6.2.4. 边际化 全概率公式又可写作: Pr ( A ) = ∑ n Pr ( A ∣ B n ) Pr ( B n )
为了能进行事件概率求和,我们需要求和法则(sum rule),即B的概率相当于计算A的所有可能选择,并将所有选择的联合概率聚合在一起:
P(B) = ∑AP(A, B).
这也称为边际化(marginalization)。边际化结果的概率或分布称为边际概率(marginal probability)或边际分布(marginal distribution)。
期望和方差
为了概括概率分布的关键特征,我们需要一些测量方法。一个随机变量X的期望(expectation,或平均值(average))表示为
E[X] = ∑x xP(X=x).
当函数f(x)的输入是从分布P中抽取的随机变量时,f(x)的期望值为
E_x~P[f(x)] = ∑x f(x)P(x).
在许多情况下,我们希望衡量随机变量X与其期望值的偏置。这可以通过方差来量化
Var[X] = E[(X - E[X])^2] = E[X^2] - E[X]^2.
方差的平方根被称为标准差(standard deviation)。随机变量函数的方差衡量的是:当从该随机变量分布中采样不同值x时,函数值偏离该函数的期望的程度:
Var[f(x)] = E[(f(x) - E[f(x)])^2].
小结
我们可以从概率分布中采样。
我们可以使用联合分布、条件分布、Bayes定理、边缘化和独立性假设来分析多个随机变量。
期望和方差为概率分布的关键特征的概括提供了实用的度量形式。
测验
fair_probs = torch.ones([10]) / 10
torch.ones([10]) / 10 , multinomial.Multinomial(1, fair_probs).sample()
multinomial.Multinomial(500, fair_probs).sample()
tensor([44., 54., 49., 40., 52., 52., 53., 55., 58., 43.])
tensor([0.0940, 0.1160, 0.0980, 0.1140, 0.0760, 0.1100, 0.0840, 0.1080, 0.1040,
0.0960])
1 0
P(A∪B) = P(A) + P(B) - P(A∩B)
P(A∩B) >= 0
P(A∩B) <= P(A)
P(A∩B) <= P(B)
P(A, B, C) = P(A)P(B|A)P(C|B)
这符合马尔可夫链的条件独立性质。
前提条件说明一下,帮助大家理解:检测结果为阳性只能说明指标为阳性但是并不是说病人就为阳性。并不能完全说明只要是该指标异常,病人就一定是患病状态,可能是比如病人因为其他原因导致的(也就是P(D = 1, H = 0) ≠ 0)所以才有了检测为阳性不一定患病这个问题。那么如果检测方法1第一次给病人检测为阳性,那么第二次还为阳性的可能性会比较大。也就是说先后两次用同一检测方法检测结果大概率是相关的。
在计算D1和D2的联合概率 P(D1 = 1, D2 = 1 | H = 1)
或者 P(D1 = 1, D2 = 1 | H = 0)
的时候,这里需要注意的是:
P(D1 = 1, D2 = 1 | H = 1) = P(D1 = 1 | H = 1) * P(D2 = 1 | H = 1)
P(D1 = 1, D2 = 1 | H = 1) ≠ P(D1 = 1 | H = 1) * P(D2 = 1 | H = 1)
举个例子,比如小明、小黑是一个班的同学(小黑给小明说好了,考试给他抄一抄),小红是另外一个班的学生。
P(小明 > 80) = 0.8
;P(小黑 > 80) = 0.1
;P(小红 > 80) = 0.1
。在一次考试中,请问,小明、小黑同时考80分以上的概率和小明、小红都考80分以上的概率相同吗?
很显然不同,前面的概率会小于0.8
,但是会大于0.8 * 0.1 = 0.08
,当然小黑可能抄不全;后者的概率为0.8 * 0.1 = 0.08
。
可以想象,因为两个测试不独立,导致分母P(D1 = 1, D2 = 1)
可能会极其的大(因为相互关联,P(D1 = 1)
的同时几乎P(D1 = 1)
),最后计算出来的病人阳性的概率也不高。
查找模块中的所有函数和类
为了知道模块中可以调用哪些函数和类,可以调用dir
函数。 例如,我们可以查询随机数生成模块中的所有属性:
import torch
print(dir(torch.distributions))
['AbsTransform', 'AffineTransform', 'Bernoulli', 'Beta', 'Binomial', 'CatTransform', 'Categorical', 'Cauchy', 'Chi2', 'ComposeTransform', 'ContinuousBernoulli', 'CorrCholeskyTransform', 'CumulativeDistributionTransform', 'Dirichlet', 'Distribution', 'ExpTransform', 'Exponential', 'ExponentialFamily', 'FisherSnedecor', 'Gamma', 'Geometric', 'Gumbel', 'HalfCauchy', 'HalfNormal', 'Independent', 'IndependentTransform', 'Kumaraswamy', 'LKJCholesky', 'Laplace', 'LogNormal', 'LogisticNormal', 'LowRankMultivariateNormal', 'LowerCholeskyTransform', 'MixtureSameFamily', 'Multinomial', 'MultivariateNormal', 'NegativeBinomial', 'Normal', 'OneHotCategorical', 'OneHotCategoricalStraightThrough', 'Pareto', 'Poisson', 'PowerTransform', 'RelaxedBernoulli', 'RelaxedOneHotCategorical', 'ReshapeTransform', 'SigmoidTransform', 'SoftmaxTransform', 'SoftplusTransform', 'StackTransform', 'StickBreakingTransform', 'StudentT', 'TanhTransform', 'Transform', 'TransformedDistribution', 'Uniform', 'VonMises', 'Weibull', 'Wishart', '__all__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__', 'bernoulli', 'beta', 'biject_to', 'binomial', 'categorical', 'cauchy', 'chi2', 'constraint_registry', 'constraints', 'continuous_bernoulli', 'dirichlet', 'distribution', 'exp_family', 'exponential', 'fishersnedecor', 'gamma', 'geometric', 'gumbel', 'half_cauchy', 'half_normal', 'identity_transform', 'independent', 'kl', 'kl_divergence', 'kumaraswamy', 'laplace', 'lkj_cholesky', 'log_normal', 'logistic_normal', 'lowrank_multivariate_normal', 'mixture_same_family', 'multinomial', 'multivariate_normal', 'negative_binomial', 'normal', 'one_hot_categorical', 'pareto', 'poisson', 'register_kl', 'relaxed_bernoulli', 'relaxed_categorical', 'studentT', 'transform_to', 'transformed_distribution', 'transforms', 'uniform', 'utils', 'von_mises', 'weibull', 'wishart']
通常可以忽略以“__
”(双下划线)开始和结束的函数,它们是Python中的特殊对象, 或以单个“_
”(单下划线)开始的函数,它们通常是内部函数。 根据剩余的函数名或属性名,我们可能会猜测这个模块提供了各种生成随机数的方法, 包括从均匀分布(uniform
)、正态分布(normal
)和多项分布(multinomial
)中采样。
查找特定函数和类的用法
有关如何使用给定函数或类的更具体说明,可以调用help
函数。 例如,我们来查看张量ones
函数的用法。
help(torch.ones)
Help on built-in function ones in module torch:
ones(...)
ones(*size, *, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False) -> Tensor
Returns a tensor filled with the scalar value `1`, with the shape defined
by the variable argument :attr:`size`.
Args:
size (int...): a sequence of integers defining the shape of the output tensor.
Can be a variable number of arguments or a collection like a list or tuple.
Keyword arguments:
out (Tensor, optional): the output tensor.
dtype (:class:`torch.dtype`, optional): the desired data type of returned tensor.
Default: if ``None``, uses a global default (see :func:`torch.set_default_tensor_type`).
layout (:class:`torch.layout`, optional): the desired layout of returned Tensor.
Default: ``torch.strided``.
device (:class:`torch.device`, optional): the desired device of returned tensor.
Default: if ``None``, uses the current device for the default tensor type
(see :func:`torch.set_default_tensor_type`). :attr:`device` will be the CPU
for CPU tensor types and the current CUDA device for CUDA tensor types.
requires_grad (bool, optional): If autograd should record operations on the
returned tensor. Default: ``False``.
...
>>> torch.ones(5)
tensor([ 1., 1., 1., 1., 1.])
Output is truncated. View as a scrollable element or open in a text editor. Adjust cell output settings...
小结
官方文档提供了本书之外的大量描述和示例。
可以通过调用dir
和help
函数或在Jupyter记事本中使用?
和??
查看API的用法文档。
pip install -U d2l --user
第一步:去lzma官网查看ubuntu环境下如何安装lzma
lzma官网安装教程
(1)sudo apt-get install liblzma-dev
(2)pip install backports.lzma
如果和我一样是python 3.6,第二个操作换换成:
pip3 install backports.lzma (可能需要sudo)
第二步:修改原本就存在的lmza.py文件
把 /usr/local/lib/python3.6/lzma.py line 27行
修改如下:
try:
from _lzma import *
from _lzma import _encode_filter_properties, _decode_filter_properties
except ImportError:
from backports.lzma import *
from backports.lzma import _encode_filter_properties, _decode_filter_properties
动手学深度学习v2
1. 引言 — 动手学深度学习 2.0.0 documentation