目录
[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):由全连接层组成,负责将特征映射为概率。
-
代码关键:
pythonself.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):
-
Conv2d: 提取特征(通道变多)。
-
BatchNorm2d: 稳定分布(可选,强烈建议)。
-
ReLU: 引入非线性。
-
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()