网站宣传页面职业培训学校加盟
Pytorch最基本的操作对象——张量(tensor),张量是Pytorch中重要的数据结构,可认为是一个高维数组。一般的,标量(scalar)是只有大小没有方向的量,如1、2、3等;向量(vector)是有大小和方向的量,如[1,2,3];矩阵(matrix)是由多个向量组成的,如[[1,2,3],[4,5,6]]。张量是基于向量和矩阵的推广,可以将标量视为零阶张量,向量可以视为一阶张量,矩阵是二阶张量。张量是支持高效的科学计算的数组,它可以是一个数(标量)、一维数组(向量)、二维数组(矩阵)和更高维的数组(高阶数据)。
1.1 PyTorch张量
PyTorch最基本的操作对象是张量,表示一个多维数组,张量类似NumPy的数组(ndarray),与ndarray不同的是,张量可以在GPU使用以加速计算。张量和NumPy的数组通常可以共享相同的低层内存,无须复制数据。
演示张量的创建和运算,首先导入PyTorch和NumPy,代码如下:
import torch
import numpy as np
1.1.1 初始化张量
可以使用多种形式初始化张量,如可直接从Python数据创建张量,无须指定类型,PyTorch会自动推荐其类型,可通过张量的dtype属性查看其类型。
t = torch.tensor([1, 2]) #创建一个张量
print(t) #输出 tensor([1, 2])
print(t.dtype) #输出 torch.int64
代码输出:
tensor([1, 2])
torch.int64
在创建张量时,如果想直接创建为float类型,可使用torch.FloatTensor()方法;如果需要明确的创建为Int类型,可使用torch.LongTensor()方法。这两种类型是PyTorch中使用最多、最常见的两种类型。代码如下:
t = torch.FloatTensor([1, 2])
print(t)
print(t.dtype)
代码输出:
tensor([1., 2.])
torch.float32
======================= 代码分割线 =========================
t = torch.LongTensor([1, 2])
print(t)
print(t.dtype)
代码输出:
tensor([1, 2])
torch.int64
也可以使用torch.from_numpy()方法从NumPy数组ndarray张量。
np_array=np.array([[1,2],[3,4]]) #创建一个ndarray
t_np=torch.from_numpy(np_array) #从ndarray创建张量
t_np
代码输出:
tensor([[1, 2],[3, 4]], dtype=torch.int32)
1.1.2 张量类型
PyTorch 张量的基础数据类型主要包含以下几种:
torch.float32
(或torch.float
):32 位浮动点数,最常用的数据类型,用于表示小数。torch.float64
(或torch.double
):64 位浮动点数,用于需要更高精度的计算。torch.float16
(或torch.half
):16 位浮动点数,常用于减少内存消耗和加速计算,尤其是在GPU上。torch.int8
:8 位整数,用于表示整数值。torch.int16
:16 位整数。torch.int32
:32 位整数,常用整数类型。torch.int64
(或torch.long
):64 位整数,通常用于较大的整数或索引。torch.uint8
:8 位无符号整数,通常用于处理图像数据(如像素值)。torch.bool
:布尔类型,值为True
或False
,用于逻辑运算。
不同数据类型适用于不同的任务和计算需求。在实际使用时,可以根据需求选择合适的张量数据类型,尤其是在性能和内存管理方面。
其中32位浮点型和64位整型是最常用的类型,这两种类型也常常被表示为torch.float和torch.long,也就是说,torch.float32等价于torch.float,torch.int64等价于torch.long,这两种类型正好对应上面的两种创建张量的方法torch.LongTensor()和torch.FloatTensor()。可在构造张量时使用dtype明确其类型。
t = torch.tensor([1,2], dtype=torch.float32)
print(t)
print(t.dtype)
代码输出:
tensor([1., 2.])
torch.float32
======================= 代码分割线 =========================
#也可以使用torch.float作为dtype的参数
t = torch.tensor([1,2], dtype=torch.float)
print(t)
print(t.dtype)
代码输出:
tensor([1., 2.])
torch.float32
======================= 代码分割线 =========================
print(torch.float==torch.float32) #返回True
print(torch.long==torch.int64) #返回True
代码输出:
True
True
以上代码中直接将torch.float与torch.float32做相等判断,判断后发现返回结果为True,说明这两个写法是完全等价的。PyTorch针对torch.float32与torch.int64类型有专门这样的简写形式是因为,这两种类型特别重要,模型的输入类型一般都是torch.float32,而模型分类问题的标签类型一般为torch.int64。
PyTorch中张量的类型可以使用type()方法转换,代码如下。
t = torch.tensor([1,2], dtype=torch.float32)
print(t.dtype)
#type()方法转换类型
t=t.type(torch.int64)
print(t.dtype)
代码输出:
torch.float32
torch.int64
编写代码过程中,由于转换为torch.float32和torh.int64两个类型最常见,框架提供了两个快捷的实例转换方法,即float()方法和long()方法。代码如下。
t = torch.tensor([1,2], dtype=torch.float32)
t=t.long()
print(t.dtype)
t=t.float()
print(t.dtype)
代码输出 :
torch.int64
torch.float32
1.1.3 创建随机值张量
可使用torch.rand()方法创建0~1均匀的随机数,使用torch.randn()方法创建标准正态分布随机数,使用torch.zeros()和torch.ones()方法创建全0和全1的张量,代码如下。
t = torch.rand(2, 3)
print(t)
代码输出:
tensor([[0.7330, 0.2682, 0.0917],[0.8569, 0.2710, 0.8089]])
======================= 代码分割线 =========================
t = torch.rand(2, 3)
print(t)
代码输出:
tensor([[0.5205, 0.9647, 0.5108],[0.7746, 0.4297, 0.5655]])
======================= 代码分割线 =========================
t = torch.zeros(3)
print(t)
代码输出:
tensor([0., 0., 0.])
======================= 代码分割线 =========================
t = torch.ones(3, 2)
print(t)
代码输出:
tensor([[1., 1.],[1., 1.],[1., 1.]])
还可以从另一个张量创建新的张量,除非明确覆盖,否则新的张量保留原来张量的属性(形状、数据类型),如下所示。
x = torch.zeros_like(t) #类似的方法还有torch.ones_like()
print(x)
代码输出:
tensor([[0., 0.],[0., 0.],[0., 0.]])
======================= 代码分割线 =========================
x = torch.rand_like(t)
print(x)
代码输出:
tensor([[0.2793, 0.3023],[0.5073, 0.3336],[0.7823, 0.4880]])
1.1.4 张量属性
tensor.shape属性可返回张量的形状,它与tensor.size()方法等价,后者更灵活,可以通过给定参数返回某一个维度的形状;tensor.dtype属性可返回当前张量的类型。代码如下。
t=torch.ones(2,3,dtype=torch.float64)
print(t.shape) #输出torch.Size([2, 3])
print(t.size()) #输出torch.Size([2, 3])
print(t.size(1)) #输出第一维度大小 3
print(t.dtype) #输出torch.float64
print(t.device) #输出cpu
代码输出:
torch.Size([2, 3])
torch.Size([2, 3])
3
torch.float64
cpu
可通过使用tensor.device属性查看当前张量所在的设备(device)。直接创建的张量都在内存中,所以显示的device是CPU,如果是显存中的张量,则显示为CUDA。
1.1.5 将张量移动到显存
张量可进行算术运算、线性代数、矩阵操作等计算,这些计算可以在CPU上运行,也可在GPU运行,在GPU上的运算速度通常高于CPU。默认情况下是在CPU创建张量。如果有可用的GPU,可以使用tensor.to()方法明确地将张量移动到GPU。代码如下。
#如果GPU可用,将张量移动到显存
if torch.cuda.is_available():t = t.to('cuda')
print(t.device) #如果GPU可用,输出类似device(type='cuda',index=0)
代码输出:
cpu
在编码过程中,一般可用如下代码获取当前可用设备。
#获取当前可用设备
device="cuda" if torch.cuda.is_available() else "cpu"
print("Using {} device".format(device)) #打印当前可用设备
t=t.to(device) #将t放到当前可用设备上
print(t.device) #如果GPU可用,输出类似device(type='cuda',index=0)
代码输出:
Using cpu device
cpu
这样设置要比直接使用t.to(‘cuda’)更稳妥,可以确保代码中无论有无GPU都能正常运行,并且如果GPU可用就用GPU.
1.2 张量运算
张量的运算规则、切片索引规则与NumPy类似,运算中遵循广播原则和同形状相同位置元素对方运算的原则,代码如下。
t1 = torch.randn(2, 3)
t2 = torch.ones(2, 3)
print(t1 + 3) #t1中每一个元素都加3
print(t1 + t2) #t1与t2中每一个相同位置的元素相加
print(t1.add(t2)) #等价于t1+t2,结果与上面相同print(t1) #输出当前t1值
t1.add_(t2) #运行add_这个计算方法,add_方法中的下划线代表就地改变原值
print(t1) #查看运行结果
代码输出:
tensor([[2.8404, 2.7435, 4.1129],[2.7789, 4.4589, 4.5274]])
tensor([[0.8404, 0.7435, 2.1129],[0.7789, 2.4589, 2.5274]])
tensor([[0.8404, 0.7435, 2.1129],[0.7789, 2.4589, 2.5274]])
tensor([[-0.1596, -0.2565, 1.1129],[-0.2211, 1.4589, 1.5274]])
tensor([[0.8404, 0.7435, 2.1129],[0.7789, 2.4589, 2.5274]])
在PyTorch中,如果一个运算方法后面加上下画线,代表就地改变原值,即t1.add_(t2)会直接将运算结果保存为t1,这样做可以节省内存,但是缺点是会直接改变t1原值,因为在使用的时候需要谨慎。
张量可以进行常见的算术运算,如abs(绝对值)、cunsum(累加)、divide(除)、floor_divide(整除)、mean(均值)、min(最小值)、max(最大值)、multiply(乘)等,这里不再一一演示。在深度学习中,矩阵运算常用到转置(tensor.T)和矩阵乘法(matmul或@),代码如下。
print(t1.matmul(t2.T)) #t1与t2的转置进行矩阵乘法
print(t1@(t2.T)) #与上一行运行等价,t1与t2的转置进行矩阵乘法
代码输出:
tensor([[3.6968, 3.6968],[5.7652, 5.7652]])
tensor([[3.6968, 3.6968],[5.7652, 5.7652]])
对于只有一个元素的张量,可以使用tensor.item()方法将其转换为标量,也就是Python的基本类型。
t3 = t1.sum()
print(t3) #t3是只有一个元素的张量
print(t3.item()) #输出一个Python浮点数
代码输出:
tensor(9.4620)
9.461990356445312
以上代码中,首先将张量中所有的元素求和,得到只有一个元素的张量,然后使用tensor.item()方法将其转为标量,这种转换在我们希望打印模型正确率和损失值的时候很常见。
1.2.1 与NumPy数据类型的转换
1.1.1节演示了可使用torch.from_numpy()方法将ndarray转为张量,张量也可以使用tensor.numpy()方法得到它对应的ndarray数组,它们共用相同的内存。
### 与ndarray数据类型的转换import numpy as np
a = np.random.randn(2, 3) #创建一个形状为(2,3)的ndarray
print(a) #输出此ndarray
t = torch.from_numpy(a) #使用此ndarray创建一个张量
print(t)
print(t.numpy()) #使用tensor.numpy()方法获得张量对应的ndarray
代码输出:
[[ 0.22605693 0.04398624 -0.21096077][-0.66368309 1.23512604 2.0399825 ]]
tensor([[ 0.2261, 0.0440, -0.2110],[-0.6637, 1.2351, 2.0400]], dtype=torch.float64)
[[ 0.22605693 0.04398624 -0.21096077][-0.66368309 1.23512604 2.0399825 ]]
1.2.2 张量的变形
tensor.size()方法和tensor.shape属性可以返回张量的形状。当需要改变张量的形状时,可以通过tensor.view()方法,这个方法相当于NunPy中的reshape方法,用于改变张量的形状。需要注意的是,在转换过程要确保元素数量一致。代码如下。
t=torch.randn(4,6) #创建一个形状为(4,6)的张量
print(t.shape) #输出t的形状为torch.Size([4, 6])
t1=t.view(3,8) #调整成(3,8)形状,这里元素数量是相等的
print(t1.shape) #输出t1的形状为torch.Size([3, 8])
代码输出:
torch.Size([4, 6])
torch.Size([3, 8])
======================= 代码分割线 =========================
#现在我们需要将t展平为最后一个维度为1的张量
#第二个参数1代表第二维长度为1,参数-1表示根据元素个数自动计算第一维
t1=t.view(-1,1)
print(t1.shape)
代码输出:
torch.Size([24, 1])
======================= 代码分割线 =========================
# 也可以使用view增加维度,当然元素个数是不变的
t1=t.view(1,4,6) #调整成三维的,其中第一维的长度为1
print(t1.shape)
代码输出:
torch.Size([1, 4, 6])
对于维度长度为1的张量,可以使用torch.squeeze()方法去掉长度为1的维度,相应的也有一 个增加长度为1维度的方法,即torch.unsqueeze(),代码如下。
t2=torch.squeeze(t1)
print(t2.shape)
代码输出:
torch.Size([4, 6])
======================= 代码分割线 =========================
t3=torch.unsqueeze(t2,0) #参数0表示指定在第一个维度上增加维度
print(t3.shape)
代码输出:
torch.Size([1, 4, 6])
1.3 张量的自动微分
在PyTorch中,张量有一个requires_grad属性,可以在创建张量时指定些属性为True。如果requires_grad属性被设置为True,PyTorch将开始跟踪对此张量的所有计算,完成计算后,可以对计算结果调用backward() 方法,PyTorch将自动计算所有梯度。该张量的梯度将累加到.grad属性中。张量的grad_fn属性则指向运算生成此张量的方法。简单的说,张量的requires_grad属性用来明确是否跟踪张量的梯度,grad属性表示计算得到的梯度,grad_fn属性表示运算得到生成此张量的方法。代码演示如下。注意查看代码中的注释。
t = torch.ones(2, 2, requires_grad=True) # 这里设置 requires_grad 为True
print(t.requires_grad) # 输出 True
print(t.grad) # tensor.grad输出张量的梯度,这里输出 None,因为目前 t 没有梯度
print(t.grad_fn) # tensor.grad_fn指向运算生成此张量的方法,这里为 None# 进行张量运算,得到 y
y = t + 5
print(y) # 输出 tensor([[6., 6.],[6., 6.]], grad_fn=<AddBackward0>)
# y由于是运算而创建的,因此grad_fn属性不是空
print(y.grad_fn) # 输出类似<AddBackward0 object at 0x00000237DF1A4250># 进行更多运算
z = y * 2
out = z.mean() #求张量的均值,实例方法
print(out) # 输出 tensor(12., grad_fn=<MeanBackward0>)
代码输出:
True
None
None
tensor([[6., 6.],[6., 6.]], grad_fn=<AddBackward0>)
<AddBackward0 object at 0x000001B460445750>
tensor(12., grad_fn=<MeanBackward0>)
上面的代码中,首先创建了张量,并指定requires_grad属性为True,因为这是一个新创建的张量,它的grad和grad_fn属性均为空。然后经过加法、乘法和取均值运算,得到了out这个最终结果。注意:现在out只有单个元素,它是一个标量值。下面在out上执行自动微分运算,并输出t的梯度(d(out)/d(x)微分运算的结果),代码如下。
out.backward() # 自动微分运算, 注意 out 是标量值
print(t.grad) #输出t的梯度,也就是d(out)/d(x)微分运算的结果
代码输出:
tensor([[0.5000, 0.5000],[0.5000, 0.5000]])
当张量的 requires_grad 属性为 True 时,PyTorch会一直跟踪记录此张量的运算;
print(t.requires_grad) # 输出 True ,PyTorch框架会跟踪此张量的运算
s = t + 2 # 进行运算,得到新的张量 s
print(s.requires_grad) # 输出 True ,PyTorch框架会继续跟踪此张量的运算
代码输出:
True
True
也可使用 tensor.detach() 来获得具有相同内容但不需要跟踪运算的新张量,可以认为获取张量的值,代码如下。
print(out.requires_grad) # 输出 True
s = out.detach() # 获取out的值,也可以使用out.data()方法
print(s.requires_grad) # 输出 False
代码输出:
True
False
可以使用 requires_grad_() 就地改变张量的这个属性,当我们希望模型的参数不再随着训练变化时,可以用此方法,代码如下:
print(t.requires_grad) # 输出 True
t.requires_grad_(False) # 就地改变requires_grad为False
print(t.requires_grad) # 输出 False
代码输出:
True
False
1.4 小结
讲解了张量的创建、PyTorch数据类型、张量运算、张量自动微分等基础知识,这是PyTorch中比较基础的内容。