CNN卷积神经网络入门:核心知识详解与速成指南
CNN,全称卷积神经网络(Convolutional Neural Network),从字面看,就是专门为图像这类网格结构数据量身打造的深度学习算法。当前最典型的落地方向是图像识别与处理——数据以2D网格形式存在,CNN通过卷积层、池化层和全连接层的协同,自动且高效地抽取特征,完成分类、检测等任务。
以上是CNN的标准解释,但坦白说,“卷积神经网络”这个译名本身就增加了认知门槛。如果换成“盘旋神经网络”,可能更直白。配合下面这张动图,一次搞懂:本质上就是用卷积核(也叫过滤器或核矩阵),像盘旋一样,从左到右、从上到下依次滑过输入数据,执行乘加运算。
CNN的起源
CNN的基础构想,其实可以回溯到上世纪60年代。Hubel和Wiesel在猫视觉皮层的研究中,发现存在专门对光斑、边缘这类简单模式敏感的神经元,这一发现为后来的CNN埋下了第一颗种子。
基于此,1980年Fukushima提出Neocognitron——一种层次化的无监督学习网络,这算是现代CNN的雏形。1998年,Yann LeCun正式提出CNN,目的是解决传统全连接网络处理图像时的固有缺陷。
图像数据有一个天然特性:相邻像素在空间上紧密关联,携带相似的局部特征。比如,一个像素和它周围的像素,很可能同属某条边缘或某个物体。传统全连接网络把图像直接拉成一维向量,这是丢弃了像素间的空间相邻性,破坏局部连续性,更谈不上利用图像的平移不变性。
举例来说,要判断一张图里有没有猫。无论猫在左侧还是右侧,你都希望模型能认出来。可全连接网络很难做到这一点,因为图像被拉成长条后,像素的相对位置信息彻底丢失了。
CNN的网络结构大致如此,下面拆解几个理解CNN绕不开的核心概念。
什么是卷积核
卷积核是CNN中最核心的组件,专职提取特征。每个卷积核包含一组可学习的权重,通过在输入数据上滑动——也就是前面说的“盘旋”操作——生成特征图(feature map),从而捕获空间和模式信息。
每个卷积核只与输入图像的一小块区域(局部感受野)打交道,这意味着神经元不必一次处理整张图,只关注一小块。这种机制让卷积核能捕捉到边缘、角点、纹理这类局部特征。而权重共享,就是同一个卷积核在整个输入图像上滑动时,始终使用同一组权重。这既大幅削减了参数量、提升了训练效率,更重要的是,一旦卷积核学会在图像某处识别某个特征,它就能在图像任何位置识别出同样的特征——这就是平移不变性,也是CNN擅长处理图像的关键所在。
核的大小也很有讲究。
小核尺寸(如1×1、3×3)擅长捕捉细粒度特征,对局部信息非常敏感。3×3的核尤其受欢迎,在特征提取和计算效率之间取得了良好平衡。多个3×3卷积层堆叠可以加深网络,而不会让参数量爆炸。
大核尺寸(如5×5、7×7)能捕获更广的空间信息,但参数多、计算量大,也更易过拟合。实践中,3×3核是许多现代CNN架构的默认选择。
卷积核如何与输入计算,为什么需要Padding?
卷积运算说白了就是:卷积核在输入数据上滑动,对覆盖的局部区域做元素级加权求和——小学数学的四则运算,然后输出一个新的特征图。比如下面这个例子,假设输入是二维矩阵,卷积核也是二维的,卷积核按步长滑动,在每个位置把核与对应区域做乘法再求和,就得到一个像素值。
或者换个角度:卷积核是一个3×3的矩阵,覆盖在输入数据上,两片“九宫格”重叠,对应位置相乘,再全部加总,输出到一个新位置。
那为什么要做padding?三个核心理由。
第一,提升边缘数据特征信号的有效性。 观察卷积过程会发现,边缘数据参与卷积的次数远少于中间区域。对于图像识别、边缘检测这类任务,边缘信息至关重要,不做padding的话,这些信息在深层网络里会逐渐消散。
第二,支撑深层次的网络结构。 深层网络需要多次卷积,每次卷积都会缩小特征图尺寸,不做padding,特征图会迅速小到无法继续操作。用padding保持尺寸不变,就能构建更深的网络。
第三,便于后续特征融合。——这个展开讲篇幅较长,以后聊到文生图和Unet时再细谈。想了解Unet的朋友,可以在文章底部留言,超过10人就优先安排。
具体怎么做Padding?通用做法是在原始输入数据的外围扩展一圈“边缘”,让卷积核能从边缘处开始计算。padding和计算过程可以参考下面的动图。
什么是下采样,池化又是什么意思?
下采样和池化,这两个概念经常同时出现,但稍有区别。
下采样是降低数据维度和复杂度的过程,目的是减轻后续层的计算压力,同时尽量保留重要信息。实现方式包括调整卷积核步长(比如从1步改成2步),以及池化操作。
池化是下采样的一种特殊形式,专用于深度学习,通过对特征图邻近区域做聚合来缩小尺寸。最常见的是最大池化和平均池化。
为什么引入池化?因为图像中,一旦一个特征被检测到,它的确切位置就不再像特征本身那样重要。池化让CNN对特征的存在更敏感,同时对平移等小幅变化保持一定的不变性,还能降低计算量和过拟合风险。
pooling被翻译成“池化”,确实有点故弄玄虚。本质就是在指定区域内提取最显著的特征,比如最大池化,就是在一个小区域里找最大值。这样做的优势是:如果图像有轻微平移,只要特征还在这个区域内,提取到的最大值就一样——这也是平移不变性的一种体现。
什么是Dropout,它有什么作用?
Dropout是一种非常实用的正则化技巧,由Srivastava等人在2014年提出。核心思想很简单:训练时,随机丢弃一部分神经元(以及它们的连接),让网络不过度依赖某些特定节点,从而防止过拟合。
通常dropout率设为0.5,即每次训练有一半的神经元随机消失。仅在训练阶段启用dropout,测试或评估时关闭,用全部神经元做决策。
如何用PyTorch实现CNN?
最后,用PyTorch搭一个简单的CNN试试看。
网络架构:
- 第一个卷积层:使用32个3×3的卷积核,步长为1,激活函数为ReLU。
- 第一个池化层:使用2×2的最大池化。
- 第二个卷积层:使用64个3×3的卷积核,步长为1,激活函数为ReLU。
- 第二个池化层:使用2×2的最大池化。
- 一个全连接层:将前一层的输出平坦化后,连接到一个具有128个神经元的全连接层,激活函数为ReLU。
- 输出层:一个具有10个神经元的全连接层,对应于10个类别的输出,使用Softmax作为激活函数。
import torch
import torch.nn as nn
import torch.nn.functional as F
class SimpleCNN(nn.Module):
def __init__(self):
super(SimpleCNN, self).__init__()
self.conv1 = nn.Conv2d(1, 32, kernel_size=3, stride=1, padding=1)
self.conv2 = nn.Conv2d(32, 64, kernel_size=3, stride=1, padding=1)
self.pool = nn.MaxPool2d(kernel_size=2, stride=2)
self.fc1 = nn.Linear(7*7*64, 128)
self.fc2 = nn.Linear(128, 10)
def forward(self, x):
x = self.pool(F.relu(self.conv1(x)))
x = self.pool(F.relu(self.conv2(x)))
x = x.view(-1, 7*7*64)
x = F.relu(self.fc1(x))
x = self.fc2(x)
return F.log_softmax(x, dim=1)
# 实例化模型
model = SimpleCNN()
print(model)