动手学深度学习v2 p1引言 监督学习与无监督学习
1.引言
1.2. 机器学习中的关键组件
首先介绍一些核心组件。无论什么类型的机器学习问题,都会遇到这些组件:
可以用来学习的数据(data);
如何转换数据的模型(model);
一个目标函数(objective function),用来量化模型的有效性;
调整模型参数以优化目标函数的算法(algorithm)。
1.数据
仅仅拥有海量的数据是不够的,我们还需要正确的数据。 如果数据中充满了错误,或者如果数据的特征不能预测任务目标,那么模型很可能无效。 有一句古语很好地反映了这个现象:“输入的是垃圾,输出的也是垃圾。”
2. 模型
大多数机器学习会涉及到数据的转换。 比如一个“摄取照片并预测笑脸”的系统。再比如通过摄取到的一组传感器读数预测读数的正常与异常程度。 虽然简单的模型能够解决如上简单的问题,但本书中关注的问题超出了经典方法的极限。 深度学习与经典方法的区别主要在于:前者关注的功能强大的模型,这些模型由神经网络错综复杂的交织在一起,包含层层数据转换,因此被称为深度学习(deep learning)。 在讨论深度模型的过程中,本书也将提及一些传统方法。
3. 目标函数
前面的内容将机器学习介绍为“从经验中学习”。 这里所说的“学习”,是指自主提高模型完成某些任务的效能。 但是,什么才算真正的提高呢? 在机器学习中,我们需要定义模型的优劣程度的度量,这个度量在大多数情况是“可优化”的,这被称之为目标函数(objective function)。 我们通常定义一个目标函数,并希望优化它到最低点。 因为越低越好,所以这些函数有时被称为损失函数(loss function,或cost function)。 但这只是一个惯例,我们也可以取一个新的函数,优化到它的最高点。 这两个函数本质上是相同的,只是翻转一下符号。
当任务在试图预测数值时,最常见的损失函数是平方误差(squared error),即预测值与实际值之差的平方。 当试图解决分类问题时,最常见的目标函数是最小化错误率,即预测与实际情况不符的样本比例。 有些目标函数(如平方误差)很容易被优化,有些目标(如错误率)由于不可微性或其他复杂性难以直接优化。 在这些情况下,通常会优化替代目标。
通常,损失函数是根据模型参数定义的,并取决于数据集。 在一个数据集上,我们可以通过最小化总损失来学习模型参数的最佳值。 该数据集由一些为训练而收集的样本组成,称为训练数据集(training dataset,或称为训练集(training set))。 然而,在训练数据上表现良好的模型,并不一定在“新数据集”上有同样的性能,这里的“新数据集”通常称为测试数据集(test dataset,或称为测试集(test set))。
综上所述,可用数据集通常可以分成两部分:训练数据集用于拟合模型参数,测试数据集用于评估拟合的模型。 然后我们观察模型在这两部分数据集的性能。 “一个模型在训练数据集上的性能”可以被想象成“一个学生在模拟考试中的分数”。 这个分数用来为一些真正的期末考试做参考,即使成绩令人鼓舞,也不能保证期末考试成功。 换言之,测试性能可能会显著偏离训练性能。 当一个模型在训练集上表现良好,但不能推广到测试集时,这个模型被称为过拟合(overfitting)的。 就像在现实生活中,尽管模拟考试考得很好,真正的考试不一定百发百中。
4. 优化算法
当我们获得了一些数据源及其表示、一个模型和一个合适的损失函数,接下来就需要一种算法,它能够搜索出最佳参数,以最小化损失函数。 深度学习中,大多流行的优化算法通常基于一种基本方法–梯度下降(gradient descent)。 简而言之,在每个步骤中,梯度下降法都会检查每个参数,看看如果仅对该参数进行少量变动,训练集损失会朝哪个方向移动。 然后,它在可以减少损失的方向上优化参数。
1.3 监督学习
监督学习(supervised learning)擅长在“给定输入特征”的情况下预测标签。 每个“特征-标签”对都称为一个样本(example)。 有时,即使标签是未知的,样本也可以指代输入特征。 我们的目标是生成一个模型,能够将任何输入特征映射到标签(即预测)。
举一个具体的例子: 假设我们需要预测患者的心脏病是否会发作,那么观察结果“心脏病发作”或“心脏病没有发作”将是样本的标签。 输入特征可能是生命体征,如心率、舒张压和收缩压等。
监督学习之所以能发挥作用,是因为在训练参数时,我们为模型提供了一个数据集,其中每个样本都有真实的标签。 用概率论术语来说,我们希望预测“估计给定输入特征的标签”的条件概率。 虽然监督学习只是几大类机器学习问题之一,但是在工业中,大部分机器学习的成功应用都使用了监督学习。 这是因为在一定程度上,许多重要的任务可以清晰地描述为,在给定一组特定的可用数据的情况下,估计未知事物的概率。
监督学习的学习过程一般可以分为三大步骤:
从已知大量数据样本中随机选取一个子集,为每个样本获取真实标签。有时,这些样本已有标签(例如,患者是否在下一年内康复?);有时,这些样本可能需要被人工标记(例如,图像分类)。这些输入和相应的标签一起构成了训练数据集;
选择有监督的学习算法,它将训练数据集作为输入,并输出一个“已完成学习的模型”;
将之前没有见过的样本特征放到这个“已完成学习的模型”中,使用模型的输出作为相应标签的预测。
回归:回归问题是预测连续值的问题。例如,预测房价、股票价格或者人的身高等。这些都是连续的数值,我们的目标是找到输入特征和连续目标值之间的关系。例如,我们可能会使用房屋的面积、位置、建造年份等特征来预测房价。
分类:分类问题是预测离散值的问题。例如,判断一封电子邮件是垃圾邮件还是非垃圾邮件,或者判断一张图片是猫还是狗。在这些情况下,我们的目标是根据输入特征将样本分到两个或更多的类别中。
标记问题:标记问题是关于对象的多个属性的预测,这些属性并不是互斥的。例如,在自然语言处理中,我们可能需要标记句子中的每个词的词性(名词、动词、形容词等)。在这种情况下,每个词可以有多个标签。
搜索:在监督学习中,搜索可以被看作是学习一种策略,以在大量可能的解决方案中找到最好的一个。例如,棋类游戏的AI,它需要在每一步中决定最佳的移动。
推荐系统:推荐系统是一种信息过滤系统,用于预测用户对项目的“评分”或“偏好”。例如,根据用户过去的购买历史、浏览历史等信息,预测用户可能喜欢哪些新产品或服务。例如,Netflix推荐系统会根据用户过去观看的电影来推荐可能喜欢的新电影。
1.3.2. 无监督学习
到目前为止,所有的例子都与监督学习有关,即需要向模型提供巨大数据集:每个样本包含特征和相应标签值。 打趣一下,“监督学习”模型像一个打工仔,有一份极其专业的工作和一位极其平庸的老板。 老板站在身后,准确地告诉模型在每种情况下应该做什么,直到模型学会从情况到行动的映射。 取悦这位老板很容易,只需尽快识别出模式并模仿他们的行为即可。
相反,如果工作没有十分具体的目标,就需要“自发”地去学习了。 比如,老板可能会给我们一大堆数据,然后要求用它做一些数据科学研究,却没有对结果有要求。 这类数据中不含有“目标”的机器学习问题通常被为无监督学习(unsupervised learning), 本书后面的章节将讨论无监督学习技术。 那么无监督学习可以回答什么样的问题呢?来看看下面的例子。
聚类(clustering)问题:没有标签的情况下,我们是否能给数据分类呢?比如,给定一组照片,我们能把它们分成风景照片、狗、婴儿、猫和山峰的照片吗?同样,给定一组用户的网页浏览记录,我们能否将具有相似行为的用户聚类呢?
主成分分析(principal component analysis)问题:我们能否找到少量的参数来准确地捕捉数据的线性相关属性?比如,一个球的运动轨迹可以用球的速度、直径和质量来描述。再比如,裁缝们已经开发出了一小部分参数,这些参数相当准确地描述了人体的形状,以适应衣服的需要。另一个例子:在欧几里得空间中是否存在一种(任意结构的)对象的表示,使其符号属性能够很好地匹配?这可以用来描述实体及其关系,例如“罗马” − “意大利” + “法国” = “巴黎”。
因果关系(causality)和概率图模型(probabilistic graphical models)问题:我们能否描述观察到的许多数据的根本原因?例如,如果我们有关于房价、污染、犯罪、地理位置、教育和工资的人口统计数据,我们能否简单地根据经验数据发现它们之间的关系?
生成对抗性网络(generative adversarial networks):为我们提供一种合成数据的方法,甚至像图像和音频这样复杂的非结构化数据。潜在的统计机制是检查真实和虚假数据是否相同的测试,它是无监督学习的另一个重要而令人兴奋的领域。
1.3.3. 与环境互动
有人一直心存疑虑:机器学习的输入(数据)来自哪里?机器学习的输出又将去往何方? 到目前为止,不管是监督学习还是无监督学习,我们都会预先获取大量数据,然后启动模型,不再与环境交互。 这里所有学习都是在算法与环境断开后进行的,被称为离线学习(offline learning)。 对于监督学习,从环境中收集数据的过程类似于 图1.3.6。
1.3.4. 强化学习
如果你对使用机器学习开发与环境交互并采取行动感兴趣,那么最终可能会专注于强化学习(reinforcement learning)。 这可能包括应用到机器人、对话系统,甚至开发视频游戏的人工智能(AI)。 深度强化学习(deep reinforcement learning)将深度学习应用于强化学习的问题,是非常热门的研究领域。 突破性的深度Q网络(Q-network)在雅达利游戏中仅使用视觉输入就击败了人类, 以及 AlphaGo 程序在棋盘游戏围棋中击败了世界冠军,是两个突出强化学习的例子。
在强化学习问题中,智能体(agent)在一系列的时间步骤上与环境交互。 在每个特定时间点,智能体从环境接收一些观察(observation),并且必须选择一个动作(action),然后通过某种机制(有时称为执行器)将其传输回环境,最后智能体从环境中获得奖励(reward)。 此后新一轮循环开始,智能体接收后续观察,并选择后续操作,依此类推。 强化学习的过程在 图1.3.7 中进行了说明。 请注意,强化学习的目标是产生一个好的策略(policy)。 强化学习智能体选择的“动作”受策略控制,即一个从环境观察映射到行动的功能。
强化学习框架的通用性十分强大。 例如,我们可以将任何监督学习问题转化为强化学习问题。 假设我们有一个分类问题,可以创建一个强化学习智能体,每个分类对应一个“动作”。 然后,我们可以创建一个环境,该环境给予智能体的奖励。 这个奖励与原始监督学习问题的损失函数是一致的。
当然,强化学习还可以解决许多监督学习无法解决的问题。 例如,在监督学习中,我们总是希望输入与正确的标签相关联。 但在强化学习中,我们并不假设环境告诉智能体每个观测的最优动作。 一般来说,智能体只是得到一些奖励。 此外,环境甚至可能不会告诉是哪些行为导致了奖励。
以强化学习在国际象棋的应用为例。 唯一真正的奖励信号出现在游戏结束时:当智能体获胜时,智能体可以得到奖励1;当智能体失败时,智能体将得到奖励-1。 因此,强化学习者必须处理学分分配(credit assignment)问题:决定哪些行为是值得奖励的,哪些行为是需要惩罚的。 就像一个员工升职一样,这次升职很可能反映了前一年的大量的行动。 要想在未来获得更多的晋升,就需要弄清楚这一过程中哪些行为导致了晋升。
强化学习可能还必须处理部分可观测性问题。 也就是说,当前的观察结果可能无法阐述有关当前状态的所有信息。 比方说,一个清洁机器人发现自己被困在一个许多相同的壁橱的房子里。 推断机器人的精确位置(从而推断其状态),需要在进入壁橱之前考虑它之前的观察结果。
最后,在任何时间点上,强化学习智能体可能知道一个好的策略,但可能有许多更好的策略从未尝试过的。 强化学习智能体必须不断地做出选择:是应该利用当前最好的策略,还是探索新的策略空间(放弃一些短期回报来换取知识)。
1.4. 小结
机器学习研究计算机系统如何利用经验(通常是数据)来提高特定任务的性能。它结合了统计学、数据挖掘和优化的思想。通常,它是被用作实现人工智能解决方案的一种手段。
表示学习作为机器学习的一类,其研究的重点是如何自动找到合适的数据表示方式。深度学习是通过学习多层次的转换来进行的多层次的表示学习。
深度学习不仅取代了传统机器学习的浅层模型,而且取代了劳动密集型的特征工程。
最近在深度学习方面取得的许多进展,大都是由廉价传感器和互联网规模应用所产生的大量数据,以及(通过GPU)算力的突破来触发的。
整个系统优化是获得高性能的关键环节。有效的深度学习框架的开源使得这一点的设计和实现变得非常容易。
2. 预备知识
2.1. 数据操作
为了能够完成各种数据操作,我们需要某种方法来存储和操作数据。 通常,我们需要做两件重要的事:(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)
2.1.1. 入门
导入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]])
2.1.2. 运算符、广播机制、索引与切片、节省内存
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. 数据预处理
2.2.1. 读取数据集
创建数据集:
import osos.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 pddata = pd.read_csv(data_file)
dataNumRooms 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())
inputsNumRooms 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 torchX, y = torch.tensor(inputs.values), torch.tensor(outputs.values)
X, y
2.3 线性代数
向量点乘:
A·B = A1B1 + A2B2 + ... + AnBn
正交 A·B=0
范数:
2.3.1. 标量
import torchx = 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]),
2.3.7. 点积
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
2.4. 矩阵计算、自动求导、微积分
导数
总结一下:维度不变,分子不转分母转
2.4.1. 导数和微分
假设我们有一个函数 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有一个轴,输出Truedef 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), Xelif 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,链式法则给出:
小结
微分和积分是微积分的两个分支,前者可以应用于深度学习中的优化问题。
导数可以被解释为函数相对于其变量的瞬时变化率,它也是函数曲线的切线的斜率。
梯度是一个向量,其分量是多变量函数相对于其所有变量的偏导数。
链式法则可以用来微分复合函数。
作业
- 绘制函数y = f(x) = x³ - 1/x 和其在x=1处切线的图像。
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)'])
- 求函数f(x) = 3x₁² + 5eˣ₂的梯度。
6x1,5ex2
- 函数f(x) = ‖x‖²的梯度是什么?
2x
- 尝试写出函数u = f(x, y, z),其中x = x(a, b),y = y(a, b),z = z(a, b)的链式法则。(原来还要分类讨论)
2.5. 自动微分
深度学习框架通过自动计算导数,即自动微分(automatic differentiation)来加快求导。 实际中,根据设计好的模型,系统会构建一个计算图(computational graph), 来跟踪计算是哪些数据通过哪些操作组合起来产生输出。 自动微分使系统能够随后反向传播梯度。 这里,反向传播(backpropagate)意味着跟踪整个计算图,填充关于每个参数的偏导数。
例子
首先,我们创建变量x
并为其分配一个初始值。在我们计算y关于x的梯度之前,需要一个地方来存储梯度。 重要的是,我们不会在每次对一个参数求导时都分配新的内存。 因为我们经常会成千上万次地更新相同的参数,每次都分配新的内存可能很快就会将内存耗尽。 注意,一个标量函数关于向量x的梯度是向量,并且与x具有相同的形状。
import torchx = 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=<MulBackward0>)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 * 2while b.norm() < 1000:b = b * 2if b.sum() > 0:c = belse:c = 100 * breturn 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=<MulBackward0>),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 torchx = torch.randn(3, requires_grad=True)
y = torch.dot(x,x)
y.backward()
x.gradimport 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 torchx = 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 torchf,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()
常见问题:
ModuleNotFoundError: No module named ‘d2l’
pip install -U d2l --user
No module named ‘_lzma’
第一步:去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
动手学深度学习v2 p1引言 监督学习与无监督学习相关推荐
- 《动手学深度学习v2》之细致解析(1)内容及介绍及安装
前言 作者来自北京某不知名985,现在是本科在读学生,专业是数据科学与大数据技术,班上同学都太卷了,没办法,需要学习深度学习,经大佬介绍,在B站上找到了一个很不错的资源,李沐老师的<动手学深度学 ...
- new 动手学深度学习V2环境安装
动手学深度学习V2 环境安装 虚拟环境 d2l-zh安装 conda create -n d2l-zh -y python=3.8 pip pip install jupyter d2l torch ...
- 使用AWS最便宜的GPU实例 from 动手学深度学习v2 李沐大神
使用AWS最便宜的GPU实例 from 动手学深度学习v2 李沐大神 视频链接https://www.bilibili.com/video/BV1MA411L78X?t=493 由于购买的电脑没有配 ...
- 57 长短期记忆网络(LSTM)【动手学深度学习v2】
57 长短期记忆网络(LSTM)[动手学深度学习v2] 深度学习学习笔记 学习视频:https://www.bilibili.com/video/BV1JU4y1H7PC/?spm_id_from=a ...
- 【动手学深度学习v2李沐】学习笔记07:权重衰退、正则化
前文回顾:模型选择.欠拟合和过拟合 文章目录 一.权重衰退 1.1 硬性限制 1.2 柔性限制(正则化) 1.3 参数更新法则 1.4 总结 二.代码实现 2.1 从零开始实现 2.1.1 人工数据集 ...
- 动手学深度学习 v2 视频版
动手学深度学习 v2 01 课程安排 目标 内容 形式 你将学到什么? 资源 02 深度学习介绍 AI 地图 图片分类 物体检测和分割 样式迁移 人脸合成 文字生成图片 文字生成 03 安装 本地安装 ...
- 李沐动手学深度学习v2/总结1
总结 编码过程 数据 数据预处理 模型 参数,初始化参数 超参数 损失函数,先计算损失,清空梯度(防止有累积的梯度),再对损失后向传播计算损失关于参数的梯度 优化算法,使用优化算法更新参数 训练求参数 ...
- 动手学深度学习V2——李沐Bilibili直播视频Jupyter Notebook安装
在哔哩哔哩上发现李沐是视频直播讲解<动手学深度V2>- Pytorch,准备按照视频中的安装教程来搭建一个新的虚拟环境d2l,李沐使用的是Jupyter Notebook 而不是 Pych ...
- 08 线性回归 + 基础优化算法【动手学深度学习v2】
线性回归 输出层不当成一层 输入层和权重层放一起 x和y是列向量 因为loss=1/2(y-y_hat)^2 又因为y_hat的平均值=1/n(xi*w+b)=1/n(Xw+b) 唯一一个有显示解的模 ...
最新文章
- 别盲目调参!深度学习要先找到最佳策略
- python字典输出_Python字典/循环输出
- python文件不存在时创建文件_python-创建一个文件(如果不存在)
- elasticSearch6源码分析(5)gateway模块
- 解决 FtpClient 类无法导入 .
- DataTable转成字符串复制到txt文本的小例子
- 30.课时30.【Django模板】autoescape标签使用详解(Av61533158,P30)
- java 参数内存释放_JNI创建变量和释放变量
- 【Unity】Geometry Shader实现
- html图片在td上不能显示,动态添加表格数据和表格行到HTML,TD不显示在HTML中
- msxml6_x86.msi和msxml6_ia64.msi和msxml6_x64.msi的选择
- 【vue】ramda.js在vue中的使用
- 【人工智能】AI竞赛,到底有什么价值?
- 7.3 使用“设计视图”创建报表
- NVR录像机 人机界面鼠标光标消失如何解决
- springboot/maven-orika 映射
- 固定资产减值准备、累计折旧
- 表示微型计算机系统稳定性,计算机选择题
- 关于最小二乘估计的一点理解和感悟
- 电影先生搭建影视网站教程
热门文章
- random的基本使用
- 关于几种分布式锁的简单介绍
- codemirror mysql_Angular6 CodeMirror在线编辑sql 智能提示
- java suspend_Java Thread suspend()方法
- 工作总结及下一年工作计划怎么写,用敬业签总结今年工作内容
- 谈一次java web系统的重构思路
- 三菱FX3U版本V11源代码
- 格林威治标准时间/世界时
- JavaScript如何实现禁用浏览器后退按钮
- 【构建PB级准实时分析引擎】 -- azkaban、airflow、dolphinscheduler、quartz、xxl-job 、oozie调度方案评测