ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

老鼠动作行为图像分类:YOLOv5训练与行为学统计数据集解析

老鼠动作行为图像分类:YOLOv5训练与行为学统计数据集解析 简介一套面向动物行为分析、实验医学与图像分类任务的老鼠动作识别数据集包含焦虑、身体抽搐、惊厥、探索移动、伸展肢体、摇头、中度呼吸困难、抓挠、重度呼吸困难、洗脸等10种典型行为类别。数据已按训练集与验证集组织可直接用于yolov5等分类模型的训练与评估。资源包共2000个文件其中1998张jpg图片、1个Python可视化脚本、1个json类别字典文件压缩包大小63.76MB目录结构清晰。可视化脚本可随机抽取4张图片展示并保存到当前目录便于核对标签与样本质量。样本命名包含时间、实验编号、行为类型与时间点有助于定位和分析原始实验片段。目前已有46人学习下载适合计算机视觉初学者、行为识别研究者以及需要标准分类数据集进行模型验证的开发者使用。1. 老鼠动作行为图像分类数据集从25m文件名到10类行为标签一批来自焦虑模型的老鼠行为视频切片文件名带有动物编号、行为名和时长比如25m_240905_Num10_HBO9_anxiety(cage circling)_6_5.0s.jpg。这个资源已经把它整理成了 YOLOv5 分类格式的数据集train 2300 张、val 1000 张覆盖甩头、理毛、呼吸困难等 10 类动作并附带类别字典文件和图像分类用的 Python 可视化脚本。它的价值不只是省掉手工整理图片的时间更在于文件命名保留了时间轴信息训练完还能直接做行为学统计。适合做动物行为识别、yolov5训练自己的数据集或者想熟悉图像分类数据集组织方式的工程师。2. 目录约定与类别字典文件YOLOv5分类格式的数据集构建2.1 train/val目录结构与ImageFolder约定YOLOv5 分类任务对目录的约定很直接它不看单独标注文件只看目录名。数据集根目录下必须有train/和val/每个子目录名代表一个类别图片按类别放进对应文件夹。这个资源里的 data 目录正是这种结构路径作用数量data/train/anxiety/焦虑转圈训练样本train 总量 2300data/train/head_shaking/甩头训练样本分布在各子类data/train/washing_face/洗脸训练样本分布在各子类data/val/...10 类验证样本val 总量 1000这种结构可以直接被torchvision.datasets.ImageFolder读取。ImageFolder 会按目录名字母序生成class_to_idx映射比如anxiety对应 0body_twitching对应 1这个索引顺序和后续训练生成的预测标签必须一一对应否则画混淆矩阵时类别名会错位。要注意的一个隐蔽问题是文件名里出现了anxiety(cage circling)这种带括号的写法但类别字典里写的是anxiety。实际目录名到底是anxiety(cage circling)还是anxiety要以解压后ls data/train的输出为准。如果两者混用ImageFolder 会把它当成两个不同类别导致类别数变成 11训练时 loss 看起来正常但验证集类别数量和字典对不上。2.2 类别字典文件与label映射资源里提供的类别字典是一个 10 类别的 JSON 格式文件内容等价于{ 0: anxiety, 1: body_twitching, 2: Convusion, 3: exploratory_moving, 4: extend_limbs, 5: head_shaking, 6: moderate_dyspnea, 7: scratching, 8: severe_dyspnea, 9: washing_face }注意这里键是字符串数字2对应的Convusion并不是标准英文拼写正确拼法应该是Convulsion但既然原始标注和目录用它就保持一致不要训练到一半去“修正”拼写否则模型输出和字典对不上。加载时最好把键转成 intimport json with open(class_dict.json, r, encodingutf-8) as f: raw_dict json.load(f) class_dict {int(k): v for k, v in raw_dict.items()} print(class_dict[0]) # anxiety这段代码的逻辑是JSON 读出来键全是字符串而模型 argmax 出来的是整数索引不转类型直接class_dict[pred_idx]会报 KeyError。实际推理脚本里建议把这段封装成load_class_dict(path)函数train 和 val 阶段复用避免在多个文件里重复定义字典。2.2.1 检查类别数与目录数的一致性训练前先做一次目录一致性检查比训到一半发现类别错位要省时间得多import os dict_values set(class_dict.values()) train_dirs set(os.listdir(data/train)) val_dirs set(os.listdir(data/val)) print(字典有但train缺, dict_values - train_dirs) print(train有但字典缺, train_dirs - dict_values) print(val与train目录差集, train_dirs ^ val_dirs)这段脚本分别计算三个差集字典里定义了但目录里缺失的类、目录里存在但字典里没有的类、train 和 val 目录名不一致的部分。任何一个差集不为空都要先解决再训练。尤其是最后一个train 和 val 的目录名只要有一个字母大小写不同YOLOv5 在验证阶段会直接按 train 的类别顺序取目录导致验证标签错乱。2.3 文件命名中的编码信息与划分依据看几个典型文件名25m_240905_Num10_HBO9_anxiety(cage circling)_6_5.0s.jpg25m_240904_Num5_HBO1_head shaking_2_1.0s.jpg25m_240904_Num5_HBO2_washing face_8_7.0s.jpg可以拆出几段信息25m是录制计划编号240905是日期Num10_HBO9是动物和笼位编号中间部分是行为名最后的_6_5.0s表示第 6 个片段、持续 5.0 秒。还有一些文件名形如_slice_1_2_1.0s表示从片段 1 中按位置 2 切出 1.0 秒的短切片。这些字段不只是给人看的。做行为识别时同一只老鼠的连续切片如果同时出现在 train 和 val会让验证集准确率虚高因为模型看到过同一只个体非常相似的姿态。严谨的做法是按动物编号划分数据集而不是按图片随机划分。2.3.1 提取动物编号避免数据泄露可以用正则把动物编号从文件名里抽出来检查 train 和 val 是否有交集import re from pathlib import Path def animal_key(path: Path): name path.stem m re.search(rNum\d_HBO\d, name) return m.group(0) if m else name train_animals {animal_key(p) for p in Path(data/train).rglob(*.jpg)} val_animals {animal_key(p) for p in Path(data/val).rglob(*.jpg)} overlap train_animals val_animals print(跨数据集动物编号数量, len(overlap))如果 overlap 数量不是 0说明官方划分可能存在个体级别泄露。此时有两种处理要么接受这个划分只把它当成算法 demo要么自己按动物编号重新划分保证同一个动物只出现在一个集合里。对论文或正式实验后者是必须的因为行为学模型要验证的是对新个体的泛化能力而不是对同一只老鼠的帧级记忆。2.4 转换到其他分类框架如果不想用 YOLOv5而是用 timm、MMClassification 或纯 PyTorch这个目录结构可以直接复用。需要 CSV 标签文件时一行命令就能生成find data/train -name *.jpg | awk -F/ {print $0, $3} train_labels.csv这里awk的分隔符是/$0是完整路径$3是 train 下的第一级子目录名也就是类别名。生成的 CSV 第一列是图片路径第二列是类别名。之后用 pandas 读进来再按类别字典映射成整数标签即可。3. 随机4张数据可视化脚本Matplotlib网格与批量保存3.1 脚本的定位与调用方式资源附带的 Python 可视化脚本核心功能是从数据目录随机取 4 张图片展示成 2x2 网格并保存到当前目录。它主要解决两个问题第一确认图片内容真的对得上类别名第二快速检查是否存在模糊、截断、背景干扰严重的样本。这些问题是行为图像数据集里最常见的质量风险直接看指标不一定看得出来。脚本不是一个完整的 GUI 程序而是一个命令行脚本。常见实现是接收--data、--dict、--out三个参数--data指定从 train 还是 val 目录采样默认data/train--dict类别字典文件路径用于把目录名和中文注释对应起来--out输出图片文件名默认sample_grid.jpg3.2 代码实现与保存路径import os import json import random import argparse import matplotlib matplotlib.use(Agg) import matplotlib.pyplot as plt from PIL import Image def load_class_dict(path): with open(path, r, encodingutf-8) as f: return {int(k): v for k, v in json.load(f).items()} def sample_random_images(root, n4): classes [d for d in os.listdir(root) if os.path.isdir(os.path.join(root, d))] class_name random.choice(classes) class_dir os.path.join(root, class_name) images random.sample(os.listdir(class_dir), n) return class_name, [os.path.join(class_dir, i) for i in images] def visualize(class_name, image_paths, save_path): fig, axes plt.subplots(2, 2, figsize(10, 10)) for ax, img_path in zip(axes.flatten(), image_paths): img Image.open(img_path) ax.imshow(img) ax.set_title(f{class_name}: {os.path.basename(img_path)}) ax.axis(off) plt.tight_layout() plt.savefig(save_path, dpi200, bbox_inchestight) plt.close(fig) print(fsaved to {save_path}) if __name__ __main__: ap argparse.ArgumentParser() ap.add_argument(--data, defaultdata/train) ap.add_argument(--dict, defaultclass_dict.json) ap.add_argument(--out, defaultsample_grid.jpg) args ap.parse_args() load_class_dict(args.dict) cls, paths sample_random_images(args.data) visualize(cls, paths, args.out)关键逻辑是sample_random_images里的两步先从根目录随机选一个类别文件夹再从该文件夹随机抽 4 张图。这里用了random.sample而不是random.choice重复 4 次避免抽到重复图片。plt.savefig里dpi200是为了让图片放大后还能看清老鼠姿态bbox_inchestight会自动裁掉多余白边保存的文件更紧凑。末尾plt.close(fig)容易被忽略但在批量跑多轮采样时非常重要。如果不关闭当前 figure内存会一直累积跑几百轮后 matplotlib 会明显变慢甚至卡死。3.3 中文路径与Agg后端这个数据集本身是英文但使用者的工程路径可能包含中文。Matplotlib 在标题包含中文时会变成方块需要在导入 pyplot 前设置字体import matplotlib matplotlib.use(Agg) # 无GUI服务器环境必须放在pyplot之前 import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei, DejaVu Sans] plt.rcParams[axes.unicode_minus] Falseuse(Agg)的作用是让 matplotlib 不依赖系统图形界面直接渲染到文件。很多人在远程服务器上跑可视化脚本报_tkinter.TclError: no display name and no $DISPLAY environment variable就是因为默认后端是 TkAgg。把 Agg 设置放在import pyplot之前就能稳定避坑。4. 基于YOLOv5的分类训练模型选择与CLI参数4.1 模型选型YOLOv5-cls与预训练权重YOLOv5 仓库里除了目标检测还提供了一套分类模型文件名为yolov5s-cls.pt、yolov5m-cls.pt等。这些模型在 ImageNet 上预训练过迁移到这个老鼠行为数据集上收敛速度远快于从头训练 ResNet。2300 张训练图属于小样本场景用yolov5n-cls.pt或yolov5s-cls.pt就足够不需要上yolov5l-cls.pt或更大模型。更大的模型在小数据集上容易过拟合训练时间却成倍增加。这里值得强调YOLOv5 分类模型和检测模型使用同一套训练框架但入口脚本完全不同。分类训练用classify/train.py检测用train.py很多人把检测的--data coco.yaml习惯带到分类任务结果启动报错。4.2 训练命令与关键参数假设数据集放在/datasets/mouse_behaviorYOLOv5 仓库在/workspace/yolov5训练命令cd /workspace/yolov5 python classify/train.py \ --model yolov5s-cls.pt \ --data /datasets/mouse_behavior \ --epochs 50 \ --batch-size 32 \ --img 224 \ --device 0 \ --project runs/classify \ --name mouse_behavior \ --class-weights True参数作用如下参数建议值说明--modelyolov5s-cls.pt指定预训练权重自动下载--data数据集根目录必须包含 train 和 val 子目录--epochs50小数据集 50 轮足够多了易过拟合--batch-size32显存不足可降到 16 或 8--img224输入分辨率越大细节越多但更慢--device0用 GPU 训练CPU 设为 cpu--class-weightsTrue按类别样本数反比加权损失--data指定的不是 yaml 文件而是数据集根目录。YOLOv5 分类训练会在内部自动读取 train 和 val 子目录不需要额外写 data.yaml。这是分类任务和检测任务最容易被混淆的地方。4.2.1 如何应对类别不平衡行为数据天然不均衡焦虑转圈这类高发行为样本多extend_limbs这类低频行为样本少。直接训练时模型会倾向把不确定样本预测成高发类别。--class-weights True会让损失函数按每个类别样本数的反比加权少数类样本的 loss 贡献更大从而提升低频行为召回率。代价是总体准确率可能会微降但对行为学研究来说漏报一个severe_dyspnea的风险远比多报一个普通动作严重。如果还想进一步控制数据增强可以修改 YOLOv5 的增强超参文件。分类训练默认开启随机翻转和颜色抖动但对老鼠行为识别水平翻转会镜像左右不对称的甩头动作颜色抖动会把视频截图的色偏抹掉导致模型学到错误的鲁棒性。建议在data/hyps/hyp.scratch-low.yaml里做如下调整hsv_h: 0.0 hsv_s: 0.0 hsv_v: 0.2 fliplr: 0.0 flipud: 0.0fliplr和flipud分别控制水平和垂直翻转行为识别场景不建议开启。hsv_h是色调扰动设为 0 避免颜色迁移hsv_s是饱和度扰动同样设为 0hsv_v保留 0.2只做亮度抖动模拟不同光照条件。这样模型学到的是姿态和纹理而不是颜色片段。5. 模型评估与行为切片误差分析5.1 用验证集生成混淆矩阵训练完成后用验证集评估模型python classify/val.py \ --data /datasets/mouse_behavior \ --weights runs/classify/mouse_behavior/weights/best.pt \ --save-txt--save-txt会保存每个验证样本的预测结果方便后续做细粒度分析。只看 top1 准确率远远不够行为识别更关心哪些类互相混淆。把预测结果和真实标签导出成 npy 后用 sklearn 生成分类报告import numpy as np from sklearn.metrics import classification_report y_true np.load(val_labels.npy) y_pred np.load(val_preds.npy) with open(class_dict.json, r, encodingutf-8) as f: import json class_names list(json.load(f).values()) print(classification_report(y_true, y_pred, target_namesclass_names))这里的classification_report会分别输出每个类别的 precision、recall 和 f1-score。对行为数据集recall比precision更值得优先看recall 低意味着这种动作经常被漏掉实验统计时行为时长会被低估。5.2 易混淆行为对与切片时长从行为学角度看body_twitching和moderate_dyspnea姿态接近anxiety(cage circling)和exploratory_moving都是持续移动最容易互相混淆。建议从混淆矩阵中把错误样本单独抽出来看grep -oP [\d.]s\.jpg runs/classify/mouse_behavior/val_preds.txt | sort | uniq -c这行命令统计验证集文件名中不同时长切片的数量配合每个时长段的准确率可以判断模型是否对短切片更差。1.0 秒的切片通常只包含一个动作姿态5.0 秒的切片里可能发生了从甩头到理毛的过渡中间帧会被模型标成更稳定的类别。如果发现短切片准确率明显偏低可以在训练时增加 1.0 秒切片的比例或者对预测序列做时间平滑。5.3 阈值调优与人复核队列默认分类器取概率最大的类别作为 predict 结果但在severe_dyspnea这类安全相关行为上漏报代价更高。常见做法是对 softmax 输出设置置信度阈值低于阈值的样本进入人工复核队列import torch import torch.nn.functional as F probs F.softmax(logits, dim1) # logits shape: [batch, num_classes] max_prob, pred probs.max(dim1) needs_review max_prob 0.7 # 阈值可调阈值 0.7 表示模型对预测结果没有足够信心时不直接输出标签而是交给人工判断。max_prob是每个样本最高类别的概率needs_review为 True 的样本可以单独存到一个目录。通过验证集 precision-recall 曲线调节这个阈值如果漏报严重把阈值调高到 0.8会让更多低置信度样本进入复核队列减少错误标签流入行为统计。6. 行为学统计从帧级预测到行为时长聚合6.1 文件名中时间信息的解析图像分类模型只解决“这一张是什么行为”实验要的是“这只老鼠焦虑了多久”。文件名里保留的_6_5.0s和_slice_1_2_1.0s就是时间轴信息需要解析出来import re def parse_time_info(filename: str): if _slice_ in filename: parts re.findall(r_(\d)_([\d.])s, filename) idx, duration parts[-1] if parts else (None, None) return int(idx), float(duration) if duration else None m re.search(r_(\d)_([\d.])s, filename) if m: return int(m.group(1)), float(m.group(2)) return None, None这个函数对普通文件名取_6_5.0s解析出片段序号 6 和时长 5.0 秒对slice文件名取最后一个_数字_数字s对因为 slice 前的_1_2_1.0s中真正的切片位置和时长在末尾。解析时先判断_slice_分支避免正则匹配到前面的片段序号。6.2 中值滤波与行为budget表模型对单张切片的预测会存在抖动比如连续几帧head_shaking中间蹦出一个body_twitching这通常是分类器受姿态角度影响产生的孤立错误。用中值滤波对预测序列做平滑窗口取 5能有效去除这类单点噪声from scipy.ndimage import median_filter smoothed median_filter(pred_sequence, size5)平滑后的序列按连续相同标签做游程编码再累加时长就得到一张行为预算表行为类别发生次数总时长/s占比anxiety1263.421.1%washing_face3445.215.1%head_shaking5628.09.3%这张表可以直接进入后续统计。需要留个底图像分类只做帧级判断时间聚合后丢失了动作频率和运动速度信息如果实验设计还需要转圈圈数或头部抖动频率就要回到视频光流或目标跟踪来补。本文还有配套的精品资源点击获取
返回列表