吴恩达《深度学习》之拆解CNN的空间游戏

今天我们要拆解一个在计算机视觉(CV)领域统治了数十年的超级巨星------卷积神经网络(Convolutional Neural Network,简称 CNN)

在学 CNN 之前,很多同学会陷入一种传统的思维僵局:"我们要怎么教计算机去认出一张图里是一只猫?是不是要写死几万行代码,去定义猫的眼睛必须是圆的、胡须必须是直的?"

CNN 的伟大之处在于,它彻底解放了人类工程师。它不需要我们去定义"什么是猫",而是通过一套极其精妙的"空间游戏",让机器自己从像素点里进化出看世界的眼睛。

核心知识点:

  • 直觉解释: 利用具有参数共享特性的过滤器在图像上滑动,自动学习从边缘到复杂物体的层级特征提取。
  • 数学核心: 输出尺寸计算 ⌊n+2p−fs+1⌋\lfloor \frac{n+2p-f}{s} + 1 \rfloor⌊sn+2p−f+1⌋(nnn: 输入尺寸, ppp: 填充, fff: 核大小, sss: 步幅)。
  • 常见变体及适用场景: 1×11 \times 11×1 卷积(用于改变通道数)、池化层(缩减模型大小);适用于所有计算机视觉任务。

请坐,让我们依然用最纯粹的直觉,把这个"视觉魔术"解构得一清二楚。

第一步:传统全连接网络的"维度灾难"

在 CNN 诞生之前,人们试图用普通的全连接网络(MLP)来处理图像。

提问: 假设我们有一张高清的彩色猫咪图片,分辨率是 1000×10001000 \times 10001000×1000 像素。因为它有 RGB 三个颜色通道,所以这张图一共有 1000×1000×3=3,000,0001000 \times 1000 \times 3 = 3,000,0001000×1000×3=3,000,000(300万)个数字输入。

如果隐藏层只有区区 1000 个神经元,且它们与输入层"全连接"(每个神经元和每个像素都有一根连线)。

请问,光是这一层,我们需要训练多少个权重(Weight)参数?如果全网叠上几十层,你的显卡顶得住吗?

解析: 你的大脑一定会算出一个天文数字:3,000,000×1000=3,000,000 \times 1000 =3,000,000×1000= 30 亿个参数!这还仅仅只是第一层。

这种简单粗暴的连线方式,不仅会让计算量直接爆炸(维度灾难),更致命的是,它把图像压扁连成了一根长线,彻底割裂了像素之间的空间邻里关系

第二步:参数共享与局部感知(手电筒的光斑)

为了拯救显卡并保留空间结构,CNN 引入了两个神级的心智模型:局部感知(Local Receptive Fields)参数共享(Parameter Sharing)

我们可以把卷积核(Filter/Kernel)想象成一把小巧的手电筒。

提问: 假设这把手电筒的光斑大小是 3×33 \times 33×3 像素。我们拿着它,从图像的左上角开始,一行一行地向右、向下扫过整张图。

当手电筒照在左上角时,它只关心这 3×33 \times 33×3 区域内的像素之间的关系(局部感知)。

更重要的是,当这把手电筒移动到图像右下角去扫描时,手电筒内部镜片的物理结构(权重参数 WWW)发生改变了吗?

直觉觉醒: 没有变!手电筒还是那把手电筒。

这就是参数共享 的本质:无论图像有多大,同一个过滤器(Filter)在整张图上滑动时,它的权重参数是完全复用的。这样一来,原本全连接需要的 30 亿个参数,直接暴降到了区区 3×3=93 \times 3 = 93×3=9 个参数(加上通道数也就几十个)!显卡瞬间活了过来。

第三步:视觉层级的建立(从搬砖到造城堡)

这把手电筒(卷积核)在滑动时,到底在看什么?

提问: 假设这把手电筒的矩阵参数被训练成了左边全是 −1-1−1、右边全是 111 的结构。当它扫过一处"左边黑、右边白"的强烈边缘时,算出来的乘积绝对值会极大;而扫过一片纯黑或纯白的平坦区域时,乘积直接归零。

这说明,这第一层的手电筒,自发学会了看图像里的什么特征?

推老演练: 学会了看"边缘(Edges)"和"线条"。

终极追问: 如果我们把第一层所有手电筒吐出的"边缘图"打包当成输入,喂给第二层稍大一点的手电筒。第二层的手电筒在这些"线条"上继续滑动、组合,它们会进化出看什么的能力?到了第三层、第四层呢?

