残差网络 (ResNet 与 ResNeXt)

探索残差连接的革命性突破,深入理解ResNet如何解决深度网络训练难题

发布于 2025-08-10 更新于 2025-08-102,493 字 6 分钟
残差网络 (ResNet 与 ResNeXt)的文章封面
展开文章目录
  1. 1. 残差网络的理论基础
  2. 1.1 函数嵌套与表达能力
  3. 1.2 最优函数寻找问题
  4. 1.3 函数嵌套的重要性
  5. 1.4 残差网络的核心洞察
  6. 2. ResNet 网络架构设计
  7. 2.1 残差块的设计原理
  8. 3. ResNet 与 ResNeXt 完整网络架构
  9. 3.1 ResNet 网络结构
  10. 3.2 ResNet-18 结构分析
  11. 3.3 ResNeXt 的改进
  12. 3.4 PyTorch 实现
  13. 4. 网络结构分析
  14. 4.1 ResNet 与 ResNeXt 参数对比
  15. 4.2 网络参数统计对比
  16. 5. 模型训练与评估
  17. 5.1 训练配置设置
  18. 5.2 ResNet 训练结果
  19. 5.3 ResNeXt 训练结果
  20. 5.4 性能对比分析
  21. 参考资料

1. 残差网络的理论基础

1.1 函数嵌套与表达能力

1.2 最优函数寻找问题

只要问题是可计算的,理论上就一定存在最佳函数 ff^* 将问题完美解决。使用现有神经网络 F\mathcal{F} 解决该问题的前提,是找到当前架构 F\mathcal{F} 下的最佳函数 fFf_{\mathcal{F}}^* 作为问题的近似解。训练模型,调整现有函数 ff 的参数,使输入数据 xx 经网络 ff 输出后与真实标签 yy 的损失值 L(f(x),y)L(f(x), y) 最小,最终得到使损失值最小的函数 fFf_{\mathcal{F}}^*

fF=argminfFL(f(x),y)f_{\mathcal{F}}^* = \arg\min_{f \in \mathcal{F}} L(f(x), y)

1.3 函数嵌套的重要性

如下图,对于非嵌套的函数类,虽 fF1f_{\mathcal{F}_1}^*fF3f_{\mathcal{F}_3}^* 逐渐与 ff^* 接近,但 fF4f_{\mathcal{F}_4}^*fF6f_{\mathcal{F}_6}^* 又逐渐远离 ff^*;只有对于嵌套的函数类,F1F2F6\mathcal{F}_1 \subseteq \mathcal{F}_2 \subseteq \cdots \subseteq \mathcal{F}_6fF6f_{\mathcal{F}_6}^* 逐步比 fF1f_{\mathcal{F}_1}^* 更接近 ff^*

嵌套与非嵌套函数类的性能对比

1.4 残差网络的核心洞察


2. ResNet 网络架构设计

2.1 残差块的设计原理

残差块设计

传统网络层的学习方式

  • 直接学习 H(x)H(x):输入到输出的完整映射
  • 当网络很深时,直接学习复杂映射变得困难
  • 梯度消失和训练困难问题

残差学习方式

  • 学习残差 F(x)=H(x)xF(x) = H(x) - x
  • 假设残差比原映射更容易学习
  • 最终输出:H(x)=F(x)+xH(x) = F(x) + x
  • F(x)=0F(x) = 0 时,自然退化为恒等映射

3. ResNet 与 ResNeXt 完整网络架构

3.1 ResNet 网络结构

3.2 ResNet-18 结构分析

ResNet结构组成

网络开始部分

  • 7×7 卷积层 (输出通道64,步幅2)
  • 3×3 最大池化层 (步幅2)

残差块分组设计

  • 第1组:两个连续的64通道残差块
  • 第2组:特征图减半、通道数翻倍 + 瓶颈结构
  • 第3组:重复第2组的设计模式
  • 第4组:继续重复,形成深层特征提取

ResNet-18 层数计算

  • 不考虑跳跃连接的1×1卷积层
  • 共计:1 + 2×2 + (2+2)×3 = 17 个卷积层
  • 考虑最后一个全连接层:共18个计算密集层
  • 因此称为 ResNet-18
  • 其他变体:ResNet-101、ResNet-152等

3.3 ResNeXt 的改进

ResNet与ResNeXt架构对比图

3.4 PyTorch 实现

