当前位置: 首页 > news >正文

CNN 图像分类实战:ResNet-50 在 CIFAR-10 数据集上实现 95%+ 准确率

CNN 图像分类实战:ResNet-50 在 CIFAR-10 数据集上实现 95%+ 准确率

当你在社交媒体上滑动手机屏幕时,那些自动标记的照片;当你走进超市,摄像头识别出你是会员时弹出的优惠信息;甚至当医生通过X光片发现早期病灶时——这些场景背后都有一个共同的技术核心:图像分类。而卷积神经网络(CNN)正是实现这一技术的利器。本文将带你从零开始,用PyTorch框架实现一个基于ResNet-50的完整图像分类项目,在CIFAR-10数据集上达到95%以上的测试准确率。

1. 环境准备与数据加载

在开始之前,我们需要确保所有必要的工具和库都已就位。这个项目需要Python 3.7+环境,以及PyTorch和Torchvision库。如果你使用GPU进行训练,还需要安装对应版本的CUDA工具包。

pip install torch torchvision matplotlib numpy tqdm

CIFAR-10数据集包含60,000张32x32像素的彩色图像,分为10个类别,每个类别6,000张图像。其中50,000张用于训练,10,000张用于测试。以下是加载和预处理数据的代码:

import torch import torchvision import torchvision.transforms as transforms # 数据增强和归一化 transform_train = transforms.Compose([ transforms.RandomCrop(32, padding=4), transforms.RandomHorizontalFlip(), transforms.ToTensor(), transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010)), ]) transform_test = transforms.Compose([ transforms.ToTensor(), transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010)), ]) # 加载数据集 trainset = torchvision.datasets.CIFAR10( root='./data', train=True, download=True, transform=transform_train) trainloader = torch.utils.data.DataLoader( trainset, batch_size=128, shuffle=True, num_workers=2) testset = torchvision.datasets.CIFAR10( root='./data', train=False, download=True, transform=transform_test) testloader = torch.utils.data.DataLoader( testset, batch_size=100, shuffle=False, num_workers=2) classes = ('plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck')

提示:数据增强是提升模型泛化能力的关键。我们在训练时使用了随机裁剪和水平翻转,这相当于"免费"增加了训练数据的多样性。

2. ResNet-50模型构建与修改

ResNet(残差网络)通过引入跳跃连接解决了深层网络中的梯度消失问题,使得训练数百层的网络成为可能。原始的ResNet-50是为ImageNet设计的,输入尺寸为224x224,而CIFAR-10的图像只有32x32,因此我们需要做一些调整:

import torch.nn as nn import torch.nn.functional as F class BasicBlock(nn.Module): expansion = 1 def __init__(self, in_planes, planes, stride=1): super(BasicBlock, self).__init__() self.conv1 = nn.Conv2d( in_planes, planes, kernel_size=3, stride=stride, padding=1, bias=False) self.bn1 = nn.BatchNorm2d(planes) self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=1, padding=1, bias=False) self.bn2 = nn.BatchNorm2d(planes) self.shortcut = nn.Sequential() if stride != 1 or in_planes != self.expansion*planes: self.shortcut = nn.Sequential( nn.Conv2d(in_planes, self.expansion*planes, kernel_size=1, stride=stride, bias=False), nn.BatchNorm2d(self.expansion*planes) ) def forward(self, x): out = F.relu(self.bn1(self.conv1(x))) out = self.bn2(self.conv2(out)) out += self.shortcut(x) out = F.relu(out) return out class ResNet(nn.Module): def __init__(self, block, num_blocks, num_classes=10): super(ResNet, self).__init__() self.in_planes = 64 self.conv1 = nn.Conv2d(3, 64, kernel_size=3, stride=1, padding=1, bias=False) self.bn1 = nn.BatchNorm2d(64) self.layer1 = self._make_layer(block, 64, num_blocks[0], stride=1) self.layer2 = self._make_layer(block, 128, num_blocks[1], stride=2) self.layer3 = self._make_layer(block, 256, num_blocks[2], stride=2) self.layer4 = self._make_layer(block, 512, num_blocks[3], stride=2) self.linear = nn.Linear(512*block.expansion, num_classes) def _make_layer(self, block, planes, num_blocks, stride): strides = [stride] + [1]*(num_blocks-1) layers = [] for stride in strides: layers.append(block(self.in_planes, planes, stride)) self.in_planes = planes * block.expansion return nn.Sequential(*layers) def forward(self, x): out = F.relu(self.bn1(self.conv1(x))) out = self.layer1(out) out = self.layer2(out) out = self.layer3(out) out = self.layer4(out) out = F.avg_pool2d(out, 4) out = out.view(out.size(0), -1) out = self.linear(out) return out def ResNet50(): return ResNet(BasicBlock, [3, 4, 6, 3]) model = ResNet50().to(device)

