DAY47 简单CNN

目录

[1. 数据增强 (Data Augmentation)](#1. 数据增强 (Data Augmentation))

[2. 卷积神经网络定义的写法](#2. 卷积神经网络定义的写法)

[3. Batch 归一化 (Batch Normalization / BN)](#3. Batch 归一化 (Batch Normalization / BN))

[4. 特征图 (Feature Map)](#4. 特征图 (Feature Map))

[5. 调度器 (Scheduler)](#5. 调度器 (Scheduler))

[6. 卷积操作的标准流程 (Standard CNN Architecture)](#6. 卷积操作的标准流程 (Standard CNN Architecture))

附例:


1. 数据增强 (Data Augmentation)

  • 目的:让训练集"变大"、"变难",防止模型死记硬背(过拟合),提高泛化能力。

  • 写法 :通常定义在 transforms.Compose 中,仅用于训练集

  • 常用操作

    • RandomHorizontalFlip():随机水平翻转(模拟物体朝向不同)。

    • RandomCrop():随机裁剪(模拟物体位置偏移或不完整)。

    • ColorJitter():颜色抖动(模拟光照变化)。

  • 注意 :测试集通常只进行 Resize(如有必要)和 ToTensor + Normalize,保持数据的真实性。

2. 卷积神经网络定义的写法

通常继承 nn.Module,主要包含两个部分:

  • 特征提取层 (Feature Extractor):由卷积、BN、激活、池化组成,负责将图片"变厚"(通道增加)、"变小"(尺寸减小)。

  • 分类器 (Classifier):由全连接层组成,负责将特征映射为概率。

  • 代码关键

    python 复制代码
    self.conv1 = nn.Conv2d(in_channels=3, out_channels=32, kernel_size=3, padding=1)
    # 注意:下一层的 in_channels 必须等于上一层的 out_channels

3. Batch 归一化 (Batch Normalization / BN)

  • 定义nn.BatchNorm2d(num_features)

  • 作用

    • 强行将每一层输出的数据分布拉回到均值为 0、方差为 1 的正态分布。

    • 加速收敛:可以使用更大的学习率。

    • 防止梯度消失/爆炸

  • 参数num_features 必须等于上一层卷积输出的通道数 (即 out_channels)。

  • 位置 :通常放在 卷积层之后,激活函数之前(即 Conv -> BN -> ReLU)。

4. 特征图 (Feature Map)

  • 定义 :经过卷积核(Filter)提取后生成的 3D 张量

  • 本质 :它不再是原始的像素点(颜色),而是特征的集合(如这里有眼睛、那里有线条)。

  • 辨析:严格来说,卷积层的输出叫特征图。池化层的输出通常也被称为(下采样后的)特征图。

5. 调度器 (Scheduler)

  • 作用:在训练过程中动态调整学习率(Learning Rate)。

  • 策略

    • StepLR:每隔 N 个 Epoch,学习率乘以一个衰减系数(如 0.1)。

    • CosineAnnealingLR:余弦退火,学习率像余弦波一样下降。

  • 写法 :通常在 optimizer.step() 之后(即每个 Epoch 结束时)调用 scheduler.step()

6. 卷积操作的标准流程 (Standard CNN Architecture)

您总结的流程非常经典,我将其细化为更标准的"块(Block)"结构。现代 CNN 通常是由堆叠的 Block 组成的。

Step 1: 特征提取阶段 (Feature Extraction)

这个阶段通常由 N 个卷积块串联而成:

  • 输入 (Input) :

  • 卷积块 (Conv Block):

    1. Conv2d: 提取特征(通道变多)。

    2. BatchNorm2d: 稳定分布(可选,强烈建议)。

    3. ReLU: 引入非线性。

    4. MaxPool2d: 降维(高宽减半),保留最显著特征。

  • 循环上述块多次...

Step 2: 过渡阶段

  • Flatten : 将立体的特征图 拍扁成一维向量

Step 3: 分类阶段 (Classification / Dense Layers)

  • Linear: 全连接层。

  • ReLU: 激活。

  • Dropout : 随机丢弃(防止过拟合,仅训练时开启)。

  • Linear (Output): 输出最终类别的 Logits(通常不加激活,或加 Softmax)。

总结公式:

附例:

修改不同的调度器和 CNN 的结构,观察训练的差异

python 复制代码
import torch
import torch.nn as nn
import torch.optim as optim
from torchvision import datasets, transforms
from torch.utils.data import DataLoader
import matplotlib.pyplot as plt
import numpy as np

# ---------------------- 1. 环境与数据准备 ----------------------

# 设置中文字体支持 (可选,防止绘图乱码)
plt.rcParams["font.family"] = ["SimHei"]
plt.rcParams['axes.unicode_minus'] = False

# 检查设备
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(f"当前使用设备: {device}")

# 定义超参数
BATCH_SIZE = 64
LEARNING_RATE = 0.001
EPOCHS = 20

# 数据增强与预处理
# 训练集:增强策略
train_transform = transforms.Compose([
    transforms.RandomCrop(32, padding=4),       # 随机裁剪
    transforms.RandomHorizontalFlip(),          # 随机翻转
    transforms.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.2, hue=0.1), # 颜色抖动
    transforms.RandomRotation(15),              # 随机旋转
    transforms.ToTensor(),
    transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010))
])

# 测试集:仅标准化
test_transform = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010))
])