from typing import Optional

import torch.nn.functional as F
from torch import nn, Tensor


class ResNetBlock(nn.Module):
    def __init__(
            self,
            in_channels: int,
            out_channels: int, *,
            stride: int = 1,
            bottleneck_ratio: int = 4,
            downsample: Optional[nn.Module] = None):
        """
        后激活的残差块

        :param in_channels: 输入通道数
        :param out_channels: 输出通道数
        :param stride: 卷积步长
        :param bottleneck_ratio: 瓶颈比率,控制中间层宽度
        :param downsample: 下采样层,用于维度匹配
        """
        super().__init__()

        # 计算中间层通道数
        assert bottleneck_ratio >= 1, f'瓶颈比率 ({bottleneck_ratio}) 应大于或等于 1'
        mid_channels = out_channels // bottleneck_ratio if bottleneck_ratio > 1 else out_channels

        if bottleneck_ratio > 1:
            # 1x1 Conv -> 3x3 Conv -> 1x1 Conv
            assert mid_channels > 0, f'中间通道数 ({mid_channels}) 必须大于 0'

            self.main_path = nn.Sequential(
                nn.Conv2d(in_channels, mid_channels, kernel_size=1, stride=1, padding=0, bias=False),
                nn.BatchNorm2d(mid_channels),
                nn.ReLU(),

                nn.Conv2d(mid_channels, mid_channels, kernel_size=3, stride=stride, padding=1, bias=False),
                nn.BatchNorm2d(mid_channels),
                nn.ReLU(),

                nn.Conv2d(mid_channels, out_channels, kernel_size=1, stride=1, padding=0, bias=False),
                nn.BatchNorm2d(out_channels)
            )
        else:
            # 3x3 Conv -> 3x3 Conv
            self.main_path = nn.Sequential(
                nn.Conv2d(in_channels, out_channels, kernel_size=3, stride=stride, padding=1, bias=False),
                nn.BatchNorm2d(out_channels),
                nn.ReLU(),

                nn.Conv2d(out_channels, out_channels, kernel_size=3, stride=1, padding=1, bias=False),
                nn.BatchNorm2d(out_channels)
            )

        # 匹配维度用于跳跃连接
        if downsample is not None:
            self.shortcut = downsample
        elif stride != 1 or in_channels != out_channels:  # 空间尺寸、通道数发生变化时,再调整维度
            self.shortcut = nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=stride, bias=False)
        else:
            self.shortcut = nn.Identity()  # 恒等映射

    def forward(self, x: Tensor) -> Tensor:
        out = self.main_path(x) + self.shortcut(x)  # 主路径与跳跃连接相加
        out = F.relu(out)  # 激活加和后的结果
        return out


class ResNeXtBlock(nn.Module):
    def __init__(
            self,
            in_channels: int,
            out_channels: int, *,
            stride: int = 1,
            groups: int = 1,
            bottleneck_ratio: int = 4,
            downsample: Optional[nn.Module] = None):
        """
        预激活的分组残差块

        :param in_channels: 输入通道数
        :param out_channels: 输出通道数
        :param stride: 卷积步长
        :param groups: 分组卷积的组数
        :param bottleneck_ratio: 瓶颈比率,控制中间层宽度
        :param downsample: 下采样层,用于维度匹配
        """
        super().__init__()

        # 计算中间层通道数
        assert bottleneck_ratio >= 1, f'瓶颈比率 ({bottleneck_ratio}) 应大于或等于 1'
        mid_channels = out_channels // bottleneck_ratio if bottleneck_ratio > 1 else out_channels

        if bottleneck_ratio > 1:
            # 1x1 Conv -> 3x3 Conv -> 1x1 Conv
            assert mid_channels % groups == 0, f'中间通道数 ({mid_channels}) 必须能被卷积分组数 ({groups}) 整除'
            assert mid_channels > 0, f'中间通道数 ({mid_channels}) 必须大于 0'

            self.main_path = nn.Sequential(
                nn.BatchNorm2d(in_channels), nn.ReLU(),
                nn.Conv2d(in_channels, mid_channels, kernel_size=1, stride=1, padding=0, bias=False),

                nn.BatchNorm2d(mid_channels), nn.ReLU(),
                nn.Conv2d(mid_channels, mid_channels, kernel_size=3, stride=stride, padding=1, groups=groups,
                          bias=False),

                nn.BatchNorm2d(mid_channels), nn.ReLU(),
                nn.Conv2d(mid_channels, out_channels, kernel_size=1, stride=1, padding=0, bias=False)
            )
        else:
            # 3x3 Conv -> 3x3 Conv
            assert out_channels % groups == 0, f'输出通道数 ({out_channels}) 必须能被卷积分组数 ({groups}) 整除'

            self.main_path = nn.Sequential(
                nn.BatchNorm2d(in_channels), nn.ReLU(),
                nn.Conv2d(in_channels, out_channels, kernel_size=3, stride=stride, padding=1, groups=groups,
                          bias=False),

                nn.BatchNorm2d(out_channels), nn.ReLU(),
                nn.Conv2d(out_channels, out_channels, kernel_size=3, stride=1, padding=1, groups=groups, bias=False)
            )

        # 匹配维度用于跳跃连接
        if downsample is not None:
            self.shortcut = downsample
        elif stride != 1 or in_channels != out_channels:  # 空间尺寸、通道数发生变化时,再调整维度
            self.shortcut = nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=stride, bias=False)
        else:
            self.shortcut = nn.Identity()

    def forward(self, x: Tensor) -> Tensor:
        return self.main_path(x) + self.shortcut(x)


