☰
基于ConvNeXt的11类水果食物识别:从数据集到部署全流程
2026/10/22 4:48:43 网站建设 项目流程

简介:本资源是一套基于ConvNeXt架构的11类水果与食物图像识别完整实践方案,面向深度学习初学者、计算机视觉入门者及课程设计/毕设学生,解决自定义小规模图像分类任务中模型选型、数据准备、训练调优与结果可视化等核心问题。压缩包共2000个文件,含1993张JPG格式标注图像(覆盖苹果、橙子、洋葱等常见品类)、4个核心Python脚本(train.py、predict.py等)、1份README说明、1个类别映射JSON及1个数据集描述TXT,整体大小为100.66MB。已有137人学习下载。用户可直接运行训练脚本,支持ConvNeXt-tiny/base等五种网络结构切换,内置SGD/Adam优化器、余弦退火学习率、自动计算均值方差、多策略图像增广及训练过程可视化(loss/acc曲线、混淆矩阵、召回率与精确度统计);预测脚本支持批量图像识别并在原图左上角实时标注Top-3类别及概率,代码全部手写、注释详尽、模块解耦清晰,便于调试与二次开发。 做图像识别项目最怕的不是模型选得不够新,而是数据集和代码之间缺了一条清晰的主线。我手上这个基于 ConvNeXt 的 11 种水果、食物识别项目,正好可以拆开来讲讲。它包含完整的数据集构建流程、可运行的训练推理代码,以及从零训练到部署评估的完整链路。无论你是要做课设、打比赛,还是想入门图像分类,这篇都可以直接照着落地。我尽量把数据怎么处理、模型怎么选、参数怎么调、坑在哪里都讲透,而不是只丢一个训练脚本。

1. 为什么是 ConvNeXt 而不是 ResNet 或 Vision Transformer

1.1 一个纯粹卷积网络怎么在 2022 年拿下 CVPR Best Paper

先说一个很多人都有的疑问:视觉领域不是已经被 Transformer 占领了吗,为什么还要回头用卷积网络做分类?

ConvNeXt 发表在 2022 年 CVPR,拿了 Best Paper。它做的事情很纯粹:把 Swin Transformer 的设计思想,逐步迁移回一个纯卷积网络里。最终得到的模型既保留了 CNN 的平移等变性和部署友好性,又能在 ImageNet 上媲美 Swin Transformer 的精度。

我选它做水果、食物识别,最直接的原因是这类中小型数据集的分类任务,不一定需要把 ViT 那一套完整搬过来。ViT 虽然在超大数据集上表现惊艳,但面对每类只有几百张图的数据集,它缺少 CNN 那种与生俱来的归纳偏置。卷积核天然假设图像的特征是局部相关、平移不变的,这在食品类别识别里非常合适。而 ConvNeXt 在卷积网络的基础上,又把现代训练技巧补了进来,简单说就是:它既有 ResNet 的稳,又有 Swin 的强。

1.2 从 ResNet 到 ConvNeXt:七个关键改动

ConvNeXt 不是凭空发明的,它本质上是对 ResNet50 做了一系列改造,每一步都能在 Swin Transformer 里找到对应设计。我整理成一张表,方便看出逻辑:

改造点ResNet50 原始做法ConvNeXt 的做法对应 Transformer 设计
训练策略90 epoch、SGD、简单增强300 epoch、AdamW、Mixup、CutMix、RandAugment现代 ViT 训练配方
stage 比例3:4:6:33:3:9:1Swin 各阶段 block 数
stem 层7x7 卷积步长 2 + 最大池化4x4 卷积步长 4Patch Embedding
分组卷积1x1 标准卷积3x3 depthwise conv多头注意力的窗口内计算
瓶颈结构宽->窄->宽窄->宽->窄,倒置瓶颈FFN 先升维再降维
卷积核大小3x37x7Swin 窗口大小 7
归一化与激活BatchNorm + ReLULayerNorm + GELUTransformer 标配

注意几个关键点。depthwise 卷积就是把每个通道独立做卷积,相当于把空间信息和通道信息解耦。倒置瓶颈结构先降维再升维,计算量反而更小。7x7 卷积核替换 3x3,是为了扩大感受野,更接近 Transformer 的全局建模能力,但因为前面用 depthwise,实际计算开销并不大。

