1.1. 概念:张量
1.1.1定义
在深度学习的实践中,我们通常使用向量或矩阵运算来提高计算效率。比如w1x1+w2x2+⋯+wNxNw_1x_1 + w_2 x_2 +\cdots +w_N x_Nw1x1+w2x2+⋯+wNxN的计算可以用w⊤x\bm w^\top \bm xw⊤x来代替(其中w=[w1w2⋯wN]⊤,x=[x1x2⋯xN]⊤\bm w=[w_1 w_2 \cdots w_N]^\top,\bm x=[x_1 x_2 \cdots x_N]^\topw=[w1w2⋯wN]⊤,x=[x1x2⋯xN]⊤),这样可以充分利用计算机的并行计算能力,特别是利用GPU来实现高效矩阵运算。
在深度学习框架中,数据经常用张量(Tensor)的形式来存储。张量是矩阵的扩展与延伸,可以认为是高阶的矩阵。1阶张量为向量,2阶张量为矩阵。如果你对Numpy熟悉,那么张量是类似于Numpy的多维数组(ndarray)的概念,可以具有任意多的维度。
笔记
注意:这里的“维度”是“阶”的概念,和线性代数中向量的“维度”含义不同。
张量的大小可以用形状(shape)来描述。比如一个三维张量的形状是 [2,2,5][2, 2, 5][2,2,5],表示每一维(也称为轴(axis))的元素的数量,即第0轴上元素数量是2,第1轴上元素数量是2,第2轴上的元素数量为5。
图1.5给出了3种纬度的张量可视化表示。
图1.5 不同维度的张量可视化表示
张量中元素的类型可以是布尔型数据、整数、浮点数或者复数,但同一张量中所有元素的数据类型均相同。因此我们可以给张量定义一个数据类型(dtype)来表示其元素的类型。
1.2自己的理解
张量连续存储,计算能力更快更强,可以看成多维数组。支持cpu和gpu加速。高阶张量由多个低阶张量组成。Pytorch中的张量为torch.Tensor,即为torch下的Tensor类,其数据类型(dtype)有多种。
1.2. 使用pytorch实现张量运算
1.2.1 创建张量
1.2.1.1 指定数据创建
直接使用torch.Tensor(数据)创建
(1)一维:
import torch
first=torch.Tensor([2.0, 3.0, 4.0])
print(first)
结果:tensor([2., 3., 4.])
(2)二维:
first=torch.Tensor([[1.0, 2.0, 3.0],[4.0, 5.0, 6.0]])
print(first)
结果:
tensor([[1., 2., 3.],
[4., 5., 6.]])
(3)多维
first=torch.Tensor([[[1, 2, 3, 4, 5],
[6, 7, 8, 9, 10]],
[[11, 12, 13, 14, 15],
[16, 17, 18, 19, 20]]])
结果:
tensor([[[ 1., 2., 3., 4., 5.],
[ 6., 7., 8., 9., 10.]],
[[11., 12., 13., 14., 15.],
[16., 17., 18., 19., 20.]]])
1.2.1.2 指定形状创建
torch.zeros(size) 是 PyTorch 中用来创建全 0 张量的函数。size 参数表示张量的形状(shape).
torch.ones(size) 是 PyTorch 中用来创建全 1张量的函数。size 参数表示张量的形状(shape).
torch.full(size, fill_value)返回创建size大小的形状,里面元素全部填充为fill_value的张量。
x=torch.zeros(2,3)
y=torch.ones(2,3)
print(x)
print(y)
结果:
tensor([[0., 0., 0.],
[0., 0., 0.]])
tensor([[1., 1., 1.],
[1., 1., 1.]])
x=torch.full([2,3],10)
print(x)
结果:
tensor([[10, 10, 10],
[10, 10, 10]])
1.2.1.3 指定区间创建
x=torch.arange(start,end,step) 等间隔取值的张量
start:起始值,默认值:0 end:结束值 step:步长,默认值:1。[ start , end )
y=torch.linspace(start,end,steps) 等间隔取值的张量
start:开始值 end:结束值 steps:分割的点数,默认是100 [ start , end ]
x=torch.arange(start=1,end=5,step=1)#start:起始值,默认值:0 end:结束值 step:步长,默认值:1[ start , end )
y=torch.linspace(start=1,end=5,steps=4)#start:开始值 end:结束值 steps:分割的点数,默认是100 [ start , end ]
print(x)
print(y)
结果:
tensor([1, 2, 3, 4])
tensor([1.0000, 2.3333, 3.6667, 5.0000])
1.2.2 张量的属性
1.2.2.1 张量的形状
x=torch.Tensor([[2, 3, 4, 5],[2,3,4,6]])
print(x.ndim)#张量的维度
print(x.shape)#张量每个维度上元素的数量
print(x.shape[0])#张量第 𝑛 维的大小。第 𝑛 维也称为轴(axis
print(x.size) #存在的内存位置
结果:
2
torch.Size([2, 4])
2
<built-in method size of Tensor object at 0x0000020E02AAE810>
1.2.2.2 形状的改变
torch.reshape()
torch.unsqueeze()升维
x=torch.Tensor([[[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, 27, 28, 29, 30]]])
print(x.shape)
y=torch.reshape(x,[2,3,5])#注意变换前后元素个数必须相等,元素顺序没有发生改变
print(y)
结果:
torch.Size([3, 2, 5])
tensor([[[ 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., 27., 28., 29., 30.]]])
#torch.squeeze(input, dim=None, out=None)函数的功能是维度压缩。返回一个tensor(张量),其中 input 中维度大小为1的所有维都已删除。
#unsqueeze()函数起升维的作用,参数dim表示在哪个地方加一个维度
x=torch.ones([5,10])
y=torch.unsqueeze(x,1)
y=torch.unsqueeze(y,2)
print(y.shape)
结果:torch.Size([5, 1, 1, 10])
1.2.2.3 张量的数据类型
数据类型的访问
x=torch.tensor(1)
print(x.dtype)#返回元素的数据类型
print(x.type())#返回参数的数据类型
s=torch.tensor([2,3,4],dtype=torch.float64)#也可指定数据类型创建张量
print(s)
结果:
torch.int64
torch.LongTensor
tensor([2., 3., 4.], dtype=torch.float64)
元素数据类型的改变
t=s.int() #,t变成元素数据类型为int的了,但并不会改变s原来的元素的数据类型
print(s.dtype)
print(t.dtype)
结果:
torch.float64
torch.int32
1.2.2.4 张量的设备位置
初始化张量时可以通过place来指定其分配的设备位置,可支持的设备位置有三种:CPU、GPU和固定内存。固定内存也称为不可分页内存或锁页内存,它与GPU之间具有更高的读写效率,并且支持异步传输,这对网络整体性能会有进一步提升,但它的缺点是分配空间过多时可能会降低主机系统的性能,因为它减少了用于存储虚拟内存数据的可分页内存。
如下代码分别创建了CPU、GPU和固定内存上的张量,并通过Tensor.device查看张量所在的设备位置。
x=torch.tensor(1,device=torch.device('cpu'))
print(x.device)
结果:cpu
1.2.3 张量与Numpy数组转换
import torch
import numpy
x=np.array([2,3,4])
y=torch.Tensor(x)#numpy转换为张量
z=y.numpy()#张量转换为numpy
print(x)
print(y)
print(z
结果:
[2 3 4]
tensor([2., 3., 4.])
[2. 3. 4.]
1.2.4 张量的访问
1.2.4.1 索引和切片
x=torch.tensor([[1,2,3,4],[5,6,7,8],[9,10,11,12]])
print(x[0])#索引访问
print(x[0:2])#切片访问[start : end : step]左闭右开
结果:
tensor([1, 2, 3, 4])
tensor([[1, 2, 3, 4],
[5, 6, 7, 8]])
1.2.4.3 修改张量
!慎重通过索引或切片操作来修改张量,此操作仅会原地修改该张量的数值,且原值不会被保存。
x=torch.tensor([[1,2,3,4],[5,6,7,8],[9,10,11,12]])
print('刚开始',x)
x[0]=1
print('改变第一维',x)
x[...]=6
print('全改成6',x)
结果:
刚开始 tensor([[ 1, 2, 3, 4],
[ 5, 6, 7, 8],
[ 9, 10, 11, 12]])
改变第一维 tensor([[ 1, 1, 1, 1],
[ 5, 6, 7, 8],
[ 9, 10, 11, 12]])
全改成6 tensor([[6, 6, 6, 6],
[6, 6, 6, 6],
[6, 6, 6, 6]])
1.2.5 张量的运算
1.2.5.1 数学运算
x=torch.tensor([1,2,3,4])
y=torch.tensor([9,10,11,12])
print(x+y)# 逐元素加
print(x*y) # 逐元素乘(积)
print(x-y) # 逐元素减
print(x/y) # 逐元素除
print(x**y)# 逐元素幂
print(x%y) # 逐元素除并取余
结果:
tensor([10, 12, 14, 16])
tensor([ 9, 20, 33, 48])
tensor([-8, -8, -8, -8])
tensor([0.1111, 0.2000, 0.2727, 0.3333])
tensor([ 1, 1024, 177147, 16777216])
tensor([1, 2, 3, 4])
1.2.5.2 逻辑运算
x=torch.tensor([1,2,3,4])
y=torch.tensor([1,2,11,12])
print(x.isfinite())# 判断Tensor中元素是否是有限的数字
print(x.equal(y))# 比较两个张量是否相等
print(x.eq(y))# 判断两个Tensor的每个元素是否相等
print(x.not_equal(y))# 判断两个Tensor的每个元素是否不相等
print(x.less_equal(y))# 判断Tensor x的元素是否小于或等于Tensor y的对应
print(x.greater_equal(y) )# 判断Tensor x的元素是否大于或等于Tensor y的对应元素
print(x.allclose(y) ) # 判断两个Tensor的全部元素是否接近
结果:
tensor([True, True, True, True])
False
tensor([ True, True, False, False])
tensor([False, False, True, True])
tensor([True, True, True, True])
tensor([ True, True, False, False])
False
1.2.5.3 矩阵运算
x=torch.tensor([[1,2,3,4],[5,6,7,8]])
q=torch.tensor([[9,10,11,12],[15,16,17,18]])
y=x.t()# 矩阵转置
print(y)
z=torch.transpose(x, 0, 1) # 交换第 0 维与第 1 维的顺序,可以看成交换中括号的顺序
print(z)
m=torch.matmul(y,q)#矩阵乘法
print(m)
有些矩阵运算中也支持大于两维的张量,比如matmul函数,对最后两个维度进行矩阵乘。比如x是形状为[j,k,n,m]的张量,另一个y是[j,k,m,p]的张量,则x.matmul(y)输出的张量形状为[j,k,n,p]。
结果:
tensor([[1, 5],
[2, 6],
[3, 7],
[4, 8]])
tensor([[1, 5],
[2, 6],
[3, 7],
[4, 8]])
tensor([[ 84, 90, 96, 102],
[108, 116, 124, 132],
[132, 142, 152, 162],
[156, 168, 180, 192]])
1.2.5.4 广播机制
飞桨的一些API在计算时支持广播(Broadcasting)机制,允许在一些运算时使用不同形状的张量。通常来讲,如果有一个形状较小和一个形状较大的张量,会希望多次使用较小的张量来对较大的张量执行某些操作,看起来像是形状较小的张量首先被扩展到和较大的张量形状一致,然后再做运算。
广播机制的条件
飞桨的广播机制主要遵循如下规则(参考Numpy广播机制):
1)每个张量至少为一维张量。
2)从后往前比较张量的形状,当前维度的大小要么相等,要么其中一个等于1,要么其中一个不存在
import numpy as np
import torch
# 当两个Tensor的形状一致时,可以广播
x = torch.ones((2, 3, 4))
y = torch.ones((2, 3, 4))
z = x + y
print('broadcasting with two same shape tensor: ', z.shape)
x = torch.ones((2, 3, 1, 5))
y = torch.ones((3, 4, 1))
# 从后往前依次比较:
# 第一次:y的维度大小是1
# 第二次:x的维度大小是1
# 第三次:x和y的维度大小相等,都为3
# 第四次:y的维度不存在
# 所以x和y是可以广播的
z = x + y
print('broadcasting with two different shape tensor:', z.shape)
结果:
broadcasting with two same shape tensor: torch.Size([2, 3, 4])
broadcasting with two different shape tensor: torch.Size([2, 3, 4, 5])
广播机制的计算规则
现在我们知道在什么情况下两个张量是可以广播的。两个张量进行广播后的结果张量的形状计算规则如下:
1)如果两个张量shape的长度不一致,那么需要在较小长度的shape前添加1,直到两个张量的形状长度相等。
2) 保证两个张量形状相等之后,每个维度上的结果维度就是当前维度上较大的那个。
以张量x和y进行广播为例,x的shape为[2, 3, 1,5],张量y的shape为[3,4,1]。首先张量y的形状长度较小,因此要将该张量形状补齐为[1, 3, 4, 1],再对两个张量的每一维进行比较。从第一维看,x在一维上的大小为2,y为1,因此,结果张量在第一维的大小为2。以此类推,对每一维进行比较,得到结果张量的形状为[2, 3, 4, 5]。
按需要看看需不需要广播机制并注意是否满足条件
总结:
-
维度不同,小维度的增加维度
-
每个维度,计算结果取大的
-
扩展维度是对数值进行复制
-
特别注意:关于torch.matmul
1)如果两个张量均为一维,则获得点积结果。
2) 如果两个张量都是二维的,则获得矩阵与矩阵的乘积。
3) 如果张量x是一维,y是二维,则将x的shape转换为[1, D],与y进行矩阵相乘后再删除前置尺寸。
4) 如果张量x是二维,y是一维,则获得矩阵与向量的乘积。
5) 如果两个张量都是N维张量(N > 2),则根据广播规则广播非矩阵维度(除最后两个维度外其余维度)。比如:如果输入x是形状为[j,1,n,m]的张量,另一个y是[k,m,p]的张量,则输出张量的形状为[j,k,n,p]。
1.3 心得体会
1、【PyTorch的张量或者NumPy数组通常在内存中存储的是一段连续的、未装箱的C数字类型】
2、【在张量中,第一维指的是最外层的维度,也就是第0维,而第二维指的是第一个嵌套的维度,即第一个非外层的维度。】
3、更加了解pytorch与tensor的关系,numpy与tensor的关系。
4、本节结合了PaddlePaddle中的张量的知识,也学习了pytorch里的张量的知识。学到了张量的创建,属性,访问及相关运算。
5、广播机制第一次了解,明白了一些规则和条件。