天天看点

pytorch gather_Pytorch之Tensor超详细理解(一)

Tensor,又名张量,几乎所有的深度学习框架都有这种数据结构。下面对工程实现的方法进行详细介绍,若能精通Tensor,以后想建立自己的深度学习模型时,速度必将得到巨大的提升,同时也能加强对模型细节的理解。

1. 分类

从接口的角度来讲,对tensor的操作可分为两类:

(1)torch.function,如torch.save(保存模型)等。

(2)tensor.function,如tensor.view(修改形状)等。

从存储的角度来讲,又可分为两类:

(1)不会修改自身,如a.add(b),加法的结果会返回一个新的tensor。

(2)会修改自身,如a.add_(b),加法的结果存储在a中,a被修改了。

函数名以"_"结尾的都是inplace方式,即会修改调用者自己的数据,在实际写代码的时候应加以区分。

2. 创建Tensor

在Pytorch中新建Tensor的方法有很多,具体如下表所示:

函数 功能
Tensor(*sizes) 基础构造函数
ones(*sizes) 全1的Tensor
zeros(*sizes) 全0的Tensor
eyes(*sizes) 对角线为1,其他为0
arange(s,e,step) 从s到e,步长为step
linspace(s,e,steps) 从s到e,均匀切分成steps
rand/randn(*sizes) 均匀/标准分布
normal(mean,std)/uniform(from,to) 正态/均匀分布
randperm(m) 随机排列

其中,使用Tensor()创建是最复杂的方式,它既可以接收一个list,将它变成Tensor;也可以根据指定的形状创建Tensor。下面开始举例:

>>> import torch as t>>> a=t.Tensor(2,3)>>> a   #a里面的值取决于内存空间的状态tensor([[-1.2628e-24,  4.5712e-41,  2.5193e-38],        [ 0.0000e+00,  2.8734e-38,  0.0000e+00]])>>> b=t.Tensor([[1,2,3],[3,2,5]])>>> btensor([[1., 2., 3.],        [3., 2., 5.]])
           
>>> b.tolist()  # 把Tensor--->list[[1.0, 2.0, 3.0], [3.0, 2.0, 5.0]]
           

b.size()返回torch.size对象:

>>> b.size()torch.Size([2, 3])>>> b.numel() # b中元素的总个数6
           
# 创建一个和b形状一样的tensor>>> c=t.Tensor(b.size())>>> ctensor([[2.9370e-37, 0.0000e+00, 3.0000e+00],        [3.0000e+00, 2.0000e+00, 5.0000e+00]])# 创建一个元素为2和3的tensor>>> d = t.Tensor((2,3))>>> dtensor([2., 3.])
           

注意:t.Tensor(*sizes)创建之后,不会马上分配内存空间,只会计算剩余的内存是否够用,使用到Tensor才进行内存的分配,而其他操作都是创建时就立即分配空间,其他操作如下:

>>> t.ones(2,3)tensor([[1., 1., 1.],        [1., 1., 1.]])>>> t.zeros(2,3)tensor([[0., 0., 0.],        [0., 0., 0.]])>>> t.arange(1,6,2)tensor([1, 3, 5])>>> t.linspace(1,10,20)tensor([ 1.0000,  1.4737,  1.9474,  2.4211,  2.8947,  3.3684,  3.8421,  4.3158,         4.7895,  5.2632,  5.7368,  6.2105,  6.6842,  7.1579,  7.6316,  8.1053,         8.5789,  9.0526,  9.5263, 10.0000])>>> t.randn(2,3)tensor([[ 2.0619,  1.1582, -1.1900],        [-0.6511,  0.5418, -0.8803]])>>> t.rand(2,3)tensor([[0.4519, 0.5684, 0.9345],        [0.4053, 0.9160, 0.1647]])>>> t.eye(2,3) #对角线为1,不要求行列数一致tensor([[1., 0., 0.],        [0., 1., 0.]])
           

3. 常用 Tensor 操作

通过tensor.view可以更改tensor的形状,但是必须保证调整前后元素总数是一致的。并且新的tensor和源tensor是共享内存的,即更改其中一个,另外一个也会跟着改变。而且,在实际应用中,可能会需要添加或者减少某一个维度,这时squeeze和unsqueeze这俩函数就派上用场了。

