D2L_ch02_预备知识
数据操作
首先,我们导入torch
。请注意,虽然它被称为PyTorch
,但是代码中使用torch
而不是pytorch
1 | import torch |
张量:
表示一个由数值组成的数组,这个数组可能有多个维度
1 | x = torch.arange(12) |
shape
属性:
访问张量(沿每个轴的长度)的形状
1 | x.shape # 获取张量的形状 |
numel
函数:
获取张量中元素的总数
1 | x.numel() # 获取张量中元素的总数 |
reshape
函数:
改变一个张量的形状而不改变元素数量和元素值
1 | X = x.reshape(3, 4) # 不改变元素数量和元素值的前提下,改变张量的形状。 |
zeros((a,b,c...))
函数:
构造全0张量
1 | torch.zeros((2, 3, 4)) |
ones((a,b,c...))
函数:
构造全1张量
1 | torch.ones((2, 3, 4)) |
randn(a,b,c...)
函数:
从标准高斯(正态)分布中随机采样
1 | torch.randn(3, 4) |
通过提供包含数值的Python列表(或嵌套列表),来为所需张量中的每个元素赋予确定值
1 | torch.tensor([[2, 1, 4, 3], [1, 2, 3, 4], [4, 3, 2, 1]]) |
标准算术运算符`(+、-、*、/和)`**:
都可以被升级为按元素运算
1 | x = torch.tensor([1.0, 2, 4, 8]) |
求幂运算
1 | torch.exp(x) |
张量连结(concatenate):
我们也可以把多个张量连结(concatenate)在一起
1 | X = torch.arange(12, dtype=torch.float32).reshape((3,4)) |
逻辑运算符
使用逻辑运算符构建二元张量,
True
orFalse
1 | X > Y |
对张量中的所有元素进行求和,会产生一个单元素张量
1 | X.sum() |
广播机制:
即使形状不同,我们仍然可以通过调用广播机制(broadcasting mechanism)来执行按元素操作。通过适当复制元素来扩展一个或两个数组,以便在转换之后,两个张量具有相同的形状。
1 | a = torch.arange(9).reshape((3, 1,-1)) # "-1"表示该轴会自动计算长度 |
不同形状的张量相加:
1 | a + b |
切片:
可以用
[-1]
选择最后一个元素,可以用[1:3]
选择第二个和第三个元素
1 | X[-1], X[1:3] |
通过索引修改元素的值
除读取外,我们还可以通过指定索引来将元素写入矩阵
1 | X[1, 2] = 9 # 将X中第二行第三列的元素值改为9 |
通过切片赋值:
为多个元素赋值相同的值,我们只需要索引所有元素,然后为它们赋值
1 | X[0:2, :] = 12 |
内存分配问题:
运行一些操作可能会导致为新结果分配内存
1 | before = id(Y) |
执行原地操作
1 | Z = torch.zeros_like(Y) |
如果在后续计算中没有重复使用X
,我们也可以使用X[:] = X + Y
或X += Y
来减少操作的内存开销
1 | before = id(X) |
转换为NumPy
张量(ndarray
)
1 | A = X.numpy() |
将大小为1的张量转换为Python标量
1 | a = torch.tensor([3.5]) |
数据预处理
CSV(逗号分隔值)文件操作
创建一个人工数据集,并存储在CSV(逗号分隔值)文件
1 | import os |
从创建的CSV文件中加载原始数据集
1 | import pandas as pd |
为了处理缺失的数据,典型的方法包括插值法和删除法,这里,我们将考虑插值法
1 | inputs, outputs = data.iloc[:, 0:2], data.iloc[:, 2] |
对于
inputs
中的类别值或离散值,我们将“NaN”
视为一个类别
1 | inputs = pd.get_dummies(inputs, dummy_na=True) |
现在
inputs
和outputs
中的所有条目都是数值类型,它们可以转换为张量格式
1 | import torch |
线性代数
标量与张量
标量由只有一个元素的张量表示
1 | import torch |
向量可以被视为标量值组成的列表
1 | x = torch.arange(4) |
通过张量的索引来访问任一元素
1 | x[3] |
访问张量的长度
1 | len(x) |
只有一个轴的张量,形状只有一个元素
1 | x.shape |
矩阵
通过指定两个分量$m$和$n$来创建一个形状为$m \times n$的矩阵
1 | A = torch.arange(20).reshape(5, 4) |
矩阵的转置
1 | A.T |
对称矩阵(symmetric matrix)$\mathbf{A}$等于其转置:$\mathbf{A} = \mathbf{A}^\top$
1 | B = torch.tensor([[1, 2, 3], [2, 0, 4], [3, 4, 5]]) |
1 | B == B.T |
就像向量是标量的推广,矩阵是向量的推广一样,我们可以构建具有更多轴的数据结构
1 | X = torch.arange(24).reshape(2, 3, 4) |
给定具有相同形状的任意两个张量,任何按元素二元运算的结果都将是相同形状的张量
1 | A = torch.arange(20, dtype=torch.float32).reshape(5, 4) |
两个矩阵的按元素乘法称为Hadamard积(Hadamard product)(数学符号$\odot$)
1 | A * B |
1 | a = 2 |
计算其元素的和
1 | x = torch.arange(4, dtype=torch.float32) |
表示任意形状张量的元素和
1 | A.shape, A.sum() |
降维
指定张量沿哪一个轴来通过求和降低维度
1 | A_sum_axis0 = A.sum(axis=0) |
1 | A_sum_axis1 = A.sum(axis=1) |
1 | A.sum(axis=[0, 1]) |
一个与求和相关的量是平均值(mean或average)
1 | A.mean(), A.sum() / A.numel() |
1 | A.mean(axis=0), A.sum(axis=0) / A.shape[0] |
非降维求和
计算总和或均值时保持轴数不变
1 | sum_A = A.sum(axis=1, keepdims=True) |
通过广播将
A
除以sum_A
1 | A / sum_A |
某个轴计算
A
元素的累积总和
1 | A.cumsum(axis=0) |
点积
点积是相同位置的按元素乘积的和
1 | y = torch.ones(4, dtype = torch.float32) |
我们可以通过执行按元素乘法,然后进行求和来表示两个向量的点积
1 | torch.sum(x * y) |
向量积
矩阵向量积$\mathbf{A}\mathbf{x}$是一个长度为$m$的列向量,其第$i$个元素是点积$\mathbf{a}^\top_i \mathbf{x}$
1 | A.shape, x.shape, torch.mv(A, x) |
我们可以将矩阵-矩阵乘法$\mathbf{AB}$看作简单地执行$m$次矩阵-向量积,并将结果拼接在一起,形成一个$n \times m$矩阵
1 | B = torch.ones(4, 3) |
范数
$L_2$范数是向量元素平方和的平方根:
1 | u = torch.tensor([3.0, -4.0]) |
$L_1$范数,它表示为向量元素的绝对值之和:
1 | torch.abs(u).sum() |
矩阵的Frobenius(弗罗贝尼乌斯)范数(Frobenius norm)是矩阵元素平方和的平方根:
1 | torch.norm(torch.ones((4, 9))) # 36开方 |
微积分
如果$f$的导数存在,这个极限被定义为,定义$u=f(x)=3x^2-4x$
1 | %matplotlib inline |
通过令$x=1$并让$h$接近$0$,$\frac{f(x+h)-f(x)}{h}$的数值结果接近$2$
1 | def numerical_lim(f, x, h): |
为了对导数的这种解释进行可视化,我们将使用
matplotlib
定义几个函数
1 | def use_svg_display(): |
绘制函数$u=f(x)$及其在$x=1$处的切线$y=2x-3$
1 | x = np.arange(0, 3, 0.1) |
自动微分
假设我们想对函数$y=2\mathbf{x}^{\top}\mathbf{x}$关于列向量$\mathbf{x}$求导
1 | import torch |
在我们计算$y$关于$\mathbf{x}$的梯度之前,需要一个地方来存储梯度
1 | x.requires_grad_(True) |
现在计算$y$
1 | y = 2 * torch.dot(x, x) |
通过调用反向传播函数来自动计算
y
关于x
每个分量的梯度
1 | y.backward() |
1 | x.grad == 4 * x |
现在计算
x
的另一个函数
1 | x.grad.zero_() |
深度学习中,我们的目的不是计算微分矩阵,而是单独计算批量中每个样本的偏导数之和
1 | x.grad.zero_() |
将某些计算移动到记录的计算图之外
1 | x.grad.zero_() |
1 | x.grad.zero_() |
即使构建函数的计算图需要通过Python控制流(例如,条件、循环或任意函数调用),我们仍然可以计算得到的变量的梯度
1 | def f(a): |