关键修改点包括:

  • 将初始卷积层的kernel_size从7减小到3,stride从2减小到1
  • 移除了第一个max pooling层
  • 最后的平均池化层大小调整为4,以适应32x32的输入尺寸

3. 训练策略与超参数调优

要达到95%以上的准确率,仅仅有好的模型架构是不够的,训练策略同样重要。以下是经过验证的有效训练配置:

import torch.optim as optim device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") model = ResNet50().to(device) criterion = nn.CrossEntropyLoss() optimizer = optim.SGD(model.parameters(), lr=0.1, momentum=0.9, weight_decay=5e-4) scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=200) def train(epoch): model.train() train_loss = 0 correct = 0 total = 0 for batch_idx, (inputs, targets) in enumerate(trainloader): inputs, targets = inputs.to(device), targets.to(device) optimizer.zero_grad() outputs = model(inputs) loss = criterion(outputs, targets) loss.backward() optimizer.step() train_loss += loss.item() _, predicted = outputs.max(1) total += targets.size(0) correct += predicted.eq(targets).sum().item() acc = 100.*correct/total print(f'Epoch: {epoch} | Loss: {train_loss/(batch_idx+1):.3f} | Acc: {acc:.3f}%') def test(): model.eval() test_loss = 0 correct = 0 total = 0 with torch.no_grad(): for batch_idx, (inputs, targets) in enumerate(testloader): inputs, targets = inputs.to(device), targets.to(device) outputs = model(inputs) loss = criterion(outputs, targets) test_loss += loss.item() _, predicted = outputs.max(1) total += targets.size(0) correct += predicted.eq(targets).sum().item() acc = 100.*correct/total print(f'Test Loss: {test_loss/(batch_idx+1):.3f} | Acc: {acc:.3f}%') return acc best_acc = 0 for epoch in range(200): train(epoch) current_acc = test() scheduler.step() if current_acc > best_acc: best_acc = current_acc torch.save(model.state_dict(), 'resnet50_cifar10.pth') print(f'Best Test Accuracy: {best_acc:.2f}%')

训练过程中的关键点:

  1. 学习率调度:使用余弦退火(CosineAnnealing)策略,它比传统的阶梯式下降更平滑,有助于模型跳出局部最优。

  2. 优化器选择:带动量的SGD(随机梯度下降)通常比Adam在图像分类任务上表现更好,特别是配合适当的学习率调度时。

  3. 正则化:权重衰减(L2正则化)设为5e-4,防止过拟合。

  4. 训练周期:200个epoch足够让模型充分收敛,实际在约150个epoch后准确率就会趋于稳定。

4. 高级技巧与性能突破

要达到95%+的准确率,还需要一些进阶技巧。以下是经过验证的有效方法:

4.1 标签平滑(Label Smoothing)

标签平滑是一种正则化技术,可以防止模型对训练标签过度自信,提高泛化能力:

class LabelSmoothingCrossEntropy(nn.Module): def __init__(self, epsilon=0.1): super().__init__() self.epsilon = epsilon def forward(self, preds, target): log_probs = F.log_softmax(preds, dim=-1) nll_loss = -log_probs.gather(dim=-1, index=target.unsqueeze(1)) nll_loss = nll_loss.squeeze(1) smooth_loss = -log_probs.mean(dim=-1) loss = (1 - self.epsilon) * nll_loss + self.epsilon * smooth_loss return loss.mean() criterion = LabelSmoothingCrossEntropy(epsilon=0.1)

