以下内容来自于曾芃壹的《PyTroch 深度学习入门》。

第二章 Tensor 张量基础

Tensor

Tensor 即张量,是 PyTorch 最基础的数据类型,是进行数据存储和运算的基本单元。

1. 数学含义

张量用来表示高维数据(矩阵),之前接触过的标量、向量都是张量的特例。标量是 0 维张量,向量是 1 维张量,矩阵则是 2 维张量,如果需要表示三维张量,则需要三个维度的坐标。
图 2.1 张量示意图

2. 创建方法

想要使用 PyTorch 里的张量,自然需要先导入 PyTorch:

1
import torch

使用 torch.Tensor() 函数创建 Tensor。这里传入参数是创建一个 2 × 4 大小的矩阵:

1
2
3
4
5
x= torch.Tensor(2, 4)
x
# 输出
# tensor([[2.8086e+35, 4.4542e-41, 1.8886e-34, 0.0000e+00],
# [4.4842e-44, 0.0000e+00, 1.1210e-43, 0.0000e+00]])

查看变量的类型:

1
2
3
4
5
6
x.type() # 返回变量的类型
# 输出
# 'torch.FloatTensor'
x.dtype() # 返回 dtype 类型
# 输出
# torch.float32

以下显示的树 Tensor 的 8 种数据类型:
表2.1 Tensor 数据类型
创建 2 × 3 × 4 的 64 位浮点数 Tensor:

1
2
3
4
5
6
7
8
9
10
11
y = torch.DoubleTensor(2, 3, 4)
y
# 输出
# tensor([[[4.9407e-324, 1.1858e-322, 0.0000e+00, 0.0000e+00],
# [ 0.0000e+00, 0.0000e+00, 4.9407e-324, 0.0000e+00],
# [ 0.0000e+00, 4.0019e-322, 3.9007e-316, 6.1995e-316]],
#
# [[1.1858e-322, 1.7438e-309, 2.3715e-322, 1.5810e-322],
# [6.2258e-316, 0.0000e+00, 9.4861e-322, 5.5335e-322],
# [6.2408e-316, 4.1733e-57, 2.5278e-52, 6.7977e-310]]],
# dtype=torch.float64)

创建 Tensor 的时候可以进行初始化,可以通过传入 List 数据结构进行初始化:

1
2
3
4
5
6
list = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
torch.Tensor(list)
# 输出
# tensor([[1., 2., 3.],
# [4., 5., 6.],
# [7., 8., 9.]])

通过 Python 的索引来获取 Tensor 的值(就像使用 Python 列表一样,索引也从 0 开始):

1
2
3
4
x = torch.Tensor([[2, 4, 5], [7, 6, 3]])
x[0][2]
# 输出
# tensor(5.)

图 2.2 Tensor 索引示意图

3. 快速创建方法

  • torch.zeros() 函数:创建全为 0 的 Tensor。
1
2
3
4
torch.zeros(2, 4)
# 输出
# tensor([[0., 0., 0., 0.],
# [0., 0., 0., 0.]])
  • torch.eye() 函数:创建对角线位置元素全为 1、其他位置全为 0 的 Tensor。
1
2
3
4
5
torch.eye(3)
# 输出
# tensor([[1., 0., 0.],
# [0., 1., 0.],
# [0., 0., 1.]])
  • torch.ones() 函数:创建全为 1 的 Tensor。
1
2
3
4
torch.ones(2, 4)
# 输出
# tensor([[1., 1., 1., 1.],
# [1., 1., 1., 1.]])
  • torch.rand() 函数:创建随机初始化区间为 [0, 1) 的随机数 Tensor。
1
2
3
4
torch.rand(2, 4)
# 输出
# tensor([[0.4530, 0.5580, 0.2956, 0.0357],
# [0.3973, 0.1589, 0.6523, 0.5951]])
  • torch.arange() 函数:创建一个在指定区间内按指定步长递增的一维 Tensor,前两个参数指定区间范围,第三个参数指定步长。
1
2
3
4
5
6
torch.arange(1, 4)
# 输出
# tensor([1, 2, 3])
torch.arange(1, 4, 0.5)
# 输出
# tensor([1.0000, 1.5000, 2.0000, 2.5000, 3.0000, 3.5000])

常用 Tensor 创建方法:
表 2.2 Tensor 常见创建方法

4. 常用数学操作

Tensor 的数学操作方法有 90 多中,以下是常见的 25 种数学操作方法:
表 2.3 常用的 Tensor 数学方法
续表 2.3
Tensor 的数学操作实现方法一般有两种:1. 直接用 Tensor 实例调用数学操作方法;2. 使用 torch 库方法。如在进行加法之前,如果不初始化两个形状相同的 Tensor,则不能进行相加。
下面是初始化两个性状 2 × 3 的张量:

