1. 数据操作

代码文件见 chapter_preliminaries/ndarray.ipynb

1.1 张量 Tensor / n 维数组

张量 Tensor 其实就是 n 维数组,只不过在 pytorch 和 tensorflow 中称为张量(tensor)
N 维数组(张量 Tensor)样例:
image.pngimage.png

1.1.1 创建张量

创建张量,需要

  1. 形状:eg. 3×4 矩阵
  2. 每个元素的数据类型:eg. 32 位浮点数
  3. 每个元素的值:eg. 全是 0,或者随机数

    1. # 用 arange 创建一个行向量 x,这个行向量包含从 0 开始的前 12 个整数,它们被默认创建为浮点数
    2. x = torch.arange(12)
    3. x # tensor([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
    1. # 通过张量的shape属性来访问张量(沿每个轴的长度)的形状
    2. x.shape # torch.Size([12])
    1. # 张量中元素的总数,即形状的所有元素乘积
    2. x.numel() # 12
    1. X = x.reshape(3, 4)
    2. X # tensor([[ 0, 1, 2, 3],
    3. # [ 4, 5, 6, 7],
    4. # [ 8, 9, 10, 11]])
    1. torch.zeros((2, 3, 4)) # tensor([[[0., 0., 0., 0.],
    2. # [0., 0., 0., 0.],
    3. # [0., 0., 0., 0.]],
    4. # [[0., 0., 0., 0.],
    5. # [0., 0., 0., 0.],
    6. # [0., 0., 0., 0.]]])
    1. torch.ones((2, 3, 4)) # tensor([[[1., 1., 1., 1.],
    2. # [1., 1., 1., 1.],
    3. # [1., 1., 1., 1.]],
    4. # [[1., 1., 1., 1.],
    5. # [1., 1., 1., 1.],
    6. # [1., 1., 1., 1.]]])
    1. torch.randn(3, 4) # tensor([[ 0.8678, -0.7441, -1.8607, -0.1561],
    2. # [-0.8376, -0.6062, 0.5473, -0.6876],
    3. # [-0.9210, -0.4903, -1.2670, -1.5453]])
    1. torch.tensor([[2, 1, 4, 3], [1, 2, 3, 4], [4, 3, 2, 1]])
    2. # tensor([[2, 1, 4, 3],
    3. # [1, 2, 3, 4],
    4. # [4, 3, 2, 1]])

1.1.2 访问元素(索引/切片)

image.png

  1. X = torch.arange(12, dtype=torch.float32).reshape((3,4))
  2. # tensor([[ 0., 1., 2., 3.],
  3. # [ 4., 5., 6., 7.],
  4. # [ 8., 9., 10., 11.]])
  1. # 读取一个元素
  2. X[1, 2] # tensor(6.)
  3. # 改写一个元素
  4. X[1, 2] = 9
  5. X # tensor([[ 0., 1., 2., 3.],
  6. # [ 4., 5., 9., 7.],
  7. # [ 8., 9., 10., 11.]])
  1. # 访问最后一行
  2. X[-1] # tensor([ 8., 9., 10., 11.])
  3. X[-1, :] # tensor([ 8., 9., 10., 11.])
  1. X[:, 1] # tensor([1., 5., 9.])
  1. # 注意 m:n 代表的是左闭右开区间 [m, n)
  2. # 取第 [0,2) 行,第 [1, end) 列
  3. X[0:2, 1:] # tensor([[1., 2., 3.],
  4. # [5., 9., 7.]])
  1. X[0:2, :] = 12
  2. X # tensor([[12., 12., 12., 12.],
  3. # [12., 12., 12., 12.],
  4. # [ 8., 9., 10., 11.]])

