百度云深度学习竞赛模型设计实战:从迁移学习到模型融合的完整套路
简介本资源是面向深度学习竞赛参赛者与进阶学习者的百度云深度学习应用大赛完整模型设计源码集聚焦四则混合运算识别等典型CV任务覆盖从初赛到决赛的全周期算法迭代与工程优化实践。压缩包共92个文件总计20.59MB包含29个Jupyter Notebook含数据探索、预处理、多GPU训练、模型融合、loss曲线可视化等关键实验、36张PNG图像涵盖预处理效果、模型结构图、预测结果可视化及 leaderboard 截图、12个字体文件支撑报告渲染与图表美观性、3个核心Python脚本如make_parallel.py实现模型并行化、4份Markdown技术文档含初/决赛方案说明与验证码识别方法论以及LICENSE和readme.txt等规范性文件。已有283人学习下载可直接复现高分方案获取端到端建模流程、生成器对比分析、固定尺寸图像测试策略、全集训练150代调优记录等实战细节特别适合需提升竞赛建模能力与工业级代码组织水平的Python深度学习开发者。 我去年在百度云平台上认真跟完了一整季深度学习竞赛从初赛的跌跌撞撞到决赛勉强挤进前排最大的收获不是名次而是一整套可以反复用的Python深度学习模型设计套路。这份源码和模型设计思路我在赛后重新整理了一遍去掉了所有和具体赛题绑死的东西沉淀成了一套相对通用的框架。这篇文章就围绕这套源码拆一拆当时的设计决策、踩过的坑、以及最后几次关键的提分操作。如果你是第一次参加百度云上的深度学习竞赛或者虽然参加过但总觉得自己的模型设计有点“随缘”那这篇文章应该能帮你把思路理清楚。我会从竞赛场景分析、模型结构选型、数据增强策略、训练细节、源码模块拆解、到最后的调参和排错技巧一条线讲完。1. 竞赛场景分析与整体设计思路1.1 百度云竞赛到底是什么样的环境百度云上的深度学习竞赛通常跑在AI Studio或者BML全功能AI开发平台上预置了PaddlePaddle框架同时也支持PyTorch。平台会提供免费的GPU资源我记得当时是V10016G显存训练一轮中等规模的图像分类任务大概十几分钟。这种资源对个人开发者来说相当友好不用自己攒机器。竞赛的常见流程是平台给出训练集和验证集选手下载数据后本地或在线训练模型然后提交预测结果到排行榜。排行榜通常分A榜和B榜A榜用公开验证集算分B榜用隐藏测试集算分。这里有个关键点A榜分数高不代表B榜一定高因为公开验证集是有可能被“过拟合”的这一点在后面调参部分我会重点展开。所以整个竞赛的核心链路就是下载数据、做数据增强、设计模型、训练、验证、预测提交。而“模型设计”并不只是指网络结构它还包括数据怎么喂、损失函数怎么选、优化器怎么配、训练多少个epoch、什么时候该早停、以及最后怎么把多个模型的预测融合起来。1.2 赛题类型决定了模型设计的主线百度云竞赛的赛题覆盖很广有图像分类、目标检测、语义分割、文本分类、结构化数据预测偶尔还有多模态。不同赛题对模型设计的要求完全不同但我发现一个共性在训练数据量有限的情况下迁移学习数据增强适当的模型融合永远是最稳的组合。以我当时参加的一项图像分类赛题为例训练集大概只有两万张图类别数有100类。这个规模说大不大说小不小如果直接从零训练一个ResNet50很容易过拟合而且训练时间也不够。我当时的第一版方案就是直接使用在ImageNet上预训练好的ResNet50作为主干冻结前几层只微调后面的层。但这里有一个容易忽略的细节预训练模型的输入尺寸通常是224x224但赛题图像的原始分辨率可能更高比如400x300。直接把图缩到224会导致很多细节丢失。我在第一版就把输入尺寸固定在了224结果验证集准确率一直上不去后来改成训练阶段用随机裁剪到256再缩放到224验证阶段用中心裁剪效果立刻提升了一个多点。这个操作看似简单但对最终分数影响很大。1.3 为什么我用PaddlePaddle而不是PyTorch这个问题很多朋友问过我。坦率说PyTorch在社区生态上确实更丰富但百度云的竞赛环境对PaddlePaddle的适配最友好主要体现在三处平台的预置环境已经是PaddlePaddle的最新稳定版不用自己折腾CUDA、cuDNN这些底层依赖。平台的官方基线代码大多是PaddlePaddle写的直接在这个基础上改省去翻译模型的麻烦。AI Studio上有些数据集已经转成了PaddlePaddle专用的格式加载起来只需要几行代码。不过如果你更习惯PyTorch也没问题平台同样支持自己安装相关依赖。我的建议是哪个框架你更熟就用哪个不要在竞赛期间临时换框架。因为模型设计的大部分时间其实花在调参和debug上框架本身只是工具熟悉度才是效率的关键。我这套源码最终整理成了PaddlePaddle版本目录结构如下project/ ├── data/ │ ├── train/ │ ├── val/ │ └── test/ ├── config.py ├── dataset.py ├── model.py ├── train.py ├── infer.py └── utils.py后面我就按这个结构逐个模块拆解。2. 模型结构设计的关键决策2.1 主干网络选型ResNet还是EfficientNet模型设计的第一步是选主干网络。我在参加比赛前做过一组对比实验分别用ResNet50、ResNet101、EfficientNet-B3和DenseNet121在同一个验证集上跑了一遍结果如下主干网络验证集准确率单轮训练耗时分钟模型体积ResNet5089.2%1298MBResNet10189.8%20170MBEfficientNet-B390.5%2548MBDenseNet12188.9%1831MB从数据上看EfficientNet-B3的准确率最高而且模型体积最小但训练耗时也最长。在竞赛场景下我们不仅要看单模型分数还要考虑后续做模型融合时的总资源消耗。我当时用自己的账号在AI Studio上每天有免费的GPU时长配额四个模型都训练显然不现实。最后的决策是主模型用EfficientNet-B3辅助模型用ResNet50。EfficientNet负责冲高分ResNet50用来做差异化的模型融合。这个组合在后续的融合实验中被验证是有效的融合后的分数比单纯用EfficientNet再涨了0.8个点。2.2 分类头的设计全局池化到底怎么选很多开源代码里的分类头就是“全局平均池化全连接层Softmax”但这其实隐藏着可以优化的空间。在图像分类任务中池化方式决定了模型是否保留了空间位置信息。全局平均池化GlobalAveragePooling是把整个特征图压成一个向量优点是不会过拟合缺点是丢失了空间信息。全局最大池化GlobalMaxPooling则更关注最显著的特征点。我在实验中发现把两者拼接起来Concat即同时使用平均池化和最大池化的结果再送入全连接层效果比单独用任何一种都要好。原因很简单平均池化保留整体纹理信息最大池化保留局部强响应信息两者互补。具体实现是在PaddlePaddle里这样写import paddle import paddle.nn as nn class ClsHead(nn.Layer): def __init__(self, in_channels, num_classes): super().__init__() self.avg_pool nn.AdaptiveAvgPool2D(1) self.max_pool nn.AdaptiveMaxPool2D(1) self.fc nn.Sequential( nn.Dropout(0.2), nn.Linear(in_channels * 2, 512), nn.ReLU(), nn.Dropout(0.2), nn.Linear(512, num_classes) ) def forward(self, x): avg_feat self.avg_pool(x).flatten(1) max_feat self.max_pool(x).flatten(1) feat paddle.concat([avg_feat, max_feat], axis1) return self.fc(feat)这里有两个容易踩坑的地方。第一in_channels必须是主干网络最后一层输出的通道数EfficientNet-B3是1536ResNet50是2048写错会直接报shape不匹配的错。第二Dropout的位置很讲究放在全连接层之间是常规操作但不要放在池化之前否则会破坏特征图的完整性。2.3 损失函数Label Smoothing比想象中重要分类任务最常用的损失函数是交叉熵但在竞赛里直接使用普通交叉熵往往会遇到一个问题模型对训练集的拟合非常好但对验证集的预测过于“自信”导致泛化能力下降。我当时的解决办法是给损失函数加上Label Smoothing。Label Smoothing的原理很简单传统的one-hot标签是[0,1,0,0,...]模型会被迫学到非常极端的概率分布。Label Smoothing则是把标签变成[ε/(K-1), 1-ε, ε/(K-1), ...]其中ε是一个很小的值通常是0.1。这样模型就不需要把预测概率推到极端反而能学到更平滑的决策边界。我当时的实验数据显示加了Label Smoothing后验证集准确率提升了0.3到0.5个点而且训练过程更稳定loss曲线没有出现剧烈震荡。在PaddlePaddle里的实现是这样class LabelSmoothingCrossEntropy(nn.Layer): def __init__(self, smoothing0.1): super().__init__() self.smoothing smoothing def forward(self, pred, target): # pred: [N, C], target: [N] log_probs paddle.nn.functional.log_softmax(pred, axis-1) nll_loss -log_probs.gather(indextarget.unsqueeze(1), axis-1).squeeze(1) smooth_loss -log_probs.mean(axis-1) loss (1 - self.smoothing) * nll_loss self.smoothing * smooth_loss return loss.mean()注意在PaddlePaddle里gather这个API的用法和PyTorch里的gather几乎一样但如果你的PaddlePaddle版本比较旧可能不支持index参数这种写法需要改成paddle.gather(log_probs, indextarget, axis-1)。我建议直接用我上面这个版本因为它在新版框架里跑得最稳。2.4 为什么我坚持在推理阶段做TTATest-Time AugmentationTTA是竞赛里非常常见的提分手段。原理是推理时对同一张测试图做多种数据增强比如水平翻转、随机裁剪到不同位置把多个预测结果平均起来作为最终输出。这个方法几乎不花额外训练成本但能稳定提升0.2到0.3个点。我当时在推理脚本里做了三种增强原图、水平翻转、中心裁剪后缩放回原尺寸。对应的预测结果直接做softmax概率平均。这里有一个细节需要注意并不是所有增强都适合TTA。例如对图像做高斯模糊作为TTA通常效果不好因为模糊后的图像已经偏离了训练数据的分布。我只推荐翻转、微小的随机裁剪、以及多尺度变换这三种。3. 数据增强与训练策略的细节3.1 数据增强的组合拳不只是随机翻转很多新手在数据增强上做得比较保守就加一个RandomHorizontalFlip然后感叹为什么模型不涨点。其实数据增强是竞赛提分最直接的手段之一性价比非常高。我当时使用的增强组合是随机水平翻转、随机旋转10度、随机裁剪到256x256再缩放回224、颜色抖动亮度、对比度、饱和度各随机调整0.2、以及MixUp。MixUp就是随机两张训练图按比例混合标签也按同样的比例混合。很多人对MixUp有顾虑觉得会破坏数据分布但我的实验结果是MixUp在训练后期能明显提升模型泛化能力尤其当训练集数量不多的时候。我在PaddlePaddle里的实现是这样import paddle.vision.transforms as T train_transform T.Compose([ T.RandomHorizontalFlip(), T.RandomVerticalFlip(prob0.1), T.RandomRotation(degrees10), T.RandomResizedCrop((256, 256), scale(0.7, 1.0)), T.ColorJitter(brightness0.2, contrast0.2, saturation0.2), T.Resize((224, 224)), T.ToTensor(), T.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ])注意这里的顺序很重要。RandomResizedCrop在Resize之前这样裁剪后的图片还会被统一缩放相当于做了尺度和位置的增强。如果你把Resize放在前面裁剪就只能在固定大小的图上进行灵活性会差很多。3.2 优化器和学习率调度隐藏的涨分点优化器的选择上我的经验是AdamW配warmupcosine退火效果普遍好于SGD和原始Adam。原因在于AdamW把权重衰减从梯度更新中分离出来避免了L2正则对动量的干扰。在PaddlePaddle里AdamW不需要额外设置直接用paddle.optimizer.AdamW即可。学习率调度是我认为整个训练配置里最值得花时间调的部分。我当时的配置是初始学习率0.001前5个epoch做线性warmup把学习率从0逐渐升到0.001然后采用cosine退火策略在后续的epoch里把学习率缓慢降到接近0。这样做的逻辑是warmup让模型在训练初期不会因为学习率过大而“跑偏”cosine退火则在训练后期帮助模型更精细地收敛到局部最优。import paddle scheduler paddle.optimizer.lr.CosineAnnealingDecay( learning_rate0.001, T_max30, eta_min1e-6, warmup_epoch5, warmup_start_lr0.0 ) optimizer paddle.optimizer.AdamW( learning_ratescheduler, parametersmodel.parameters(), weight_decay0.05 )一个需要注意的地方是T_max应该和总训练epoch数保持一致。我在一次实验中把T_max设成了总epoch数的一半结果学习率在训练进行到一半时就降到了最低后半段一直以极低的学习率训练模型几乎不再收敛。这个问题排查了很久才发现是学习率调度器的时间和epoch不匹配。3.3 训练多少轮合适早停与模型平均训练轮数不是越多越好。我在一次实验中把训练轮数从30增加到60结果验证集准确率不仅没涨反而从90.2%掉到了89.7%。原因是模型在后期开始过拟合训练集。我的做法是每训练完一个epoch就在验证集上计算准确率如果连续5个epoch验证集准确率没有提升就触发早停。同时我把“历史最优模型”单独保存而不是直接保存最后一个epoch的模型。因为最优模型可能出现在第18个epoch而第20个epoch的模型已经过拟合了。在竞赛后期我还会使用Stochastic Weight AveragingSWA技巧。这个技巧的思想是在训练的最后阶段每隔几个epoch就把模型权重取一次平均最终用平均后的权重作为预测模型。这样做的好处是平均后的模型通常比任何单一时刻的模型都更平滑、更鲁棒。我在一次测试中发现SWA模型的验证集分数比最优单点模型高了0.4个点。3.4 模型融合走向最终高分的必经之路如果想进竞赛的前排模型融合是绕不开的。我当时的经验是不同结构的模型做融合效果远好于同一个模型不同种子的融合。道理很简单不同结构的模型学到的特征表示差异大融合时互补信息更多。我当时融合的方式是“概率平均”即把EfficientNet-B3和ResNet50的softmax输出直接取平均作为最终预测。这个操作简单但效果不错。如果你的时间充足还可以尝试加权平均权重的确定可以通过在验证集上做网格搜索从0.5:0.5开始每次调整0.05找到最优比例。不过模型融合也有一个副作用推理时间会翻倍。如果你的竞赛对单次提交时间有要求或者你的GPU配额不够了建议只融合两个模型不要贪多。4. 源码核心模块与实操笔记4.1 配置管理把所有超参数写进config.py我见过很多选手喜欢把超参数直接写在训练脚本里每次调参都要去改代码改完还会忘记之前用的是哪组参数。这种做法在竞赛后期特别吃亏因为你无法快速复现一个曾经跑出高分的配置。我习惯把所有可调参数集中放到config.py里包括数据路径、输入尺寸、批量大小、学习率、epoch数、标签平滑系数、模型类型等。这样每次实验只需要改config跑完以后记录下配置和对应的分数就能形成一条清晰的实验日志。# config.py class Config: seed 42 data_root data/ model_name efficientnet_b3 num_classes 100 input_size 224 batch_size 64 epochs 30 lr 0.001 weight_decay 0.05 label_smoothing 0.1 warmup_epochs 5 num_workers 4这里要特别强调seed的重要性。深度学习中几乎所有的操作都有随机性包括数据加载顺序、模型初始化和增强操作。如果不固定seed每次训练的结果会有几分的波动。固定seed后相同配置可以稳定复现。固定seed的操作在PaddlePaddle里是import paddle import numpy as np import random def set_seed(seed42): paddle.seed(seed) np.random.seed(seed) random.seed(seed)4.2 数据加载模块dataset.py的工程细节数据加载写得不好会直接影响训练速度尤其是在GPU资源有限的情况下数据加载如果变成瓶颈GPU会一直空闲等待数据造成极大的浪费。我当时的做法是使用PaddlePaddle的Dataset类自定义数据读取器在__getitem__里完成图片读取和增强。但这里有一个细节每次__getitem__都去硬盘读图片效率很低。PaddlePaddle的DataLoader会开多个子进程预取数据但如果你的操作是遍历一个小数据集直接用基础的Dataset就足够了。当时我把整个训练集在初始化时读入内存虽然占用了大概10G内存但换来的是训练过程中几乎没有数据等待。from paddle.io import Dataset from PIL import Image import os class TrainDataset(Dataset): def __init__(self, data_root, transformNone): self.images [] self.labels [] self.transform transform for label in os.listdir(data_root): label_dir os.path.join(data_root, label) if not os.path.isdir(label_dir): continue for img_name in os.listdir(label_dir): self.images.append(os.path.join(label_dir, img_name)) self.labels.append(int(label)) def __getitem__(self, index): img Image.open(self.images[index]).convert(RGB) if self.transform: img self.transform(img) label self.labels[index] return img, label def __len__(self): return len(self.images)这里要注意的是convert(RGB)。有些图片是四通道的RGBA或者单通道的灰度图如果不做转换后续的归一化操作会报错。统一转换成RGB是最保险的做法。4.3 模型定义与训练循环train.py的骨架训练脚本是整个项目的核心。我把它拆成了几个清晰的步骤加载模型、构建优化器和调度器、创建数据加载器、进入epoch循环。在每个epoch里遍历数据集、前向传播、计算loss、反向传播、更新参数、周期性输出日志。具体代码如下import paddle from model import build_model from dataset import TrainDataset, ValDataset from config import Config def train(): cfg Config() set_seed(cfg.seed) model build_model(cfg.model_name, cfg.num_classes, pretrainedTrue) scheduler paddle.optimizer.lr.CosineAnnealingDecay( learning_ratecfg.lr, T_maxcfg.epochs, eta_min1e-6, warmup_epochcfg.warmup_epochs, warmup_start_lr0.0 ) optimizer paddle.optimizer.AdamW( learning_ratescheduler, parametersmodel.parameters(), weight_decaycfg.weight_decay ) criterion LabelSmoothingCrossEntropy(smoothingcfg.label_smoothing) train_dataset TrainDataset( os.path.join(cfg.data_root, train), transformtrain_transform ) train_loader paddle.io.DataLoader( train_dataset, batch_sizecfg.batch_size, shuffleTrue, num_workerscfg.num_workers ) val_dataset ValDataset( os.path.join(cfg.data_root, val), transformval_transform ) val_loader paddle.io.DataLoader( val_dataset, batch_sizecfg.batch_size, shuffleFalse, num_workerscfg.num_workers ) best_acc 0.0 for epoch in range(cfg.epochs): model.train() total_loss 0.0 for i, (images, labels) in enumerate(train_loader): preds model(images) loss criterion(preds, labels) loss.backward() optimizer.step() optimizer.clear_grad() total_loss loss.item() if i % 100 0: print(f[Epoch {epoch}] batch {i}, loss: {loss.item():.4f}) scheduler.step() # validation model.eval() acc evaluate(model, val_loader) print(f[Epoch {epoch}] val_acc: {acc:.4f}) if acc best_acc: best_acc acc paddle.save(model.state_dict(), best_model.pdparams) print(fBest val_acc: {best_acc:.4f})这个训练循环有几个值得注意的点optimizer.clear_grad()必须放在loss.backward()之后、optimizer.step()之前或之后都行但一定要在下一轮前清空梯度不能图省事不调。model.eval()和model.train()的切换不能省。PaddlePaddle里BatchNorm和Dropout在两种模式下的行为不同漏掉切换会导致验证结果异常。paddle.save(model.state_dict(), ...)保存的是状态字典不是整个模型。这样保存的文件体积小而且加载时只要先构建模型结构再load进去就行。4.4 推理脚本与提交文件生成infer.py的细节推理脚本的目标很纯粹读取测试集中的图片用训练好的模型预测类别概率把结果输出成竞赛平台要求的csv格式。但这里的坑也不少。我遇到的最诡异的问题是测试图片的尺寸和训练时的预处理不一致导致预测结果极其不稳定。排查后发现是平台给的测试集里有一些图片带有EXIF旋转信息PIL的Image.open()不会自动旋转导致某些图是倒着的。解决办法是在infer.py里用ImageOps.exif_transpose()修复。另一个问题是在生成提交文件时类别顺序搞反了。竞赛平台通常要求提交的是“图片名,类别id”两列但类别id的排序方式不同平台不一样。有的按字母序有的按数字序有的是按训练集出现的顺序。这一点务必仔细阅读平台的提交说明或者直接用官方给的示例提交文件来对格式。生成提交文件的核心代码def infer(): cfg Config() model build_model(cfg.model_name, cfg.num_classes, pretrainedFalse) model.set_state_dict(paddle.load(best_model.pdparams)) model.eval() results [] for img_path in test_images: img Image.open(img_path).convert(RGB) img ImageOps.exif_transpose(img) # 修复方向 img_tensor val_transform(img).unsqueeze(0) with paddle.no_grad(): pred model(img_tensor) prob paddle.nn.functional.softmax(pred, axis1) results.append((img_path, int(prob.argmax().numpy()[0]))) with open(submission.csv, w) as f: f.write(image,label\n) for path, label in results: f.write(f{os.path.basename(path)},{label}\n)这里有两个常识性但容易忽略的点推理时一定要用paddle.no_grad()包住前向传播否则会占用不必要的显存在测试集较大时可能直接OOM。prob.argmax()返回的索引就是类别id前提是训练时的label编码和测试时的类别id对齐。如果不对齐提交后分数会是0这类错误很难排查所以最好在提交前做一个小批量的本地自检。4.5 一个容易被忽略的问题不同框架版本间的兼容性我在整理这套源码时发现网上很多开源代码是几个月前写的使用的API在新版本中已经废弃或改名了。例如早期PaddlePaddle 2.0时代的paddle.fluid.layers在2.4以后被大幅重构很多教程里的代码直接复制会报错。我建议所有选手在开赛前先花十分钟在平台上建一个环境把官方给的baseline代码完整跑通再开始自己的模型设计。这一步能避免后面90%的框架版本坑。5. 实战中的调参与排错记录5.1 训练loss不降或者直接爆掉怎么办这是新手最常遇到的问题。我遇到过的原因主要有三个学习率设置过大loss在初始几步直接变成NaN比如把学习率设成了0.1。解决办法是把学习率降到0.001或更低或者干脆用带warmup的调度器。标签越界类别id从1开始而模型的输出维度是0到C-1导致交叉熵计算时索引越界。解决办法是检查数据集的标签定义统一从0开始。数据归一化错误使用ImageNet预训练模型时归一化的均值和标准差必须和预训练时保持一致即mean[0.485, 0.456, 0.406]、std[0.229, 0.224, 0.225]。如果写成0均值1方差模型大概率无法收敛。另外我还遇到过一种特殊情况训练正常验证集loss很低但提交后线上分数极差。这种问题通常是训练集和测试集分布不一致。我当时的应对方法是做“伪标签”策略用训练好的模型给没有标签的测试数据打上高置信度的伪标签再把伪标签数据加入训练集重新训练。这个方法在当时帮我涨了将近一个点。5.2 显存溢出OOM的排查思路显存溢出在竞赛中很常见尤其是在使用大模型、大输入尺寸或大batch size时。我遇到过两次一次是batch size从64增加到128时直接OOM一次是推理时加载了两个大模型同时跑导致OOM。合理的排查顺序是先把batch size降到8确认代码能跑通再逐步增大batch size直到OOM为止。同时注意检查输入图片分辨率是否过大。我当时把输入尺寸从224改成320显存占用翻了一倍还多。此外PaddlePaddle的DataLoader有时会把预处理等操作放在GPU上执行也会增加显存占用可以在DataLoader里设置use_shared_memoryTrue来缓解。5.3 如何在排行榜上稳步提分我整理一下自己在不同阶段的实际操作可能是最直接可用的提分清单第一周跑通baseline确认数据格式和提交格式拿到初始分。这一步的目标是“上板”不是高分。第二周专注做数据增强和基础模型选型把分数从baseline往上推2到3个点。这个阶段性价比最高。第三周做模型结构的对比实验尝试换主干、调分类头、加Label Smoothing、做TTA。每一步0.3到0.5个点积少成多。第四周做模型融合、伪标签、SWA等进阶操作争取再涨1到2个点。这里我想强调一个心态上的问题我见过很多选手在第二周就陷入“调参黑洞”每天都在瞎试超参数但没有任何实验依据。其实与其调10个参数不如固定大部分参数每次只改一个变量并严格记录结果。只有这样才能形成可复用的经验而不是靠运气出分。5.4 几个容易在最后时刻翻车的细节最后提交前有几个细节值得逐项检查提交文件的编码格式是不是UTF-8是否有BOM头。有些平台对文件编码很敏感BOM头会导致第一行字段名解析失败。文件名是否和测试集的文件名完全一致。我用过一次os.path.basename来处理路径结果把子目录名的前缀也带进去了导致几千行文件的文件名对不上提交直接爆0。模型的权重文件是否保存了最佳版本而不是最后一次训练的版本。这个坑我踩过一次训练后期过拟合严重但保存的却是最后一轮模型提交分数比前一天低了2个点查了半天才发现是保存逻辑的问题。提示建议把“生成提交文件”这一步也做成固定的脚本不要每次手工拼csv。手工操作的出错率极高而且出了问题不好追溯。6. 写在最后的实战心得参加完这轮百度云深度学习竞赛我最大的体会是模型设计这件事真正拉开差距的不是你用了多前沿的网络结构而是你是否能在一套清晰的工程框架下系统地做实验、耐心地调细节。EfficientNet之类的主干网络几十行代码就能调起来真正需要花心思打磨的是数据增强的组合、训练策略的搭配、以及如何在有限资源下做最有效的模型融合。另一个感触是竞赛源码的整理比竞赛本身更重要。比赛结束时我手里有几十个版本的模型文件和上百组实验记录如果没有当时严格的配置管理这些资产几乎无法复用。后来我把整理好的这份源码开源给了同组的朋友他仅改了数据路径和类别数就能直接跑通自己的赛题这种沉淀下来的工程能力我认为才是参加竞赛最值得带走的收获。如果你正准备参加下一季的百度云深度学习竞赛我建议你从今天开始先搭好config、dataset、model、train、infer这五个模块的骨架把官方baseline跑通然后在第一周就把数据增强和迁移学习的基础版本做出来。后面每一步的提分都是在这套地基之上不断叠加的结果。本文还有配套的精品资源点击获取

相关新闻