1
2
3
4
5
6
7
8
9
10
a = torch.Tensor([[1, 2, 3], [4, 5, 6]])
b = torch.ones(2, 3)
a
# 输出
# tensor([[1., 2., 3.],
# [4., 5., 6.]])
b
# 输出
# tensor([[1., 1., 1.],
# [1., 1., 1.]])

使用方法一相加:

1
2
3
4
b.add(a)
# 输出
# tensor([[2., 3., 4.],
# [5., 6., 7.]])

第二种方法,使用 torch.add() 方法:

1
2
3
4
torch.add(a, b)
# 输出
# tensor([[2., 3., 4.],
# [5., 6., 7.]])

另外,如果数学操作函数带下划线 _,返回值将覆盖对象:

1
2
3
4
5
6
7
8
b.add_(a)
# 输出
# tensor([[2., 3., 4.],
# [5., 6., 7.]])
b
# 输出
# tensor([[2., 3., 4.],
# [5., 6., 7.]])

Tensor 每个元素都加上一个标量:

1
2
3
4
5
6
7
8
9
10
11
12
13
a = torch.rand(3)
a
# 输出
# tensor([0.4743, 0.6836, 0.2396])
a + 2
# 输出
# tensor([2.4743, 2.6836, 2.2396])
torch.add(a, 2)
# 输出
# tensor([2.4743, 2.6836, 2.2396])
a.add(2)
# 输出
# tensor([2.4743, 2.6836, 2.2396])
  • abs() 方法:返回 Tensor 种每个元素的绝对值:
1
2
3
4
torch.abs(torch.Tensor([[-5, -4, -3], [-3, -2, -1]]))
# 输出
# tensor([[5., 4., 3.],
# [3., 2., 1.]])
  • ceil() 方法:Tensor 每个元素向上取整:
1
2
3
torch.ceil(torch.Tensor([0.2, 1.5, 3.4]))
# 输出
# tensor([1., 2., 4.])
  • exp() 方法:返回 Tensor 每个元素以 e 为底的指数:
1
2
3
torch.exp(torch.Tensor([1, 2, 3]))
# 输出
# tensor([ 2.7183, 7.3891, 20.0855])
  • max() 方法:返回 Tensor 种所有元素的最大值:
1
2
3
torch.max(torch.Tensor([[1, 2, 3], [4, 5, 6]]))
# 输出
# tensor(6.)

5. 线性代数运算

  • torch.dot() 向量点积运算:
1
2
3
4
5
a = torch.Tensor([1, 2, 3])
b = torch.Tensor([2, 3, 4])
torch.dot(a, b)
# 输出
# tensor(20.)
  • torch.mv() 矩阵和向量乘法:
1
2
3
4
5
6
7
8
a = torch.Tensor([[1, 2, 3], [2, 3, 4], [3, 4, 5]])
a
b = torch.Tensor([1, 2, 3])
b
torch.mv(a, b)

# 输出
# tensor([14., 20., 26.])
  • torch.mm() 两个矩阵相乘:
1
2
3
4
5
6
7
8
9
10
a = torch.Tensor([[1, 2, 3], [2, 3, 4], [3, 4, 5]])
a
b = torch.Tensor([[2, 3, 4], [3, 4, 5], [4, 5, 6]])
b
torch.mm(a, b)

# 输出
# tensor([[20., 26., 32.],
# [29., 38., 47.],
# [38., 50., 62.]])

其他的常用线性代数操作:
其他常用的线性代数操作

6. 连接和切片

torch.cat() 可以将多个 Tensor 沿某维度进行连接。cat() 函数的两个参数:①需要进行连接的所有 Tensor 组成的元组;②连接的维度。
如让 a 和 b 在第一个维度进行连接,第二个参数为 0:

1
2
3
4
5
6
7
8
9
10
11
a = torch.rand(2, 2)
a
b = torch.rand(2, 2)
b
torch.cat((a, b), 0)

# 输出
# tensor([[0.8963, 0.2463],
# [0.4969, 0.6975],
# [0.6397, 0.9786],
# [0.3246, 0.5599]])

若 a 和 b 在第二个维度相连,第二个参数为 1:

1
2
3
4
5
torch.cat((a, b), 1)

# 输出
# tensor([[0.8963, 0.2463, 0.6397, 0.9786],
# [0.4969, 0.6975, 0.3246, 0.5599]])

torch.chunk() 可以将 Tensor 沿某维度进行切片,chunk() 函数需要传入的 3 个参数:①被切片的 Tensor 对象;②切分的块数;③切分的维度。
先把张量 c 按第二个维度切分为两块:

1
2
3
4
5
6
7
8
9
10
11
12
13
c = torch.rand(2, 4)
c