>>> a=t.arange(6)>>> a.view(2,3)tensor([[0, 1, 2],        [3, 4, 5]])>>> b=a.view(-1,3)>>> btensor([[0, 1, 2],        [3, 4, 5]])        >>> b.unsqueeze(1) #注意形状,在第1维上(维度从0开始)增加1tensor([[[0, 1, 2]],        [[3, 4, 5]]])>>> b.size()torch.Size([2, 3])>>> b.shapetorch.Size([2, 3])>>> c=b.unsqueeze(1)>>> c.size()torch.Size([2, 1, 3])>>> c.squeeze(-2) tensor([[0, 1, 2],        [3, 4, 5]])>>> d=b.view(1,1,1,2,3)>>> d.squeeze(0) #压缩第0维的1tensor([[[[0, 1, 2],          [3, 4, 5]]]])>>> d.squeeze() #将所有维度为1的进行压缩tensor([[0, 1, 2],        [3, 4, 5]])>>> a[2]=10 # 共享内存,b也跟着变化了>>> btensor([[ 0,  1, 10],        [ 3,  4,  5]])
           

resize是另一种可以调整tensor形状的方法,但是它与view最大的区别就是:它可以修改tensor的形状,即如果新尺寸超过了原尺寸会自动分配新的内存空间;如果小于原尺寸,会保留新尺寸的部分数据。

>>> btensor([[0, 1, 2],        [3, 4, 5]])>>> b.resize_(1,3)tensor([[0, 1, 2]])>>> b.resize_(3,3)tensor([[                  0,                   1,                   2],        [                  3,                   4,                   5],        [4849316058747981391, 4995148752773008735, 5493058101194933581]])
           

3. 索引操作

>>> import torch as t>>> a=t.randn(3,4) #正态分布>>> atensor([[-1.7722, -1.5571,  1.0267,  1.4227],        [ 0.8671, -1.6075,  2.1283,  1.6735],        [ 0.9111, -1.1461, -0.1792, -0.3909]])>>> a[0] #第0行tensor([-1.7722, -1.5571,  1.0267,  1.4227])>>> a[:,0] #第0列tensor([-1.7722,  0.8671,  0.9111])>>> a[0][2] tensor(1.0267)>>> a[0,2] #和a[0][2]上面相等tensor(1.0267)>>> a[:2] #前两行tensor([[-1.7722, -1.5571,  1.0267,  1.4227],        [ 0.8671, -1.6075,  2.1283,  1.6735]])>>> a[:2,0:2] #前两行,前两列tensor([[-1.7722, -1.5571],        [ 0.8671, -1.6075]])>>> a[0:1,:2]#注意和下面一个例子的区别,这是二维的tensor([[-1.7722, -1.5571]])>>> a[0,:2] #这是一维的tensor([-1.7722, -1.5571])>>> a > 1 tensor([[False, False,  True,  True],        [False, False,  True,  True],        [False, False, False, False]])>>> a[a>1] #等于masked_select(a,a>1)或者a.masked_select(a>1)tensor([1.0267, 1.4227, 2.1283, 1.6735])
           
函数 功能
index_select(input,dim,index) 在指定维度dim上选取,例如选取某些行、某些列
masked_select(input,mask) 例子如a[a>0],使用ByteTensor进行选取
non_zero(input) 非0元素的下标
gather(input,dim,index) 根据index,在dim维度上选取数据,输出的size和index一样
>>> a=t.arange(0,16).view(4,4)>>> atensor([[ 0,  1,  2,  3],        [ 4,  5,  6,  7],        [ 8,  9, 10, 11],        [12, 13, 14, 15]])>>> index=t.Tensor([[0,1,2,3]])>>> t.gather(a,0,index)Traceback (most recent call last):  File "", line 1, in <module>RuntimeError: gather_cpu(): Expected dtype int64 for index#可以看到这里发生了错误,原因是索引index的数据类型不是LongTensor,# 是Tensor;至于为啥,作者也不知道,没有百度出来,反正以后索引用LongTensor就对了>>> index=t.LongTensor([[0,1,2,3]])>>> a.gather(0,index)tensor([[ 0,  5, 10, 15]])>>> t.gather(a,0,index)tensor([[ 0,  5, 10, 15]])
           

4. 高级索引

高级索引可以看作是普通索引的扩展,但是不和原始的Tensor共享内存。