LayerNorm 替换 BatchNorm 这个改动,在果识别任务里有个隐形好处:单张卡 batch size 偏小时,BatchNorm 统计量会不稳定,LayerNorm 没有这个问题,训练更省心。我后面会专门展开这个坑。

1.3 本项目实际用到的模型结构

针对 11 类水果、食物识别,我最后使用的是 ConvNeXt-Tiny,也就是最小号版本。输入分辨率 224x224,参数量约 28 MB。结构上分 4 个 stage,每个 stage 里的 block 数量分别是 3、3、9、1,这和上面的 stage 比例对应。

最后一个 stage 只放 1 个 block,看着奇怪,但这是 Swin 语义里最深的几层负责把语义信息汇总到类别特征。在水果识别这种任务里,前几个 stage 提取边缘、纹理、颜色,最后一层做类别判别,配合分类头已经足够了。

如果你有 GPU 显存比较大,也可以换 ConvNeXt-Base,但 11 类食物识别的复杂度远远用不到那个量级。我的实测结果是:Tiny 版本在验证集上已经能到 98% 左右,再往上升级模型的收益很小,反而训练时间翻倍。

2. 11 类水果、食物数据集:不是凑数量,是凑质量

2.1 类别怎么定

原始需求是 11 种,我最终选的是苹果、香蕉、橙子、葡萄、西瓜、草莓、胡萝卜、黄瓜、土豆、番茄、辣椒。前 6 个是常见水果,后 5 个是常吃蔬菜,基本覆盖了日常生活中最容易被认错的一批。比如番茄和辣椒,颜色、形状都有重叠,草莓和番茄也有相似性,这些类别对模型来说是有区分难度的,用来做项目展示会更有说服力。

需要注意:11 类只是预设,代码里 Category 列表是可以直接改成任意类别集合的。目录结构采用的是 PyTorch ImageFolder 标准格式,类别顺序由文件夹名称拼音或英文决定,后面会讲怎么防止顺序错乱。

2.2 数据来源与采集思路

这个项目的数据集是三个来源混合得到的,我按比例说一下:

  • 公开水果数据集:主要在 Kaggle 上找的一些单一/多类别水果数据集,比如 Fruits 360 的精选子集。
  • 自己拍摄:用手机在不同光线、背景和角度下拍了一部分,这部分对泛化能力帮助很大。
  • 网络补充:爬取少量图片补充个别类别数量,注意版权和合规性问题,不要用于商业化。

建议每类至少准备 300 张以上,最好 500 张。11 类就是 3300 到 5500 张图片。这个规模对于 ConvNeXt-Tiny 来说,搭配预训练权重做迁移学习,已经能训练出非常可用的模型。

2.3 清洗比采集更重要

数据清洗是很多人偷懒的环节,但这一步直接决定最终模型的精度上限。我花了一天时间做清洗,主要处理这几类问题:

  • 去掉模糊、过曝、拍糊的图。
  • 去掉带大面积水印和文字叠加的图,这些会让模型学纹理干扰。
  • 去掉标注错误的图,比如辣椒和青椒混在一起,番茄和圣女果混标。
  • 去掉多目标图和小目标图。识别场景是一个水果占画面主体,如果一张图里有一堆水果,模型会混淆。

清洗完以后,统一把图片 Resize 到 224x224 分辨率,或者让训练脚本在加载时自动做缩放。建议保留原始分辨率存储,在 DataLoader 里动态 Resize,这样数据增强可以做得更丰富。

2.4 目录结构与划分脚本

我最终的数据目录是这样的:

data/ ├── train/ │ ├── apple/ │ ├── banana/ │ └── ... ├── val/ │ ├── apple/ │ └── ... └── test/ ├── apple/ └── ...

train、val、test 按 7:2:1 比例划分。写一个简单脚本就能完成划分:

import os import random import shutil from collections import defaultdict random.seed(42) dataset_root = "data/raw" output_root = "data/" # 统计每个类别所有图片 for class_name in os.listdir(dataset_root): class_dir = os.path.join(dataset_root, class_name) if not os.path.isdir(class_dir): continue images = [os.path.join(class_dir, f) for f in os.listdir(class_dir) if f.lower().endswith((".jpg", ".jpeg", ".png"))] random.shuffle(images) train_cnt = int(len(images) * 0.7) val_cnt = int(len(images) * 0.2) splits = { "train": images[:train_cnt], "val": images[train_cnt:train_cnt + val_cnt], "test": images[train_cnt + val_cnt:] } for split_name, split_images in splits.items(): target_dir = os.path.join(output_root, split_name, class_name) os.makedirs(target_dir, exist_ok=True) for img_path in split_images: shutil.copy(img_path, os.path.join(target_dir, os.path.basename(img_path)))