class ResNet(nn.Module):
    def __init__(self, num_classes: int):
        super().__init__()
        self.model = nn.Sequential(
            nn.Conv2d(in_channels=1, out_channels=64, kernel_size=7, stride=2, padding=3, bias=False),
            nn.BatchNorm2d(64),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=3, stride=2, padding=1),

            ResNetBlock(64, 64, bottleneck_ratio=1),
            ResNetBlock(64, 64, bottleneck_ratio=1),

            # 组 1
            ResNetBlock(64, 128, stride=2, bottleneck_ratio=1),
            ResNetBlock(128, 128, bottleneck_ratio=4),

            # 组 2
            ResNetBlock(128, 256, stride=2, bottleneck_ratio=1),
            ResNetBlock(256, 256, bottleneck_ratio=4),

            # 组 3
            ResNetBlock(256, 512, stride=2, bottleneck_ratio=1),
            ResNetBlock(512, 512, bottleneck_ratio=4),

            nn.AdaptiveAvgPool2d(1), nn.Flatten(),
            nn.Linear(in_features=512, out_features=num_classes),
        )

        self._initialize_weights()

    def _initialize_weights(self):
        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                nn.init.kaiming_normal_(m.weight, mode='fan_in', nonlinearity='relu')
                if m.bias is not None: nn.init.constant_(m.bias, 0)
            elif isinstance(m, nn.Linear):
                nn.init.xavier_uniform_(m.weight)
                if m.bias is not None: nn.init.constant_(m.bias, 0)

    def forward(self, x) -> Tensor:
        return self.model(x)


class ResNeXt(nn.Module):
    def __init__(self, num_classes: int, cardinality: int = 32):
        super().__init__()
        self.model = nn.Sequential(
            nn.Conv2d(in_channels=1, out_channels=64, kernel_size=7, stride=2, padding=3, bias=False),
            nn.BatchNorm2d(64),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=3, stride=2, padding=1),

            ResNeXtBlock(64, 64, groups=1, bottleneck_ratio=1),
            ResNeXtBlock(64, 64, groups=1, bottleneck_ratio=1),

            # 组 1
            ResNeXtBlock(64, 128, stride=2, groups=cardinality, bottleneck_ratio=4),
            ResNeXtBlock(128, 128, groups=cardinality, bottleneck_ratio=4),

            # 组 2
            ResNeXtBlock(128, 256, stride=2, groups=cardinality, bottleneck_ratio=4),
            ResNeXtBlock(256, 256, groups=cardinality, bottleneck_ratio=4),

            # 组 3
            ResNeXtBlock(256, 512, stride=2, groups=cardinality, bottleneck_ratio=4),
            ResNeXtBlock(512, 512, groups=cardinality, bottleneck_ratio=4),

            nn.AdaptiveAvgPool2d(1), nn.Flatten(),
            nn.Linear(in_features=512, out_features=num_classes),
        )

        self._initialize_weights()

    def _initialize_weights(self):
        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                nn.init.kaiming_normal_(m.weight, mode='fan_in', nonlinearity='relu')
                if m.bias is not None: nn.init.constant_(m.bias, 0)
            elif isinstance(m, nn.Linear):
                nn.init.xavier_uniform_(m.weight)
                if m.bias is not None: nn.init.constant_(m.bias, 0)

    def forward(self, x) -> Tensor:
        return self.model(x)

