Skip to content

第一章 卷积神经网络基础 v1.0 ​

第一部分 概述 ​

一、什么是CNN ​

卷积神经网络是一种前馈神经网络,其核心思想是通过卷积运算自动提取数据的局部特征,然后逐层抽象,最终实现对复杂模式的识别

CNN的特点包括:

  1. 局部连接:每个神经元只与输入数据的一个局部区域相连
  2. 权值共享:同一个卷积核在整个输入上共享参数
  3. 平移不变性:对目标的位置变化具有一定的鲁棒性

二、为什么需要CNN ​

在CNN出现之前,神经网络都是全连接的。每个神经元都要和上一层的所有神经元相连,参数量大得惊人

举个例子,一张224x224x3的图片,展平后有150528个像素。如果第一个隐藏层有1000个神经元,光这一层就需要1.5亿个参数

这显然不现实,于是CNN应运而生

第二部分 核心组件 ​

一、卷积层 ​

卷积层是CNN的核心组件,其作用是提取输入数据的局部特征

1.卷积运算 ​

卷积运算的本质是滑动窗口加权求和。想象你拿着一个小窗口(卷积核)在图片上滑动,每到一个位置就把对应区域的像素值加权求和

python
import numpy as np

# 一张5x5的灰度图
image = np.array([
    [0, 1, 1, 0, 0],
    [0, 1, 1, 1, 0],
    [0, 1, 1, 1, 0],
    [0, 0, 1, 1, 0],
    [0, 0, 0, 1, 0]
])

# 3x3的卷积核,用于检测边缘
kernel = np.array([
    [-1, -1, -1],
    [-1,  8, -1],
    [-1, -1, -1]
])

# 手动计算卷积
def conv2d(image, kernel):
    h, w = image.shape
    kh, kw = kernel.shape
    oh, ow = h - kh + 1, w - kw + 1
    result = np.zeros((oh, ow))

    for i in range(oh):
        for j in range(ow):
            window = image[i:i+kh, j:j+kw]
            result[i, j] = np.sum(window * kernel)

    return result

print(conv2d(image, kernel))

这个卷积核的作用是检测边缘。你会发现,输出中数值大的地方正好对应原图中"1"聚集的边缘

2.卷积核 ​

卷积核(Filter)是一个小的权重矩阵,用于在输入数据上滑动并提取特征

不同的卷积核可以提取不同的特征,例如:

  • 边缘检测
  • 纹理识别
  • 形状检测

3.步长与填充 ​

  • 步长(Stride):卷积核每次滑动的距离
  • 填充(Padding):在输入数据边缘填充0,保持输出尺寸

二、池化层 ​

池化层的作用是降低特征图的尺寸,减少计算量

最常用的是最大池化(Max Pooling):在一个小区域内取最大值

python
import numpy as np

# 4x4的特征图
feature_map = np.array([
    [1, 3, 2, 4],
    [5, 6, 7, 8],
    [9, 10, 11, 12],
    [13, 14, 15, 16]
])

# 2x2最大池化
def max_pooling(feature_map, size=2):
    h, w = feature_map.shape
    oh, ow = h // size, w // size
    result = np.zeros((oh, ow))

    for i in range(oh):
        for j in range(ow):
            window = feature_map[i*size:(i+1)*size, j*size:(j+1)*size]
            result[i, j] = np.max(window)

    return result

print(max_pooling(feature_map))
# 输出:
# [[ 6.  8.]
#  [14. 16.]]

池化后,特征图尺寸减半,但保留了最重要的特征

三、全连接层 ​

全连接层位于CNN的最后几层,用于将提取的特征进行分类

其结构与传统的全连接神经网络相同,每个神经元都与前一层的所有神经元相连

第三部分 经典架构 ​

一、LeNet-5 (1998) ​

最早的CNN之一,由Yann LeCun提出,用于手写数字识别

结构:卷积层 -> 池化层 -> 卷积层 -> 池化层 -> 全连接层 -> 输出

二、AlexNet (2012) ​

在ImageNet竞赛中一战成名,把错误率从26%降到了15%

关键创新:

  1. 使用ReLU激活函数
  2. Dropout正则化
  3. GPU训练

三、VGGNet (2014) ​

证明了"深度很重要"这个道理。只用3x3的小卷积核,但堆了很多层

四、GoogLeNet (2014) ​

GoogLeNet也叫Inception网络,核心创新是Inception模块:用不同大小的卷积核并行提取特征,再拼接起来

这样既能捕捉多尺度特征,又控制了计算量。在当年ImageNet比赛中以较大优势夺冠

python
import torch
import torch.nn as nn

