卷积神经网络实验:从 MNIST 到 CIFAR-10


一、实验内容

二、实验过程

2.1 视频学习

2.2 代码练习

2.2.1 MNIST数据集分类

STEP 1:加载数据
  • 作用
    • 使用 torchvision.datasets.MNIST 下载并加载手写数字数据集(28×28灰度图像,类别 0–9)。
    • 利用 transforms.ToTensor()transforms.Normalize((0.1307,), (0.3081,)) 进行预处理。
    • 通过 DataLoader 封装成迭代器,方便批量训练(batch_size=64)。
    • 代码还显示了部分样本图像用于验证加载是否正确。
  • 原理
    • Tensor 转换:把图片转为 [0, 1] 浮点张量。
    • 标准化:减去均值 0.1307 并除以标准差 0.3081,使模型更容易收敛。
    • DataLoader:封装了自动打乱(shuffle=True)和多进程加载(num_workers)功能。
  • 思考
    • 数据加载是所有深度学习的入口,其效率会直接影响训练速度。
    • 标准化让不同输入维度具有近似分布,利于梯度传播。
    • 观察数据(plt.imshow)可以及时发现通道错误或归一化问题。
  • 实验结果如图1~2。
图1 选用GPU 图2 显示数据集中部分图像
STEP 2:创建网络结构
  • 作用

    • 定义两种神经网络

      • FC2Layer(全连接网络):由多层 Linear + ReLU 组成。

      • CNN(卷积网络):包含两层卷积 + 池化 + 两层全连接。

    • forward() 方法规定了数据的计算流程,自动触发 autograd 计算梯度。

  • 原理

    • 全连接网络(FNN)
      • 每个神经元与上一层所有节点相连,没有空间结构概念。
      • 优点:能拟合任意函数。
      • 缺点:参数多,忽略图像的空间局部特性。
    • 卷积神经网络(CNN)
      • 通过卷积核在局部窗口提取特征。
      • 池化层 (max_pool2d) 降采样、增强平移不变性。
      • 权重共享大大减少参数数量。
  • 思考

    • 这一步体现了从全连接到卷积的结构转变
    • CNN 的设计灵感源自视觉皮层,能自动学习局部纹理、边缘等特征。在相同参数量条件下(≈6422),CNN 的表现更好,说明结构优于数量
# === 7. A small Fully Connected network (FNN) ===
class FC2Layer(nn.Module):
"""
A simple fully-connected classifier for MNIST.
Input: 28x28 image flattened to 784-dim vector.
Architecture: Linear -> ReLU -> Linear -> ReLU -> Linear -> LogSoftmax
"""
def __init__(self, input_size=28*28, n_hidden=50, output_size=10):
super().__init__()
self.input_size = input_size
self.network = nn.Sequential(
nn.Linear(input_size, n_hidden),
nn.ReLU(),
nn.Linear(n_hidden, n_hidden),
nn.ReLU(),
nn.Linear(n_hidden, output_size),
nn.LogSoftmax(dim=1)
)

def forward(self, x):
# Flatten N x 1 x 28 x 28 to N x 784
x = x.view(-1, self.input_size)
return self.network(x)


# === 8. A small Convolutional Neural Network (CNN) ===
class CNN(nn.Module):
"""
A basic CNN for MNIST.
conv1: in_channels=1 -> out_channels=n_feature, kernel_size=5
conv2: in_channels=n_feature -> out_channels=n_feature, kernel_size=5
After two 2x2 max-pools, feature map size becomes (n_feature, 4, 4)
Then fully-connected layers to 50 -> 10 followed by log_softmax.
"""
def __init__(self, n_feature=20, output_size=10):
super().__init__()
self.n_feature = n_feature

self.conv1 = nn.Conv2d(in_channels=1, out_channels=n_feature, kernel_size=5)
self.conv2 = nn.Conv2d(in_channels=n_feature, out_channels=n_feature, kernel_size=5)

