深度学习实战 - 图像分类与目标检测
通过实战项目串联深度学习知识,完成图像分类与目标检测项目
前置知识:需要先掌握 CNN基础
本文重点:完整项目实践,建立端到端开发能力
一、项目概述
1.1 项目目标
项目1: 图像分类
- 数据集: CIFAR-10 / 自定义数据集
- 任务: 识别图像中的物体类别
- 技术: CNN、数据增强、迁移学习
项目2: 目标检测
- 任务: 检测图像中的物体位置和类别
- 技术: YOLO、锚框、NMS
1.2 开发环境
import torch
import torch.nn as nn
import torch.optim as optim
import torchvision
import torchvision.transforms as transforms
from torch.utils.data import DataLoader
import matplotlib.pyplot as plt
import numpy as np
# 检查GPU
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(f"使用设备: {device}")
二、图像分类实战
2.1 数据增强
# 训练集数据增强
train_transform = transforms.Compose([
transforms.RandomResizedCrop(32, scale=(0.8, 1.0)), # 随机裁剪
transforms.RandomHorizontalFlip(p=0.5), # 随机水平翻转
transforms.RandomRotation(15), # 随机旋转
transforms.ColorJitter(brightness=0.2, contrast=0.2,
saturation=0.2, hue=0.1), # 颜色抖动
transforms.RandomAffine(degrees=0, translate=(0.1, 0.1)), # 随机平移
transforms.ToTensor(),
transforms.Normalize((0.4914, 0.4822, 0.4465),
(0.2470, 0.2435, 0.2616))
])
# 测试集只需要标准化
test_transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.4914, 0.4822, 0.4465),
(0.2470, 0.2435, 0.2616))
])
# 可视化数据增强效果
def visualize_augmentation(image, transform, num_images=5):
fig, axes = plt.subplots(1, num_images + 1, figsize=(15, 3))
axes[0].imshow(image)
axes[0].set_title('原始图像')
for i in range(num_images):
aug_img = transform(image)
aug_img = aug_img.permute(1, 2, 0).numpy()
axes[i+1].imshow(aug_img)
axes[i+1].set_title(f'增强{i+1}')
plt.show()
# 示例
from PIL import Image
# img = Image.open('sample.jpg')
# visualize_augmentation(img, train_transform)
2.2 自定义数据集
import os
from PIL import Image
from torch.utils.data import Dataset, DataLoader
class CustomImageDataset(Dataset):
"""自定义图像数据集"""
def __init__(self, root_dir, transform=None):
self.root_dir = root_dir
self.transform = transform
self.classes = sorted(os.listdir(root_dir))
self.class_to_idx = {cls: i for i, cls in enumerate(self.classes)}
self.samples = []
for class_name in self.classes:
class_dir = os.path.join(root_dir, class_name)
for img_name in os.listdir(class_dir):
self.samples.append((
os.path.join(class_dir, img_name),
self.class_to_idx[class_name]
))
def __len__(self):
return len(self.samples)
def __getitem__(self, idx):
img_path, label = self.samples[idx]
image = Image.open(img_path).convert('RGB')
if self.transform:
image = self.transform(image)
return image, label
# 使用示例
"""
数据集目录结构:
dataset/
├── train/
│ ├── cat/
│ │ ├── cat1.jpg
│ │ └── ...
│ └── dog/
│ ├── dog1.jpg
│ └── ...
└── val/
├── cat/
└── dog/
train_dataset = CustomImageDataset('dataset/train', transform=train_transform)
train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)
"""
2.3 训练脚本封装
import time
import copy
class Trainer:
"""训练器封装"""
def __init__(self, model, criterion, optimizer, scheduler, device):
self.model = model
self.criterion = criterion
self.optimizer = optimizer
self.scheduler = scheduler
self.device = device
self.best_model_wts = copy.deepcopy(model.state_dict())
self.best_acc = 0.0
self.history = {'train_loss': [], 'train_acc': [],
'val_loss': [], 'val_acc': []}
def train_epoch(self, dataloader):
self.model.train()
running_loss = 0.0
running_corrects = 0
for inputs, labels in dataloader:
inputs = inputs.to(self.device)
labels = labels.to(self.device)
self.optimizer.zero_grad()
outputs = self.model(inputs)
_, preds = torch.max(outputs, 1)
loss = self.criterion(outputs, labels)
loss.backward()
self.optimizer.step()
running_loss += loss.item() * inputs.size(0)
running_corrects += torch.sum(preds == labels.data)
epoch_loss = running_loss / len(dataloader.dataset)
epoch_acc = running_corrects.double() / len(dataloader.dataset)
return epoch_loss, epoch_acc
def validate(self, dataloader):
self.model.eval()
running_loss = 0.0
running_corrects = 0
with torch.no_grad():
for inputs, labels in dataloader:
inputs = inputs.to(self.device)
labels = labels.to(self.device)
outputs = self.model(inputs)
_, preds = torch.max(outputs, 1)
loss = self.criterion(outputs, labels)
running_loss += loss.item() * inputs.size(0)
running_corrects += torch.sum(preds == labels.data)
epoch_loss = running_loss / len(dataloader.dataset)
epoch_acc = running_corrects.double() / len(dataloader.dataset)
return epoch_loss, epoch_acc
def fit(self, train_loader, val_loader, num_epochs):
since = time.time()
for epoch in range(num_epochs):
print(f'Epoch {epoch+1}/{num_epochs}')
print('-' * 30)
# 训练
train_loss, train_acc = self.train_epoch(train_loader)
self.history['train_loss'].append(train_loss)
self.history['train_acc'].append(train_acc.item())
# 验证
val_loss, val_acc = self.validate(val_loader)
self.history['val_loss'].append(val_loss)
self.history['val_acc'].append(val_acc.item())
# 学习率调整
if self.scheduler:
self.scheduler.step()
print(f'Train Loss: {train_loss:.4f} Acc: {train_acc:.4f}')
print(f'Val Loss: {val_loss:.4f} Acc: {val_acc:.4f}')
# 保存最佳模型
if val_acc > self.best_acc:
self.best_acc = val_acc
self.best_model_wts = copy.deepcopy(self.model.state_dict())
time_elapsed = time.time() - since
print(f'训练完成,耗时: {time_elapsed // 60:.0f}m {time_elapsed % 60:.0f}s')
print(f'最佳验证准确率: {self.best_acc:.4f}')
# 加载最佳权重
self.model.load_state_dict(self.best_model_wts)
return self.model
def plot_history(self):
fig, axes = plt.subplots(1, 2, figsize=(12, 4))
axes[0].plot(self.history['train_loss'], label='Train')
axes[0].plot(self.history['val_loss'], label='Val')
axes[0].set_xlabel('Epoch')
axes[0].set_ylabel('Loss')
axes[0].legend()
axes[0].set_title('Loss Curve')
axes[1].plot(self.history['train_acc'], label='Train')
axes[1].plot(self.history['val_acc'], label='Val')
axes[1].set_xlabel('Epoch')
axes[1].set_ylabel('Accuracy')
axes[1].legend()
axes[1].set_title('Accuracy Curve')
plt.tight_layout()
plt.savefig('training_history.png', dpi=100)
plt.show()
2.4 完整训练流程
# 加载CIFAR-10
train_set = torchvision.datasets.CIFAR10(
root='./data', train=True, download=True, transform=train_transform
)
test_set = torchvision.datasets.CIFAR10(
root='./data', train=False, download=True, transform=test_transform
)
train_loader = DataLoader(train_set, batch_size=128, shuffle=True, num_workers=2)
test_loader = DataLoader(test_set, batch_size=128, shuffle=False, num_workers=2)
# 使用预训练ResNet
import torchvision.models as models
model = models.resnet18(pretrained=True)
# 修改最后一层
model.fc = nn.Linear(model.fc.in_features, 10)
model = model.to(device)
# 损失函数和优化器
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.9, weight_decay=5e-4)
scheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=50)
# 创建训练器
trainer = Trainer(model, criterion, optimizer, scheduler, device)
# 训练
model = trainer.fit(train_loader, test_loader, num_epochs=30)
# 可视化
trainer.plot_history()
# 保存模型
torch.save(model.state_dict(), 'cifar10_resnet18.pth')
三、目标检测入门
3.1 目标检测基础
"""
目标检测核心概念:
1. 边界框 (Bounding Box)
- 格式: [x1, y1, x2, y2] 或 [cx, cy, w, h]
- x1,y1: 左上角坐标
- x2,y2: 右下角坐标
2. 锚框 (Anchor Box)
- 预设的参考框
- 不同尺度和宽高比
3. IoU (Intersection over Union)
- 衡量预测框与真实框的重叠程度
- IoU = 交集面积 / 并集面积
4. NMS (Non-Maximum Suppression)
- 去除重复检测框
- 保留最高置信度的框
"""
def calculate_iou(box1, box2):
"""计算两个框的IoU"""
# box格式: [x1, y1, x2, y2]
x1 = max(box1[0], box2[0])
y1 = max(box1[1], box2[1])
x2 = min(box1[2], box2[2])
y2 = min(box1[3], box2[3])
inter_area = max(0, x2 - x1) * max(0, y2 - y1)
box1_area = (box1[2] - box1[0]) * (box1[3] - box1[1])
box2_area = (box2[2] - box2[0]) * (box2[3] - box2[1])
union_area = box1_area + box2_area - inter_area
iou = inter_area / (union_area + 1e-6)
return iou
def nms(boxes, scores, iou_threshold=0.5):
"""非极大值抑制"""
indices = []
# 按置信度排序
order = scores.argsort()[::-1]
while order.size > 0:
i = order[0]
indices.append(i)
# 计算与其他框的IoU
ious = np.array([calculate_iou(boxes[i], boxes[j]) for j in order[1:]])
# 保留IoU小于阈值的框
inds = np.where(ious <= iou_threshold)[0]
order = order[inds + 1]
return indices
3.2 使用YOLOv8
# 安装: pip install ultralytics
try:
from ultralytics import YOLO
# 加载预训练模型
model = YOLO('yolov8n.pt') # nano版本,速度快
# 目标检测
results = model('image.jpg')
# 显示结果
for result in results:
result.show()
# 获取检测框信息
boxes = result.boxes
for box in boxes:
x1, y1, x2, y2 = box.xyxy[0].cpu().numpy()
conf = box.conf[0].cpu().numpy()
cls = box.cls[0].cpu().numpy()
print(f"类别: {int(cls)}, 置信度: {conf:.2f}, 位置: [{x1:.0f}, {y1:.0f}, {x2:.0f}, {y2:.0f}]")
# 在自定义数据集上训练
# model.train(data='coco.yaml', epochs=50, imgsz=640)
except ImportError:
print("ultralytics未安装,运行: pip install ultralytics")
四、模型部署
4.1 ONNX导出
# 导出为ONNX格式
dummy_input = torch.randn(1, 3, 224, 224).to(device)
torch.onnx.export(
model,
dummy_input,
"model.onnx",
input_names=['input'],
output_names=['output'],
dynamic_axes={'input': {0: 'batch_size'}, 'output': {0: 'batch_size'}},
opset_version=11
)
print("模型已导出为ONNX格式")
# 使用ONNX Runtime推理
try:
import onnxruntime as ort
ort_session = ort.InferenceSession("model.onnx")
outputs = ort_session.run(None, {'input': dummy_input.cpu().numpy()})
print(f"ONNX输出形状: {outputs[0].shape}")
except ImportError:
print("onnxruntime未安装")
4.2 TorchServe部署
"""
TorchServe部署步骤:
1. 安装TorchServe
pip install torchserve torch-model-archiver
2. 打包模型
torch-model-archiver --model-name mymodel \
--version 1.0 --model-file model.py \
--serialized-file model.pth \
--handler image_classifier
3. 启动服务
torchserve --start --model-store model_store \
--models mymodel.mar
4. API调用
curl -X POST http://localhost:8080/predictions/mymodel \
-T image.jpg
"""
参考资源
- PyTorch官方教程 - 深度学习教程
- TorchVision模型库 - 预训练模型
- YOLOv8文档 - YOLO官方文档
- Detectron2 - Facebook目标检测框架
- Albumentations - 高级图像增强库
- ONNX官方文档 - 模型格式标准
- TorchServe文档 - 模型部署指南
返回:深度学习基础 最后更新: 2026年4月14日
讨论与反馈