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()
相关推荐
程序员打怪兽12 小时前
详解Visual Transformer (ViT)网络模型
深度学习
CoovallyAIHub2 天前
仿生学突破:SILD模型如何让无人机在电力线迷宫中发现“隐形威胁”
深度学习·算法·计算机视觉
CoovallyAIHub2 天前
从春晚机器人到零样本革命:YOLO26-Pose姿态估计实战指南
深度学习·算法·计算机视觉
CoovallyAIHub2 天前
Le-DETR:省80%预训练数据,这个实时检测Transformer刷新SOTA|Georgia Tech & 北交大
深度学习·算法·计算机视觉
CoovallyAIHub2 天前
强化学习凭什么比监督学习更聪明?RL的“聪明”并非来自算法,而是因为它学会了“挑食”
深度学习·算法·计算机视觉
CoovallyAIHub3 天前
YOLO-IOD深度解析:打破实时增量目标检测的三重知识冲突
深度学习·算法·计算机视觉
用户1474853079743 天前
AI-动手深度学习环境搭建-d2l
深度学习
OpenBayes贝式计算3 天前
解决视频模型痛点,TurboDiffusion 高效视频扩散生成系统;Google Streetview 涵盖多个国家的街景图像数据集
人工智能·深度学习·机器学习
OpenBayes贝式计算3 天前
OCR教程汇总丨DeepSeek/百度飞桨/华中科大等开源创新技术,实现OCR高精度、本地化部署
人工智能·深度学习·机器学习
在人间耕耘4 天前
HarmonyOS Vision Kit 视觉AI实战:把官方 Demo 改造成一套能长期复用的组件库
人工智能·深度学习·harmonyos