1.1.3 运算符

  1. 按元素运算

    1. x = torch.tensor([1.0, 2, 4, 8])
    2. y = torch.tensor([2, 2, 2, 2])
    3. x + y, x - y, x * y, x / y, x ** y # **运算符是求幂运算
    4. # (tensor([ 3., 4., 6., 10.]),
    5. # tensor([-1., 0., 2., 6.]),
    6. # tensor([ 2., 4., 8., 16.]),
    7. # tensor([0.5000, 1.0000, 2.0000, 4.0000]),
    8. # tensor([ 1., 4., 16., 64.]))
    1. torch.exp(x) # tensor([2.7183e+00, 7.3891e+00, 5.4598e+01, 2.9810e+03])
  2. concat 沿指定轴拼接两个张量

  • 沿形状的维度 0 (轴 0)拼接张量:拼接后形状的维度 0 = 两个输入张量的形状的第 0 维数值之和
    • 这里两个 shape=[3, 4] 的张量拼接,输出张量 shape = [6, 4]
  • 沿形状的维度 1 (轴 1)拼接张量:拼接后形状的维度 1 = 两个输入张量的形状的第 1 维数值之和
    • 这里两个 shape=[3, 4] 的张量拼接,输出张量 shape = [3, 8]
      1. X = torch.arange(12, dtype=torch.float32).reshape((3,4))
      2. Y = torch.tensor([[2.0, 1, 4, 3], [1, 2, 3, 4], [4, 3, 2, 1]])
      3. # 沿形状的维度 0 (轴 0)拼接张量、沿形状的维度 1 (轴 1)拼接张量
      4. torch.cat((X, Y), dim=0), torch.cat((X, Y), dim=1)
      5. # (tensor([[ 0., 1., 2., 3.],
      6. # [ 4., 5., 6., 7.],
      7. # [ 8., 9., 10., 11.],
      8. # [ 2., 1., 4., 3.],
      9. # [ 1., 2., 3., 4.],
      10. # [ 4., 3., 2., 1.]]),
      11. # tensor([[ 0., 1., 2., 3., 2., 1., 4., 3.],
      12. # [ 4., 5., 6., 7., 1., 2., 3., 4.],
      13. # [ 8., 9., 10., 11., 4., 3., 2., 1.]]))
  1. 逻辑运算符,构建二元张量:输出张量的每个元素都为 True/False,代表两个输入向量对应位置的元素是否相等

    1. X == Y
    2. # tensor([[False, True, False, True],
    3. # [False, False, False, False],
    4. # [False, False, False, False]])
  2. 求和

    1. 对张量中的所有元素进行求和,会产生一个单元素张量

      1. X = torch.arange(12, dtype=torch.float32).reshape((3,4))
      2. X.sum() # tensor(66.)
    2. 沿特定轴求和,参见 3.6 降维(sum、mean)

1.1.4 广播机制

在某些情况下,即使形状不同,仍然可以通过调用 广播机制(broadcasting mechanism)来执行按元素操作

  1. a = torch.arange(3).reshape((3, 1))
  2. b = torch.arange(2).reshape((1, 2))
  3. a, b # (tensor([[0],
  4. # [1],
  5. # [2]]),
  6. # tensor([[0, 1]]))
  1. # 3*1 矩阵 和 1*2 矩阵相加,广播为 3*2 矩阵
  2. a + b # tensor([[0, 1],
  3. # [1, 2],
  4. # [2, 3]])

1.1.5 张量和 numpy、标量的转化

  1. tensor 张量 ↔ numpy 张量 (ndarray):注意这样 tensor 和 numpy 张量共享底层内存,就地更改一个张量的同时也会更改另一个张量

    1. A = X.numpy()
    2. B = torch.tensor(A)
    3. type(A), type(B) # (numpy.ndarray, torch.Tensor)
  2. 大小为 1 的 tensor 张量 -> 标量

    1. a = torch.tensor([3.5])
    2. a, a.item(), float(a), int(a) # (tensor([3.5000]), 3.5, 3.5, 3)

2. 数据预处理

代码文件见 chapter_preliminaries/pandas.ipynb

使用 pandas 软件包

2.1 读取数据集

  1. 创建一个人工数据集,并存储在 CSV(逗号分隔值)文件 ../data/house_tiny.csv 中 ```python 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’)

  1. 2. 从创建的 CSV 文件中**加载原始数据集**
  2. ```python
  3. # 如果没有安装pandas,只需取消对以下行的注释来安装pandas
  4. # !pip install pandas
  5. import pandas as pd
  6. data = pd.read_csv(data_file)
  7. # print(data)
  8. data
  9. # NumRooms Alley Price
  10. # 0 NaN Pave 127500
  11. # 1 2.0 NaN 106000
  12. # 2 4.0 NaN 178100
  13. # 3 NaN NaN 140000

2.2 处理缺失值

处理缺失值的典型方法:

  1. 插值法:用一个替代值(比如默认值、均值等)弥补缺失值
  2. 删除法:直接忽略缺失值