# 加载 CIFAR-10 数据集
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)

train_loader = DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=BATCH_SIZE, shuffle=False)

# ---------------------- 2. 定义 DeeperCNN 模型 ----------------------

class DeeperCNN(nn.Module):
    def __init__(self):
        super(DeeperCNN, self).__init__()
        
        # --- Block 1: 3 -> 32 ---
        self.conv1 = nn.Conv2d(3, 32, kernel_size=3, padding=1)
        self.bn1 = nn.BatchNorm2d(32)
        self.relu = nn.ReLU()
        self.pool = nn.MaxPool2d(2, 2) # 图片尺寸: 32 -> 16
        
        # --- Block 2: 32 -> 64 ---
        self.conv2 = nn.Conv2d(32, 64, kernel_size=3, padding=1)
        self.bn2 = nn.BatchNorm2d(64)
        # pool: 16 -> 8
        
        # --- Block 3: 64 -> 128 ---
        self.conv3 = nn.Conv2d(64, 128, kernel_size=3, padding=1)
        self.bn3 = nn.BatchNorm2d(128)
        # pool: 8 -> 4
        
        # --- Block 4 (新增): 128 -> 256 ---
        # 增加这一层可以提取更高级的语义特征
        self.conv4 = nn.Conv2d(128, 256, kernel_size=3, padding=1)
        self.bn4 = nn.BatchNorm2d(256)
        # pool: 4 -> 2
        
        # --- 全连接层 ---
        # 经过4次下采样(池化),图片尺寸变为 32 / (2^4) = 2
        # 展平后的维度 = 通道数(256) * 高(2) * 宽(2) = 1024
        self.flatten_size = 256 * 2 * 2
        
        self.fc1 = nn.Linear(self.flatten_size, 512)
        self.dropout = nn.Dropout(0.5) # 训练时丢弃 50%
        self.fc2 = nn.Linear(512, 10)  # 输出10个类别

    def forward(self, x):
        # 依次通过 4 个卷积块
        # 写法:Pool(ReLU(BN(Conv(x))))
        x = self.pool(self.relu(self.bn1(self.conv1(x))))
        x = self.pool(self.relu(self.bn2(self.conv2(x))))
        x = self.pool(self.relu(self.bn3(self.conv3(x))))
        x = self.pool(self.relu(self.bn4(self.conv4(x))))
        
        # 展平:保留 batch 维度,其余拉直
        x = x.view(-1, self.flatten_size)
        
        # 全连接分类
        x = self.dropout(self.relu(self.fc1(x)))
        x = self.fc2(x) # 输出 Logits
        return x

# 初始化模型并移至设备
model = DeeperCNN().to(device)

# ---------------------- 3. 定义损失、优化器与调度器 ----------------------

criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=LEARNING_RATE)

# 定义余弦退火调度器
# T_max: 周期长度,通常设为总 Epoch 数
# eta_min: 最小学习率,防止降为0
scheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=EPOCHS, eta_min=1e-5)

# ---------------------- 4. 训练与测试函数 ----------------------

