☰
CNN 卷积神经网络入门指南:让机器“看懂“图像
2026/10/8 18:53:45 网站建设 项目流程

前面三篇文章,我们学完了全连接神经网络(ANN)的完整知识体系。但 ANN 处理图像有一个致命弱点——它要把图像展平成一维向量,丢失了空间结构信息。比如一张 28×28 的手写数字图片,ANN 直接把它拉成 784 维向量,像素之间的空间关系全没了。

卷积神经网络(CNN)就是为了解决这个问题而生的。它保留图像的二维结构,通过卷积核在图像上"滑动扫描",自动学习局部特征——从边缘、纹理到复杂图案,层层递进。CNN 是计算机视觉领域的基石模型,也是深度学习中最经典的网络结构之一。

今天这篇文章,我们从图像的基本知识出发,理解卷积神经网络的三大核心组件——卷积层、池化层、全连接层,掌握特征图大小计算、多通道卷积、填充与步长等关键概念,最后走通一个图像分类的完整流程。


一、图像基本知识

在学 CNN 之前,得先搞清楚图像在计算机里是怎么表示的。

像素与通道

一张图片在计算机里就是一个数字矩阵,每个元素就是一个像素值。

像素范围:0 到 255。

  • 0 表示纯黑色

  • 255 表示纯白色

  • 中间的值表示不同深浅的灰色或颜色

RGB 三通道:彩色图片由三个通道组成——红(R)、绿(G)、蓝(B)。每个通道是一个二维矩阵,三个矩阵叠加在一起就形成了我们看到的彩色图像。所以一张彩色图片的形状是 H×W×3,其中 H 是高度,W 是宽度,3 是通道数。

举个例子:一张 224×224 的彩色图片,在计算机里就是一个 224×224×3 的三维数组,总共有 224×224×3 = 150528 个数值。每个数值都在 0 到 255 之间,表示该位置该通道的颜色强度。而灰度图片只有一个通道,形状是 H×W×1,或者直接就是 H×W。

图片形状:HWC vs BCHW

不同的深度学习框架对图片形状的约定不同:

matplotlib(HWC):高度在前,通道在后。读取和展示图片时用的格式:

import matplotlib.pyplot as plt img = plt.imread('photo.jpg') # 读取图片,形状 HWC:[H, W, C] plt.imshow(img) # 展示图片

CNN(BCHW):批量在前,通道在中,高度宽度在后。PyTorch 处理图像时需要的格式。多了个 B(Batch)是因为训练时一次处理一批图片。

形状转换:从 HWC 转成 BCHW,需要两步操作:

# 假设 img 形状为 [H, W, C](HWC) img = img.permute(2, 0, 1) # 维度交换 → [C, H, W](CHW) img = img.unsqueeze(0) # 添加 batch 维度 → [1, C, H, W](BCHW)

permute()用于多个维度位置的交换,unsqueeze()在指定位置添加一个大小为 1 的维度。这两步是处理图像数据时的标准操作,务必牢记。

为什么要做这个维度转换?因为 PyTorch 的卷积层nn.Conv2d要求输入格式必须是[B, C, H, W]——批量在第一个维度,通道在第二个维度,高度和宽度在后两个维度。如果直接把 matplotlib 读出来的 HWC 格式扔进卷积层,会直接报维度不匹配的错误。


二、卷积神经网络概述

什么是 CNN

定义:CNN 是包含卷积层的神经网络,适合处理图像任务。

和 ANN 的全连接层相比,卷积层有两个核心优势:

  • 局部感知:卷积核只关注图像的一小块区域,不是全部像素,能捕捉局部特征(边缘、角点、纹理等)。

  • 参数共享:同一个卷积核在整张图上滑动,每个位置用的是同一组权重。参数量大大减少,而且特征具有平移不变性——物体出现在图片哪个位置都能被检测到。

组成部分

一个完整的 CNN 由三种层组成:

组件

作用

关键操作

卷积层

提取特征

卷积核滑动做点积