# 输出
# tensor([[0.9313, 0.2512, 0.2672, 0.5346],
# [0.3681, 0.8085, 0.2904, 0.6409]])
torch.chunk(c, 2, 1)

# 输出
# (tensor([[0.9313, 0.2512],
# [0.3681, 0.8085]]),
# tensor([[0.2672, 0.5346],
# [0.2904, 0.6409]]))

torch.t() 函数求转置矩阵,该函数只适用于二维 Tensor,也就是矩阵:

1
2
3
4
5
6
7
8
9
10
11
12
a = torch.rand(2, 2)
a

# 输出
# tensor([[0.2841, 0.4642],
# [0.9186, 0.8903]])

torch.t(a)

# 输出
# tensor([[0.2841, 0.9186],
# [0.4642, 0.8903]])

其他常见的连接切片操作:
其他连接和切片操作

7. 变形

view() 函数可以改变 Tensor 的形状,当想要高维的 Tensor 转化为一维的 Tensor,就是使用 view() 函数。

1
2
3
4
5
x = torch.rand(2, 3, 4)
x.size()

# 输出
# torch.Size([2, 3, 4])

使用 view() 函数把该 Tensor 转换为 2 × 12 的 Tensor,总元素数目保持不变:

1
2
3
4
5
y = x.view(2, 12)
y.size()

# 输出
# torch.Size([2, 12])

若使用 -1 为 view() 函数的参数,代表该维度数目自动计算。若想要设置第二位为 1,第一维自动计算:

1
2
3
4
5
z = x.view(-1, 1)
z.size()

# 输出
# torch.Size([24, 1])

8. CUDA 加速

使用 cuda() 函数可以让接下来的任何运算都调用 GPU 进行加速计算。

Autograd

1. 基本原理

Autograd 就是自动微分,PyTorch 可以帮我们进行自动微分。
Tensor 在自动微分方面有 3 个重要属性:requires_gradgradgrad_fn
required_grad 属性是布尔值,默认为 False。若为 True,表示该 Tensor 需要自动微分。
grad 属性用于存储 Tensor 的微分值。
grad_fn 用于存储 Tensor 的微分函数。
当叶子节点的 requires_grad 为 True 时,信息流经过该节点时,所有中间节点的 requires_grad 属性都会变成 True,只需要在输出节点调用反向传播函数 backward() ,PyTorch 就会自动求出叶子节点的为分支并更新存储在叶子节点的 grad 属性中。只有叶子节点的 grad 属性能被更新。
计算示意图

2. 前向传播

Autograd 技术可以帮助我们从叶子节点开始追踪信息流,记下整个过程使用的函数,直到输出节点,这个过长被称为前向传播。
先初始化叶子节点 xx

1
2
3
4
5
x = torch.ones(2)
x.requires_grad

# 输出
# False

默认 requires_grad 为 False。如果需要让 PyTorch 自动帮我们进行微分计算,需要把 xx 的 requires_grad 设置为 True:

1
2
3
4
5
x.requires_grad = True
x

# 输出
# tensor([1., 1.], requires_grad=True)

设置完成后,xx 的 grad 和 grad_fn 属性为空值:

1
2
x.grad
x.grad_fn

xx × 4 得到 zz

1
2
3
4
5
z = x * 4
z

# 输出
# tensor([4., 4.], grad_fn=<MulBackward0>)

grad_fn 是微分函数,这里是乘法的反向函数。最后用 norm() 函数求 zz 的长度:

1
2
3
4
5
y = z.norm()
y

# 输出
# tensor(5.6569, grad_fn=<LinalgVectorNormBackward0>)

yy 的 grad_fn 是 norm() 的反向函数。

3. 反向传播

使用 backward() 函数对整个图进行反向传播,求出微分值:

1
2
3
4
5
y.backward()
x.grad

# 输出
# tensor([2.8284, 2.8284])

运行完后 xx 的 grad 属性更新为 xx 的微分值,而 zzyy 的 grad 值并没有变,因为他们都不是叶子节点。

1
2
z.grad()
y.grad()

4. 非标量输出

如果输出节点不是标量,使用 backward() 函数需要增加一个参数 geradient。gradient 的形状应该与输出节点的形状保持一致且元素值均为 1。

1
2
3
4
5
6
7
8
9
10
z = torch.ones(2, 1)
X = torch.Tensor([[2, 3], [1, 2]])
X.requires_grad=True

y = X.mm(z)
y

# 输出
# tensor([[5.],
# [3.]], grad_fn=<MmBackward0>)

若此时调用 yybackward() 函数,需传入一个形状与 yy 形状一样且元素全为 1 的向量:

1
2
3
4
5
6
y.backward(torch.ones(2, 1))
X.grad

# 输出
# tensor([[1., 1.],
# [1., 1.]])