>>> x=t.arange(27).view(3,3,3)>>> xtensor([[[ 0,  1,  2],         [ 3,  4,  5],         [ 6,  7,  8]],        [[ 9, 10, 11],         [12, 13, 14],         [15, 16, 17]],        [[18, 19, 20],         [21, 22, 23],         [24, 25, 26]]])>>> x[[1,2],[2,2],[2,1]] # 相当于x[1,2,2]和x[2,2,1]tensor([17, 25])>>> x[[2,1,0],1,1] #相当于x[2,1,1],x[1,1,1]和x[0,1,1]tensor([22, 13,  4])>>> x[[0,2],...] tensor([[[ 0,  1,  2],         [ 3,  4,  5],         [ 6,  7,  8]],        [[18, 19, 20],         [21, 22, 23],         [24, 25, 26]]])
           

5. Tensor类型

Tensor有许多不同的数据类型,每种类型还有CPU和GPU版本(HalfTensor除外)。默认的tensor是FloatTensor,可通过t.set_default_tensor_type修改默认的tensor类型;HalfTensor是专门为GPU设计的,可以减少内存消耗,但是HalfTensor所能表示的数据大小有限,所以可能会出现溢出问题。

数据类型 CPU tensor GPU tensor
32bit 浮点 torch.FloatTensor torch.cuda.FloatTensor
64bit 浮点 torch.DoubleTensor torch.cuda.DoubleTensor
16bit 半精度浮点 N/A torch.cuda.HalfTensor
32bit 有符号整型 torch.IntTensor torch.cuda.IntTensor
64bit 有符号整型 torch.FloatTensor torch.cuda.FloatTensor

数据类型的相互转换:

>>> t.set_default_tensor_type("torch.IntTensor")Traceback (most recent call last):  File "", line 1, in <module>  File "/usr/python3.8/lib/python3.8/site-packages/torch/__init__.py", line 206, in set_default_tensor_type    _C._set_default_tensor_type(t)TypeError: only floating-point types are supported as the default type# 说明不允许转换默认类型,好吧,那就不换了>>> a=t.Tensor([1,2,3]) #是torch.FloatTensor类型>>> atensor([1., 2., 3.])>>> b=a.type(t.IntTensor)>>> btensor([1, 2, 3], dtype=torch.int32)>>> c=a.type_as(b)>>> ctensor([1, 2, 3], dtype=torch.int32)
           

5. 逐元素操作

这里将的是对tensor的每一个元素进行操作的函数,这类函数的输入和输出一般都保持一致,常见的操作如下表所示。

函数 功能
abs/sqrt/div/exp/fmod/log/pow/mul 绝对值/平方根/除法/指数/求余/log/求幂/乘法(非矩阵相乘)
cos/sin 三角函数
ceil/round/floor/trunc 向上取整/四舍五入/向下取整/取整数部分
clamp(input,min,max) 超过min和max部分截断
sigmod/tanh 激活函数

注意:矩阵相乘为torch.nn(a,b),这不是逐元素操作,要和mul区分开来

其中,有很多运算都实现了运算符重载,何为重载呢,比如a**2 等于torch.pow(a,2),a*2 等于torch.mul(a,2)。

其中 clamp(x,min,max)的输出公式为:

pytorch gather_Pytorch之Tensor超详细理解(一)

clamp常用在一些需要比较大小的地方,比如说取一个tensor的每个元素和另一个数的较大值。

>>> import torch as t>>> a=t.arange(0,6).view(2,3)>>> t.cos(a)tensor([[ 1.0000,  0.5403, -0.4161],        [-0.9900, -0.6536,  0.2837]])>>> a%3 tensor([[ 0.,  1.,  2.],        [ 0.,  1.,  2.]])>>> t.fmod(a,3)tensor([[ 0.,  1.,  2.],        [ 0.,  1.,  2.]])>>> a**2tensor([[  0.,   1.,   4.],        [  9.,  16.,  25.]])>>> t.pow(a,2)tensor([[  0.,   1.,   4.],        [  9.,  16.,  25.]])>>> a*2tensor([[  0.,   2.,   4.],        [  6.,   8.,  10.]])>>> t.mul(a,2)tensor([[  0.,   2.,   4.],        [  6.,   8.,  10.]])>>> t.clamp(a,min=3) # a中的元素和3比较,取较大的一个tensor([[ 3.,  3.,  3.],        [ 3.,  4.,  5.]])
           

