CNN-卷积神经网络


在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述


1. 卷积神经网络发展史

1.1 什么是 CNN

卷积神经网络是一类专门处理网格状结构数据的神经网络,最典型的是图像(2D 像素网格)。其核心思想是:局部连接 + 参数共享 + 层级抽象

2012 年 AlexNet 在 ImageNet 上取得突破性成绩后,CNN 成为计算机视觉领域的主导模型。

1.2 生物启发:视觉系统的层级结构

1962 年,神经科学家 Hubel & Wiesel 通过猫的视觉皮层实验发现:

细胞类型 功能 CNN 对应
简单细胞 对特定方向边缘响应 卷积层(特征提取)
复杂细胞 对位置偏移不敏感 池化层(平移不变性)

核心发现:视觉处理是层级化的——从边缘到纹理再到物体部件。

这正是深度学习优于传统特征工程的根本原因:CNN 的每一层自动学习不同抽象层次的特征,无需手工设计 SIFT、HOG 等特征提取器。

浅层 ──→ 边缘、颜色斑点
中层 ──→ 纹理、简单图案
深层 ──→ 眼睛、轮子、人脸

1962年,生物学家Torsten Wisesl 和 David H.Hunel (1981的诺贝尔医学奖)对猫的视觉结构进行研究,首次发现猫的视觉系统中存在层级结构, 并且发现了两种重要的细胞simple cells和complex cells,不同类型细胞承受不同抽象层次的视觉感知功能。

注意:层级结构的发现是十分十分重要的,正是因为有了层级结构,才能够在不同层抽取不同程度的图像特征,才让深度卷积神经网络大行其道,击败了传统的图像特征工程,就是因为深度卷积网络能够在不同层能提取出不同程度的特征,替代了传统的特征工程,就是因为深度卷积网络,不同层就是对应了不同的图像特征工程,如初始层是边缘特征提取器,中间层是纹理特征,后面的卷积层则是高级语义特征。

simple cells和complex cells:则对应了卷积和池化操作;

可以说1962年的生物实验是卷积神经网络诞生的理论基石。

1.3 发展里程碑

年份 模型 贡献
1980 Neocognitron CNN 雏形
1998 LeNet-5 首个完整 CNN,MNIST 手写识别
2012 AlexNet ImageNet 夺冠,开启深度学习时代
2014 VGG 小卷积核(3×3),更深
2014 GoogLeNet Inception 模块,多尺度
2015 ResNet 残差连接,可训练超深层(152 层)
2017 DenseNet 密集连接,极致特征复用

2. 卷积操作

2.1 数学定义

对于输入图像 X∈RH×W×CinX \in \mathbb{R}^{H \times W \times C_{in}}XRH×W×Cin 和卷积核 K∈Rkh×kw×Cin×CoutK \in \mathbb{R}^{k_h \times k_w \times C_{in} \times C_{out}}KRkh×kw×Cin×Cout,卷积输出 Y∈RH′×W′×CoutY \in \mathbb{R}^{H' \times W' \times C_{out}}YRH×W×Cout 为:

Y[i,j,cout]=∑u=0kh−1∑v=0kw−1∑cin=0Cin−1X[i+u,j+v,cin]⋅K[u,v,cin,cout]+b[cout]Y[i,j,c_{out}] = \sum_{u=0}^{k_h-1} \sum_{v=0}^{k_w-1} \sum_{c_{in}=0}^{C_{in}-1} X[i+u, j+v, c_{in}] \cdot K[u, v, c_{in}, c_{out}] + b[c_{out}]Y[i,j,cout]=u=0kh1v=0kw1cin=0Cin1X[i+u,j+v,cin]K[u,v,cin,cout]+b[cout]

2.2 输出尺寸公式

输出维度由以下参数决定:

  • 输入尺寸:H,WH, WH,W
  • 卷积核尺寸:kkk
  • 填充(padding):ppp
  • 步长(stride):sss

H′=⌊H+2p−ks⌋+1H' = \left\lfloor \frac{H + 2p - k}{s} \right\rfloor + 1H=sH+2pk+1

W′=⌊W+2p−ks⌋+1W' = \left\lfloor \frac{W + 2p - k}{s} \right\rfloor + 1W=sW+2pk+1

