Be for thing头像

Pytorch【10】卷积神经网络CNN实战|卷积、池化、padding、stride,CNN训练MNIST

标签:Pytorch、CNN、卷积神经网络、Conv2d、池化
简介:全连接网络把图片摊平会丢失空间信息,CNN利用卷积核提取图像局部特征(边缘、纹理、轮廓)。本文吃透卷积核心参数、池化作用,从零搭建CNN完成MNIST手写数字分类。

一、前言

上一篇我们用全连接层完成MNIST分类。全连接网络有明显短板:

  1. 把28×28二维图像直接Flatten成一维向量,丢失像素之间空间位置关系
  2. 参数数量巨大,容易过拟合

CNN(卷积神经网络)三大核心组件:卷积层Conv、池化层Pool、激活函数
卷积:滑动卷积核提取局部特征;池化:降采样、压缩特征图,减少计算量。

二、Conv2d 卷积层核心参数

nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0)

参数含义
in_channels输入通道数:灰度图=1,RGB彩色图=3
out_channels输出通道数,等价于卷积核数量,想要多少组特征图
kernel_size卷积核大小,常用 3 等价 (3,3)
stride步长,卷积核每次滑动几格,默认1
padding边缘填充0,填充大小,控制输出特征图尺寸

输出特征图尺寸公式:
Hout=Hin+2×padding−kernel_sizestride+1H_{out} = \frac{H_{in}+2\times padding - kernel\_size}{stride}+1Hout​=strideHin​+2×padding−kernel_size​+1

示例:输入28×28,kernel=3,padding=1,stride=1
(28+2×1−3)/1+1=28(28 + 2\times1 -3)/1 +1 = 28(28+2×1−3)/1+1=28
padding=1时3×3卷积不改变宽高(same padding)

三、池化层 MaxPool2d

最常用最大池化 nn.MaxPool2d(kernel_size, stride)
作用:

  • 下采样,特征图宽高减半,减少参数量与计算量
  • 保留显著特征,增加特征鲁棒性
  • 没有可学习权重,只是取区域最大值

示例:MaxPool2d(2,2),宽高变为原来1/2

平均池化 AvgPool2d:取区域平均值,现在CV场景用得比较少

四、CNN网络结构设计思路

经典堆叠顺序:
Conv2d → ReLU → MaxPool2d → Conv2d → ReLU → MaxPool2d → Flatten → Linear
浅层卷积:提取边缘、线条;深层卷积:提取组合特征(数字轮廓)

五、完整CNN代码(MNIST)

import torch
import torch.nn as nn
from torch.utils.data import DataLoader, random_split
from torchvision import datasets, transforms

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(f"运行设备:{device}")

# 数据预处理
trans = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize((0.1307,), (0.3081,))
])

# 数据集划分
full_data = datasets.MNIST(root="./data", train=True, transform=trans, download=True)
train_ds, val_ds = random_split(full_data, [int(0.8*len(full_data)), len(full_data)-int(0.8*len(full_data))])
train_loader = DataLoader(train_ds, batch_size=64, shuffle=True)
val_loader = DataLoader(val_ds, batch_size=64, shuffle=False)

# 搭建CNN
class MnistCNN(nn.Module):
    def __init__(self):
        super().__init__()
        self.conv_layers = nn.Sequential(
            # 输入 [batch,1,28,28]
            nn.Conv2d(in_channels=1, out_channels=16, kernel_size=3, stride=1, padding=1),
            nn.ReLU(),
            nn.MaxPool2d(2,2), # 输出 [batch,16,14,14]

            nn.Conv2d(in_channels=16, out_channels=32, kernel_size=3, stride=1, padding=1),
            nn.ReLU(),
            nn.MaxPool2d(2,2), # 输出 [batch,32,7,7]
        )
        self.fc_layers = nn.Sequential(
            nn.Flatten(),
            nn.Linear(32 * 7 * 7, 128),
            nn.ReLU(),
            nn.Linear(128, 10)
        )

    def forward(self, x):
        x = self.conv_layers(x)
        x = self.fc_layers(x)
        return x

model = MnistCNN().to(device)
loss_fn = nn.CrossEntropyLoss()
opt = torch.optim.Adam(model.parameters(), lr=1e-3)

epoch_num = 10
best_val_acc = 0.0

for epoch in range(epoch_num):
    # 训练
    model.train()
    train_loss_sum = 0.0
    for imgs, labels in train_loader:
        imgs, labels = imgs.to(device), labels.to(device)
        outputs = model(imgs)
        loss = loss_fn(outputs, labels)

        opt.zero_grad()
        loss.backward()
        opt.step()
        train_loss_sum += loss.item()
    train_loss_avg = train_loss_sum / len(train_loader)

    # 验证
    model.eval()
    val_loss_sum = 0.0
    correct_sum = 0
    total_num = 0
    with torch.no_grad():
        for imgs, labels in val_loader:
            imgs, labels = imgs.to(device), labels.to(device)
            outputs = model(imgs)
            loss = loss_fn(outputs, labels)
            val_loss_sum += loss.item()
            preds = torch.max(outputs, dim=1)[1]
            correct_sum += torch.sum(preds == labels)
            total_num += labels.size(0)
    val_loss_avg = val_loss_sum / len(val_loader)
    val_acc = correct_sum.item() / total_num

    print(f"【Epoch {epoch+1}/{epoch_num}】")
    print(f"train_loss:{train_loss_avg:.4f} | val_loss:{val_loss_avg:.4f} | val_acc:{val_acc:.4f}\n")

    if val_acc > best_val_acc:
        best_val_acc = val_acc
        torch.save(model.state_dict(), "mnist_cnn_best.pth")
        print(f"✅ 保存最优CNN模型,最佳acc:{best_val_acc:.4f}")

六、CNN对比全连接网络优势总结

  1. 局部感受野:卷积核只看一小块区域,符合图像特征局部相关性
  2. 权值共享:一张图共用同一套卷积核,大幅减少参数量
  3. 平移不变性:同一个特征,在图里左右移动依然可以被识别

七、高频踩坑点

  1. Conv2d输入维度必须是 [batch, C, H, W],通道在前!PIL图片默认HWC,ToTensor自动转CHW
  2. 算错Flatten之后Linear输入维度:改卷积、池化参数之后一定要重新算特征尺寸
  3. 通道数顺序:in_channels要和上一层输出通道对齐
  4. 池化没有参数,不需要训练
  5. 卷积后忘记激活函数,网络退化成线性组合,拟合能力暴跌

八、拓展小知识

  • 1×1卷积:用来升降通道数,不改变宽高,压缩通道、融合跨通道信息
  • 空洞卷积dilation:扩大感受野,不增加卷积核尺寸
  • groups分组卷积:深度可分离卷积的基础,轻量化模型常用

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/Beforthing/article/details/170366158

文章来源转载

评论

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--