2.4 Numpy与Tensor
第1章已经介绍了Numpy,了解到其存取数据非常方便,而且还拥有大量的函数,所以深得数据处理、机器学习者喜爱。这节我们将介绍PyTorch的Tensor,它可以是零维(又称为标量或一个数)、一维、二维及多维的数组。Tensor自称为神经网络界的Numpy,它与Numpy相似,二者可以共享内存,且之间的转换非常方便和高效。不过它们也有不同之处,最大的区别就是Numpy会把ndarray放在CPU中进行加速运算,而由Torch产生的Tensor会放在GPU中进行加速运算(假设当前环境有GPU)。
2.4.1 Tensor概述
对Tensor的操作很多,从接口的角度来划分,可以分为两类:
1)torch.function,如torch.sum、torch.add等;
2)tensor.function,如tensor.view、tensor.add等。
这些操作对大部分Tensor都是等价的,如torch.add(x,y)与x.add(y)等价。在实际使用时,可以根据个人爱好选择。
如果从修改方式的角度来划分,可以分为以下两类:
1)不修改自身数据,如x.add(y),x的数据不变,返回一个新的Tensor。
2)修改自身数据,如x.add_(y)(运行符带下划线后缀),运算结果存在x中,x被修改。
import torch x=torch.tensor([1,2]) y=torch.tensor([3,4]) z=x.add(y) print(z) print(x) x.add_(y) print(x)
运行结果:
tensor([4, 6]) tensor([1, 2]) tensor([4, 6])
2.4.2 创建Tensor
创建Tensor的方法有很多,可以从列表或ndarray等类型进行构建,也可根据指定的形状构建。常见的创建Tensor的方法可参考表2-1。
表2-1 常见的创建Tensor的方法
下面举例说明。
import torch #根据list数据生成Tensor torch.Tensor([1,2,3,4,5,6]) #根据指定形状生成Tensor torch.Tensor(2,3) #根据给定的Tensor的形状 t=torch.Tensor([[1,2,3],[4,5,6]]) #查看Tensor的形状 t.size() #shape与size()等价方式 t.shape #根据已有形状创建Tensor torch.Tensor(t.size())
【说明】
注意torch.Tensor与torch.tensor的几点区别:
1)torch.Tensor是torch.empty和torch.tensor之间的一种混合,但是,当传入数据时,torch.Tensor使用全局默认dtype(FloatTensor),而torch.tensor是从数据中推断数据类型。
2)torch.tensor(1)返回一个固定值1,而torch.Tensor(1)返回一个大小为1的张量,它是随机初始化的值。
import torch t1=torch.Tensor(1) t2=torch.tensor(1) print("t1的值{},t1的数据类型{}".format(t1,t1.type())) print("t2的值{},t2的数据类型{}".format(t2,t2.type()))
运行结果:
t1的值tensor([3.5731e-20]),t1的数据类型torch.FloatTensor t2的值1,t2的数据类型torch.LongTensor
下面是根据一定规则,自动生成Tensor的一些例子。
import torch #生成一个单位矩阵 torch.eye(2,2) #自动生成全是0的矩阵 torch.zeros(2,3) #根据规则生成数据 torch.linspace(1,10,4) #生成满足均匀分布随机数 torch.rand(2,3) #生成满足标准分布随机数 torch.randn(2,3) #返回所给数据形状相同,值全为0的张量 torch.zeros_like(torch.rand(2,3))
2.4.3 修改Tensor形状
在处理数据、构建网络层等过程中,经常需要了解Tensor的形状、修改Tensor的形状。与修改Numpy的形状类似,修改Tenor的形状也有很多类似函数,具体可参考表2-2。
表2-2 为tensor常用修改形状的函数
以下为一些实例:
import torch #生成一个形状为2x3的矩阵 x = torch.randn(2, 3) #查看矩阵的形状 x.size() #结果为torch.Size([2, 3]) #查看x的维度 x.dim() #结果为2 #把x变为3x2的矩阵 x.view(3,2) #把x展平为1维向量 y=x.view(-1) y.shape #添加一个维度 z=torch.unsqueeze(y,0) #查看z的形状 z.size() #结果为torch.Size([1, 6]) #计算Z的元素个数 z.numel() #结果为6
【说明】
torch.view与torch.reshpae的异同
1)reshape()可以由torch.reshape(),也可由torch.Tensor.reshape()调用。但view()只可由torch.Tensor.view()来调用。
2)对于一个将要被view的Tensor,新的size必须与原来的size与stride兼容。否则,在view之前必须调用contiguous()方法。
3)同样也是返回与input数据量相同,但形状不同的Tensor。若满足view的条件,则不会copy,若不满足,则会copy。
4)如果你只想重塑张量,请使用torch.reshape。如果你还关注内存使用情况并希望确保两个张量共享相同的数据,请使用torch.view。
2.4.4 索引操作
Tensor的索引操作与Numpy类似,一般情况下索引结果与源数据共享内存。从Tensor获取元素除了可以通过索引,也可以借助一些函数,常用的选择函数可参考表2-3。
表2-3 常用选择操作函数
以下为部分函数的实现代码:
import torch #设置一个随机种子 torch.manual_seed(100) #生成一个形状为2x3的矩阵 x = torch.randn(2, 3) #根据索引获取第1行,所有数据 x[0,:] #获取最后一列数据 x[:,-1] #生成是否大于0的Byter张量 mask=x>0 #获取大于0的值 torch.masked_select(x,mask) #获取非0下标,即行,列索引 torch.nonzero(mask) #获取指定索引对应的值,输出根据以下规则得到 #out[i][j] = input[index[i][j]][j] # if dim == 0 #out[i][j] = input[i][index[i][j]] # if dim == 1 index=torch.LongTensor([[0,1,1]]) torch.gather(x,0,index) index=torch.LongTensor([[0,1,1],[1,1,1]]) a=torch.gather(x,1,index) #把a的值返回到一个2x3的0矩阵中 z=torch.zeros(2,3) z.scatter_(1,index,a)
2.4.5 广播机制
在1.7节中介绍了Numpy的广播机制,广播机制是向量运算的重要技巧。PyTorch也支持广播机制,以下通过几个示例进行说明。
import torch import numpy as np A = np.arange(0, 40,10).reshape(4, 1) B = np.arange(0, 3) #把ndarray转换为Tensor A1=torch.from_numpy(A) #形状为4x1 B1=torch.from_numpy(B) #形状为3 #Tensor自动实现广播 C=A1+B1 #我们可以根据广播机制,手工进行配置 #根据规则1,B1需要向A1看齐,把B变为(1,3) B2=B1.unsqueeze(0) #B2的形状为1x3 #使用expand函数重复数组,分别的4x3的矩阵 A2=A1.expand(4,3) B3=B2.expand(4,3) #然后进行相加,C1与C结果一致 C1=A2+B3
2.4.6 逐元素操作
与Numpy一样,Tensor也有逐元素操作(Element-Wise),且操作内容相似,但使用函数可能不尽相同。大部分数学运算都属于逐元素操作,其输入与输出的形状相同。常见的逐元素操作可参考表2-4。
【说明】
这些操作均会创建新的Tensor,如果需要就地操作,可以使用这些方法的下划线版本,例如abs_。
表2-4 常见逐元素操作
以下为部分逐元素操作代码实例。
import torch t = torch.randn(1, 3) t1 = torch.randn(3, 1) t2 = torch.randn(1, 3) #t+0.1*(t1/t2) torch.addcdiv(t, 0.1, t1, t2) #计算sigmoid torch.sigmoid(t) #将t限制在[0,1]之间 torch.clamp(t,0,1) #t+2进行就地运算 t.add_(2)
2.4.7 归并操作
归并操作顾名思义,就是对输入进行归并或合计等操作,这类操作的输入输出形状一般并不相同,而且往往是输入大于输出形状。归并操作可以对整个Tensor,也可以沿着某个维度进行归并。常见的归并操作可参考表2-5。
表2-5 常见的归并操作
【说明】
归并操作一般涉及一个dim参数,指定沿哪个维进行归并。另一个参数是keepdim,说明输出结果中是否保留维度1,缺省情况是False,即不保留。
以下为归并操作的部分代码:
import torch #生成一个含6个数的向量 a=torch.linspace(0,10,6) #使用view方法,把a变为2x3矩阵 a=a.view((2,3)) #沿y轴方向累加,即dim=0 b=a.sum(dim=0) #b的形状为[3] #沿y轴方向累加,即dim=0,并保留含1的维度 b=a.sum(dim=0,keepdim=True) #b的形状为[1,3]
2.4.8 比较操作
比较操作一般是进行逐元素比较,有些是按指定方向比较。常用的比较函数可参考表2-6。
表2-6 常用的比较函数
以下是部分函数的代码实现。
import torch x=torch.linspace(0,10,6).view(2,3) #求所有元素的最大值 torch.max(x) #结果为10 #求y轴方向的最大值 torch.max(x,dim=0) #结果为[6,8,10] #求最大的2个元素 torch.topk(x,1,dim=0) #结果为[6,8,10],对应索引为tensor([[1, 1, 1]
2.4.9 矩阵操作
机器学习和深度学习中存在大量的矩阵运算,常用的算法有两种:一种是逐元素乘法,另外一种是点积乘法。PyTorch中常用的矩阵函数可参考表2-7。
表2-7 常用矩阵函数
【说明】
1)Torch的dot与Numpy的dot有点不同,Torch中的dot是对两个为1D张量进行点积运算,Numpy中的dot无此限制。
2)mm是对2D的矩阵进行点积,bmm对含batch的3D进行点积运算。
3)转置运算会导致存储空间不连续,需要调用contiguous方法转为连续。
import torch a=torch.tensor([2, 3]) b=torch.tensor([3, 4]) torch.dot(a,b) #运行结果为18 x=torch.randint(10,(2,3)) y=torch.randint(6,(3,4)) torch.mm(x,y) x=torch.randint(10,(2,2,3)) y=torch.randint(6,(2,3,4)) torch.bmm(x,y)
2.4.10 PyTorch与Numpy比较
PyTorch与Numpy有很多类似的地方,并且有很多相同的操作函数名称,或虽然函数名称不同但含义相同;当然也有一些虽然函数名称相同,但含义不尽相同。有些很容易混淆,下面我们把一些主要的区别进行汇总,具体可参考表2-8。
表2-8 PyTorch与Numpy函数对照表