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)的输出公式为:
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入门与实战