6. 归并操作

此类操作一般用作统计,可以沿着某一维进行操作。如求和操作sum,既可以计算整个Tensor的值,也可以计算Tensor中每一行或每一列的和。常用的归并操作如下。

函数 功能
mean/sum/median/mode 均值/和/中位数/众数
norm/dist 范数/距离
std/var 方差/标准差
cumsum/cumprod 累加/累乘

这些操作大多数都有一个参数dim,用来指定在哪个维度上进行这些操作。这里提供一个简单的记忆方法。

假设输入的形状是(x,y,k):

如果指定dim=0,输出的形状是(1,n,k)或(n,k)。

如果指定dim=1,输出的形状是(x,1,k)或(x,k)。

如果指定dim=2,输出的形状是(x,y,1)或(x,y)。

输出的形状里是否有“1”,取决于这些操作的另一个参数keepdim,如果keepdim=1则会保留维度1,pytorch中默认是False。

>>> b=t.ones(2,3)>>> b.sum(dim=0,keepdim=True) # 注意维度,这是2维的tensor([[ 2.,  2.,  2.]])>>> b.sum(dim=0) # 而这是一维的tensor([ 2.,  2.,  2.])>>> b.sum(dim=1)tensor([ 3.,  3.])>>> c=t.arange(6).view(2,3)>>> ctensor([[ 0.,  1.,  2.],        [ 3.,  4.,  5.]])>>> c.cumsum(dim=1) # 沿着行累加tensor([[  0.,   1.,   3.],        [  3.,   7.,  12.]])
           

7. 比较操作

比较操作有些是逐元素进行操作,有些则类似于归并操作。常用的比较函数如下所示。

函数 功能
gt/lt/ge/le/eq/ne 大于/小于/大于等于/小于等于/等于/不等
topk 最大的k个数
sort 排序
max/min 比较两个tensor的最大值和最小值

表中的第一行操作已经实现了运算符重载,因此可以使用a<=b等来操作,返回结果是一个ByteTensor,可以用来选取元素。max/min这两个操作,有如下解释:

t.max(a),返回a中最大的一个值;

t.max(a,dim),返回a中指定维度上最大的数和该数的下标;

t.max(a,b),比较两个tensor中比较大的元素。

比较一个tensor和一个数,可以用clamp(x,min,max),下面开始看例子。

>>> a=t.linspace(0,15,6).view(2,3)>>> atensor([[  0.,   3.,   6.],        [  9.,  12.,  15.]])>>> b=t.linspace(15,0,6).view(2,3)>>> btensor([[ 15.,  12.,   9.],        [  6.,   3.,   0.]])>>> a>btensor([[ 0,  0,  0],        [ 1,  1,  1]], dtype=torch.uint8)>>> a[a>b] # a中大于b的元素,注意形状改变了tensor([  9.,  12.,  15.])>>> t.max(a)tensor(15.)>>> t.max(b,dim=1) # 15和6是该行最大的元素,0和0是该行的第几个元素(tensor([ 15.,   6.]), tensor([ 0,  0]))>>> t.max(a,b) # 取a和b中的最大值tensor([[ 15.,  12.,   9.],        [  9.,  12.,  15.]])>>> t.clamp(a,min=10) # 和10比较,取最大值tensor([[ 10.,  10.,  10.],        [ 10.,  12.,  15.]])
           

8. 线性代数

函数 功能
trace 对角线元素之和(迹)
diag 对角线元素
mm/bmm 矩阵乘法/batch的矩阵乘法
t 转置
dot/cross 内积/外积
inverse 求逆矩阵
svd 奇异值分解
>>> a=t.arange(6).view(2,3)>>> atensor([[ 0.,  1.,  2.],        [ 3.,  4.,  5.]])>>> t.trace(a) # 对角线元素之和0+4tensor(4.)>>> t.diag(a) # 提取对角线元素tensor([ 0.,  4.])
           

其他函数的用法待后期慢慢介绍,今天先掌握到这里为止哦,回去好好复习把,这些暂时够用了!!!

参考书籍:深度学习框架:PyTorch入门与实战

pytorch gather_Pytorch之Tensor超详细理解(一)

继续阅读