LeNet

1

LeNet网络是第一个商用成功的CNN模型,属于CNN的始祖级别的开山之作。

其输入到输出结构如下:

输入层=》卷积层=》激活函数=》池化层=》

卷积层=》激活函数=》池化层 =》

展平操作=》

全连接层=》激活函数=》全连接层=》输出层。

它原始是7层结构,如上面结构带"层"的一共8个,但是输入层不算(为啥?因为输入层不需要计算)。

现在又有个说法------有意思------我的理解就是他们按照类来重新划分了,现在这个结构由卷积块和全连接块组成。

卷积块 有如下几部分组成:
卷积层=》激活函数=》池化层

这个卷积块的作用就是特征信息学习。

原始LeNet有2个卷积块,那么现代改进可以加深,比如你可以搞成3个、4个等卷积块。

全连接块 就是展平后面的全连接神经网络部分:
全连接层=》激活函数=》全连接层=》激活函数=》输出层

展平操作可以看作是全连接神经网络的数据输入层------这让我想起了因为是输入层所以不算层数的规则。

2 改进1

c 复制代码
# 导入PyTorch深度学习框架
import torch
# 导入PyTorch的神经网络模块
import torch.nn as nn
# 导入numpy用于科学计算
import numpy as np
# 导入matplotlib用于绘图
import matplotlib.pyplot as plt
# 从torchsummary导入模型结构可视化工具
from torchsummary import summary
# 从torchvision.datasets导入CIFAR10数据集
from torchvision.datasets import CIFAR10
# 从torchvision.transforms导入数据转换模块
from torchvision.transforms import Compose, ToTensor, Normalize
# 从torch.utils.data导入数据加载相关模块
from torch.utils.data import DataLoader, Dataset
# 导入PyTorch的优化器模块
import torch.optim as optim
# 导入操作系统模块,用于文件和目录操作
import os
# 从datetime导入日期时间模块
from datetime import datetime
# 导入随机数模块
import random

# ========== 配置宏定义 ==========
# 注释行,说明下面是配置定义
# 设置为True使用GPU,False使用CPU
USE_GPU = True
# 定义批量大小,每次训练128个样本
BATCH_SIZE = 128
# 定义训练的总轮数
EPOCHS = 50
# 定义初始学习率
LEARNING_RATE = 0.001
# 是否保存模型的开关
SAVE_MODEL = True
# 模型保存的目录路径
MODEL_DIR = "./models"
# 随机种子,用于保证结果可重复
SEED = 42

# 检查CUDA是否可用(CUDA是NVIDIA的GPU计算平台)
cuda_available = torch.cuda.is_available()
# 打印CUDA可用性信息
print(f"CUDA Available: {cuda_available}")

# ===============================

# 设置随机种子,确保可重复性
def set_seed(seed=42):
    # 设置Python内置random模块的随机种子
    random.seed(seed)
    # 设置numpy的随机种子
    np.random.seed(seed)
    # 设置PyTorch的CPU随机种子
    torch.manual_seed(seed)
    # 如果CUDA可用
    if cuda_available:
        # 设置当前GPU的随机种子
        torch.cuda.manual_seed(seed)
        # 设置所有GPU的随机种子
        torch.cuda.manual_seed_all(seed)
    # 设置cuDNN确定性模式,保证结果可重复
    torch.backends.cudnn.deterministic = True
    # 关闭cuDNN基准测试
    torch.backends.cudnn.benchmark = False

# 调用函数设置随机种子
set_seed(SEED)

# 根据配置选择设备
if USE_GPU and cuda_available:
    # 如果配置使用GPU且GPU可用,选择CUDA设备
    device = torch.device("cuda")
    # 打印GPU名称
    print(f"Using GPU: {torch.cuda.get_device_name(0)}")
    # 打印GPU属性
    print(f"GPU Properties: {torch.cuda.get_device_properties(0)}")
else:
    # 否则使用CPU设备
    device = torch.device("cpu")
    # 打印使用CPU信息
    print("Using CPU")

# 数据集加载
# 加载CIFAR10训练数据集
cifar10_train_dataset = CIFAR10(root='./data',  # 数据集保存路径
                               train=True,      # 加载训练集
                               download=True,   # 如果本地没有则下载
                               # 数据转换:将PIL图像转换为张量
                               transform=Compose([ToTensor()]))
# 创建训练数据加载器
cifar10_train_loader = DataLoader(cifar10_train_dataset,  # 要加载的数据集
                                 batch_size=BATCH_SIZE,   # 批量大小
                                 shuffle=True,            # 打乱数据顺序
                                 num_workers=0)          # 加载数据的进程数