# After conv+pool twice: 28 -> 24 -> pool->12; 12 -> 8 -> pool->4
self.fc1 = nn.Linear(n_feature * 4 * 4, 50)
self.fc2 = nn.Linear(50, output_size)

def forward(self, x, verbose=False):
x = self.conv1(x)
x = F.relu(x)
x = F.max_pool2d(x, kernel_size=2)

x = self.conv2(x)
x = F.relu(x)
x = F.max_pool2d(x, kernel_size=2)

# Flatten to (N, n_feature*4*4)
x = x.view(-1, self.n_feature * 4 * 4)

x = self.fc1(x)
x = F.relu(x)
x = self.fc2(x)
x = F.log_softmax(x, dim=1)
return x

# === 9. Train and Test loops using NLL loss (model returns log-probabilities) ===
def train_one_epoch(model, optimizer, train_loader, log_interval=100):
"""Train the model for one epoch and print periodic loss logs."""
model.train()
total = 0
for batch_idx, (data, target) in enumerate(train_loader):
data, target = data.to(device), target.to(device)

optimizer.zero_grad()
output = model(data)
loss = F.nll_loss(output, target)
loss.backward()
optimizer.step()

total += len(data)
if batch_idx % log_interval == 0:
print(f"Train: [{total:>5}/{len(train_loader.dataset)} ({100*total/len(train_loader.dataset):5.1f}%)] "
f"Loss: {loss.item():.6f}")


@torch.no_grad()
def evaluate(model, test_loader):
"""Evaluate the model on test set: return (avg_loss, accuracy_float)."""
model.eval()
test_loss = 0.0
correct = 0
for data, target in test_loader:
data, target = data.to(device), target.to(device)
output = model(data)
# Sum loss over the batch to later average by total samples
test_loss += F.nll_loss(output, target, reduction='sum').item()
# Predicted class is the argmax of log-prob along dim=1
pred = output.argmax(dim=1)
correct += (pred == target).sum().item()

test_loss /= len(test_loader.dataset)
acc = 100.0 * correct / len(test_loader.dataset)
print(f"\nTest set: Average loss: {test_loss:.4f}, Accuracy: {correct}/{len(test_loader.dataset)} ({acc:.2f}%)\n")
return test_loss, acc
STEP 3: 在小型全连接网络上训练
  • 作用

    • 定义并训练一个小型全连接神经网络。
    • 输入层大小 28×28 = 784,输出 10 类(数字 0–9)。
    • 使用 SGD 优化器(学习率 0.01,动量 0.5)。
    • 打印训练过程中的 loss 与测试集准确率。
  • 原理

    • 全连接层 (Linear) 每个神经元都与上一层所有节点相连,没有局部空间结构。
    • ReLU 激活用于非线性变换,LogSoftmax 输出用于分类概率。
    • 网络靠学习全局灰度模式区分数字,而不理解像素位置关系。
  • 思考

    • 准确率约 87.73%,说明FNN能完成识别,但泛化有限,因为它忽略了图像的空间结构,参数利用效率较低。
    • 这一步提供了CNN 性能对照的基线。
  • 实验步骤见图3.

# === 10. Train the small FNN ===
input_size = 28*28
output_size = 10
n_hidden = 8 # you can try 8 or 50 to compare

model_fnn = FC2Layer(input_size=input_size, n_hidden=n_hidden, output_size=output_size).to(device)
print("FNN parameters:", get_n_params(model_fnn))

optimizer = optim.SGD(model_fnn.parameters(), lr=0.01, momentum=0.5)

