简介:一套面向动物行为分析、实验医学与图像分类任务的老鼠动作识别数据集,包含焦虑、身体抽搐、惊厥、探索移动、伸展肢体、摇头、中度呼吸困难、抓挠、重度呼吸困难、洗脸等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 总量 2300 |
| data/train/head_shaking/ | 甩头训练样本 | 分布在各子类 |
| data/train/washing_face/ | 洗脸训练样本 | 分布在各子类 |
| data/val/... | 10 类验证样本 | val 总量 1000 |
这种结构可以直接被torchvision.datasets.ImageFolder读取。ImageFolder 会按目录名字母序生成class_to_idx映射,比如anxiety对应 0,body_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,但既然原始标注和目录用它,就保持一致,不要训练到一半去“修正”拼写,否则模型输出和字典对不上。加载时最好把键转成 int:
import json with open("class_dict.json", "r", encoding="utf-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(r"Num\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.jpg
3.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", encoding="utf-8") as f: return {int(k): v for k, v in json.load(f).items()} def sample_random_images(root, n=4): 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, dpi=200, bbox_inches="tight") plt.close(fig) print(f"saved to {save_path}") if __name__ == "__main__": ap = argparse.ArgumentParser() ap.add_argument("--data", default="data/train") ap.add_argument("--dict", default="class_dict.json") ap.add_argument("--out", default="sample_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里dpi=200是为了让图片放大后还能看清老鼠姿态;bbox_inches="tight"会自动裁掉多余白边,保存的文件更紧凑。
末尾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_behavior,YOLOv5 仓库在/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参数作用如下:
| 参数 | 建议值 | 说明 |
|---|---|---|
| --model | yolov5s-cls.pt | 指定预训练权重,自动下载 |
| --data | 数据集根目录 | 必须包含 train 和 val 子目录 |
| --epochs | 50 | 小数据集 50 轮足够,多了易过拟合 |
| --batch-size | 32 | 显存不足可降到 16 或 8 |
| --img | 224 | 输入分辨率,越大细节越多但更慢 |
| --device | 0 | 用 GPU 训练,CPU 设为 cpu |
| --class-weights | True | 按类别样本数反比加权损失 |
--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是饱和度扰动,同样设为 0;hsv_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", encoding="utf-8") as f: import json class_names = list(json.load(f).values()) print(classification_report(y_true, y_pred, target_names=class_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, dim=1) # logits shape: [batch, num_classes] max_prob, pred = probs.max(dim=1) 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, size=5)平滑后的序列按连续相同标签做游程编码,再累加时长,就得到一张行为预算表:
| 行为类别 | 发生次数 | 总时长/s | 占比 |
|---|---|---|---|
| anxiety | 12 | 63.4 | 21.1% |
| washing_face | 34 | 45.2 | 15.1% |
| head_shaking | 56 | 28.0 | 9.3% |
这张表可以直接进入后续统计。需要留个底:图像分类只做帧级判断,时间聚合后丢失了动作频率和运动速度信息,如果实验设计还需要转圈圈数或头部抖动频率,就要回到视频光流或目标跟踪来补。
本文还有配套的精品资源,点击获取