# 加载CIFAR10测试数据集
cifar10_test_dataset = CIFAR10(root='./data',  # 数据集保存路径
                              train=False,     # 加载测试集
                              download=True,   # 如果本地没有则下载
                              # 数据转换:将PIL图像转换为张量
                              transform=Compose([ToTensor()]))
# 创建测试数据加载器
cifar10_test_loader = DataLoader(cifar10_test_dataset,  # 要加载的数据集
                                batch_size=BATCH_SIZE,  # 批量大小
                                shuffle=False,          # 测试集不打乱
                                num_workers=0)          # 加载数据的进程数

# 打印训练集数据的形状
print(f"Train dataset shape: {cifar10_train_dataset.data.shape}")
# 打印测试集数据的形状
print(f"Test dataset shape: {cifar10_test_dataset.data.shape}")
# 打印数据集类别名称
print(f"Classes: {cifar10_train_dataset.classes}")
# 打印类别到索引的映射
print(f"Class to index: {cifar10_train_dataset.class_to_idx}")

# 获取测试集的第一个样本
img, lb = cifar10_test_dataset[0]
# 打印图像的形状
print(f"Image shape: {img.shape}")
# 打印标签和对应的类别名称
print(f"Label: {lb} ({cifar10_train_dataset.classes[lb]})")


# 定义干净的LeNet模型类
class CleanLenet(nn.Module):
    # 类的初始化函数
    def __init__(self):
        # 调用父类的初始化方法
        super().__init__()
        # 定义第一个卷积层:输入通道3(RGB),输出通道6,卷积核大小5×5
        self.conv1 = nn.Conv2d(3, 6, 5)
        # 定义第一个最大池化层:池化窗口2×2,步长2
        self.pool1 = nn.MaxPool2d(2, 2)
        # 定义第二个卷积层:输入通道6,输出通道16,卷积核大小5×5
        self.conv2 = nn.Conv2d(6, 16, 5)
        # 定义第二个最大池化层
        self.pool2 = nn.MaxPool2d(2, 2)
        # 定义Dropout层,丢弃概率0.25
        self.dropout = nn.Dropout(0.25)
        # 定义第一个全连接层:输入维度16×5×5=400,输出120
        self.fc1 = nn.Linear(16 * 5 * 5, 120)
        # 定义第二个全连接层:输入120,输出84
        self.fc2 = nn.Linear(120, 84)
        # 定义第三个全连接层:输入84,输出10(CIFAR10有10个类别)
        self.fc3 = nn.Linear(84, 10)

    # 定义前向传播函数
    def forward(self, x):
        # 第一个卷积块:卷积 -> ReLU激活 -> 池化
        x = torch.relu(self.conv1(x))
        x = self.pool1(x)
        # 第二个卷积块:卷积 -> ReLU激活 -> 池化
        x = torch.relu(self.conv2(x))
        x = self.pool2(x)
        # 将特征图展平为一维向量
        x = x.view(x.size(0), -1)
        # 第一个全连接层 -> ReLU激活 -> Dropout
        x = torch.relu(self.fc1(x))
        x = self.dropout(x)
        # 第二个全连接层 -> ReLU激活 -> Dropout
        x = torch.relu(self.fc2(x))
        x = self.dropout(x)
        # 第三个全连接层(输出层)
        x = self.fc3(x)
        # 返回输出
        return x


# 定义模型评估函数
def evaluate(model, test_loader, device):
    """评估模型在测试集上的性能"""
    # 将模型设置为评估模式
    model.eval()
    # 初始化正确预测的数量
    correct = 0
    # 初始化总样本数
    total = 0

    # 不计算梯度,节省内存
    with torch.no_grad():
        # 遍历测试数据加载器
        for images, labels in test_loader:
            # 将图像数据移动到指定设备
            images = images.to(device)
            # 将标签数据移动到指定设备
            labels = labels.to(device)

            # 前向传播,获取模型输出
            outputs = model(images)
            # 获取预测结果(最大概率的索引)
            _, predicted = torch.max(outputs.data, 1)

            # 累加总样本数
            total += labels.size(0)
            # 累加正确预测的样本数
            correct += (predicted == labels).sum().item()

    # 计算准确率百分比
    accuracy = 100 * correct / total
    # 返回准确率
    return accuracy