epochs = 1 # start with 1 epoch like the lab; feel free to increase to 3–5 later
for epoch in range(1, epochs + 1):
print(f"=== Epoch {epoch} / {epochs} (FNN) ===")
train_one_epoch(model_fnn, optimizer, train_loader)
evaluate(model_fnn, test_loader)
STEP 4:在卷积神经网络上训练
  • 作用

    • 定义并训练一个小型 CNN (卷积、池化、全连接)。
    • 设置 n_features = 6 以控制参数量约6422,与 FNN 持平。
    • 对比同样参数规模下 CNN 与 FNN 的性能差异。
  • 原理

    • 卷积层利用局部感受野权值共享提取空间特征。
    • 池化层降低分辨率、增强平移不变性
    • 同参数条件下,CNN 能捕获边缘、笔画等局部模式,性能显著提升。
  • 思考

    • 测试准确率为95.55%,远高于 FNN 的 87.73%。
    • 证明 CNN 能够更有效地理解图像。
    • 关键原因:locality决定卷积核关注局部结构,stationarity使同一模式可出现在任意位置。
    • 说明卷积层带来了更强的归纳偏好。
  • 实验步骤见图4.

图3 在FNN上训练 图4 在CNN上训练
# === Train a small CNN whose parameter count ~= small FNN (about 6422) ===
n_features = 6 # number of feature maps to match ~6422 params

model_cnn = CNN(n_feature=n_features, output_size=10).to(device)
print("Number of parameters (CNN):", get_n_params(model_cnn))

optimizer = optim.SGD(model_cnn.parameters(), lr=0.01, momentum=0.5)

# one epoch to mirror the lab page; feel free to increase later
print("=== Train small CNN (n_features=6) ===")
train_one_epoch(model_cnn, optimizer, train_loader)
evaluate(model_cnn, test_loader)
STEP 5: 打乱像素顺序再次在两个网络上训练与测试
  • 作用

    • 随机生成像素置换 perm = torch.randperm(784),打乱每张图的像素顺序。
    • 可视化对比原图与打乱后的图像。
    • 分别在打乱数据上重新训练 FNN 和 CNN。
  • 原理

    • 打乱像素破坏了图像的空间邻接关系: CNN 失去局部相关性假设的支撑, FNN 仍可直接处理784维输入。
    • perm_pixel() 函数对每个 batch 执行相同置换, train_perm() / test_perm() 函数在输入前调用 perm_pixel()
  • 思考

    • 实验结果:FNN 准确率基本不变 (87.52%),CNN 准确率显著下降 (83.59%)。
    • 说明:FNN 不依赖空间结构,CNN 的性能提升源自局部性与平移平稳性假设。即卷积的强大依赖于图像的空间组织,一旦结构被破坏,其优势大大减小。
  • 实验步骤如图5-7.

图5 打乱图片像素顺序 图6 重新在FNN上训练
图7 重新在CNN上训练

2.2.2 CIFAR10 数据集分类

STEP 1:导入库、加载并预处理CIFAR-10数据、可视化图像
  • 作用

    • 导入PyTorch及其视觉扩展库torchvision,用于构建、训练和评估神经网络。
    • 使用torchvision.datasets.CIFAR10下载并加载CIFAR-10数据集(含10类彩色图像,每张大小32×32×3)。
    • 利用transforms.ToTensor()将像素从[0, 255]范围转换为[0, 1]浮点数;再通过transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))将像素映射到[-1, 1]范围。
    • 使用DataLoader按批次加载数据,提高训练效率,同时通过shuffle=True随机打乱顺序避免模型记忆样本序列。
    • 利用matplotlib可视化部分训练图像,验证数据加载与预处理是否正确。
  • 原理

    • 归一化原理:图像的每个通道执行 (x − mean)/std,当mean = 0.5、std = 0.5时,结果范围即为[-1, 1]。该操作能使数据分布更平衡,梯度下降更稳定。
    • DataLoader机制:将数据封装成可迭代对象,支持多线程num_workers)实现mini-batch训练;每个batch包含若干样本,有助于加速收敛并减小波动。
    • 可视化检查:反归一化(img × 0.5 + 0.5)恢复原始像素范围,确保模型输入正常。
  • 思考

    • 数据预处理是深度学习流程的起点。如果这一步出现错误(如归一化范围不正确或维度顺序颠倒),后续训练结果将毫无意义。
    • 通过可视化我们能直观判断数据的合理性,这是调试与研究中的理智检查。
    • 归一化与批加载体现了PyTorch在数据工程上的抽象与灵活性,也揭示了机器学习中数据质量比模型更重要的理念。
  • 实验代码如下,实验结果如图8.

