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()
相关推荐
YelloooBlue26 分钟前
深度学习 SOP: conda通过命令快速构建指定版本tensorflow gpu环境。
深度学习·conda·tensorflow
AI即插即用41 分钟前
即插即用系列 | AAAI 2026 WaveFormer: 当视觉建模遇上波动方程,频率-时间解耦的新SOTA
图像处理·人工智能·深度学习·神经网络·计算机视觉·视觉检测
轻览月42 分钟前
【DL】复杂卷积神经网络Ⅰ
人工智能·神经网络·cnn
逄逄不是胖胖1 小时前
《动手学深度学习》-55-2RNN的简单实现
人工智能·深度学习
咚咚王者1 小时前
人工智能之核心技术 深度学习 第四章 循环神经网络(RNN)与序列模型
人工智能·rnn·深度学习
机 _ 长2 小时前
YOLO26 改进 | 训练策略 | 知识蒸馏 (Response + Feature + Relation)
python·深度学习·yolo·目标检测·机器学习·计算机视觉
美狐美颜sdk3 小时前
抖动特效在直播美颜sdk中的实现方式与优化思路
前端·图像处理·人工智能·深度学习·美颜sdk·直播美颜sdk·美颜api
Yeats_Liao3 小时前
异步推理架构:CPU-NPU流水线设计与并发效率提升
python·深度学习·神经网络·架构·开源
哥布林学者4 小时前
吴恩达深度学习课程五:自然语言处理 第三周:序列模型与注意力机制(一)seq2seq 模型
深度学习·ai
gsgbgxp5 小时前
WSL迁移至非系统盘
深度学习·ubuntu