python
import torch
import numpy as np
12 张量的广播(Broadcast)特性
张量具备和
numpy
相同的广播特性,也就是允许不同形状的张量之间进行计算。
12.1 相同形状的张量计算
python
t = torch.arange(3)
t + t
# output :
tensor([0, 2, 4])
思考:如果两个
list
相加,是什么结果?
pythonli = [0, 1, 2] li + li # output : [0, 1, 2, 0, 1, 2]
12.2 不同形状的张量计算
广播的特性 是在不同形状的张量进行计算时,一个或多个张量通过隐式转化 ,转化成相同形状的两个张量。
但并非任意两个不同形状的张量都可以通过广播进行计算,因此我们需要了解广播的基本规则。
标量和任意形状的张量
python
# 一维标量与零维标量相加
t = torch.arange(3)
t + torch.tensor(1)
# output :
tensor([1, 2, 3])
# 一维张量与标量相加
t = torch.arange(3)
t + 1
# output :
tensor([1, 2, 3])
# 二维张量与标量相加
t2 = torch.zeros((3,4))
t2 + 1
# output :
tensor([[1., 1., 1., 1.],
[1., 1., 1., 1.],
[1., 1., 1., 1.]])
由上面代码可知标量可以看成是零维张量与张量进行计算.
相同维度、不同形状的张量之间计算
首先,我们都知道,张量的形状可以用.shape
属性查看
python
t2 = torch.zeros((3,4))
t2.shape
# output :
torch.Size([3, 4])
对于返回结果,我们可以看成是一个序列,代表着张量各维度的信息。
当然,对于二维张量,由于我们可以将其视为一个矩阵,因此我们可以说t2
是一个拥有三行四列的二维张量,但这种理解方法对于更高维度张量就存在一定的局限,因此我们需要树立另外一种理解方法,那就是:t2
是由3
个一维张量组成,并且每个一维张量都包含4
个元素。
类似的,我们可以创建更高维度张量并对其形状进行解释。
python
t3 = torch.zeros(3, 4, 5)
t3
# 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., 0.],
[0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0.]],
[[0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0.]]])
python
t3.shape
# output :
torch.Size([3, 4, 5])
我们可以将t3
解释为:t3
是由3
个二维张量组成了三维张量,并且每个二维张量都是由4
个包含5
个元素的一维张量所组成。
由二维拓展至三维,即可拓展至N
维。
接下来以t2
为例,探讨相同维度、不同形状的张量之间的广播规则。
python
t2
# output :
tensor([[0., 0., 0., 0.],
[0., 0., 0., 0.],
[0., 0., 0., 0.]])
python
t21 = torch.ones(1, 4)
t21
# output :
tensor([[1., 1., 1., 1.]])
t21
的形状是(1,4)
,和t2
的形状(3,4)
在第一个分量上取值不同,但该分量上t21
取值为1
,因此可以广播,也就是说可以进行计算。
python
# 进行广播计算
t21 + t2
# output :
tensor([[1., 1., 1., 1.],
[1., 1., 1., 1.],
[1., 1., 1., 1.]])
而t21
和t2
的实际计算过程如下:
注意理解 :此处的广播相对于将t21
的形状(1,4)
拓展成了t2
的(3,4)
,也就是复制了第一行三次,然后二者进行相加。
python
t22 = torch.ones(3, 1)
# output :
tensor([[1.],
[1.],
[1.]])
python
# 进行广播计算
t22 + t2
# output :
tensor([[1., 1., 1., 1.],
[1., 1., 1., 1.],
[1., 1., 1., 1.]])
形状为(3,1)
的张量和形状为(3,4)
的张量相加可以广播,实际计算过程如下:
无法广播的情况:
python
t23 = torch.ones(2, 4)
# 此时t2和t23的形状第一个分量维度不同,但二者取值均不为1,因此无法广播
t23 + t2
# output :
RuntimeError: The size of tensor a (2) must match the size of tensor b (3) at non-singleton dimension 0
python
t24 = torch.arange(3).reshape(3, 1)
t25 = torch.arange(3).reshape(1, 3)
此时,t24
的形状是(3,1)
,而t25
的形状是(1,3)
,二者的形状在两个分量上均不相同,但都有存在1
的情况,因此可以广播。
python
t24 + t25
# output :
tensor([[0, 1, 2],
[1, 2, 3],
[2, 3, 4]])
二者计算过程如下:
来看看三维张量
python
t3 = torch.zeros(3, 4, 5)
t3
# 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., 0.],
[0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0.]],
[[0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0.]]])
python
t31 = torch.ones(3, 4, 1)
t31
# output :
tensor([[[1.],
[1.],
[1.],
[1.]],
[[1.],
[1.],
[1.],
[1.]],
[[1.],
[1.],
[1.],
[1.]]])
python
# 进行广播计算
t3 + t31
# output :
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., 1.],
[1., 1., 1., 1., 1.],
[1., 1., 1., 1., 1.],
[1., 1., 1., 1., 1.]],
[[1., 1., 1., 1., 1.],
[1., 1., 1., 1., 1.],
[1., 1., 1., 1., 1.],
[1., 1., 1., 1., 1.]]])
两个张量的形状上有两个分量不同时,只要有不同的分量有一个取值为1,则仍然可以广播。
python
t33 = torch.ones(1, 1, 5)
t3 + t33
# output :
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., 1.],
[1., 1., 1., 1., 1.],
[1., 1., 1., 1., 1.],
[1., 1., 1., 1., 1.]],
[[1., 1., 1., 1., 1.],
[1., 1., 1., 1., 1.],
[1., 1., 1., 1., 1.],
[1., 1., 1., 1., 1.]]])
t3
和t33
的计算过程如下
不同维度的张量计算过程中广播
在理解相同维度、不同形状的张量广播之后,对于不同维度的张量之间的广播就会容易很多。
因为对于不同维度的张量,我们首先可以将低维的张量升维,然后根据相同维度不同形状的张量广播规则进行广播。
而低维向量对的升维只需将更高维度方向的形状填充为1即可。
t2 = torch.arange(4).reshape(2, 2)
t3 = torch.zeros(3, 2, 2)
t2
和t3
进行广播计算时,首先t2
的形状会先转化为(1,2,2)
再与t3
相加,再与形状为(3,2,2)
的t3
相加,就转化为了相同维度不同形状的两个张量进行相加。
python
t3 + t2
# output : 等价于t3 + t2.reshape(1, 2, 2)
tensor([[[0., 1.],
[2., 3.]],
[[0., 1.],
[2., 3.]],
[[0., 1.],
[2., 3.]]])
总结:广播机制的规则
维度补齐:如果两个张量的维度数量不同,则将较小的那个张量的形状前面补1,直到两个张量的维度数量相同。
维度匹配:如果两个张量在某个维度上的大小不一致,但其中一个张量在该维度上的大小是1,则可以在该维度上进行广播。如果两个张量在任何维度上的大小既不相等也不为1,则无法进行广播。
结果形状:广播后的张量形状是每个维度上大小的最大值。
13 逐点计算(Pointwise Ops)
Pytorch
中逐点计算大部分都是可以针对Tensor
中每个元素都进行的数学科学运算,与Numpy
中针对Array
的科学运算类似。
逐点运算主要包括数学基本运算、数值调整运算和数据科学运算三块。
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 |
示例
python
t1 = torch.tensor([1, 2])
t2 = torch.tensor([3, 4])
python
torch.add(t1, t2)
# output : 等效于t1 + t2
tensor([4, 6])
Tensor数值调整函数
函数 | 描述 |
---|---|
torch.abs(t) | 返回绝对值 |
torch.ceil(t) | 向上取整 |
torch.floor(t) | 向下取整 |
torch.round(t) | 四舍五入取整 |
torch.neg(t) | 返回相反的数 |
python
t = torch.randn(5)
t
# output :
tensor([ 1.6031, -0.2424, 0.0985, -0.1315, 0.3840])
python
torch.round(t)
# output :
tensor([2., -0., 0., -0., 0.])
python
torch.abs(t)
# output :
tensor([1.6031, 0.2424, 0.0985, 0.1315, 0.3840])
python
torch.neg(t)
# output :
tensor([-1.6031, 0.2424, -0.0985, 0.1315, -0.3840])
注: 虽然此类型函数是数值调整函数,但并不会对原对象进行调整,而是输出新的结果。
python
t
# output : t本身并未发生变化
tensor([ 1.6031, -0.2424, 0.0985, -0.1315, 0.3840])
而若要对原对象本身进行修改 ,则可考虑使用方法_()
的表达形式,对对象本身进行修改。此时方法就是上述同名函数。
python
t.abs_()
# output :
tensor([1.6031, 0.2424, 0.0985, 0.1315, 0.3840])
python
t
# output :
tensor([1.6031, 0.2424, 0.0985, 0.1315, 0.3840])
python
t.neg_()
# output :
tensor([-1.6031, -0.2424, -0.0985, -0.1315, -0.3840])
python
t
# output :
tensor([-1.6031, -0.2424, -0.0985, -0.1315, -0.3840])
除了上述数值调整函数有对应的同名方法外,本节介绍的许多科学计算都有同名方法。
Tensor常用科学计算
数学运算函数 | 数学公式 | 描述 |
---|---|---|
torch.exp(t) | e^t^ | 返回以e为底,t中元素为幂的张量 |
torch.expm(t) | e^t^-1 | 对张量中的所有元素计算exp(t) - 1 |
torch.exp2(t) | 2^t^ | 逐个元素计算2的t次方 |
torch.pow(t, n) | t^n^ | 返回t的n次幂 |
torch.sqrt(t) | t^1/2^ | 返回t的平方根 |
torch.square(t) | t^2^ | 返回输入的元素平方 |
torch.log10(t) | log~10~t | 返回以10为底的t的对数 |
torch.log(t) | log~e~t | 返回以e为底的t的对数 |
torch.log2(t) | log~2~t | 返回以2为底的t的对数 |
torch.log1p(t) | log~e~(t + 1) | 计算输入张量t中每个元素的自然对数加 1 |
torch.sin(t) | 三角正弦 | |
torch.cos(t) | 元素余弦 | |
torch.tan(t) | 逐元素计算切线 |
tensor
的大多数科学计算只能作用于tensor
对象.
python
torch.pow(2, 2)
# output :
TypeError: pow() received an invalid combination of arguments - got (int, int), but expected one of:
* (Tensor input, Tensor exponent, *, Tensor out = None)
* (Number self, Tensor exponent, *, Tensor out = None)
* (Tensor input, Number exponent, *, Tensor out = None)
python
torch.pow(torch.tensor(2), 2)
# output :
tensor(4)
理解: 相比于python
原生数据类型,张量是一类更加特殊的对象,例如张量可以指定运行在CPU
或GPU
上,因此很多张量的科学计算函数都不允许张量和python
原生的数值型对象混合使用。
tensor的大多数科学运算具有一定的静态性
静态性指的是对输入的张量类型有明确的要求,例如部分函数只能输入浮点型张量,而不能输入整型张量。
python
t = torch.arange(1, 4)
t.dtype
# output :
torch.int64
python
torch.exp(t)
# output :
RuntimeError: exp_vml_cpu not implemented for 'long'
虽然
python
是动态编译的编译语言,但在pytorch
中,由于会涉及GPU
计算,因此很多时候元素类型不会在实际执行函数计算时进行调整。此处的科学运算大多数都要求对象类型是浮点型,我们需要提前进行类型转化。
python
t1 = t.float()
torch.exp(t1)
# output :
tensor([ 2.7183, 7.3891, 20.0855])
python
torch.expm1(t1)
# output :
tensor([ 1.7183, 6.3891, 19.0855])
此处返回结果是e^t^ - 1,在数值科学计算中,expm1
和log1p
函数是一对对应的函数关系,后面在介绍log1p
的时候会讲解这对函数的实际作用。
排序运算:sort
sort
函数将同时返回排序结果和对应索引值的排列
python
t = torch.tensor([1.0, 3.0, 2.0])
python
# 升序排列
torch.sort(t)
# output :
torch.return_types.sort(
values=tensor([1., 2., 3.]),
indices=tensor([0, 2, 1]))
python
# 降序排列
torch.sort(t, descending = True)
# output :
torch.return_types.sort(
values=tensor([3., 2., 1.]),
indices=tensor([1, 2, 0]))
14 规约运算
规约运算指的是针对某张量进行某种总结,最后得出一个具体总结值的函数。
此类函数主要包含了数据科学领域内的诸多统计分析函数,如均值、极值、方差、中位差函数等。
Tensor统计分析函数
函数 | 描述 |
---|---|
torch.mean(t) | 返回张量均值 |
torch.var(t) | 返回张量方差 |
torch.std(t) | 返回张量标准差 |
torch.var_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个值对应的指标 |
python
t = torch.arange(10).float()
# 计算均值
torch.mean(t)
# output :
tensor(4.5000)
# 计算标准差、均值
torch.std_mean(t)
# output :
(tensor(3.0277), tensor(4.5000))
dist计算距离
t1 = torch.tensor([1.0, 2])
t2 = torch.tensor([3.0, 4])
dist
函数可计算闵式距离(闵可夫斯基距离),通过不同的p
值,可以计算多种类型的距离,如欧式距离、街道距离等。闵可夫斯基距离公式如下:
D ( x , y ) = ( ∑ u = 1 n ∣ x u − y u ∣ p ) 1 / p D(x, y) = \left( \sum_{u=1}^{n} |x_u - y_u|^p \right)^{1/p} D(x,y)=(u=1∑n∣xu−yu∣p)1/p
p取值为2时,计算欧式距离:
python
torch.dist(t1, t2, 2) # 相当于torch.sqrt(torch.tensor(8.0))
# output :
tensor(2.8284)
p取值为1时,计算街道距离:
python
torch.dist(t1, t2, 1)
# output :
tensor(4.)
规约运算的维度
由于规约运算是一个序列返回一个结果,因此若是针对高维张量,则可指定某维度进行计算。
python
# 创建一个3*3的二维张量
t2 = torch.arange(12).float().reshape(3, 4)
t2
# output :
tensor([[ 0., 1., 2., 3.],
[ 4., 5., 6., 7.],
[ 8., 9., 10., 11.]])
python
# 按照第一个维度求和(每次计算三个)/按列求和
torch.sum(t2, dim = 0)
# output :
tensor([12., 15., 18., 21.])
python
# 按照第二个维度求和(每次计算四个)/按行求和
torch.sum(t2, dim = 1)
# output :
tensor([ 6., 22., 38.])
python
# 创建一个2*3*4的三维张量
t3 = torch.arange(24).float().reshape(2, 3, 4)
t3
# output :
tensor([[[ 0., 1., 2., 3.],
[ 4., 5., 6., 7.],
[ 8., 9., 10., 11.]],
[[12., 13., 14., 15.],
[16., 17., 18., 19.],
[20., 21., 22., 23.]]])
python
# 按照第一个维度求和(每次计算两个)
torch.sum(t3, dim = 0)
# output :
tensor([[12., 14., 16., 18.],
[20., 22., 24., 26.],
[28., 30., 32., 34.]])
python
# 按照第二个维度求和(每次计算三个)
torch.sum(t3, dim = 1)
# output :
tensor([[12., 15., 18., 21.],
[48., 51., 54., 57.]])
python
# 按照第三个维度求和(每次计算四个)
torch.sum(t3, dim = 2)
# output :
tensor([[ 6., 22., 38.],
[54., 70., 86.]])
二维张量的排序
和上述过程类似,在进行排序过程中,二维张量也可以按行或者按列进行排序。
python
t22 = torch.randn(3, 4)
t22
# output :
tensor([[ 1.0475, 1.7860, -1.0797, -0.8695],
[ 0.2919, 0.8268, -0.2798, -1.0550],
[ 0.4554, 0.0364, -0.2027, 1.0527]])
python
# 默认情况下,是按行进行升序排序的
torch.sort(t22)
# output :
torch.return_types.sort(
values=tensor([[-1.0797, -0.8695, 1.0475, 1.7860],
[-1.0550, -0.2798, 0.2919, 0.8268],
[-0.2027, 0.0364, 0.4554, 1.0527]]),
indices=tensor([[2, 3, 0, 1],
[3, 2, 0, 1],
[2, 1, 0, 3]]))
python
# 修改dim和descending参数,使得按列进行降序排序
torch.sort(t22, dim = 1, descending = True)
# output :
torch.return_types.sort(
values=tensor([[ 1.7860, 1.0475, -0.8695, -1.0797],
[ 0.8268, 0.2919, -0.2798, -1.0550],
[ 1.0527, 0.4554, 0.0364, -0.2027]]),
indices=tensor([[1, 0, 3, 2],
[1, 0, 2, 3],
[3, 0, 1, 2]]))
15 比较运算
比较运算常用于不同张量之间的逻辑运算,最终返回逻辑运算结果(逻辑类型张量)
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各元素是否不相同,等效 != |
python
t1 = torch.tensor([1.0, 3, 4])
t2 = torch.tensor([1.0, 2, 5])
python
t1 == t2
# output : 等价于torch.eq(t1, t2)
tensor([ True, False, False])
python
torch.equal(t1, t2)
# output :
False
python
t1 > t2
# output :
tensor([False, True, False])
python
t1 >= t2
# output :
tensor([ True, True, False])