# Download & build train/test datasets
trainset = torchvision.datasets.CIFAR10(
root='./data', train=True, download=True, transform=transform
)
testset = torchvision.datasets.CIFAR10(
root='./data', train=False, download=True, transform=transform
)

# DataLoaders
trainloader = torch.utils.data.DataLoader(
trainset, batch_size=64, shuffle=True, num_workers=2
)
testloader = torch.utils.data.DataLoader(
testset, batch_size=8, shuffle=False, num_workers=2
)

# Class names (as in tutorial)
classes = ('plane', 'car', 'bird', 'cat',
'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
# Helper: unnormalize and show a batch
def imshow(img):
plt.figure(figsize=(8, 8))
# Unnormalize: x in [-1,1] -> (x * 0.5 + 0.5) back to [0,1]
img = img * 0.5 + 0.5
npimg = img.numpy()
plt.imshow(np.transpose(npimg, (1, 2, 0)))
plt.axis('off')
plt.show()

# Get one batch from trainloader and visualize
dataiter = iter(trainloader)
images, labels = next(dataiter)
imshow(torchvision.utils.make_grid(images))

# Print labels for the first row (8 images)
for j in range(8):
print(classes[labels[j]])
STEP 2:定义LeNet网络结构
  • 作用

    • 构建一个CNN模型 LeNet,用于在CIFAR-10上进行图像分类。
    • 网络包含两个Conv和Pool用于特征提取,以及三个FC用于分类输出。
    • 指定损失函数为CrossEntropyLoss(),优化器为Adam(),学习率lr=0.001
  • 原理

    • 卷积层(Conv2d):通过滑动卷积核在局部区域内提取空间特征,如边缘、角点、纹理。
    • 激活函数(ReLU):引入非线性,提高网络表达能力。
    • 池化层(MaxPool2d):进行空间下采样,减少参数量并提升特征平移不变性。
    • 全连接层(Linear):将高维特征映射到类别空间,实现最终分类。
    • 反向传播与优化:利用Adam优化算法自动调整权重,以最小化交叉熵损失。
  • 思考

    • LeNet结构虽然简单,但它奠定了卷积神经网络的基本框架,是现代CNN的原型,相较于传统的全连接网络,LeNet充分利用了图像的空间局部性参数共享特性。
  • 定义代码如下。

class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
# Conv blocks
self.conv1 = nn.Conv2d(3, 6, kernel_size=5) # in: 3x32x32 -> 6x28x28
self.pool = nn.MaxPool2d(2, 2) # 6x28x28 -> 6x14x14
self.conv2 = nn.Conv2d(6, 16, kernel_size=5) # 6x14x14 -> 16x10x10 -> pool -> 16x5x5

# Fully-connected layers
self.fc1 = nn.Linear(16 * 5 * 5, 120) # 400 -> 120
self.fc2 = nn.Linear(120, 84) # 120 -> 84
self.fc3 = nn.Linear(84, 10) # 84 -> 10 (10 classes)

def forward(self, x):
# Feature extractor
x = self.pool(F.relu(self.conv1(x)))
x = self.pool(F.relu(self.conv2(x)))
# Flatten
x = x.view(-1, 16 * 5 * 5)
# Classifier
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x) # logits (no softmax needed for CrossEntropyLoss)
return x

# Move model to device
net = Net().to(device)

# Loss & Optimizer (as tutorial: CrossEntropy + Adam, lr=1e-3)
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(net.parameters(), lr=1e-3)

