卷积神经网络(CNN)算法详解

一、CNN 是什么

CNN 是一类专门处理网格状数据的神经网络,最典型的是图像。它和普通全连接网络(MLP)最大的区别是:不把图像一次性拉平成一维向量,而是保留空间结构,用"卷积核"在图像上滑动提取局部特征。

可以把它理解成一套逐层加工流程:

浅层卷积:找边缘、颜色变化、简单纹理;

中层卷积:组合成角点、局部形状、图案;

深层卷积:形成物体部件甚至整体类别特征;

最后用全连接层或全局池化做分类。

CNN 适合图像的核心原因有三个:

局部连接:每个神经元只看图像的一小块区域,符合图像局部相关性。

权值共享:同一个卷积核在整张图上滑动复用,参数量大幅减少。

平移不变性:物体左右移动一点,卷积核仍能在新位置检测到相似特征。

二、CNN 的核心组件

  1. 卷积层

卷积层里有一组"卷积核"也叫滤波器。每个卷积核是一个小窗口,比如 3×3 或 5×5。

工作时,卷积核在输入图像上从左到右、从上到下滑动。每到一个位置,就把窗口内的输入值和卷积核对应位置的权重逐项相乘再相加,再加一个偏置,得到一个输出值。所有位置的结果拼起来,形成一张"特征图"。

一个卷积核提取一种特征。多个卷积核就得到多张特征图,表示不同特征。

关键参数:

卷积核大小:常见 3×3、5×5。3×3 最常用。

步幅 stride:每次滑动几格。步幅为 1 通常保留较多空间信息;步幅为 2 会缩小特征图。

填充 padding:在输入边缘补零。常用 padding=1 配合 3×3 卷积核,让输出尺寸和输入接近。

输入通道和输出通道:灰度图输入通道为 1,彩色图为 3;输出通道数等于该层卷积核个数。

  1. 激活函数

卷积后通常接 ReLU。它把负数变零,正数保留。作用是引入非线性,让网络能学习复杂模式,同时计算简单、缓解梯度消失。

  1. 池化层

池化用于降采样,减少特征图尺寸和计算量,同时增强一定程度的平移鲁棒性。

最常见的是最大池化:把一个小区域里的最大值保留下来。比如 2×2 最大池化,每次看 2×2 区域,只留最大值,特征图长宽减半。

池化没有可学习参数。

  1. 批归一化

批归一化在训练时对每层输出做标准化,让训练更稳定、可以使用更大学习率。通常放在卷积层和激活函数之间,或者卷积后接批归一化再接 ReLU。

  1. 全连接层

经过多轮卷积和池化后,特征图被拉平,送入全连接层做最终分类。现代网络中有时用全局平均池化代替拉平,减少参数量。

  1. Dropout

训练时随机丢弃一部分神经元,防止过拟合。通常放在全连接层之间。推理时关闭。

  1. 残差连接

深层 CNN 常用残差连接:把输入直接加到后面某层输出上,让梯度更容易传回去。ResNet 就是典型代表。入门可以先不写,但要知道这是深层网络的关键技巧。

三、CNN 的训练流程

和普通神经网络一样:

前向传播:输入图像经过卷积、激活、池化、全连接,得到各类别得分。

计算损失:把预测得分和真实标签比较。多分类常用交叉熵损失。

反向传播:从损失往回算每个卷积核权重和全连接权重的梯度。

参数更新:优化器根据梯度调整权重。常用 Adam 或带动量的随机梯度下降。

反复迭代:多轮训练,观察验证集准确率和损失。

推理:新图片前向传播一次,取得分最高的类别作为预测。

四、一个典型 CNN 结构

以 Fashion-MNIST 灰度图分类为例,输入是 28×28 单通道图片,共 10 类。

结构可以这样设计:

卷积层:输入 1 通道,输出 32 通道,3×3 卷积核,padding=1;

ReLU;

最大池化 2×2;

卷积层:32 通道到 64 通道,3×3,padding=1;

ReLU;

最大池化 2×2;

拉平;

全连接层到 128 维;

ReLU;

Dropout;

全连接层到 10 类输出。

两次 2×2 池化后,28×28 变成 7×7,所以拉平后是 64×7×7 个特征。

五、PyTorch 完整实例:Fashion-MNIST 图像分类

下面代码可直接运行。依赖:

bash

pip install torch torchvision