4. 网络结构分析

4.1 ResNet 与 ResNeXt 参数对比

使用torchinfo库的summary函数执行输出维度测试:

from torchinfo import summary

model = ResNet(num_classes=10)
summary(model, input_size=(1, 1, 224, 224))
==========================================================================================
Layer (type:depth-idx)                   Output Shape              Param #
==========================================================================================
ResNet                                   [1, 10]                   --
├─Sequential: 1-1                        [1, 10]                   --
│    └─Conv2d: 2-1                       [1, 64, 112, 112]         3,136
│    └─BatchNorm2d: 2-2                  [1, 64, 112, 112]         128
│    └─ReLU: 2-3                         [1, 64, 112, 112]         --
│    └─MaxPool2d: 2-4                    [1, 64, 56, 56]           --
│    └─ResNetBlock: 2-5                  [1, 64, 56, 56]           --
│    │    └─Sequential: 3-1              [1, 64, 56, 56]           73,984
│    │    └─Identity: 3-2                [1, 64, 56, 56]           --
│    └─ResNetBlock: 2-6                  [1, 64, 56, 56]           --
│    │    └─Sequential: 3-3              [1, 64, 56, 56]           73,984
│    │    └─Identity: 3-4                [1, 64, 56, 56]           --
│    └─ResNetBlock: 2-7                  [1, 128, 28, 28]          --
│    │    └─Sequential: 3-5              [1, 128, 28, 28]          221,696
│    │    └─Conv2d: 3-6                  [1, 128, 28, 28]          8,192
│    └─ResNetBlock: 2-8                  [1, 128, 28, 28]          --
│    │    └─Sequential: 3-7              [1, 128, 28, 28]          17,792
│    │    └─Identity: 3-8                [1, 128, 28, 28]          --
│    └─ResNetBlock: 2-9                  [1, 256, 14, 14]          --
│    │    └─Sequential: 3-9              [1, 256, 14, 14]          885,760
│    │    └─Conv2d: 3-10                 [1, 256, 14, 14]          32,768
│    └─ResNetBlock: 2-10                 [1, 256, 14, 14]          --
│    │    └─Sequential: 3-11             [1, 256, 14, 14]          70,400
│    │    └─Identity: 3-12               [1, 256, 14, 14]          --
│    └─ResNetBlock: 2-11                 [1, 512, 7, 7]            --
│    │    └─Sequential: 3-13             [1, 512, 7, 7]            3,540,992
│    │    └─Conv2d: 3-14                 [1, 512, 7, 7]            131,072
│    └─ResNetBlock: 2-12                 [1, 512, 7, 7]            --
│    │    └─Sequential: 3-15             [1, 512, 7, 7]            280,064
│    │    └─Identity: 3-16               [1, 512, 7, 7]            --
│    └─AdaptiveAvgPool2d: 2-13           [1, 512, 1, 1]            --
│    └─Flatten: 2-14                     [1, 512]                  --
│    └─Linear: 2-15                      [1, 10]                   5,130
==========================================================================================
Total params: 5,345,098
Trainable params: 5,345,098
Non-trainable params: 0
Total mult-adds (Units.GIGABYTES): 1.08
==========================================================================================
Input size (MB): 0.20
Forward/backward pass size (MB): 36.93
Params size (MB): 21.38
Estimated Total Size (MB): 58.51
==========================================================================================
from torchinfo import summary