STEP 3:训练网络
  • 作用

    • 使用训练集数据迭代优化模型参数。
    • 每个epoch遍历全部训练样本,计算损失、反向传播梯度并更新权重。
    • 每100个mini-batch打印一次loss值,用于监控收敛趋势。
  • 原理

    • 前向传播(Forward Pass):输入图像通过网络层层变换,输出预测分布。
    • 损失函数(Loss):计算预测输出与真实标签之间的差异。
    • 反向传播(Backward Pass):自动求导(loss.backward())计算梯度。
    • 参数更新(Optimizer Step):Adam根据梯度方向与动量调整网络参数。
    • 多轮训练(Epoch):重复该过程直至loss收敛或达到预设次数。
  • 思考

    • 训练过程中,loss值的下降说明模型正在学习特征,若loss不降或震荡,可能是学习率或归一化存在问题。
    • 观察loss随时间变化可判断网络是否过拟合(训练loss持续下降而测试精度不升)。
  • 训练结果如图9.

图8 可视化部分训练图像 图9 训练网络
STEP 4:在测试集上预测与可视化
  • 作用

    • 从测试集中随机抽取一个batch的图像,输入训练好的模型,得到预测结果。
    • 可视化预测前后的图像与标签,用以观察分类准确度与模型表现。
  • 原理

    • 前向推理(Inference):调用net(images.to(device))进行预测,不计算梯度以节省内存。
    • 输出解释torch.max(outputs, 1)选择每行最大值对应的类别索引,即模型预测结果。
    • 可视化比较:打印真实标签与预测标签,对比判断模型是否识别正确。
  • 思考

    • CNN通过卷积核提取空间特征,因此对局部模式(如动物形态、车辆轮廓)较敏感。
    • 在CIFAR-10这样的小尺寸数据集上,模型可能会混淆视觉相似的类别(如猫与狗、飞机与鸟)。
  • 实验结果如图10.

图10 可视化预测
STEP 5:计算整体准确率
  • 作用

    • 对测试集的所有10000张图片进行预测,统计预测正确的数量,计算整体分类准确率。
    • 输出模型的总体性能指标。
  • 原理

    • 在评估阶段使用torch.no_grad()关闭梯度计算,加快推理速度并节省显存。

    • 每轮迭代累加预测正确样本数correct与总样本数total,最后计算准确率:$Accuracy = \frac{correct}{total} \times 100%$

    • 通过准确率可以定量衡量模型的泛化性能。

  • 思考

    • LeNet在CIFAR-10上的准确率通常约为55–60%,这说明其结构对复杂彩色图像的表达能力有限。
    • CNN的性能在很大程度上取决于网络深度、卷积核大小、归一化与优化策略。
  • 实验结果如图11,代码改进与结果如图12.

图11 计算准确率 图12 参数改进

2.2.3 VGG16对CIFAR10分类

STEP 1:定义 Dataloader
  • 作用

    • 从 CIFAR-10 数据集中加载图像与标签。
    • 对训练集应用数据增强(随机裁剪、水平翻转)以提升泛化能力。
    • 对所有图像进行标准化,确保输入分布稳定。
  • 原理

    • torchvision.datasets.CIFAR10 自动下载并生成 Dataset 对象。
    • DataLoader 负责按 batch 取数据并打乱顺序(shuffle=True)。
    • 图像预处理由 transforms.Compose 串联完成。
  • 思考

    • 小型数据集如 CIFAR-10 非常依赖数据增强,否则容易过拟合。
    • 合理的归一化能加快收敛并提高模型稳定性。
# Data augmentation for training to improve generalization
transform_train = transforms.Compose([
transforms.RandomCrop(32, padding=4), # random crop with padding
transforms.RandomHorizontalFlip(), # flip with 0.5 probability
transforms.ToTensor(), # [0,255] -> [0,1], HWC->CHW
transforms.Normalize((0.4914, 0.4822, 0.4465),
(0.2023, 0.1994, 0.2010)) # per-channel normalization
])

