如无特殊说明,默认使用 pytorch 编写代码 (虽然它被称为 PyTorch,但是代码中使用 torch 而不是 pytorch )

import torch

# 一、数据操作 ——Pytorch

# 常见数据类型 —— 张量 (tensor)

张量也称 n 维数组。一维张量对应数学上的 向量(vector) ; 二维张量对应数学上的 矩阵(matrix) ; 二维以上的张量没有特殊的数学名称。

# 常用定义方法
  • # 定义一维向量 vector
x = torch.arange(12)
x
>> tensor([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11])
  • # 定义多维张量 tensor

    1⃣️直接定义

    x = torch.tensor([[2, 1, 4, 3], [1, 2, 3, 4], [4, 3, 2, 1]])
    x
    >> tensor([[2, 1, 4, 3],
               [1, 2, 3, 4],
               [4, 3, 2, 1]])

    2⃣️reshape 一维向量

    x = torch.arange(12)
    X = x.reshape(3, 4)	# 或 x.reshape (-1, 4), x.reshape (3, -1) (-1 表示自动计算)
    X
    >> tensor([[ 0,  1,  2,  3],
               [ 4,  5,  6,  7],
               [ 8,  9, 10, 11]])
    Y = x.reshape(2, 2, 3) # 同理可用 - 1 自动计算某维度数据个数
    Y
    >> tensor([[[ 0,  1,  2],
                [ 3,  4,  5]],
               [[ 6,  7,  8],
                [ 9, 10, 11]]])

    3⃣️使用 zeros, ones 函数

    torch.zeros((2, 3, 4))
    >> tensor([[[0., 0., 0., 0.],
                [0., 0., 0., 0.],
                [0., 0., 0., 0.]],
               [[0., 0., 0., 0.],
                [0., 0., 0., 0.],
                [0., 0., 0., 0.]]])
    torch.ones((2, 3, 4))
    >> tensor([[[1., 1., 1., 1.],
                [1., 1., 1., 1.],
                [1., 1., 1., 1.]],
               [[1., 1., 1., 1.],
                [1., 1., 1., 1.],
                [1., 1., 1., 1.]]])

    4⃣️使用 randn 函数

    randn 函数从均值为 0、标准差为 1 的标准高斯分布(正态分布)中随机采样

    torch.randn(3, 4)
    >> tensor([[-1.4437, -0.1485,  1.2988,  1.8582],
               [ 0.7805, -0.6908,  2.9051, -0.2981],
               [-0.8622,  0.5315, -2.4702, -1.2276]])
# 常用数据操作
  • # shape

    可以通过张量的 shape 属性来访问张量(沿每个轴的长度)的形状

    x = torch.arange(12)
    x.shape
    >> torch.Size([12])
  • # numel()

    通过 numel() 函数,我们可以迅速查看一个张量到底又多少元素。

    x = torch.arange(12)
    x.numel()
    >> 12

# 数据运算

# 按元素运算
  • # + - * / ** exp()
    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)  # ** 运算符是求幂运算,exp () 是求 e 的指数幂
    >> 	(tensor([ 3.,  4.,  6., 10.]),
         tensor([-1.,  0.,  2.,  6.]),
         tensor([ 2.,  4.,  8., 16.]),
         tensor([0.5000, 1.0000, 2.0000, 4.0000]),
         tensor([ 1.,  4., 16., 64.]))
    		 tensor([2.7183e+00, 7.3891e+00, 5.4598e+01, 2.9810e+03])
  • # 布尔运算 == > < >= <=
    x = torch.arange(12).reshape(3, 4)
    y = torch.arange(12).reshape(3, 4)
    x >= y
    >> tensor([[True, True, True, True],
               [True, True, True, True],
               [True, True, True, True]])
  • # 广播机制

    在某些情况下,即使形状不同,我们仍然可以通过调用 广播机制(broadcasting mechanism)来执行按元素操作。 这种机制的工作方式如下:

    1. 通过适当复制元素来扩展一个或两个数组,以便在转换之后,两个张量具有相同的形状;
    2. 对生成的数组执行按元素操作。
    a = torch.arange(3).reshape((3, 1))
    b = torch.arange(2).reshape((1, 2))
    a, b
    >> (tensor([[0],
                [1],
                [2]]),
        tensor([[0, 1]]))
    # 由于 a 和 b 分别是 3*1 和 1*2 矩阵,如果让它们相加,它们的形状不匹配。我们将两个矩阵广播为一个更大的矩阵,
    # 如下所示:矩阵 a 将复制列,矩阵 b 将复制行,然后再按元素相加。
    a + b
    >> tensor([[0, 1],
               [1, 2],
               [2, 3]])
