ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

32.完整的模型验证套路

32.完整的模型验证套路

一、模型验证套路(CIFAR数据集)

如果模型训练好了,那么就可以对外提供实际的应用了。
下面我们要做一个完整的 ‌PyTorch 模型推理(预测)流程‌。它的主要目的是:加载一张图片,使用预先训练好的神经网络模型对该图片进行分类预测,并输出预测结果。

1. 图像读取与预处理

我们的模型是基于CIFAR10数据集去训练的,这个数据集有10个类别的图片,这个模型结果会输出测试图像的分别属于10个类别概率,那么概率最大的类别,就是这个模型推测出来我们测试的图片所属的类别。
所以首先,我们在网上任意挑选一张属于这10个类别的图片,来测试这个模型推测的准确性,是否能正确识别这个图片属于什么类别的图片。

(1)如何查看CIFAR10的类别

如下,我们在红框位置打断点,然后就可以在变量面板里查看class_to_idx的对应的类别到我们的索引。


如上,我们看到,这个模型有如下10种类别,那么我们就任意挑选一张飞机的图片进行测试,看我们的模型输出的10个类别中,最大概率的类别是否是索引为0的位置的概率。

0:airplane1:automobile2:bird3:cat4:deer5:dog6:frog7:horse8:ship9:truck

那么我们从网上找了一张飞机图片,命名为airplane.png

(2)对输入图像做预处理

注意我们之前实现的模型要求的输入图片尺寸如下,是3通道的32 X 32的像素图像:



由于我们的模型是要求输入3通道32 x 32像素的图像的,所以我们需要将我们的飞机图片转化为对应要求的图片格式。
操作步骤:
使用Compose组合多个transform,Resize调整图片尺寸,ToTensor转为张量。
根据官方文档,我们使用Resize,应当传入的参数size 是 (32,32)。

关于convert(‘RGB’)

强制将图片转换为 RGB 模式。png是四个通道,除了RGB三个通道之外,还有一个透明通道。因为后续的卷积层期望输入是 3 个通道(红、绿、蓝),如果图片是灰度(1通道)或带透明通道(4通道),会导致形状不匹配报错。

图像预处理代码

总的来说,我们的图像预处理代码如下:

importtorchimporttorchvisionfromPILimportImagefromtorchimportnn image_path="../imgs/airplane.png"image=Image.open(image_path)print(image)image=image.convert('RGB')transform=torchvision.transforms.Compose([torchvision.transforms.Resize((32,32)),torchvision.transforms.ToTensor()])image=transform(image)print(image.shape)

我们执行如上代码,控制台打印如下,打印出来的图片形状如我们所要求的。是[3,32,32]

2.加载训练好的神经网络模型并且拷贝Tudui类代码

我们在加载训练好的神经网络模型前,需要引入之前训练时定义的Tudui 类的代码。原因如下:
Tudui 类的代码定义了模型的“骨架”或“结构”,而 torch.load加载的是这个骨架里填充的“血肉”(权重参数)。如果没有这个类定义,PyTorch 就不知道如何构建这个模型来容纳加载进来的参数。‌
class Tudui(nn.Module): …‌:定义了模型长什么样(几层卷积、几层线性层等)。这是‌蓝图‌。
‌torch.load(“tudui_29_gpu.pth”)‌:根据蓝图重建模型,并填入训练好的权重。这是‌建造过程‌。

importtorchimporttorchvisionfromPILimportImagefromtorchimportnn image_path="../imgs/airplane.png"image=Image.open(image_path)print(image)image=image.convert('RGB')transform=torchvision.transforms.Compose([torchvision.transforms.Resize((32,32)),torchvision.transforms.ToTensor()])image=transform(image)print(image.shape)classTudui(nn.Module):def__init__(self):super(Tudui,self).__init__()self.model=nn.Sequential(nn.Conv2d(3,32,5,1,2),nn.MaxPool2d(2),nn.Conv2d(32,32,5,1,2),nn.MaxPool2d(2),nn.Conv2d(32,64,5,1,2),nn.MaxPool2d(2),nn.Flatten(),nn.Linear(64*4*4,64),nn.Linear(64,10))defforward(self,x):x=self.model(x)returnx model=torch.load("tudui_29_gpu.pth",weights_only=False,map_location=torch.device('cpu'))print(model)image=torch.reshape(image,(1,3,32,32))
细说torch.load:

代码中没有显式地执行 model = Tudui()。这是因为 torch.load ‌内部‌完成了实例化的过程。
torch.load 读取文件,发现里面存的是一个 Tudui 类的实例。
它在内存中重新创建了一个 Tudui 对象。
它将文件中保存的权重参数填入这个新创建的对象中。
最后将这个填充好参数的对象赋值给变量 model。
所以,Tudui 类的定义是 torch.load 能够成功工作的‌前提条件‌。

关于torch.load中的map_location=torch.device(‘cpu’)参数

当我们没有加上map_location=torch.device(‘cpu’)参数的时候,控制台会报错如下:

raiseRuntimeError(RuntimeError:Attempting to deserializeobjecton a CUDA device but torch.cuda.is_available()isFalse.If you are running on a CPU-only machine,please use torch.loadwithmap_location=torch.device('cpu')tomapyour storages to the CPU.进程已结束,退出代码为1

这个报错的核心含义是:‌设备不匹配‌。
你尝试加载的模型文件(.pth 或 .pt)是在 ‌GPU (CUDA)‌ 环境下训练并保存的,但你当前运行代码的环境‌没有可用的 GPU‌(或者 PyTorch 无法识别到 GPU),导致加载失败。
解决办法如下:
强制加载到 CPU(最推荐,通用性强)
在 torch.load 中添加 map_location 参数,告诉 PyTorch 把原本在 GPU 上的数据全部映射到 CPU 上
所以我们需要加上 map_location=torch.device(‘cpu’)参数

关于torch.reshape(image, (1, 3, 32, 32))

image = torch.reshape(image, (1, 3, 32, 32)) 的核心目的是‌为单张图片添加“批次(Batch)”维度,使其符合模型输入的格式要求‌。在 PyTorch 中,卷积神经网络(如代码中的 Tudui 模型)的第一层是 nn.Conv2d。根据 PyTorch官方文档及最佳实践,Conv2d 层期望的输入张量必须是 ‌4维‌ 的(见如下红框中的输入格式要求)。
image = torch.reshape(image, (1, 3, 32, 32)) 也便是在告诉模型,虽然我只给你一张图,但请把它当作一个包含1个样本的‘批次’来处理。

3.执行模型,预测结果

# -*- coding: utf-8 -*-# 作者:小土堆# 公众号:土堆碎念importtorchimporttorchvisionfromPILimportImagefromtorchimportnn image_path="../imgs/airplane.png"image=Image.open(image_path)print(image)image=image.convert('RGB')transform=torchvision.transforms.Compose([torchvision.transforms.Resize((32,32)),torchvision.transforms.ToTensor()])image=transform(image)print(image.shape)classTudui(nn.Module):def__init__(self):super(Tudui,self).__init__()self.model=nn.Sequential(nn.Conv2d(3,32,5,1,2),nn.MaxPool2d(2),nn.Conv2d(32,32,5,1,2),nn.MaxPool2d(2),nn.Conv2d(32,64,5,1,2),nn.MaxPool2d(2),nn.Flatten(),nn.Linear(64*4*4,64),nn.Linear(64,10))defforward(self,x):x=self.model(x)returnx model=torch.load("tudui_29_gpu.pth",weights_only=False,map_location=torch.device('cpu'))print(model)image=torch.reshape(image,(1,3,32,32))model.eval()withtorch.no_grad():output=model(image)print(output)print(output.argmax(1))
关于model.eval()

关于torch.no_grad()

关于output = model(image)

作用‌: 执行模型的前向传播,得到原始输出结果。
‌结果形态‌:
根据我们tudui模型定义,最后一层是 nn.Linear(64, 10),即创建一个全连接层,它接收长度为 64 的特征向量作为输入,并输出长度为 10 的结果向量,通常用于将提取到的特征映射到 10 个分类类别上。‌所以 output 是一个形状为 (1, 10) 的张量(因为之前 reshape 成了 batch_size=1,输出结果‌是这个(Batch_Size, Num_Classes)‌格式的)。
这 10 个数值被称为 ‌Logits‌(未归一化的预测分数),分别代表图片属于 10 个类别的可能性得分。‌注意:这些数值不是概率,可能有负数,且总和不为 1。‌

关于 print(output.argmax(1))

作用‌: 找出预测概率最大的类别索引。
argmax 意为 “argument of the maximum”,即返回最大值所在的‌位置索引‌,而不是最大值本身。

总结

这段代码完整表达了:告诉模型现在是测试状态(eval),为了省内存别算梯度(no_grad),把图片丢进去跑一遍(forward),然后看看哪个类别的得分最高(argmax),打印出这个类别的编号

完整模型验证代码

# -*- coding: utf-8 -*-# 作者:小土堆# 公众号:土堆碎念importtorchimporttorchvisionfromPILimportImagefromtorchimportnn image_path="../imgs/airplane.png"image=Image.open(image_path)print(image)# 强制将图片转换为 RGB 模式。png是四个通道,除了RGB三个通道之外,还有一个透明通道。因为后续的卷积层期望输入是 3 个通道(红、绿、蓝),如果图片是灰度(1通道)或带透明通道(4通道),会导致形状不匹配报错。image=image.convert('RGB')# 操作步骤:# 使用Compose组合多个transform# Resize调整图片尺寸# ToTensor转为张量transform=torchvision.transforms.Compose([torchvision.transforms.Resize((32,32)),torchvision.transforms.ToTensor()])image=transform(image)print(image.shape)classTudui(nn.Module):def__init__(self):super(Tudui,self).__init__()self.model=nn.Sequential(nn.Conv2d(3,32,5,1,2),nn.MaxPool2d(2),nn.Conv2d(32,32,5,1,2),nn.MaxPool2d(2),nn.Conv2d(32,64,5,1,2),nn.MaxPool2d(2),nn.Flatten(),nn.Linear(64*4*4,64),nn.Linear(64,10))defforward(self,x):x=self.model(x)returnx# CPU加载GPU训练的模型需指定map_locationmodel=torch.load("tudui_29_gpu.pth",weights_only=False,map_location=torch.device('cpu'))# 模型验证:打印model结构确认加载成功print(model)# 使用reshape添加batch维度image=torch.reshape(image,(1,3,32,32))model.eval()withtorch.no_grad():output=model(image)print(output)# 使用argmax获取预测类别print(output.argmax(1))

我们执行如上代码,发现控制台打印的输出推测正确,打印出来的十个类别概率中,只有索引位置为0的概率6.4093的概率是最大的,而类别0对应的就是飞机。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进