李沐动手学深度学习(PyTorch)课程学习笔记第一章:预备知识。
1. 环境安装
首先安装好 PyTorch 环境:Anaconda 与 PyTorch 安装教程。
安装需要的包:
1 | pip install d2l |
2. 数据操作与数据预处理
张量表示一个数值组成的数组,这个数组可能有多个维度:
1 | import torch |
我们可以通过张量的 shape
属性来访问张量的形状和张量中元素的总数:
1 | print(x.shape) # torch.Size([12]) |
要改变一个张量的形状而不改变元素数量和元素值,我们可以调用 reshape
函数:
1 | x = x.reshape(3, 4) |
使用全0、全1、其他常量或者从特定分布中随机采样的数字:
1 | print(torch.zeros((2, 3, 4))) |
通过提供包含数值的 Python 列表(或嵌套列表)来为所需张量中的每个元素赋予确定值:
1 | x = torch.tensor([2, 3, 4, 5]) |
常见的标准算术运算符(+
、-
、*
、/
和 **
)都可以被升级为按元素运算:
1 | x = torch.tensor([1.0, 2, 3]) |
我们也可以把多个张量拼接在一起:
1 | x = torch.arange(6, dtype=torch.float32).reshape(2, 3) |
通过逻辑运算符构建二元张量:
1 | x = torch.tensor([1, 2, 3]) |
对张量中的所有元素进行求和会产生一个只有一个元素的张量,或者指定在某一维度上求和:
1 | x = torch.tensor([[1, 2, 3], [1, 1, 1]]) |
即使形状不同,我们仍然可以通过调用广播机制(broadcasting mechanism)来执行按元素操作:
1 | x = torch.arange(3).reshape((3, 1)) |
与 NumPy 张量相互转化:
1 | a = x.numpy() |
将大小为1的张量转换为 Python 标量:
1 | x = torch.tensor([3.5]) |
创建一个人工数据集,并存储在 CSV(逗号分隔值)文件中:
1 | import os |
从创建的 CSV 文件中加载原始数据集:
1 | import pandas as pd |
为了处理缺失的数据,典型的方法包括插值和删除,这里,我们将考虑插值:
1 | inputs, outputs = data.iloc[:, 0:2], data.iloc[:, 2] # 将data的第0、1列作为input,第2列作为output |
对于 inputs
中的类别值或离散值,我们可以将 NaN
视为一个类别:
1 | inputs = pd.get_dummies(inputs, dummy_na=True) |
现在 inputs
和 outputs
中的所有条目都是数值类型,它们可以转换为张量格式:
1 | import torch |
3. 线性代数
标量由只有一个元素的张量表示,你可以将向量视为标量值组成的列表:
1 | import torch |
访问张量的长度和形状:
1 | print(len(x)) # 4 |
矩阵和矩阵的转置:
1 | A = torch.arange(9).reshape(3, 3) |
给定具有相同形状的任何两个张量,任何按元素二元运算的结果都将是相同形状的张量:
1 | A = torch.arange(12, dtype=torch.float32).reshape(3, 4) |
求和与求平均值:
1 | print(A.sum()) # tensor(66.),所有元素求和 |
点积是相同位置的按元素乘积的和,torch.dot
只能对一维向量做点积。注意 NumPy 中的 np.dot
函数计算的是两个矩阵的矩阵乘法,而非对应元素相乘求和:
1 | x = torch.arange(4, dtype=torch.float32) # tensor([0., 1., 2., 3.]) |
矩阵向量积:
1 | x = torch.arange(6, dtype=torch.float32).reshape(2, 3) |
矩阵乘法,torch.mm
与 np.dot
类似:
1 | x = torch.arange(6, dtype=torch.float32).reshape(2, 3) |
L2 范数是向量所有元素的平方和的平方根:
1 | x = torch.tensor([3, -4], dtype=torch.float32) |
L1 范数为向量所有元素的绝对值之和:
1 | print(torch.abs(x).sum()) # tensor(7.) |
F 范数(弗罗贝尼乌斯范数)是矩阵所有元素的平方和的平方根:
1 | print(torch.norm(torch.ones((4, 9)))) # tensor(6.) |
4. 自动微分
先举一个简单的例子:
1 | import torch |
当 y
不是标量时,向量 y
关于向量 x
的导数的最自然解释是一个矩阵。对于高阶和高维的 y
和 x
,求导的结果可以是一个高阶张量。
然而,虽然这些更奇特的对象确实出现在高级机器学习中(包括深度学习中),但当调用向量的反向计算时,我们通常会试图计算一批训练样本中每个组成部分的损失函数的导数。这里,我们的目的不是计算微分矩阵,而是单独计算批量中每个样本的偏导数之和:
1 | # 对非标量调用backward()需要传入一个gradient参数,该参数指定微分函数关于self的梯度。 |
有时,我们希望将某些计算移动到记录的计算图之外。例如,假设 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
的偏导数。
1 | x.grad.zero_() |
使用自动微分的一个好处是:即使构建函数的计算图需要通过 Python 控制流(例如,条件、循环或任意函数调用),我们仍然可以计算得到变量的梯度。在下面的代码中,while
循环的迭代次数和 if
语句的结果都取决于输入 a
的值。对于任何 a
,存在某个常量标量 k
,使得 d = f(a) = k * a
,其中 k
的值取决于输入 a
,因此可以用 d / a
验证梯度是否正确。
1 | def f(a): |
下一章:线性神经网络。