# 线性代数运算
  • # 点积

    两个向量的点积 (dot product) x⊤y (或⟨x,y⟩) 是相同位置的按元素乘积的和。

    x = torch.arange(4, dtype = torch.float32)
    y = torch.ones(4, dtype = torch.float32)
    x, y, torch.dot(x, y), torch.sum(x * y)
    >> (tensor([0., 1., 2., 3.]), tensor([1., 1., 1., 1.]), tensor(6.)), tensor(6.)
  • # 矩阵 - 向量积
    A = torch.arange(20).reshape(5, 4)
    x = torch.arange(4)
    torch.mv(A, x)
    >> (tensor([[ 0,  1,  2,  3],
             	 [ 4,  5,  6,  7],
             	 [ 8,  9, 10, 11],
             	 [12, 13, 14, 15],
             	 [16, 17, 18, 19]]),
     	  tensor([0, 1, 2, 3]),
     	  tensor([ 14,  38,  62,  86, 110]))
  • # 矩阵乘法
    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.]])
  • # 求矩阵 (二维 tensor) 的转置
B = torch.arange(9).reshape(3, 3)
B.T
>> tensor([[0, 3, 6],
        	 [1, 4, 7],
        	 [2, 5, 8]])
  • # 范数

    线性代数中最有用的一些运算符是范数(norm)。 非正式地说,向量的范数是表示一个向量有多大。 这里考虑的大小(size)概念不涉及维度,而是分量的大小。

    在线性代数中,向量范数是将向量映射到标量的函数 f。 给定任意向量 x,向量范数要满足一些属性。 第一个性质是:如果我们按常数因子 α 缩放向量的所有元素, 其范数也会按相同常数因子的绝对值缩放:

    f(αx)=αf(x).f(\alpha \mathbf{x}) = |\alpha| f(\mathbf{x}).

    第二个性质是熟悉的三角不等式:

    f(x+y)f(x)+f(y).f(\mathbf{x} + \mathbf{y}) \leq f(\mathbf{x}) + f(\mathbf{y}).

    第三个性质简单地说范数必须是非负的:

    f(x)0.f(\mathbf{x}) \geq 0.

    这是有道理的。因为在大多数情况下,任何东西的最小的大小是 0。 最后一个性质要求范数最小为 0,当且仅当向量全由 0 组成。

    i,[x]i=0f(x)=0.\forall i, [\mathbf{x}]_i = 0 \Leftrightarrow f(\mathbf{x})=0.

    范数听起来很像距离的度量。 欧几里得距离和毕达哥拉斯定理中的非负性概念和三角不等式可能会给出一些启发。 事实上,欧几里得距离是一个 L2 范数: 假设 n 维向量 x 中的元素是 x1,…,xn,其 L2 范数是向量元素平方和的平方根:

    x2=i=1nxi2\|\mathbf{x}\|_2 = \sqrt{\sum_{i=1}^n x_i^2}

    其中,在 L2 范数中常常省略下标 2,也就是说‖x‖等同于‖x‖2。 在代码中,我们可以按如下方式计算向量的 L2 范数。

    u = torch.tensor([3.0, -4.0])
    torch.norm(u)
    >> tensor(5.)

    深度学习中更经常地使用 L2 范数的平方,也会经常遇到 L1 范数,它表示为向量元素的绝对值之和:

    x1=i=1nxi.\|\mathbf{x}\|_1 = \sum_{i=1}^n \left|x_i \right|.

    与 L2 范数相比,L1 范数受异常值的影响较小。 为了计算 L1 范数,我们将绝对值函数和按元素求和组合起来。

    torch.abs(u).sum()

    L2 范数和 L1 范数都是更一般的 Lp 范数的特例

    xp=(i=1nxip)1/p.\|\mathbf{x}\|_p = \left(\sum_{i=1}^n \left|x_i \right|^p \right)^{1/p}.

    类似于向量的 L2 范数,矩阵 X∈Rm×n 的 Frobenius 范数(Frobenius norm)是矩阵元素平方和的平方根:

    XF=i=1mj=1nxij2.\|\mathbf{X}\|_F = \sqrt{\sum_{i=1}^m \sum_{j=1}^n x_{ij}^2}.

    Frobenius 范数满足向量范数的所有性质,它就像是矩阵形向量的 L2 范数。 调用以下函数将计算矩阵的 Frobenius 范数。

    torch.norm(torch.ones((4, 9)))
    >> tensor(6.)

    在深度学习中,我们经常试图解决优化问题: 最大化分配给观测数据的概率;最小化预测和真实观测之间的距离。 用向量表示物品(如单词、产品或新闻文章),以便最小化相似项目之间的距离,最大化不同项目之间的距离。 目标,或许是深度学习算法最重要的组成部分(除了数据),通常被表达为范数。

