深度学习入门:一文搞懂卷积神经网络(CNN)原理与实战
深度学习入门:一文搞懂卷积神经网络(CNN)原理与实战
关键词:CNN、卷积神经网络、图像分类、PyTorch、深度学习、计算机视觉
引言:为什么需要CNN?CNN能做什么?
你是否好奇:
- 手机是如何“看懂”人脸进行解锁的?
- 自动驾驶汽车如何识别红绿灯和行人?
- 医疗AI如何从X光片中发现早期肺癌?
这些看似“魔法”的能力,背后都离不开一种强大的深度学习模型——卷积神经网络(Convolutional Neural Network, CNN)。
CNN 是深度学习在计算机视觉(CV) 领域的突破性成果,专门用于处理图像、视频等具有空间结构的数据。它模仿人类视觉系统,能够自动从原始像素中学习到边缘、纹理、形状等特征,最终完成分类、检测、分割等复杂任务。
本文学习目标
学完本文,你将能够:
- 理解图像在计算机中的表示方式
- 掌握CNN三大核心组件(卷积、池化、全连接)的工作原理
- 知道如何计算特征图尺寸
- 使用PyTorch搭建并训练一个图像分类模型
- 了解经典CNN网络的发展脉络
无论你是初学者还是想巩固基础,本文都力求讲透每一个细节,让你真正“搞懂”CNN!
一、图像基础知识:图像 = 数组
在深入CNN之前,我们必须先理解:图像是什么?
1. 图像的本质是像素数组
- 黑白图像:是一个二维数组(高 × 宽),每个元素代表一个像素的灰度值,范围是
[0, 255]。0表示黑色,255表示白色。
- 彩色图像:是一个三维数组(高 × 宽 × 通道),通常有 3 个通道:R(红)、G(绿)、B(蓝)。
示例:一个 3×3 的彩色图像
想象一个 3×3 像素的小图像,其RGB三个通道的像素值如下:
红色通道 (R) 绿色通道 (G) 蓝色通道 (B)
[[100, 150, 200], [[ 50, 80, 120], [[ 20, 30, 40],
[ 80, 100, 180], [ 40, 60, 100], [ 10, 15, 25],
[ 60, 80, 160]] [ 30, 50, 90]] [ 5, 10, 20]]
这三个二维数组堆叠起来,就构成了一个 3×3×3 的三维数组,这就是计算机“看到”的图像。

小结:图像是一个三维数组,CNN 的任务就是从这个数组中自动学习有用的特征。
2. 加载真实图像
我们用 matplotlib 加载一张真实图片,查看其形状:
import numpy as np
import matplotlib.pyplot as plt
# 加载图像
img = plt.imread("data/img.jpg")
print("图像形状(H, W, C):", img.shape) # 输出:(640, 640, 3)
# 显示图像
plt.imshow(img)
plt.axis('off')
plt.title("Original Image")
plt.show()
二、CNN概述:结构与流程
卷积神经网络(CNN)是专为处理图像数据设计的深度学习模型。其核心结构包括:
输入层 → 卷积层 → 激活函数 → 池化层 → ... → 全连接层 → 输出层

CNN 的三大核心组件:
| 层类型 | 功能说明 |
|---|---|
| 卷积层(Conv) | 提取图像的局部特征(如边缘、纹理) |
| 池化层(Pool) | 降低特征图尺寸,减少计算量,防止过拟合 |
| 全连接层(FC) | 将特征整合,输出最终结果(如分类概率) |
CNN 的三大优势
- 局部连接(Local Connectivity):每个神经元只连接输入的一小块区域,减少参数。
- 参数共享(Parameter Sharing):同一个卷积核在整个图像上滑动,共享权重。
- 平移不变性(Translation Invariance):无论物体在图像中哪个位置,CNN都能识别。
三、卷积层:特征提取的核心
1. 什么是卷积?
卷积操作的本质是:用一个小的滤波器(卷积核)在图像上滑动,计算局部区域的加权和。
计算过程(步长=1,无填充)
假设我们有一个 5×5 的灰度图像和一个 3×3 的卷积核:
输入图像 (5×5) 卷积核 (3×3) 特征图 (3×3)
[[1, 2, 3, 4, 5], [[1, 0, -1], [[?, ?, ?],
[6, 7, 8, 9, 10], [1, 0, -1], [?, ?, ?],
[11,12,13,14,15], [1, 0, -1]] [?, ?, ?]]
[16,17,18,19,20],
[21,22,23,24,25]]
计算左上角值:
1×1 + 2×0 + 3×(-1) + 6×1 + 7×0 + 8×(-1) + 11×1 + 12×0 + 13×(-1)
= 1 + 0 - 3 + 6 + 0 - 8 + 11 + 0 - 13 = -6

