卷积神经网络 CNN

卷积神经网络(Convolutional Neural Network, CNN)是处理图像、视频、语音频谱等网格数据的经典模型。它通过局部感受野、参数共享和逐层抽象,把像素级输入转换成边缘、纹理、局部形状和高级语义特征。

本章使用 TensorFlow/Keras 搭建一个完整 CNN,并说明每个模块的作用、常见调参方法和排错思路。

适用场景

CNN 常用于:

  • 图像分类:判断图片属于猫、狗、车辆、植物等类别
  • 目标检测:定位并识别图片中的多个对象
  • 图像分割:为每个像素预测类别
  • 医学影像:辅助识别病灶、器官区域或异常模式
  • 时序或文本特征图:把一维序列转成局部模式学习问题

如果数据存在明显的局部结构,并且相邻位置之间有关系,CNN 往往是很好的起点。

核心层

Conv2D

Conv2D 使用多个卷积核在图像上滑动,提取局部特征。

from tensorflow import keras
from tensorflow.keras import layers

layers.Conv2D(
    filters=32,
    kernel_size=(3, 3),
    activation="relu",
    padding="same",
)

关键参数:

  • filters:卷积核数量,决定输出通道数
  • kernel_size:卷积核大小,常见为 3x3
  • padding="same":保持特征图尺寸,便于堆叠多层
  • activation="relu":引入非线性能力

MaxPooling2D

池化层降低特征图尺寸,减少计算量,并增强局部平移鲁棒性。

layers.MaxPooling2D(pool_size=(2, 2))

Dropout

Dropout 在训练时随机关闭一部分神经元,降低过拟合风险。

layers.Dropout(0.3)

Dense

卷积层负责提取特征,最后通常使用全连接层完成分类。

layers.Dense(10, activation="softmax")

构建一个基础 CNN

下面以 CIFAR-10 为例。CIFAR-10 包含 10 类彩色图片,每张图片大小为 32x32x3

import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers

(x_train, y_train), (x_test, y_test) = keras.datasets.cifar10.load_data()

x_train = x_train.astype("float32") / 255.0
x_test = x_test.astype("float32") / 255.0

model = keras.Sequential([
    layers.Input(shape=(32, 32, 3)),

    layers.Conv2D(32, 3, padding="same", activation="relu"),
    layers.BatchNormalization(),
    layers.Conv2D(32, 3, padding="same", activation="relu"),
    layers.MaxPooling2D(),
    layers.Dropout(0.25),

    layers.Conv2D(64, 3, padding="same", activation="relu"),
    layers.BatchNormalization(),
    layers.Conv2D(64, 3, padding="same", activation="relu"),
    layers.MaxPooling2D(),
    layers.Dropout(0.3),

    layers.Conv2D(128, 3, padding="same", activation="relu"),
    layers.BatchNormalization(),
    layers.GlobalAveragePooling2D(),

    layers.Dense(128, activation="relu"),
    layers.Dropout(0.4),
    layers.Dense(10, activation="softmax"),
])

model.compile(
    optimizer=keras.optimizers.Adam(learning_rate=1e-3),
    loss="sparse_categorical_crossentropy",
    metrics=["accuracy"],
)

model.summary()

这里使用 GlobalAveragePooling2D 替代 Flatten,可以减少参数量,让模型更不容易过拟合。

训练模型

callbacks = [
    keras.callbacks.EarlyStopping(
        monitor="val_accuracy",
        patience=8,
        restore_best_weights=True,
    ),
    keras.callbacks.ReduceLROnPlateau(
        monitor="val_loss",
        factor=0.5,
        patience=3,
        min_lr=1e-5,
    ),
]

history = model.fit(
    x_train,
    y_train,
    validation_split=0.1,
    epochs=50,
    batch_size=64,
    callbacks=callbacks,
)

test_loss, test_acc = model.evaluate(x_test, y_test, verbose=0)
print(f"test accuracy: {test_acc:.4f}")

EarlyStopping 能在验证集效果不再提升时停止训练;ReduceLROnPlateau 会在验证损失停滞时降低学习率。

加入数据增强

图像数据增强可以让模型见到更多变体,常用于提升泛化能力。

data_augmentation = keras.Sequential([
    layers.RandomFlip("horizontal"),
    layers.RandomRotation(0.08),
    layers.RandomZoom(0.1),
    layers.RandomTranslation(0.08, 0.08),
])

augmented_model = keras.Sequential([
    layers.Input(shape=(32, 32, 3)),
    data_augmentation,
    model,
])

对于真实项目,推荐把数据增强放在模型或 tf.data 管道中,并只在训练阶段启用。

训练曲线诊断

训练完成后应检查训练集和验证集曲线。

import matplotlib.pyplot as plt

plt.plot(history.history["accuracy"], label="train acc")
plt.plot(history.history["val_accuracy"], label="val acc")
plt.xlabel("epoch")
plt.ylabel("accuracy")
plt.legend()
plt.grid(True)
plt.show()

常见现象:

  • 训练准确率高、验证准确率低:过拟合,增加数据增强、Dropout 或正则化
  • 两条曲线都低:欠拟合,增加模型容量、训练轮次或改善输入特征
  • 验证损失震荡明显:学习率可能过高,或 batch size 太小
  • 训练很慢:检查是否使用 GPU,并优化输入管道

常见改进

  1. 使用更深的网络结构,例如 ResNet、EfficientNet、MobileNet。
  2. 使用迁移学习,用预训练模型作为特征提取器。
  3. 增加数据增强,但避免破坏标签语义。
  4. 使用 BatchNormalization 稳定训练。
  5. 对类别不均衡数据使用 class weight 或重采样。

小结

CNN 的基本套路是:卷积提取局部特征,池化降低空间尺寸,正则化减少过拟合,最后用分类层输出类别概率。掌握这一章后,可以继续学习图像分类、迁移学习和模型部署,把 CNN 应用到真实图片任务中。