完整代码:

python

import torch

from torch import nn

from torch.utils.data import DataLoader

from torchvision import datasets, transforms

import matplotlib.pyplot as plt

1. 设备

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

print("使用设备:", device)

2. 数据预处理

transform = transforms.Compose([

transforms.ToTensor(),

transforms.Normalize((0.2860,), (0.3530,))

])

train_dataset = datasets.FashionMNIST(

root="./data", train=True, download=True, transform=transform

)

test_dataset = datasets.FashionMNIST(

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=256, shuffle=False)

3. 定义 CNN

class SimpleCNN(nn.Module):

def init (self, num_classes=10):

super().init ()

self.features = nn.Sequential(

nn.Conv2d(in_channels=1, out_channels=32, kernel_size=3, padding=1),

nn.ReLU(),

nn.MaxPool2d(kernel_size=2),

复制代码
        nn.Conv2d(in_channels=32, out_channels=64, kernel_size=3, padding=1),
        nn.ReLU(),
        nn.MaxPool2d(kernel_size=2)
    )
    self.classifier = nn.Sequential(
        nn.Flatten(),
        nn.Linear(64 * 7 * 7, 128),
        nn.ReLU(),
        nn.Dropout(0.3),
        nn.Linear(128, num_classes)
    )

def forward(self, x):
    x = self.features(x)
    x = self.classifier(x)
    return x

model = SimpleCNN().to(device)

4. 损失函数和优化器

loss_fn = nn.CrossEntropyLoss()

optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)

5. 训练和评估函数

def train_one_epoch(model, loader, loss_fn, optimizer, device):

model.train()

total_loss = 0.0

correct = 0

total = 0

复制代码
for images, labels in loader:
    images = images.to(device)
    labels = labels.to(device)

    optimizer.zero_grad()
    logits = model(images)
    loss = loss_fn(logits, labels)
    loss.backward()
    optimizer.step()

    total_loss += loss.item() * images.size(0)
    preds = logits.argmax(dim=1)
    correct += (preds == labels).sum().item()
    total += labels.size(0)

return total_loss / total, correct / total

@torch.no_grad()

def evaluate(model, loader, loss_fn, device):

model.eval()

total_loss = 0.0

correct = 0

total = 0

复制代码
for images, labels in loader:
    images = images.to(device)
    labels = labels.to(device)

    logits = model(images)
    loss = loss_fn(logits, labels)

    total_loss += loss.item() * images.size(0)
    preds = logits.argmax(dim=1)
    correct += (preds == labels).sum().item()
    total += labels.size(0)

return total_loss / total, correct / total

6. 开始训练

epochs = 5

for epoch in range(epochs):

train_loss, train_acc = train_one_epoch(

model, train_loader, loss_fn, optimizer, device

)

test_loss, test_acc = evaluate(model, test_loader, loss_fn, device)

复制代码
print(
    f"Epoch {epoch + 1}/{epochs} | "
    f"训练损失 {train_loss:.4f} | 训练准确率 {train_acc:.4f} | "
    f"测试损失 {test_loss:.4f} | 测试准确率 {test_acc:.4f}"
)

7. 单张图片预测示例

class_names = [

"T-shirt/top", "Trouser", "Pullover", "Dress", "Coat",

"Sandal", "Shirt", "Sneaker", "Bag", "Ankle boot"

]

model.eval()

image, label = test_dataset0

with torch.no_grad():

logits = model(image.unsqueeze(0).to(device))

pred = logits.argmax(dim=1).item()

print("真实类别:", class_nameslabel)

print("预测类别:", class_namespred)

8. 显示图片

plt.imshow(image.squeeze(), cmap="gray")

plt.title(f"true: {class_nameslabel}, pred: {class_namespred}")

plt.axis("off")

plt.show()

六、代码关键点解释

datasets.FashionMNIST:自动下载并加载数据集。

ToTensor():把图片转成 PyTorch 张量,并把像素缩放到 0 到 1。

Normalize:按数据集均值和标准差做标准化,训练更稳定。

Conv2d:二维卷积层。in_channels 是输入通道,out_channels 是卷积核个数。

padding=1 配合 3×3 卷积核,让卷积后长宽不变。

MaxPool2d(kernel_size=2):2×2 最大池化,特征图长宽减半。

Flatten():把多维特征图拉平成一维,送进全连接层。

