基于Python的图像分类模型训练与部署实践
在当前人工智能技术飞速发展的背景下,图像分类作为计算机视觉领域的一个核心任务,已经成为许多实际应用的基础。本文将通过一个完整的实战案例,介绍如何使用 Python 和深度学习框架(如 PyTorch)构建、训练并部署一个图像分类模型。整个流程包括数据准备、模型构建、训练、评估以及模型导出和部署。
我们将以经典的 CIFAR-10 数据集为例,使用 ResNet 架构进行图像分类任务。最终我们会将训练好的模型转换为 ONNX 格式,并使用 ONNX Runtime 进行推理测试。
环境准备
首先,我们需要安装必要的库:
pip install torch torchvision onnx onnxruntime
我们使用的工具如下:
PyTorch:用于模型构建和训练。ONNX:用于模型格式转换。ONNX Runtime:用于部署模型进行推理。数据准备与预处理
我们将使用 PyTorch 提供的 torchvision.datasets.CIFAR10
数据集,包含 10 类彩色图像(每张大小为 32x32)。
import torchfrom torchvision import datasets, transformsfrom torch.utils.data import DataLoader# 定义数据变换transform = transforms.Compose([ transforms.ToTensor(), transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])# 加载训练集和测试集train_dataset = datasets.CIFAR10(root='./data', train=True, download=True, transform=transform)test_dataset = datasets.CIFAR10(root='./data', train=False, download=True, transform=transform)train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True)test_loader = DataLoader(test_dataset, batch_size=64, shuffle=False)
模型构建:ResNet-18
我们使用 PyTorch 提供的预定义 ResNet-18 模型,并修改最后一层以适配 CIFAR-10 的类别数量。
import torch.nn as nnimport torchvision.models as modelsdef get_model(): model = models.resnet18(pretrained=False) model.conv1 = nn.Conv2d(3, 64, kernel_size=3, stride=1, padding=1, bias=False) model.maxpool = nn.Identity() # 修改 maxpool 层以适应 CIFAR-10 图像尺寸 model.fc = nn.Linear(512, 10) # 修改输出层为10类 return modelmodel = get_model()device = torch.device("cuda" if torch.cuda.is_available() else "cpu")model.to(device)
模型训练
我们使用交叉熵损失函数和 Adam 优化器进行训练。
import torch.optim as optimcriterion = nn.CrossEntropyLoss()optimizer = optim.Adam(model.parameters(), lr=0.001)# 训练循环for epoch in range(5): # 只训练5个epoch作为示例 model.train() running_loss = 0.0 for images, labels in train_loader: images, labels = images.to(device), labels.to(device) optimizer.zero_grad() outputs = model(images) loss = criterion(outputs, labels) loss.backward() optimizer.step() running_loss += loss.item() print(f"Epoch {epoch+1}, Loss: {running_loss/len(train_loader)}")# 测试准确率model.eval()correct = 0total = 0with torch.no_grad(): for images, labels in test_loader: images, labels = images.to(device), labels.to(device) outputs = model(images) _, predicted = torch.max(outputs.data, 1) total += labels.size(0) correct += (predicted == labels).sum().item()print(f"Test Accuracy: {100 * correct / total:.2f}%")
运行结果可能如下:
Epoch 1, Loss: 1.23Epoch 2, Loss: 0.91...Test Accuracy: 78.5%
模型保存与导出为 ONNX
为了便于部署,我们将训练好的模型导出为 ONNX 格式。
dummy_input = torch.randn(1, 3, 32, 32, device=device)input_names = ["input"]output_names = ["output"]torch.onnx.export( model, dummy_input, "resnet_cifar10.onnx", export_params=True, # 存储训练参数 opset_version=11, # ONNX 算子集版本 do_constant_folding=True, # 优化常量 input_names=input_names, output_names=output_names, dynamic_axes={ 'input': {0: 'batch_size'}, 'output': {0: 'batch_size'} })print("Model has been converted to ONNX")
使用 ONNX Runtime 进行推理
我们可以使用 ONNX Runtime 来加载并运行 ONNX 模型。
import numpy as npimport onnxruntime as ort# 加载 ONNX 模型ort_session = ort.InferenceSession("resnet_cifar10.onnx")# 获取测试集中的一张图片images, labels = next(iter(test_loader))img = images[0].unsqueeze(0).cpu().numpy() # 转换为 numpy array# 推理outputs = ort_session.run( None, {'input': img})# 获取预测结果predicted_class = np.argmax(outputs[0], axis=1)print(f"Predicted class: {predicted_class[0]}, Ground truth: {labels[0]}")
模型部署与性能优化建议
在生产环境中部署模型时,可以考虑以下几点:
量化压缩:使用 ONNX 或 PyTorch 自带的量化工具减少模型大小。多线程推理:利用 ONNX Runtime 的多线程支持提高吞吐量。GPU加速:配置 ONNX Runtime 使用 CUDA 加速推理过程。服务化部署:结合 Flask 或 FastAPI 封装成 REST API 提供图像识别服务。总结
本文完整地演示了从图像分类模型的构建、训练、评估到模型导出与部署的全过程。通过 PyTorch 我们能够快速实现深度学习模型的开发,而借助 ONNX 与 ONNX Runtime,我们可以方便地将模型部署到不同平台和设备上,实现跨语言、跨系统的推理能力。
未来的工作可以扩展至更复杂的网络结构、更大的数据集,或引入迁移学习提升准确率。同时,也可以探索模型蒸馏、剪枝等方法来优化模型效率,以适应边缘计算场景的需求。
附录:完整代码汇总
import torchfrom torchvision import datasets, transformsfrom torch.utils.data import DataLoaderimport torchvision.models as modelsimport torch.nn as nnimport torch.optim as optimimport numpy as npimport onnxruntime as ort# 数据预处理transform = transforms.Compose([ transforms.ToTensor(), transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])train_dataset = datasets.CIFAR10(root='./data', train=True, download=True, transform=transform)test_dataset = datasets.CIFAR10(root='./data', train=False, download=True, transform=transform)train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True)test_loader = DataLoader(test_dataset, batch_size=64, shuffle=False)# 构建模型def get_model(): model = models.resnet18(pretrained=False) model.conv1 = nn.Conv2d(3, 64, kernel_size=3, stride=1, padding=1, bias=False) model.maxpool = nn.Identity() model.fc = nn.Linear(512, 10) return modelmodel = get_model()device = torch.device("cuda" if torch.cuda.is_available() else "cpu")model.to(device)# 训练模型criterion = nn.CrossEntropyLoss()optimizer = optim.Adam(model.parameters(), lr=0.001)for epoch in range(5): model.train() running_loss = 0.0 for images, labels in train_loader: images, labels = images.to(device), labels.to(device) optimizer.zero_grad() outputs = model(images) loss = criterion(outputs, labels) loss.backward() optimizer.step() running_loss += loss.item() print(f"Epoch {epoch+1}, Loss: {running_loss/len(train_loader)}")# 测试模型model.eval()correct = 0total = 0with torch.no_grad(): for images, labels in test_loader: images, labels = images.to(device), labels.to(device) outputs = model(images) _, predicted = torch.max(outputs.data, 1) total += labels.size(0) correct += (predicted == labels).sum().item()print(f"Test Accuracy: {100 * correct / total:.2f}%")# 导出ONNX模型dummy_input = torch.randn(1, 3, 32, 32, device=device)torch.onnx.export( model, dummy_input, "resnet_cifar10.onnx", export_params=True, opset_version=11, do_constant_folding=True, input_names=["input"], output_names=["output"], dynamic_axes={'input': {0: 'batch_size'}, 'output': {0: 'batch_size'}})print("Model has been converted to ONNX")# ONNX推理测试ort_session = ort.InferenceSession("resnet_cifar10.onnx")images, labels = next(iter(test_loader))img = images[0].unsqueeze(0).cpu().numpy()outputs = ort_session.run(None, {'input': img})predicted_class = np.argmax(outputs[0], axis=1)print(f"Predicted class: {predicted_class[0]}, Ground truth: {labels[0]}")
如果你有特定的技术方向(比如图像分割、自然语言处理等),我也可以为你定制类似的文章内容。