张量
先行操作:导入相关包
import torch
import numpy as np
张量的初始化
使用列表和numpy数组对张量进行初始化
data=[[1,2],[3,4]] #list 内有两个元素,每个元素是一个内含两个元素的列表
np_array=np.array(data) #numpy数组,一个二维数组 2x2
t=torch.tensor(data) #用列表进行初始化
tensor_from_np=torch.from_numpy(np_array) #用numpy数组初始化张量
#类型转换
np_from_tensor = t.numpy() # 将Tensor对象转换为NumPy数组
listdata = tensor_from_np.tolist() # 将Tensor对象转换为list数组
print(t, '\n', np_from_tensor, '\n', listdata)
张量间进行初始化
tensor_data = t.clone() # 返回与t值相同的tensor,新对象存储在新的内存中
new_data = t.detach() # 返回与t完全相同的tensor,新对象与旧对象t共享内存
ones_data = torch.ones_like(t) # 和t形状一致的全1张量
zeros_data = torch.zeros_like(t) # 和t形状一致的全0张量
rand_data = torch.rand_like(t, dtype=torch.float) # 和t形状一致的随机浮点数张量
张量的属性
张量的属性描述了其形状、数据类型、存储设备及在内存中的存储形式等信息。
tensor = torch.rand(3,4) # 3x4的随机tensor
print(f"张量形状: {tensor.shape}")
print(f"张量数据类型: {tensor.dtype}")
print(f"张量存储设备: {tensor.device}")
Tensor也可存储的GPU上进行加速运算,例如:
device = "cuda" if torch.cuda.is_available() else "cpu" #判断是否可用CUDA加速运算
print(device)
tensor = tensor.to(device) #将Tensor放到GPU上
print(f"张量存储设备: {tensor.device}")
张量的索引和切片
张量的索引和切片类似NumPy的索引和切片。示例如下:
tensor = torch.randint(1,100,(4, 4)) #4行4列,各元素值为[1,100)区间的随机数
print(f"第一行: {tensor[0]}")
print(f"第一列: {tensor[:, 0]}")
print(f"最后一列: {tensor[..., -1]}") # ...和:是一样的
tensor[:, 1] = 0 # 第二列置为0
print(tensor)
张量间的连接
tensor1 = torch.randint(1,100,(4, 4))
tensor2 = torch.rand(3,4)
tensor3 = torch.rand(4,2)
t1 = torch.cat([tensor1, tensor2], dim=0) # 纵向连接
t2 = torch.cat((tensor1, tensor3), dim=1) # 横向连接,用()和[]均可
print(tensor1.shape, tensor2.shape, tensor3.shape, t1.shape, t2.shape)
torch.Size([4, 4]) torch.Size([3, 4]) torch.Size([4, 2]) torch.Size([7, 4]) torch.Size([4, 6])
除了torch.cat函数外,torch.stack函数也可以进行连接,不过各个被链接的张量的各个维度值都应一致,返回的新张量维度会多增加一维。示例如下:
tensor1 = torch.randint(1,100,(2, 4))
tensor2 = torch.zeros(2,4)
tensor3 = torch.ones(2,4)
t1 = torch.stack([tensor1, tensor2, tensor3],dim=0) #dim=0:在维度0处进行拼接
t2 = torch.stack((tensor1, tensor2, tensor3),dim=1)
t3 = torch.stack((tensor1, tensor2, tensor3),dim=2)
print(tensor1.shape, tensor2.shape, tensor3.shape, t1.shape, t2.shape, t3.shape)
t1,t2,t3
t1,t2,t3张量均从二维张量转变成三维张量
张量的运算
张量作为矩阵,可以进行加、减、乘、转置、按元素乘、按元素除等操作。
张量的加法运算
tensor1 = torch.randint(1,100,(2, 4))
tensor2 = torch.ones(2,4)
t_add1 = tensor1 + tensor2 # 张量加法,本质上是按元素相加
t_add2 = tensor1.add(tensor2) # 与上面的操作是一致的
t_add3 = torch.add(tensor1, tensor2) # 与上面的操作是一致的
t_add4 = tensor1 + 3 # 张量所有元素都加3,得到新的张量,原张量未改变
t_add5 = tensor1.add(3) # 与上面的操作是一致的
t_add6 = torch.add(tensor1, 3) # 与上面的操作是一致的
t_add7 = tensor1.add_(3) # 张量所有元素都加3,原张量tensor1也被修改。
张量的减法运算
t_sub1 = tensor1 - tensor2 # 张量减法
t_sub2 = tensor1.sub(tensor2) # 与上面的操作是一致的
t_sub4 = 1 - tensor2 # 张量所有元素都被1减
t_sub6 = torch.sub(tensor1, 1) # 张量所有元素都减1
张量的乘法(矩阵间的乘法,结果仍然是矩阵)
#张量乘法,参与运算的张量类型需一致
t_matmul1 = tensor1.float() @ tensor2.T # T属性表示张量转置,按单精度浮点数(32bit)进行相乘
t_matmul2 = tensor1.matmul(tensor2.T.long()) # 与上面的操作不同,两张量按长整型(64bit)进行相乘
按元素相乘/除
#按元素相乘,参加运算的张量形状应一致
t_mul1 = tensor1 * tensor2
t_mul3 = torch.mul(tensor1, tensor2) # 与上面的操作是一致的
t_mul4 = tensor1 * 3 # 张量每元素都乘以3
t_mul5 = tensor1.mul(3) # 与上面的操作是一致的
#按元素相除,参加运算的张量形状应一致
t_div1 = tensor1 / tensor1
t_div2 = tensor1.div(tensor2)
t_div3 = torch.div(tensor1, tensor2) # 与上面的操作是一致的
pytorch中内置的函数,如dot,mm
t1=torch.randn((5)) # 一维张量
t2=torch.ones((5)) # 一维张量
t3=torch.randn((2,5)) # 二维张量
t4=torch.ones((5,2)) # 二维张量
t_d1 = torch.dot(t1, t2) # dot函数仅支持两个一维向量的点集
t_d2 = torch.matmul(t1, t2) # 与上面的操作是一致的
print(t_d1 == t_d2) # 观察两个结果是否相同
t_m1 = torch.mm(t3, t4) # mm函数仅支持两个二维张量的相乘
t_m2 = torch.matmul(t3, t4) # 与上面的操作一致
print(t_m1 == t_m2) # 观察两个结果是否相同
t_n1 = torch.matmul(t3, t1) # 二维张量和一维张量相乘,一维张量自动维度扩展, 结果会删掉扩展维度
print(t_n1.shape) # 打印计算结果形状
t_n2 = torch.matmul(t3, t1.view(5,1)).T # 手动扩展进行计算,与上面的操作结果一致
print(t_n1 == t_n2) # 观察两个结果是否相同
若要取张量中的某个元素,变为普通的数据类型进行运算,可使用item()函数。
tensor = torch.randperm(10)
sum = tensor.sum()
sum_item = sum.item()
print(tensor)
print(sum, type(sum))
print(sum_item, type(sum_item))
张量的其他运算
PyTorch提供的张量运算函数是非常丰富的,如幂运算、指数运算(以e为底数)、对数运算、近似运算(如取整)、统计运算(如取平均值)等等,在此不再给出示例,只列举一些常用的函数,读者可自行练习这些函数的使用方法。
Autograd自动求导
神经网络模型训练时,最常用的是BP算法,即反向传播算法。模型参数根据梯度进行学习,为了计算梯度,PyTorch提供了Autograd支持自动求导来计算梯度。 下面以最简单的一层神经网络为例,给出Autograd进行反向传播优化参数的示例。例子中,设x和y是真实数据,z为模型预测值,设z = w * x + b,则w和b是模型需要优化的参数,通过loss损失可根据梯度下降法来优化w和b。
import torch
from torch.nn.functional import binary_cross_entropy_with_logits
x = torch.ones(5) # 输入x设为[1.,1.,1.,1.,1.]
# print('x',x.shape)
y = torch.zeros(3) # 输出y设为[0.,0.,0.]
w = torch.randn(5, 3, requires_grad=True) # w形状为[5, 3],梯度计算设为True
# print('w',w.shape)
b = torch.randn(3, requires_grad=True) # b形状为[1, 3],梯度计算设为True
z = torch.matmul(x, w) + b # z = w * x + b
loss = binary_cross_entropy_with_logits(z, y) # 计算损失根据loss来优化网络参数
loss.backward() # 损失反向传播进行自动求导,得到参数梯度
print(w.grad) # 输出w的梯度,存储在w的grad属性中,grad属性也是张量
print(b.grad) # 输出b的梯度
loss.backward() 返回的是 None。它的作用是进行反向传播计算梯度,并将计算得到的参数梯度存储在对应的张量的 grad 属性中。具体来说,w.grad 和 b.grad 分别表示损失函数关于权重矩阵 w 和偏置向量 b 的梯度值。
当调用 loss.backward() 方法时,PyTorch 会自动计算每个参数的梯度,并将其存储在对应的 .grad 属性中。需要注意的是,每次调用 backward() 方法时,计算得到的梯度会累加到之前存储的梯度上,因此如果需要每次单独计算梯度,需要通过 zero_grad() 方法将之前的梯度清零。
需要注意的是,在调用 backward() 方法之前,必须保证相关的张量都设置了 requires_grad=True,才能够计算出相应张量的梯度。
参数梯度算法的意义
参数梯度是指在机器学习算法中,用于调整模型参数的一种计算方法。在训练模型时,我们通常需要通过最小化损失函数来优化模型的参数。而参数梯度就是指损失函数对于模型参数的偏导数(即梯度)。
通过计算参数梯度,我们可以确定当前参数取值下,损失函数下降最快的方向,并据此更新参数,使得损失函数的值逐渐减小,从而使模型更加拟合训练数据。在深度学习中,参数梯度通常使用反向传播算法来计算。
损失函数下降最快的方向即在某一个变量上的偏导数是最大的。即该变量大小变化一点,相较于其他变量,损失函数的变化最大。
停止梯度跟踪
默认情况下,所有张量的requires_grad属性被设置为True,表示都在跟踪梯度历史,但是有些情况下并不需要跟踪梯度,比如测试集上的评估就不需要反向传播来跟踪梯度,可用以下代码来停止对计算结果的梯度跟踪,
即无需调用bp函数,即使有输入也不更改权值
第一种方法with torch.no_grad()
z = torch.matmul(x, w)+b # z = w * x + b
print(z.requires_grad) #True
with torch.no_grad(): # 禁用梯度跟踪
z = torch.matmul(x, w)+b
print(z.requires_grad) #False
第二种方法
z = torch.matmul(x, w)+b
z_det = z.detach()
print(z_det.requires_grad)