PyTorch张量运算与自动微分
PyTorch由Facebook人工智能研究院于2017年推出,具有强大的GPU加速张量计算功能,并且能够自动进行微分计算,从而可以使用基于梯度的方法对模型参数进行优化,大部分研究人员、公司机构、数据比赛都使用PyTorch。
张量创建
在深度学习领域你会经常看到张量(Tensor)的表述,张量是深度学习的基础,所以谷歌会把他的深度学习框架叫做TensorFlow。深度学习中的张量可以理解成数组,类似numpy的array。例如:
- 单个数字就是0维张量,称为标量(scalar);
- 1维张量称为向量(vector);
- 2维张量称为矩阵(matrix);
- 再多点维度就统一称作张量了。
高等代数中学习过矩阵运算,就是最基本的张量运算。
在用Transformers时最常见的是二维和三维张量。二维张量一般是权重矩阵W等,三维张量一般是原数据处理成序列长度模型维度batchsize×序列长度×模型维度。
在描述张量维度时,或者创建多维张量时,你会经常看到 W ∈ R d m × d k × d h W\in\mathbb{R}^{d_m \times d_k \times d_h} W∈Rdm×dk×dh这种类似表述,用几行几列这样的方式去理解的话,相当不直观。
一种直观理解就是用类似多维数组的思想,用"框的数量"来理解。
d m × d k × d h d_m \times d_k \times d_h dm×dk×dh代表最大一个框包着m个框、再下一层有k个,最里层有h个。
第零维m个框:
m 个 [ [ . . . ] , [ . . . ] , . . . , [ . . . ] ⏞ ] \begin{array}{c} m个 \\ [\overbrace{[...],[...],...,[...]}] \end{array} m个[[...],[...],...,[...] ]
第一维k个框
k 个 [ [ [ . . . ] , . . . , [ . . . ] ⏞ ] , . . . , ] \begin{array}{} k个 \\ [[\overbrace{[...],...,[...]}],...,] \end{array} k个[[[...],...,[...] ],...,]
第二维h个框
h 个 [ [ [ [ . . . ] , . . . , [ . . . ] ⏞ ] , . . . ] , . . . ] \begin{array}{} h个 \\ [[[\overbrace{[...],...,[...]}],...],...] \end{array} h个[[[[...],...,[...] ],...],...]
Pytorch张量操作
PyTorch提供了多种方式来创建张量,以创建一个2×3的矩阵为例:
python
import torch
# empty作用就是初始化一块内存放着,里面数据不重要,根本不会用
t = torch.empty(2, 3)
# 随机初始化张量,范围是[0,1)
t = torch.rand(2, 3)
# 随机初始化张量,服从标准正态分布
t = torch.randn(2, 3)
# 全0矩阵,其中的0是长整型,也可以换成torch.double、torch.float64
t = torch.zeros(2, 3, dtype=torch.long)
# 同理有全1矩阵
t = torch.ones(2, 3, dtype=torch.long)
上面比较常用的是全0和全1,对判断真假很有用。也可以从一个张量创造维度相同的张量:
python
import torch
t = torch.empty(2, 3)
x = torch.rand_like(a)
x = torch.randn_like(a)
x = torch.zeros_like(a)
x = torch.ones_like(a)
也可以通过基于已有的数组创建张量:
python
# 从列表
_list = [[1.0, 3.8, 2.1], [8.6, 4.0, 2.4]]
t = torch.tensor(_list)
# 从ndarray
import numpy as np
array = np.array([[1.0, 3.8, 2.1], [8.6, 4.0, 2.4]])
t = torch.from_numpy(array)
这样创建的张量默认在CPU ,将其调入GPU有如下方式:
python
t = torch.empty(2, 3).cuda()
t = torch.empty(2, 3, device="cuda")
t = torch.empty(2, 3).to("cuda")
默认是使用当前第0张卡,指定用第1张卡:
python
t= torch.empty(2, 3).cuda(1)
t= torch.empty(2, 3, device="cuda:1")
t= torch.empty(2, 3).to("cuda:1")
对应的可以调入CPU:
python
t = torch.empty(2, 3).cpu()
t = torch.empty(2, 3, device="cpu")
t = torch.empty(2, 3).to("cpu")
张量运算
张量的加减乘除、拆拼换调、特殊函数,都能在PyTorch找到快速方法。
加减乘除
python
x = torch.rand(2, 3)
y = torch.rand(2, 3)
# 等价于x + y
z = torch.add(x, y)
# torch没有减方法,但是可以x - y
# 矩阵点乘,multiplication,Hadamard积,等价于x * y
z = torch.mul(x, y)
# 矩阵叉乘,矩阵乘法,matrix multiplication,等价于x @ y
z = torch.mm(x, y)
# 会报错,因为两者的维度不能做叉乘,需要如下转置
z = torch.mm(x, y.T)
# 三维对应矩阵乘法,batch matrix multiplication
x = torch.rand(2, 3, 4)
y = torch.rand(2, 4, 3)
z = torch.bmm(x, y)
# 更普遍的矩阵叉乘
z = torch.matmul(x, y)
# 除法不常用,但也可以x / y
广播机制
前面我们都是假设参与运算的两个张量形状相同,但是PyTorch同样可以处理不相同形状的张量。
python
x = torch.ones(2, 3, 4)
y = torch.ones(1, 3, 4)
z = x + y
PyTorch会使得最外面的框维度相同,做法是复制,如上例的y复制一份变成2×3×4,然后以此类推使得前面的框框都相同,最后可以做相同维度运算。再来个更极端的例子:
python
import torch
x = torch.ones(2, 1, 3, 4)
y = torch.ones(5, 4, 3)
z = torch.matmul(x, y)
print(z)
这么乱都能乘?耶斯。
- 首先来看,不乱的是最后两位的3×4和4×3和,刚好能做叉乘,好,所以结果的最后两位是3×3。
- 再看前面的维度,y少了框,先补最外面y变成2×5×4×3
- 这时第二维1的少了,复制成2×5×3×4,这样就可以乘了。
聪明的你要问,如果x第二维是3,复制不成5啊,那怎么办?怎么办?难办就别办了!答案就是会报错。
拆拼换调
这些方法几乎是最常用的,跟着我好好理解一遍哦。首先是拼接 的cat
方法:
python
x = torch.tensor([[1, 2, 3], [ 4, 5, 6]], dtype=torch.double)
y = torch.tensor([[7, 8, 9], [10, 11, 12]], dtype=torch.double)
z = torch.cat((x, y), dim=0)
看到dim=0了吗,根据框框理论,这是把第0维的几个框框拼起来,得到:
python
tensor([[ 1., 2., 3.],
[ 4., 5., 6.],
[ 7., 8., 9.],
[10., 11., 12.]], dtype=torch.float64)
当dim=1
,则是把第一个框框里的拼起来,得到:
python
tensor([[ 1., 2., 3., 7., 8., 9.],
[ 4., 5., 6., 10., 11., 12.]], dtype=torch.float64)
拆分 就用索引与切片,操作如同list
。
python
# 取第0维第1个框里的第2位,注意第X是从0开始
t = torch.randn(3, 4)
x = t[1, 2]
# 取第0维的前两项
x = t[0:2]