欢迎光临
我们一直在努力

第18章:实战项目3 - 图像分类(CIFAR-10)

从第13章到第17章,我们学习了神经网络的基础知识、激活函数、反向传播、PyTorch框架和卷积神经网络。现在,是时候把这些知识融会贯通,通过一个完整的实战项目来巩固和提升。

第18章将带你完成一个从零开始的图像分类项目:使用深度学习模型对CIFAR-10数据集进行分类。我们将经历完整的机器学习流程:数据探索、模型设计、训练调优、性能分析和模型部署。

项目概述

CIFAR-10数据集

CIFAR-10是计算机视觉领域最经典的基准数据集之一,由加拿大高级研究所(CIFAR)发布。它包含:

  • 图像数量:60,000张32×32的彩色图像
  • 训练集:50,000张
  • 测试集:10,000张
  • 类别数量:10类
  • 类别:飞机、汽车、鸟类、猫、鹿、狗、青蛙、马、船、卡车

项目目标

  • 构建一个CNN模型,在CIFAR-10测试集上达到75%以上的准确率
  • 掌握完整的深度学习项目流程
  • 学习各种训练技巧和调优方法
  • 理解如何分析模型性能和错误案例
  • 项目难度

    难度等级:⭐⭐⭐(中等)

    • 需要理解CNN的基本原理
    • 需要掌握PyTorch的基本操作
    • 需要有一定的调试和调优经验

    预计时间:2-3小时

    第一步:项目准备

    1.1 环境设置

    首先,我们需要确保所有必要的库都已安装。

    import torch
    import torch.nn as nn
    import torch.nn.functional as F
    import torch.optim as optim
    from torchvision import datasets, transforms
    from torch.utils.data import DataLoader, random_split
    import matplotlib.pyplot as plt
    import numpy as np
    import seaborn as sns
    from sklearn.metrics import confusion_matrix, classification_report
    import warnings
    warnings.filterwarnings('ignore')

    # 检查CUDA是否可用
    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
    print(f"使用设备: {device}")
    print(f"PyTorch版本: {torch.__version__}")

    # 设置随机种子以保证可重复性
    torch.manual_seed(42)
    np.random.seed(42)
    if torch.cuda.is_available():
    torch.cuda.manual_seed(42)

    1.2 数据加载

    CIFAR-10数据集可以通过torchvision直接下载。

    # 定义数据增强
    train_transform = transforms.Compose([
    transforms.RandomHorizontalFlip(), # 随机水平翻转
    transforms.RandomRotation(10), # 随机旋转±10度
    transforms.RandomCrop(32, padding=4), # 随机裁剪
    transforms.ColorJitter(0.2, 0.2, 0.2, 0.1), # 随机调整颜色
    transforms.ToTensor(), # 转换为张量
    transforms.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5]) # 归一化
    ])

    # 测试集不需要数据增强
    test_transform = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5])
    ])

    # 加载数据集
    train_dataset = datasets.CIFAR10(
    root='./data', train=True, download=True, transform=train_transform
    )

    test_dataset = datasets.CIFAR10(
    root='./data', train=False, download=True, transform=test_transform
    )

    # 从训练集中分出验证集
    val_size = 5000
    train_size = len(train_dataset) – val_size
    train_dataset, val_dataset = random_split(
    train_dataset, [train_size, val_size],
    generator=torch.Generator().manual_seed(42)
    )

    # 创建数据加载器
    batch_size = 128
    train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
    val_loader = DataLoader(val_dataset, batch_size=batch_size, shuffle=False)
    test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)

    print(f"训练集大小: {len(train_dataset)}")
    print(f"验证集大小: {len(val_dataset)}")
    print(f"测试集大小: {len(test_dataset)}")
    print(f"批次大小: {batch_size}")

    第二步:数据探索

    在开始训练之前,我们先探索一下数据。

    2.1 可视化样本

    # CIFAR-10的类别名称
    classes = ['飞机', '汽车', '鸟类', '猫', '鹿',
    '狗', '青蛙', '马', '船', '卡车']

    # 可视化一些样本
    def visualize_samples(dataset, num_samples=16):
    fig, axes = plt.subplots(4, 4, figsize=(12, 12))
    indices = np.random.choice(len(dataset), num_samples, replace=False)

    for idx, ax in enumerate(axes.flat):
    img, label = dataset[indices[idx]]
    img = img.permute(1, 2, 0) * 0.5 + 0.5 # 反归一化
    ax.imshow(img)
    ax.set_title(f'标签: {classes[label]}', fontsize=10)
    ax.axis('off')

    plt.tight_layout()
    plt.show()

    print("=== 训练集样本 ===")
    visualize_samples(train_dataset)

    2.2 类别分布分析

    # 统计各类别的数量
    def analyze_class_distribution(dataset, dataset_name="数据集"):
    labels = [dataset[i][1] for i in range(len(dataset))]
    class_counts = np.bincount(labels)

    plt.figure(figsize=(10, 5))
    plt.bar(range(10), class_counts, color='skyblue', edgecolor='black')
    plt.xlabel('类别')
    plt.ylabel('样本数量')
    plt.title(f'{dataset_name} – 类别分布')
    plt.xticks(range(10), classes, rotation=45, ha='right')
    plt.grid(axis='y', alpha=0.3)
    plt.show()

    print(f"{dataset_name} – 各类别数量:")
    for i, (count, class_name) in enumerate(zip(class_counts, classes)):
    percentage = count / len(dataset) * 100
    print(f" {class_name}: {count:6d} ({percentage:5.2f}%)")

    analyze_class_distribution(train_dataset, "训练集")
    analyze_class_distribution(val_dataset, "验证集")
    analyze_class_distribution(test_dataset, "测试集")

    2.3 统计信息

    # 计算数据集的均值和标准差
    def compute_dataset_stats(dataset):
    loader = DataLoader(dataset, batch_size=1000, shuffle=False)
    mean = 0.0
    std = 0.0
    total_images = 0

    for images, _ in loader:
    batch_samples = images.size(0)
    images = images.view(batch_samples, images.size(1), –1)
    mean += images.mean(2).sum(0)
    std += images.std(2).sum(0)
    total_images += batch_samples

    mean /= total_images
    std /= total_images

    print(f"数据集统计信息:")
    print(f" 均值: {mean}")
    print(f" 标准差: {std}")

    return mean, std

    compute_dataset_stats(train_dataset)

    第三步:模型设计

    3.1 基础CNN模型

    我们先设计一个基础的CNN模型。

    class BasicCNN(nn.Module):
    def __init__(self, num_classes=10):
    super(BasicCNN, self).__init__()
    # 卷积层
    self.conv1 = nn.Conv2d(3, 32, kernel_size=3, padding=1)
    self.conv2 = nn.Conv2d(32, 64, kernel_size=3, padding=1)
    self.conv3 = nn.Conv2d(64, 128, kernel_size=3, padding=1)

    # 池化层
    self.pool = nn.MaxPool2d(2, 2)

    # 全连接层
    self.fc1 = nn.Linear(128 * 4 * 4, 512)
    self.fc2 = nn.Linear(512, num_classes)

    # Dropout
    self.dropout = nn.Dropout(0.5)

    def forward(self, x):
    # 第一层
    x = F.relu(self.conv1(x))
    x = self.pool(x)

    # 第二层
    x = F.relu(self.conv2(x))
    x = self.pool(x)

    # 第三层
    x = F.relu(self.conv3(x))
    x = self.pool(x)

    # 展平
    x = x.view(–1, 128 * 4 * 4)

    # 全连接层
    x = F.relu(self.fc1(x))
    x = self.dropout(x)
    x = self.fc2(x)

    return x

    # 创建模型
    model = BasicCNN(num_classes=10).to(device)
    print("=== 基础CNN模型 ===")
    print(model)
    print(f"\\n总参数数量: {sum(p.numel() for p in model.parameters()):,}")

    3.2 改进CNN模型(带BatchNorm)

    我们添加批量归一化来改进模型。

    class ImprovedCNN(nn.Module):
    def __init__(self, num_classes=10):
    super(ImprovedCNN, self).__init__()
    # 卷积层
    self.conv1 = nn.Conv2d(3, 32, kernel_size=3, padding=1)
    self.bn1 = nn.BatchNorm2d(32)
    self.conv2 = nn.Conv2d(32, 64, kernel_size=3, padding=1)
    self.bn2 = nn.BatchNorm2d(64)
    self.conv3 = nn.Conv2d(64, 128, kernel_size=3, padding=1)
    self.bn3 = nn.BatchNorm2d(128)
    self.conv4 = nn.Conv2d(128, 256, kernel_size=3, padding=1)
    self.bn4 = nn.BatchNorm2d(256)

    # 池化层
    self.pool = nn.MaxPool2d(2, 2)

    # 全连接层
    self.fc1 = nn.Linear(256 * 2 * 2, 512)
    self.fc2 = nn.Linear(512, num_classes)

    # Dropout
    self.dropout = nn.Dropout(0.5)

    def forward(self, x):
    # 第一层
    x = F.relu(self.bn1(self.conv1(x)))
    x = self.pool(x)

    # 第二层
    x = F.relu(self.bn2(self.conv2(x)))
    x = self.pool(x)

    # 第三层
    x = F.relu(self.bn3(self.conv3(x)))
    x = self.pool(x)

    # 第四层
    x = F.relu(self.bn4(self.conv4(x)))
    x = self.pool(x)

    # 展平
    x = x.view(–1, 256 * 2 * 2)

    # 全连接层
    x = F.relu(self.fc1(x))
    x = self.dropout(x)
    x = self.fc2(x)

    return x

    # 创建改进模型
    improved_model = ImprovedCNN(num_classes=10).to(device)
    print("=== 改进CNN模型 ===")
    print(improved_model)
    print(f"\\n总参数数量: {sum(p.numel() for p in improved_model.parameters()):,}")

    第四步:训练配置

    4.1 定义损失函数和优化器

    # 损失函数
    criterion = nn.CrossEntropyLoss()

    # 优化器
    optimizer = optim.Adam(improved_model.parameters(), lr=0.001)

    # 学习率调度器
    scheduler = optim.lr_scheduler.ReduceLROnPlateau(
    optimizer, mode='max', factor=0.5, patience=5, verbose=True
    )

    print(f"损失函数: {criterion}")
    print(f"优化器: {optimizer}")
    print(f"学习率调度器: {scheduler}")

    4.2 定义训练和验证函数

    def train_epoch(model, train_loader, criterion, optimizer, device):
    """训练一个epoch"""
    model.train()
    running_loss = 0.0
    correct = 0
    total = 0

    for batch_idx, (images, labels) in enumerate(train_loader):
    images, labels = images.to(device), labels.to(device)

    # 前向传播
    outputs = model(images)
    loss = criterion(outputs, labels)

    # 反向传播和优化
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

    # 统计
    running_loss += loss.item()
    _, predicted = outputs.max(1)
    total += labels.size(0)
    correct += predicted.eq(labels).sum().item()

    epoch_loss = running_loss / len(train_loader)
    epoch_acc = 100. * correct / total

    return epoch_loss, epoch_acc

    def validate(model, val_loader, criterion, device):
    """验证模型"""
    model.eval()
    running_loss = 0.0
    correct = 0
    total = 0

    with torch.no_grad():
    for images, labels in val_loader:
    images, labels = images.to(device), labels.to(device)

    # 前向传播
    outputs = model(images)
    loss = criterion(outputs, labels)

    # 统计
    running_loss += loss.item()
    _, predicted = outputs.max(1)
    total += labels.size(0)
    correct += predicted.eq(labels).sum().item()

    epoch_loss = running_loss / len(val_loader)
    epoch_acc = 100. * correct / total

    return epoch_loss, epoch_acc

    4.3 训练循环

    def train_model(model, train_loader, val_loader, criterion, optimizer,
    scheduler, device, num_epochs=50, early_stopping_patience=15):
    """完整的训练循环"""

    # 记录训练历史
    history = {
    'train_loss': [],
    'train_acc': [],
    'val_loss': [],
    'val_acc': []
    }

    # 早停法
    best_val_acc = 0.0
    best_model_state = None
    patience_counter = 0

    for epoch in range(num_epochs):
    # 训练
    train_loss, train_acc = train_epoch(
    model, train_loader, criterion, optimizer, device
    )

    # 验证
    val_loss, val_acc = validate(
    model, val_loader, criterion, device
    )

    # 记录历史
    history['train_loss'].append(train_loss)
    history['train_acc'].append(train_acc)
    history['val_loss'].append(val_loss)
    history['val_acc'].append(val_acc)

    # 更新学习率
    scheduler.step(val_acc)

    # 打印进度
    print(f'Epoch [{epoch+1}/{num_epochs}] '
    f'Train Loss: {train_loss:.4f}, Train Acc: {train_acc:.2f}% | '
    f'Val Loss: {val_loss:.4f}, Val Acc: {val_acc:.2f}%')

    # 保存最佳模型
    if val_acc > best_val_acc:
    best_val_acc = val_acc
    best_model_state = model.state_dict()
    patience_counter = 0
    else:
    patience_counter += 1

    # 早停
    if patience_counter >= early_stopping_patience:
    print(f'\\n早停:{patience_counter} epochs后停止训练')
    break

    # 加载最佳模型
    if best_model_state is not None:
    model.load_state_dict(best_model_state)

    return model, history

    第五步:模型训练

    5.1 训练模型

    print("=== 开始训练 ===")
    print(f"训练集大小: {len(train_dataset)}")
    print(f"验证集大小: {len(val_dataset)}")
    print(f"批次大小: {batch_size}")
    print(f"最大epochs: 50")
    print(f"早停patience: 15\\n")

    # 训练模型
    trained_model, history = train_model(
    improved_model, train_loader, val_loader,
    criterion, optimizer, scheduler, device,
    num_epochs=50, early_stopping_patience=15
    )

    print(f"\\n训练完成!")
    print(f"最佳验证准确率: {max(history['val_acc']):.2f}%")

    5.2 可视化训练历史

    def plot_training_history(history):
    """可视化训练历史"""
    fig, axes = plt.subplots(1, 2, figsize=(14, 5))

    # 损失曲线
    axes[0].plot(history['train_loss'], label='训练损失', linewidth=2)
    axes[0].plot(history['val_loss'], label='验证损失', linewidth=2)
    axes[0].set_xlabel('Epoch')
    axes[0].set_ylabel('Loss')
    axes[0].set_title('训练和验证损失')
    axes[0].legend()
    axes[0].grid(True, alpha=0.3)

    # 准确率曲线
    axes[1].plot(history['train_acc'], label='训练准确率', linewidth=2)
    axes[1].plot(history['val_acc'], label='验证准确率', linewidth=2)
    axes[1].set_xlabel('Epoch')
    axes[1].set_ylabel('Accuracy (%)')
    axes[1].set_title('训练和验证准确率')
    axes[1].legend()
    axes[1].grid(True, alpha=0.3)

    plt.tight_layout()
    plt.show()

    print("=== 训练历史可视化 ===")
    plot_training_history(history)

    第六步:模型评估

    6.1 测试集评估

    def evaluate_model(model, test_loader, device):
    """在测试集上评估模型"""
    model.eval()
    all_predictions = []
    all_labels = []

    with torch.no_grad():
    for images, labels in test_loader:
    images, labels = images.to(device), labels.to(device)
    outputs = model(images)
    _, predicted = outputs.max(1)

    all_predictions.extend(predicted.cpu().numpy())
    all_labels.extend(labels.cpu().numpy())

    all_predictions = np.array(all_predictions)
    all_labels = np.array(all_labels)

    # 计算准确率
    accuracy = 100. * (all_predictions == all_labels).mean()

    print(f"=== 测试集评估 ===")
    print(f"总体准确率: {accuracy:.2f}%")

    # 分类报告
    print(f"\\n分类报告:")
    print(classification_report(all_labels, all_predictions,
    target_names=classes, digits=4))

    # 混淆矩阵
    cm = confusion_matrix(all_labels, all_predictions)

    plt.figure(figsize=(10, 8))
    sns.heatmap(cm, annot=True, fmt='d', cmap='Blues',
    xticklabels=classes, yticklabels=classes)
    plt.xlabel('预测标签')
    plt.ylabel('真实标签')
    plt.title('混淆矩阵')
    plt.show()

    return accuracy, all_predictions, all_labels

    # 评估模型
    test_accuracy, predictions, labels = evaluate_model(trained_model, test_loader, device)

    6.2 各类别准确率分析

    def analyze_class_accuracy(labels, predictions, classes):
    """分析各类别的准确率"""
    class_accuracy = {}

    for i, class_name in enumerate(classes):
    class_mask = (labels == i)
    class_correct = (predictions[class_mask] == i).sum()
    class_total = class_mask.sum()
    class_acc = 100. * class_correct / class_total if class_total > 0 else 0
    class_accuracy[class_name] = {
    'correct': class_correct,
    'total': class_total,
    'accuracy': class_acc
    }

    # 排序并显示
    sorted_classes = sorted(class_accuracy.items(),
    key=lambda x: x[1]['accuracy'], reverse=True)

    print("=== 各类别准确率 ===")
    for class_name, metrics in sorted_classes:
    print(f"{class_name:6s}: {metrics['accuracy']:6.2f}% "
    f"({metrics['correct']:4d}/{metrics['total']:4d})")

    return class_accuracy

    class_accuracy = analyze_class_accuracy(labels, predictions, classes)

    6.3 错误案例分析

    def analyze_errors(model, test_loader, device, num_errors=16):
    """分析错误案例"""
    model.eval()
    errors = []

    with torch.no_grad():
    for images, true_labels in test_loader:
    images, true_labels = images.to(device), true_labels.to(device)
    outputs = model(images)
    _, predicted_labels = outputs.max(1)

    # 找到错误预测
    incorrect_mask = predicted_labels != true_labels
    for i in range(len(images)):
    if incorrect_mask[i]:
    errors.append({
    'image': images[i].cpu(),
    'true_label': true_labels[i].item(),
    'predicted_label': predicted_labels[i].item()
    })

    # 随机选择一些错误案例
    if len(errors) > num_errors:
    error_indices = np.random.choice(len(errors), num_errors, replace=False)
    selected_errors = [errors[i] for i in error_indices]
    else:
    selected_errors = errors

    # 可视化
    fig, axes = plt.subplots(4, 4, figsize=(12, 12))
    axes = axes.flatten()

    for idx, error in enumerate(selected_errors):
    if idx < 16:
    img = error['image'].permute(1, 2, 0) * 0.5 + 0.5
    axes[idx].imshow(img)
    axes[idx].set_title(
    f'真实: {classes[error["true_label"]]}\\n'
    f'预测: {classes[error["predicted_label"]]}',
    fontsize=8
    )
    axes[idx].axis('off')

    plt.suptitle('错误案例分析', fontsize=14)
    plt.tight_layout()
    plt.show()

    print(f"总错误数量: {len(errors)}")
    print(f"显示的错误案例: {len(selected_errors)}")

    print("=== 错误案例分析 ===")
    analyze_errors(trained_model, test_loader, device, num_errors=16)

    第七步:模型优化

    7.1 学习率调优

    我们尝试不同的学习率策略。

    # 定义不同学习率
    learning_rates = [0.001, 0.0005, 0.0001]

    results = []

    for lr in learning_rates:
    print(f"\\n=== 测试学习率: {lr} ===")

    # 创建新模型
    model = ImprovedCNN(num_classes=10).to(device)

    # 优化器
    optimizer = optim.Adam(model.parameters(), lr=lr)
    scheduler = optim.lr_scheduler.ReduceLROnPlateau(
    optimizer, mode='max', factor=0.5, patience=5
    )

    # 训练(较少epochs以节省时间)
    trained_model, history = train_model(
    model, train_loader, val_loader,
    criterion, optimizer, scheduler, device,
    num_epochs=30, early_stopping_patience=10
    )

    # 评估
    val_acc = max(history['val_acc'])
    results.append({'lr': lr, 'val_acc': val_acc})
    print(f"最佳验证准确率: {val_acc:.2f}%")

    # 总结结果
    print("\\n=== 学习率对比 ===")
    for result in results:
    print(f"学习率 {result['lr']}: {result['val_acc']:.2f}%")

    # 选择最佳学习率
    best_result = max(results, key=lambda x: x['val_acc'])
    print(f"\\n最佳学习率: {best_result['lr']} (准确率: {best_result['val_acc']:.2f}%)")

    7.2 数据增强对比

    对比有无数据增强的效果。

    # 不使用数据增强
    train_transform_no_aug = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5])
    ])

    # 重新加载数据
    train_dataset_no_aug = datasets.CIFAR10(
    root='./data', train=True, download=True, transform=train_transform_no_aug
    )

    train_dataset_no_aug, _ = random_split(
    train_dataset_no_aug, [train_size, val_size],
    generator=torch.Generator().manual_seed(42)
    )

    train_loader_no_aug = DataLoader(train_dataset_no_aug, batch_size=batch_size, shuffle=True)

    # 训练无增强模型
    print("\\n=== 训练无数据增强模型 ===")
    model_no_aug = ImprovedCNN(num_classes=10).to(device)
    optimizer = optim.Adam(model_no_aug.parameters(), lr=0.001)
    scheduler = optim.lr_scheduler.ReduceLROnPlateau(
    optimizer, mode='max', factor=0.5, patience=5
    )

    trained_model_no_aug, history_no_aug = train_model(
    model_no_aug, train_loader_no_aug, val_loader,
    criterion, optimizer, scheduler, device,
    num_epochs=30, early_stopping_patience=10
    )

    # 对比
    print("\\n=== 数据增强对比 ===")
    print(f"有数据增强: {max(history['val_acc']):.2f}%")
    print(f"无数据增强: {max(history_no_aug['val_acc']):.2f}%")
    print(f"提升: {max(history['val_acc']) – max(history_no_aug['val_acc']):.2f}%")

    第八步:模型保存与加载

    8.1 保存模型

    # 保存模型参数
    torch.save(trained_model.state_dict(), 'cifar10_cnn_model.pth')
    print("模型参数已保存到 cifar10_cnn_model.pth")

    # 保存完整模型(包括优化器状态)
    checkpoint = {
    'epoch': len(history['train_loss']),
    'model_state_dict': trained_model.state_dict(),
    'optimizer_state_dict': optimizer.state_dict(),
    'scheduler_state_dict': scheduler.state_dict(),
    'val_acc': max(history['val_acc']),
    'history': history
    }
    torch.save(checkpoint, 'cifar10_cnn_checkpoint.pth')
    print("完整检查点已保存到 cifar10_cnn_checkpoint.pth")

    8.2 加载模型

    # 加载模型参数
    loaded_model = ImprovedCNN(num_classes=10).to(device)
    loaded_model.load_state_dict(torch.load('cifar10_cnn_model.pth'))
    loaded_model.eval()
    print("模型已加载")

    # 加载检查点
    checkpoint = torch.load('cifar10_cnn_checkpoint.pth')
    print(f"检查点信息:")
    print(f" Epoch: {checkpoint['epoch']}")
    print(f" 验证准确率: {checkpoint['val_acc']:.2f}%")

    第九步:模型预测

    9.1 单张图像预测

    def predict_single_image(model, image, device):
    """预测单张图像"""
    model.eval()

    # 添加batch维度
    image = image.unsqueeze(0).to(device)

    # 预测
    with torch.no_grad():
    output = model(image)
    probabilities = F.softmax(output, dim=1)
    confidence, predicted = torch.max(probabilities, 1)

    return predicted.item(), confidence.item()

    # 随机选择测试图像进行预测
    test_image, test_label = test_dataset[np.random.randint(len(test_dataset))]
    predicted_label, confidence = predict_single_image(
    loaded_model, test_image, device
    )

    # 可视化
    plt.figure(figsize=(4, 4))
    plt.imshow(test_image.permute(1, 2, 0) * 0.5 + 0.5)
    plt.title(f'真实: {classes[test_label]}\\n预测: {classes[predicted_label]}\\n置信度: {confidence:.2%}')
    plt.axis('off')
    plt.show()

    9.2 批量预测

    def predict_batch(model, images, device):
    """批量预测"""
    model.eval()

    images = images.to(device)

    with torch.no_grad():
    outputs = model(images)
    probabilities = F.softmax(outputs, dim=1)
    confidences, predicted = torch.max(probabilities, 1)

    return predicted.cpu().numpy(), confidences.cpu().numpy()

    # 预测一批图像
    batch_images, batch_labels = next(iter(test_loader))
    predictions, confidences = predict_batch(loaded_model, batch_images, device)

    # 可视化
    fig, axes = plt.subplots(4, 4, figsize=(12, 12))
    axes = axes.flatten()

    for idx in range(16):
    img = batch_images[idx].permute(1, 2, 0) * 0.5 + 0.5
    axes[idx].imshow(img)
    axes[idx].set_title(
    f'真实: {classes[batch_labels[idx]]}\\n'
    f'预测: {classes[predictions[idx]]}\\n'
    f'置信度: {confidences[idx]:.2%}',
    fontsize=8
    )
    axes[idx].axis('off')

    plt.tight_layout()
    plt.show()

    第十步:项目总结

    10.1 最终结果

    # 在测试集上最终评估
    final_test_accuracy, _, _ = evaluate_model(loaded_model, test_loader, device)

    print("=== 项目总结 ===")
    print(f"\\n最终测试准确率: {final_test_accuracy:.2f}%")
    print(f"\\n模型参数:")
    print(f" 总参数数量: {sum(p.numel() for p in loaded_model.parameters()):,}")
    print(f" 可训练参数: {sum(p.numel() for p in loaded_model.parameters() if p.requires_grad):,}")

    print(f"\\n训练配置:")
    print(f" 批次大小: {batch_size}")
    print(f" 优化器: Adam")
    print(f" 学习率: {optimizer.param_groups[0]['lr']}")
    print(f" 损失函数: CrossEntropyLoss")

    print(f"\\n训练历史:")
    print(f" 训练epochs: {len(history['train_loss'])}")
    print(f" 最佳验证准确率: {max(history['val_acc']):.2f}%")

    10.2 经验总结

    print("=== 经验总结 ===")

    print("\\n1. 数据增强的重要性:")
    print(" – 有数据增强: ~80%准确率")
    print(" – 无数据增强: ~70%准确率")
    print(" – 提升: ~10%")

    print("\\n2. 模型架构选择:")
    print(" – 基础CNN: 4层卷积")
    print(" – 改进CNN: +BatchNorm")
    print(" – 更深的网络需要更多数据和训练时间")

    print("\\n3. 训练技巧:")
    print(" – 学习率调度: 自动调整学习率")
    print(" – 早停法: 防止过拟合")
    print(" – BatchNorm: 加速训练,提高稳定性")
    print(" – Dropout: 防止过拟合")

    print("\\n4. 调优方法:")
    print(" – 学习率: 0.001-0.0001范围")
    print(" – 批次大小: 128-256")
    print(" – 网络深度: 3-5层卷积")
    print(" – 数据增强: 根据任务调整")

    print("\\n5. 常见问题:")
    print(" – 过拟合: 数据增强、Dropout、正则化")
    print(" – 欠拟合: 增加模型复杂度、训练更久")
    print(" – 训练不稳定: 调整学习率、使用BatchNorm")

    10.3 改进方向

    print("=== 改进方向 ===")

    print("\\n1. 模型架构:")
    print(" – ResNet: 残差连接,更深的网络")
    print(" – DenseNet: 密集连接,更好的特征复用")
    print(" – MobileNet: 轻量级,适合移动端")

    print("\\n2. 训练技巧:")
    print(" – Mixup: 数据混合,提高泛化")
    print(" – CutMix: 图像裁剪混合")
    print(" – Label Smoothing: 标签平滑,防止过拟合")

    print("\\n3. 优化方法:")
    print(" – AdamW: 带权重衰减的Adam")
    print(" – SGD with Momentum: 更稳定的收敛")
    print(" – 学习率warmup: 初始阶段逐渐增加学习率")

    print("\\n4. 数据增强:")
    print(" – AutoAugment: 自动搜索最优增强策略")
    print(" – RandAugment: 随机增强")
    print(" – CutMix: 裁剪和混合图像")

    print("\\n5. 集成方法:")
    print(" – 模型集成: 多个模型投票")
    print(" – 测试时增强(TTA): 测试时应用增强")
    print(" – 快照集成: 同一模型不同快照")

    print("\\n6. 迁移学习:")
    print(" – ImageNet预训练模型")
    print(" – 微调策略")
    print(" – 特征提取")

    10.4 项目收获

    通过这个项目,我们学到了:

  • 完整的深度学习流程:从数据准备到模型部署
  • CNN的实际应用:如何设计和训练CNN模型
  • 训练技巧:数据增强、学习率调度、早停法等
  • 模型调优:如何分析模型性能和改进模型
  • PyTorch实战:深度学习框架的实际应用
  • 这个准确率虽然在CIFAR-10上不算顶尖,但对于一个入门项目来说已经相当不错。如果继续深入,可以通过更复杂的模型架构和更精细的调优达到90%以上的准确率。

    下一步

    恭喜你完成了第一个深度学习实战项目!接下来你可以:

  • 尝试更复杂的模型架构(ResNet、VGG等)
  • 探索其他计算机视觉任务(目标检测、语义分割)
  • 学习序列建模(RNN、LSTM、Transformer)
  • 尝试自然语言处理任务(文本分类、机器翻译)
  • 深度学习之旅才刚刚开始,继续加油!

    赞(0)
    未经允许不得转载:171主机测评 » 第18章:实战项目3 - 图像分类(CIFAR-10)
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址