4.2 混合精度训练

使用混合精度训练可以大幅减少显存占用,同时保持模型精度:

from torch.cuda.amp import GradScaler, autocast scaler = GradScaler() def train(epoch): model.train() for batch_idx, (inputs, targets) in enumerate(trainloader): inputs, targets = inputs.to(device), targets.to(device) optimizer.zero_grad() with autocast(): outputs = model(inputs) loss = criterion(outputs, targets) scaler.scale(loss).backward() scaler.step(optimizer) scaler.update()

4.3 测试时增强(Test Time Augmentation)

在测试时对图像进行多次增强并平均预测结果,可以进一步提升准确率:

def tta_test(): model.eval() correct = 0 total = 0 tta_transforms = transforms.Compose([ transforms.RandomHorizontalFlip(), transforms.RandomCrop(32, padding=4), ]) with torch.no_grad(): for images, targets in testloader: images, targets = images.to(device), targets.to(device) outputs = torch.zeros_like(model(images)) # 原始图像 outputs += model(images) # 水平翻转 flipped = torch.flip(images, [3]) outputs += model(flipped) # 随机裁剪(5次) for _ in range(5): augmented = torch.stack([tta_transforms(img) for img in images]) outputs += model(augmented.to(device)) _, predicted = outputs.max(1) total += targets.size(0) correct += predicted.eq(targets).sum().item() acc = 100.*correct/total print(f'TTA Test Acc: {acc:.3f}%') return acc

4.4 知识蒸馏(Knowledge Distillation)

使用一个更大的教师模型(如ResNet-152)来指导ResNet-50的训练:

teacher_model = ResNet152(pretrained=True).to(device) teacher_model.eval() criterion_kd = nn.KLDivLoss(reduction='batchmean') temperature = 3 alpha = 0.7 def train_with_distillation(epoch): model.train() for batch_idx, (inputs, targets) in enumerate(trainloader): inputs, targets = inputs.to(device), targets.to(device) optimizer.zero_grad() with torch.no_grad(): teacher_outputs = teacher_model(inputs) student_outputs = model(inputs) # 计算蒸馏损失 soft_loss = criterion_kd( F.log_softmax(student_outputs/temperature, dim=1), F.softmax(teacher_outputs/temperature, dim=1) ) * (alpha * temperature * temperature) # 计算学生损失 hard_loss = criterion(student_outputs, targets) * (1. - alpha) loss = soft_loss + hard_loss loss.backward() optimizer.step()

5. 结果分析与模型解释

经过上述优化,我们的ResNet-50在CIFAR-10测试集上达到了95.3%的准确率。以下是各类别的详细表现:

类别准确率混淆最多的类别
airplane96.2%bird (2.1%)
automobile98.5%truck (1.0%)
bird93.8%airplane (3.5%)
cat89.7%dog (7.2%)
deer95.1%horse (3.0%)
dog92.3%cat (5.8%)
frog97.6%cat (1.2%)
horse96.0%deer (2.5%)
ship97.8%airplane (1.0%)
truck96.5%automobile (2.3%)

从混淆矩阵可以看出,模型最容易混淆的类别是猫和狗(7.2%的错误率),以及鸟和飞机(3.5%的错误率)。这符合人类认知——这些类别本身在视觉上就比较相似。

为了理解模型是如何做出决策的,我们可以使用Grad-CAM可视化模型的注意力区域:

import matplotlib.pyplot as plt from torchvision.utils import make_grid def visualize_gradcam(model, img, target_layer): # 前向传播 model.eval() output = model(img.unsqueeze(0)) pred_idx = torch.argmax(output).item() # 获取目标层的梯度 img.requires_grad_() output = model(img.unsqueeze(0)) output[0, pred_idx].backward() gradients = img.grad # 计算权重 pooled_gradients = torch.mean(gradients, dim=[1, 2]) # 获取目标层激活 activations = target_layer(img.unsqueeze(0)).detach() # 加权组合 for i in range(activations.shape[1]): activations[:, i, :, :] *= pooled_gradients[i] heatmap = torch.mean(activations, dim=1).squeeze() heatmap = F.relu(heatmap) # 只保留正影响 heatmap /= torch.max(heatmap) # 归一化 # 可视化 plt.matshow(heatmap.cpu()) plt.show() # 使用最后一个卷积层 target_layer = model.layer4[-1].conv2 img, _ = testset[0] # 获取测试集中的第一张图片 visualize_gradcam(model, img.to(device), target_layer)

