顾文强
顾文强
Published on 2025-03-02 / 4 Visits
0
0

举例讲解 CNN 原理

我们以经典的 MNIST手写数字识别 为例,用 TensorFlow/Keras 框架的代码逐步解释CNN的运行过程。我会在代码中插入详细注释,并用比喻辅助理解。


完整代码及逐行解释

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

# 1. 加载数据:MNIST手写数字数据集(28x28像素的灰度图)
(train_images, train_labels), (test_images, test_labels) = tf.keras.datasets.mnist.load_data()

# 2. 数据预处理:归一化到0~1,并调整形状以适应CNN输入
train_images = train_images.reshape((60000, 28, 28, 1)).astype('float32') / 255
test_images = test_images.reshape((10000, 28, 28, 1)).astype('float32') / 255

# 3. 构建CNN模型
model = models.Sequential([
    # --- 第一层:卷积 + 激活 + 池化 ---
    # 卷积层:用32个3x3的“放大镜”扫描图像
    layers.Conv2D(32, (3,3), activation='relu', input_shape=(28,28,1)),
    # 池化层:2x2窗口取最大值,相当于生成缩略图
    layers.MaxPooling2D((2,2)),
    
    # --- 第二层:卷积 + 激活 + 池化 ---
    layers.Conv2D(64, (3,3), activation='relu'),
    layers.MaxPooling2D((2,2)),
    
    # --- 展开特征图,连接全连接层 ---
    layers.Flatten(),  # 把多维数据压扁成一维
    layers.Dense(64, activation='relu'),  # 全连接层做高级判断
    layers.Dense(10, activation='softmax')  # 输出10个数字的概率
])

# 4. 编译模型:定义如何训练
model.compile(optimizer='adam',
              loss='sparse_categorical_crossentropy',
              metrics=['accuracy'])

# 5. 训练模型
model.fit(train_images, train_labels, epochs=5, batch_size=64)

# 6. 评估模型
test_loss, test_acc = model.evaluate(test_images, test_labels)
print(f"测试准确率:{test_acc}")

# 7. 预测单张图片(比如第一张测试图)
predictions = model.predict(test_images[0:1])
print("预测结果概率:", predictions)
print("预测数字:", tf.argmax(predictions[0]).numpy())

关键代码段解释

1. 数据预处理

train_images = train_images.reshape((60000, 28, 28, 1))  # 添加通道维度(灰度图通道数为1)
train_images = train_images.astype('float32') / 255       # 归一化到0~1
  • 比喻:把图片从“一叠纸”整理成“标准卡片”,每张卡片尺寸28x28,单色,亮度值压缩到0~1。


2. 卷积层

layers.Conv2D(32, (3,3), activation='relu', input_shape=(28,28,1))
  • 参数解释

    • 32:使用32种不同的“放大镜”(滤波器),每种负责找一种基础特征(如横线、竖线、弧线)。

    • (3,3):每个放大镜是3x3像素的小窗口。

    • activation='relu':只保留明显的特征(负数归零,正数保留)。

  • 输出形状:输入28x28 → 经过3x3卷积后变为26x26(边缘被裁剪),32个通道(每个滤波器生成一个通道)。


3. 池化层

layers.MaxPooling2D((2,2))
  • 作用:把2x2区域内的最大值保留,其他丢弃。

  • 效果:26x26 → 13x13(缩小一半),保留关键特征的位置,忽略细微变化。


4. 深层卷积

layers.Conv2D(64, (3,3), activation='relu')
  • 第二层卷积:用64种更复杂的“放大镜”扫描上一层的结果。

  • 特征升级:第一层找的是“边缘”,第二层可能找“拐角”、“圆圈”等组合特征。


5. 全连接层

layers.Flatten()
layers.Dense(64, activation='relu')
layers.Dense(10, activation='softmax')
  • Flatten:把最后的高维特征图(如3x3x64)压成一维向量(如576个数字)。

  • Dense层:像传统神经网络一样,综合所有特征判断数字是0~9中的哪一个。

  • Softmax:输出10个概率值,总和为1(例如“有80%概率是数字5”)。


运行过程可视化

假设输入是一个手写数字“5”:

  1. 第一层卷积:识别出“5”的顶部横线、右下弯曲线条。

    • 特征图可能显示这些线条的位置(激活区域高亮)。

  2. 池化后:缩小图像,保留这些线条的大致区域。

  3. 第二层卷积:识别出“横线+弯曲线”组合成的“上半圆”和“下半竖线”。

  4. 全连接层:综合这些高级特征,判断这是数字“5”。


为什么代码要这样写?

  1. 卷积层堆叠:逐步提取从简单到复杂的特征。

  2. 池化层穿插:减少计算量,增强鲁棒性。

  3. 通道数递增:浅层用较少滤波器(如32),深层用更多(如64),因为高层特征更复杂。

  4. 全连接收尾:将空间特征转换为分类决策。


试试改变参数!

  • 修改滤波器数量:Conv2D(16, ...) → 观察准确率变化。

  • 移除池化层:模型可能过拟合(记住训练集细节,但测试集表现差)。

  • 增加卷积层:模型可以识别更复杂的图案,但需要更多数据和计算资源。

通过这段代码,你可以直观地看到CNN如何从像素到边缘,再到形状,最后识别出数字的整个过程


Comment