资讯动态

CNN猫行为识别实战:从数据集排雷到模型训练调参全指南

发布时间:2026/10/1 3:03:29 来源:尧图企业网站定制
简介涵盖通过CNN卷积网络识别猫行为的PyTorch项目资源适合深度学习入门者与计算机视觉学习者作为图像分类实践参考。压缩包共544个文件约41.35MB主体为538张猫行为图片数据集配合3个Python脚本与3个txt文件分别用于生成数据集路径、训练模型、展示PyQt界面以及说明环境安装与标签信息。目前已有102人学习下载。项目中包含较完整的数据处理思路先将较短边补灰边转为正方形再结合随机旋转完成数据增强运行第一个脚本生成图片路径与标签文本第二个脚本读取训练集与验证集训练网络并保存模型第三个脚本提供可视化界面便于实际推理。同时附带环境依赖清单按清单安装即可运行便于按数据准备、模型训练、界面推理三阶段逐步学习与调试。1. 用 CNN 做猫行为识别难点从来不是模型把「通过 CNN 卷积网络对猫行为识别」拆开看它其实是一个标准的图像分类任务但比常见的猫狗识别多了一层麻烦猫的行为不像「品种」那样有稳定的视觉特征。「跑酷」和「炸毛」可能出现在同一只猫身上而「睡觉」和「躺尸」之间几乎没有边界。CNN 卷积神经网络擅长抓空间纹理特征但对这种类间差异小、类内差异大的问题数据质量往往比模型结构更决定上限。这个标题附带了一个图片数据集 zip 压缩包这恰恰是项目真正的入口。拿到数据集先别急着训练解压之后的第一小时决定了后面几天的训练是否白做。本文面向的是第一次或第二次做图像分类落地的开发者会带你走完「解压数据→发现问题→搭建 CNN→训练调参→定位 badcase」的完整链路每一步都给可复现的命令和参数。整篇下来你得到的不是一个玩具 Demo而是一套能应对真实数据噪声的小型视觉方案。2. 解压后的第一小时给猫行为数据集“排雷”2.1 从 zip 到可用目录先认清数据集长什么样zip 数据集的解压本身没难度麻烦的是解压之后怎么做。很多人在这一步直接把图片路径扔进model.fit()然后被各种 shape 不匹配、标签错位的问题砸到怀疑人生。我一般会先写一段脚本把解压结果完整列一遍搞清楚目录层级、图片数量、格式分布。# 解压命令注意不要解压到中文路径下 unzip cat_behavior_dataset.zip -d ./cat_data # 列出解压后的目录树深度控制在三层以内 find ./cat_data -maxdepth 3 -type d | sort # 统计各类图片数量 find ./cat_data -type f -name *.jpg | awk -F/ {print $(NF-1)} | sort | uniq -c # 检查是否有非图片文件混进来 find ./cat_data -type f ! -name *.jpg ! -name *.jpeg ! -name *.png | head -20上面的命令有几个容易忽略的点。-maxdepth 3是为了快速看清目录骨架如果数据集是train/category/xxx.jpg的三层结构三层深度刚刚好如果更深建议先看第一层和第二层别让 find 输出刷屏。统计数量时用$(NF-1)取父目录名做类别标记前提是图片确实放在类别目录下如果数据集把所有图片平铺在同一目录、用文件名前缀区分类别这一步就要改成解析文件名。提示如果unzip报「zip 伪加密」或文件损坏先用zip -T测试压缩包完整性再考虑7z x或python zipfile读取。真正需要警惕的是三种情况。第一类别目录名和实际内容对不上比如playing目录里混着睡觉的图这在人工采集数据集里极常见。第二图片格式不统一有的数据集同时混了 jpg、png、bmp 甚至 gif读图库一报错就中断整个训练流程。第三文件里还有一份labels.csv和目录结构并存的双轨标注两边标签不一致时得先定一个为准我一般以 CSV 为准因为它能承载多标签信息而目录只能表达单标签。这一轮排雷做完你对数据集的「脾气」就有数了。2.2 图片质量初检模糊图、误标记、坏图一网打尽数据集排雷的第二步是检查图片本身的可用性。CNN 卷积网络对输入图片的分辨率、通道数、完整性都有硬性要求但 zip 里的图片可不会自动满足。常见的问题包括图片尺寸从 1920x1080 到 96x96 参差不齐、部分文件实际是损坏的截断 JPEG、还有少量灰度图混在 RGB 图里。这些问题如果不在数据阶段解决训练时会以ImageDataGenerator或者tf.data的报错形式还给你。import os from PIL import Image from collections import Counter img_dir ./cat_data/train format_counter Counter() size_counter Counter() broken_imgs [] for root, _, files in os.walk(img_dir): for f in files: if not f.lower().endswith((.jpg, .jpeg, .png)): continue path os.path.join(root, f) try: with Image.open(path) as im: # 触发一次像素加载确认文件没有截断 im.load() format_counter[im.format] 1 size_counter[im.size] 1 except Exception as e: broken_imgs.append((path, str(e))) print(格式分布:, format_counter) print(尺寸分布 top10:, size_counter.most_common(10)) print(损坏图片数量:, len(broken_imgs)) for p, e in broken_imgs[:10]: print(p, e)这段代码的核心价值有两个。im.load()看起来多余但 PIL 的open()只是读取文件头真正把像素数据读入内存的是load()很多截断图片能通过open()却会在load()时抛异常。size_counter则是为后续 resize 做准备知道尺寸分布才能定合理的统一尺寸。如果发现灰度图混入可以考虑统一转成 RGB否则按单通道读入会把后续搭建的 CNN 输入层搞乱三通道模型会直接拒绝单通道输入。2.3 划分训练集和验证集不要用 sklearn 默认方式数据排雷的最后一环是划分数据集。很多教程直接写train_test_split(X, y, test_size0.2)这在猫行为识别场景下会带来一个隐患同一只猫的不同行为图片可能分布在 train 和 val 两侧导致验证集严重高估模型泛化能力。因为 CNN 卷积网络会记住猫的外貌特征来「作弊」而不是真正学习行为模式。import pandas as pd from sklearn.model_selection import train_test_split # 假设已经整理出 image_path 和 label 两列的 DataFrame df pd.read_csv(./cat_data/labels.csv) # 按图片所属的猫个体分组同一个体的图片只进 train 或只进 val cat_ids df[cat_id].unique() train_cats, val_cats train_test_split(cat_ids, test_size0.2, random_state42) train_df df[df[cat_id].isin(train_cats)] val_df df[df[cat_id].isin(val_cats)] print(f训练集个体数: {len(train_cats)}, 图片数: {len(train_df)}) print(f验证集个体数: {len(val_cats)}, 图片数: {len(val_df)})这里用cat_id而不是直接对样本做随机切分是为了避免「同猫串集」。如果数据集没提供猫个体标识退而求其次的做法是按时间戳切分因为同一段视频的相邻帧往往属于同一行为事件。参数上test_size0.2和random_state42是常规选择但更关键的是划分完成后要做一次类别分布对比确认 train 和 val 里各类别占比大致接近否则小类别可能在 val 里只剩一两个样本评估结果会剧烈抖动。3. 搭建 CNN 卷积网络结构、选型与可抄代码3.1 CNN 的基本框架Conv、ReLU、Pooling 三件套怎么搭猫行为识别的 CNN 结构不需要很深。行为识别依赖的是局部纹理特征毛发方向、身体姿态轮廓、运动模糊模式这些特征用两到三个卷积块就能提取。ResNet 那种 50 层以上的深网络在这个任务上属于「杀鸡用牛刀」参数量大、训练慢且小数据集上容易过拟合。一个标准的卷积块是Conv2D → BatchNormalization → ReLU → MaxPooling2D的组合。Conv2D负责提取局部特征卷积核尺寸一般取 3x3步长为 1BatchNormalization加速收敛并缓解梯度消失MaxPooling2D下采样减少参数量的同时增强平移不变性。池化窗口我倾向于 2x2因为猫行为的边缘细节较多太激进的池化会把姿态信息糊掉。两个卷积块之后接Flatten展平再连一到两层全连接最后是 softmax 输出层。这里有一个选型权衡要讲清楚为什么不用现成的 VGG16 或 ResNet 预训练模型迁移学习确实能提升小数据集上的表现但它的前提是你有一台带 GPU 的机器并且愿意处理下载预训练权重和输入尺寸归一化这些额外步骤。对于先跑通流程、再优化效果的节奏从零搭建一个小型 CNN 是最稳的起步方案后续再换迁移学习也不冲突。3.2 从零搭一个可训练的猫行为分类 CNN下面这份代码是完整可跑的模型定义针对猫行为分类输入图片统一 resize 到 128x128。这个尺寸对行为识别来说够用细节不会丢太多训练速度也快。import tensorflow as tf from tensorflow.keras import layers, models def build_cat_behavior_model(num_classes6, input_shape(128, 128, 3)): model models.Sequential([ # 第一个卷积块 layers.Conv2D(32, (3, 3), paddingsame, input_shapeinput_shape), layers.BatchNormalization(), layers.ReLU(), layers.MaxPooling2D((2, 2)), # 第二个卷积块通道数翻倍 layers.Conv2D(64, (3, 3), paddingsame), layers.BatchNormalization(), layers.ReLU(), layers.MaxPooling2D((2, 2)), # 第三个卷积块提取更高层语义 layers.Conv2D(128, (3, 3), paddingsame), layers.BatchNormalization(), layers.ReLU(), layers.MaxPooling2D((2, 2)), # 分类头 layers.Flatten(), layers.Dense(128, activationrelu), layers.Dropout(0.5), layers.Dense(num_classes, activationsoftmax), ]) return model model build_cat_behavior_model(num_classes6) model.summary()几点参数说明。第一个卷积块用 32 个卷积核后面每层翻倍到 64、128这是小网络的标准做法——浅层学边缘纹理深层学行为语义。paddingsame保证特征图尺寸不缩水池化层负责下采样。Dropout(0.5)是全连接层之间的正则化猫行为数据集通常只有几千张图没有 Dropout 基本必过拟合。num_classes要和前面数据集里统计的类别数一致多一个或少一个都会在编译时报 shape 错误。如果你发现 128x128 分辨率下行为类别区分度不够比如「梳理」和「舔毛」看起来差不多可以尝试把输入尺寸加到 160 或 192但训练时间会按平方增长性价比要自己权衡。我一般先用 128 跑通全流程再回头试更大尺寸。3.3 数据增强把几千张图用出几万张的效果猫行为识别的小数据集痛点在于样本量少一个类别可能只有两三百张图直接训练的话模型会把背景当作猫行为的一部分。数据增强在训练时对图片做随机变换相当于在线扩展数据集。from tensorflow.keras.preprocessing.image import ImageDataGenerator train_datagen ImageDataGenerator( rescale1.0 / 255, rotation_range15, width_shift_range0.1, height_shift_range0.1, zoom_range0.2, horizontal_flipTrue, fill_modenearest, ) val_datagen ImageDataGenerator(rescale1.0 / 255) train_generator train_datagen.flow_from_directory( ./cat_data/train, target_size(128, 128), batch_size32, class_modecategorical, ) val_generator val_datagen.flow_from_directory( ./cat_data/val, target_size(128, 128), batch_size32, class_modecategorical, )参数选择的理由要说明一下。rotation_range15只转了 15 度因为猫行为的姿态方向本身就是不固定的转太多会让「躺卧」看起来像「侧翻」反而制造错误标签。horizontal_flipTrue是安全的水平翻转猫不存在左右不对称的行为语义。fill_modenearest在平移旋转产生空白区域时用邻近像素填充对猫毛这种纹理型图像比constant填零更自然。注意验证集只做rescale不做任何增强否则验证集和训练集分布不一致评估指标就失真了。4. 训练与调参让损失曲线和准确率对上号4.1 训练主循环回调函数才是省心神器数据准备好、模型搭好之后训练环节的代码量反而最少但参数配置的坑最多。很多新手直接model.fit一把梭训练到一半 loss 爆炸或者 val_acc 震荡却不知道原因。我写训练代码时会把回调函数当成重点因为它们能在训练过程中动态调整策略而不是傻等几十个 epoch 结束才看结果。checkpoint tf.keras.callbacks.ModelCheckpoint( best_cat_model.keras, monitorval_accuracy, save_best_onlyTrue, modemax, ) reduce_lr tf.keras.callbacks.ReduceLROnPlateau( monitorval_loss, factor0.5, patience3, min_lr1e-6, ) early_stop tf.keras.callbacks.EarlyStopping( monitorval_loss, patience10, restore_best_weightsTrue, ) model.compile( optimizertf.keras.optimizers.Adam(learning_rate1e-3), losscategorical_crossentropy, metrics[accuracy], ) history model.fit( train_generator, steps_per_epochtrain_generator.samples // 32, validation_dataval_generator, validation_stepsval_generator.samples // 32, epochs50, callbacks[checkpoint, reduce_lr, early_stop], )ModelCheckpoint的save_best_onlyTrue保证你随时有一个历史最优权重可以后悔药式回滚。ReduceLROnPlateau在验证损失连续 3 个 epoch 不下降时把学习率减半猫行为数据集的 loss 曲面常有局部平坦区减学习率能帮模型滑出去。EarlyStopping的patience10和restore_best_weightsTrue配合防止后 10 个 epoch 在过拟合区打转且结束后模型自动回到验证集最优状态。steps_per_epoch不设置也行但显式设置能避免 flow_from_directory 的无穷生成器和 fit 的步数计算不一致。4.2 三个必调参数输入尺寸、batch size、学习率第一个必调参数是统一输入尺寸。前面在数据初检时统计过尺寸分布这里就要做出取舍。如果图片大多数在 500x500 以上resize 到 128x128 会丢失一些细粒度行为特征比如瞳孔放大这种压力信号但如果你用的是 CPU 训练192 以上的尺寸会让每个 epoch 的时间翻倍。我的经验是先用 128 跑通再用验证集准确率决定是否升级尺寸。第二个是 batch size。默认 32 是个安全值但猫行为数据集类别不平衡时小 batch 反而有优势——因为每个 batch 更有可能包含少数类样本梯度更新对少数类的偏向更明显。如果显存允许也可以试 64但要注意验证集准确率可能会因为 batch 统计噪声而波动变大。# 其他参数不变单独验证 batch size 的影响 batch_size_candidates [16, 32, 64] for bs in batch_size_candidates: print(fTesting batch size: {bs}) # 重新构建 train_generator其余流程一致第三个是学习率。Adam 默认的1e-3对大多数 CNN 分类任务都能工作但猫行为识别的小数据集场景下我见过不少1e-3直接导致 loss 发散的情况。如果训练到第 3 个 epoch 时loss不降反升先确认不是数据增强过度造成的再把学习率降到3e-4重试。学习率是「先找数量级再微调」的策略不要每个 epoch 手动去动它。4.3 训练日志怎么看欠拟合还是过拟合训练结束后绘制的损失曲线是判断模型状态的唯一可靠依据。欠拟合的标识是训练集损失和验证集损失都很高且两者都在下降但还没平坦这时增加 epoch、加深网络或加大卷积核数量。过拟合的标识是训练损失持续下降但验证损失在第 N 个 epoch 后开始回升这时靠 Dropout 增强、早停和数据扩充解决。import matplotlib.pyplot as plt def plot_training_history(history): fig, (ax1, ax2) plt.subplots(1, 2, figsize(12, 4)) ax1.plot(history.history[loss], labeltrain_loss) ax1.plot(history.history[val_loss], labelval_loss) ax1.set_title(Loss) ax1.legend() ax2.plot(history.history[accuracy], labeltrain_acc) ax2.plot(history.history[val_accuracy], labelval_acc) ax2.set_title(Accuracy) ax2.legend() plt.show() plot_training_history(history)这个可视化脚本本身很简单但它的用处在于把「玄学」变成「可解释」。比如 val_acc 从第 20 个 epoch 开始剧烈震荡而 train_acc 稳步上升这是典型的过拟合信号早停应该在 22 个 epoch 附近触发。如果只盯着最终准确率看你根本不知道模型已经过拟合了多久。另一个容易忽略的点是val_loss 和 val_acc 的走势可能不一致。有些行为类别的概率分布很平softmax 输出不置信但 argmax 恰好猜对了导致 acc 不降而 loss 升高。这时候把重点放在 loss 曲线上的判断更可靠。5. 猫行为识别排查手册4 个绕不开的坑5.1 行为类别里混入「非行为」图片现象训练完成后验证集准确率 90% 以上但把一张空房间的图扔进去模型硬要说猫在「睡觉」。原因数据集中「睡觉」类图片大量包含猫窝、地板、沙发等静态背景模型学到了「有猫窝就是睡觉」而不是猫的蜷缩姿态。这是数据集标注者无意识引入的偏差CNN 卷积网络会优先抓最容易区分的特征背景纹理往往比猫的行为姿态更容易识别。解决在数据初检阶段把「无猫背景图」单独挑出来抽样看每个类别图片里猫占画面的比例。更彻底的方案是引入一个「无猫」负类让模型显式学习「没有猫时不输出行为」。如果你不想动类别结构至少要在训练前确认每个类别里猫都是主体不是远景小点。5.2 zip 解压后图片打不开或格式伪装现象训练脚本跑到第 12 个 epoch突然ImageDataGenerator报OSError: image file is truncated训练中断。原因zip 压缩包里的部分 JPEG 文件头完整但数据段截断PIL默认在解码时报错。有些图片虽然扩展名是.jpg内部实际是 PNG 编码读图库也会异常。这类问题在从网络爬取构建的数据集里特别常见。解决不要等训练报错用第 2.2 节的检查脚本提前扫一遍把损坏图片单独归入broken/目录。对于截断的 JPEG可以尝试PIL.ImageFile.LOAD_TRUNCATED_IMAGES True强制加载但这类图通常只剩上半部分训练进去相当于噪声标签不如直接删除。删除数量超过总数 5% 时要警惕数据集采集环节出了问题重新审视数据源。5.3 准确率虚高混淆矩阵暴露的行为边界问题现象模型在验证集上准确率 93%但实际用的时候猫「伸懒腰」总是被识别成「睡觉」。原因行为分类的边界本身是模糊的。伸懒腰前半段身体趴着后半段弓背如果数据集标注时把两者混在一起CNN 学到的决策边界就会摇摆。准确率这个指标被多数类主导掩盖了少数类之间的混淆。解决训练完不要只看 accuracy打一张混淆矩阵看每个类别的精确率和召回率。对猫行为识别重点关注「趴卧 vs 睡觉」「舔毛 vs 梳理」这类高混淆对。针对性方案是给这些对类别的训练图做更细的裁剪让猫的肢体区域占更大比例弱化背景干扰。from sklearn.metrics import confusion_matrix, classification_report import numpy as np val_generator.reset() y_pred model.predict(val_generator) y_pred_classes np.argmax(y_pred, axis1) y_true_classes val_generator.classes[: len(y_pred)] print(classification_report(y_true_classes, y_pred_classes)) print(confusion_matrix(y_true_classes, y_pred_classes))5.4 训练到一半 loss 变成 NaN现象前 5 个 epoch 一切正常第 6 个 epoch 的 loss 突然变成nan之后所有指标全是nan。原因最常见的是学习率过大导致梯度爆炸但猫行为识别的小数据集上还有一个特殊诱因——数据增强把图片像素值推到非法范围或者某个类别的图片几乎全是同一只猫模型对该类别的特征响应过度饱和经过 softmax 后梯度异常。解决先排除最愚蠢的原因检查增强参数里的zoom_range是否过大过大的缩放可能把猫的身体裁出画面之外产生语义完全错误的训练样本。然后把学习率从1e-3降到3e-4重跑。最后用tf.keras.backend.set_floatx(float64)跑一次小规模实验如果 float64 下正常、float32 下 NaN说明是数据中有极端值而非网络结构问题。6. 进阶混淆矩阵之外还有两条提升路径当你的模型在验证集上的准确率达到 85% 以上但离实际可用还差一口气时有两个方向的优化值得投入。第一个是迁移学习把前面搭建的Sequential模型换成预训练的MobileNetV2或EfficientNet作骨干特征提取器只训练最后的分类头。这种方案对猫行为识别的提升非常明显因为预训练权重已经在千万级图片上学过纹理、边缘、形状的通用表征猫的姿态特征属于这些通用表征的下游组合。代价是你需要下载几十 MB 到几百 MB 的权重文件并且输入分辨率通常要提升到 224x224。我一般会在自定义 CNN 跑通全流程后把迁移学习作为第二版迭代而不是第一版就上。base_model tf.keras.applications.MobileNetV2( weightsimagenet, include_topFalse, input_shape(128, 128, 3), ) base_model.trainable False # 先冻结只训练分类头 model models.Sequential([ base_model, layers.GlobalAveragePooling2D(), layers.Dense(128, activationrelu), layers.Dropout(0.3), layers.Dense(num_classes, activationsoftmax), ])第二条路径是视频时序信息利用。单张图片的猫行为识别有天然的天花板因为很多行为是动态过程比如「捕猎准备」的伏地动作和「休息」的趴卧在单帧上几乎无法区分。如果你手中的数据不是单张图而是一段段视频或连拍序列可以把 CNN 替换成TimeDistributed(CNN) LSTM的架构让模型在时间维度上学习行为模式准确率会有质的飞跃。但这条路径的数据标注成本也高不仅要知道某帧猫在做什么还要标注行为的起始帧和结束帧。如果数据集只有静态图这个方向只能作为后续自己采集数据的预案。我自己的习惯是每次训完一个模型都会把预测失败的那一批图片单独输出到badcase/目录里按预测类别归档。这样迭代到下一版时先看这些老面孔有没有被解决比看准确率涨了 0.5% 更有实感。猫行为识别的落地没有银弹把数据做干净、把日志看懂、把边界认清比换个更花哨的网络结构更有胜算。希望这些经验能帮你少走几步弯路。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