CrossEntropyLoss:多分类常用损失,内部包含 Softmax 逻辑,所以模型输出直接是 logits,不再手动加 Softmax。

optimizer.zero_grad():清空上一轮梯度。

loss.backward():反向传播。

optimizer.step():更新参数。

model.train() 和 model.eval():切换训练/推理模式,影响 Dropout 和批归一化行为。

七、一个更小的 NumPy 直观例子:卷积核如何滑动

如果想理解"卷积核在图像上滑动"这件事,可以看这个简化演示。它不做训练,只展示一次二维卷积操作。

python

import numpy as np

5x5 输入,模拟一张小图

image = np.array([

1, 2, 3, 0, 1,

0, 1, 2, 3, 1,

1, 0, 1, 2, 0,

2, 1, 0, 1, 3,

1, 2, 1, 0, 2

], dtype=float)

3x3 卷积核,模拟边缘检测

kernel = np.array([

1, 0, -1,

1, 0, -1,

1, 0, -1

], dtype=float)

def conv2d_simple(img, k):

kh, kw = k.shape

out_h = img.shape0 - kh + 1

out_w = img.shape1 - kw + 1

out = np.zeros((out_h, out_w))

复制代码
for i in range(out_h):
    for j in range(out_w):
        window = img[i:i+kh, j:j+kw]
        # 逐元素相乘再求和
        out[i, j] = np.sum(window * k)
return out

feature_map = conv2d_simple(image, kernel)

print("输入:\n", image)

print("卷积核:\n", kernel)

print("输出特征图:\n", feature_map)

这个例子里,卷积核每滑动到一个 3×3 窗口,就把窗口和卷积核逐项相乘再求和,得到一个输出值。所有输出值组成特征图。实际 CNN 会加上偏置、激活函数,并且有很多个卷积核。

八、训练 CNN 的实用建议

先跑小数据集:Fashion-MNIST、CIFAR-10 很适合入门。

卷积核优先用 3×3:堆两个 3×3 通常比一个大卷积核更灵活。

通道数逐层增加:比如 32、64、128,特征越深通道越多。

池化或步幅卷积降尺寸:每降一次尺寸,通道数可适当增加。

全连接层前加 Dropout:小数据集上防过拟合。

学习率先用 1e-3 配 Adam:不收敛再调小。

看验证集:训练准确率很高但测试不涨,说明过拟合。

数据增强:随机裁剪、翻转、颜色扰动能显著提升泛化。

不要一开始堆很深:入门网络 2 到 4 个卷积层足够。

理解输出尺寸:卷积核大小、填充、步幅、池化共同决定特征图尺寸;常用"3×3 卷积加 padding=1 保持尺寸,2×2 池化减半"。

九、总结

CNN 的核心是:用卷积核在图像上滑动提取局部特征,通过多层堆叠形成从低级到高级的特征表示,再用池化降采样,最后分类。它比全连接网络更适合图像,因为局部连接、权值共享和层级特征提取大幅减少参数量并保留空间结构。上面的 PyTorch 例子是一个完整可训练的最小 CNN 流程,适合先跑通,再逐步加入批归一化、数据增强、残差连接等进阶组件。

相关推荐
FII工业富联科技服务1 小时前
多智能体如何进入制造现场?从业务协同到数字孪生验证
大数据·人工智能·算法·机器人·制造·数字孪生
golang学习记1 小时前
IDEA 开始换芯,发布全新AI版终端
java·人工智能·intellij-idea
武子康1 小时前
给游戏 Agent 接口时,隐藏答案会从哪条路漏出去?
人工智能
指针向南2 小时前
批量压缩视频有文件没变小?HandBrake等3款对比
图像处理·人工智能·计算机视觉·音视频
≮傷£≯√2 小时前
httpserver_QNetWorkClient
算法
isfox2 小时前
Python3 协程:异步编程的核心机制与实战
python
Whoami!2 小时前
F-《AI接口协议:API调模型、MCP接工具、A2A连Agent》
人工智能·mcp·a2a·ai协议
vipxieliang2 小时前
AI Agent 安全治理:从原则倡导到能力落地
人工智能·openai
Evand J2 小时前
【PMSM例程11】永磁同步电机(PMSM)基于UKF的转速与转子位置估计。附下载链接
算法·matlab·卡尔曼滤波·永磁同步电机·ukf·pmsm·位置估计