Lesson 3.张量的广播和科学运算
??作为PyTorch中执行深度学习的基本数据类型,张量(Tensor)也拥有非常多的数学运算函数和方法,以及对应的一系列计算规则。在PyTorch中,能够作用与Tensor的运算,被统一称作为算子。并且相比于NumPy,PyTorch给出了更加规范的算子(运算)的分类,从而方便用户在不同场景下调用不同类型的算子(运算)。
- 数学运算的分类
PyToch总共为Tensor设计了六大类数学运算,分别是: - 1.逐点运算(Pointwise Ops):指的是针对Tensor中每个元素执行的相同运算操作;
- 2.规约运算(Reduction Ops):指的是对于某一张量进行操作得出某种总结值;
- 3.比较运算(Comparison Ops):指的是对多个张量进行比较运算的相关方法;
- 4.谱运算(Spectral Ops):指的是涉及信号处理傅里叶变化的操作;
- 5.BLAS和LAPACK运算:指的是基础线性代数程序集(Basic Linear Algeria Subprograms)和线性代数包(Linear Algeria Package)中定义的、主要用于线性代数科学计算的函数和方法;
- 6.其他运算(Other Ops):其他未被归类的数学运算。
??由于谱运算(Spectral Ops)前期不会涉及,而要理解傅里叶变换本身需要更多额外的数学基础,而很多其他运算,我们在前面介绍张量的基本方法时已经介绍,因此接下来将主要围绕逐点运算、规约运算、比较运算和线性代数运算四块进行讲解,而线性代数部分由于涉及到大量的数学内容,因此将放在Lesson 4中单独进行讲解。
import torch import numpy as np
关于数学运算的另一种分类方法,是根据运算使用场景进行分类,如基础数学运算、数理统计运算等。由于PyTorch官网是按照六类算子进行的分类,因此本节将结合两种分类方法进行讲解。
一、张量的广播(Broadcast)特性
??在具体介绍张量的运算操作之前,我们先要了解张量的运算规则,其中最重要的一点,就是张量具备和NumPy相同的广播特性,也就是允许不同形状的张量之间进行计算。
1.相同形状的张量计算
??根据官网说法,“same shapes are always broadcastable”,相同形状数组总是可以进行广播计算。这里简单强调一下,虽然我们往往觉得不同形状之间的张量计算才是应用到广播特性,但其实相同形状的张量计算,尽管是对应位置元素进行计算,但本质上也是应用到了广播特性。
t1 = torch.arange(3) t1
t1 + t1 # 对应位置元素依次相加
思考:如果是两个list相加,是什么结果?
2.不同形状的张量计算
??广播的特性是在不同形状的张量进行计算时,一个或多个张量通过隐式转化,转化成相同形状的两个张量,从而完成计算的特性。但并非任何两个不同形状的张量都可以通过广播特性进行计算,因此,我们需要了解广播的基本规则及其核心依据。
2.1 标量和任意形状的张量
??标量可以和任意形状的张量进行计算,计算过程就是标量和张量的每一个元素进行计算。
t1 + 1 # 1是标量,可以看成是零维
二维加零维
t1 + torch.tensor(1)
t2 = torch.zeros((3, 4)) t2
t2 + 1
2.2 相同维度、不同形状的张量之间计算
??对于不同形状的张量计算,我们首先需要回顾张量的形状属性,并深化对其的理解。
??首先,我们都知道,张量的形状可以用.shape属性查看
t2.shape
??对于返回结果,我们可以看成是一个序列,代表着张量各维度的信息。当然,对于二维张量,由于我们可以将其视作一个矩阵,因此我们可以说t2是一个拥有三行四列的二维张量,但这种理解方式对于更高维度张量就存在一定的局限,因此我们需要树立另外一种理解方法,那就是:t2是由3个一维张量组成,并且该一维张量、每个都包含四个元素。类似的,我们可以创建更高维度张量并对其形状进行解释。
t3 = torch.zeros(3, 4, 5) t3
t3.shape
我们可以将t3解释为:t3是3个二维张量组成了三维张量,并且这些每个二维张量,都是由四个包含五个元素的一维张量所组成。由二维拓展至三维,即可拓展至N维。
接下来,我们以t2为例,来探讨相同维度、不同形状的张量之间的广播规则。
t2
t2.shape
t21 = torch.ones(1, 4) t21
t21的形状是(1, 4),和t2的形状(3, 4)在第一个分量上取值不同,但该分量上t21取值为1,因此可以广播,也就可以进行计算
t21 + t2
而t21和t2的实际计算过程如下:
注意理解:此处的广播相当于将t22的形状(1, 4)拓展成了t2的(3, 4),也就是复制了第一行三次,然后二者进行相加。当然,也可以理解成t22的第一行和t2的三行分别进行了相加。
t22 = torch.ones(3, 1) t22
t2
t2.shape
t22 + t2 # 形状为(3,1)的张量和形状为(3,4)的张量相加,可以广播
t2和t22实际计算过程如下:
t23 = torch.ones(2, 4) t23.shape
t2.shape
注:此时t2和t23的形状第一个分量维度不同,但二者取值均不为1,因此无法广播
t2 + t23
t24 = torch.arange(3).reshape(3, 1) t24
t25 = torch.arange(3).reshape(1, 3) t25
此时,t24的形状是(3, 1),而t25的形状是(1, 3),二者的形状在两个份量上均不相同,但都有存在1的情况,因此也是可以广播的
t24 + t25
二者计算过程如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-n9X8bxUy-1634303363579)(https://i.loli.net/2021/01/13/rX53dcTDMBxGmg4.jpg)]
三维张量的广播
t3 = torch.zeros(3, 4, 5) t3
t31 = torch.ones(3, 4, 1) t31
t3 + t31
t32 = torch.ones(3, 1, 5) t32
t32 + t3
两个张量的形状上有两个分量不同时,只要不同的分量仍然有一个取值为1,则仍然可以广播
t3.shape
t33 = torch.ones(1, 1, 5) t33
t3 + t33
t3和t33计算过程如下
注:此处标注的两次广播,我们也可认为上述全部过程的实现是一次“大的”广播。同时,此处最开始的t33也就相当于一个一维的、包含五个元素的张量,因此上述过程也可视为一个一维张量和一个三维张量计算时的广播过程。
2.3 不同维度的张量计算过程中广播
??在理解相同维度、不同形状的张量广播之后,对于不同维度的张量之间的广播其实就会容易很多,因为对于不同维度的张量,我们首先可以将低维的张量升维,然后依据相同维度不同形状的张量广播规则进行广播。而低维向量的升维也非常简单,只需将更高维度方向的形状填充为1即可,例如:
二维张量转化为三维张量
t2 = torch.arange(4).reshape(2, 2) t2
转化为三维张量
t2.reshape(1, 2, 2)
转化之后表示只包含一个二维张量的三维张量,且二维张量就是t2
转化为四维张量
t2.reshape(1, 1, 2, 2)
转化之后表示只包含一个三维张量的四维张量,且三维张量只包含一个二维张量,且二维张量就是t2
t3 = torch.zeros(3, 2, 2)
t3和t2的计算过程,就相当于形状为(1,2,2)和(3,2,2)的两个张量进行计算
t3 + t2
t3 + t2.reshape(1, 2, 2)
思考:形状为(2,1)的张量和形状为(3,2,3)的张量可以进行广播计算么?计算过程是怎样的?
二、逐点运算(Pointwise Ops)
??PyTorch中逐点运算大部分都是可以针对Tensor中每个元素都进行的数学科学运算,并且都是较为通用的数学科学运算,和NumPy中针对Array的科学运算类似。在PyTorch中文文档中有全部运算符的相关介绍,此处仅针对常用计算函数进行介绍。 ??逐点运算主要包括数学基本运算、数值调整运算和数据科学运算三块,相关函数如下:
Tensor基本数学运算
函数 | 描述 |
---|
torch.add(t1,t2 ) | t1、t2两个张量逐个元素相加,等效于t1+t2 | torch.subtract(t1,t2) | t1、t2两个张量逐个元素相减,等效于t1-t2 | torch.multiply(t1,t2) | t1、t2两个张量逐个元素相乘,等效于t1*t2 | torch.divide(t1,t2) | t1、t2两个张量逐个元素相除,等效于t1/t2 |
t1 = torch.tensor([1, 2]) t1
t2 = torch.tensor([3, 4]) t2
torch.add(t1, t2)
t1 + t2
t1 / t2
Tensor数值调整函数
函数 | 描述 |
---|
torch.abs(t) | 返回绝对值 | torch.ceil(t) | 向上取整 | torch.floor(t) | 向下取整 | torch.round(t) | 四舍五入取整 | torch.neg(t) | 返回相反的数 |
t = torch.randn(5) t
torch.round(t)
torch.abs(t)
torch.neg(t)
注:虽然此类型函数是数值调整函数,但并不会对原对象进行调整,而是输出新的结果。
t # t本身并未发生变化
而若要对原对象本身进行修改,则可考虑使用方法_() 的表达形式,对对象本身进行修改。此时方法就是上述同名函数。
t.abs_()
t
t.neg_()
t
除了上述数值调整函数有对应的同名方法外,本节介绍的许多科学计算都有同名方法。
t.exp_()
t
Tensor常用科学计算
数学运算函数 | 数学公式 | 描述 |
---|
幂运算 | | | torch.exp(t) | $ y_{i} = e^{x_{i}} $ | 返回以e为底、t中元素为幂的张量 | torch.expm1(t) | $ y_{i} = e^{x_{i}} $ - 1 | 对张量中的所有元素计算exp(x) - 1 | torch.exp2(t) | $ y_{i} = 2^{x_{i}} $ | 逐个元素计算2的t次方。 | torch.pow(t,n) | $\text{out}_i = x_i ^ \text{exponent} $ | 返回t的n次幂 | torch.sqrt(t) | $ \text{out}{i} = \sqrt{\text{input}{i}} $ | 返回t的平方根 | torch.square(t) | $ \text{out}_i = x_i ^ \text{2} $ | 返回输入的元素平方。 | 对数运算 | | | torch.log10(t) | $ y_{i} = \log_{10} (x_{i}) $ | 返回以10为底的t的对数 | torch.log(t) | $ y_{i} = \log_{e} (x_{i}) $ | 返回以e为底的t的对数 | torch.log2(t) | $ y_{i} = \log_{2} (x_{i}) $ | 返回以2为底的t的对数 | torch.log1p(t) | $ y_i = \log_{e} (x_i $ + 1) | 返回一个加自然对数的输入数组。 | 三角函数运算 | | | torch.sin(t) | 三角正弦。 | | torch.cos(t) | 元素余弦。 | | torch.tan(t) | 逐元素计算切线。 | |
- tensor的大多数科学计算只能作用于tensor对象
计算2的2次方
torch.pow(2, 2)
torch.pow(torch.tensor(2), 2)
理解:相比于Python原生数据类型,张量是一类更加特殊的对象,例如张量可以指定运行在CPU或者GPU上,因此很多张量的科学计算函数都不允许张量和Python原生的数值型对象混合使用。
??所谓静态性,指的是对输入的张量类型有明确的要求,例如部分函数只能输入浮点型张量,而不能输入整型张量。
t = torch.arange(1, 4) t.dtype
torch.exp(t)
需要注意的是,虽然Python是动态编译的编程语言,但在PyTorch中,由于会涉及GPU计算,因此很多时候元素类型不会在实际执行函数计算时进行调整。此处的科学运算大多数都要求对象类型是浮点型,我们需要提前进行类型转化。
t1 = t.float() t1
torch.exp(t1)
torch.expm1(t1)
注,此处返回结果是
e
t
?
1
e^{t} - 1
et?1,在数值科学计算中,expm1函数和log1p函数是一对对应的函数关系,后面再介绍log1p的时候会讲解这对函数的实际作用。
torch.exp2(t1)
torch.pow(t, 2)
注意区分2的t次方和t的2次方
torch.square(t)
torch.sqrt(t1)
torch.pow(t1, 0.5)
开根号也就相当于0.5次幂
torch.log10(t1)
torch.log(t1)
torch.log2(t1)
同时,我们也可简单回顾幂运算和对数运算之间的关系
torch.exp(torch.log(t1))
torch.exp2(torch.log2(t1))
a = torch.tensor(-1).float() a
torch.exp2(torch.log2(a))
??在PyTorch中,sort排序函数将同时返回排序结果和对应的索引值的排列。
t = torch.tensor([1.0, 3.0, 2.0]) t
升序排列
torch.sort(t)
降序排列
torch.sort(t, descending=True)
三、规约运算
??规约运算指的是针对某张量进行某种总结,最后得出一个具体总结值的函数。此类函数主要包含了数据科学领域内的诸多统计分析函数,如均值、极值、方差、中位数函数等等。
Tensor统计分析函数
函数 | 描述 |
---|
torch.mean(t) | 返回张量均值 | torch.var(t) | 返回张量方差 | torch.std(t) | 返回张量标准差 | torch.var_mean(t) | 返回张量方差和均值 | torch.std_mean(t) | 返回张量标准差和均值 | torch.max(t) | 返回张量最大值 | torch.argmax(t) | 返回张量最大值索引 | torch.min(t) | 返回张量最小值 | torch.argmin(t) | 返回张量最小值索引 | torch.median(t) | 返回张量中位数 | torch.sum(t) | 返回张量求和结果 | torch.logsumexp(t) | 返回张量各元素求和结果,适用于数据量较小的情况 | torch.prod(t) | 返回张量累乘结果 | torch.dist(t1, t2) | 计算两个张量的闵式距离,可使用不同范式 | torch.topk(t) | 返回t中最大的k个值对应的指标 |
生成浮点型张量
t = torch.arange(10).float() t
计算均值
torch.mean(t)
计算标准差、均值
torch.std_mean(t)
计算最大值
torch.max(t)
返回最大值的索引
torch.argmax(t)
计算中位数
torch.median(t)
求和
torch.sum(t)
求积
torch.prod(t)
torch.prod(torch.tensor([1, 2, 3]))
t1 = torch.tensor([1.0, 2]) t1
t2 = torch.tensor([3.0, 4]) t2
t
torch.topk(t, 2)
??dist函数可计算闵式距离(闵可夫斯基距离),通过输入不同的p值,可以计算多种类型的距离,如欧式距离、街道距离等。闵可夫斯基距离公式如下:
$ D(x,y) = (\sum^{n}_{u=1}|x_u-y_u|^{p})^{1/p}$
p取值为2时,计算欧式距离
torch.dist(t1, t2, 2)
torch.sqrt(torch.tensor(8.0))
p取值为1时,计算街道距离
torch.dist(t1, t2, 1)
??由于规约运算是一个序列返回一个结果,因此若是针对高维张量,则可指定某维度进行计算。
创建一个3*3的二维张量
t2 = torch.arange(12).float().reshape(3, 4) t2
t2.shape
按照第一个维度求和(每次计算三个)、按列求和
torch.sum(t2, dim = 0)
按照第二个维度求和(每次计算四个)、按行求和
torch.sum(t2, dim = 1)
创建一个234的三维张量
t3 = torch.arange(24).float().reshape(2, 3, 4) t3
t3.shape
torch.sum(t3, dim = 0)
torch.sum(t3, dim = 1)
torch.sum(t3, dim = 2)
理解:dim参数和shape返回结果一一对应。
和上述过程类似,在进行排序过程中,二维张量也可以按行或者按列进行排序
t22 = torch.randn(3, 4) # 创建二维随机数张量 t22
默认情况下,是按照行进行升序排序
torch.sort(t22)
修改dim和descending参数,使得按列进行降序排序
torch.sort(t22, dim = 1, descending=True)
四、比较运算
??比较运算是一类较为简单的运算类型,和Python原生的布尔运算类似,常用于不同张量之间的逻辑运算,最终返回逻辑运算结果(逻辑类型张量)。基本比较运算函数如下所示:
Tensor比较运算函数
函数 | 描述 |
---|
torch.eq(t1, t2) | 比较t1、t2各元素是否相等,等效== | torch.equal(t1, t2) | 判断两个张量是否是相同的张量 | torch.gt(t1, t2) | 比较t1各元素是否大于t2各元素,等效> | torch.lt(t1, t2) | 比较t1各元素是否小于t2各元素,等效< | torch.ge(t1, t2) | 比较t1各元素是否大于或等于t2各元素,等效>= | torch.le(t1, t2) | 比较t1各元素是否小于等于t2各元素,等效<= | torch.ne(t1, t2) | 比较t1、t2各元素是否不相同,等效!= |
t1 = torch.tensor([1.0, 3, 4])
t2 = torch.tensor([1.0, 2, 5])
t1 == t2
torch.equal(t1, t2) # 判断t1、t2是否是相同的张量
torch.eq(t1, t2)
t1 > t2
t1 >= t2
|