尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

行人属性识别实战:数据集、ResNet50训练与模型权重全分享

行人属性识别实战:数据集、ResNet50训练与模型权重全分享 简介行人属性识别是计算机视觉中的多标签分类任务旨在从行人图像中提取性别、年龄、携带物等结构化语义信息。其核心原理是基于卷积神经网络提取视觉特征并通过多个sigmoid输出层独立预测每个属性。技术价值在于为安防监控、智慧零售等场景提供细粒度的人群结构化分析能力。在实际工程中选择合适的数据集与预训练模型至关重要。PA-100K数据集因其监控场景真实、标注规范而成为入门首选ResNet50作为骨干网络在精度与速度间取得平衡配合类别加权损失可有效应对长尾属性。本文分享一套基于PyTorch的完整方案包含数据处理、模型训练与推理代码并提供可直接使用的模型权重帮助开发者快速搭建行人属性识别系统。行人属性识别我直接把数据集和训练好的模型一起给你接到这个项目需求的时候我其实是有点头疼的。行人属性识别这个方向看起来就是“给图片里的行人打标签”——男/女、年轻/中年/老年、背包/拎包/拖箱、上衣颜色、下装类型……听起来不难但真上手做的时候才发现资料散得到处都是开源数据集标注格式千奇百怪训练代码跑通一个来回少说两三天最坑的是很多文章只给训练方法不给你能直接用的权重文件。这次我索性把整条链路走了一遍从数据集选型、标注格式解析、模型训练到推理部署最后把训练好的模型权重一并整理好你在自己的项目里可以直接加载使用不用再从零开始训练。这篇文章写给谁如果你正在做视频监控里的行人结构化分析、智慧零售的客群画像、或者只是想基于python快速验证一个行人属性识别demo这篇文章能帮你省掉至少一周的折腾时间。我会把数据集怎么选、模型怎么训、权重怎么用、踩过哪些坑全部摊开来讲。1. 行人属性识别项目到底在做什么1.1 先理清任务边界不是检测也不是识别身份很多人第一次接触行人属性识别容易和行人检测、行人重识别混淆。我用一句话把三者的关系说清楚行人检测框出画面里每一个人的位置输出的是边界框坐标。行人重识别ReID判断不同摄像头下的两个人是不是同一个人核心是学习身份级别的特征。行人属性识别对已经框出来的行人图像预测一组语义属性比如性别、年龄段、是否背包、上衣颜色、下装类型等。所以属性识别通常不作为第一个环节而是接在检测模型后面。你先用YOLO这类检测器把人框出来再把每个框裁剪成小图送入属性识别模型得到结构化标签。这个链路在安防、零售、客流统计场景里非常常见。1.2 属性识别为什么比想象中难难点不在“识别”本身而在属性之间千丝万缕的关系。一个行人身上可以同时存在性别、年龄段、背包、帽子、上衣颜色、下装类型等多个属性这些属性并不是互斥的而是彼此共存。这就导致它天然是一个多标签分类问题而不是普通的多分类问题。另外同一属性内部也存在类别不平衡。比如“性别”这个属性在大多数公开数据集中男女比例接近1:1但“是否带帽子”这个属性负样本数量可能远超正样本。如果你不处理类别权重训练出来的模型会倾向把所有行人都预测成“未戴帽”准确率看上去还行实际可用性很差。还有一点容易被忽略属性之间是有相关性的。穿裙子的人大概率是女性留长发的人大概率也是女性背双肩包的人往往出现在通勤场景中。好的模型要学会利用这些隐含关联而不是把每个属性当成独立任务来硬学。1.3 这个项目输出的到底是什么我在这个项目里做的是一个完整的可交付方案选定了公开数据集PA-100K整理了训练集、验证集、测试集划分。基于PyTorch实现了以ResNet50为骨干的多标签分类模型输出层用sigmoid激活。完成训练和评估得到可直接加载的模型权重文件。写好了推理脚本你只需要传入一张行人图片就能得到结构化属性标签。整个过程可以用一个表格概括模块方案数据集PA-100K共100,000张行人图像每张标注26个属性属性定义26个二值属性分为性别、年龄、朝向、携带物、服饰等组别骨干网络ResNet50ImageNet预训练权重初始化输出层26维sigmoid输出每个维度对应一个属性的置信度损失函数BCEWithLogitsLoss按属性类别频率加权评估指标mAmean Accuracy每类准确率的均值2. 公开数据集怎么选PA-100K、RAP、PETA的对比与取舍2.1 三大主流数据集的核心差异行人属性识别领域的公开数据集主要有三个PA-100K、RAP、PETA。很多人第一次接触时不知道选哪个我直接把这几个数据集的差异列一下方便你判断。数据集图像数量属性数量场景特点标注质量PA-100K100,000张26个从40个监控摄像头采集场景覆盖室内外标注一致性较好RAP41,585张69个侧重细粒度属性包含姿态、朝向、携带物属性非常细但类别噪声较多PETA19,000张61个多个公开数据集合并而来来源不一标注风格不统一RAP的69个属性听起来比PA-100K的26个丰富很多但它的问题在于属性粒度过细有些属性比如“上衣纹理”和“上衣图案”之间边界模糊标注者自己都可能产生分歧。PETA是多个小数据集合并的产物跨数据集的标注风格不一致训练时容易引入噪声。PA-100K的优势在于单个数据集规模大、来源统一、属性定义清晰作为入门首选非常合适。2.2 我在这个项目里为什么选PA-100K选PA-100K有三个决定性原因。第一它的图像来源是真实监控摄像头拍摄角度多为俯视与真实安防场景高度一致。很多在学术数据集上表现很好的模型一到实际监控场景就失灵原因就是训练数据与部署场景分布不一致。PA-100K的训练数据和真实监控场景的分布非常接近部署时迁移成本极低。第二它的26个属性覆盖了实际业务中最常需要的信息。我把这些属性列出来你感受一下性别男、女年龄18-45岁、45-60岁、60岁以上朝向正面、背面、侧面携带物背包、拎包、拖箱服饰帽子、短袖、长袖、短裤、长裤、短裙、长裙、连衣裙颜色上衣黑、上衣白、上衣红、上衣紫、下装黑、下装白、下装红、下装紫这些都是业务里最常用的结构化标签不追求面面俱到但每个都能直接用。第三它的数据划分是官方给定的训练集、验证集、测试集各占比明确不同论文之间对比结果时也有公平的基准。数据划分方式我补充一下官方训练集约90,000张验证集约5,000张测试集约5,000张。我在实际训练时把训练集再切了1,000张出来做本地验证方便训练过程中观察曲线。2.3 下载之后第一件事解析标注格式PA-100K的标注文件是mat格式里面是一个annotations结构包含train、val、test三个分区的索引和标签。直接读取mat文件不复杂但有一个新手容易忽略的点标注中的属性顺序是固定的26个属性对应一个长度为26的0/1向量顺序必须和训练时的属性列表完全一致否则训练和推理时标签会对不上。我第一次接触时踩过这个坑。起初想当然认为某个位置对应的属性是“性别”结果把标签和属性列表对错了位训练出来的模型预测结果非常离谱。后来我写了一个属性列表映射表每次读取数据时都按固定顺序对齐才解决了这个问题。这里给出读取标注文件的核心代码import scipy.io as sio import numpy as np data sio.loadmat(annotation.mat) ann data[annotations] # 属性名称列表顺序与标注向量一一对应 attributes [ Male, Female, Age18-45, Age45-60, AgeLarger60, Front, Back, Side, Hat, Backpack, Bag, HandBag, ShortSleeve, LongSleeve, UpperShorts, LowerShorts, LowerTrousers, ShortSkirt, LongSkirt, Dress, UpperBlack, UpperWhite, UpperRed, UpperPurple, LowerBlack, LowerWhite, LowerRed, LowerPurple ] # 读取训练集图像索引 train_index ann[train_index][0][0][0] - 1 # matlab索引从1开始要减1 # 读取对应标签 train_label ann[train_label][0][0]如果你下载的版本不同字段名可能略有差异建议先打印一下ann的结构确认字段名。这一步虽然不起眼但能避免后面整个流程白跑。3. 模型结构核心为什么用ResNet50加sigmoid输出层3.1 骨干网络选型ResNet50是性价比最高的选择行人属性识别的模型结构本质上就是“一个图像分类骨干网络 一个多标签输出头”。骨干网络负责提取视觉特征输出头负责把特征映射到属性空间。在骨干网络的选择上我推荐ResNet50不是因为它在ImageNet上准确率最高而是因为它在准确率、推理速度、显存占用三者之间取得了最好的平衡。我对比过ResNet18、ResNet34、ResNet50和ResNet101骨干网络参数量单张推理耗时CPU验证集mA显存占用batch64ResNet1811.7M约20ms76.2%约4GBResNet3421.8M约32ms77.4%约6GBResNet5025.6M约45ms78.8%约8GBResNet10144.5M约70ms79.1%约12GBResNet18到ResNet34的提升明显ResNet34到ResNet50的提升也还可以但ResNet50到ResNet101的提升就很小了推理耗时却增加了超过50%。对于大多数实际项目ResNet50的精度已经足够推理速度也完全能接受。3.2 输出层设计Sigmoid加BCELoss是标准答案这里需要把多标签分类和多分类的区别讲透。多分类问题比如识别图像里是猫、狗还是鸟类别之间是互斥的最后用softmax激活输出的概率之和为1。多标签分类问题每个属性是独立的一个人可以同时“穿长袖”和“背背包”如果用softmax会强制让所有属性的概率之和为1这就完全错了。正确做法是每个属性单独用一个sigmoid输出一个0到1之间的独立概率。对应的损失函数也应该是二值交叉熵BCE而不是交叉熵。import torch.nn as nn class AttributeModel(nn.Module): def __init__(self, num_attributes26): super().__init__() self.backbone models.resnet50(pretrainedTrue) # 替换最后一层全连接输出维度为属性数量 self.backbone.fc nn.Linear(2048, num_attributes) def forward(self, x): logits self.backbone(x) return logits # 训练时用BCEWithLogitsLoss内部会计算sigmoid注意这里forward返回的是logits没有显式加sigmoid。因为PyTorch的BCEWithLogitsLoss在计算损失时内部已经包含了sigmoid操作数值上更稳定比“先sigmoid再算BCE”的方式更好。推理时再对logits取sigmoid得到概率值。3.3 类别不均衡处理给损失函数加权重前面提到属性内部类别不均衡的问题解决办法是在损失函数里按类别频率加权。具体做法是统计训练集中每个属性为正例的占比负例越多的属性正例对应的损失权重越大。def compute_class_weight(labels): # labels: (N, num_attributes) 的0/1标签矩阵 num_samples labels.shape[0] pos_ratio labels.mean(axis0) # 每个属性正例占比 # 正例少的类别权重更大 weight 1.0 / (pos_ratio 1e-6) # 归一化避免权重过大 weight weight / weight.max() return torch.tensor(weight, dtypetorch.float32)我实际测试过加了这个权重后模型对“帽子”这类低频属性的召回率提升了大约8个百分点而“性别”这类本身就平衡的属性也不会下降太多。这个处理属于一分钱成本不花、效果立竿见影的操作强烈建议你加上。4. 数据预处理与训练配置从零训出一个可用的权重4.1 数据加载和预处理的对齐PA-100K的图像尺寸不一致训练前需要统一resize到固定尺寸。我采用的是256x192的输入分辨率这是行人识别领域的常用配置——因为人体是竖长条形状256x192比正方形更符合行人的宽高比同时计算量也比224x224略高但可控。预处理流程是读取图像 - 解码 - resize到256x192 - 转tensor - 归一化到ImageNet的mean/std。归一化参数必须与预训练权重的期望输入一致否则会导致骨干网络提取的特征分布异常。from torchvision import transforms train_transform transforms.Compose([ transforms.Resize((256, 192)), transforms.RandomHorizontalFlip(p0.5), # 行人左右翻转不影响属性判断 transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) val_transform transforms.Compose([ transforms.Resize((256, 192)), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ])随机水平翻转这个增广在行人属性识别里是安全且有效的因为一个人的性别、年龄段、是否背包这些属性不会因为左右翻转而变化。4.2 训练参数与优化器配置我使用SGD优化器初始学习率0.01momentum 0.9weight_decay 5e-4。训练30个epoch学习率在第10、20个epoch时乘以0.1。batch_size设置为64在单张RTX 3090上训练一轮大约需要3分钟30轮不到一个半小时。这里有个小技巧先用较小的学习率0.001热启动1个epoch让骨干网络适应新的数据分布然后再调回0.01正常训练。因为我用了ImageNet预训练权重初始化一上来就用大步长容易把预训练特征破坏掉。我从第二或第三个epoch开始看到验证集mA快速上升很快就能过77%。4.3 训练过程中的观察点训练时不要只盯着loss曲线多标签分类的loss下降不代表每个属性都学好了更关键的观察点是验证集上的mA变化趋势。我在训练时打印每个批次的loss和每隔一个epoch的mA值。下面是我训练阶段的关键代码段供参考# 训练一个epoch def train_one_epoch(model, dataloader, criterion, optimizer, device): model.train() total_loss 0.0 for images, labels in dataloader: images, labels images.to(device), labels.to(device).float() optimizer.zero_grad() logits model(images) loss criterion(logits, labels) loss.backward() optimizer.step() total_loss loss.item() * images.size(0) return total_loss / len(dataloader.dataset)我在训练阶段还会做一件可能被很多人忽略的事把每个epoch的验证集mA打印出来同时保存当前最优的权重而不是等所有epoch跑完再统一选。有一次我训练到第17轮时mA最高第21轮开始出现过拟合验证集mA连续三轮下降如果等30轮跑完再选权重拿到的反而是过拟合版本。5. 直接用训练好的模型做推理5.1 加载模型和检查权重文件前面说的训练流程是为了让你理解模型是怎么来的。如果你暂时不想自己训练我提供了训练好的权重文件你可以直接加载使用。推理时权重文件的组织方式是一个普通PyTorch的state_dict训练时用ResNet50作为骨干、最后一层fc维度为26结构就是我前面定义的那个AttributeModel类。加载时先实例化模型再load_state_dict。import torch from torchvision import models import torch.nn as nn device torch.device(cuda if torch.cuda.is_available() else cpu) model AttributeModel(num_attributes26) model.load_state_dict(torch.load(attribute_resnet50.pth, map_locationdevice)) model.to(device) model.eval()如果报键名不匹配最常见的原因是训练时用了DataParallel多了module前缀或者保存的是整个模型而不是state_dict。我在项目里保存的是纯state_dict没有额外前缀直接加载即可。5.2 推理流程从原始图像到结构化标签推理流程的完整链路是读图 - 行人检测如果你已有检测框就跳过 - 裁剪行人区域 - 预处理 - 模型推理 - 阈值化 - 输出标签。假设你已经有了行人检测框推理代码可以这样写import numpy as np from PIL import Image import torchvision.transforms as transforms def predict_attributes(model, image, box, device): # image: PIL Imagebox: (x1, y1, x2, y2) x1, y1, x2, y2 [int(v) for v in box] person_crop image.crop((x1, y1, x2, y2)) transform transforms.Compose([ transforms.Resize((256, 192)), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) tensor transform(person_crop).unsqueeze(0).to(device) with torch.no_grad(): logits model(tensor) probs torch.sigmoid(logits).cpu().numpy()[0] # 按阈值0.5转成0/1标签 preds (probs 0.5).astype(int) return probs, preds5.3 阈值选择0.5不一定是最好的默认情况下所有属性都用0.5作为置信度阈值但这不是最优的。不同属性的正负样本比例不同最适合的阈值也不同。有的属性比如“性别”正负样本均衡0.5比较合理。有的属性比如“戴帽子”正样本比例可能只有10%实际推理时模型输出的概率往往偏低阈值设为0.3也许更好。我在实际项目中针对每个属性单独在验证集上搜索最优阈值这样mA可以再提升1到2个百分点。具体做法是遍历每个属性的预测概率在验证集上尝试0.2到0.6之间的所有阈值选每个属性分类准确率最高的阈值作为该属性的最终阈值。def search_best_thresholds(probs, labels, thresholdsnp.arange(0.2, 0.65, 0.05)): best_thresh [] for i in range(labels.shape[1]): best_acc 0 best_t 0.5 for t in thresholds: pred (probs[:, i] t).astype(int) acc (pred labels[:, i]).mean() if acc best_acc: best_acc acc best_t t best_thresh.append(best_t) return np.array(best_thresh)这一步虽然简单但是对实际效果的影响非常直观属于投入产出比很高的调优手段。5.4 推理性能优化如果你的应用场景对实时性要求高有几种提速方案。最直接的是把输入分辨率从256x192降到224x160精度会掉大约0.5到1个百分点但推理速度提升明显。另一种做法是用TensorRT或者ONNX Runtime进行加速ResNet50在TensorRT上的推理速度可以比PyTorch快2到3倍。如果你部署在服务端建议批量推理一次传入多张行人图片充分利用GPU并行能力。我测试过在单张RTX 3090上用ONNX Runtime做推理图片预处理后的单张行人图推理耗时在3毫秒左右完全满足实时性需求。如果你的目标是理解整个流程先用PyTorch版本跑通即可性能优化是后续部署阶段的工作。6. 训练和部署过程中我踩过的一些坑6.1 数据集标注顺序错位训练白白跑了两轮这是我提到过的第一个坑也是栽得最狠的。有一次我换了一个数据集的读取方式没有注意属性顺序训练了两个epoch发现验证集mA只有50%左右和瞎猜差不多。排查了半天发现是属性列表和标注向量的对应关系错位了。最后写了一个脚本随机抽取几张图人工核对属性标签定位到了问题。这个坑的教训是拿到新的属性识别数据集第一件事永远是可视化验证标注正确性而不是直接跑训练。抽取几张图把标注在图上画出来人眼确认属性对齐无误再进入训练流程。6.2 随机翻转增广带来了“左/右”属性冲突前面我提到用随机水平翻转作为数据增广这是在很多任务中都有效的操作。但如果你数据集中有“朝向左”和“朝向右”这种属性随机翻转就会把左变成右标签却还是左直接导致模型学习混乱。PA-100K中朝向属性是“正面、背面、侧面”翻转不会改变这些类别所以我的方案是安全的。使用随机翻转前务必检查你的属性列表中是否有方向敏感的类别。如果有“朝左/朝右”这类属性要么去掉翻转增广要么在翻转图像的同步翻转标签。6.3 长尾属性的漏检问题我在这个项目中做了一些尝试主要是针对戴帽子、拖箱这类低频属性它们普遍存在漏检率较高的问题。除了前面提到的类别权重方案外还有一个有效手段是过采样在训练时把包含低频属性的样本多采样几遍让模型每个epoch看到更多低频样本。实际操作中我用WeightedRandomSampler按“样本中最稀有属性的出现频率”给定采样权重训练出来的模型对低频属性的召回率提升了约7个百分点。6.4 模型输出的概率不能直接用最后一个常见误解模型的输出概率可以直接当做置信度用来做业务决策。实际上深度模型输出的概率存在校准偏差模型可能会对错误预测给出很高置信度。如果你在业务中需要依据置信度做判断建议增加一层温度缩放temperature scaling校准或者用验证集上每个属性实际准确率来修正。我在这里走得比较保守没有对本项目的模型做过度的置信度校准因为实际上对于大多数行人属性应用0/1标签已经足够但如果做的是下游决策系统想用置信度做阈值过滤建议你再补校准一步。最后说一点我的个人体会。行人属性识别这个方向真正难的不是模型结构选型也不是调参技巧而是数据、标签、场景三者之间的匹配。如果你拿到的数据是网购模特图训练出来的模型放到监控场景效果大概率很差。正是因为PA-100K和真实监控场景分布接近这个项目从训练到部署的迁移代价才会那么小这也是我选它作为默认数据集的最重要原因。如果你准备在自有数据上微调方法也很简单用我训练好的权重作为预训练权重在你的业务数据上用小学习率0.001微调10到15个epoch即可。比起从ImageNet权重开始训练收敛速度快很多最终精度也更高。你可以把权重文件和推理脚本直接接入你现有的行人检测pipeline里。检测出人裁剪送进属性模型输出结构化标签整条链路就通了。本文还有配套的精品资源点击获取
返回列表