# 定义模型训练函数
def train(model, train_loader, test_loader, epochs, device, model_name="model"):
    """训练函数,支持GPU/CPU,并保存最佳模型"""
    # 将模型移动到指定设备
    model = model.to(device)

    # 定义Adam优化器
    optimizer = optim.Adam(model.parameters(), lr=LEARNING_RATE)
    # 定义学习率调度器(当验证损失不再下降时降低学习率)
    scheduler = optim.lr_scheduler.ReduceLROnPlateau(optimizer, mode='min',
                                                     factor=0.1, patience=5, verbose=True)
    # 定义交叉熵损失函数
    criterion = nn.CrossEntropyLoss()

    # 记录训练损失的列表
    train_losses = []
    # 记录验证准确率的列表
    val_accuracies = []
    # 初始化最佳准确率
    best_accuracy = 0.0
    # 初始化最佳模型路径
    best_model_path = None

    # 创建模型保存目录
    if SAVE_MODEL and not os.path.exists(MODEL_DIR):
        os.makedirs(MODEL_DIR)

    # 打印训练信息
    print(f"\n开始训练,共 {epochs} 个epochs,batch size: {BATCH_SIZE}")
    print(f"学习率: {LEARNING_RATE}")
    print(f"随机种子: {SEED}")
    print(f"训练设备: {device}")
    print("-" * 60)

    # 开始训练循环
    for epoch in range(epochs):
        # 将模型设置为训练模式
        model.train()
        # 初始化本轮损失累加值
        running_loss = 0.0
        # 初始化步数计数器
        steps = 0

        # 遍历训练数据加载器
        for batch_idx, (images, labels) in enumerate(train_loader):
            # 将数据移动到指定设备
            images = images.to(device)
            labels = labels.to(device)

            # 前向传播
            outputs = model(images)
            # 计算损失
            loss = criterion(outputs, labels)

            # 反向传播
            # 清空梯度
            optimizer.zero_grad()
            # 计算梯度
            loss.backward()
            # 更新参数
            optimizer.step()

            # 累加损失
            running_loss += loss.item()
            # 步数加1
            steps += 1

            # 每100个batch打印一次进度
            if batch_idx % 100 == 0:
                print(f'Epoch [{epoch + 1}/{epochs}], Step [{batch_idx + 1}/{len(train_loader)}], '
                      f'Loss: {loss.item():.4f}')

        # 计算本轮平均损失
        avg_loss = running_loss / steps
        # 将平均损失添加到列表
        train_losses.append(avg_loss)

        # 在验证集上评估模型
        val_accuracy = evaluate(model, test_loader, device)
        # 将验证准确率添加到列表
        val_accuracies.append(val_accuracy)

        # 学习率调整
        scheduler.step(avg_loss)

        # 保存最佳模型
        if SAVE_MODEL and val_accuracy > best_accuracy:
            # 更新最佳准确率
            best_accuracy = val_accuracy
            # 生成时间戳
            timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
            # 构建最佳模型保存路径
            best_model_path = os.path.join(MODEL_DIR, f"{model_name}_best_epoch{epoch + 1}_{best_accuracy:.2f}.pth")

            # 保存模型检查点
            torch.save({
                'epoch': epoch + 1,                     # 当前epoch
                'model_state_dict': model.state_dict(),  # 模型参数
                'optimizer_state_dict': optimizer.state_dict(),  # 优化器状态
                'loss': avg_loss,                       # 当前损失
                'accuracy': val_accuracy,               # 当前准确率
                'seed': SEED,                           # 随机种子
            }, best_model_path)

            # 打印保存信息
            print(f"✨ 保存最佳模型 -> 准确率: {val_accuracy:.2f}%")

        # 打印本轮训练信息
        print(f'Epoch: {epoch + 1:3d}/{epochs}, '
              f'Loss: {avg_loss:.6f}, '
              f'Val Accuracy: {val_accuracy:.2f}%, '
              f'LR: {optimizer.param_groups[0]["lr"]:.6f}')

    # 绘制训练曲线
    # 创建图形和子图,1行2列,图形大小12×4英寸
    fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 4))

    # 在第一个子图上绘制损失曲线
    ax1.plot(range(1, epochs + 1), train_losses, 'b-', linewidth=2, label='Training Loss')
    # 设置x轴标签
    ax1.set_xlabel('Epoch')
    # 设置y轴标签
    ax1.set_ylabel('Loss')
    # 设置子图标题
    ax1.set_title('Training Loss')
    # 显示网格,透明度0.3
    ax1.grid(True, alpha=0.3)
    # 显示图例
    ax1.legend()

    # 在第二个子图上绘制准确率曲线
    ax2.plot(range(1, epochs + 1), val_accuracies, 'g-', linewidth=2, label='Validation Accuracy')
    # 设置x轴标签
    ax2.set_xlabel('Epoch')
    # 设置y轴标签
    ax2.set_ylabel('Accuracy (%)')
    # 设置子图标题
    ax2.set_title('Validation Accuracy')
    # 显示网格,透明度0.3
    ax2.grid(True, alpha=0.3)
    # 显示图例
    ax2.legend()

    # 自动调整子图布局
    plt.tight_layout()
    # 显示图形
    plt.show()

    # 最终评估
    # 在测试集上评估最终模型
    final_accuracy = evaluate(model, test_loader, device)
    # 打印分割线
    print(f"\n{'=' * 60}")
    # 打印最终准确率
    print(f"训练完成!最终验证集准确率: {final_accuracy:.2f}%")
    # 打印最佳准确率
    print(f"最佳准确率: {best_accuracy:.2f}%")
    # 如果保存了最佳模型,打印路径
    if best_model_path:
        print(f"最佳模型保存在: {best_model_path}")
    # 打印分割线
    print(f"{'=' * 60}")

    # 返回训练好的模型、训练损失列表、验证准确率列表
    return model, train_losses, val_accuracies


