
简介面向具有一定机器学习与计算机视觉基础的学生和研究者这份2024秋季机器学习竞赛指导资料围绕古代中国书法、绘画扫描图像的朝代预测展开。比赛数据集包含3600张512×512图像其中900张为AI生成且训练/验证集标签存在故意噪声仅一半AI图被明确标注其余被随机分入五个非AI朝代类别指南重点讲解了如何先训练专用模型完成数据清洗再开展图像分类建模对数据不均衡与长尾问题的处理也给出了明确提示。资源内附1个docx文档压缩包约554KB内容覆盖任务定义、数据预处理、模型优化、测试集提交等完整流程并详细说明了总体准确率、非AI类F1、AI类F1和长尾权益共享评分四类指标及其权重同时列出Kaggle提交规则、每日提交上限与阶段性排行榜时间节点。目前已有67人学习适合需要快速掌握竞赛框架并实际动手完成历史文物朝代预测任务的备赛者参考。1. 一个还打得动的赛题古代艺术品朝代预测的实战路径当机器学习比赛把“中国古代艺术品”和“朝代预测”放在一起时多数人的第一反应是“这得堆多深的网络才够用”。但这类赛题在2024年秋季的竞赛平台里反复出现核心诉求其实很朴素给定一张瓷器、书画或青铜器图像让模型输出它所属的朝代标签。本质是细粒度图像分类难点不在网络结构而在数据清洗、类别不平衡和标注噪声。这篇笔记写给两类人第一次参加图像识别比赛、想用预训练模型快速跑通全流程的新手以及已跑通过baseline、想在验证集与排行榜之间稳住分数的进阶选手。按“数据准备→模型训练→交叉验证→提交自检”的顺序把每一步的可行做法、参数依据和坑位讲透。2. 赛题拆解与数据准备把“朝代预测”变成图像分类问题的第一道关2.1 标签体系与类别不平衡为什么朝代分布本身就是一道坑比赛给出的原始数据通常是“图片文件夹 一个CSV标注文件”。CSV里至少有两列图片文件名和朝代标签。朝代标签可能是“唐”“宋”“元”“明”“清”这类离散值也可能混入“不详”“清代仿”这类模糊描述——后者在真实数据里相当常见因为它依赖文物专家的标注。拿到数据的第一件事不是写模型而是统计标签分布。常见的做法是import pandas as pd df pd.read_csv(train.csv) label_counts df[dynasty].value_counts() print(label_counts) # 把疑似明代明晚期合并到主类别减少标注噪声 df[dynasty] df[dynasty].replace({疑似明代: 明, 明晚期: 明}) df df[df[dynasty] ! 不详] # 重新统计确认合并后每类至少有多少张 print(df[dynasty].value_counts())这段代码做两件事第一步统计原始分布第二步清洗标签。合并“疑似明代”“明晚期”这类近似标签到主类别能降低标注噪声对训练的干扰“不详”这类无法确定朝代的样本与其让模型学一个混合分布不如直接剔除。合并规则要保留原始映射关系方便后面反向核查。在实际的比赛数据里清代和明代的样本往往远多于元代和宋代类别不平衡是常态。处理不平衡有几个层次如果最少的类别还有几十张以上可以先不做重采样靠加权采样和Focal Loss解决如果最少类别只有个位数就需要考虑类别合并或数据增强补样。我建议优先保留类别语义不要为了平衡而强行合并“元”和“明”——这会破坏模型对朝代边界的判别能力。2.2 数据清洗与切分拿到的图集不等于可直接训练的训练集古代艺术品图像来自拍卖行、博物馆官网、展览图录扫描件质量参差不齐。常见问题包括水印、Photoshop背景板、多角度拼图、黑白图、低分辨率缩略图。这些图如果直接进训练集模型很容易学到“带水印清代”这类虚假相关。我一般会先做一轮规则过滤from PIL import Image import os bad_files [] min_size (224, 224) for fname in df[image_id]: path os.path.join(train_images, fname) try: img Image.open(path) if img.width min_size[0] or img.height min_size[1]: bad_files.append((fname, too_small)) elif img.mode in (1, L) and fname not in allow_gray: bad_files.append((fname, gray_image)) except Exception: bad_files.append((fname, corrupted)) # 删除坏文件前的二次确认先打印统计别急着删 print(len(bad_files), 个文件需要处理)这段过滤代码的思路是“能删才删不能删就做标记”。过低分辨率的图比如小于224×224在ResNet这类模型里即使强行resize也会丢失纹理细节对朝代判断没有正向贡献灰度图在瓷器釉色、书画墨色这类特征上是信息缺失的除非训练集里灰度图占比极高否则建议剔除。注意allow_gray这个变量——如果你发现测试集里确实有灰度图就要保留一部分灰度图参与训练否则推理时容易翻车。数据切分是另一个容易出问题的点。官方训练的train.csv可能包含几千到几万张图切分策略直接影响验证集的可信度from sklearn.model_selection import StratifiedKFold df df.sample(frac1, random_state42).reset_index(dropTrue) skf StratifiedKFold(n_splits5, shuffleTrue, random_state42) for fold, (train_idx, valid_idx) in enumerate(skf.split(df, df[dynasty])): train_df df.iloc[train_idx] valid_df df.iloc[valid_idx] print(fFold {fold}: train{len(train_df)}, valid{len(valid_df)})用分层K折而不是简单的train_test_split目的是保证每个fold里各个朝代的比例和全量数据相近。对于类别不平衡的数据集这比随机切分稳定得多。如果不做分层极端情况下某个fold可能一个元代样本都没有验证分数会严重失真。2.3 数据增强策略让有限的艺术品图像扛住过拟合古代艺术品数据集通常只有几千张而分类模型动辄上千万参数过拟合是必然要面对的问题。数据增强在这类赛题里不是可选项是保命项。常见的PyTorch增强配置长这样from torchvision import transforms train_transform transforms.Compose([ transforms.Resize((256, 256)), transforms.RandomCrop(224), transforms.RandomHorizontalFlip(p0.5), transforms.RandomRotation(degrees15), transforms.ColorJitter(brightness0.2, contrast0.2, saturation0.1), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) valid_transform transforms.Compose([ transforms.Resize(256), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ])这里的关键参数有三个随机裁剪、随机旋转和颜色扰动。RandomCrop让模型在器物局部纹理和整体构型之间切换视角RandomRotation只给了15度因为古董器物图大多是正位拍摄旋转过大反而破坏了朝代的典型特征ColorJitter的饱和度扰动只给0.1原因在于青花瓷的釉色、青铜器的锈色本身是判别信息颜色动得太狠等于破坏标签。需要强调的是验证集和测试集的预处理必须和训练集分离。验证集用CenterCrop而不是RandomCrop是为了让每次验证的输入确定分数可复现。Normalize的均值标准差直接沿用ImageNet的统计值因为预训练模型的权重是在ImageNet分布上学出来的输入保持一致能最大化迁移效果。如果数据量实在太少少于每类50张还可以考虑MixUp和CutMix这类强增强from torchvision.transforms import v2 train_transform v2.Compose([ v2.Resize((256, 256)), v2.RandomCrop(224), v2.RandomHorizontalFlip(p0.5), v2.MixUp(alpha0.2), v2.ToTensor(), v2.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ])MixUp按alpha参数把两张图的像素和标签按比例混合等于强制模型在类别之间的连续空间里学习对减少预测震荡很有帮助。但MixUp会改变loss曲线形态训练前期验证loss偏高是正常的不要因此关掉它。这类强增强只在训练集规模小、且baseline已经跑通之后再引入。3. 模型选型与训练配置从预训练权重到微调的最小可跑通路径3.1 基线模型为什么用ImageNet预训练模型而不是从零训练古代艺术品图像和ImageNet里的自然图像有分布差异但底层特征边缘、纹理、形状是通用的。直接拿随机初始化的ResNet从零训练几千张艺术图根本喂不饱模型训练十几个epoch后验证集分数通常只有三四成。用ImageNet预训练权重做迁移学习相当于让模型带着“见过世界”的常识进入一个更细分的领域。PyTorch里加载预训练模型很方便import torchvision.models as models model models.resnet50(weightsmodels.ResNet50_Weights.IMAGENET1K_V1) num_classes len(df[dynasty].unique()) model.fc torch.nn.Linear(model.fc.in_features, num_classes)关键是最后一行把全连接层替换成自己的类别数。注意不同模型的分类层属性名不一样——ResNet系列叫fcEfficientNet系列叫classifier换成后者时要改这一行。冻结前几层还是全量微调取决于数据量。数据量在几千张级别时我一般冻结前两个stage只微调stage3、stage4和全连接层数据量上万后直接全量微调更快。如果选择冻结前两层代码上要做一点额外工作for name, param in model.named_parameters(): if name.startswith(layer1) or name.startswith(layer2): param.requires_grad False冻结的层不会计算梯度反向传播时也会跳过显存占用和训练时间都有明显下降。判断哪些层可以冻结的一个简单经验是先做一次全量微调跑几个epoch观察各层梯度的norm梯度接近0的层就是已经够用的通用特征层下次训练可以把它们冻结。选ResNet50还是EfficientNet、ViT我的建议是比赛初期以ResNet50或ResNet101作为baseline理由是训练稳定、显存占用适中、调参经验丰富、社区踩坑案例多。等baseline分数稳定后再尝试EfficientNet-B4或Swin-T看验证集是否真的有提升。不要在第一天就上ViT-Large训练速度和显存消耗会拖慢整个迭代节奏。3.2 训练超参数学习率、batch size、epochs怎么设微调阶段的学习率应该是预训练时的十分之一到百分之一。常见做法是optimizer torch.optim.AdamW(model.parameters(), lr1e-4, weight_decay1e-4) scheduler torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max20) criterion torch.nn.CrossEntropyLoss()AdamW 1e-4的学习率在大多数图像分类微调场景里都能稳定收敛。如果你用的是SGDmomentum学习率可以放到1e-3到3e-3之间但收敛速度慢一些。CosineAnnealingLR让学习率随epoch余弦下降前期大步探索、后期小步精修在分类任务里比StepLR更平滑。batch size的设定取决于显存和图像尺寸。ResNet50 224×224输入batch size 32在12GB显存上基本是上限batch size 64需要16GB以上。一个小技巧是用梯度累积accumulation_steps 4 for step, (images, labels) in enumerate(train_loader): loss criterion(model(images), labels) loss loss / accumulation_steps loss.backward() if (step 1) % accumulation_steps 0: optimizer.step() optimizer.zero_grad()这段代码把batch size 16等效成batch size 64的梯度更新但显存占用还是16的量级。注意loss除以accumulation_steps否则梯度会偏大。epochs的设定没有标准答案我习惯在验证集loss连续5个epoch不下降时触发早停而不是硬跑到固定轮数。早停后如果还想继续训练用验证集分数最高的权重重新设置学习率为原来的十分之一再续跑几个epoch往往还能再涨一点。训练过程的监控也很关键。我至少记录三个指标训练loss、验证loss、验证集Macro F1。判断训练状态是否正常有一个经验标准前三个epoch训练loss快速下降、验证集分数缓慢上升是正常状态如果验证集分数在前两个epoch就冲到高位然后不动大概率是数据泄漏或验证集切分有问题先停下去检查数据。3.3 损失函数与评估指标Top-1 / Top-5 / F1 怎么选比赛评估指标通常在赛题说明里写明可能是Top-1准确率也可能是各类别F1的均值。不同指标对应不同的优化目标和不同的训练技巧。Top-1准确率模型输出的最高概率类别和真实标签一致就算对。直接优化CrossEntropyLoss即可类别不平衡的影响相对小因为分母是总体本数。Top-5准确率只要真实标签在模型预测的前5个类别里就算对。对朝代分类这类类别层次复杂的任务Top-5比较宽容比如“明晚期”被预测成“明”或“清”都可能命中Top-5。Macro F1每个类别的F1先单独计算再做算术平均。对少数类非常敏感少数类表现不好分数就会明显掉。这种指标下类别权重和Focal Loss会有更明显的收益。如果评估指标是Macro F1CrossEntropyLoss可能不够。可以给loss加上类别权重class_counts df[dynasty].value_counts().to_dict() weights [1.0 / class_counts[c] for c in sorted(class_counts.keys())] weights torch.tensor(weights, dtypetorch.float32) weights weights / weights.sum() * len(weights) # 归一化到均值1 criterion torch.nn.CrossEntropyLoss(weightweights.to(device))权重归一化到均值1保证整体loss量级和普通CrossEntropyLoss接近不至于因为权重过大导致训练震荡。注意加了类别权重后验证集上观察到的loss和官方指标不一定线性对应要回到评估指标本身来判断好坏。Focal Loss在类别不平衡且存在难例时值得一试import torch.nn.functional as F class FocalLoss(torch.nn.Module): def __init__(self, gamma2.0, alphaNone): super().__init__() self.gamma gamma self.alpha alpha def forward(self, logits, targets): ce F.cross_entropy(logits, targets, reductionnone) pt torch.exp(-ce) focal (1 - pt) ** self.gamma * ce if self.alpha is not None: focal focal * self.alpha[targets] return focal.mean()gamma2.0是Focal Loss论文里的默认值在难例上降低易别样本的loss贡献。alpha可以直接复用前面算出来的类别权重。要注意的是Focal Loss在训练前期的loss比CrossEntropyLoss低验证集分数在前几个epoch可能反而下降这是正常的给它足够多的epoch再下结论。4. 从验证集到排行榜交叉验证、TTA与模型融合的实战配置4.1 K折交叉验证分组泄漏与同源泄漏比赛数据如果来自同一个博物馆的同一批拍摄项目图片之间可能存在“同源泄漏”——同一件器物拍了多张照片被分到训练集和验证集验证分数会虚高。避免的方法是按拍摄批次或器物编号分组而不仅仅是按图片随机切分。如果CSV里没有批次字段可以用图像感知哈希做相似度初筛from PIL import Image def image_hash(path, hash_size8): img Image.open(path).resize((hash_size, hash_size), Image.LANCZOS).convert(L) avg sum(img.getdata()) / (hash_size * hash_size) return .join(1 if p avg else 0 for p in img.getdata())这个感知哈希函数把图像缩放成8×8灰度图用每个像素与均值的比较生成64位字符串。两张相同器物的不同角度照片感知哈希不一定完全相同但可以按汉明距离做分组聚类的初筛。聚类后把同一簇的图放进同一个fold能显著降低泄漏风险。另一个容易踩的场景是“时间泄漏”。艺术品的朝代分布具有长尾特征清代样本可能来自近几年的拍卖记录宋代样本可能来自早年图录。如果测试集的采集时间晚于训练集模型可能学到“新图清代”的捷径。处理方式是如果官方给的图像文件名或元数据里带时间信息尽量按时间切分验证集而不是随机切分。切分成K折之后还有一个细节是不同fold的模型权重管理。我习惯把每一折训练出的模型按验证分数排序只保留前若干个用于最终集成而不是把5个fold全用上。某些fold因为数据切分偏差分数明显低强行拉进集成只会拖后腿。4.2 TTA与多尺度推理把推断过程也变成涨点手段训练结束后推理方式对最终分数的贡献常常被低估。TTATest Time Augmentation的核心思想是把一张测试图做多个变换后分别预测再对概率取平均以此平滑掉单次推理中裁剪位置、翻转方向带来的随机噪声。def predict_with_tta(model, image, tta_transforms, device): model.eval() probs [] with torch.no_grad(): for transform in tta_transforms: aug_image transform(image).unsqueeze(0).to(device) p torch.softmax(model(aug_image), dim1) probs.append(p) return torch.mean(torch.stack(probs), dim0).cpu().numpy()TTA变换一般用水平翻转、垂直翻转、小角度旋转、中心裁剪缩放组合。对古代艺术品来说水平翻转是最安全的——器物的左右镜像不影响朝代判断垂直翻转要谨慎字画的上下颠倒会破坏阅读方向但陶瓷器垂直翻转影响不大可以先在验证集上对比测试。多尺度推理是TTA的变体把图像分别缩放到224、256、320输入对不同尺度的概率取平均。这样能缓解模型对物体大小的敏感性对拍摄距离不一致的测试集尤其有效。TTA的代价是推理时间乘以变换次数。如果测试集有1万张图开3种TTA意味着3万次前向传播在单张显卡上可能需要一两个小时。比赛后期如果时间充裕建议开启如果离提交截止只剩两小时优先保证提交格式正确TTA可以缩减到只做水平翻转这一种。4.3 模型集成平均概率还是软投票集成是比赛后期最常见的涨点手段。对单模型验证分数在88%左右的基线3个不同结构的模型做集成通常能再涨1到2个百分点。集成方式有两种主流做法# 方式一概率平均 final_prob (prob_model_a prob_model_b prob_model_c) / 3 # 方式二软投票按置信度加权 weights [0.4, 0.3, 0.3] # 按验证集分数分配 final_prob (prob_model_a * weights[0] prob_model_b * weights[1] prob_model_c * weights[2])概率平均对模型间的互补性要求最高如果三个模型结构完全一样、训练数据也一样平均结果几乎没有提升。互补性来源于多样性——不同网络结构ResNet vs EfficientNet、不同训练数据原图 vs 裁剪图、不同增强策略强增强 vs 弱增强。软投票的权重一般按验证集分数做简单归一化但我见过不少翻车案例验证集分数最高的模型权重给最大集成后在排行榜上反而下降。原因在于验证集本身就是偏差抽样权重分配过拟合了验证集。我的习惯是权重先按1:1:1起步如果验证集结果有提升再微调权重最多试两三轮不做细粒度搜索。集成能涨分但也会掩盖单个模型的问题提交前务必把集成后的softmax输出保存下来至少抽50张图做人工复核。此外集成用的各个模型最好在验证集上尽量互补——如果两个模型在相同样本上犯同样的错误集成也不会修正它。可以在验证集上计算两两预测结果的相关性选择预测差异大的模型组合。5. 避坑指南古代艺术品图像识别比赛里最常见的5个翻车现场5.1 现象训练loss下降但验证分数纹丝不动训练集loss一路降到0.03验证集分类准确率停在82%不动越来越像“背答案”。原因是数据量太少模型容量太大。尤其是预训练模型全量微调时低层特征会被“洗掉”通用性完全适配训练集上的统计噪声。验证集上出现任何训练时没见过的光线、背景、角度模型就露馅。解决方法是分三步走。第一步冻结模型前两个stage只微调高层特征和分类头代码上对requires_grad做置位即可。第二步增强训练数据的随机性——把RandomCrop的裁剪范围从固定224改成(160, 224)区间随机相当于每一轮都让模型看不同尺度的器物细节。第三步如果还压不住在分类头之前加nn.Dropout(0.3)强制模型减少对少数特征的依赖。5.2 现象模型学到背景而不是器物有一个很典型的翻车案例训练集里大量瓷器照片带深红色展台背景模型在验证集准确率95%提交后在测试集上暴跌到七成。检查注意力热图后发现问题出在背景——模型靠“红色区域占比”判断清代瓷器而不是靠器物的器形和釉色。原因是数据采集时的拍摄环境高度一致背景成为泄漏特征。古代艺术品数据尤其严重同一个展览拍摄的几百张图背景完全相同模型只要记住背景就能“高分”过关。解决办法之一是做随机背景替换或背景模糊。用torchvision.transforms.GaussianBlur把图像四周区域模糊掉降低背景的判别力。更彻底的做法是用分割模型先提取器物mask按mask裁剪后把背景置灰切断这条捷径。如果时间不够至少要把包含明显展台、灯光、玻璃反光的图像挑出来做数据增强测试确认模型在不同背景上的表现。关于注意力热图的检查可以参考GRAD-CAM的思路在验证集上随便挑五张图把热图叠在原图上肉眼观察高响应区域是落在器物上还是背景上。这一步操作成本很低却是排查预测原因的体检项。5.3 现象标注噪声导致硬样本上反复震荡训练到中期loss已经收敛但在“明晚期青花”和“清代青花”这两类上预测结果随epoch反复横跳验证集分数时涨时跌。原因是这批样本的标注本身就是专家有分歧的两类之间的视觉差异极小——釉色、器形、款识的区别往往在毫厘之间模型处在一个“在两个类别间摇摆”的区域。解决办法有两个方向。第一个是直接降低这类样本的loss权重用torch.nn.CrossEntropyLoss(ignore_index-1)先把最模糊的样本剔除保证训练信号纯净。第二个是保留样本但把标签软化把“明晚期”样本的one-hot标签改成[0.6, 0.4]的软标签分布让模型学会输出“偏向明晚期的模糊预测”。软标签需要在数据加载层实现改写collate_fn函数来自定义标签张量的生成方式细节会多一点但效果值得。这些硬样本其实也是最后提交时最可能被扣分的位置。如果比赛指标是Macro F1宁可把硬样本预测成“两边概率接近”也不要冒险猜死其中一个。5.4 现象验证集分数很高但排行榜分数暴跌本地验证集Macro F1做到0.91提交后排行榜只有0.74。这类场景在图像识别比赛里很常见区别往往在于测试集的分布和训练集不一致——测试集可能全是不同博物馆的展品拍摄角度、光线、背景完全不同。原因是验证集的切分方式没有模拟测试集的分布。随机切分让验证集和训练集共享了太多“同源”信息比如同一个展柜、相同机位拍出来的批次都被分到了两边。解决方法是检查官方提供的图像文件名是否有拍摄批次或来源信息。如果有按来源分组切分验证集如果没有用聚类方式把图像按视觉风格色彩直方图、图像哈希距离分组确保验证集中包含多种拍摄风格而不是集中于一种。这一步做对了本地验证分数会下降几个点但排行榜上的信心会大幅上升。5.5 现象显存溢出与训练速度失控224×224输入、ResNet50、batch size 64在12GB显卡上直接OOM换batch size 16后训练一个epoch要30分钟10个epoch就是5小时比赛周期根本撑不住。原因是显存瓶颈通常不在模型本身而在数据加载管线——每次做RandomResizedCrop的CPU预处理太慢GPU在等数据同时验证集推理时如果开了多尺度TTA显存占用会翻倍。解决方法是先排查数据加载是否是瓶颈用torch.utils.data.DataLoader(num_workers8, pin_memoryTrue)把预处理并行化再做混合精度训练torch.cuda.amp.GradScaler配合autocast能让显存占用下降三成训练速度提升约一倍。如果显存仍然不够把图像输入尺寸从224降到192这是个不容易察觉但有效的折中——验证集分数可能掉0.5个百分点但训练速度能提升30%左右。如果换GPU不现实就坚持batch size 16 梯度累积配混合精度。6. 提交前最后一步可复现的推理管线与结果自检技巧6.1 把模型输出转成规范的提交CSV比赛对提交格式的要求各不相同最常见的格式是第一列是图片ID后面每一列是各个朝代的预测概率。这里有个容易被忽视的坑——列的顺序必须和官方给定的类别顺序一致否则分数会莫名其妙地低。import pandas as pd submission pd.DataFrame({ image_id: test_ids, }) for i, dynasty in enumerate(sorted(class_names)): submission[dynasty] all_probs[:, i] submission.to_csv(submission.csv, indexFalse)这段代码用sorted(class_names)确定列名顺序保证预测概率的列索引和官方类别顺序对应。实际提交前建议先读一下官方sample_submission.csv的表头把列名对齐再生成。提示生成提交文件前先打开官方sample_submission.csv核对列名和顺序。光看题面描述容易漏掉“按类别字典序排列”这类细节。6.2 用混淆矩阵定位系统性格失误提交前最后一步我习惯在验证集上跑一次混淆矩阵。不需要复杂的可视化直接打印预测标签和真实标签的交叉表from sklearn.metrics import confusion_matrix import numpy as np cm confusion_matrix(y_true, y_pred) np.set_printoptions(precision2, suppressTrue) print(cm)看混淆矩阵时重点关注两个方向相邻朝代的混叠如“明”和“清”和跨朝代混淆如“宋”和“清”。前者说明模型对细粒度特征把握不足后者说明模型可能学到了错误的全局特征比如把水墨画技法的变化当成了朝代标签。如果跨朝代混淆集中在某个特定类别上回到原始数据里查这个类别的图像质量和水印情况往往能找到泄漏线索。另外把混淆矩阵按行归一化后看召回率能判断哪些类别被系统性地漏判了。如果一个类的召回率显著低于其他类说明训练时它的信息量不足考虑为它增加增强权重或找更多样本。6.3 软标签与伪标签后期还能用的稳健招数训练后期如果时间还够伪标签是最稳的进阶手段用当前集成模型预测测试集的置信度把高置信度样本比如最大概率超过0.9当作训练数据加入第二轮训练。注意伪标签只加置信度高的样本且加入后必须在验证集上确认分数不下降才继续。软标签是把伪标签的置信度作为训练目标loss BCE(softmax_pred, pseudo_prob)。这比硬标签更平滑对噪声的容忍度更高尤其适合那些模型置信度不算特别高、但也不想完全丢弃的样本。我自己的习惯是提交前最后一晚先跑一次不带TTA的快速提交确认格式无误再开TTA做正式版本同时把每个模型的输出概率分别归档万一副榜分数异常时可以快速回溯。这个习惯让我躲过了不止一次“提交格式错误0分”的事故。希望帮到你。本文还有配套的精品资源点击获取