class InceptionModule(nn.Module):
    def __init__(self, in_channels, ch1x1, ch3x3_reduce, ch3x3, ch5x5_reduce, ch5x5, pool_proj):
        super().__init__()

        # 1x1卷积分支
        self.branch1 = nn.Sequential(
            nn.Conv2d(in_channels, ch1x1, 1),
            nn.BatchNorm2d(ch1x1),
            nn.ReLU()
        )

        # 1x1卷积降维 + 3x3卷积
        self.branch2 = nn.Sequential(
            nn.Conv2d(in_channels, ch3x3_reduce, 1),
            nn.BatchNorm2d(ch3x3_reduce),
            nn.ReLU(),
            nn.Conv2d(ch3x3_reduce, ch3x3, 3, padding=1),
            nn.BatchNorm2d(ch3x3),
            nn.ReLU()
        )

        # 1x1卷积降维 + 5x5卷积
        self.branch3 = nn.Sequential(
            nn.Conv2d(in_channels, ch5x5_reduce, 1),
            nn.BatchNorm2d(ch5x5_reduce),
            nn.ReLU(),
            nn.Conv2d(ch5x5_reduce, ch5x5, 5, padding=2),
            nn.BatchNorm2d(ch5x5),
            nn.ReLU()
        )

        # 3x3池化 + 1x1卷积
        self.branch4 = nn.Sequential(
            nn.MaxPool2d(3, stride=1, padding=1),
            nn.Conv2d(in_channels, pool_proj, 1),
            nn.BatchNorm2d(pool_proj),
            nn.ReLU()
        )

    def forward(self, x):
        branch1 = self.branch1(x)
        branch2 = self.branch2(x)
        branch3 = self.branch3(x)
        branch4 = self.branch4(x)
        return torch.cat([branch1, branch2, branch3, branch4], dim=1)

# 示例:Inception模块
inception = InceptionModule(192, 64, 96, 128, 16, 32, 32)
x = torch.randn(1, 192, 28, 28)
print(inception(x).shape)  # 输出: torch.Size([1, 256, 28, 28])

Inception模块的设计哲学是"更宽而不是更深",让网络自己选择最合适的特征尺度

五、ResNet (2015) ​

引入了残差连接,解决了深层网络的梯度消失问题。让训练几百层的网络成为可能

python
import torch
import torch.nn as nn

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.relu = nn.ReLU()
        self.conv2 = nn.Conv2d(channels, channels, 3, padding=1)
        self.bn2 = nn.BatchNorm2d(channels)

    def forward(self, x):
        residual = x  # 保存输入
        out = self.relu(self.bn1(self.conv1(x)))
        out = self.bn2(self.conv2(out))
        out += residual  # 跳跃连接
        return self.relu(out)

残差连接的精髓在于:让网络学习"残差"而不是完整的映射。如果某一层没什么用,至少不会让信息变差

六、DenseNet (2017) ​

DenseNet的核心思想是密集连接:每一层都与前面所有层直接相连,实现特征重用

与ResNet不同,DenseNet不是相加,而是拼接。这样既能缓解梯度消失,又能减少参数量

python
import torch
import torch.nn as nn

class DenseLayer(nn.Module):
    def __init__(self, in_channels, growth_rate):
        super().__init__()
        self.bn1 = nn.BatchNorm2d(in_channels)
        self.relu1 = nn.ReLU()
        self.conv1 = nn.Conv2d(in_channels, 4 * growth_rate, 1)
        self.bn2 = nn.BatchNorm2d(4 * growth_rate)
        self.relu2 = nn.ReLU()
        self.conv2 = nn.Conv2d(4 * growth_rate, growth_rate, 3, padding=1)

    def forward(self, x):
        out = self.conv1(self.relu1(self.bn1(x)))
        out = self.conv2(self.relu2(self.bn2(out)))
        return torch.cat([x, out], dim=1)  # 密集连接:拼接

class DenseBlock(nn.Module):
    def __init__(self, num_layers, in_channels, growth_rate):
        super().__init__()
        self.layers = nn.ModuleList()
        for i in range(num_layers):
            self.layers.append(DenseLayer(in_channels + i * growth_rate, growth_rate))

    def forward(self, x):
        for layer in self.layers:
            x = layer(x)
        return x

# 示例:4层Dense Block,增长率为12
block = DenseBlock(num_layers=4, in_channels=64, growth_rate=12)
x = torch.randn(1, 64, 32, 32)
print(block(x).shape)  # 输出: torch.Size([1, 112, 32, 32])

DenseNet通过密集连接让特征在网络中充分流动,特别适合数据量不大的场景

第四部分 应用场景 ​

CNN在很多领域仍然是主力

  1. 图像分类:识别图片中有什么
  2. 目标检测:找出物体在哪里(YOLO、Faster R-CNN)
  3. 语义分割:给每个像素分类
  4. 人脸识别:解锁手机用的就是它
  5. 风格迁移:把照片变成名画风格

第五部分 局限性 ​

CNN也不是万能的,它有几个明显的缺点

  1. 对旋转敏感:一只横着的猫和竖着的猫,CNN可能认不出是同一只
  2. 感受野有限:虽然可以通过堆叠层数来扩大感受野,但还是有上限
  3. 需要大量数据:CNN的参数量大,需要足够多的训练数据