# 求和
  • # 降维求和

    默认情况下,调用求和函数会沿所有的轴降低张量的维度,使它变为一个标量。

    A = torch.arange(20, dtype=torch.float32).reshape(5, 4)
    A, A.sum()
    >> (tensor([[ 0.,  1.,  2.,  3.],
                [ 4.,  5.,  6.,  7.],
                [ 8.,  9., 10., 11.],
                [12., 13., 14., 15.],
                [16., 17., 18., 19.]]),
        tensor(190.))

    我们还可以指定张量沿哪一个轴来通过求和降低维度。 指定 axis=1 将通过汇总所有列的元素降维(轴 1)。因此,输入轴 1 的维数在输出形状中消失。

    A.sum(axis=0), A.sum(axis=1)
    >> (tensor([40., 45., 50., 55.]), tensor([ 6., 22., 38., 54., 70.]))

    沿着行和列对矩阵求和,等价于对矩阵的所有元素进行求和。

    A.sum(axis=[0, 1])  # 结果和 A.sum () 相同
    >> tensor(190.)
  • # 非降维求和

    有时在调用函数来计算总和或均值时需要保持轴数。

    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]])

    如果我们想沿某个轴计算 A 元素的累积总和, 比如 axis=0 (按行计算),可以调用 cumsum 函数。 此函数不会沿任何轴降低输入张量的维度。

    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.]])
# 求平均值

一个与求和相关的量是平均值(mean 或 average)。 我们通过将总和除以元素总数来计算平均值。 在代码中,我们可以调用函数来计算任意形状张量的平均值。

A = torch.arange(20, dtype=torch.float32).reshape(5, 4)
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.]))
# 张量连结(concatenate)

使用 torch.cat 函数可以把多个张量连结在一起,把它们端对端地叠起来形成一个更大的张量。

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]])
torch.cat((X, Y), dim=0) # dim = 0,按行连结(行数增加)
>> tensor([[ 0.,  1.,  2.,  3.],
           [ 4.,  5.,  6.,  7.],
           [ 8.,  9., 10., 11.],
           [ 2.,  1.,  4.,  3.],
           [ 1.,  2.,  3.,  4.],
           [ 4.,  3.,  2.,  1.]])
torch.cat((X, Y), dim=1) # dim = 1,按列连结(列数增加)
>> tensor([[ 0.,  1.,  2.,  3.,  2.,  1.,  4.,  3.],
           [ 4.,  5.,  6.,  7.,  1.,  2.,  3.,  4.],
           [ 8.,  9., 10., 11.,  4.,  3.,  2.,  1.]])

# 二、数据预处理 ——pandas

为了能用深度学习来解决现实世界的问题,我们经常从预处理原始数据开始, 而不是从那些准备好的张量格式数据开始。 在 Python 中常用的数据分析工具中,我们通常使用 pandas 软件包。 像庞大的 Python 生态系统中的许多其他扩展包一样, pandas 可以与张量兼容。

import pandas as pd

# 读取数据集 (csv 文件)

data = pd.read_csv(data_file)
print(data)
>>   NumRooms Alley   Price
0       NaN  Pave  127500
1       2.0   NaN  106000
2       4.0   NaN  178100
3       NaN   NaN  140000

# 处理缺失值

在 pandas 中,“NaN” 项代表缺失值。 为了处理缺失的数据,典型的方法包括插值法删除法, 其中插值法用一个替代值弥补缺失值,而删除法则直接忽略缺失值。

  • # 插值法

    通过位置索引 iloc ,我们将 data 分成 inputsoutputs , 其中前者为 data 的前两列,而后者为 data 的最后一列。 对于 inputs 中缺少的数值,我们用同一列的均值替换 “NaN” 项。

    inputs, outputs = data.iloc[:, 0:2], data.iloc[:, 2]
    inputs = inputs.fillna(inputs.mean())
    print(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

# 转换为张量格式

现在 inputsoutputs 中的所有条目都是数值类型,它们可以转换为张量格式。

X = torch.tensor(inputs.to_numpy(dtype=float))
y = torch.tensor(outputs.to_numpy(dtype=float))
X, y
>> (tensor([[3., 1., 0.],
         [2., 0., 1.],
         [4., 0., 1.],
         [3., 0., 1.]], dtype=torch.float64),
 tensor([127500., 106000., 178100., 140000.], dtype=torch.float64))

当数据采用张量格式后,可以使用张量函数来进一步操作。