model = ResNeXt(num_classes=10, cardinality=16)
summary(model, input_size=(1, 1, 224, 224))
==========================================================================================
Layer (type:depth-idx)                   Output Shape              Param #
==========================================================================================
ResNeXt                                  [1, 10]                   --
├─Sequential: 1-1                        [1, 10]                   --
│    └─Conv2d: 2-1                       [1, 64, 112, 112]         3,136
│    └─BatchNorm2d: 2-2                  [1, 64, 112, 112]         128
│    └─ReLU: 2-3                         [1, 64, 112, 112]         --
│    └─MaxPool2d: 2-4                    [1, 64, 56, 56]           --
│    └─ResNeXtBlock: 2-5                 [1, 64, 56, 56]           --
│    │    └─Sequential: 3-1              [1, 64, 56, 56]           73,984
│    │    └─Identity: 3-2                [1, 64, 56, 56]           --
│    └─ResNeXtBlock: 2-6                 [1, 64, 56, 56]           --
│    │    └─Sequential: 3-3              [1, 64, 56, 56]           73,984
│    │    └─Identity: 3-4                [1, 64, 56, 56]           --
│    └─ResNeXtBlock: 2-7                 [1, 128, 28, 28]          --
│    │    └─Sequential: 3-5              [1, 128, 28, 28]          6,976
│    │    └─Conv2d: 3-6                  [1, 128, 28, 28]          8,192
│    └─ResNeXtBlock: 2-8                 [1, 128, 28, 28]          --
│    │    └─Sequential: 3-7              [1, 128, 28, 28]          9,152
│    │    └─Identity: 3-8                [1, 128, 28, 28]          --
│    └─ResNeXtBlock: 2-9                 [1, 256, 14, 14]          --
│    │    └─Sequential: 3-9              [1, 256, 14, 14]          27,392
│    │    └─Conv2d: 3-10                 [1, 256, 14, 14]          32,768
│    └─ResNeXtBlock: 2-10                [1, 256, 14, 14]          --
│    │    └─Sequential: 3-11             [1, 256, 14, 14]          35,840
│    │    └─Identity: 3-12               [1, 256, 14, 14]          --
│    └─ResNeXtBlock: 2-11                [1, 512, 7, 7]            --
│    │    └─Sequential: 3-13             [1, 512, 7, 7]            108,544
│    │    └─Conv2d: 3-14                 [1, 512, 7, 7]            131,072
│    └─ResNeXtBlock: 2-12                [1, 512, 7, 7]            --
│    │    └─Sequential: 3-15             [1, 512, 7, 7]            141,824
│    │    └─Identity: 3-16               [1, 512, 7, 7]            --
│    └─AdaptiveAvgPool2d: 2-13           [1, 512, 1, 1]            --
│    └─Flatten: 2-14                     [1, 512]                  --
│    └─Linear: 2-15                      [1, 10]                   5,130
==========================================================================================
Total params: 658,122
Trainable params: 658,122
Non-trainable params: 0
Total mult-adds (Units.MEGABYTES): 571.92
==========================================================================================
Input size (MB): 0.20
Forward/backward pass size (MB): 39.04
Params size (MB): 2.63
Estimated Total Size (MB): 41.87
==========================================================================================

4.2 网络参数统计对比

参数对比分析

ResNet 网络参数:

  • 总参数量:5,345,098 个参数(约 535 万)
  • 计算复杂度:1.08 GB 乘加运算
  • 内存占用:总计约 58.51 MB
  • 特点:深层网络但参数控制合理

ResNeXt 网络参数:

  • 总参数量:658,122 个参数(约 66 万)
  • 计算复杂度:571.92 MB 乘加运算
  • 内存占用:总计约 41.87 MB
  • 特点:分组卷积显著减少参数数量

ResNet vs ResNeXt 对比:

  • 参数效率:ResNeXt 参数量仅为 ResNet 的 12.3%
  • 计算量:ResNeXt 计算量约为 ResNet 的 52.8%
  • 内存效率:ResNeXt 内存占用减少约 28.5%
  • 设计哲学:ResNeXt “分而治之” vs ResNet “深度加宽”

5. 模型训练与评估

5.1 训练配置设置

继续使用training_tools.py中的工具训练评估模型:

if __name__ == '__main__':
    import torch
    from torch import optim

    from training_tools import fashionMNIST_loader, Trainer

    BATCH_SIZE = 256
    EPOCHS_NUM = 30
    LEARNING_RATE = 0.01
    USE_RESNEXT = False

    model = ResNeXt(num_classes=10, cardinality=16) if USE_RESNEXT else ResNet(num_classes=10)
    train_loader, test_loader = fashionMNIST_loader(BATCH_SIZE, resize=96)
    criterion = nn.CrossEntropyLoss()
    optimizer = optim.SGD(model.parameters(), LEARNING_RATE)
    platform = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

    with Trainer(model, train_loader, test_loader, criterion, optimizer, platform) as trainer:
        trainer.train(EPOCHS_NUM)

