找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

407

积分

0

好友

47

主题
发表于 2025-12-27 10:37:19 | 查看: 34| 回复: 0

在计算机视觉领域,随着网络层数的增加,模型性能的提升并非总是线性的,深层网络往往会遭遇梯度消失、爆炸或网络退化等问题。ResNet(残差网络)通过引入“残差块”这一核心结构,巧妙地解决了深层网络的训练难题,使得构建数百甚至上千层的网络成为可能,并显著提升了图像识别的精度。本文将介绍ResNet的结构原理,并使用TensorFlow分别实现ResNet50和ResNet18模型,在经典的Fashion-MNIST和CIFAR10数据集上完成图像分类任务。

CNN结构演进与深度问题

卷积神经网络(CNN)通过卷积、池化等操作自动提取图像特征,在图像识别任务上取得了巨大成功。从早期的LeNet到后来的VGG、GoogleNet,网络结构不断加深。然而,实验发现,简单地增加网络深度并不会带来性能的持续提升,反而可能导致训练误差和测试误差双双增大,这种现象被称为“网络退化”(Degradation Problem)。这并非过拟合,而是因为深度网络在反向传播时,梯度信息难以有效传递。

深度卷积神经网络(ResNet)

ResNet的核心思想是引入“快捷连接”(Shortcut Connection)或“跳跃连接”(Skip Connection),让网络能够学习输入与输出之间的“残差”,而非直接学习一个复杂的底层映射。

残差块(Residual Block)

残差块是ResNet的基本构建单元。对于一个堆积层结构,其输入为 x,期望输出为 H(x)。我们让网络学习残差 F(x) = H(x) - x,那么原始映射就变成了 F(x) + x
这种设计的优点是:即使堆叠的层没有学到有用的信息(即 F(x) ≈ 0),残差块也至少能恒等映射(Identity Mapping)回输入 x,从而保证网络性能不会下降。这为构建更深的网络提供了保障。

残差网络

通过堆叠多个残差块,可以构建出很深的残差网络,如ResNet-34, ResNet-50, ResNet-101等。不同深度的ResNet主要区别在于残差块的堆叠数量。对于输入和输出维度不同的情况,快捷连接需要通过一个1x1的卷积层来进行维度匹配(Projection Shortcut)。

ResNet v2

在原始ResNet(常称为ResNet v1)的基础上,研究者提出了改进版本ResNet v2。其主要变化是采用了“预激活”(Pre-activation)结构:在每个残差块中,先进行批量归一化(BatchNorm)和ReLU激活,再进行卷积操作。这种结构使信息在前向和反向传播中流动更加顺畅,进一步提升了训练效果。

残差块的 TensorFlow 2 实现

以下是残差块的一个基础实现示例:

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

class ResidualBlock(layers.Layer):
    def __init__(self, filters, strides=1, use_projection=False):
        super(ResidualBlock, self).__init__()
        self.conv1 = layers.Conv2D(filters, 3, strides=strides, padding='same', use_bias=False)
        self.bn1 = layers.BatchNormalization()
        self.relu1 = layers.Activation('relu')

        self.conv2 = layers.Conv2D(filters, 3, padding='same', use_bias=False)
        self.bn2 = layers.BatchNormalization()

        # 如果需要匹配维度(如下采样时)
        if use_projection:
            self.projection = layers.Conv2D(filters, 1, strides=strides, padding='same', use_bias=False)
        else:
            self.projection = lambda x: x

        self.relu2 = layers.Activation('relu')

    def call(self, inputs, training=False):
        residual = self.projection(inputs)

        x = self.conv1(inputs)
        x = self.bn1(x, training=training)
        x = self.relu1(x)

        x = self.conv2(x)
        x = self.bn2(x, training=training)

        x = layers.add([x, residual]) # 关键:将残差与主路径输出相加
        return self.relu2(x)

ResNet50 搭建

ResNet50由多个阶段(Stage)构成,每个阶段包含若干个瓶颈残差块(Bottleneck Residual Block),其特点是使用1x1卷积先降维再升维,以减少参数量。

ResNet18/34 模型

ResNet18和ResNet34使用基础残差块(两个3x3卷积层),结构相对简单。

残差块(ResNet18/34):如上述ResidualBlock所示,每个块包含两个3x3卷积层。
ResNet18/34搭建:通过堆叠不同数量的基础残差块(如ResNet18为[2, 2, 2, 2]),并配合池化层和全连接层,即可构建完整的网络模型。

Fashion-MNIST分类实战

Fashion-MNIST是一个包含10个类别、共7万张灰度服装图像的数据集,是测试图像分类模型的经典基准。

TensorFlow实现ResNet50

首先,我们构建一个适用于Fashion-MNIST(需调整输入通道)的ResNet50模型。这里我们定义一个简化的构建函数。