卷积核向右移动1格,计算下一个值,直到覆盖整个图像。
卷积核就像一个“探测器”,不同的核可以检测不同的特征(如垂直边缘、水平边缘)。
2. 关键参数详解
(1)Padding(填充)
- 问题:卷积后图像变小(如 5×5 → 3×3)
- 解决:在图像边缘补一圈 0(zero-padding)
- 作用:
- 保持输出尺寸不变
- 保留边缘信息
# PyTorch 中 padding=1 表示四周各补一圈0
nn.Conv2d(in_channels=3, out_channels=6, kernel_size=3, padding=1)

(2)Stride(步长)
- 卷积核每次移动的像素数。
stride=1:每次移动1格stride=2:每次移动2格,输出尺寸减半

大步长可快速降维,常用于深层网络。
(3)多通道卷积
- 输入是 RGB 图(3通道),卷积核也必须是 3通道。
- 每个通道分别卷积,结果相加得到一个输出通道。
输入: [H, W, 3]
卷积核: [3, 3, 3] (3个3×3的核,分别对应R,G,B)
输出: [H', W', 1]

(4)多卷积核
- 使用多个卷积核 → 得到多个特征图 → 增加通道数
out_channels=6表示使用 6 个不同的卷积核,提取 6 种特征

3. 特征图大小计算公式
N = W − F + 2 P S + 1 N = \frac{W - F + 2P}{S} + 1 N=SW−F+2P+1
- W W W:输入尺寸
- F F F:卷积核大小
- P P P:padding
- S S S:stride
- N N N:输出尺寸(向下取整)
示例:输入 640×640,kernel=3,stride=2,padding=0
N = ( 640 − 3 + 0 ) / 2 + 1 = 319.5 → 319 N = (640 - 3 + 0)/2 + 1 = 319.5 → 319 N=(640−3+0)/2+1=319.5→319
4. PyTorch 实现卷积层
import torch
import torch.nn as nn
# 定义卷积层
conv_layer = nn.Conv2d(
in_channels=3, # 输入通道(RGB)
out_channels=4, # 输出通道(4个卷积核)
kernel_size=3, # 卷积核大小
stride=2, # 步长
padding=0 # 填充
)
# 图像预处理:HWC → CHW → Batch
img = torch.tensor(img_data).permute(2, 0, 1) # [H, W, C] → [C, H, W]
img = img.unsqueeze(0) # [C, H, W] → [1, C, H, W] (增加batch维度)
# 前向传播
feature_map = conv_layer(img.float())
print(feature_map.shape) # [1, 4, 319, 319]
注意:PyTorch 要求输入格式为
[Batch, Channel, Height, Width]
四、池化层:降维与鲁棒性
池化层位于卷积层之后,用于压缩特征图尺寸,减少参数量。
1. 两种常见池化方式
最大池化(Max Pooling)
输入 (4×4) 池化窗口 (2×2) 输出 (2×2)
[[1, 2, 3, 4], [[max, max], [[7, 9],
[5, 6, 7, 8], [max, max]] [13,15]]
[9,10,11,12],
[13,14,15,16]]

取每个 2×2 区域的最大值。
平均池化(Avg Pooling)
[[1, 2, 3, 4],
[5, 6, 7, 8],
[9,10,11,12],
[13,14,15,16]]
→ [[3, 5], [11,13]] (取平均值)