# Test transform: only tensor + normalization
transform_test = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.4914, 0.4822, 0.4465),
(0.2023, 0.1994, 0.2010))
])

# Download datasets (auto in Colab) and create loaders
trainset = torchvision.datasets.CIFAR10(root='./data', train=True,
download=True, transform=transform_train)
testset = torchvision.datasets.CIFAR10(root='./data', train=False,
download=True, transform=transform_test)

trainloader = torch.utils.data.DataLoader(trainset, batch_size=128,
shuffle=True, num_workers=2, pin_memory=True)
testloader = torch.utils.data.DataLoader(testset, batch_size=256,
shuffle=False, num_workers=2, pin_memory=True)

classes = ('plane', 'car', 'bird', 'cat', 'deer',
'dog', 'frog', 'horse', 'ship', 'truck')
print("Train batches:", len(trainloader), " Test batches:", len(testloader))
STEP 2:定义 VGG 网络
  • 作用

    • 构建一个基于 VGG16 结构的卷积神经网络,用于图像特征提取与分类。
    • 根据 CIFAR-10 输入尺寸(32×32)简化原 VGG16 网络。
  • 原理

    • 卷积层Conv和ReLU交替提取局部空间特征。
    • 池化层逐步缩小空间尺寸,提取高层语义。
    • 最终通过 Linear(512, 10) 输出 10 个类别的分类结果。
  • 思考

    • VGG 的设计理念是多层小卷积核 (3×3) 叠加等于大感受野与更少参数。
    • CIFAR-10 图像较小,因此最终 feature map 仅 1×1 即可。若用于更大图像,可恢复原始 VGG16 的 4096 节点全连接层。
  • 实验结果如图13.

class VGG(nn.Module):
"""
A compact VGG-like model for CIFAR-10.
We use a configuration close to VGG16 but adapted to 32x32 inputs.
"""
def __init__(self, num_classes: int = 10):
super().__init__()
# VGG16-ish: 64,64,M, 128,128,M, 256,256,M, 512,512,M, 512,512,M
# This gives 5 max pools -> spatial 32 -> 1 after pooling chain.
cfg = [64, 64, 'M', 128, 128, 'M',
256, 256, 'M', 512, 512, 'M',
512, 512, 'M']
self.features = self._make_layers(cfg)
# After 5 pools on 32x32 -> 1x1 feature map; channels=512
self.classifier = nn.Linear(512, num_classes)

# Weight initialization following VGG practice (Kaiming normal for conv)
for m in self.modules():
if isinstance(m, nn.Conv2d):
nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
if m.bias is not None:
nn.init.zeros_(m.bias)
elif isinstance(m, nn.BatchNorm2d):
nn.init.ones_(m.weight)
nn.init.zeros_(m.bias)
elif isinstance(m, nn.Linear):
nn.init.normal_(m.weight, 0, 0.01)
nn.init.zeros_(m.bias)

def _make_layers(self, cfg):
layers = []
in_channels = 3 # RGB input
for x in cfg:
if x == 'M':
layers += [nn.MaxPool2d(kernel_size=2, stride=2)]
else:
# 3x3 conv with padding=1 keeps spatial size
layers += [
nn.Conv2d(in_channels, x, kernel_size=3, padding=1, bias=False),
nn.BatchNorm2d(x),
nn.ReLU(inplace=True)
]
in_channels = x
# Optional: final avgpool is not necessary since we already reach 1x1 after 5 pools
return nn.Sequential(*layers)

def forward(self, x):
x = self.features(x) # shape: (B, 512, 1, 1)
x = torch.flatten(x, 1) # shape: (B, 512)
x = self.classifier(x) # logits
return x

