转载自:https://www.bilibili.com/video/BV14X4y1A7KT?p=3
Lesson 1. 张量(Tensor)的创建和常用方法
??在实际使用PyTorch的过程中,张量(Tensor)对象是我们操作的基本数据类型。
??很多时候,在我们没有特别明确什么是深度学习计算框架的时候,我们可以把PyTorch简单看成是Python的深度学习第三方库,在PyTorch中定义了适用于深度学习的基本数据结构————张量,以及张量的各类计算。其实也就相当于NumPy中定义的Array和对应的科学计算方法,正是这些基本数据类型和对应的方法函数,为我们进一步在PyTorch上进行深度学习建模提供了基本对象和基本工具。因此,在正式使用PyTorch进行深度学习建模之前,我们需要熟练掌握PyTorch中张量的基本操作方法。
??当然,值得一提的是,张量的概念并非PyTorch独有,目前来看,基本上通用的深度学习框架都拥有张量这一类数据结构,但不同的深度学习框架中张量的定义和使用方法都略有差别。而张量作为数组的衍生概念,其本身的定义和使用方法和NumPy中的Array非常类似,甚至,在复现一些简单的神经网络算法场景中,我们可以直接使用NumPy中的Array来进行操作。当然,此处并不是鼓励大家使用NumPy来进行深度学习,因为毕竟NumPy中的Array只提供了很多基础功能,写简单神经网络尚可,写更加复杂的神经网络则会非常复杂,并且Array数据结构本身也不支持GPU运行,因此无法应对工业场景中复杂神经网络背后的大规模数值运算。但我们需要知道的是,工具的差异只会影响实现层的具体表现,因此,一方面,我们在学习的过程中,不妨对照NumPy中的Array来进行学习,另一方面,我们更需要透过工具的具体功能,来理解和体会背后更深层次的数学原理和算法思想。
- 首次使用,先导入PyTorch包
import torch
- 查看版本号
torch.__version__
'1.7.1'
一、张量(Tensor)的基本创建及其类型
??和NumPy中的dnarray一样,张量的本质也是结构化的组织了大量的数据。并且,在实际操作过程中,张量的创建和基本功能也和NumPy中的array非常类似。
1.张量(Tensor)函数创建方法
??张量的最基本创建方法和NumPy中创建Array的格式一致,都是创建函数(序列)
的格式:张量创建函数:torch.tensor()
# 通过列表创建张量
t = torch.tensor([1, 2])
t
tensor([1, 2])
# 通过元组创建张量
torch.tensor((1, 2))
tensor([1, 2])
import numpy as np
a = np.array((1, 2))
a
array([1, 2])
# 通过数组创建张量
t1 = torch.tensor(a)
t1
tensor([1, 2], dtype=torch.int32)
**Point:**通过上述返回结果,我们发现张量也有detype类型。
2.张量的类型
??张量和数组类似,都拥有dtype方法,可返回张量类型。
# 数组类型
a.dtype
dtype('int32')
t.dtype
torch.int64
t1.dtype
torch.int32
??在这里,我们发现,整数型的数组默认创建int32(整型)类型,而张量则默认创建int64(长整型)类型。
np.array([1.1, 2.2]).dtype
dtype('float64')
torch.tensor(np.array([1.1, 2.2])).dtype
torch.float64
torch.tensor([1.11, 2.2]).dtype
torch.float32
??相对的,创建浮点型数组时,张量默认是float32(单精度浮点型),而Array则是默认float64(双精度浮点型)。
??当然,除了数值型张量,常用的常量类型还有布尔型张量,也就是构成张量的各元素都是布尔类型的张量。
t2 = torch.tensor([True, False])
t2
tensor([ True, False])
t2.dtype
torch.bool
??和数组不同,对于张量而言,数值型和布尔型张量就是最常用的两种张量类型,相关类型总结如下。
数据类型 | dtype |
---|---|
32bit浮点数 | torch.float32或torch.float |
64bit浮点数 | torch.float64或torch.double |
16bit浮点数 | torch.float16或torch.half |
8bit无符号整数 | torch.unit8 |
8bit有符号整数 | torch.int8 |
16bit有符号整数 | torch.int16或torch.short |
16bit有符号整数 | torch.int16或torch.short |
32bit有符号整数 | torch.int32或torch.int |
64bit有符号整数 | torch.int64或torch.long |
布尔型 | torch.bool |
复数型 | torch.complex64 |
此外,我们还可以通过dtype参数,在创建张量过程中设置输出结果。
# 创建int16整型张量
torch.tensor([1.1, 2.7], dtype = torch.int16)
tensor([1, 2], dtype=torch.int16)
当然,在PyTorch中也支持复数类型对象创建
a = torch.tensor(1 + 2j) # 1是实部、2是虚部
a
tensor(1.+2.j)
3.张量类型的转化
- 张量类型的隐式转化
??和NumPy中array相同,当张量各元素属于不同类型时,系统会自动进行隐式转化。
# 浮点型和整数型的隐式转化
torch.tensor([1.1, 2]).dtype
torch.float32
# 布尔型和数值型的隐式转化
torch.tensor([True, 2.0])
tensor([1., 2.])
- 张量类型的转化方法
??当然,我们还可以使用.float()、.int()等方法对张量类型进行转化。
t
tensor([1, 2])
# 转化为默认浮点型(32位)
t.float()
tensor([1., 2.])
# 转化为双精度浮点型
t.double()
tensor([1., 2.], dtype=torch.float64)
t.dtype
torch.int64
# 转化为16位整数
t.short()
tensor([1, 2], dtype=torch.int16)
Point:
- 当在torch函数中使用dtype参数时候,需要输入torch.float表示精度;
- 在使用方法进行类型转化时,方法名称则是double。(虽然torch.float和double都表示双精度浮点型。)
二、张量的维度与形变
??张量作为一组数的结构化表示,也同样拥有维度的概念。简答理解,向量就是一维的数组,而矩阵则是二维的数组,以此类推,在张量中,我们还可以定义更高维度的数组。当然,张量的高维数组和NumPy中高维Array概念类似。
1.创建高维张量
- 用简单序列创建一维数组
??包含“简单”元素的序列可创建一维数组。
t1 = torch.tensor([1, 2])
t1
tensor([1, 2])
# 使用ndim属性查看张量的维度
t1.ndim
1
# 使用shape查看形状
t1.shape
torch.Size([2])
# 和size函数相同
t1.size()
torch.Size([2])
注:和NumPy不同,PyTorch中size方法返回结果和shape属性返回结果一致。
此外,还需要注意有两个常用的函数/方法,用来查看张量的形状。
# 返回拥有几个(N-1)维元素
len(t1)
2
# 返回总共拥有几个数
t1.numel()
2
**注:**一维张量len和numel返回结果相同,但更高维度张量则不然
- 用“序列”的“序列”创建二维数组
??以此类推,我们还可以用形状相同的序列组成一个新的序列,进而将其转化为二维张量。
# 用list的list创建二维数组
t2 = torch.tensor([[1, 2], [3, 4]])
t2
tensor([[1, 2],[3, 4]])
t2.ndim
2
t2.shape
torch.Size([2, 2])
t2.size()
torch.Size([2, 2])
len(t2)
2
**理解:**此处len函数返回结果代表t2由两个1维张量构成
t2.numel()
4
**理解:**此处numel方法返回结果代表t2由总共由4个数构成
- “零”维张量
??在PyTorch中,还有一类特殊的张量,被称为零维张量。该类型张量只包含一个元素,但又不是单独一个数。
t = torch.tensor(1)
t
tensor(1)
t.ndim
0
t.shape
torch.Size([])
t.numel()
1
理解零维张量:
??目前,我们可将零维张量视为拥有张量属性的单独一个数。(例如,张量可以存在GPU上,但Python原生的数值型对象不行,但零维张量可以,尽管是零维。)从学术名称来说,Python中单独一个数是scalars(标量),而零维的张量则是tensor。
- 高维张量
??一般来说,三维及三维以上的张量,我们就将其称为高维张量。当然,在高维张量中,最常见的还是三维张量。我们可以将其理解为二维数组或者矩阵的集合。
a1 = np.array([[1, 2, 2], [3, 4, 4]])
a1
array([[1, 2, 2],[3, 4, 4]])
a2 = np.array([[5, 6, 6], [7, 8, 8]])
a2
array([[5, 6, 6],[7, 8, 8]])
# 由两个形状相同的二维数组创建一个三维的张量
t3 = torch.tensor([a1, a2])
t3
tensor([[[1, 2, 2],[3, 4, 4]],[[5, 6, 6],[7, 8, 8]]], dtype=torch.int32)
t3.ndim
3
t3.shape # 包含两个,两行三列的矩阵的张量。
torch.Size([2, 2, 3])
len(t3)
2
t3.numel()
12
当然,N维张量的创建方法,我们可以先创建M个N-1维的数组,然后将其拼成一个N维的张量。关于更高维度的张量,我们将在后续遇到时再进行讲解。在张量的学习过程中,三维张量就已经足够。
2.张量的形变
??张量作为数字的结构化集合,其结构也是可以根据实际需求灵活调整的。
2.1 flatten拉平:将任意维度张量转化为一维张量
t2
tensor([[1, 2],[3, 4]])
t2.flatten()
tensor([1, 2, 3, 4])
按行排列,拉平。
t3
tensor([[[1, 2, 2],[3, 4, 4]],[[5, 6, 6],[7, 8, 8]]], dtype=torch.int32)
t3.flatten()
tensor([1, 2, 2, 3, 4, 4, 5, 6, 6, 7, 8, 8], dtype=torch.int32)
注:如果将零维张量使用flatten,则会将其转化为一维张量。
t
tensor(1)
t.flatten()
tensor([1])
t.flatten().ndim
1
2.2 reshape方法:任意变形
t1
tensor([1, 2])
# 转化为两行、一列的向量
t1.reshape(2, 1)
tensor([[1],[2]])
注意,reshape过程中维度的变化:reshape转化后的维度由该方法输入的参数“个数”决定
- 转化后生成一维张量
t1.reshape(2)
tensor([1, 2])
t1.reshape(2).ndim
1
# 注,另一种表达形式
t1.reshape(2, )
tensor([1, 2])
- 转化后生成二维张量
t1.reshape(1, 2) # 生成包含一个两个元素的二维张量
tensor([[1, 2]])
t1.reshape(1, 2).ndim
2
- 转化后生成三维张量
t1.reshape(1, 1, 2)
tensor([[[1, 2]]])
t1.reshape(1, 2, 1)
tensor([[[1],[2]]])
# 注意转化过程维度的变化
t1.reshape(1, 2, 1).ndim
3
Q1:如何利用reshape方法,将t3拉平?
二、特殊张量的创建方法
??在很多数值科学计算的过程中,都会创建一些特殊取值的张量,用于模拟特殊取值的矩阵,如全0矩阵、对角矩阵等。因此,PyTorch中也存在很多创建特殊张量的函数。
1.特殊取值的张量创建方法
- 全0张量
torch.zeros([2, 3]) # 创建全是0的,两行、三列的张量(矩阵)
tensor([[0., 0., 0.],[0., 0., 0.]])
注:由于zeros就已经确定了张量元素取值,因此该函数传入的参数实际上是决定了张量的形状
- 全1张量
torch.ones([2, 3])
tensor([[1., 1., 1.],[1., 1., 1.]])
张量和列表、数组之间的转化
- 单位矩阵
torch.eye(5)
tensor([[1., 0., 0., 0., 0.],[0., 1., 0., 0., 0.],[0., 0., 1., 0., 0.],[0., 0., 0., 1., 0.],[0., 0., 0., 0., 1.]])
- 对角矩阵
略有特殊的是,在PyTorch中,需要利用一维张量去创建对角矩阵。
t1
tensor([1, 2])
torch.diag(t1)
tensor([[1, 0],[0, 2]])
torch.diag([1, 2]) # 不能使用list直接创建对角矩阵
---------------------------------------------------------------------------TypeError Traceback (most recent call last)<ipython-input-237-314c189cb631> in <module>
----> 1 torch.diag([1, 2]) # 不能使用list直接创建对角矩阵TypeError: diag(): argument 'input' (position 1) must be Tensor, not list
- rand:服从0-1均匀分布的张量
torch.rand(2, 3)
tensor([[0.9223, 0.9948, 0.2804],[0.8130, 0.2890, 0.5319]])
- randn:服从标准正态分布的张量
torch.randn(2, 3)
tensor([[-1.2513, 0.6465, -2.3011],[ 0.8447, 1.6856, 1.3615]])
- normal:服从指定正态分布的张量
torch.normal(2, 3, size = (2, 2)) # 均值为2,标准差为3的张量
tensor([[2.4660, 1.4952],[6.0202, 0.7525]])
- randint:整数随机采样结果
torch.randint(1, 10, [2, 4]) # 在1-10之间随机抽取整数,组成两行四列的矩阵
tensor([[5, 8, 8, 3],[6, 1, 4, 2]])
- arange/linspace:生成数列
torch.arange(5) # 和range相同
tensor([0, 1, 2, 3, 4])
torch.arange(1, 5, 0.5) # 从1到5(左闭右开),每隔0.5取值一个
tensor([1.0000, 1.5000, 2.0000, 2.5000, 3.0000, 3.5000, 4.0000, 4.5000])
torch.linspace(1, 5, 3) # 从1到5(左右都包含),等距取三个数
tensor([1., 3., 5.])
- empty:生成未初始化的指定形状矩阵
torch.empty(2, 3)
tensor([[0.0000e+00, 1.7740e+28, 1.8754e+28],[1.0396e-05, 1.0742e-05, 1.0187e-11]])
- full:根据指定形状,填充指定数值
torch.full([2, 4], 2)
tensor([[2, 2, 2, 2],[2, 2, 2, 2]])
2.创建指定形状的数组
??当然,我们还能根据指定对象的形状进行数值填充,只需要在上述函数后面加上_like
即可。
t1
tensor([1, 2])
t2
tensor([[1, 2],[3, 4]])
torch.full_like(t1, 2) # 根据t1形状,填充数值2
tensor([2, 2])
torch.randint_like(t2, 1, 10)
tensor([[4, 8],[5, 8]])
torch.zeros_like(t1)
tensor([0, 0])
Point:
- 更多
_like
函数,可查阅帮助文档; - 需要注意一点的是,
_like
类型转化需要注意转化前后数据类型一致的问题;
torch.randn_like(t1) # t1是整数,而转化后将变为浮点数,此时代码将报错
---------------------------------------------------------------------------RuntimeError Traceback (most recent call last)<ipython-input-285-82e06104e8a8> in <module>
----> 1 torch.randn_like(t1)RuntimeError: "normal_kernel_cpu" not implemented for 'Long'
t10 = torch.tensor([1.1, 2.2]) # 重新生成一个新的浮点型张量
t10
tensor([1.1000, 2.2000])
torch.randn_like(t10) # 即可执行相应的填充转化
tensor([1.0909, 0.0379])
三、张量(Tensor)和其他相关类型之间的转化方法
??张量、数组和列表是较为相似的三种类型对象,在实际操作过程中,经常会涉及三种对象的相互转化。在此前张量的创建过程中,我们看到torch.tensor函数可以直接将数组或者列表转化为张量,而我们也可以将张量转化为数组或者列表。另外,前文介绍了0维张量的概念,此处也将进一步给出零维张量和数值对象的转化方法。
- .numpy方法:张量转化为数组
t1
tensor([ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
t1.numpy()
array([ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], dtype=int64)
# 当然,也可以通过np.array函数直接转化为array
np.array(t1)
array([ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], dtype=int64)
- .tolist方法:张量转化为列表
t1.tolist()
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
- list函数:张量转化为列表
list(t1)
[tensor(1),tensor(2),tensor(3),tensor(4),tensor(5),tensor(6),tensor(7),tensor(8),tensor(9),tensor(10)]
需要注意的是,此时转化的列表是由一个个零维张量构成的列表,而非张量的数值组成的列表。
- .item()方法:转化为数值
在很多情况下,我们需要将最终计算的结果张量转化为单独的数值进行输出,此时需要使用.item方法来执行。
n = torch.tensor(1)
n
tensor(1)
n.item()
1
四、张量的深拷贝
??Python中其他对象类型一样,等号赋值操作实际上是浅拷贝,需要进行深拷贝,则需要使用clone方法
t1
tensor([ 1, 10, 3, 4, 5, 6, 7, 8, 9, 10])
t11 = t1 # t11是t1的浅拷贝
t11
tensor([ 1, 10, 3, 4, 5, 6, 7, 8, 9, 10])
t1[1]
tensor(2)
t1[1] = 10 # t1修改
t1
tensor([ 1, 10, 3, 4, 5, 6, 7, 8, 9, 10])
t11 # t11会同步修改
tensor([ 1, 10, 3, 4, 5, 6, 7, 8, 9, 10])
此处t1和t11二者指向相同的对象。而要使得t11不随t1对象改变而改变,则需要对t11进行深拷贝,从而使得t11单独拥有一份对象。
t11 = t1.clone()
t1
tensor([ 1, 10, 3, 4, 5, 6, 7, 8, 9, 10])
t11
tensor([ 1, 10, 3, 4, 5, 6, 7, 8, 9, 10])
t1[0]
tensor(1)
t1[0] = 100
t1
tensor([100, 10, 3, 4, 5, 6, 7, 8, 9, 10])
t11
tensor([ 1, 10, 3, 4, 5, 6, 7, 8, 9, 10])
本节练习答案:
A1
t3
tensor([[[1, 2, 2],[3, 4, 4]],[[5, 6, 6],[7, 8, 8]]], dtype=torch.int32)
t3.reshape(t3.numel())
tensor([1, 2, 2, 3, 4, 4, 5, 6, 6, 7, 8, 8], dtype=torch.int32)