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()
相关推荐
Chasing__Dreams4 小时前
大模型应用开发--6--Transformer架构介绍
深度学习·架构·transformer
147API4 小时前
蒸馏模型版本升级怎么做,权重、评测器和服务配置一起管
人工智能·深度学习·蒸馏·模型蒸馏
XLYcmy5 小时前
京东 算法实习一面 上
rnn·神经网络·llm·微调·transformer·训练·对齐
chen_zn9510 小时前
《VLA 系列》π0.5 + KI | 知识隔离 | 离散与连续动作联合训练 | 论文与源码解析
人工智能·深度学习·注意力机制·vla
EasyGBS11 小时前
告别“通用AI”的三大痛点:国标GB28181公网平台EasyGBS+DLTM让企业拥有专属AI安防大脑
人工智能·深度学习·机器学习
就是一顿骚操作12 小时前
VGG:用小卷积块把 CNN 做深的经典解读
人工智能·深度学习·神经网络·cnn·论文解读
高洁0113 小时前
工信部教考中心证书
人工智能·深度学习·算法·机器学习·知识图谱
DogDaoDao14 小时前
NNVC-17.1 深度解析:神经网络视频编码的最新进展与性能全景
神经网络·音视频·视频编解码·h266·vvc·vtm·nnvc
hhzz15 小时前
《深度学习框架PyTorch入门与实践》系列:09-分布式与并行训练之DataParallel、DDP与Horovod
pytorch·分布式·深度学习
心运软件16 小时前
基于深度学习的宝石图像分类系统
人工智能·python·深度学习·机器学习·分类·数据挖掘