资讯动态

ConvNeXt植物幼苗分类实战:数据增强、分层学习率与TTA调优

发布时间:2026/10/26 21:08:58 来源:尧图企业网站定制
植物幼苗分类这个题目我在农业视觉的项目里反复碰到过田里刚冒头的小苗叶片还没完全展开作物和杂草挤在一垄里颜色、形状、纹理都高度相似人眼在强光或者湿泥背景下都容易看错更别说让模型去分。ConvNeXt 是我在这类细粒度、小样本任务上试下来比较顺手的一个骨干网络纯卷积结构、部署简单、预训练权重好找、对小数据集不像纯 Transformer 那样挑食。这篇就把我用 ConvNeXt 做植物幼苗分类的整套流程摊开讲数据怎么清、划分怎么防泄漏、增强开到什么程度、学习率和层衰减怎么配、训练完怎么用 TTA 把最后几个点抠出来以及我在验证集上翻车之后是怎么一项项排查回去的。不管你是刚接触迁移学习、想找个能跑通的完整流程还是已经在做农业图像分类、想看看别人的参数是怎么定的下面这些内容都可以直接拿去改改就用。1. 项目整体设计与技术选型思路1.1 先把任务本质说清楚幼苗分类到底难在哪很多人一上来就调模型结果卡在验证集不动其实问题根本不在网络。幼苗分类的难点有四层得先认清楚。第一层是细粒度十字花科的几个近缘种在子叶期长得几乎一样差别可能只在叶片边缘的锯齿深浅、叶面是否有细毛、茎基部是否泛紫这些信息在 224 分辨率下可能就剩十几个像素。第二层是样本少这类数据集通常是每类几十张到一百多张整体规模在千张量级用百万级参数量的模型去拟合过拟合是必然的不是意外。第三层是域偏移同一个物种晴天拍、阴天拍、湿土拍、干土拍颜色分布能差出一大截模型很容易学到背景是黑土还是砂土而不是叶子长什么样。第四层是长尾不同类别的样本数能差两三倍直接用普通交叉熵训练模型会倾向于把少数类往多数类上靠。我一开始没意识到第四层的杀伤力只看总体准确率结果 78% 的准确率看着还行一拉混淆矩阵才发现有两个类别召回率只有 40% 出头全被吞进了一个样本量最大的类。所以这个项目从一开始就要把评估指标定成macro F1而不是 accuracy这不是洁癖是防止自己被平均数骗了。1.2 为什么选 ConvNeXt而不是 ResNet 或纯 ViT我拿三个方向都试过说说取舍逻辑。ResNet50 是最稳的基线训起来不挑参数但它的问题是感受野和特征表达上限摆在那细粒度任务上想再往上抬很难我这边卡在一个平台上怎么调增强都没突破。纯 ViT 方向理论上全局注意力对叶形这种整体结构敏感但它对数据量的胃口太大千张级别的数据即使挂 ImageNet 预训练微调时也很容易出现注意力图乱飘、需要极小的学习率和很长的 warmup调参成本高。ConvNeXt 的定位正好在中间它把 Transformer 训练配方里那些真正有用的东西大感受野、LayerNorm、GELU、Layer Scale、更patch 化的下采样搬进了一个纯卷积骨架里。对我这个任务来说有三个实际好处。一是感受野7×7 的深度可分离卷积堆起来第三个 stage 的有效感受野能覆盖整片叶子判断锯齿、叶脉走向这类中尺度结构时信息是完整的。二是归一化方式LayerNorm 不依赖 batch 统计量我本地显卡只有一张batch 只能开到 32 甚至 16用 BatchNorm 的模型在小 batch 下统计量噪声大ConvNeXt 就没这个问题。三是部署纯卷积在推理端的算子支持最成熟后面要往边缘设备上挪的时候省事。代价也要说清楚ConvNeXt-T 大约 2800 万参数比 ResNet50 重千张数据直接全量微调必然过拟合所以必须配套强增强、随机深度和分层学习率。如果显存或者数据量更紧张直接退到convnext_femto或convnext_pico这类小号版本效果未必差多少。1.3 整体方案的骨架和几个关键取舍整套流程我拆成五段数据清洗与分组划分 → 强增强的预处理流水线 → ConvNeXt 骨干 分层学习率微调 → EMA 权重 余弦退火 → 多尺度 TTA 与多折集成。中间有两个取舍值得单独讲。第一个是要不要冻结骨干。我的做法是前 2 个 epoch 冻结 stem 和 stage1只训后面的 stage 和分类头之后全部解冻。理由是 stem 是 4×4 stride 4 的大步长卷积它学的是最底层的颜色和边缘统计这部分在 ImageNet 上学到的东西对植物图像同样成立没必要在训练初期用随机梯度把它冲掉。实测下来这一步能让前期 loss 曲线平滑不少。第二个是输入分辨率。数据集原图普遍在 300 到 500 像素之间短边缩放到 256 再中心裁 224 是标准做法。但幼苗分类的判别信息经常在细节上我后来把训练分辨率提到 288、推理用 320macro F1 有可感知的提升代价是显存涨了约 1.6 倍、单 epoch 时间长了 40%。如果你的卡够用我建议直接从 288 起步。2. ConvNeXt 核心结构拆解改动到底发生在哪2.1 从 ResNet 到 ConvNeXt 的四组现代化改造ConvNeXt 的论文思路很直白拿一个 ResNet50 当起点然后把 Transformer 那边被验证有效的东西一项项搬过来每搬一项测一次精度最后得到一个卷积外壳 Transformer 内脏的结构。拆开看是四组改动。宏观结构上它把 ResNet 的 stem7×7 stride 2 卷积 maxpool换成了 4×4 stride 4 的卷积这一步等价于 ViT 的 patchify把输入直接下采样 4 倍后面的计算量大幅下降。四个 stage 的 block 数量比例也从 ResNet 的 3:4:6:3 改成了 3:3:9:3通道数 96:192:384:768把算力往第三个 stage 集中。深度可分离卷积替换了普通 3×3 卷积这是省参数的关键。倒残差结构inverted bottleneck来自 MobileNetV2先 1×1 升维 4 倍做深度卷积再 1×1 降维回原通道形成窄-宽-窄的沙漏。大卷积核是最显眼的一步深度卷积从 3×3 一路试到 7×77×7 精度最好且计算量增加有限最终定在 7×7。微观层面还有四个小改动激活函数从 ReLU 换成 GELU每个 block 里只保留一个激活放在升维和深度卷积之间而 ResNet 的 bottleneck 有两个归一化从 BatchNorm 换成 LayerNorm并且挪到每个卷积之前残差分支输出加一个 Layer Scale初始值 1e-6让训练初期整个 block 近似恒等映射深层网络更好训。一个 ConvNeXt block 的前向顺序是7×7 深度卷积 → LayerNorm → 1×1 升维 4 倍 → GELU → 1×1 降维 → Layer Scale 缩放 → 加残差。你写代码的时候不需要自己搭但理解这个顺序对排查问题很有用比如后面我遇到 loss 突然炸掉就是因为 Layer Scale 被我在自定义模型里漏掉了。2.2 7×7 大卷积核为什么又大又便宜这是很多人第一次看 ConvNeXt 时的疑惑卷积核从 3 变成 7计算量不是涨了 5 倍多吗答案在于它是深度卷积每个通道独立卷积不跨通道做乘加。拿 stage1 举例特征图通道 96空间尺寸 H×W。一个 7×7 深度卷积的乘加次数约等于 49 × 96 × H × W。而同一个 block 里那个 1×1 升维卷积是 96 × 384 × H × W也就是 36864 × H × W比大核卷积高出将近 8 倍。所以在这个结构里把核从 3 加到 7只让深度卷积那一项从 9 涨到 49占整个 block 的比例依然很小但感受野从中等直接扩大到能覆盖叶片的主要结构。我做过一个粗略的对比实验把convnext_tiny的 7×7 换成 3×3参数量几乎不变但在幼苗数据上 macro F1 掉了将近 2 个点尤其是那两个十字花科近缘类混淆更严重。这从侧面说明大核带来的上下文对细粒度判别是真有用的。代价主要是显存和访存大核深度卷积在 GPU 上的实现效率不如小核实际吞吐会比 FLOPs 算出来的低一些这一点在做部署预算时要注意。2.3 训练配方比结构更关键别只盯着网络ConvNeXt 论文里反复强调的一点是结构改造的收益有一大半来自配套的训练配方。我完全同意这个项目里的调参经验也印证了这点。配方里最值得抄的几项是训练技巧推荐值在幼苗任务上的作用AdamW 权重衰减wd 0.05比 SGD 收敛快配合 LN 更稳余弦退火含 3~5 epoch warmup小数据上避免前期震荡标签平滑0.1缓解近缘类的过度自信随机深度 drop_path0.1~0.2千张数据必开防过拟合主力Mixup / CutMix0.2 / 0.5微调阶段减半提升校准和鲁棒性EMA 权重decay 0.9998验证集指标稳定提升层衰减 layer decay0.75~0.85保护预训练底层特征其中层衰减是我强烈建议加上的一项它的意思是越靠近输入的层学习率越小按lr × decay^k逐层递减。ConvNeXt 的底层特征通用性强不该被小数据集的高梯度冲掉上层语义特征才需要大幅调整。没加层衰减和加了层衰减我在同一个配置下看到的差别是验证曲线在后半程更平稳不容易在第 30 个 epoch 之后开始回弹。3. 数据准备与预处理实操3.1 数据清洗先保证标签是对的拿到数据别急着训模型先花半天做三件事。第一是查重田间拍摄经常同一株苗连拍好几张这些图如果分散到训练和验证集验证指标会虚高十几个点。我用感知哈希pHash做近重复检测汉明距离小于 5 的归为一组只保留组内一张进验证集之前先剔除冗余。第二是核对类别体系幼苗数据集最容易出问题的地方是两个近缘种被人工标错或者同一物种在不同批次被抓成了两个类。我一般会把每个类的样本抽 20 张拼成一张联系图扫一遍肉眼能发现大部分异常。第三是剔除废图重度失焦、叶子被遮挡超过一半、只有土壤没有苗的直接移走这类样本对训练只有负面作用。这一步做完通常能砍掉 5% 到 10% 的样本看着心疼但验证集的可信度会完全不同。我之前有次没做去重训练准确率冲到 99%验证一直卡在 65%查了两天才发现是同株连拍泄漏白折腾。3.2 划分策略别让同一株苗同时出现在两侧标准做法是分层采样stratified split保证每类在两个集合里的比例一致但对这个任务不够因为同一拍摄批次的图像之间有强相关同一盘苗、同一天、同一光照条件。如果按图片随机划分验证集里的背景分布和训练集几乎一样测出来的数字偏乐观一换到新批次的图上就崩。我的做法是给每张图打一个批次标签产地托盘编号或者文件名前缀里能提取出来按批次做分组划分同一批次整体进训练集或验证集。数据量够的时候做 5 折分组交叉验证每折的验证集来自不同批次这样得到的分数更接近真实泛化。如果批次信息拿不到退一步用感知哈希聚类的结果当分组依据效果也还可以。划分比例建议 8:2 起步数据到两千张以上再考虑 7:1.5:1.5 的三段划分。随机种子固定下来写进配置里别每次跑都不一样否则你没法判断改进来自代码还是来自运气。3.3 预处理的几个参数为什么这么定预处理流水线不长但每个参数都有理由。尺寸短边 resize 到 256中心裁 224训练时用 RandomResizedCrop 替代中心裁剪。缩放用双线性插值不要用最近邻幼苗的细锯齿在最近邻下会产生锯齿伪影。归一化直接用 ImageNet 的均值方差[0.485, 0.456, 0.406] / [0.229, 0.224, 0.225]因为骨干是在这个分布上预训练的改了反而要重新适应。有一个我特意没做的操作直方图均衡化和白平衡校正。很多农业图像的教程会建议做色彩归一化理由是对抗光照变化但幼苗分类里颜色本身是判别信息比如某些物种的茎基部偏紫、子叶下表面偏灰绿这些是重要线索。归一化把颜色分布拉平的同时也把线索抹掉了。我的替代方案是训练时用温和的颜色抖动做数据增强让模型学会忽略光照差异但保留物种固有的色调。还有一个细节填充方式。如果做旋转增强边缘会引入黑边用reflect或constant填充配合背景色别留纯黑边模型会学到有黑边的图就是某个类这种无意义线索。3.4 数据增强组合开到什么程度算合适千张数据训 2800 万参数模型增强就是生命线。我的组合是这样的基于 torchvision.transforms.v2用 albumentations 也一样的思路RandomResizedCrop(224, scale(0.6, 1.0), ratio(0.85, 1.15))缩放范围不要压到 0.3幼苗主体本来就小再裁就只剩背景了。RandomHorizontalFlip(p0.5)和RandomVerticalFlip(p0.2)植物图像上下翻转一般不会改变语义可以开但概率别太高。RandomRotation(degrees20)拍摄角度本身有随机性小幅旋转合理超过 30 度会引入大量黑边。ColorJitter(brightness0.3, contrast0.3, saturation0.2, hue0.05)hue 一定要小0.05 到 0.1 之间再大就把物种固有色调没了。RandAugment(num_ops2, magnitude7)有用但要留意它自带的 shear 和 posterize 对这类图像收益不大可以从策略里剔掉。RandomErasing(p0.25, scale(0.02, 0.15))模拟叶片被遮挡配合 CutMix 一起用。Mixup(alpha0.2)或CutMix(alpha0.5)二选一微调阶段把 alpha 减半太强会让收敛变慢。注意事项增强强度和训练轮数是绑定的。增强开得猛就需要更多 epoch 才能收敛我第一次把 RandAugment magnitude 提到 9 配 30 个 epoch结果模型根本没学完验证集一直在爬坡。这种情况要么降强度要么加 epoch别急着换模型。4. 训练流程与关键代码实现4.1 环境准备与显存预算环境不复杂PyTorch 2.x、timm 0.9 以上、CUDA 对应版本。timm 里直接有现成的 ConvNeXt 实现和预训练权重别自己抄结构容易漏 Layer Scale 这种细节。显存预算我实测的量级是这样convnext_tiny、输入 224、batch 32、混合精度打开大约占 6 到 7 GB输入提到 288、batch 32涨到 10 GB 左右如果只开 batch 16224 输入下 4 GB 足够。一张 12 GB 的卡跑 288 输入加 batch 32 是比较舒服的配置。batch 再小也可以训LayerNorm 不依赖 batch 统计量这是选 ConvNeXt 的一个隐性优势。pip install torch torchvision --index-url https://download.pytorch.org/whl/cu121 pip install timm0.9.16 pip install albumentations opencv-python-headless scikit-learn4.2 模型构建一行创建重点在参数import timm import torch.nn as nn NUM_CLASSES 12 # 换成你实际的类别数 model timm.create_model( convnext_tiny, pretrainedTrue, num_classesNUM_CLASSES, drop_path_rate0.15, # 随机深度千张数据必开 drop_rate0.1, # 分类头前的 dropout ) # 分类头单独拆出来方便给它更大的学习率 head model.get_classifier() in_features head.in_features model.reset_classifier(0) # 先去掉原分类头 model.head nn.Sequential( nn.LayerNorm(in_features), nn.Dropout(0.3), nn.Linear(in_features, NUM_CLASSES), )三个参数的选择理由drop_path_rate设 0.15太低了没正则效果太高了收敛慢0.1 到 0.2 之间都可以试分类头加一层 LayerNorm 是因为reset_classifier之后原始归一化被去掉加回来训练更稳分类头的 dropout 给到 0.3比主干里的 0.1 大因为这里是最后一道防线。如果显存吃紧或者数据更少把模型换成convnext_femto约 5M 参数或convnext_pico配合冻结前两个 stage小数据上经常比大模型表现更好。4.3 优化器、学习率与分层衰减优化器用 AdamW主干和分类头分开配置def build_optimizer(model, base_lr5e-4, head_lr2e-3, weight_decay0.05): no_decay (bias, norm, bn, ln) decay_params, no_decay_params [], [] for name, p in model.named_parameters(): if not p.requires_grad: continue (no_decay_params if any(k in name.lower() for k in no_decay) else decay_params).append(p) return torch.optim.AdamW( [ {params: decay_params, lr: base_lr, weight_decay: weight_decay}, {params: no_decay_params, lr: base_lr, weight_decay: 0.0}, ], betas(0.9, 0.999), )学习率的取值逻辑预训练模型微调主干学习率一般取预训练阶段的 1/10 到 1/20ConvNeXt 原论文用 4e-3 从头训 300 epoch我们微调 50 个 epoch主干给 5e-4 比较合适分类头是随机初始化的需要更大步长给 2e-3 到 4 倍主干的量级。权重衰减要排除所有归一化层和偏置这个细节漏了会导致 LN 的参数被往零拉。层衰减的实现方式是给不同 stage 分配不同学习率def layer_decay_param_groups(model, base_lr5e-4, decay0.8): groups [] num_layers 4 # stem 4 个 stage按从深到浅排列 for idx, (name, module) in enumerate(model.named_children()): lr base_lr * (decay ** (num_layers - idx)) params [p for p in module.parameters() if p.requires_grad] if params: groups.append({params: params, lr: lr}) return groups层衰减系数 0.75 到 0.85 之间我一般取 0.8。这套写法比按参数名正则匹配更直观model.named_children()的顺序就是从 stem 到 head。调度器用带 warmup 的余弦退火warmup 占总 epoch 的 10% 左右50 epoch 对应 5 个 warmup epoch注意 warmup 要从很小的值起步比如 1e-6不是从学习率的一半起。4.4 训练循环混合精度、梯度裁剪和 EMAimport torch from torch.cuda.amp import autocast, GradScaler from copy import deepcopy scaler GradScaler() ema_model deepcopy(model).eval() for p in ema_model.parameters(): p.requires_grad_(False) EMD_DECAY 0.9998 def update_ema(ema_model, model, decay): with torch.no_grad(): for ep, mp in zip(ema_model.parameters(), model.parameters()): mp.mul_(decay).add_(ep.detach(), alpha1 - decay) for eb, mb in zip(ema_model.buffers(), model.buffers()): mb.copy_(eb) for epoch in range(EPOCHS): model.train() for images, targets in train_loader: images images.cuda(non_blockingTrue) targets targets.cuda(non_blockingTrue) with autocast(dtypetorch.float16): outputs model(images) loss criterion(outputs, targets) scaler.scale(loss).backward() scaler.unscale_(optimizer) torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm5.0) scaler.step(optimizer) scaler.update() optimizer.zero_grad(set_to_noneTrue) update_ema(ema_model, model, EMD_DECAY) scheduler.step() # 每轮用 EMA 权重算一次验证集 macro F1 val_f1 evaluate(ema_model, val_loader)几个关键数字梯度裁剪阈值取 5.0ConvNeXt 配合大学习率时偶发梯度尖峰裁到 1.0 会拖慢收敛5.0 足够兜底。EMA 衰减 0.9998对应大约 5000 步的有效窗口50 epoch × 每 epoch 30 步已经够用如果训练步数很少把 decay 调到 0.999否则 EMA 权重跟不动。验证时用 EMA 模型而不是原始模型这一步在小数据上往往能直接带来 1 到 2 个点的提升。损失函数用标准的交叉熵加标签平滑criterion nn.CrossEntropyLoss(label_smoothing0.1)类别不均衡的处理我的优先级是先试分层采样WeightedRandomSampler采样权重取类别频次的倒数平方根这比改损失函数更直接效果不够再上类别权重CrossEntropyLoss(weight...)权重取倒数但做归一化别直接取 1/n会出现极端值把梯度带偏Focal Loss 我放在最后考虑它在这类任务里经常把简单样本压得太狠收敛变慢。4.5 推理与 TTA最后两三个点从这里捞推理阶段先确认一件事要不要做多尺度。训练用 288推理时试 256 / 288 / 320 三个尺度把 softmax 输出平均。我这边 320 单尺度和三尺度平均差不太多但三尺度平均对少数类的召回更友好。TTA 的组合我通常用原图 水平翻转 两个角裁剪一共 4 到 6 个视角概率平均。注意别加垂直翻转 TTA训练时虽然开了垂直翻转增强但测试时垂直翻转在部分类别上会引入偏差比如某些叶片的上表面形状和下表面不同实测反而掉点。torch.no_grad() def predict_tta(model, images): probs torch.softmax(model(images), dim1) probs torch.softmax(model(torch.flip(images, dims[3])), dim1) probs torch.softmax(model(center_crop(images, 0.88)), dim1) return probs / 3最后是多折集成。5 折分组交叉验证训出来的 5 个模型每个都是 EMA 权重做软投票macro F1 一般比单折高 2 到 3 个点代价是推理成本翻 5 倍。如果只做线上服务且对延迟敏感选单折里验证最好的那个或者用权重融合把 5 个模型的参数平均后者推理成本不变效果比单模型略好。5. 常见问题与排查实录5.1 训练准确率 99%验证卡在 60% 上不去这是这个任务里出现频率最高的问题本质是过拟合但成因有好几种得逐项排除。第一步查数据泄漏按我上面说的感知哈希去重和批次分组重做一次划分这一步能解决超过一半的诡异过拟合。第二步看增强强度把 drop_path 从 0.1 提到 0.2RandAugment 的 magnitude 提到 9Mixup 开起来如果验证集立刻有反应说明之前正则不够。第三步缩模型从 tiny 换到 femto或者冻结 stage1 和 stage2只训后两个 stage 加分类头小数据上这招很有效。还有一个容易被忽略的原因学习率太大把预训练特征冲掉了。判断方法是看训练 loss 前 2 个 epoch 是不是掉得太快如果第 1 个 epoch 就从 2.5 掉到 0.3基本可以确定是学习率过高把主干学习率除以 5 再试。我自己在这个坑里待过挺久一直以为是过拟合其实是骨干被冲坏之后又重新学了一遍而样本量根本支撑不起重新学。5.2 混淆矩阵里特定两类互相吞怎么破先别急着加模型先看图。把混淆最严重的那两类错分样本各抽 10 张拼起来看通常会看到两种情况。一种是真难分子叶期近缘种肉眼也只能靠叶柄颜色或者绒毛判断这种靠提升分辨率解决从 224 提到 320让细节可见同时把 RandomResizedCrop 的下限从 0.6 抬到 0.75保证训练时叶子始终占据足够像素。另一种是背景混淆两类样本恰好来自不同采集批次一个是黑土一个是砂土模型学的是背景。这种情况要么在增强里加更强的颜色抖动和随机擦除要么干脆在预处理里做前景分割把背景抠掉成本高一般不作为首选。我还会做一件事针对混淆类的加权采样。把这两类的采样权重单独放大 1.5 倍让模型在每个 batch 里更频繁地见到它们。这比全局加权更精准副作用更小。5.3 显存不够、batch 太小怎么调batch 从 32 降到 16 时学习率不要机械地减半我的经验是取 0.7 倍左右比较稳因为 AdamW 的自适应特性和 batch 大小不是线性关系。另外三个省显存的开关按收益排序开混合精度省 40% 左右、开梯度累积累积 2 步等效 batch 32不影响显存峰值但会略微影响 BatchNorm 类的统计ConvNeXt 用 LN 所以无所谓、降低输入分辨率288 降到 224 省 40%但对细粒度任务有损最后才考虑。还有一个坑timm的预训练模型加载时会占额外显存做权重转换如果你在模型创建前就已经把数据搬到卡上了可能 OOM。养成先建模型、再建 dataloader、最后搬数据的顺序。5.4 问题速查表现象最可能原因处理动作训练 loss 不降学习率过小 / 标签错位 / 冻结了全部参数检查 requires_gradlr 乘 3 重跑loss 变 NaN混合精度溢出 / lr 过大开 GradScaler、降 lr、梯度裁剪 5.0验证指标震荡大batch 太小 / 学习率调度太陡加 EMA、延长 warmup某些类召回极低类别不均衡分层采样 针对性加权换批次数据就崩背景泄漏 / 划分不合理分组划分、加强颜色增强推理结果和验证差很多训练/推理预处理不一致核对 resize 插值、归一化、裁剪方式EMA 权重反而不如原模型decay 太大、步数太少decay 降到 0.999最后一行那个问题我踩过只有 20 个 epoch、每 epoch 25 步总共 500 步用 0.9998 的 decayEMA 权重基本等于初始权重验证指标当然难看。EMA 的有效窗口要跟总步数匹配公式上大致是1/(1-decay)步取总步数的 1/10 到 1/5 比较合适。6. 我的迭代记录与几个不太上台面的经验6.1 五次实验的相对变化把我这份数据上的迭代过程列出来数值只代表相对趋势你那边绝对值肯定不同但排序逻辑应该能参考轮次配置macro F1 相对变化1ResNet50 基线224基础增强基准2ConvNeXt-T224加 drop_path 0.13 个点左右3分辨率提到 288RandomResizedCrop 下限 0.75再 2 个点左右4加 EMA 层衰减 0.8再 1.5 个点且曲线明显更稳55 折分组集成 多尺度 TTA再 2 个点涨幅趋缓到第 5 轮之后基本就压不动了再往上要么换更大的预训练权重ImageNet-22k 预训练版本比我用的 1k 版本好一点要么回去做数据层面的工作比如补拍少数类、人工复核错标样本。模型层面的边际收益在这个数据规模下已经很低了。6.2 三个我确实踩过的坑第一个坑是忘了改crop_pct。timm 的预训练模型自带一个默认的测试时裁剪比例如果你手动把输入改成 288 但没同步调整裁剪策略训练和推理的视野不一致验证指标会莫名其妙掉一两个点。判断方法很简单拿训练集的一张图分别走训练预处理和验证预处理把两张图存下来肉眼看视野差多少一目了然。第二个坑是num_classes改了但没重置分类头。直接用create_model(..., num_classes12)是对的但如果先创建再手动替换 head容易把原来的 1000 类头留在计算图里loss 看着正常但梯度是错的。养成打印一次model.head结构的习惯。第三个坑是验证集上的假提升。我有一轮调参后验证集涨了 3 个点兴冲冲换到另一批新拍的图上测反而掉了 5 个点。原因是那一轮我把颜色抖动的 hue 调小了模型更依赖颜色特征而新批次的光照色温完全不同。这件事之后我固定了一个习惯留一份从来不参与任何调参的最终测试集只在最后看一次平时所有决策都基于交叉验证的均值不看单折最高分。6.3 这套流程还能往哪延伸如果这个分类任务后面要往实际场景走我建议按这个顺序往下做先做开集识别田里的杂草种类远超这十几个类遇到没见过的类要能给出低置信度而不是硬分到某个已知类加一个基于能量分数或最大 softmax 的阈值就能做个初版再做半监督田间能拍的图远多于能标注的图用伪标签加一致性正则在未标注数据上做自训练通常能再拿几个点最后才是检测把分类模型接在一个轻量检测器后面先定位再分类这一步的工作量和数据标注成本都上一个台阶别一上来就做。我个人在实际操作中的体会是这类农业视觉任务模型结构带来的差异远小于数据质量带来的差异。同一份数据把错标清干净、把泄漏堵上、把长尾采样调平收益比换三个骨干网络加起来都大。ConvNeXt 好用但它不是银弹它只是让你在数据已经做扎实之后能多榨出那两三个点。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