2.3 PyTorch 代码示例

import torch
import torch.nn as nn
import torch.nn.functional as F

# 输入特征图: [batch, channels, height, width]
x = torch.randn(1, 3, 32, 32)  # 单张 32x32 RGB 图像

# 标准卷积层: 3通道输入 → 16通道输出
conv = nn.Conv2d(
    in_channels=3,
    out_channels=16,
    kernel_size=3,
    stride=1,
    padding=1   # padding=1 保证尺寸不变 (32+2-3)/1+1=32
)

y = conv(x)
print(f"输入尺寸: {x.shape}")   # torch.Size([1, 3, 32, 32])
print(f"输出尺寸: {y.shape}")   # torch.Size([1, 16, 32, 32])

# ============================================
# 手动实现二维卷积(教育目的)
def conv2d_manual(x, kernel, stride=1, padding=0):
    """x: (C_in, H, W), kernel: (C_out, C_in, kH, kW)"""
    c_in, h, w = x.shape
    c_out, _, k_h, k_w = kernel.shape
    
    # 填充
    x_pad = F.pad(x, (padding, padding, padding, padding))
    h_out = (h + 2*padding - k_h) // stride + 1
    w_out = (w + 2*padding - k_w) // stride + 1
    
    out = torch.zeros(c_out, h_out, w_out)
    for i in range(c_out):
        for j in range(h_out):
            for k in range(w_out):
                # 滑动窗口
                h_start, w_start = j * stride, k * stride
                region = x_pad[:, h_start:h_start+k_h, w_start:w_start+k_w]
                out[i, j, k] = torch.sum(region * kernel[i])
    return out

# 验证
x_simple = torch.randn(1, 5, 5)
kernel = torch.randn(2, 1, 3, 3)
manual_out = conv2d_manual(x_simple, kernel, stride=1, padding=0)
pytorch_out = F.conv2d(x_simple.unsqueeze(0), kernel)
print(f"手动实现与 PyTorch 一致: {torch.allclose(manual_out, pytorch_out.squeeze(0), atol=1e-6)}")

2.4 工程要点

参数 典型值 影响
kernel_size 3×3 更大感受野但参数多(现多用堆叠小核)
stride 1(特征提取)/ 2(下采样) 步长 > 1 可替代池化
padding ‘same’ 保持尺寸 保留边缘信息
dilation 1(标准) 空洞卷积,增大感受野不增参数

3. 池化操作

3.1 数学定义

最大池化(Max Pooling)在 k×kk \times kk×k 窗口内取最大值:

Y[i,j]=max⁡0≤u<k,0≤v<kX[i⋅s+u,j⋅s+v]Y[i,j] = \max_{0 \le u < k, 0 \le v < k} X[i \cdot s + u, j \cdot s + v]Y[i,j]=0u<k,0v<kmaxX[is+u,js+v]

平均池化(Average Pooling):

Y[i,j]=1k2∑u=0k−1∑v=0k−1X[i⋅s+u,j⋅s+v]Y[i,j] = \frac{1}{k^2} \sum_{u=0}^{k-1} \sum_{v=0}^{k-1} X[i \cdot s + u, j \cdot s + v]Y[i,j]=k21u=0k1v=0k1X[is+u,js+v]

全局平均池化(Global Average Pooling, GAP):

  • 将整个特征图 H×WH \times WH×W 池化为 1×11 \times 11×1
  • 常用于分类层前,替代全连接层,大幅减少参数

3.2 PyTorch 代码示例

import torch
import torch.nn as nn

x = torch.randn(1, 64, 28, 28)

# 最大池化: 2x2 窗口, stride=2 → 尺寸减半
maxpool = nn.MaxPool2d(kernel_size=2, stride=2)
y_max = maxpool(x)
print(f"最大池化: {x.shape}{y_max.shape}")  # [1,64,28,28] → [1,64,14,14]

# 平均池化
avgpool = nn.AvgPool2d(kernel_size=2, stride=2)
y_avg = avgpool(x)

# 全局平均池化 (常用于分类层前)
gap = nn.AdaptiveAvgPool2d(1)
y_gap = gap(x)  # [1,64,28,28] → [1,64,1,1]
y_gap = y_gap.view(1, -1)  # → [1,64]
print(f"全局平均池化: {y_gap.shape}")