5.2 ResNet 训练结果

查看ResNet完整训练过程
第 001/30 轮,训练损失:0.7456,训练精度:75.69%,测试损失:0.5445,测试精度:81.32%
第 002/30 轮,训练损失:0.4158,训练精度:85.33%,测试损失:0.4383,测试精度:84.61%
第 003/30 轮,训练损失:0.3470,训练精度:87.80%,测试损失:0.3901,测试精度:86.17%
第 004/30 轮,训练损失:0.3044,训练精度:89.31%,测试损失:0.3702,测试精度:87.16%
第 005/30 轮,训练损失:0.2712,训练精度:90.54%,测试损失:0.3472,测试精度:87.71%
第 006/30 轮,训练损失:0.2454,训练精度:91.48%,测试损失:0.3242,测试精度:88.35%
第 007/30 轮,训练损失:0.2219,训练精度:92.31%,测试损失:0.3324,测试精度:88.03%
第 008/30 轮,训练损失:0.2010,训练精度:93.13%,测试损失:0.3449,测试精度:88.19%
第 009/30 轮,训练损失:0.1823,训练精度:93.92%,测试损失:0.3210,测试精度:88.26%
第 010/30 轮,训练损失:0.1638,训练精度:94.73%,测试损失:0.3600,测试精度:87.18%
第 011/30 轮,训练损失:0.1469,训练精度:95.39%,测试损失:0.3198,测试精度:88.87%
第 012/30 轮,训练损失:0.1310,训练精度:95.96%,测试损失:0.3082,测试精度:89.11%
第 013/30 轮,训练损失:0.1142,训练精度:96.72%,测试损失:0.3256,测试精度:88.75%
第 014/30 轮,训练损失:0.1003,训练精度:97.25%,测试损失:0.3263,测试精度:88.85%
第 015/30 轮,训练损失:0.0884,训练精度:97.69%,测试损失:0.3254,测试精度:89.12%
第 016/30 轮,训练损失:0.0743,训练精度:98.27%,测试损失:0.3286,测试精度:89.86%
第 017/30 轮,训练损失:0.0629,训练精度:98.67%,测试损失:0.3608,测试精度:88.71%
第 018/30 轮,训练损失:0.0535,训练精度:98.98%,测试损失:0.3162,测试精度:89.73%
第 019/30 轮,训练损失:0.0440,训练精度:99.36%,测试损失:0.3553,测试精度:89.56%
第 020/30 轮,训练损失:0.0376,训练精度:99.48%,测试损失:0.3171,测试精度:90.01%
第 021/30 轮,训练损失:0.0311,训练精度:99.69%,测试损失:0.3264,测试精度:89.91%
第 022/30 轮,训练损失:0.0249,训练精度:99.81%,测试损失:0.3746,测试精度:88.89%
第 023/30 轮,训练损失:0.0214,训练精度:99.90%,测试损失:0.3387,测试精度:89.95%
第 024/30 轮,训练损失:0.0185,训练精度:99.92%,测试损失:0.3616,测试精度:89.70%
第 025/30 轮,训练损失:0.0155,训练精度:99.96%,测试损失:0.3775,测试精度:89.90%
第 026/30 轮,训练损失:0.0134,训练精度:99.97%,测试损失:0.3476,测试精度:90.39%
第 027/30 轮,训练损失:0.0117,训练精度:99.99%,测试损失:0.3604,测试精度:90.26%
第 028/30 轮,训练损失:0.0103,训练精度:99.99%,测试损失:0.3939,测试精度:89.50%
第 029/30 轮,训练损失:0.0095,训练精度:99.99%,测试损失:0.3723,测试精度:90.18%
第 030/30 轮,训练损失:0.0084,训练精度:100.00%,测试损失:0.3758,测试精度:90.04%

ResNet训练过程可视化

5.3 ResNeXt 训练结果