# 程序入口
if __name__ == '__main__':
    # 创建模型实例
    model = CleanLenet()
    # 将模型移动到指定设备
    model = model.to(device)

    # 打印模型摘要
    print(f"\n{'=' * 60}")
    print(f"模型结构:")
    print(f"{'=' * 60}\n")

    # 根据设备类型打印模型摘要
    if device.type == 'cuda':
        # 在GPU上打印模型摘要
        summary(model, (3, 32, 32), device='cuda', batch_size=BATCH_SIZE)
    else:
        # 在CPU上打印模型摘要
        summary(model, (3, 32, 32), device='cpu', batch_size=BATCH_SIZE)

    # 训练模型
    trained_model, losses, accuracies = train(model, cifar10_train_loader,
                                              cifar10_test_loader, EPOCHS, device,
                                              model_name="cleanlenet_cifar10")

    # 保存最终模型
    if SAVE_MODEL:
        # 生成时间戳
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        # 构建最终模型保存路径
        final_model_path = os.path.join(MODEL_DIR, f"cleanlenet_final_{timestamp}.pth")
        # 保存模型状态字典
        torch.save(trained_model.state_dict(), final_model_path)
        # 打印保存信息
        print(f"最终模型保存在: {final_model_path}")

    # 打印设备信息
    print(f"\n{'=' * 60}")
    print(f"设备信息:")
    print(f"  设备: {device}")
    # 如果是GPU设备,打印GPU信息
    if device.type == 'cuda':
        print(f"  GPU名称: {torch.cuda.get_device_name(0)}")
        print(f"  已分配显存: {torch.cuda.memory_allocated(0) / 1024 ** 2:.2f} MB")
        print(f"  缓存显存: {torch.cuda.memory_reserved(0) / 1024 ** 2:.2f} MB")
    # 打印训练配置信息
    print(f"  随机种子: {SEED}")
    print(f"  Batch Size: {BATCH_SIZE}")
    print(f"  Epochs: {EPOCHS}")
    print(f"{'=' * 60}")
相关推荐
sld1682 小时前
农资行业B2B多租户商城系统推荐,适配农业经销商层级管理
大数据·人工智能
Mixtral4 小时前
2026年春招复盘记录工具测评:告别手动整理,AI自动生成求职总结
人工智能·面试·职场和发展·语音转文字·ai语音转文字
jaray8 小时前
PyCharm 2024.3.2 Professional 如何更换 PyPI 镜像源
ide·python·pycharm·pypi 镜像源
Psycho_MrZhang8 小时前
Neo4j Python SDK手册
开发语言·python·neo4j
Quintus五等升8 小时前
深度学习④|分类任务—VGG13
人工智能·经验分享·深度学习·神经网络·学习·机器学习·分类
2501_936146048 小时前
小型机械零件识别与分类--基于YOLO12-A2C2f-DFFN-DYT模型的创新实现
人工智能·分类·数据挖掘
web3.08889998 小时前
1688图片搜索API,相似商品精准推荐
开发语言·python
少云清9 小时前
【性能测试】15_JMeter _JMeter插件安装使用
开发语言·python·jmeter
天天讯通9 小时前
金融邀约实时质检:呼叫监控赋能客服主管
人工智能·金融