# 可以接分类头
classifier = nn.Linear(64, 10)
logits = classifier(y_gap)

3.3 池化的替代方案

现代 CNN 中,池化逐渐被 stride=2 卷积空洞卷积 替代:

# 用卷积做下采样(更可学习,效果通常更好)
downsample_conv = nn.Conv2d(64, 64, kernel_size=3, stride=2, padding=1)
y_conv_down = downsample_conv(x)  # [1,64,28,28] → [1,64,14,14]

4. LeNet-5 及 CNN 结构进化史

4.1 LeNet-5 完整实现

import torch.nn as nn

class LeNet5(nn.Module):
    """LeNet-5 for MNIST (32x32 input)"""
    def __init__(self, num_classes=10):
        super().__init__()
        # C1: 卷积 + 池化
        self.conv1 = nn.Conv2d(1, 6, kernel_size=5, padding=2)  # 32→32 (保持)
        self.pool1 = nn.AvgPool2d(kernel_size=2, stride=2)      # 32→16
        
        # C3: 卷积 + 池化
        self.conv2 = nn.Conv2d(6, 16, kernel_size=5)            # 16→12 (无padding)
        self.pool2 = nn.AvgPool2d(kernel_size=2, stride=2)      # 12→6
        
        # 全连接层
        self.fc1 = nn.Linear(16 * 5 * 5, 120)
        self.fc2 = nn.Linear(120, 84)
        self.fc3 = nn.Linear(84, num_classes)
        
    def forward(self, x):
        x = torch.tanh(self.conv1(x))
        x = self.pool1(x)
        x = torch.tanh(self.conv2(x))
        x = self.pool2(x)
        x = x.view(x.size(0), -1)  # 展平
        x = torch.tanh(self.fc1(x))
        x = torch.tanh(self.fc2(x))
        x = self.fc3(x)
        return x

# 测试
model = LeNet5()
dummy = torch.randn(1, 1, 32, 32)
print(f"LeNet-5 输出: {model(dummy).shape}")  # [1, 10]

4.2 关键架构创新(带代码片段)

ResNet:残差连接

y=F(x,{Wi})+xy = F(x, \{W_i\}) + xy=F(x,{Wi})+x

class ResidualBlock(nn.Module):
    def __init__(self, channels):
        super().__init__()
        self.conv1 = nn.Conv2d(channels, channels, 3, padding=1)
        self.bn1 = nn.BatchNorm2d(channels)
        self.conv2 = nn.Conv2d(channels, channels, 3, padding=1)
        self.bn2 = nn.BatchNorm2d(channels)
        
    def forward(self, x):
        residual = x
        out = F.relu(self.bn1(self.conv1(x)))
        out = self.bn2(self.conv2(out))
        out += residual  # 残差连接
        return F.relu(out)
Inception:多尺度并行
class InceptionBlock(nn.Module):
    def __init__(self, in_ch, out1, out3_1, out3, out5_1, out5, pool_proj):
        super().__init__()
        # 1x1 分支
        self.branch1 = nn.Conv2d(in_ch, out1, 1)
        # 3x3 分支
        self.branch2 = nn.Sequential(
            nn.Conv2d(in_ch, out3_1, 1),
            nn.Conv2d(out3_1, out3, 3, padding=1)
        )
        # 5x5 分支
        self.branch3 = nn.Sequential(
            nn.Conv2d(in_ch, out5_1, 1),
            nn.Conv2d(out5_1, out5, 5, padding=2)
        )
        # 池化分支
        self.branch4 = nn.Sequential(
            nn.MaxPool2d(3, stride=1, padding=1),
            nn.Conv2d(in_ch, pool_proj, 1)
        )
        
    def forward(self, x):
        return torch.cat([self.branch1(x), self.branch2(x), 
                          self.branch3(x), self.branch4(x)], dim=1)

4.3 进化趋势总结