2. 池化特点
- 无参数:不需要学习,只是下采样操作
- 通道不变:输入 6 通道 → 输出仍为 6 通道
- 只对 H、W 降维,C 不变
# 最大池化
nn.MaxPool2d(kernel_size=2, stride=2)
# 平均池化
nn.AvgPool2d(kernel_size=2, stride=2)
五、经典 CNN 架构发展史
| 模型 | 年份 | 贡献 |
|---|---|---|
| LeNet | 1998 | 最早的 CNN,用于手写数字识别 |
| AlexNet | 2012 | 首次在 ImageNet 夺冠,引爆深度学习热潮 |
| VGG | 2014 | 结构规整(全用 3×3 卷积),易于理解 |
| GoogLeNet | 2014 | 引入 Inception 模块,提升效率 |
| ResNet | 2015 | 提出残差连接,解决深层网络退化问题 |
建议学习顺序:LeNet → AlexNet → VGG → ResNet
六、实战:用 PyTorch 实现图像分类
我们使用 CIFAR-10 数据集,包含 10 类 32×32 小图像(飞机、汽车、猫、狗等)。
1. 数据加载
from torchvision.datasets import CIFAR10
from torchvision.transforms import ToTensor
from torch.utils.data import DataLoader
# 加载训练集和测试集
train_dataset = CIFAR10(root='data', train=True, transform=ToTensor(), download=True)
valid_dataset = CIFAR10(root='data', train=False, transform=ToTensor())
# 创建数据加载器
train_loader = DataLoader(train_dataset, batch_size=8, shuffle=True)
ToTensor()自动将 PIL 图像转为[0,1]归一化的 Tensor。
2. 构建 CNN 模型
import torch.nn as nn
class ImageClassification(nn.Module):
def __init__(self):
super().__init__()
self.conv1 = nn.Conv2d(3, 6, 3) # 32→30
self.pool1 = nn.MaxPool2d(2, 2) # 30→15
self.conv2 = nn.Conv2d(6, 16, 3) # 15→13
self.pool2 = nn.MaxPool2d(2, 2) # 13→6
self.fc1 = nn.Linear(16*6*6, 120) # 576 → 120
self.fc2 = nn.Linear(120, 84)
self.out = nn.Linear(84, 10)
def forward(self, x):
# 卷积1 + 激活 + 池化
x = torch.relu(self.conv1(x)) # [8, 3, 32, 32] → [8, 6, 30, 30] → [8, 6, 15, 15]
x = self.pool1(x)
# 卷积2 + 激活 + 池化
x = torch.relu(self.conv2(x)) # [8, 6, 15, 15] → [8, 16, 13, 13] → [8, 16, 6, 6]
x = self.pool2(x)
# 展平:将特征图变为一维向量
# x.size(0) = batch_size = 8
# -1 表示自动计算,16*6*6 = 576
x = x.reshape(x.size(0), -1) # [8, 16, 6, 6] → [8, 576]
# 全连接层
x = torch.relu(self.fc1(x))
x = torch.relu(self.fc2(x))
return self.out(x)
关键点解释:
x.reshape(x.size(0), -1):将多维特征图展平为一维向量,供全连接层使用。16*6*6 = 576:最后一个池化层输出的特征图尺寸为 6×6,有16个通道,总特征数为 576。
3. 训练模型
def train(model, train_loader):
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)
for epoch in range(100):
total_loss = 0
correct = 0
total = 0
for x, y in train_loader:
output = model(x)
loss = criterion(output, y)
optimizer.zero_grad()
loss.backward()
optimizer.step()
total_loss += loss.item()
correct += (output.argmax(1) == y).sum().item()
total += y.size(0)
print(f"Epoch {epoch+1}, Loss: {total_loss/len(train_loader):.4f}, Acc: {correct/total:.2f}")
torch.save(model.state_dict(), 'cnn_model.pth')
4. 模型评估
def test(model, valid_loader):
model.eval()
correct = 0
total = 0
with torch.no_grad():
for x, y in valid_loader:
output = model(x)
correct += (output.argmax(1) == y).sum().item()
total += y.size(0)
print(f"Test Accuracy: {correct/total:.2f}")
运行结果:经过 100 轮训练,准确率可达 ~42%(简单模型,未调优)
七、常见问题解答
Q1:为什么卷积核大小通常是奇数?
- 中心对称:奇数核(如3×3)有明确的中心点,便于定位特征。
- Padding 方便:使用
padding=1可以完美保持图像尺寸不变。 - 偶数核(如2×2)没有中心像素,不利于特征定位。
Q2:如何选择卷积核数量(out_channels)?
- 没有固定公式,通常通过实验确定。
- 浅层网络:32、64
- 深层网络:128、256、512
- 原则:越深的层,提取的特征越抽象,需要更多卷积核。
Q3:卷积层和池化层的作用分别是什么?
| 层 | 作用 | 是否可学习 |
|---|---|---|
| 卷积层 | 提取局部特征(边缘、纹理) | 是(有权重) |
| 池化层 | 降维、减少计算、增强鲁棒性 | 否(无参数) |
八、总结与建议
CNN 核心知识思维导图

学习建议:
- 动手实践:自己写一遍代码,调试维度变化
- 可视化特征图:观察不同卷积层提取的特征
- 尝试改进模型:
- 增加网络深度
- 使用 BatchNorm
- 尝试 ResNet 结构
- 扩展学习:
- 图像检测(YOLO、Faster R-CNN)
- 图像分割(U-Net、Mask R-CNN)
参考资料
更多推荐
所有评论(0)