插值法处理缺失值:

  • 数值型的缺失值,用均值代替

    inputs, outputs = data.iloc[:, 0:2], data.iloc[:, 2]
    inputs = inputs.fillna(inputs.mean())
    # print(inputs)
    inputs
    #     NumRooms    Alley
    # 0        3.0        Pave
    # 1        2.0        NaN
    # 2        4.0        NaN
    # 3        3.0        NaN
    
  • 对于类别型/离散型的缺失值的处理:将缺失值 NAN 也视为一个类别,则 Alley 这一列包含两种类别,可以将每个类别作为一列,类似 multi-hot 编码

    inputs = pd.get_dummies(inputs, dummy_na=True)
    # print(inputs)
    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
    

    通过以上操作,所有条目都变为数值类型

    2.3 转换为张量格式

    通过以上操作,所有条目都变为数值类型,因此可以转换为张量格式 ```python import torch

X, y = torch.tensor(inputs.values), torch.tensor(outputs.values) X, y

(tensor([[3., 1., 0.],

[2., 0., 1.],

[4., 0., 1.],

[3., 0., 1.]], dtype=torch.float64),

tensor([127500, 106000, 178100, 140000]))


<a name="t9F7B"></a>
# 3. 线性代数
<a name="LlhJ6"></a>
## 3.1 标量
**标量 scalar**:称仅包含一个数值的叫标量(scalar),**标量由只有一个元素的张量表示**
```python
import torch

x = torch.tensor(3.0)
y = torch.tensor(2.0)

x + y, x * y, x / y, x**y        # (tensor(5.), tensor(6.), tensor(1.5000), tensor(9.))

3.2 向量

从数学的习惯表达来看,向量(一维数组)应该是列向量。但是在 pytorch 中是以行向量的形式展现的,打印出来是一个行向量。并且在 pytorch 中,同一个向量(一维数组)放在 matmul 做第一个乘数就是行向量,放在 matmul 做第二个乘数就是列向量。

向量:标量值组成的列表,通过一维张量(只有一个轴的张量)处理向量

x = torch.arange(4)
x                                # tensor([0, 1, 2, 3])

向量元素的访问:通过张量的索引来访问任一元素

x[3]                            # tensor(3)

长度、维度和形状

  • 向量的长度通常称为向量的维度(dimension)

    len(x)                            # 4
    
  • 因为向量是一维张量,也就是说只有一个轴,因此也可以通过 shape 属性访问向量长度

    x.shape                            # torch.Size([4])
    

3.3 矩阵

矩阵:即二维张量有两个轴的张量

A = torch.arange(20).reshape(5, 4)
A                                # tensor([[ 0,  1,  2,  3],
                                #         [ 4,  5,  6,  7],
                                #           [ 8,  9, 10, 11],
                                #           [12, 13, 14, 15],
                                #           [16, 17, 18, 19]])
A.T                                # tensor([[ 0,  4,  8, 12, 16],
                                #          [ 1,  5,  9, 13, 17],
                                #          [ 2,  6, 10, 14, 18],
                                #          [ 3,  7, 11, 15, 19]])
B = torch.tensor([[1, 2, 3], [2, 0, 4], [3, 4, 5]])
B == B.T                        # tensor([[True, True, True],
                                #          [True, True, True],
                                #          [True, True, True]])

3.4 张量

张量:具有任意轴数的 n 维数组(标量 -> 向量 -> 矩阵 -> 张量)

X = torch.arange(24).reshape(2, 3, 4)
X                                # tensor([[[ 0,  1,  2,  3],
                                #            [ 4,  5,  6,  7],
                                #            [ 8,  9, 10, 11]],

                                #          [[12, 13, 14, 15],
                                #            [16, 17, 18, 19],
                                #           [20, 21, 22, 23]]])

3.5 张量算法的基本性质

元素操作:给定具有相同形状的任意两个张量,任何按元素二元运算的结果都将是相同形状的张量

A = torch.arange(20, dtype=torch.float32).reshape(5, 4)
B = A.clone()  # 通过分配新内存,将A的一个副本分配给B
A, A + B                        # (tensor([[ 0.,  1.,  2.,  3.],
                                #           [ 4.,  5.,  6.,  7.],
                                #            [ 8.,  9., 10., 11.],
                                #            [12., 13., 14., 15.],
                                #           [16., 17., 18., 19.]]),
                                #  tensor([[ 0.,  2.,  4.,  6.],
                                #           [ 8., 10., 12., 14.],
                                #           [16., 18., 20., 22.],
                                #           [24., 26., 28., 30.],
                                #           [32., 34., 36., 38.]]))