特性 早期 (LeNet) 现代 (ResNet/EfficientNet)
深度 5-7 层 50-100+ 层
卷积核 5×5, 7×7 3×3 为主
归一化 BatchNorm + LayerNorm
激活函数 tanh, sigmoid ReLU, GELU, Swish
池化 平均池化 较少用,stride卷积替代
正则化 Dropout, DropPath, 数据增强
连接方式 直连 残差、密集连接

5. 总结与工程讨论

5.1 核心公式速查

操作 公式
卷积参数数 params=kh×kw×Cin×Cout\text{params} = k_h \times k_w \times C_{in} \times C_{out}params=kh×kw×Cin×Cout
感受野(叠 k×k) RF=1+(k−1)×LRF = 1 + (k-1) \times LRF=1+(k1)×L(L 为经过层数)
BatchNorm x^=x−μσ2+ϵ,y=γx^+β\hat{x} = \frac{x - \mu}{\sqrt{\sigma^2 + \epsilon}}, \quad y = \gamma \hat{x} + \betax^=σ2+ϵ xμ,y=γx^+β
Xavier 初始化 W∼U[−6nin+nout,6nin+nout]W \sim \mathcal{U}\left[-\frac{\sqrt{6}}{\sqrt{n_{in}+n_{out}}}, \frac{\sqrt{6}}{\sqrt{n_{in}+n_{out}}}\right]WU[nin+nout 6 ,nin+nout 6 ]

5.2 工程实战建议

1. 选择默认配置

# 标准卷积块(工程最佳实践)
class ConvBlock(nn.Module):
    def __init__(self, in_ch, out_ch, kernel=3, stride=1):
        super().__init__()
        self.conv = nn.Conv2d(in_ch, out_ch, kernel, stride, padding=kernel//2)
        self.bn = nn.BatchNorm2d(out_ch)
        self.relu = nn.ReLU(inplace=True)
        
    def forward(self, x):
        return self.relu(self.bn(self.conv(x)))

2. 记忆/速度平衡

需求 推荐方案
最快推理 MobileNetV3, EfficientNet-Lite
最高精度 ConvNeXt, ResNet-RS
显存受限 Group Convolution, Depthwise Conv
小数据集 使用预训练模型 fine-tune

3. 常见调试问题

# 检查梯度消失
for name, param in model.named_parameters():
    if param.grad is not None:
        print(f"{name}: mean_grad={param.grad.abs().mean():.2e}")

# 检查特征图尺寸变化
def print_shape_hook(module, input, output):
    print(f"{module.__class__.__name__}: {input[0].shape}{output.shape}")

5.3 局限性与讨论

  1. 归纳偏置:CNN 假设局部性和平移不变性,适用于图像,但不适合非网格数据
  2. 旋转/尺度敏感:需要数据增强或引入 SE、CoordConv 等模块
  3. 长距离依赖不足:Attention(如 ViT)在某些任务上超越 CNN
  4. 可解释性:特征图可视化、Grad-CAM 可部分解释,但仍未完全解决

5.4 未来趋势

  • CNN + Transformer 混合架构(ConvNeXt, CoAtNet)
  • 神经架构搜索(NAS):自动化设计高效 CNN
  • 边缘部署:量化、剪枝、知识蒸馏
  • 自监督 CNN:SimCLR, BYOL,减少标注依赖

附录:完整训练示例(PyTorch)

import torch
import torch.nn as nn
import torch.optim as optim
from torchvision import datasets, transforms

# 1. 数据加载
transform = transforms.Compose([
    transforms.Resize(32),
    transforms.ToTensor(),
    transforms.Normalize((0.5,), (0.5,))
])
train_data = datasets.MNIST('.', train=True, download=True, transform=transform)
train_loader = torch.utils.data.DataLoader(train_data, batch_size=64, shuffle=True)

# 2. 模型
model = LeNet5()
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model.to(device)

# 3. 损失与优化器
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)

# 4. 训练循环
model.train()
for epoch in range(5):
    total_loss = 0
    for images, labels in train_loader:
        images, labels = images.to(device), labels.to(device)
        
        optimizer.zero_grad()
        outputs = model(images)
        loss = criterion(outputs, labels)
        loss.backward()
        optimizer.step()
        
        total_loss += loss.item()
    
    print(f"Epoch {epoch+1}, Loss: {total_loss/len(train_loader):.4f}")

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