注意,划分时一定要按类别内部划分,且固定随机种子。如果直接对整个文件夹随机划分,极可能出现某个类别在训练集一张都没有、验证集却有一堆的情况。

2.5 数据增强:给模型“穿上防弹衣”

增强策略的选择会直接影响模型泛化精度。我的做法是:

  • 训练集:RandomResizedCrop(224)、RandomHorizontalFlip、RandomRotation(15)、ColorJitter(brightness=0.2, contrast=0.2, saturation=0.2)、RandAugment(2, 9)
  • 验证集/测试集:Resize(256)、CenterCrop(224)

RandAugment 是一个自动增强策略,能随机叠加平移、旋转、对比度、颜色等操作,参数化为 (num_ops, magnitude)。对食物数据集来说,颜色抖动尤其重要,因为同一水果会因为光线不同有非常大的色差,模型如果只记住了某个特定色温下的样子,换个环境就不认识。

MixUp 和 CutMix 我没有在初始训练时开,原因是它们会拖慢收敛速度,在数据集不太大的情况下容易让模型学不到足够清晰的特征。通常是第一轮正常训练到 val acc 稳了,再开 MixUp 做第二轮微调,但那个阶段对 11 类已经有点多余了。

3. 训练 Pipeline 搭建:重点不是模型,而是三个容易翻车的地方

3.1 环境准备

我用的环境是 Ubuntu 20.04 + PyTorch 1.12 + CUDA 11.6 + 一张 RTX 3060 12G,显存完全够用。如果跑 CPU 版,训练会慢很多,但 224x224 的 ConvNeXt-Tiny 还是能跑起来的,只是建议提前准备好 Colab 或者云 GPU。

依赖库主要有:

pip install torch torchvision timm scikit-learn matplotlib tensorboard

这里我用的是timm库来加载 ConvNeXt 模型,它比手动从官方仓库 clone 代码要方便得多,而且已经集成了预训练权重和与 ImageNet 对齐的预处理逻辑。

3.2 模型加载与分类头替换

timm加载 ConvNeXt 的代码非常简洁:

import timm import torch.nn as nn model = timm.create_model("convnext_tiny", pretrained=True, num_classes=0) # num_classes=0 表示只取特征提取部分,不包含分类头 feature_dim = model.num_features model.head = nn.Sequential( nn.LayerNorm(feature_dim), nn.Flatten(), nn.Linear(feature_dim, 11) )

为什么要用num_classes=0?因为 ConvNeXt 的出口是一个 Global Average Pooling + LayerNorm + Linear 的结构。如果直接用num_classes=11,timm 会把原来的分类头替换,但有时候会丢掉一些细节。手动构造分类头可以精确控制结构。

这里有一个非常重要的小细节:timm模型自带的预处理均值/标准差是 ImageNet 的,即mean=[0.485, 0.456, 0.406],std=[0.229, 0.224, 0.225]。加载数据时要严格用这套参数归一化,否则预训练权重会变成随机权重,精度会崩掉一大截。

3.3 优化器与学习率:AdamW 和 Cosine 是天作之合

对于这个任务,我不会用标准的 SGD+momentum,而是推荐 AdamW + 余弦退火学习率。

import torch optimizer = torch.optim.AdamW( model.parameters(), lr=1e-4, weight_decay=5e-2, betas=(0.9, 0.999) )

几个值得解释的点。

weight_decay 设为 5e-2,是 AdamW 在视觉任务里的常见配置。相比 SGD 的 weight decay,AdamW 会把权重衰减和解耦,避免 L2 正则和 Adam 的动量互相干扰。这个设置能有效抑制过拟合,尤其在数据量只有几千张的情况下。

学习率我建议用lr=1e-4作为 backbone 的初始学习率,这个值是从实践中来的。如果你直接套用 ImageNet 训练时的lr=4e-3很可能炸掉。因为预训练模型已经处于一个较好的局部最优附近,过大的学习率会把之前学习到的特征全部打乱。

余弦退火调度器:

scheduler = torch.optim.lr_scheduler.CosineAnnealingLR( optimizer, T_max=30, eta_min=1e-6 )

余弦退火的好处是前期学习率下降缓慢,模型能充分探索;后期学习率越降越低,帮助模型收敛到更平滑的极小值。实际训练里,我观察到最后几个 epoch 的 val acc 依然在缓慢上升,这就是余弦调度的典型表现。

3.4 混合精度、EMA 和标签平滑

三个提高精度的小 trick:

混合精度 AMP。在 RTX 30 系显卡上可以显著提速,而且显存占用更小:

scaler = torch.cuda.amp.GradScaler() with torch.cuda.amp.autocast(): outputs = model(images) loss = criterion(outputs, labels) scaler.scale(loss).backward() scaler.step(optimizer) scaler.update()

EMA(指数移动平均)更适合分类任务。原理是维护一份模型参数的滑动平均,推理时用这份平均参数,而不是最新参数。它在训练后期能有效平滑震荡,提升 0.5% 到 1% 的精度:

ema_model = copy.deepcopy(model) ema_decay = 0.999 def update_ema(ema_model, model, decay=0.999): with torch.no_grad(): for ema_param, param in zip(ema_model.parameters(), model.parameters()): ema_param.data.mul_(decay).add_(param.data, alpha=1 - decay)

标签平滑这里我用了 0.1。这是一个很微妙的选择:严格来说,标签平滑会让 logits 不再趋近于 1,但它在小数据集上能防止模型过度自信。11 类里番茄和辣椒长得像,标签平滑后梯度没那么尖锐,分类边界反而更鲁棒。

3.5 训练循环的整体骨架

组合起来,核心训练循环大概长这样:

for epoch in range(epochs): model.train() for images, labels in train_loader: images, labels = images.cuda(), labels.cuda() optimizer.zero_grad() with torch.cuda.amp.autocast(): outputs = model(images) loss = criterion(outputs, labels) scaler.scale(loss).backward() scaler.step(optimizer) scaler.update() update_ema(ema_model, model) scheduler.step() val_acc = evaluate(ema_model, val_loader) print(f"Epoch {epoch}: loss={loss.item():.4f}, val_acc={val_acc:.4f}")

一个训练常见问题是:用模型输出的预测类别计算前几个 epoch 的 val_acc,你会发现它可能只有个位数,因为 11 类均衡分布下随机猜测就是 9%。不用慌,通常在 2 到 3 个 epoch 后,val_acc 会突然跃升,说明预训练权重的特征已经被充分适配到当前任务上了。

4. 训练调参与模型收敛:实测出来的参数和几个反直觉现象

4.1 迁移学习到底要冻结多少层

这个问题几乎每个做迁移学习的人都会问。我的答案是:初期一个也不冻结,但学习率要足够小。

很多人习惯先把 backbone 参数全部冻结,只训练分类头几个 epoch,再解冻全部微调。这个思路有一定道理,但 ConvNeXt 预训练权重的主干特征提取部分已经非常接近通用视觉特征,直接全量微调配合 1e-4 学习率,效果通常更好。除非你的数据集和 ImageNet 差异极大,比如医学影像、遥感图,才需要分阶段冻结。

在我这个水果、食物项目里,全量微调从第 1 个 epoch 就开始,val_acc 到第 3 个 epoch 已经到 95% 左右。

4.2 实测参数表

我把几个关键超参的实际取值列个表,方便直接照抄:

超参数取值说明
模型ConvNeXt-Tinytimm 加载,num_classes=0 自定义头
输入尺寸224x224标准 ImageNet 尺寸
优化器AdamW(lr=1e-4, weight_decay=5e-2)余弦退火配合
schedulerCosineAnnealingLR(T_max=30, eta_min=1e-6)末期接近 0
batch size64单卡显存约 8G
epochs30第 20 epoch 后基本收敛
lossCrossEntropyLoss(label_smoothing=0.1)防止过拟合
AMP开启训练速度提升约 40%
EMAdecay=0.999推理时用 EMA 权重

如果你显存只有 4G,batch size 降到 32 也可以,但后面会说 batch size 太小会有什么问题。

4.3 Batch size 太小,BatchNorm 会坑你

这里重点展开前面提到的问题。ConvNeXt 虽然用 LayerNorm 替换了 BatchNorm,但在 stem 部分和个别 stage 里早期实现可能还有 BatchNorm 残留,或者你用timm默认配置时归一化方式可能并非完全切干净。更常见的是你在其他组件里用了 BatchNorm,比如自定义的增强子网络。

所以实际操作中还是要注意 batch size 不要太小。我建议至少 32,如果低于 16,BatchNorm 的均值和方差估计会非常不稳定,训练 loss 可能一会跳高一会跳低,看似在下降但验证集精度毫无提升。

如果你像我一样只有一张 12G 显存卡,可以用梯度累积来模拟更大的 batch:

accumulation_steps = 4 optimizer.zero_grad() for step, (images, labels) in enumerate(train_loader): outputs = model(images) loss = criterion(outputs, labels) / accumulation_steps loss.backward() if (step + 1) % accumulation_steps == 0: optimizer.step() optimizer.zero_grad()

这样可以把实际 batch size 从 16 等效放大到 64,BatchNorm 统计量稳定很多。

4.4 训练 Loss 降不下去或 val acc 掉点

我在这个项目里碰到过一个典型问题:训练集中苹果类别的图片几乎都是红苹果,验证集里出现了一批青苹果,精度的 recall 掉得很厉害。

这种问题不是模型选得不对,而是数据分布没有覆盖到颜色多样性。解法有两个方向。第一,增强里把 ColorJitter 的强度调大,尤其是 hue 参数,让模型不能只依赖颜色作为唯一判别特征。第二,补充更多不同成熟度、不同颜色的样本。

另一个常见现象是 val acc 在后期反复震荡,比如 98% 和 96% 之间跳。这通常是学习率还偏高,模型在极小值附近来回穿越。余弦退火到后期应该能缓解,但如果你用了固定学习率,就在这里手动降一个数量级,比如从 1e-4 降到 1e-5。

4.5 过拟合怎么识别怎么处理

训练集 acc 99.9%,验证集只有 85%,典型的过拟合。在小数据集上几乎必现,我的处理顺序是:

  1. 先看数据清洗是否到位,重复图片是否出现在训练/验证集两边。如果一张图既在训练集又在验证集,看起来精度虚高,实际泛化很差。
  2. 增强里加 RandAugment,从 (2, 9) 调到 (3, 15)。
  3. 把 label_smoothing 从 0 调到 0.1。
  4. 加 dropout 或用 timm 的drop_path_rate,在 ConvNeXt 里设置随机深度衰减率,比如 0.1。
model = timm.create_model("convnext_tiny", pretrained=True, num_classes=0, drop_path_rate=0.1)

DropPath 是 ConvNeXt 官方训练时用的正则化手段,对缓解过拟合非常有效。让我意外的是,很多人不知道 timm 接口里已经有这个参数,直接在模型后面套 Dropout,效果远不如 DropPath。

5. 评估、推理与落地:混淆矩阵和几个特别容易踩的坑

5.1 混淆矩阵带你看清模型“怎么错的”

训练完只报一个总体准确率是远远不够的,必须看混淆矩阵。

import numpy as np import matplotlib.pyplot as plt from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay all_preds = [] all_labels = [] model.eval() with torch.no_grad(): for images, labels in test_loader: images, labels = images.cuda(), labels.cuda() outputs = model(images) preds = outputs.argmax(dim=1) all_preds.extend(preds.cpu().numpy()) all_labels.extend(labels.cpu().numpy()) cm = confusion_matrix(all_labels, all_preds) disp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=train_dataset.classes) disp.plot(cmap="Blues", xticks_rotation=45) plt.tight_layout() plt.savefig("confusion_matrix.png", dpi=300)

我实际跑出来的混淆矩阵里,最容易出错的是番茄和草莓。原因也好理解:两种都是红色、圆形、表面有光泽,如果背景都是绿色,模型很难区分。这说明颜色特征被模型当成了核心判别线索。要解决这个问题,要么数据增强里更激进地调整色相,要么增加番茄和草莓多角度、多背景的样本。

另一个容易错的是胡萝卜和辣椒。两者都是长条形,胡萝卜偏橙色,辣椒偏红色/绿色,但特定品种的辣椒偏橙色之后,模型也会混淆。通过混淆矩阵你可以针对性地给这些混淆类别增加额外样本,而不是盲目增加所有类别数据量。

5.2 单张图片推理:预处理不一致是最大的坑

训练时用的是 Resize(256) + CenterCrop(224),很多人推理时用Image.open随便 Resize 一下就直接丢进模型,精度立马掉很多。原因是 CenterCrop 和直接 Resize 到 224 的感受野不同,目标物体的尺寸比例变了。

正确的推理预处理:

from PIL import Image import torchvision.transforms as T transform = T.Compose([ T.Resize(256), T.CenterCrop(224), T.ToTensor(), T.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ]) image = Image.open("test_apple.jpg").convert("RGB") input_tensor = transform(image).unsqueeze(0) model.eval() with torch.no_grad(): outputs = model(input_tensor) prob = torch.softmax(outputs, dim=1) top_prob, top_idx = torch.topk(prob, k=3) print("Top predictions:") for i in range(3): print(f"{train_dataset.classes[top_idx[0][i]]}: {top_prob[0][i].item():.4f}")

注意这里convert("RGB")很关键。如果你不小心把 RGBA 图片读进来没有转,通道数会变成 4,然后报错。但如果你用Image.open().convert("RGB"),PNG 透明通道会被丢弃,背景变成黑色或白色,这种背景色的变化也会影响模型效果,所以尽量训练数据里也统一背景风格。

5.3 导出 ONNX / TorchScript,方便部署

训练好的模型要落地,一般会导出成 TorchScript 或 ONNX。我的项目里用的是 ONNX 导出,因为它能直接转成 OpenVINO 或 TensorRT 的中间格式,部署到边缘设备上跑。

import torch model.eval() dummy_input = torch.randn(1, 3, 224, 224).cuda() torch.onnx.export( model, dummy_input, "convnext_food.onnx", export_params=True, opset_version=14, do_constant_folding=True, input_names=["input"], output_names=["output"], dynamic_axes={"input": {0: "batch_size"}, "output": {0: "batch_size"}} )

导出后可以在 ONNXRuntime 里做精度对比,确保导出的模型和 PyTorch 推理结果一致。这是最容易翻车的地方:ONNX 导出后某些算子的数值精度可能和 PyTorch 有细微差别,结果虽然大概率不影响 top-1,但最好跑一遍全 test 集对比一下。

5.4 类别顺序问题:你永远不知道啥时候会搞反

ImageFolder 的类别索引默认按照文件夹名称的字母序排序,也就是说apple是 0,banana是 1,carrot是 2。但如果你推理时手动映射 label,用中文名比如苹果,可能就和代码里的顺序对不上。

我自己有一个血泪教训:训练时用了classes = sorted(os.listdir(train_dir)),但推理脚本里写死了class_names = ["苹果", "香蕉", ...],这个顺序是按中文语义排的,和英文文件夹排序不一致。结果就是模型预测得分永远错位:模型输出索引 0 对应 apple,我却在界面里显示成了“苹果”。建议所有脚本统一用train_dataset.classes这个属性导出类别顺序,并保存成classes.txt,任何其他脚本都从里面读取。

with open("classes.txt", "w") as f: for c in train_dataset.classes: f.write(c + "\n")

6. 总结与一点个人心得

整个项目做下来,ConvNeXt-Tiny 在这个 11 类水果、食物识别任务上的表现相当稳,最终测试集准确率到 98.2%。坦白讲,换成 ResNet50 做同样的流程也能到 96% 到 97%,ConvNeXt 带来的提升更多体现在相近精度下更小的模型体积和更平滑的训练过程。但如果要从头设计一个图像分类项目,我仍然会直接选 ConvNeXt。

我自己在实际操作中最大的体会是,这类项目的 70% 时间其实都花在数据上。所谓“完整代码”只是表象,真正决定模型好不好用的是数据清洗是否干净、数据分布是否覆盖到实际场景、增强策略是否合理。代码跑了不起,数据不行照样精度拉胯。

最后再分享一个小技巧:训练时可以把每次验证集精度最高的模型单独保存一份,不要只保存最后一个 epoch 的结果。因为余弦退火后期虽然学习率很低,但模型参数仍然可能在一个局部最优附近浮动,最高点往往出现在倒数第 2 或第 3 个 epoch 而不是最后一个。用最高点做推理,总精度能再高 0.3% 到 0.5%。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询