A * B                            # tensor([[  0.,   1.,   4.,   9.],
                                #          [ 16.,  25.,  36.,  49.],
                                #          [ 64.,  81., 100., 121.],
                                #          [144., 169., 196., 225.],
                                #           [256., 289., 324., 361.]])
a = 2
X = torch.arange(24).reshape(2, 3, 4)
a + X, (a * X).shape            # (tensor([[[ 2,  3,  4,  5],
                                #            [ 6,  7,  8,  9],
                                #             [10, 11, 12, 13]],

                                #           [[14, 15, 16, 17],
                                #            [18, 19, 20, 21],
                                #             [22, 23, 24, 25]]]),
                                #  torch.Size([2, 3, 4]))

3.6 降维(summean

  1. 张量求和 sum
  • 对张量的所有元素求和 ```python x = torch.arange(4, dtype=torch.float32) x, x.sum() # (tensor([0., 1., 2., 3.]), tensor(6.))

A.shape, A.sum() # (torch.Size([5, 4]), tensor(190.))


- 指定张量**沿某个轴求和**(降维):
   - **沿 axis = k 求和,就相当于消去轴 k**(第 k 维),更形象地可以理解为**沿轴 k 拍扁**。
   - 下面的例子沿 axis = 0 求和,就是将 shape = [5, 4] 的轴 0 (也就是 5)消去/拍扁,因此求和后的 shape = [4]
```python
A_sum_axis0 = A.sum(axis=0)
A_sum_axis0, A_sum_axis0.shape    # (tensor([40., 45., 50., 55.]), torch.Size([4]))
A_sum_axis1 = A.sum(axis=1)
A_sum_axis1, A_sum_axis1.shape    # (tensor([ 6., 22., 38., 54., 70.]), torch.Size([5]))
  • 指定张量沿多个轴求和(降维)
    • 沿 axis = [k1,k2] 求和,就相当于先消去轴 k1,再消去轴 k2(第 k1、k2 维),更形象地可以理解为先沿轴 k1 拍扁,再沿轴 k2 拍扁
    • shape = [2, 5, 4] 的张量沿轴 axis=[1,2] 求和,结果变为 shape = [2](只剩下轴 0)
      A.sum(axis=[0, 1])              # Same as `A.sum()`
      # tensor(190.)
      
  1. 张量求均值 mean
  • 对张量所有元素求均值

    A.mean(), A.sum() / A.numel()    # (tensor(9.5000), tensor(9.5000))
    
  • 指定张量沿某个轴求均值(降维)

    A.mean(axis=0), A.sum(axis=0) / A.shape[0]    
    # (tensor([ 8.,  9., 10., 11.]), tensor([ 8.,  9., 10., 11.]))
    

    3.6.1 非降维求和/平均

    通过 **keepdims=True** 使得计算总和 or 均值时保持轴数不变。例如 shape = [2, 5, 4] 的张量沿轴 axis=1 求和,结果变为 shape = [2,1,4](轴 1 的长度变为 1);shape = [2, 5, 4] 的张量沿轴 axis=[1,2] 求和,结果变为 shape = [2,1,1](轴 1、2 的长度都变为 1)

  • 如下求和操作的结果 sum_Ashape 仍包含两个轴,只不过轴 1 的 shape 变为 1

    sum_A = A.sum(axis=1, keepdims=True)
    sum_A                            # tensor([[ 6.],
                                  #           [22.],
                                  #         [38.],
                                  #         [54.],
                                  #           [70.]])
    
  • 由于 sum_A 在对每行进行求和后仍保持两个轴,可以通过广播将 A 除以 sum_A

    A / sum_A                        # tensor([[0.0000, 0.1667, 0.3333, 0.5000],
                                  #         [0.1818, 0.2273, 0.2727, 0.3182],
                                  #          [0.2105, 0.2368, 0.2632, 0.2895],
                                  #          [0.2222, 0.2407, 0.2593, 0.2778],
                                  #          [0.2286, 0.2429, 0.2571, 0.2714]])
    
  • cumsum 沿某个轴计算 A 元素的累积总和,不会沿任何轴降低输入张量的维度

    A.cumsum(axis=0)                # tensor([[ 0.,  1.,  2.,  3.],
                                  #          [ 4.,  6.,  8., 10.],
                                  #          [12., 15., 18., 21.],
                                  #          [24., 28., 32., 36.],
                                  #          [40., 45., 50., 55.]])
    

    3.7 矩阵乘法

    3.7.1 向量点积 dot product

    两个向量点积,等价于元素乘再求和 ```python y = torch.ones(4, dtype = torch.float32) x, y, torch.dot(x, y)

    (tensor([0., 1., 2., 3.]), tensor([1., 1., 1., 1.]), tensor(6.))

torch.sum(x * y) # tensor(6.)

<a name="CoaRp"></a>
### 3.7.2 矩阵-向量积
矩阵和向量相乘,使用与点积相同的 `mv` 函数,要求矩阵 A 的轴 1 的长度 = x 的长度(维数)
```python
A.shape, x.shape, torch.mv(A, x)    
# (torch.Size([5, 4]), torch.Size([4]), tensor([ 14.,  38.,  62.,  86., 110.]))

3.7.3 矩阵-矩阵乘法(矩阵乘法)

矩阵和矩阵相乘,用 mm 函数

B = torch.ones(4, 3)
torch.mm(A, B)                    # tensor([[ 6.,  6.,  6.],
                                #         [22., 22., 22.],
                                #          [38., 38., 38.],
                                #          [54., 54., 54.],
                                #          [70., 70., 70.]])

3.8 范数

向量范数是将向量映射到标量的函数 f(·)

  1. L2 范数:张量元素平方和的平方根

    1. 向量的 L2 范数

      u = torch.tensor([3.0, -4.0])
      torch.norm(u)                    # tensor(5.)
      
    2. 矩阵的 L2 范数

      torch.norm(torch.ones((4, 9)))    # tensor(6.)
      
  2. L1 范数:张量元素的绝对值之和

    torch.abs(u).sum()                # tensor(7.)
    

4. 矩阵计算(矩阵求导/微积分)

梯度指向值变化最大的方向

4.1 标量导数

image.png

4.2 梯度:将导数拓展到向量

数学的习惯表达上,向量是列向量

深度学习中一般是标量对向量/矩阵求导,因为目标函数(loss)一般都是标量

  1. 标量对向量shape = [n, 1]求导得到的是行向量shape = [1, n],相当于经过了转置)

