跳转至

第5章 卷积神经网络基础

📌 章节定位:本文档隶属于计算机视觉教程体系,侧重CNN在视觉任务中的实际应用。 - 本文档重点:CNN在图像分类任务中的应用、OpenCV/PyTorch工具链使用、实际项目开发流程、模型训练与调优 - 理论原理方向:如需深入了解卷积运算的数学推导、感受野计算公式、各种卷积变体(转置卷积、深度可分离卷积等)的原理,请参考 深度学习/02-卷积神经网络/01-卷积神经网络基础.md

卷积神经网络基础图

📚 章节概述

本章深入介绍卷积神经网络(CNN)的核心原理,包括卷积操作、池化、激活函数、反向传播等。CNN是现代计算机视觉的基石,理解其原理对于掌握深度学习至关重要。

学习时间:5-7天 难度等级:⭐⭐⭐⭐⭐ 前置知识:第1-4章、深度学习基础

🎯 学习目标

完成本章后,你将能够: - 深入理解CNN的工作原理 - 掌握卷积、池化等核心操作 - 理解CNN的反向传播 - 能够从零实现简单的CNN - 完成手写数字分类项目


5.1 CNN基本原理

5.1.1 为什么需要CNN?

传统方法的局限: - 手工特征提取(SIFT、HOG)依赖经验 - 难以处理大规模数据 - 特征表达能力有限

CNN的优势: - 自动学习特征 - 层次化特征表示 - 参数共享(减少参数量) - 平移不变性

5.1.2 CNN架构

Text Only
输入层 → 卷积层 → 激活层 → 池化层 → ... → 全连接层 → 输出层

核心组件: 1. 卷积层:特征提取 2. 激活层:非线性变换 3. 池化层:降维、不变性 4. 全连接层:分类


5.2 卷积操作

5.2.1 卷积原理

注意:在深度学习中,卷积层实际实现的是互相关(Cross-Correlation)操作,而非严格数学意义上的卷积。互相关无需对核进行翻转,因此深度学习框架(如PyTorch、TensorFlow)中实现的是互相关,但在传统文献中常统称为"卷积"。

数学定义 - 互相关操作

Text Only
(I ⊛ K)(i, j) = Σ Σ I(i+m, j+n) * K(m, n)

严格数学意义上的卷积(需要先翻转核):

Text Only
(I * K)(i, j) = Σ Σ I(i+m, j+n) * K(-m, -n)

本文档中以及深度学习框架中使用的均为互相关操作,为简化表述,统一称为"卷积"。

代码实现

Python
import numpy as np

def conv2d(image, kernel, stride=1, padding=0):
    """2D卷积(互相关操作)"""
    # 添加padding
    if padding > 0:
        image = np.pad(image, padding, mode='constant')

    # 计算输出尺寸(考虑padding)
    h_out = (image.shape[0] - kernel.shape[0]) // stride + 1
    w_out = (image.shape[1] - kernel.shape[1]) // stride + 1

    # 简化公式:h_out = (H - K + 2P) / S + 1
    # 其中 H 为输入高度,K 为核大小,P 为 padding,S 为 stride

    # 初始化输出
    output = np.zeros((h_out, w_out))

    # 卷积操作
    for i in range(h_out):
        for j in range(w_out):
            region = image[i*stride:i*stride+kernel.shape[0], j*stride:j*stride+kernel.shape[1]]
            output[i, j] = np.sum(region * kernel)

    return output

# 示例
image = np.random.rand(5, 5)
kernel = np.array([[1, 0, -1], [1, 0, -1], [1, 0, -1]])  # np.array创建NumPy数组
result = conv2d(image, kernel, stride=1, padding=0)

5.2.2 使用PyTorch

Python
import torch
import torch.nn as nn

# 定义卷积层
conv = nn.Conv2d(
    in_channels=3,      # 输入通道数
    out_channels=64,   # 输出通道数
    kernel_size=3,     # 卷积核大小
    stride=1,          # 步长
    padding=1          # padding
)

# 输入图像
x = torch.randn(1, 3, 32, 32)

# 前向传播
output = conv(x)
print(f"输出尺寸: {output.shape}")  # [1, 64, 32, 32]

5.3 池化层

5.3.1 最大池化

Python
def max_pooling(image, kernel_size=2, stride=2):
    """最大池化"""
    h_out = (image.shape[0] - kernel_size) // stride + 1
    w_out = (image.shape[1] - kernel_size) // stride + 1
    output = np.zeros((h_out, w_out))

    for i in range(h_out):
        for j in range(w_out):
            region = image[i*stride:i*stride+kernel_size, j*stride:j*stride+kernel_size]
            output[i, j] = np.max(region)

    return output