def build_resnet50(input_shape=(28, 28, 1), num_classes=10):
    # 输入层
    inputs = tf.keras.Input(shape=input_shape)
    # 初始卷积与池化
    x = layers.Conv2D(64, 7, strides=2, padding='same', use_bias=False)(inputs)
    x = layers.BatchNormalization()(x)
    x = layers.Activation('relu')(x)
    x = layers.MaxPooling2D(3, strides=2, padding='same')(x)

    # 堆叠残差阶段 (此处简化,实际需根据ResNet50结构堆叠瓶颈块)
    # Stage 2, 3, 4, 5 ...
    # x = _bottleneck_block(x, filters=256, blocks=3, strides=1) 等

    # 全局平均池化与输出
    x = layers.GlobalAveragePooling2D()(x)
    outputs = layers.Dense(num_classes, activation='softmax')(x)

    model = Model(inputs, outputs)
    return model

# 检查环境
import tensorflow as tf
print("TensorFlow version:", tf.__version__)

读取数据集

TensorFlow内置了Fashion-MNIST数据集,可以方便地加载。

(x_train, y_train), (x_test, y_test) = tf.keras.datasets.fashion_mnist.load_data()
# 数据预处理:归一化并增加通道维度
x_train = x_train.astype('float32') / 255.0
x_test = x_test.astype('float32') / 255.0
x_train = x_train[..., tf.newaxis] # 增加通道维度 (28, 28, 1)
x_test = x_test[..., tf.newaxis]

训练

编译并训练模型,使用常见的优化器和损失函数。

model = build_resnet50(input_shape=(28, 28, 1), num_classes=10)
model.compile(optimizer='adam',
              loss='sparse_categorical_crossentropy',
              metrics=['accuracy'])

history = model.fit(x_train, y_train,
                    batch_size=64,
                    epochs=10,
                    validation_split=0.2)

可视化

训练完成后,可以绘制损失和准确率曲线,直观观察模型的学习过程。

import matplotlib.pyplot as plt

def plot_history(history):
    fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 4))
    ax1.plot(history.history['loss'], label='Train Loss')
    ax1.plot(history.history['val_loss'], label='Val Loss')
    ax1.set_title('Loss')
    ax1.legend()

    ax2.plot(history.history['accuracy'], label='Train Acc')
    ax2.plot(history.history['val_accuracy'], label='Val Acc')
    ax2.set_title('Accuracy')
    ax2.legend()
    plt.show()

plot_history(history)

预测

在测试集上评估模型性能,并可以进行单张图片的预测。

test_loss, test_acc = model.evaluate(x_test, y_test, verbose=2)
print(f'\nTest accuracy: {test_acc}')

# 随机选择一张测试图片进行预测
import numpy as np
idx = np.random.randint(0, len(x_test))
img = x_test[idx]
true_label = y_test[idx]

pred = model.predict(img[tf.newaxis, ...]) # 增加批次维度
pred_label = np.argmax(pred[0])
print(f'True label: {true_label}, Predicted label: {pred_label}')

CIFAR10分类实战

CIFAR10数据集包含10个类别、共6万张32x32的彩色图像,是另一个广泛使用的深度学习基准数据集。

TensorFlow实现ResNet18

对于CIFAR10这样的小尺寸图像,ResNet18是更常用且高效的选择。

读取数据集

(x_train, y_train), (x_test, y_test) = tf.keras.datasets.cifar10.load_data()
# 将标签从二维数组转换为一维
y_train = y_train.squeeze()
y_test = y_test.squeeze()

数据处理

进行数据归一化,并可使用数据增强来提升模型泛化能力。

# 归一化
x_train = x_train.astype('float32') / 255.0
x_test = x_test.astype('float32') / 255.0

# 数据增强(可选)
data_augmentation = tf.keras.Sequential([
    layers.RandomFlip("horizontal"),
    layers.RandomRotation(0.1),
    layers.RandomZoom(0.1),
])

训练与预测

构建ResNet18模型并进行训练,流程与Fashion-MNIST类似。

def build_resnet18(input_shape=(32, 32, 3), num_classes=10):
    # 实现ResNet18的网络结构
    # 包含初始卷积层,4个阶段(每个阶段2个基础残差块),全局平均池化和全连接层
    pass # 具体实现省略,结构可参考前述ResidualBlock进行堆叠

model_cifar = build_resnet18(input_shape=(32, 32, 3), num_classes=10)
model_cifar.compile(optimizer='adam',
                    loss='sparse_categorical_crossentropy',
                    metrics=['accuracy'])

history_cifar = model_cifar.fit(x_train, y_train,
                                batch_size=128,
                                epochs=50,
                                validation_split=0.2)
# 评估
test_loss_c, test_acc_c = model_cifar.evaluate(x_test, y_test, verbose=2)
print(f'CIFAR10 Test accuracy: {test_acc_c}')

通过以上实战,我们展示了如何使用TensorFlow框架搭建不同深度的ResNet模型,并将其应用于两个经典的图像分类数据集。ResNet的残差学习思想深刻影响了后续的神经网络设计,是掌握现代深度学习模型的重要一环。




上一篇:Android序列化方案对比:Parcelable与Serializable接口详解
下一篇:C语言编译链接完整流程与预处理详解:从源码到可执行程序的调试指南
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-11 20:15 , Processed in 0.362464 second(s), 39 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表