# Instantiate model, loss, optimizer
net = VGG(num_classes=10).to(device)
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(net.parameters(), lr=1e-3) # you can try SGD+momentum later
print(net)
STEP 3:网络训练
  • 作用

    • 利用训练集数据更新模型参数,使其最小化预测误差。
    • 输出每 epoch 的损失变化以观察收敛过程。
  • 原理

    • 前向传播(Forward Pass):输入给模型给输出预测。
    • 损失计算(Loss Computation):使用 CrossEntropyLoss 比较预测与真实标签。
    • 反向传播(Backward Pass):自动求导,得到梯度。
    • 参数更新(Optimization):通过 Adam 或 SGD 调整权重。
  • 思考

    • 学习率 (lr) 过大会震荡,过小会收敛缓慢。
    • Adam 收敛较快但泛化略弱。
    • 训练时应观察 loss 逐步下降,否则需检查数据或学习率设置。
  • 实验结果如图14.

图13 定义网络 图14 网络训练
STEP 4:测试与验证准确率
  • 作用

    • 在独立测试集上评估模型性能,计算总体准确率。
    • 验证模型是否具有良好的泛化能力。
  • 原理

    • with torch.no_grad() 关闭梯度计算以提升速度。
    • predicted == labels 计算每个样本是否预测正确。
    • 统计 correct / total 即 accuracy。
  • 思考

    • 模型在 CIFAR-10 上达到约 86% 准确率,说明特征提取有效。
  • 实验结果如图15.

图15 测试与验证准确率

2.3 回答相关问题

2.3.1 DataLoader ⾥⾯ shuffle 取不同值有什么区别?

  • 在 PyTorch 的 DataLoader 中,shuffle=True 表示在每个 epoch 开始时,都会随机打乱样本顺序shuffle=False 则按照原始数据顺序加载。
  • 当我们训练模型时,随机打乱数据可以避免模型记住特定样本的顺序,从而提高泛化能力让模型更稳健。
  • 对于验证集或测试集,一般会设置为 False,以便每次验证时保持一致的顺序,保证可重复性。
  • 总结shuffle=True 通常用于训练阶段,有助于模型学习更加全面的特征。而 False 则用于评估阶段,保证结果的稳定性。

2.3.2 transform ⾥取不同值有什么区别?

  • transform 定义了对输入数据的预处理或数据增强操作。不同的 transform 组合会直接影响输入特征的形式和分布,从而影响训练结果。
  • 例如:ToTensor() 将图像转为张量、Normalize() 让像素值标准化到固定范围,有助于加速收敛;而 RandomHorizontalFlip()RandomCrop() 等操作能增加样本多样性,从而提升模型的泛化能力。
  • 若使用过多随机变换,可能导致模型学习目标不稳定。若不使用增强,则容易过拟合。

2.3.3 epoch 和 batch 的区别?

  • epoch(轮次) 表示整个训练集被完整训练一遍。每完成一个 epoch,模型就看过了所有训练样本一次。
  • batch(批次) 指每次送入网络进行一次前向与反向传播的样本数量。
  • 如果训练集有 10,000 个样本,batch size 为 100,那么一个 epoch 就包含 100 个 batch。增大 batch size 能提升计算效率但占用更多显存,减小 batch size 则训练更细腻,但可能收敛更慢。
  • 总结:batch 是模型更新的最小单元,而 epoch 是整体训练进度的度量。

2.3.4 1×1 的卷积和 FC 有什么区别?主要起什么作⽤?

  • 1×1 卷积层 只在通道维上进行线性组合,不改变空间位置。而 全连接层(FC) 会将空间信息打平并全部连接,丢失了空间结构。

  • 它的主要作用包括:

    • 通道降维或升维,减少计算量。

    • 特征融合:不同通道的信息可在同一位置进行加权组合。

    • 引入非线性:与 ReLU 激活结合,可以增强模型表达能力。


2.3.5 Residual Learning 为什么能够提升准确率?

  • 深层网络常遇到梯度消失退化问题,即网络越深反而训练效果变差。残差学习通过引入捷径连接,让输入可以直接跨层传递到输出,使梯度在反向传播时能顺畅回流,从而加速收敛。
  • 它让模型只需学习输入与输出之间的差异而非整个映射函数,使优化更容易。