因果闭环:

  • 第一层: 提取最基础的横线、竖线、边缘(像素级的秘密);
  • 第二层: 把线条组合起来,看懂了圆圈、三角形、局部纹理;
  • 第三层: 把形状组合起来,看懂了猫耳朵、猫眼睛、狗鼻子的局部零件;
  • 最后一层: 把零件拼起来,高阶神经元大喊一声:"这是一只猫!"

这就是 CNN 的层级特征提取网络。它完美模拟了人类大脑视觉皮层看世界的方式。

第四步:数学核心------地盘是怎么变小的?

手电筒在图片上滑动时,输出的新图片尺寸有一套严格的物理边界数学公式:

Output Size=⌊n+2p−fs+1⌋\text{Output Size} = \left\lfloor \frac{n + 2p - f}{s} + 1 \right\rfloorOutput Size=⌊sn+2p−f+1⌋

我们千万不要死记硬背它,让我们把它拆成最直观的动作:

  • nnn:原始图片有多大(底盘)。
  • fff :手电筒(卷积核)有多大。n−fn - fn−f 表示手电筒在不超出边界的情况下,还能往右挪动多少空间。
  • ppp(Padding/填充) :如果图片边缘很重要,我们可以在图片外面强行包裹 ppp 层"零像素的外套",把底盘撑大到 n+2pn + 2pn+2p。
  • sss(Stride/步幅) :手电筒每次移动的步伐。如果 s=1s=1s=1,就一步一步挪;如果 s=2s=2s=2,就大跨步跳着走,那么能挪动的次数自然要除以 sss。
  • +1+1+1:加上手电筒一开始呆在左上角霸占的那第一步。

第五步:PyTorch 里的视觉流水线落地

在 PyTorch 中,一个标准的卷积层和变体(池化层)是这样协作的:

python 复制代码
import torch
import torch.nn as nn

class CNNModel(nn.Module):
    def __init__(self):
        super(CNNModel, self).__init__()
        # 输入通道1(灰度图),输出通道16(用16把不同的手电筒去提取16种不同的特征)
        # 卷积核大小 f=3, 步幅 s=1, 填充 p=1
        self.conv1 = nn.Conv2d(in_channels=1, out_channels=16, kernel_size=3, stride=1, padding=1)
        
        # MaxPool2d(最大池化层):手电筒扫过 2x2 区域,只留下最大的那个特征,直接把图片尺寸砍掉一半
        # 核心目的:缩减模型大小,提取最强烈的信号(抗噪)
        self.pool = nn.MaxPool2d(kernel_size=2, stride=2)
        
        # 1x1 卷积变体:专门用来在不改变图像长宽的前提下,压缩或放大通道数(跨通道信息整合)
        self.conv_1x1 = nn.Conv2d(in_channels=16, out_channels=8, kernel_size=1)

    def forward(self, x):
        # 卷积 -> 激活 -> 池化
        x = self.pool(torch.relu(self.conv1(x)))
        # 1x1 卷积改变通道数
        x = self.conv_1x1(x)
        return x

总结报告

让我们用一行最硬核的极客因果链,复盘卷积神经网络:

局部感知 + 参数共享  ⟹  参数量暴降  ⟹  手电筒滑动 (卷积)  ⟹  自底向上拼凑边缘、零件到物体  ⟹  构建像素世界的视觉机器\text{局部感知 + 参数共享} \implies \text{参数量暴降} \implies \text{手电筒滑动 (卷积)} \implies \text{自底向上拼凑边缘、零件到物体} \implies \text{构建像素世界的视觉机器}局部感知 + 参数共享⟹参数量暴降⟹手电筒滑动 (卷积)⟹自底向上拼凑边缘、零件到物体⟹构建像素世界的视觉机器

普通的网络看图片,看到的是一堆杂乱无章、被压扁的数字像素;而卷积神经网络看图片,看到的是空间位置的交织、线条的律动,以及局部到整体的浪漫演进。


欢迎在评论区留下你的思考: 在大模型时代,ViT(Vision Transformer)在很多视觉任务上大放异彩,逐渐开始挑战 CNN 的统治地位。你认为 Transformer 的"自注意力机制"相比于 CNN 的"局部感知",在看世界的方式上有什么本质的不同?