image.pngimage.png

  1. 向量shape = [n, 1]对标量求导得到的还是列向量shape = [n, 1]

image.png

  1. 向量shape = [k, 1]对向量shape = [n, 1]求导,得到的是矩阵shape = [k, n]

image.pngimage.png

4.3 梯度:拓展到矩阵

image.png

5. 自动求导(微分)

5.1 向量链式法则

image.png
两个例子:
image.pngimage.png

5.2 自动求导

通过计算图表示链式法则

  • 将代码分解成操作子
  • 将计算表示成一个无环图

链式法则 二、预备知识 - 图14 两种模式:

  1. 正向累积:二、预备知识 - 图15
    1. 缺点:需要存储正向的所有中间结果,因此空间复杂度 O(n)
  2. 反向累积(反向传播)二、预备知识 - 图16
    1. 时间复杂度和正向相同,都是 O(n);空间复杂度 O(1)

image.png

例子:

  1. 对函数二、预备知识 - 图18关于列向量二、预备知识 - 图19求导,这里 y 是标量

    深度学习中 loss 通常是标量,因此一般都是标量对张量求导

import torch

x = torch.arange(4.0)
x                        # tensor([0., 1., 2., 3.])
x.requires_grad_(True)  # 等价于 `x = torch.arange(4.0, requires_grad=True)`
x.grad                  # 变量 x 的梯度,默认值是 None
y = 2 * torch.dot(x, x)    # 点积
y                        # tensor(28., grad_fn=<MulBackward0>)
y.backward()            # 反向传播计算梯度
x.grad                    # tensor([ 0.,  4.,  8., 12.])
x.grad == 4 * x            # tensor([True, True, True, True])
  1. 计算二、预备知识 - 图20的另一个函数

    x.grad.zero_()            # 在默认情况下,PyTorch会累积梯度,我们需要清除之前的值
    y = x.sum()                # 前向传播,计算 y 的值
    y.backward()            # 反向传播,自动计算 y 关于 x 每个分量的梯度
    x.grad                    # tensor([1., 1., 1., 1.])
    
  2. 分离计算:将某些计算移动到记录的计算图之外 ```python x.grad.zero_() # 清空梯度 y = x x
    u = y.detach() # 阻止梯度回传,分离出 y,将其视作一个常数,记为 u z = u
    x

z.sum().backward() x.grad == u

```python
x.grad.zero_()
y.sum().backward()
x.grad == 2 * x

6. 概率