2.3.6 代码练习二中,网络与 1989 年 LeCun 提出的 LeNet 有什么区别?

  • LeNet 由两层卷积层和三层全连接层组成,主要使用 Sigmoid 激活函数,结构相对浅。
  • 代码练习二的网络在此基础上做了多项改进:
    • 激活函数改为 ReLU,加快收敛并避免梯度消失。
    • 增加 Batch Normalization 提升稳定性。
    • 使用 Dropout 防止过拟合。
    • 引入 残差结构,进一步优化梯度流。
  • 总结:现代网络在结构深度、激活函数与正则化方面均优于 LeNet,表现更强。

2.3.7 代码练习二中,卷积后 feature map 尺寸变小,如何应⽤ Residual Learning?

  • 当卷积操作导致 feature map 尺寸缩小时,shortcut 分支与主分支尺寸不一致,无法直接相加。
  • **解决方式:**通过 padding 或卷积调整通道数,保证残差连接两侧维度相同。

2.3.8 有什么方法可以进一步提升准确率?

  • 数据层面:可使用数据增强(如旋转、平移、随机裁剪)或增加样本数量,提高模型泛化能力。
  • 网络结构层面:采用更深或更复杂的网络(如 ResNet、DenseNet),或引入 Attention 机制增强特征提取。
  • 训练层面
    • 选择更优的优化器。
    • 适当调整 batch size 与正则化参数。

三、问题总结与体会

3.1 问题总结

问题1:
在使用 DataLoader 进行训练时,发现每个 epoch 输出的准确率波动较大,有时训练集准确率上升但验证集下降,怀疑模型未能稳定收敛。

原因与方案:
经过检查发现,DataLoader 中的 shuffle 参数未正确设置。训练阶段应设置为 shuffle=True 以打乱数据顺序,从而避免模型过拟合于特定样本序列;验证和测试阶段则应为 False,以确保结果的可重复性。修改后模型收敛更加平稳,准确率提升明显。


问题2:
模型训练初期,发现输入图像经过 transform 处理后特征差异过大,导致网络难以收敛,loss 长时间波动。

原因与方案:
不同的 transform 组合会显著影响输入数据分布。例如过多的随机旋转或裁剪可能导致模型无法学习到稳定特征。通过重新调整 transform 顺序(如先 ToTensor()Normalize()),并适当减少随机增强操作,训练曲线趋于稳定,模型表现得到改善。


问题3:
在理解 epoch 与 batch 的关系时,误以为每个 batch 都会遍历完整数据集,导致学习率调整不当。

原因与方案:
经查阅知,一个 epoch 包含若干个 batch,每个 batch 才是一次参数更新的最小单元。学习率应结合 batch size 与 epoch 数综合设置。修正后训练速度与收敛效果均得到优化。

3.2 体会

本次实验让我更直观地理解了卷积神经网络(CNN)与全连接网络(FNN)之间的结构差异及性能特点。实验结果显示,在相同参数量下,CNN 的准确率明显优于 FNN,这主要得益于卷积操作能够有效捕捉图像的局部特征与空间结构信息。当我将图像像素顺序打乱后,CNN 的性能大幅下降,而 FNN 几乎不受影响,这说明卷积网络的优势来源于其对图像局部性和平移不变性的依赖。

同时,我进一步掌握了在 PyTorch 框架下构建模型、使用 数据增强归一化 的具体方法。通过对比不同网络结构,我认识到深层卷积网络在特征提取方面的强大能力,同时也体会到模型参数量增大所带来的计算开销和调参挑战。

总体而言,这次实验帮助我认识到了模型结构设计必须与数据特征相匹配的重要性。


Author: linda1729
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint policy. If reproduced, please indicate source linda1729 !
评论
  TOC