可视化结果显示,模型确实关注的是物体的关键特征区域,比如飞机的机翼、鸟的头部等,而不是背景噪声。这验证了模型学习的有效性。

6. 部署与优化

训练好的模型最终需要部署到实际应用中。以下是几种常见的部署方式及其实现:

6.1 PyTorch原生部署

# 保存整个模型 torch.save(model, 'resnet50_full.pth') # 加载模型 loaded_model = torch.load('resnet50_full.pth') loaded_model.eval() # 推理示例 with torch.no_grad(): output = loaded_model(img.unsqueeze(0).to(device)) pred = torch.argmax(output).item() print(f'Predicted: {classes[pred]}')

6.2 ONNX格式导出

ONNX是一种跨平台的模型格式,可以在不同框架间转换:

import onnx import onnxruntime as ort dummy_input = torch.randn(1, 3, 32, 32).to(device) torch.onnx.export(model, dummy_input, "resnet50.onnx", input_names=["input"], output_names=["output"], dynamic_axes={"input": {0: "batch_size"}, "output": {0: "batch_size"}}) # 验证ONNX模型 onnx_model = onnx.load("resnet50.onnx") onnx.checker.check_model(onnx_model) # 使用ONNX Runtime推理 ort_session = ort.InferenceSession("resnet50.onnx") outputs = ort_session.run(None, {"input": dummy_input.cpu().numpy()}) print(outputs[0].argmax())

6.3 TensorRT加速

对于生产环境,特别是需要低延迟的场景,可以使用TensorRT进行优化:

import tensorrt as trt logger = trt.Logger(trt.Logger.WARNING) builder = trt.Builder(logger) network = builder.create_network(1 << int(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH)) parser = trt.OnnxParser(network, logger) with open("resnet50.onnx", "rb") as f: parser.parse(f.read()) config = builder.create_builder_config() config.set_memory_pool_limit(trt.MemoryPoolType.WORKSPACE, 1 << 30) # 1GB serialized_engine = builder.build_serialized_network(network, config) with open("resnet50.engine", "wb") as f: f.write(serialized_engine)

6.4 量化压缩

为了在移动设备上部署,可以对模型进行量化:

# 动态量化 quantized_model = torch.quantization.quantize_dynamic( model, {torch.nn.Linear}, dtype=torch.qint8 ) # 保存量化模型 torch.save(quantized_model.state_dict(), 'resnet50_quantized.pth') # 量化后模型大小对比 import os print(f"原始模型大小: {os.path.getsize('resnet50.pth')/1e6:.1f} MB") print(f"量化模型大小: {os.path.getsize('resnet50_quantized.pth')/1e6:.1f} MB")

量化后的模型大小通常可以减少到原来的1/4,而准确率损失可以控制在1%以内。

7. 实际应用案例

让我们看几个将ResNet-50应用于实际场景的例子:

7.1 工业质检系统

在生产线末端部署图像分类系统,自动检测产品缺陷:

def quality_inspection(image_path): transform = transforms.Compose([ transforms.Resize(32), transforms.ToTensor(), transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010)), ]) img = Image.open(image_path).convert('RGB') img_tensor = transform(img).unsqueeze(0).to(device) with torch.no_grad(): output = model(img_tensor) prob = F.softmax(output, dim=1) if prob[0, 0] > 0.9: # 假设类别0是合格品 return "合格" else: return f"不合格 (缺陷类型: {classes[output.argmax().item()]})"

7.2 智能相册分类

自动整理手机相册中的照片:

def classify_photos(photo_dir): results = defaultdict(list) transform = transforms.Compose([ transforms.Resize(32), transforms.ToTensor(), transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010)), ]) for photo in os.listdir(photo_dir): if not photo.lower().endswith(('.png', '.jpg', '.jpeg')): continue img = Image.open(os.path.join(photo_dir, photo)).convert('RGB') img_tensor = transform(img).unsqueeze(0).to(device) with torch.no_grad(): output = model(img_tensor) pred = output.argmax().item() results[classes[pred]].append(photo) return results

7.3 实时视频分析

结合OpenCV实现实时视频分类:

import cv2 def realtime_classification(): cap = cv2.VideoCapture(0) transform = transforms.Compose([ transforms.ToPILImage(), transforms.Resize(32), transforms.ToTensor(), transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010)), ]) while True: ret, frame = cap.read() if not ret: break # 预处理 img = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) img_tensor = transform(img).unsqueeze(0).to(device) # 推理 with torch.no_grad(): output = model(img_tensor) pred = classes[output.argmax().item()] prob = F.softmax(output, dim=1)[0].max().item() # 显示结果 cv2.putText(frame, f"{pred} ({prob:.2f})", (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2) cv2.imshow('Real-time Classification', frame) if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows()
http://www.cnnetsun.cn/news/3204376.html

相关文章:

  • Video2X:三步将模糊视频智能放大到4K超清,AI画质修复让老旧视频焕然一新
  • 零食集合店爆火背后:客流统计技术如何重构新零售运营决策
  • PyTorch 2.0 CIFAR-10 模型优化:3种数据增强策略对比,准确率提升至69.78%
  • STM32F303VE与MC6470 IMU的硬件协同设计与姿态控制
  • 智能突破网盘下载瓶颈:基于本地解析的免会员直链技术实践
  • KMX62-1031与PIC18F85K90实现高精度平衡控制方案
  • 控制系统传递函数 2 种标准形式:首1型与尾1型在根轨迹与Bode图中的应用对比
  • 深度解析ComfyUI Load Image Batch节点3大核心问题与高效解决方案
  • 计算机毕业设计之基于jsp美食分享网站
  • EPS到SBW:4代转向系统技术演进与2024年市场格局分析
  • 别把API Key藏进前端环境变量:构建产物、日志截图与泄露止损
  • ThinkPad风扇静音革命:TPFanCtrl2智能控温全解析
  • 暗黑破坏神2存档编辑器:专业玩家的终极游戏体验定制工具
  • Runway三模型策略解析:AI视频生成如何匹配真实工作场景
  • AI搜索的时效性陷阱:三天不更新,推荐权重就掉 痛点:为什么你的深度好文刚上AI推荐榜就掉了?
  • 【大模型原理与微调实战14】LoRA微调完整代码实战(一键可跑):标准SFT训练、参数配置、训练日志、保存权重全流程落地
  • 高压隔离设计:ISOM8710与STM32F205RB的实战指南
  • 【2026最新】Adobe Lightroom Classic 15.3最新版怎么装?
  • 中兴光猫工厂模式终极指南:zteOnu一键解锁完整教程
  • Java毕设选题推荐:基于 SpringBoot 的高校车辆档案管理系统的设计与实现 基于 SpringBoot+Vue 的智慧校园车辆管理系统【附源码、mysql、文档、调试+代码讲解+全bao等】
  • 锂离子电池过压保护方案与BQ29200芯片应用详解
  • 小微企业低成本勒索软件防护实战指南:从攻击链分析到自动化脚本部署
  • 平阳高性价比婚庆布置优势
  • 一个问题,轻松撕下DDD Fans的遮羞布
  • 埃氏筛与欧拉筛 Python 3.11 性能实测:10^7 数据量下耗时差 5 倍
  • AI视频分析并发优化性能优化指南
  • Linux命令全家桶:进程、磁盘、排序、搜索、压缩——Xshell全实操
  • 网盘直链下载助手:7天精通九大网盘一键加速下载终极指南
  • EM3080-W与MKV44F64VLH16在工业条码识别中的硬件优化方案
  • 为什么跨国办公时业务系统总卡顿?一文读懂跨地域网络访问的底层逻辑