跳转至

预备知识👀

约 1498 个字 79 行代码 预计阅读时间 6 分钟

数据操作👀

  • N 维数组 (也称为 张量 | tensor) 是机器学习和神经网络的主要数据结构
  • 张量在 PyTorch 中表示为一个由数值组成的数组,这个数组可能有多个维度。 具有一个轴的张量对应数学上的 向量 (vector); 具有两个轴的张量对应数学上的 矩阵 (matrix); 具有两个轴以上的张量没有特殊的数学名称。

Example

  • 一个数字 1.0
  • 可以表示一个类别
  • 一列数字 [1.0, 2.0, 3.0]
  • 可以表示一个特征向量
  • 一个表格
[[1.0, 2.0],
 [3.0, 4.0]]
  • 可以表示一个样本——特征矩阵

可以表示 RGB 图片(宽 x 高 x 通道)

可以表示一个 RGB 图片批量(批量大小 x 宽 x 高 x 通道)

可以表示一个视频批量(批量大小 x 时间 x 宽 x 高 x 通道)

入门👀

首先导入 PyTorch。

import torch

创建👀

  • 可以使用 arange 创建一个行向量 x (只有一维)。
  • 张量中的每个值都称为张量的 元素(element)。例如,张量 x 中有 8 个元素。
  • 除非额外指定,新的张量将存储在内存中,并基于 CPU 计算。

Code

code
size = 8
x = torch.arange(size)
x

output
tensor([0, 1, 2, 3, 4, 5, 6, 7])

  • 使用全 0 元素创建并初始化矩阵:

Code

code
size = 8
x = torch.zeros((2, 3, 4))
x

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

  • 使用全 1 元素创建并初始化矩阵:x=torch.ones((2, 3, 4))
  • 随机初始化矩阵:x=torch.randn(3, 4) (其中每个元素都从均值为0、标准差为1的标准高斯分布(正态分布)中随机采样。)
  • 还可以通过提供包含数值的Python列表(或嵌套列表),来为所需张量中的每个元素赋予确定值。
    • torch.tensor([[2, 1, 4, 3], [1, 2, 3, 4], [4, 3, 2, 1]])
    • 在这里,最外层的列表对应于轴 0,内层的列表对应于轴 1。

属性👀

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

Code

code
x.shape

output
torch.Size([8])

  • 通过 numel 函数可以得到张量中元素的总数,即形状的所有元素乘积。

Code

code
x.numel()

output
8

  • 要想改变一个张量的形状而不改变元素数量和元素值,可以调用 reshape 函数。
  • 例如,可以把张量 x 从形状为 (8, ) 的行向量转换为形状为 (2,4) 的矩阵。
  • 这个新的张量包含与转换前相同的值,但是它被看成一个 2 行 4 列的矩阵。

Code

code
X = x.reshape(3, 4)
X

output
tensor([[ 0,  1,  2,  3],
    [ 4,  5,  6,  7]])

Tip

  • 不需要通过手动指定每个维度来改变形状。
  • 即,如果我们的目标形状是(高度,宽度), 那么在知道宽度后,高度会被自动计算得出。
  • 实践中可以通过 -1 来调用此自动计算出维度的功能。 即我们可以用 x.reshape(-1,4)x.reshape(2,-1) 来取代 x.reshape(2,4)

运算符👀

对于任意具有相同形状的张量,常见的标准算术运算符(+-*/**)都可以被升级为按元素运算

Code

code
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  # **运算符是求幂运算

output
(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.]))


除了按元素计算,也可以执行线性代数运算,包括向量点积和矩阵乘法。

  • 通过 cat 函数来连接张量,把它们端对端地叠起来形成一个更大的张量。
  • 我们只需要提供张量列表,并给出沿哪个轴连结。

Code

code
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), torch.cat((X, Y), dim=1)

output
(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.]]),
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.]]))

  • 也可以通过 逻辑运算符 构建新的二元张量。如 X == Y,会创建大小相同的新张量,其中的每个元素都是 0(如果两个张量在相应位置相等)或 1(如果不等)。

Code

code
X == Y

output
tensor([[False,  True, False,  True],
        [False, False, False, False],
        [False, False, False, False]])

  • 可以对张量中的所有元素进行求和,得到只有一个元素的张量。

Code

code
X.sum()

output
tensor(66.)

广播机制👀

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

  1. 通过适当复制元素来扩展一个或两个数组,以便在转换之后,两个张量具有相同的形状;
  2. 对生成的数组执行按元素操作。

在大多数情况下,我们将沿着数组中长度为1的轴进行广播,如下例子:

Code

code
a = torch.arange(3).reshape((3, 1))
b = torch.arange(2).reshape((1, 2))
a, b
a + b

output
(tensor([[0],
        [1],
        [2]]),
tensor([[0, 1]]))
tensor([[0, 1],
        [1, 2],
        [2, 3]])

  • ab分别是 \(3\times 1\)\(1\times 2\) 矩阵,如果让它们相加,它们的形状不匹配。
  • 我们将两个矩阵 广播 为一个更大的 \(3\times 2\) 矩阵,如上所示:矩阵a将复制列,矩阵b将复制行,然后再按元素相加。

索引和切片👀

  • 张量中的元素可以通过索引访问。
  • 与任何 Python 数组一样:第一个元素的索引是 0,最后一个元素索引是 -1;
  • 也可以指定范围 (前闭后开), 例如 [1:3] 表示第二和第三个元素 (从 0 开始)。

除了索引读取操作外,也可以通过指定索引来将元素写入矩阵。

Code

code
X[1, 2] = 9
X

output
tensor([[ 0.,  1.,  2.,  3.],
        [ 4.,  5.,  9.,  7.],
        [ 8.,  9., 10., 11.]])

  • 如果我们想为多个元素赋值相同的值,我们只需要索引所有元素,然后为它们赋值。例如,X[0:2, :] = 12 将为矩阵 X 的前两行的所有列赋值为 12。

Code

code
X[0:2, :] = 12
X

output
tensor([[12., 12., 12., 12.],
        [12., 12., 12., 12.],
        [ 8.,  9., 10., 11.]])

节省内存👀

  • 运算符和索引的结果都会分配新的内存来存储结果。
  • 例如,如果我们写 Y = X + Y,我们会将 Y 指向新分配的内存。
    - 并且在机器学习中,可能有数百兆的参数,并且在一秒内多次更新所有参数。通常情况下,我们希望原地执行这些更新。
    - 而且如果不原地更新,其他引用仍然会指向旧的内存位置,这样我们的某些代码可能会无意中引用旧的参数;

Code

code
before = id(Y)
Y = Y + X
id(Y) == before

output
False

id()

id() 函数返回对象的唯一标识符,给我们提供了内存中引用对象的确切地址

原地更新👀

可以使用切片表示法将操作的结果分配给先前分配的数组,例如 Y[:] = <expression>

Code

code
Z = torch.zeros_like(Y)
before = id(Z)
Z[:] = X + Y
id(Z) == before

output
True

数据预处理👀

线性代数👀

微积分👀

自动微分👀

概率👀