查看ResNeXt完整训练过程
第 001/30 轮,训练损失:1.1331,训练精度:73.02%,测试损失:0.5520,测试精度:80.88%
第 002/30 轮,训练损失:0.4683,训练精度:83.26%,测试损失:0.5709,测试精度:79.78%
第 003/30 轮,训练损失:0.4076,训练精度:85.35%,测试损失:0.4815,测试精度:83.95%
第 004/30 轮,训练损失:0.3679,训练精度:86.83%,测试损失:0.4586,测试精度:83.86%
第 005/30 轮,训练损失:0.3410,训练精度:87.82%,测试损失:0.4085,测试精度:85.31%
第 006/30 轮,训练损失:0.3200,训练精度:88.52%,测试损失:0.4353,测试精度:84.45%
第 007/30 轮,训练损失:0.3013,训练精度:89.15%,测试损失:0.3780,测试精度:86.73%
第 008/30 轮,训练损失:0.2866,训练精度:89.76%,测试损失:0.3776,测试精度:86.71%
第 009/30 轮,训练损失:0.2721,训练精度:90.30%,测试损失:0.3855,测试精度:86.57%
第 010/30 轮,训练损失:0.2600,训练精度:90.77%,测试损失:0.4883,测试精度:82.86%
第 011/30 轮,训练损失:0.2459,训练精度:91.23%,测试损失:0.3821,测试精度:86.15%
第 012/30 轮,训练损失:0.2366,训练精度:91.65%,测试损失:0.5613,测试精度:82.66%
第 013/30 轮,训练损失:0.2259,训练精度:92.01%,测试损失:0.4315,测试精度:84.59%
第 014/30 轮,训练损失:0.2160,训练精度:92.39%,测试损失:0.3506,测试精度:87.52%
第 015/30 轮,训练损失:0.2065,训练精度:92.66%,测试损失:0.3490,测试精度:87.52%
第 016/30 轮,训练损失:0.1982,训练精度:93.01%,测试损失:0.3982,测试精度:85.88%
第 017/30 轮,训练损失:0.1889,训练精度:93.42%,测试损失:0.3827,测试精度:86.97%
第 018/30 轮,训练损失:0.1802,训练精度:93.83%,测试损失:0.3707,测试精度:87.14%
第 019/30 轮,训练损失:0.1715,训练精度:94.11%,测试损失:0.3806,测试精度:87.30%
第 020/30 轮,训练损失:0.1661,训练精度:94.28%,测试损失:0.4515,测试精度:85.46%
第 021/30 轮,训练损失:0.1573,训练精度:94.65%,测试损失:0.3794,测试精度:87.28%
第 022/30 轮,训练损失:0.1514,训练精度:94.88%,测试损失:0.3444,测试精度:88.06%
第 023/30 轮,训练损失:0.1449,训练精度:95.07%,测试损失:0.4038,测试精度:86.90%
第 024/30 轮,训练损失:0.1397,训练精度:95.25%,测试损失:0.3766,测试精度:87.71%
第 025/30 轮,训练损失:0.1312,训练精度:95.71%,测试损失:0.5073,测试精度:86.13%
第 026/30 轮,训练损失:0.1256,训练精度:95.88%,测试损失:0.4904,测试精度:85.24%
第 027/30 轮,训练损失:0.1181,训练精度:96.25%,测试损失:0.4093,测试精度:86.69%
第 028/30 轮,训练损失:0.1125,训练精度:96.36%,测试损失:0.5151,测试精度:85.10%
第 029/30 轮,训练损失:0.1063,训练精度:96.60%,测试损失:0.3796,测试精度:87.71%
第 030/30 轮,训练损失:0.0987,训练精度:96.85%,测试损失:0.4043,测试精度:87.98%

ResNeXt训练过程可视化

5.4 性能对比分析

性能对比

ResNet 训练表现:

  • 最终训练精度:100.00%
  • 最终测试精度:90.04%
  • 收敛特点:快速收敛,过拟合明显
  • 参数优势:结构简单,参数集中

ResNeXt 训练表现:

  • 最终训练精度:96.85%
  • 最终测试精度:87.98%
  • 收敛特点:较稳定,泛化性更好
  • 参数优势:分组卷积,参数更少

实验结论分析:


参考资料

如果这篇记录对你有帮助,可以留下一句回应。

前往留言

DISCUSSION

讨论与回应

欢迎补充细节、指出问题,或分享与这篇文章有关的经验。

昵称与邮箱为必填项,邮箱仅用于头像和回复通知,不会公开。网址可以留空。

正在准备留言区…

演示赞赏界面

谢谢你愿意支持长期写作。

第一阶段不会发起支付或收集任何信息。接入真实赞赏渠道后,这里会展示清楚的金额、渠道和完成状态。

输入关键词开始搜索 · 按 Esc 关闭
打开完整搜索页 →