文章目录
- pytorch
- 特点
- 基本概念
- 项目
- 项目实现
- 导入所需库
- 下载训练数据和测试数据
- 对训练和测试样本进行分批次
- 展示手写图片
- 判断pytorch是否支持GPU
- 定义神经网络模型
- 定义训练函数
- 定义测试函数
- 创建交叉熵损失函数和优化器
- 通过多轮训练降低损失值得到最终结果
- 注意
pytorch
PyTorch 是一个开源的深度学习框架,由 Facebook 的人工智能研究团队开发。它在学术界和工业界都得到了广泛的应用,下面从多个方面详细介绍:
特点
- 动态计算图:与 TensorFlow 的静态计算图不同,PyTorch 使用动态计算图。这意味着在运行时可以动态地改变计算图的结构,使得代码的编写和调试更加直观和灵活。例如,在训练循环中可以根据不同的条件来改变计算流程。
- Python 优先:PyTorch 深度集成 Python,代码风格简洁易懂,易于上手。开发者可以利用 Python 丰富的库和工具进行数据处理、可视化等操作。
- 强大的 GPU 支持:PyTorch 能够充分利用 NVIDIA GPU 的并行计算能力,通过简单的代码就可以将张量和模型转移到 GPU 上进行加速计算,大大提高了训练和推理的速度。
- 丰富的工具和库:提供了许多高级工具和库,如 Torchvision(用于计算机视觉任务)、Torchaudio(用于音频处理任务)等,方便开发者快速搭建和训练模型。
基本概念
-
一、张量(Tensor):类似于 NumPy 的多维数组,但可以在 GPU 上运行以加速计算。例如,创建一个简单的张量
import torch# 创建一个2x3的随机张量 x = torch.rand(2, 3) print(x)
-
二、自动求导(Autograd):PyTorch 的自动求导机制可以自动计算张量的梯度,这对于训练神经网络非常重要。在定义张量时,只需要设置requires_grad=True,PyTorch 就会跟踪所有与之相关的操作,并在需要时计算梯度。
import torch # 创建一个需要计算梯度的张量 x = torch.tensor([2.0], requires_grad=True) y = x**2 # 计算梯度 y.backward() print(x.grad) # 输出导数 2x,即 4
-
三、模块(Module):torch.nn.Module是所有神经网络模块的基类。通过继承Module类,可以方便地定义自己的神经网络模型。
import torch
import torch.nn as nn# 定义一个简单的全连接神经网络
class SimpleNet(nn.Module):def __init__(self):super(SimpleNet, self).__init__()self.fc = nn.Linear(10, 1)def forward(self, x):return self.fc(x)# 创建模型实例
model = SimpleNet()
- 四、优化器(Optimizer)
PyTorch 提供了多种优化器,如 SGD、Adam、RMSprop 等,用于更新模型的参数。优化器根据计算得到的梯度来调整模型的参数,以最小化损失函数。
项目
下面我们使用BP神经网络来实现手写数字识别项目,此项目数据集来自MNIST 数据集由美国国家标准与技术研究所(NIST)整理而成,包含手写数字的图像,主要用于数字识别的训练和测试。该数据集被分为两部分:训练集和测试集。训练集包含 60,000 张图像,用于模型的学习和训练;测试集包含 10,000 张图像,用于评估训练好的模型在未见过的数据上的性能。
- 图像格式:数据集中的图像是灰度图像,即每个像素只有一个值表示其亮度,取值范围通常为 0(黑色)到 255(白色)。
- 图像尺寸:每张图像的尺寸为 28x28 像素,总共有 784 个像素点。
- 标签信息:每个图像都有一个对应的标签,标签是 0 到 9 之间的整数,表示图像中手写数字的值
项目实现
导入所需库
import torch
from torch import nn #导入神经网络模块
from torch.utils.data import DataLoader # 数据包管理工具,打包数据
from torchvision import datasets # 封装了很对与图像相关的模型,数据集
from torchvision.transforms import ToTensor # 数据转换,张量,将其他类型的数据转换成tensor张量
下载训练数据和测试数据
'''下载训练数据集(包含训练集图片+标签)'''
training_data = datasets.MNIST( # 跳转到函数的内部源代码,pycharm 按下ctrl+鼠标点击root='data', # 表示下载的手写数字 到哪个路径。60000train=True, # 读取下载后的数据中的数据集download=True, # 如果你之前已经下载过了,就不用再下载了transform=ToTensor(), # 张量,图片是不能直接传入神经网络模型# 对于pytorch库能够识别的数据一般是tensor张量
)'''下载测试数据集(包含训练图片+标签)'''
test_data = datasets.MNIST(root='data',train=False,download=True,transform=ToTensor(),# Tensor是在深度学习中提出并广泛应用的数据类型,它与深度学习框架(如pytorch,TensorFlow)
)# numpy数组只能在cpu上运行。Tensor可以在GPU上运行,这在深度学习应用中可以显著提高计算速度。
print(len(training_data))
print(len(test_data))
训练样本和测试样本的数量
对训练和测试样本进行分批次
# 创建训练数据的 DataLoader 对象
# DataLoader 是 PyTorch 中用于批量加载数据的实用工具类,它可以帮助我们更高效地处理大规模数据集。
train_dataloader = DataLoader(training_data, batch_size=64) # 建议用2的指数当作一个包的数量
test_dataloader = DataLoader(test_data, batch_size=64)
展示手写图片
'''展示手写体图片,把训练数据集中的59000张图片展示一下'''from matplotlib import pyplot as plt
figure = plt.figure()
for i in range(9):img,label = training_data[i+59000] # 提取第59000张图片figure.add_subplot(3,3,i+1) # 图像窗口中创建多个小窗口,小窗口用于显示图片plt.title(label)plt.axis('off') # plt.show(I) # 显示矢量plt.imshow(img.squeeze(),cmap='gray') # plt.imshow()将numpy数组data中的数据显示为图像,并在图形窗口显示a = img.squeeze() # img.squeeze()从张量img中去掉维度为1的。如果该维度的大小不为1则张量不会改变。
plt.show()
判断pytorch是否支持GPU
'''判断是否支持GPU'''
device = 'cuda' if torch.cuda.is_available() else 'mps' if torch.backends.mps.is_available() else 'cpu'
print(f'Using {device} device')
如果支持GPU输出cuda,map系统则输出map,使用CPU则输出CPU
定义神经网络模型
'''定义神经网络 类的继承这种方式'''
class NeuralNetwork(nn.Module): # 通过调用类的形式来使用神经网络,神经网络模型nn.moduledef __init__(self): # self类自己本身super().__init__() # 继承的父类初始化self.flatten = nn.Flatten() # 展开,创建一个展开对象flattenself.hidden1 = nn.Linear(28*28,128)self.hidden2 = nn.Linear(128,256)self.out = nn.Linear(256,10)def forward(self,x): # 向前传播,数据的流向x = self.flatten(x) # 图像展开x = self.hidden1(x)x = torch.sigmoid(x)x = self.hidden2(x)x = torch.sigmoid(x) # 激活函数x = self.out(x)return x
model = NeuralNetwork().to(device)
print(model)
定义训练函数
def train(dataloader,model,loss_fn,optimizer):model.train() # 告诉模型,要开始训练,模型中w进行随机化操作,已经更新w,在训练过程中w会被修改# pytorch提供两种方式来切换训练和测试的模式,分别是:model.train()和model.eval()# 一般用法:在训练之前写model.train(),在测试时写model.eval()batch_size_num = 1for x,y in dataloader: # 其中batch为每一个数据的编号x,y=x.to(device),y.to(device) # 将训练数据和标签传入gpupred = model.forward(x) # .forward可以被省略,父类中已经对次功能进行了设置。自动初始化w权值loss = loss_fn(pred,y) # 通过交叉熵损失函数计算损失值loss# Backpropagation 进来个batch的数据,计算一次梯度,更新一次网络optimizer.zero_grad() #梯度值清零loss.backward() # 反向传播计算每一个参数的梯度值woptimizer.step() # 根据梯度更新网络w参数loss_value = loss.item() # 从tensor数据中提取数据出来,tensor获取损失值if batch_size_num % 100 == 0:print(f'loss:{loss_value:7f} [number:{batch_size_num}]' )batch_size_num += 1
定义测试函数
def test(dataloader,model,loss_fn):size = len(dataloader.dataset) # 10000num_batches = len(dataloader) # 打包的数据model.eval() # 测试,w就不能再更新test_loss,correct = 0,0with torch.no_grad(): # 一个上下文管理器,关闭梯度计算。当你确定不会调用Tensor.backward()的时候。这可以减少计算内存for x,y in dataloader:x,y = x.to(device),y.to(device)pred = model.forward(x)test_loss += loss_fn(pred,y).item()#test_loss是会自动累加每一个批次的损失值correct +=(pred.argmax(1) == y).type(torch.float).sum().item()a = (pred.argmax(1) == y)#dim=1表示每一行中的最大值对应的索引号,dim=0表示每一列中的最大值对应的索引号b = (pred.argmax(1) == y).type(torch.float)test_loss /=num_batches#能来衡量模型测试的好坏。correct /= size#平均的正确率print(f'Test result: \n Accuracy:{(100*correct)}%,Avg loss:{test_loss}')
创建交叉熵损失函数和优化器
loss_fn = nn.CrossEntropyLoss()#创建交叉熵损失函数对象,因为手写字识别中一共有10个数字,输出会有10个结果
#一会改成adam优化器
optimizer = torch.optim.Adam(model.parameters(),lr=0.001)#创建一个优化器,SGD为随机梯度下降算法
#params:要训练的参数,一般我们传入的都是model.parameters()
# #lr:learning_rate学习率,也就是步长。
通过多轮训练降低损失值得到最终结果
epochs = 20
for t in range(epochs):print(f'epoch{t+1}\n--------------------')train(train_dataloader,model, loss_fn, optimizer)
print('Done!')
test(test_dataloader,model, loss_fn)
可通过结果看出在使用ADam优化器步长为0.001时,训练20轮得到的正确率为97.5%,损失值为0.119。
注意
- 可以通过修改优化器来提高准确率
- 对于不同的神经网络要使用不同的激活函数,对于sigmoid函数来说隐藏层过多时会产生梯度消失,因为sigmoid函数的偏导在0~0.25之间随着反向传播的进行,梯度会不断累乘。即使初始梯度较大,但经过多层的累乘后,梯度值会迅速变小,趋近于 0,从而导致梯度消失。因此可用ReLU、tanh、P-ReLU、R-ReLU、maxout等来代替sigmoid函数。ReLU函数偏导为1,不会产生梯度消失问题。
- 当梯度在传递过程中不断增大,变得非常大时,就会导致梯度爆炸现象。此时,模型参数会因为梯度值过大而发生大幅度的更新,使得模型无法收敛,甚至可能导致数值溢出,使得训练过程崩溃。