池化层

压缩特征

取最大值或平均值

全连接层

输出结果

展平后做分类/回归

CNN 的搭建流程

输入图片 → 数据预处理(归一化、HWC→BCHW) → 卷积层(提取特征) → 激活函数(引入非线性) → 池化层(压缩特征) → (卷积+激活+池化 可重复多组) → 全连接层(展平后输出分类结果)

注意:池化层之后传入全连接层时,需要先reshape把多维特征图展平成一维向量——因为全连接层只接受一维输入。

这个"展平"操作很容易被忽略。比如经过卷积和池化后,特征图形状是[B, 32, 7, 7],直接传给全连接层会报错,必须先变成[B, 1568](32×7×7=1568)才行。PyTorch 中用x.view(x.size(0), -1)来做,其中-1表示自动计算这个维度的大小。


三、卷积层:CNN 的核心

卷积层是 CNN 中最核心的部分,作用是自动学习并提取图像的局部特征。

卷积核(Filter / Kernel / 滤波矩阵)

卷积核是一个小的权重矩阵,核心任务是和输入图像的局部区域做点积运算。

运算过程:

  1. 卷积核放在图像的某个位置

  2. 把卷积核覆盖区域的像素值与卷积核中的权重逐个相乘

  3. 把所有乘积加起来,得到一个输出值

  4. 卷积核滑动到下一个位置,重复以上步骤

每个卷积核运算完毕后都会得到一张特征图。不同的卷积核学习不同的特征——有的学习边缘,有的学习纹理,有的学习形状。

输入图像 卷积核 特征图 ┌─────────┐ ┌───┐ ┌───────┐ │ 1 2 0 1 │ │1 0│ │ 5 3 │ │ 0 1 3 2 │ × │0 1│ → │ 4 6 │ │ 2 0 1 1 │ └───┘ └───────┘ │ 1 0 2 0 │ └─────────┘

填充(Padding)

作用:在图像四周补 0,有两个目的:

  • 学习边缘特征:如果不填充,图像边缘的像素被卷积核覆盖的次数比中间少,边缘特征学不充分。

  • 保持图片形状不变:填充合适的圈数可以让输出特征图和输入图像大小相同。

特征图大小计算公式

卷积操作后输出特征图的大小由以下公式决定:

N = ⌊(W - F + 2P) / S⌋ + 1

其中:

  • W = 输入尺寸(宽或高)

  • F = 卷积核大小

  • P = 填充大小

  • S = 步长

  • N = 输出尺寸

举个例子:输入图片 28×28,卷积核 3×3,步长 1,填充 1: N = ⌊(28 - 3 + 2×1) / 1⌋ + 1 = ⌊27⌋ + 1 = 28,输出还是 28×28。

如果步长改为 2:N = ⌊(28 - 3 + 2) / 2⌋ + 1 = ⌊13.5⌋ + 1 = 14,输出变成 14×14,缩小了一半。

这个公式是 CNN 网络设计的基础工具。每搭一层卷积或池化,都要用这个公式算一下输出尺寸,否则后面的全连接层维度就会算错。实际设计中,通常用表格把每一层的输入输出尺寸列出来,确保最后展平的维度和全连接层的输入维度匹配。

多通道卷积

输入图像通常是多通道的(比如 RGB 三通道),卷积核也必须是多通道的——卷积核的通道数必须等于输入的通道数。

多通道卷积的过程:

  1. 针对每个通道的数据,分别和卷积核对应通道做计算

  2. 把各通道的结果相加

  3. 再加一个偏置值

  4. 最终得到一张特征图

注意:不管输入有几个通道,一个卷积核只产生一张特征图。要产生多张特征图,就要用多个卷积核——输出通道数 = 卷积核个数。

API 调用

PyTorch 中卷积层的 API:

import torch.nn as nn conv = nn.Conv2d( in_channels=3, # 输入通道数(如 RGB 图片为 3) out_channels=16, # 卷积核数量 = 输出特征图数量 kernel_size=3, # 卷积核大小(3×3) stride=1, # 步长 padding=1 # 填充 )

参数含义非常直观:in_channels告诉模型输入有几个通道,out_channels告诉模型你要输出几张特征图(用几个卷积核),kernel_size决定每个卷积核多大,stride和padding控制输出大小。

这里有个细节值得注意:Conv2d的权重是可学习的——卷积核里的那些数值不是人为设定的,而是网络在训练过程中通过反向传播自动学习出来的。你只需要指定卷积核的数量和大小,具体学什么特征由训练数据和损失函数来决定。这就是深度学习"自动特征提取"的核心——卷积核自己学会了对什么特征敏感。


四、池化层:压缩特征

池化层通常放在卷积层之后,作用是压缩特征图。

四大作用

作用

说明

减少计算量

特征图变小了,后面的层参数减少

降低维度

从 28×28 压缩到 14×14,维度减半

减少内存消耗

存储的数据量减少了

提高模型鲁棒性

对位置微小的变化不敏感

两种池化方式

最大池化(Max Pooling):在窗口内取最大值。保留最显著的特征,是最常用的池化方式。

特征图 池化窗口2×2 输出 ┌───────┐ ┌───────┐ │ 1 3 2 4│ stride=2 │ 4 4 │ │ 5 2 1 0│ → 取最大值 → │ 6 3 │ │ 6 1 0 3│ │ 2 0 1 2│ └───────┘ └───────┘

平均池化(Average Pooling):在窗口内取平均值。保留整体信息,但不如最大池化常用。

多通道池化

池化层的操作是每个特征图分别池化,不改变通道数。输入 16 个通道,输出还是 16 个通道,只是每个通道的空间尺寸变小了。这一点和卷积层不同——卷积会改变通道数,池化不会。

池化层没有可学习参数

重要特性:池化层没有可学习的参数(没有权重和偏置),它只根据固定的数学统计规则,设置窗口大小,在对应位置做最大值或平均值运算。这也意味着池化层不需要训练,它只是一个固定的数学操作。

API 调用

# 最大池化 maxpool = nn.MaxPool2d(kernel_size=2, stride=2, padding=0) # 平均池化 avgpool = nn.AvgPool2d(kernel_size=2, stride=2, padding=0)

最常用的配置是kernel_size=2, stride=2,效果是特征图长宽各缩小一半——28×28 变成 14×14。

注意池化层和卷积层的一个重要区别:卷积层的步长默认是 1,而池化层的步长默认等于窗口大小(kernel_size)。也就是说,池化窗口默认不会重叠——每个位置只被计算一次,干净利落。当然你也可以手动设置stride让窗口重叠,但实际中很少这么做。


五、图像分类案例

把所有知识串起来,走通一个完整的 CNN 图像分类流程。

完整流程

加载数据 → 构建CNN网络 → 训练模型 → 进行测试

构建 CNN 网络

import torch import torch.nn as nn import torch.optim as optim from torch.utils.data import DataLoader from torchvision import datasets, transforms # === 1. 加载数据 === transform = transforms.Compose([ transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,)) ]) train_dataset = datasets.MNIST(root='./data', train=True, download=True, transform=transform) test_dataset = datasets.MNIST(root='./data', train=False, download=True, transform=transform) train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True) test_loader = DataLoader(test_dataset, batch_size=1000, shuffle=False) # === 2. 构建CNN网络 === class CNN(nn.Module): def __init__(self): super().__init__() # 第一组:卷积 + 激活 + 池化 self.conv1 = nn.Conv2d(1, 16, kernel_size=3, stride=1, padding=1) self.relu1 = nn.ReLU() self.pool1 = nn.MaxPool2d(kernel_size=2, stride=2) # 第二组:卷积 + 激活 + 池化 self.conv2 = nn.Conv2d(16, 32, kernel_size=3, stride=1, padding=1) self.relu2 = nn.ReLU() self.pool2 = nn.MaxPool2d(kernel_size=2, stride=2) # 全连接层 self.fc = nn.Linear(32 * 7 * 7, 10) # 28→14→7,通道32 def forward(self, x): # x 形状:[B, 1, 28, 28] x = self.pool1(self.relu1(self.conv1(x))) # → [B, 16, 14, 14] x = self.pool2(self.relu2(self.conv2(x))) # → [B, 32, 7, 7] x = x.view(x.size(0), -1) # 展平 → [B, 1568] x = self.fc(x) # → [B, 10] return x model = CNN() criterion = nn.CrossEntropyLoss() optimizer = optim.Adam(model.parameters(), lr=0.001) # === 3. 训练模型 === for epoch in range(10): model.train() for data, target in train_loader: optimizer.zero_grad() output = model(data) loss = criterion(output, target) loss.backward() optimizer.step() # === 4. 进行测试 === model.eval() correct = 0 with torch.no_grad(): for data, target in test_loader: output = model(data) pred = output.argmax(dim=1) correct += pred.eq(target).sum().item() accuracy = 100. * correct / len(test_loader.dataset) print(f"测试准确率:{accuracy:.2f}%")

代码解读

网络结构:两组"卷积+ReLU+最大池化"提取特征,最后接一个全连接层做分类。

第一组卷积:输入 1 通道(灰度图),输出 16 个特征图,卷积核 3×3,padding=1 保持尺寸不变。经过 ReLU 激活后,2×2 最大池化把 28×28 压缩成 14×14。

第二组卷积:输入 16 通道,输出 32 个特征图,同样 padding=1。池化后 14×14 变成 7×7。

全连接层:展平后 32×7×7=1568 维输入,输出 10 维(10 个数字类别)。x.view(x.size(0), -1)这个展平操作是卷积层到全连接层的桥梁,不可少。

为什么 CNN 比 ANN 强?同样的 MNIST 任务,ANN 准确率 97% 左右,CNN 能达到 99%+。因为 CNN 保留了图像的空间结构,卷积核能学习到"左上角有个弧线"这种空间特征,而 ANN 把图像展平后这些信息全丢了。


六、学习心得与建议

第一,理解"局部感知"和"参数共享"是理解 CNN 的钥匙。这两个概念解释了为什么 CNN 比 ANN 更适合处理图像——局部感知让网络关注局部结构,参数共享让网络用少量参数覆盖整张图,而且具备平移不变性。抓住这两个核心,CNN 的设计思想就通了。

第二,特征图大小公式是基本功。(W - F + 2P) / S + 1这个公式贯穿整个 CNN 的设计——每加一层卷积或池化,都要算输出尺寸变了多少。不弄清这个,全连接层的输入维度就会算错,代码直接报错。

第三,池化层是"免费的降维"。没有可学习参数,没有反向传播计算,只是一个固定操作。但它把特征图压缩了,后面的计算量和参数量都大幅减少。简单但不可或缺。

第四,卷积核的通道数和个数是两个容易搞混的概念。卷积核的通道数必须等于输入的通道数(才能做逐通道计算再相加),而卷积核的个数等于输出的特征图数。一句话记住:输入通道数决定卷积核有多"厚",输出通道数决定有多少个卷积核。


写在最后

从 ANN 到 CNN,是深度学习处理图像的关键升级。

ANN 把图像展平成一维向量,丢失了空间信息;CNN 用卷积核在图像上滑动扫描,保留了空间结构,能学习从边缘到纹理到复杂图案的层次化特征。

今天学完了 CNN 的三大核心组件——卷积层提取特征、池化层压缩特征、全连接层输出结果。但今天的案例还比较简单,真正的 CNN 经典模型(LeNet、AlexNet、VGG、ResNet)还有更多精妙的设计。下一步,我们会深入这些经典网络结构,学习更强大的 CNN 架构。

图像的世界很精彩,CNN 就是打开这扇门的钥匙。一步一步来,你也能成为那个让机器"看懂"世界的人。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询