目录
简述
我们有时需要把训练好的模型部署到很多不同的设备。在这种情况下,我们可以把内存中训练好的模型参数存储在硬盘上供后续读取使用。
我们想要保存训练好的模型,等需要用来进行图像分类等任务的时候,不经训练,直接加载使用。
这时,可以采用torch.save(model, 'best.pt')保存模型。
代码实现
代码所需文件请点击此处免费下载
接下来用一个小案例来解释模型的读取
导入所需要的库
import torch #这行代码引入了PyTorch库,可以用于张量计算和神经网络的构建。
#从PyTorch库中引入了Dataset和DataLoader类。Dataset类是用于加载和预处理数据的,而DataLoader则是用于批量加载和打散数据
from torch.utils.data import Dataset,DataLoader
import numpy as np #用于处理数组
from PIL import Image #引入了Pillow库中的Image模块,可以用于处理图像
from torchvision import transforms #这个模块包含了很多用于图像预处理的函数,例如裁剪、旋转、翻转等
数据预处理
data_transforms = {
'train': #训练集
transforms.Compose([ #transforms.Compose对象的列表,该列表包含一系列的图像转换操作。
transforms.Resize([300,300]), #是图像变换大小
transforms.RandomRotation(45),#随机旋转,-45到45度之间随机选
transforms.CenterCrop(256),#从中心开始裁剪
transforms.RandomHorizontalFlip(p=0.5),#随机水平翻转 选择一个概率概率
transforms.RandomVerticalFlip(p=0.5),#随机垂直翻转
# transforms.ColorJitter(brightness=0.2, contrast=0.1, saturation=0.1, hue=0.1),#参数1为亮度,参数2为对比度,参数3为饱和度,参数4为色相
transforms.RandomGrayscale(p=0.1),#概率转换成灰度率,3通道就是R=G=B
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])#归一化,均值,标准差
]),
'valid': #测试集
transforms.Compose([
transforms.Resize([256,256]),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
]),
}
加载图像数据和标签
class food_dataset(Dataset): #food_dataset是自己创建的类名称,可以改为你需要的名称
##file_path是包含图像文件路径和标签的文本文件的路径,transform是一个可选的图像转换操作,默认为None
def __init__(self, file_path,transform=None):
self.file_path = file_path
self.imgs = [] #初始化两个空列表,用于存储图像文件路径和对应的标签
self.labels = []
self.transform = transform # 将传入的transform参数存储在类的实例变量transform中
with open(self.file_path) as f:
#读取文件的每一行,去除首尾空格并使用空格分割,形成一个列表。
samples = [x.strip().split(' ') for x in f.readlines()]
#遍历列表中的每个元素,其中img_path是图像文件的路径,label是对应的标签
for img_path, label in samples:
#将图像文件路径添加到self.imgs列表中
self.imgs.append(img_path)
#将标签添加到self.labels列表中
self.labels.append(label)
def __len__(self): #类实例化对象后,可以使用len函数测量对象的个数
return len(self.imgs)
def __getitem__(self, idx): #关键,可通过索引的形式获取每一个图片数据及标签
image = Image.open(self.imgs[idx]) #使用PIL库的Image.open方法打开指定路径的图像文件
if self.transform:
image = self.transform(image)
label = self.labels[idx] #获取与图像对应的标签
#将标签转换为NumPy数组,然后使用torch.from_numpy将其转换为PyTorch张量,并指定数据类型为torch.int64。
label = torch.from_numpy(np.array(label,dtype = np.int64))
return image, label #返回图像和标签的元组
#传入训练数据文件的路径和指定的图像转换操作
training_data = food_dataset(file_path = './train.txt',transform = data_transforms['train'])
#用于测试数据
test_data = food_dataset(file_path = './test.txt',transform = data_transforms['valid'])
GPU的检测
目的:GPU检测的目的是充分利用可用的硬件资源,提高计算性能,并确保代码在不同环境中的稳定运行。这对于深度学习等需要大量计算的应用尤为重要。
device = "cuda" if torch.cuda.is_available() else "mps" if torch.backends.mps.is_available() else "cpu"
print(f"Using {device} device"
运行结果:
定义卷积神经网络
from torch import nn
class CNN(nn.Module):
def __init__(self): # 输入大小 (3, 256, 256)
super(CNN, self).__init__()
self.conv1 = nn.Sequential( #将多个层组合成一起。
nn.Conv2d( #2d一般用于图像,3d用于视频数据(多一个时间维度),1d一般用于结构化的序列数据
in_channels=3, # 图像通道个数,1表示灰度图(确定了卷积核 组中的个数),
out_channels=16,# 要得到几多少个特征图,卷积核的个数
kernel_size=5, # 卷积核大小,5*5
stride=1, # 步长
padding=2, # 一般希望卷积核处理后的结果大小与处理前的数据大小相同,效果会比较好。那padding改如何设计呢?建议stride为1,kernel_size = 2*padding+1
), # 输出的特征图为 (16, 256, 256)
nn.ReLU(), # relu层
nn.MaxPool2d(kernel_size=2), # 进行池化操作(2x2 区域), 输出结果为: (16, 128, 128)
)
self.conv2 = nn.Sequential( #输入 (16, 128, 128)
nn.Conv2d(16, 32, 5, 1, 2), # 输出 (32, 128, 128)
nn.ReLU(), # relu层
nn.Conv2d(32, 32, 5, 1, 2), # 输出 (32, 128, 128)
nn.ReLU(),
nn.MaxPool2d(2), # 输出 (32, 64, 64)
)
self.conv3 = nn.Sequential( #输入 (32, 64, 64)
nn.Conv2d(32, 64, 5, 1, 2),
nn.ReLU(), # 输出 (64, 64, 64)
)
self.out = nn.Linear(64 * 64 * 64, 20) # 全连接层得到的结果
def forward(self, x):
x = self.conv1(x)
x = self.conv2(x)
x = self.conv3(x)# 输出 (64,64, 32, 32)
x = x.view(x.size(0), -1) # flatten操作,结果为:(batch_size, 64 * 32 * 32)
output = self.out(x)
return output
model = CNN().to(device)
loss_fn = nn.CrossEntropyLoss() #创建交叉熵损失函数对象,因为手写字识别中一共有10个数字,输出会有10个结果
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)#创建一个优化器Adam
训练深度学习模型
def train(dataloader,model,optimizer,loss_fn):
model.train()
for x,y in dataloader: #x,y 分别是图像和标签
x,y = x.to(device),y.to(device) #将数据放入GPU,提高训练结果
pred = model.forward(x) #对x进行前向传播,得到训练模型的预测结果
loss = loss_fn(pred,y) #使用损失函数来计算预测值pred 和 真实标签y 之间的损失
optimizer.zero_grad() # PyTorch 优化器的方法,用于将模型参数的梯度归零,以准备进行新一轮的反向传播。
loss.backward() #执行反向传播,计算模型参数相对于损失的梯度
optimizer.step() #PyTorch 优化器的方法,用于根据梯度更新模型参数,以减小损失
测试模型的性能
best_acc = 0
#dataloader(用于提供测试数据批次的数据加载器)、model(深度学习模型),和 loss_fn(损失函数,用于计算模型的预测与真实标签之间的损失)
def test(dataloader, model, loss_fn):
global best_acc #全局变量 best_acc
size = len(dataloader.dataset) #获取测试数据集的大小,用于计算准确率
num_batches = len(dataloader) #获取数据加载器中批次的数量,用于计算平均损失
model.eval() #将模型设置为评估模式。在评估模式下,模型不会进行训练
test_loss, correct = 0, 0 #初始化测试损失和正确分类的样本数
with torch.no_grad(): #一个上下文管理器,关闭梯度计算。当你确认不会调用Tensor.backward()的时候。这可以减少计算所用内存消耗。
for X, y in dataloader: # 循环,用于迭代测试数据集中的批次。X 表示图像数据,y 表示标签
X, y = X.to(device), y.to(device)
pred = model.forward(X) #执行前向传播,将输入数据 X 通过深度学习模型 model,并得到模型的预测结果 pred
test_loss += loss_fn(pred, y).item() #计算模型的预测结果 pred 和真实标签 y 之间的损失,并将其累加到 test_loss 变量中
#使用 argmax 函数找到最大概率的类别)和真实标签 y,然后将匹配的结果转换为浮点数类型,累加到 correct 变量中
correct += (pred.argmax(1) == y).type(torch.float).sum().item()
test_loss /= num_batches #计算平均测试损失,将总测试损失除以批次的数量
correct /= size #计算准确率,将正确分类的样本数除以测试数据集的总大小
#印测试结果,包括准确率和平均损失
print(f"Test result: \n Accuracy: {(100*correct)}%, Avg loss: {test_loss}")
acc_s.append(correct)
loss_s.append(test_loss)
保存最优模型的2种方
(模型的文件扩展名一般为:pt)
方法一:
if correct > best_acc:
best_acc = correct
# (w, b) ) 通常用来表示模型的参数,具体指模型的权重 ( w ) 和偏置 ( b )。
# 1、保存模型参数方法:torch.save(model.state_dict(), path)(w,b)
print(model.state_dict().keys()) # 输出模型参数名称
torch.save(model.state_dict(), "model_parameter.pt") #保存模型的参数到名为 "model_parameter.pt" 的文件中
方法二:
# 2、保存完整模型(w,b,模型cnn),
torch.save(model, 'best.pt')
提取模型的2种方法
这里我们不做深度的解析,(详解请点击此处)
acc_s = []
loss_s = []
# 提取模型的2种方法:
# 1、读取参数的方法
# model = CNN().to(device)
# model.load_state_dict(torch.load("model_parameter.pt"))
# 2、读取完整模型的方法,无需提前创建model
model = torch.load('best.pt')
执行测试
model.eval() #固定模型参数和数据,防止后面被修改
test_dataloader = DataLoader(test_data, batch_size=64, shuffle=True)
test(test_dataloader, model, loss_fn) #执行测试
输出结果: