拓冰建站拓冰建站
首页 / 资讯中心 / 正文

猪脸识别竞赛实战:迁移学习、细粒度分类与模型微调全解析

简介这份资源面向备战全国大学生电子设计竞赛及参与京东JDD猪脸识别类算法竞赛的同学整合了从数据预处理、模型微调到提交推理的完整Python实现并附带README说明文档适合已有一定Python基础、希望快速上手实战项目的参赛者。压缩包共5个文件包括4个.py脚本与1个.md文档包体仅6KB脚本按训练、测试、微调、提交等流程分工便于理解图像识别任务的整体管线。目前已有251人学习下载可作为竞赛方案参考或课设拓展。资源虽小但包含完整代码框架能帮读者省去搭环境与写流程的时间直接对照修改数据路径和模型参数即可运行同时通过简洁的代码注释了解调优思路与提交格式是一份轻量实用的备赛工具。1. 从京东JDD猪脸识别比赛看迁移学习落地的完整姿势养猪场的身份识别比想象中复杂猪脸相似度高、光照多变、耳标易脱落视觉方案是更干净的抓手。这套源码包来自京东JDD猪脸识别竞赛包含fitune.py、train.py、test.py、submit.py等完整流程不是PPT级的demo而是可以直接运行的实战程序。适合两类人想参加细粒度图像竞赛的学生以及需要在工业场景落地识别模型的工程师。拆解这份代码能清楚看到从数据准备、迁移学习微调到提交评测的每个环节是怎么衔接的也能避开许多只在真实数据上才会出现的坑。2. 赛题解析与数据准备猪脸识别不是单纯的目标检测2.1 任务定义与评估口径猪脸识别本质是细粒度图像分类目标是给定一张猪脸图像判断属于哪一头猪。它和人脸识别不同的地方在于类别数量可能很多可达数百头每头猪的样本数却很少而且不同个体之间的差异可能非常细微比如白色猪的耳型、皮肤纹理和吻部细节。竞赛通常使用Top-1准确率或Top-5召回率作为评估指标这意味着模型输出的排序要比硬分类更重要提交时给出概率最高的几个ID也可能拿到分数。源码中README.md一般会写明数据集的目录划分常见的结构是train/下面按猪的ID建子文件夹每张图片属于一个个体test/下面是没有标签的待预测图片。拿到zip包后第一步不是急着训练而是先统计每个类别的样本量。我一般会写一个快速脚本扫描全量数据看是否存在类别不平衡严重的长尾分布这直接决定了要不要做重采样或者数据增强。2.2 数据列表的生成与验证集划分竞赛数据往往不提供官方验证集通常需要自己从训练集中切分出一部分。这里要特别注意不能按图片随机切分而要按猪的ID切分否则同一头猪的图像会同时出现在训练集和验证集导致验证分数虚高测试时却崩掉。常见做法是使用GroupShuffleSplit或者按ID手动切分。下面是我基于这份源码常见写法整理的数据准备代码用于生成训练列表和验证列表import os import random data_root data/train ids [d for d in os.listdir(data_root) if os.path.isdir(os.path.join(data_root, d))] # 按猪ID进行分层划分保证验证集中出现的个体不在训练集 random.seed(42) random.shuffle(ids) val_ratio 0.1 val_ids set(ids[:int(len(ids) * val_ratio)]) train_list [] val_list [] for pid in ids: img_dir os.path.join(data_root, pid) for fname in os.listdir(img_dir): if fname.lower().endswith((.jpg, .jpeg, .png)): line f{pid}/{fname} {pid}\n if pid in val_ids: val_list.append(line) else: train_list.append(line) with open(train_list.txt, w) as f: f.writelines(train_list) with open(val_list.txt, w) as f: f.writelines(val_list)这段代码的逻辑是首先获取所有猪ID随机打乱后按比例切出验证ID集合然后遍历每头猪的图片目录以路径 标签的格式写入文本列表。这样无论是训练还是验证都保证了同一个体的图片不会跨集合出现。参数val_ratio0.1可以根据样本总量调整如果每头猪图片数很少建议降到0.05让训练集尽量多。2.3 图像增强的边界与策略表猪脸识别的图像增强不能照搬ImageNet分类的常规套路。养猪场拍摄的图片背景复杂颜色偏差大但猪脸的形状方向相对固定。合理的增强包括随机裁剪、水平翻转、颜色抖动、小角度旋转但不要使用随机擦除或大角度旋转那会破坏猪脸的局部特征。源码中一般会在数据加载部分做在线增强我通常会额外引入MixUp或CutMix作为正则化手段对少样本类目有明显帮助。增强操作推荐参数使用时机RandomResizedCropscale(0.7, 1.0)训练集模拟不同距离拍摄RandomHorizontalFlipp0.5训练集猪脸左右对称ColorJitterbrightness0.3, contrast0.3训练集适应光照变化RandomRotationdegrees15训练集小角度姿态变化CenterCrop与训练尺寸一致验证集必须保持确定注意增强的强度要随着训练轮数动态调整前期强增强后期逐渐减弱这样可以让模型先学大尺度特征再精修细节。这里有一个容易踩的坑如果把验证集也做了强增强会导致评估分数上下波动很大所以验证集必须只用Resize和CenterCrop这类确定性变换。3. 模型选型与迁移学习fitune.py 里的微调逻辑3.1 为什么必须用预训练权重从头训练一个猪脸识别模型即使数据集规模达到几万张也很难收敛到可用精度。原因在于猪脸之间的类间差异太小而通用视觉特征纹理、边缘、颜色分布在ImageNet预训练模型中已经学到。迁移学习可以理解为把特征提取器搬过来只重新学习最后的分类层和部分高维语义特征。源码中提供了fitune.py这个命名暗示了整个流程是先训练分类层再解冻全部层进行微调也就是两阶段训练策略。3.2 两阶段微调的实现细节fitune.py 里典型的流程是这样的import torch import torch.nn as nn from torchvision import models # 使用ResNet50作为骨干网络加载预训练权重 model models.resnet50(weightsmodels.ResNet50_Weights.IMAGENET1K_V1) in_features model.fc.in_features num_classes 500 # 根据实际猪的数量修改 model.fc nn.Linear(in_features, num_classes) # 第一阶段冻结backbone只训练新初始化的fc层 for name, param in model.named_parameters(): if name.startswith(fc): param.requires_grad True else: param.requires_grad False optimizer torch.optim.Adam(filter(lambda p: p.requires_grad, model.parameters()), lr1e-3)这段代码的关键是named_parameters中按名字设置梯度开关。冻结backbone后fc层因为是从头初始化的需要较大的学习率而预训练层的权重则保持不变。第一阶段通常训练5到10个epoch等验证集准确率稳定后进入第二阶段解冻backbone并把学习率调低到1e-4以下对整个网络进行细粒度调整。第二阶段微调要特别注意学习率的设置。我一般会采用分层学习率衰减backbone的学习率设置为fc层的十分之一因为backbone已经比较成熟更新幅度过大容易破坏预训练特征。优化器可以继续使用Adam但最后几个epoch切换到SGDmomentum有助于收敛到更平坦的极小值这在细粒度识别中往往能带来一个百分点左右的提升。阶段冻结范围学习率优化器训练轮数第一阶段backbone全部冻结fc层 1e-3Adam5-10第二阶段全部解冻backbone 1e-5fc 1e-4Adam 或 SGD20-503.3 损失函数与类别不平衡处理猪脸识别的类别数可能几百但每头猪的图片有限直接使用softmax交叉熵会遇到两个问题一是收敛速度慢二是对难样本不敏感。常见做法是使用加了margin的softmax变体例如ArcFace或CosFace它们能拉大类中心的距离对人脸和动物脸识别都有效。如果不想改损失函数也可以用标签平滑label smoothing来防止模型对训练集过于自信。源码中的fitune.py如果只使用普通的CrossEntropyLoss我一般会在其基础上补充一个辅助的center loss来减小类内距离。下面是一个简化的实现片段把center loss与交叉熵加权结合import torch.nn.functional as F def center_loss(features, labels, centers, alpha0.5): # features: (batch, dim), labels: (batch,), centers: (num_classes, dim) centers_batch centers.index_select(0, labels) diff features - centers_batch loss (diff.pow(2).sum(dim1)).mean() # 实际训练中会维护centers并做动量更新 return loss注意center loss的权重不能太大一般设为0.01到0.05之间否则会干扰分类损失的主导地位。另外centers需要作为模型参数保存到checkpoint中否则继续训练时会丢失类中心信息导致精度下降。4. 训练与测试train.py 和 test.py 的完整运行流程4.1 训练启动参数与常用配置train.py 通常是一个独立的训练入口可以接受命令行参数覆盖默认配置。为了快速复现我建议把关键超参数写入一个config字典再用argparse覆盖。下面是从源码风格整理出的参数表参数名默认值说明--batch_size32受显存限制可调小到16--lr1e-3初始学习率微调阶段改1e-4--epochs60配合早停使用时可以设大--num_workers8数据加载线程数Windows建议设2--model_nameresnet50可选resnet101/efficientnet--image_size224输入分辨率增大到256可能更优启动训练的命令一般是python train.py --batch_size 32 --lr 1e-3 --epochs 60 --model_name resnet50训练脚本内部的流程是读取train_list.txt和val_list.txt构建DataLoader在每个epoch结束时计算验证集准确率并保存最优模型。需要注意的是数据加载的shuffle参数必须设为True否则每个epoch的batch组成完全相同会导致BN层的统计量计算偏差验证集准确率会出现周期性的尖峰。4.2 训练循环中的关键检查点训练时最容易被忽视的是模型保存逻辑。不要只保存最后一个epoch的权重而要保存验证集上准确率最高的那个。我习惯在每个epoch后记录验证准确率并维护best_acc变量当高于历史最优时把state_dict连同optimizer和epoch一起保存到checkpoint文件。还有一个技巧是保存ema指数移动平均的权重test阶段使用ema权重通常比原始权重更稳定。best_acc 0.0 for epoch in range(start_epoch, args.epochs): train_one_epoch(model, train_loader, optimizer, criterion, epoch) acc validate(model, val_loader) if acc best_acc: best_acc acc torch.save({ epoch: epoch, model_state_dict: model.state_dict(), best_acc: best_acc, }, best_model.pth)此外PyTorch的DataLoader在num_workers0时如果训练脚本在Windows上运行需要放在if __name__ __main__:中否则会报RuntimeError。这是竞赛源码在移植到本机时最常见的报错之一遇到时先检查入口。4.3 测试阶段的TTA与输出落地test.py 负责加载训练好的模型对无标签的测试集进行预测并输出提交文件。朴素的做法是每张图只做一次前向但为了提升精度一般会使用TTATest Time Augmentation对同一张图做水平翻转、多尺度缩放然后对softmax概率取平均。import torchvision.transforms as T import torch.nn.functional as F import numpy as np def predict_with_tta(model, img, scales[0.9, 1.0, 1.1]): model.eval() probs [] with torch.no_grad(): for scale in scales: w int(img.width * scale) h int(img.height * scale) resized img.resize((w, h)) flipped T.functional.hflip(resized) for im in [resized, flipped]: input_tensor test_transform(im).unsqueeze(0).cuda() output model(input_tensor) probs.append(F.softmax(output, dim1).cpu().numpy()) avg_prob np.mean(probs, axis0) # 对所有增强结果取平均 return np.argmax(avg_prob, axis1)[0]TTA的代价是推理时间翻倍但换取的是1%到2%的准确率提升。在比赛最后一两天TTA是性价比最高的提分手段。注意测试时的图像变换必须与训练时保持一致比如训练用了RandomResizedCrop测试就不能直接用CenterCrop否则输入分布不匹配会导致精度严重下降。5. 提交与效果验证submit.py 的坑与可复现技巧5.1 提交格式的最终校验submit.py 负责生成竞赛要求的csv或者txt文件。竞赛平台通常要求两列图像路径、预测的猪ID。在写提交文件之前必须先确认测试图像列表的顺序和文件名与官方给定的一致千万不能使用os.listdir的默认顺序因为不同操作系统的目录遍历顺序不同。字段示例说明idtest/001.jpg与官方test_list完全一致label128预测的猪ID必须是字典映射后的值一个稳妥的生成方式with open(test_list.txt, r) as f: test_paths [line.strip() for line in f if line.strip()] with open(result.csv, w) as out: out.write(id,label\n) for path in test_paths: img Image.open(os.path.join(data/test, path)) label predict_with_tta(model, img) out.write(f{path},{label}\n)这里的test_list.txt应该是从压缩包内固定文件解压出来的而不是自己扫描生成避免漏掉某些图片或混入隐藏文件。5.2 常见失败模式与排查路径很多人在运行这套源码时会在提交阶段发现预测结果全是一个类或者准确率极低。通常有三个原因一是验证集和训练集的ID划分有泄漏导致验证分数虚高而测试分数崩掉二是测试图像没有做和训练一致的标准化比如ImageNet的mean和std直接用错三是最后提交时标签映射表错位比如model输出的类别索引和原始猪ID的字典顺序不一致。排查方法是随机检查50张预测结果把图片和预测的ID打印出来靠人眼判断模型是否学到了有效特征。5.3 快速复现的关键路径解压zip后建议先小规模验证取10类猪、每类5张图跑通fitune.py、train.py、test.py、submit.py全流程确认运行时间、显存占用和输出格式。之后再扩大到全量数据。小规模验证时把epochs设为1即可目标不是精度而是确保每个脚本无报错。针对zip源码包本身解压后建议用find . -type f -name *.py | xargs wc -l统计每个脚本的行数快速了解代码规模。如果发现fitune.py和train.py中有大量重复的模型定义不要急着重构先把训练流程跑通再考虑合并。最后一个实用技巧在训练结束后用torch.jit.script或者torch.onnx.export把模型导出为ONNX再用onnxruntime做推理测试。这样既能对比TensorRT加速效果也能绕开PyTorch版本不一致导致的加载问题对后续部署到比赛服务器或工业摄像头都是很有价值的收尾操作。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门