简介面向铁路轨道故障识别与图像分类场景的算法工程师、科研人员及学习者一份已标注数据集涵盖损坏与未损坏两类轨道状态图像约380张已按训练集、验证集、测试集分目录存放JSON文件中给出具体分类标签可直接用于CNN或YOLOv5等分类模型的训练与评估。压缩包共387个文件主体为368张jpg图片兼有少量jpeg/webp图片另含1个json标注文件、1个Python可视化脚本及配套说明图片整体大小约159.73MB便于快速下载解压后离线使用。目前已有192人浏览学习数据按类别归入不同文件夹运行自带show.py脚本即可一键预览样本降低数据核查与可视化门槛。配合作者发布的CNN分类网络专题和基于YOLOv5的分类教程读者能从中获得从数据准备、脚本可视化到模型训练评估的完整实践路径尤其适合刚接触轨道视觉分类的初学者作为入门数据集。1. 铁路轨道故障图像识别数据集先读图再谈模型铁路轨道故障图像识别数据集入手之后要记住的第一件事380 张已标注图片别指望靠模型规模取胜。这套资源解决的是轨道巡检场景里最实际的二分类问题——判断一张轨道照片是「损坏」还是「未损坏」数据已经按这两个类别标注完成并且预先切分好了训练集、验证集、测试集目录结构对主流训练框架基本是开箱即用。配套还给了一个 show 脚本用于可视化数据集另外附了 CNN 分类项目和 YOLOv5 分类两条技术路线的教程。适合两类人一是刚接触图像分类、想用最短路径跑通完整流程的初学者二是需要小样本数据快速验证迁移学习方案是否可行的从业者。一个反直觉的结论是这个资源的价值不在准确率而在 380 张图能暴露出的数据划分、标注噪声、过拟合问题比几千张图更典型、更集中。2. 数据集结构与标注规则目录、json 和 show 脚本的配合2.1 目录结构为什么「同类图片放一起」对训练最省事拿到数据集之后先别急着配环境第一件事是观察目录。作者在说明里写得很明确已经划分好训练集、验证集、测试集并且「存放各自的同一类数据图片」。翻译成实际布局就是数据集根目录下有三个子目录每个子目录里再按类别分子文件夹dataset/ ├── train/ │ ├── damaged/ │ └── normal/ ├── val/ │ ├── damaged/ │ └── normal/ └── test/ ├── damaged/ └── normal/这种排布的含金量在于PyTorch 的 torchvision.datasets.ImageFolder 和 Keras 的 image_dataset_from_directory 都能直接吃进这种结构开发者不需要写任何自定义 Dataset 类。这是「目录即标签」的工程范本相比 CVAT 或 LabelImg 导出的 VOC 格式需要解析 XML 定位标注框省掉了一整层中间转换环节。实操上我建议一拿到数据集就统计各子目录的图片数量别靠眼睛数。常见做法是用一条命令看全貌find dataset/train -type f | sed s/.*\.// | sort | uniq -c这条命令统计 train 目录里图片格式的分布如果混入了 .png 和 .jpg说明目录里可能存在格式混用。再细分到类别统计数量用下面这段for d in train val test; do for c in damaged normal; do echo $d/$c: $(ls dataset/$d/$c | wc -l) 张 done done参数说明$d 遍历三个子集$c 遍历两个类别ls 管道接 wc -l 统计每个类别文件夹里的图片数。输出会直接告诉你 train 里是不是只有两百多张、val 和 test 各剩多少。这步要看的是类别有没有漏放图片、目录名有没有写错。逻辑上需要注意ImageFolder 是按目录名自动生成类别 id 的目录名「damaged」「normal」会直接变成 0 和 1 标签所以目录名一定不能乱。如果你发现目录名是「good」「bad」「defect」这类别名优先统一成和 json 里一致的名称否则后面画混淆矩阵和统计报告时还得做一层名称映射徒增噪音。从工程角度看「子目录即标签」还提供一个隐藏好处后续想切到 YOLOv5 或 YOLOv8 的分类模式或者接进 TensorFlow目录结构可以原样复用。第一次做数据准备的人容易忽略这点等到想换第二个框架时才意识到要重排一遍目录白白浪费半小时。2.2 标注文件json 里有什么怎么读摘要里写的是「分类个数【2】损坏、未损坏【具体查看json文件】」。作者把类别定义放在 json 里意味着类别名可能有标准版也可能有简写实操中一定要先读一遍再写训练代码。我在拿到任何标注数据集时第一个动作就是检查 json 的顶层结构import json with open(dataset.json, r, encodingutf-8) as f: data json.load(f) # 先打印顶层字段确认数据是列表还是字典 for k, v in data.items(): if isinstance(v, list): print(f{k}: list, 长度 {len(v)}) elif isinstance(v, dict): print(f{k}: dict, 键 {list(v.keys())}) else: print(f{k}: {v})这段代码先做结构勘察。常见的 json 结构要么是根层放 categories 列表要么是 images 列表里每条记录带 label 字段先打印顶层类型能避免下一步用错下标。接着处理类别信息和划分统计# 如果结构是 {categories: [{id: 0, name: normal}, ...]} for cat in data.get(categories, []): print(cat[id], cat[name]) # 如果结构是 {train: [...], val: [...], test: [...]}统计各划分里的样本数 for split in [train, val, test]: if split in data: print(split, len(data[split]))参数说明encodingutf-8 是强制指定解码方式Windows 下 json 文件如果带中文说明字段缺这个参数会有概率触发 cp936 解码错误data.get(categories, []) 是安全取值json 结构不完整时返回空列表而不是抛 KeyError适合对标注文件先做探查再决定后续逻辑。这里给一个直接的提醒读 json 的目的是确认两件事——类别 id 与图片数量的对应关系以及训练时标签的来源。380 张图手动对一轮用不了十分钟但能省掉后面排查标签错位的大把时间。如果 json 里除了类别还记录了图片路径建议顺手校验几条路径在磁盘上是否真实存在路径失效的情况在搬运数据集中很常见。2.3 show 脚本可视化不是看热闹是看门道作者提到资源里带 show 脚本可以「可视化数据集」。这个脚本的存在非常实用380 张小数据集最稀缺的是对数据本身的理解而不是模型。可视化要回答三个问题类别是否平衡、图片分辨率是否一致、损坏形态有没有明显规律。常见的脚本逻辑是遍历每个类别文件夹按网格布局把图片铺开import os import random import matplotlib.pyplot as plt from PIL import Image data_root dataset/train classes [damaged, normal] fig, axes plt.subplots(2, 3, figsize(12, 8)) for i, cls in enumerate(classes): cls_dir os.path.join(data_root, cls) imgs os.listdir(cls_dir) random.seed(0) sample random.sample(imgs, min(3, len(imgs))) for j, img_name in enumerate(sample): img_path os.path.join(cls_dir, img_name) img Image.open(img_path) axes[i, j].imshow(img) axes[i, j].set_title(f{cls}: {img.size}) axes[i, j].axis(off) plt.tight_layout() plt.savefig(dataset_overview.png, dpi150)逻辑说明对每个类别随机抽 3 张图标题打印图片真实宽高这样一页图能同时看到类别分布和分辨率差异。如果损坏类的图普遍比未损坏类暗说明拍摄条件不一致训练预处理环节需要加入亮度归一化或对比度增强。random.seed(0) 保证每次抽样结果一致复现时不会因为随机抽样对不上。参数说明figsize(12, 8) 是画布尺寸对应 2 行 3 列的子图排布dpi150 保证保存的图能看清纹理细节。如果在远程服务器上训练plt.savefig 比 plt.show 更实用不需要 X11 转发。可视化这步看出的门道往往超出预期。比如我经常发现轨道图里有大量空白道床区域——碎石、枕木占了画面主体真正的损坏区域只占很小比例。这种「目标小、背景杂」的结构会把模型注意力带偏后面就需要考虑裁剪或加权损失来修正。3. 用 CNN 二分类跑通基线迁移学习与训练参数设计3.1 选型理由为什么 CNN 在这个规模下比手工特征稳380 张图、二分类、轨道故障检测为什么不建议用传统 CV 方案轨道表面裂纹的特征是高对比度、细长、方向随机手工特征如 Canny 边缘或方向梯度直方图需要人工调阈值而 380 张样本根本不够调出对光照、灰尘、油渍鲁棒的阈值。CNN 的核心优势是由数据驱动学特征浅层卷积核学到边缘纹理深层学到语义形状预训练模型把 ImageNet 上亿张图的通用视觉先验带过来迁移到轨道场景只需少量样本微调。另一个现实理由是配套资源里作者明确给了 CNN 分类项目的教程数据集按 ImageFolder 组织两边严丝合缝。与其另起炉灶搞特征工程不如先把迁移学习这条路跑通拿到指标后再决定要不要做细化。如果你习惯用 YOLO 系列框架下面第 5 章会给一条完全复用目录结构的对比路线。3.2 训练脚本核心ImageFolder 加载与 ResNet18 微调配好环境之后第一步不是去调参而是拿最基础的 ResNet18 跑一个能收敛的版本确认数据、代码、硬件全链路没问题。我一般这样写import torch import torch.nn as nn import torch.optim as optim from torchvision import datasets, transforms, models from torch.utils.data import DataLoader # 预处理轨道图过拟合风险高先用轻量增强 transform transforms.Compose([ transforms.Resize((224, 224)), transforms.RandomHorizontalFlip(p0.5), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) train_ds datasets.ImageFolder(dataset/train, transformtransform) val_ds datasets.ImageFolder(dataset/val, transformtransform) train_loader DataLoader(train_ds, batch_size16, shuffleTrue, num_workers4) val_loader DataLoader(val_ds, batch_size16, shuffleFalse, num_workers4) # 加载预训练 ResNet18替换最后一层全连接 model models.resnet18(weightsmodels.ResNet18_Weights.IMAGENET1K_V1) model.fc nn.Linear(model.fc.in_features, 2) # 冻结除 fc 外的所有层小数据集先只训练分类头 for param in model.parameters(): param.requires_grad False for param in model.fc.parameters(): param.requires_grad True model model.cuda() criterion nn.CrossEntropyLoss() optimizer optim.Adam(model.fc.parameters(), lr1e-3) for epoch in range(15): model.train() train_loss 0.0 for images, labels in train_loader: images, labels images.cuda(), labels.cuda() optimizer.zero_grad() outputs model(images) loss criterion(outputs, labels) loss.backward() optimizer.step() train_loss loss.item() * images.size(0) # 每轮做一次验证 model.eval() correct 0 total 0 with torch.no_grad(): for images, labels in val_loader: images, labels images.cuda(), labels.cuda() outputs model(images) _, preds torch.max(outputs, 1) correct (preds labels).sum().item() total labels.size(0) val_acc correct / total print(fepoch {epoch1:02d} | loss {train_loss/len(train_ds):.4f} | val_acc {val_acc:.4f})逻辑说明用 ResNet18 而不是自己搭网络是因为 ImageNet 预训练权重已经包含丰富的纹理和边缘特征轨道图虽然领域特殊但底层视觉模式仍然通用。冻结所有层只训练最后的全连接层是小数据集上降低过拟合最有效的做法——380 张样本不足以让深层卷积权重稳定更新强行解冻会让模型在第二个 epoch 就开始震荡。DataLoader 里 num_workers4 是让 CPU 提前加载下一批图片磁盘 IO 慢的时候能明显提速。参数说明batch_size16 是基于 380 张的画像设置的太小如 4 会导致梯度噪声大、loss 抖动太大如 64 会让每次验证集的统计都基于少量图片acc 波动大。lr1e-3 对 Adam 是安全起点只训练一层全连接时这个学习率稍偏快但 15 个 epoch 内一般不会翻车。epoch15 是刻意保守——这个数据量下 10 轮左右 val acc 就到天花板再跑多是纯浪费。如果验证集效果正常这一版就可以作为基线。注意第一次跑不要上花活不用学习率调度器、不加早停、不调 Dropout最朴素的模型先跑出来后面每一步改动才有对比参照。3.3 训练输出怎么读loss 曲线和过拟合信号训练输出看起来简单但要学会看曲线。最典型的状况是 train loss 一路降、val acc 在 70% 附近徘徊。这证明模型在分类头上已经记住了训练集里的噪声但没有真正学到轨道损伤的特征。出现这种现象时优先做三件事加强数据增强的强度加旋转、加裁剪、把 lr 降到 3e-4、把 15 个 epoch 改成 20 并每轮同时打印 val loss。还有一个常见误区是盲目相信「val acc 高」就等于模型能用。小数据集上 val 集只有几十张图val acc 波动 ±5% 都是正常噪声。正确做法是记录每一轮的 val_acc 和 val_loss观察曲线是否平滑上升而不是盯某一轮的峰值。如果 train acc 到了 99% 而 val acc 还在 80% 上下抖动这就是过拟合前兆下面第四章会展开讲。提示迁移学习小数据集的第一原则是「能冻结就冻结」。先跑通再逐层解冻这个顺序能避免大量无效调参。4. 小数据集的避坑清单380 张图训练中的五个典型问题图像识别社区流传一句话「小数据集跑通容易跑稳难。」380 张图放在今天的大模型语境下算是微型数据但恰恰是这个规模最容易踩坑而且每个坑的表现都极具迷惑性。这一章按实际排错的经验列出五个高发问题每个都按现象、原因、解决三步来写。4.1 现象准确率 92%测试集却全是未损坏类原因数据集中损坏类样本占比低模型学到的是「全猜未损坏」的捷径。二分类里如果损坏图只占 20%直接全部预测为未损坏就能拿到 80% 准确率而交叉熵损失也发现这条路损失更低。解决打印混淆矩阵而不是只看总体 acc对损坏类单独看召回率给损坏类加权或者直接把 CrossEntropyLoss 的 weight 参数设成样本比例的倒数。class_counts [len(os.listdir(dataset/train/damaged)), len(os.listdir(dataset/train/normal))] weights torch.tensor([1.0 / c for c in class_counts], dtypetorch.float32) weights weights / weights.sum() * 2 # 归一化到均值 1 criterion nn.CrossEntropyLoss(weightweights.cuda())参数说明1.0 / c 是对类别做倒数加权样本少的损坏类获得更高权重归一化到均值 1 是为了让整体 loss 量级和未加权时接近lr 不用重调。这个方法比随机过采样更干净不改变图片本身的分布。使用时要确认 class_counts 的顺序和 ImageFolder 的类别顺序一致否则权重会加反。4.2 现象验证集准确率高加入同批次拍摄的新图后性能骤降原因数据划分泄漏。轨道巡检拍摄时往往连续快门连拍相邻几帧的光照、角度、位置几乎一样若随机划分把同一连拍序列的图片同时切进 train 和 val模型的「高准确率」实际是记住了场景不是泛化。解决划分前按文件名时间戳或拍摄批次做分组同一组图片整体划到一个子集中。380 张图如果来源是几十个拍摄片段至少要保证 val 和 test 里的图片不与 train 来自同一段连拍。实操上我一般先把文件名按字典序排序再按间隔取样做划分这样相邻帧不会散落到两个集合里。排序后还可以直接观察文件名前缀同前缀的通常是同一批拍摄。4.3 现象train acc 冲上 99%val loss 反而开始回升原因过拟合的教科书症状。模型在分类头上可以轻松记住 380 张图的全部标签但记忆不等于学习。这个数据量下全连接层参数量相对图片数明显偏多训练到后半段开始死记噪声。解决加强数据增强随机旋转 ±15°、随机裁剪到 200×200 再缩放到 224、增加 Dropout例如 0.3、或者将模型继续冻结只训练 fc。我的经验是小数据集优先控制自由度参数量大的层解冻越多过拟合来得越快。判断是否要停训看 val loss 连续三轮不降反升就回退到之前一轮的权重。4.4 现象json 里标注是损坏但图片里看不出损坏痕迹原因标注噪声。损坏的定义边界在轨道场景里比较模糊轨面锈迹、道床碎石阴影、油污反光很容易被标成「损坏」另一个方向是轻微裂纹在低分辨率下肉眼难辨。解决跑 show 脚本把疑似损坏的图片全部人工过一遍把标注有疑问的图片单独放一个文件夹先不参与训练模型跑通后再单独预测这批图看预测结果与人工判断的吻合度。如果标注错误率超过 10%先修标注再训练因为网络会学标注的「平均观点」错标越多模型输出越飘。4.5 现象解冻全部层训练后val acc 从 80% 掉到 65%原因迁移学习的基础设置踩坑。预训练模型在 ImageNet 上收敛得很好380 张轨道图对深层卷积权重的更新信号太弱、太偏直接解冻全部层会让模型跳出 ImageNet 的特征空间而新数据又不足以填回这个位置。解决按阶段解冻——先只训练 fcval acc 稳定后再解冻最后一个残差块 layer4lr 降到 1e-4。什么时候解冻看 val acc 连续多个 epoch 不再上涨。代码实现就是在 epoch 计数达到阈值后把 model.layer4 里所有参数的 requires_grad 置为 True并给优化器重新设一个更小的 lr。这一步是我踩过最深、也是最容易被忽略的开关翻车率极高。5. 进阶验证数据增强、YOLOv5 分类模式与双框架交叉验证5.1 数据增强的工程化用 Albumentations 替代 torchvision 的固定组合torchvision 的 transforms 对小数据集来说力度不够灵活。我一般会用 Albumentations 做增强它和 PyTorch 的 DataLoader 配合起来只需要改动预处理部分import albumentations as A from albumentations.pytorch import ToTensorV2 aug A.Compose([ A.Resize(224, 224), A.RandomRotate90(p0.3), A.RandomBrightnessContrast(brightness_limit0.2, contrast_limit0.2, p0.5), A.GaussNoise(var_limit(10, 50), p0.2), A.Normalize(mean(0.485, 0.456, 0.406), std(0.229, 0.224, 0.225)), ToTensorV2() ])参数说明RandomBrightnessContrast 模拟不同时间拍摄的光照差异对轨道巡检数据尤其重要GaussNoise 模拟传感器噪点p0.2 是保守比例太高会把纹理细节淹没。注意 Albumentations 的 Normalize 和 torchvision 的行为不同以及 resize 放在随机操作之前避免裁剪后输入尺寸不一致。使用时要保持训练和验证两套预处理分离——验证集只做 Resize、Normalize、ToTensor不做随机扰动。5.2 YOLOv5 分类模式目录结构直接复用配套资源里专门提到基于 YOLOv5 的分类教程。YOLOv5 分类模式最省事的地方是它沿用「子目录即标签」的组织方式这个数据集解压后可以直接用不需要额外转格式python classify/train.py --model yolov5s-cls.pt \ --data dataset/ \ --epochs 30 \ --img 224 \ --batch 16 \ --lr 0.001参数说明--data 指向数据集根目录YOLO 会自动扫描根目录下的 train 和 val 文件夹以子目录名作为类别名yolov5s-cls 是分类专用预训练权重size 最小适合 380 张的小数据--img 224 与上面 CNN 的输入分辨率保持一致方便两个框架的结果互相对标。同理YOLOv8 的分类模式也支持相同的数据组织方式可以按需切换。这样做的价值在于双框架交叉验证如果 ResNet18 和 YOLOv5s-cls 两个完全不同结构的模型在同一测试集上给出相近的 acc 和混淆矩阵说明模型学到的是稳定的轨道损伤语义特征而不是某个框架的数据处理偏好。5.3 结果对比以混淆矩阵和测试集报告收尾两个框架都训练完后把结果整理成对照表记录 val acc、val 混淆矩阵、单类召回率三个指标。分类任务本身就是二分类真正的合格标准是损坏类召回率是否足够高——在轨道巡检场景里宁可把未损坏误判成损坏多一次人工复核也不能把损坏漏过去。我现在拿到任何新的小数据集都会先跑一遍可视化脚本做人工核对然后按「CNN 基线 → 数据增强 → 第二框架交叉验证」的顺序走。这套顺序帮我挡掉了至少三次数据泄漏和两次标注噪声的翻车事故。模型训练这种事坑总是藏在数据准备里参数反而是最不常出问题的地方。希望帮到你。本文还有配套的精品资源点击获取