def train_one_epoch(dataloader, model, loss_fn, optimizer, device):
    model.train() # 开启训练模式 (Dropout生效, BN更新)
    running_loss = 0.0
    correct = 0
    total = 0
    
    for batch, (X, y) in enumerate(dataloader):
        X, y = X.to(device), y.to(device)
        
        # 反向传播三部曲
        optimizer.zero_grad()
        pred = model(X)
        loss = loss_fn(pred, y)
        loss.backward()
        optimizer.step()
        
        # 统计信息
        running_loss += loss.item()
        _, predicted = pred.max(1)
        total += y.size(0)
        correct += predicted.eq(y).sum().item()
        
    avg_loss = running_loss / len(dataloader)
    acc = 100. * correct / total
    return avg_loss, acc

def test_one_epoch(dataloader, model, loss_fn, device):
    model.eval() # 开启评估模式 (Dropout关闭, BN锁定)
    test_loss = 0
    correct = 0
    total = 0
    
    with torch.no_grad(): # 不计算梯度
        for X, y in dataloader:
            X, y = X.to(device), y.to(device)
            pred = model(X)
            test_loss += loss_fn(pred, y).item()
            _, predicted = pred.max(1)
            total += y.size(0)
            correct += predicted.eq(y).sum().item()
            
    avg_loss = test_loss / len(dataloader)
    acc = 100. * correct / total
    return avg_loss, acc

# ---------------------- 5. 主循环 ----------------------

train_losses = []
test_accuracies = []
lr_history = []

print(f"开始训练 DeeperCNN (Total Epochs: {EPOCHS})...")

for epoch in range(EPOCHS):
    # 训练一个 Epoch
    train_loss, train_acc = train_one_epoch(train_loader, model, criterion, optimizer, device)
    
    # 调度器更新 (注意:Scheduler 在每个 Epoch 结束时更新)
    scheduler.step()
    current_lr = scheduler.get_last_lr()[0]
    
    # 测试一个 Epoch
    test_loss, test_acc = test_one_epoch(test_loader, model, criterion, device)
    
    # 记录数据
    train_losses.append(train_loss)
    test_accuracies.append(test_acc)
    lr_history.append(current_lr)
    
    print(f"Epoch [{epoch+1}/{EPOCHS}] "
          f"Loss: {train_loss:.4f} | "
          f"Train Acc: {train_acc:.2f}% | "
          f"Test Acc: {test_acc:.2f}% | "
          f"LR: {current_lr:.6f}")

print("训练完成!")

# ---------------------- 6. 结果可视化 ----------------------

plt.figure(figsize=(12, 4))

# 绘制损失曲线
plt.subplot(1, 2, 1)
plt.plot(train_losses, label='Training Loss')
plt.title('Training Loss per Epoch')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.grid(True)
plt.legend()

# 绘制学习率变化
plt.subplot(1, 2, 2)
plt.plot(lr_history, label='Learning Rate', color='orange')
plt.title('Learning Rate Schedule (Cosine Annealing)')
plt.xlabel('Epoch')
plt.ylabel('LR')
plt.grid(True)
plt.legend()

plt.tight_layout()
plt.show()
相关推荐
Hcoco_me2 小时前
大模型面试题46:在训练7B LLM时,如果使用AdamW优化器,那么它需要的峰值显存是多少?
开发语言·人工智能·深度学习·transformer·word2vec
Niuguangshuo2 小时前
理解MCMC、Metropolis-Hastings和Gibbs采样:从随机游走到贝叶斯推断
人工智能·神经网络·机器学习
棒棒的皮皮2 小时前
【深度学习】YOLO模型精度优化全攻略
人工智能·深度学习·yolo·计算机视觉
雍凉明月夜2 小时前
深度学习网络笔记Ⅴ(Transformer源码详解)
笔记·深度学习·transformer
田里的水稻3 小时前
E2E_基于端到端(E2E)的ViT神经网络模仿目标机械臂的示教动作一
人工智能·深度学习·神经网络
zstar-_3 小时前
DistilQwen2.5的原理与代码实践
人工智能·深度学习·机器学习
棒棒的皮皮3 小时前
【深度学习】YOLO实战之模型训练
人工智能·深度学习·yolo·计算机视觉
koo3643 小时前
pytorch深度学习笔记10
pytorch·笔记·深度学习
不惑_3 小时前
通俗理解经典CNN架构:LeNet
人工智能·神经网络·cnn
李泽辉_3 小时前
深度学习算法学习(四):深度学习-最简单实现一个自行构造的找规律(机器学习)任务
深度学习·学习·算法