# PyTorch实现
pool = nn.MaxPool2d(kernel_size=2, stride=2)
output = pool(x)

5.3.2 平均池化

Python
# PyTorch实现
avg_pool = nn.AvgPool2d(kernel_size=2, stride=2)
output = avg_pool(x)

5.4 激活函数

5.4.1 ReLU

Python
def relu(x):
    return np.maximum(0, x)

# PyTorch
relu = nn.ReLU()
output = relu(x)

5.4.2 其他激活函数

Python
# Leaky ReLU
leaky_relu = nn.LeakyReLU(negative_slope=0.01)

# GELU
gelu = nn.GELU()

# Swish
swish = nn.SiLU()

5.5 从零实现CNN

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

class SimpleCNN(nn.Module):  # 继承nn.Module定义网络层
    def __init__(self, num_classes=10):
        super(SimpleCNN, self).__init__()

        # 卷积层
        self.conv1 = nn.Conv2d(1, 32, kernel_size=3, padding=1)
        self.conv2 = nn.Conv2d(32, 64, kernel_size=3, padding=1)

        # 池化层
        self.pool = nn.MaxPool2d(2, 2)

        # 全连接层
        self.fc1 = nn.Linear(64 * 7 * 7, 128)
        self.fc2 = nn.Linear(128, num_classes)

    def forward(self, x):
        # 卷积 + ReLU + 池化
        x = self.pool(F.relu(self.conv1(x)))  # F.xxx PyTorch函数式API
        x = self.pool(F.relu(self.conv2(x)))

        # 展平
        x = x.view(-1, 64 * 7 * 7)  # 重塑张量形状

        # 全连接
        x = F.relu(self.fc1(x))
        x = self.fc2(x)

        return x

# 创建模型
model = SimpleCNN(num_classes=10)
print(model)

5.6 训练CNN

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

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

# 加载数据
train_dataset = datasets.MNIST('./data', train=True, download=True, transform=transform)
train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=64, shuffle=True)  # DataLoader批量加载数据

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

# 训练循环
def train(model, loader, criterion, optimizer, epochs=5):
    model.train()  # train()训练模式
    for epoch in range(epochs):
        running_loss = 0.0
        for i, (images, labels) in enumerate(loader):  # enumerate同时获取索引和元素
            # 前向传播
            outputs = model(images)
            loss = criterion(outputs, labels)

            # 反向传播
            optimizer.zero_grad()  # 清零梯度
            loss.backward()  # 反向传播计算梯度
            optimizer.step()  # 更新参数

            running_loss += loss.item()  # 将单元素张量转为Python数值

        print(f'Epoch {epoch+1}, Loss: {running_loss/len(loader):.4f}')

# 训练
train(model, train_loader, criterion, optimizer, epochs=5)

5.7 练习题

基础题

  1. 简答题
  2. CNN相比全连接网络有什么优势?

    ①局部连接:每个神经元只连接局部区域,捕捉局部空间特征;②参数共享:同一卷积核在整个图像上滑动,大幅减少参数量;③平移不变性:无论特征在图像哪个位置都能被检测到;④层次化特征提取:浅层学习边缘纹理,深层学习高级语义。

  3. 卷积操作的作用是什么?

    卷积操作通过滑动卷积核与局部区域做加权求和来提取特征。不同卷积核可提取不同类型特征(边缘、纹理、形状),多层卷积堆叠可从低级特征逐步构建高级语义特征。

进阶题

  1. 编程题
  2. 从零实现一个卷积层。
  3. 实现一个简单的CNN分类器。

5.8 面试准备

大厂面试题

Q1: 为什么CNN适合图像处理?

参考答案: - 局部连接:捕捉局部特征 - 参数共享:减少参数量 - 平移不变性:对位置变化鲁棒 - 层次化特征:从低层到高层

Q2: 1x1卷积的作用是什么?

参考答案: - 降维/升维(改变通道数) - 增加非线性 - 跨通道信息交互 - 计算高效


5.9 本章小结

核心知识点

  1. CNN原理:局部连接、参数共享
  2. 卷积操作:特征提取
  3. 池化:降维、不变性
  4. 激活函数:ReLU等
  5. 训练:前向传播、反向传播

下一步

下一章06-经典CNN架构.md - 学习经典架构


恭喜完成第5章! 🎉