ARTICLE DETAIL

资讯详情

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

从数据清洗到迁移学习:花类识别数据集全流程实操

从数据清洗到迁移学习:花类识别数据集全流程实操 简介面向图像分类与植物识别任务的花类图像数据集共收录4242张花朵照片按洋甘菊、郁金香、玫瑰、向日葵、蒲公英五个常见类别划分每类约800张。数据经数据流、Google Images、Yandex Images等多渠道收集整合覆盖不同拍摄角度与光线条件适合图像分类入门、CNN模型训练、迁移学习及数据增强等实践环节。压缩包内含2000个文件主要类型包括JPG图片8646个、Python脚本4个、pyc编译文件2个与说明txt1个可用于数据读取、目录遍历与简单分类示例整体包体约449.82MB。图像分辨率约320×240像素且未统一缩放、比例多样接近自然拍摄场景能真实检验预处理、归一化与数据增强策略对初学者理解完整图像分类流程颇具价值。目前已有606人学习下载适合需要多类别花卉图像开展快速实验、构建植物识别Demo或进行CNN特征分析的开发者与研究者。1. 花类识别数据集先别急着训练先把它当数据检查单拿到一份标注好的图像数据集多数人的第一反应是直接开训结果半天时间耗在解压坏包、标签对不上和图片打不开上。这份花类识别数据集解压后是 4242 张 JPEG 图片覆盖洋甘菊、郁金香、玫瑰、向日葵、蒲公英五个类别每类约 800 张统一按 Flickr 照片 ID 命名来源是搜索引擎批量采集。它适合两类人想复现完整图像分类流程的算法工程师以及需要带标签数据做迁移学习验证的入门者。图片分辨率不高大多在 320x240 左右且没有统一缩放——正是这种真实采集、未经工业化清理的状态反而能暴露训练管线里的真问题。下面的内容从数据盘点开始到训练脚本和避坑结束写清楚这条完整路径。2. 先摸清数据本体五类组成、命名规律与真实分辨率很多人下载完数据集直接跳过数据检查这是翻车的第一来源。图像分类项目里模型结构导致的误差波动往往小于脏数据导致的系统性偏差。这份数据虽然有摘要描述但摘要不等于实际文件先花二十分钟把目录里的真实情况核对一遍后面能省下两个小时的排错时间。2.1 五类花朵的数量分布与文件命名特征把图片按类别分好之后数量分布大致是洋甘菊、郁金香、玫瑰、向日葵、蒲公英各有约 800 张。总张数 4242 与“每类约 800×5”有出入因为批量抓取的时间窗口不同类别数量并不完全相等这是采集类数据集里的正常现象。文件名如 2431737309_1468526f8b.jpg 是 Flickr 照片 ID 的典型命名风格前段是照片 ID后段是图片 secret 字符串。这种命名意味着图片是从图床链路抓取后直接批量保存的而不是人工拍摄整理所以构图极不统一主体可能在中央也可能偏在角落清晰度差异也大。这种数据无法通过文件名直接判断类别必须依靠目录层级或额外映射关系来组织训练集。中文描述里的“洋甘菊”值得留意。经典的同类花分类数据集中这一类别常被标成 daisy雏菊而这份描述写的是 chamomile洋甘菊。抓取数据时用的搜索词决定标签不同批次可能存在口径差异。建议在训练前抽样三十张左右人工核对标签以图片内容为准而不是以翻译为准。这属于采集数据集的常规体检项不查很容易在后期被混淆矩阵打脸。中文标签建议英文标签大致数量视觉特征易混淆对象洋甘菊/雏菊daisy/chamomile约 800白色花瓣、黄色花心部分蒲公英蒲公英dandelion约 800黄色花瓣密集、花心大洋甘菊、向日葵玫瑰rose约 800多层花瓣、深色系为主郁金香某些品种向日葵sunflower约 800大花盘、黄色长花瓣蒲公英远拍郁金香tulip约 800杯状单层花瓣玫瑰含苞状态这张表不建议直接当最终标签使用它只是最初的锚。真正决定标签的是你抽查图片后的结论。2.2 用脚本统计真实分辨率与损坏文件摘要说分辨率约 320x240但没有说明分布。同一个数据集里可能存在 640x480 的图也可能有 256x192 的缩略图。训练前第一步我会写一个脚本把每张图的尺寸统计出来顺便检测损坏图。import os from collections import Counter from PIL import Image root flowers_raw size_counter Counter() bad_files [] total 0 for cls in os.listdir(root): cls_path os.path.join(root, cls) if not os.path.isdir(cls_path): continue for fname in os.listdir(cls_path): if not fname.lower().endswith((.jpg, .jpeg)): continue path os.path.join(cls_path, fname) total 1 try: img Image.open(path) img.load() # 强制读取像素字节损坏在这里暴露 size_counter[img.size] 1 except Exception as e: bad_files.append((fname, str(e))) print(图片总数:, total) print(损坏文件数:, len(bad_files)) for fname, err in bad_files[:10]: print(BAD:, fname, err) print(出现最多的10种尺寸:) for size, cnt in size_counter.most_common(10): print(size, cnt)这段脚本的逻辑是PIL 的 Image.open 只读文件头和元信息真正的像素解码发生在 load() 调用时所以校验损坏必须以 load() 是否抛异常为准。只判断后缀和文件大小不可靠。输出结果会告诉你三件事图片总数、损坏文件列表、真实分布的尺寸集合后面 transform 的输入尺寸基准就按这份统计来定。实际跑的时候你会看到尺寸集中在 320x240 附近但也存在宽高比不是 4:3 的图。这种情况不需要全部滤掉只需要在数据增强阶段用“缩放后再裁剪”的方式统一尺寸而不是直接拉伸变形。拉伸会改变花瓣长宽比对花类识别的影响比一般分类任务更明显。3. 预处理三步走解压校验、目录重构与脏图清洗这一章做的是把“能看的图片”变成“能训的结构”。三个步骤每次都做顺序不能换先校验压缩包再按类别划目录最后做清洗。顺序反了会出现重复劳动。3.1 解压与完整性校验别让坏包拖垮训练网上下载的数据集压缩包最容易出的问题是解压到一半提示 CRC 错误或者某个文件解压失败。尤其是文件多、体积大的包网络传输导致的字节缺失很常见。常见做法是解压前先做一次完整性测试而不是直接双击解压。import zipfile zip_path flower_classification_dataset.zip extract_dir flowers_raw with zipfile.ZipFile(zip_path) as zf: # 逐个文件做CRC校验返回第一个损坏的成员名 bad zf.testzip() if bad: print(损坏文件:, bad) else: print(全部文件完整) zf.extractall(extract_dir)参数说明testzip() 会解压每一个成员并做 CRC 校验返回第一个损坏的成员文件名。文件比较多时这一步会花几分钟但比训练到一半发现图片打不开要划算。校验通过后再 extractall 才有意义。还有一种容易被忽略的情况是“伪加密”。某些打包工具写 zip 时会把通用位标志里的加密位置 1但实际没有加密内容正常解压工具会提示需要密码。如果文件确实是从公共渠道下载的、本身不带密码可以先用 zipfile 读一下成员信息判断加密位再把标志位还原后解压。这是一个偏门技巧做数据搬运时遇到过不止一次。如果是真加密那就只有找原作者要密码一条路不要浪费时间在这上面。3.2 目录重构从平铺文件到 train/val 分层结构解压后图片按原采集目录组织文件名本身不包含类别信息。训练前需要把它整理成 ImageFolder 能直接读的结构根目录/类别名/图片.jpg同时把验证集单独切出来。如果你的包解压后是平铺在同一个目录的需要先依据采集时的类别清单建五个子目录再做这一步。import os, random, shutil src flowers_raw # 解压目录内含五个类别子目录 dst flowers_split # 输出根目录 val_ratio 0.2 seed 42 random.seed(seed) for cls in os.listdir(src): cls_path os.path.join(src, cls) if not os.path.isdir(cls_path): continue files [f for f in os.listdir(cls_path) if f.lower().endswith(.jpg)] random.shuffle(files) n_val int(len(files) * val_ratio) val_files files[:n_val] train_files files[n_val:] for split, subset in [(train, train_files), (val, val_files)]: out_dir os.path.join(dst, split, cls) os.makedirs(out_dir, exist_okTrue) for f in subset: shutil.copy2(os.path.join(cls_path, f), os.path.join(out_dir, f)) print(f{cls}: train{len(train_files)} val{len(val_files)})这里有两个关键选择。第一按类别分别 shuffle 再切分而不是把全量文件混在一起后切分。这样可以保证每个类别在验证集里都有足量样本避免某类花在验证集里只有十几张评估结果波动巨大。第二用 copy2 保留文件时间等元数据便于后续复核如果磁盘紧张改成 shutil.move 也完全可行但记得保留一份原始压缩包清洗删错了还有后悔药。切分的比例 8:2 是这个体量数据集的常见做法。如果后续想调参建议保持 train/val 划分起一个固定种子不变只动模型参数否则每一次改动都引入划分随机性很难判断指标变化来自模型还是数据。3.3 清洗损坏图、非目标图和重复图切完目录后还要过一遍清洗。这里不是清洗到完美——数据集本身是自然采集的图片包含背景杂物是正常的甚至有利于模型泛化。真正要清的是三类无法解码的损坏图、完全重复的图、内容与类别明显不符的离群图。前两类可以用脚本自动处理第三类需要抽检配合。import os from PIL import Image import hashlib base flowers_split seen_hash set() removed 0 for split in [train, val]: root os.path.join(base, split) for cls in os.listdir(root): cls_dir os.path.join(root, cls) for fname in os.listdir(cls_dir): path os.path.join(cls_dir, fname) try: img Image.open(path) img.load() # 缩小到16x16灰度后取MD5只抓完全相同或几乎相同的图 small img.resize((16, 16)).convert(L) h hashlib.md5(small.tobytes()).hexdigest() if h in seen_hash: os.remove(path) removed 1 continue seen_hash.add(h) except Exception: os.remove(path) removed 1 print(清洗删除文件数:, removed)这段的原理是把图片缩小到 16x16 灰度做 MD5能够抓到内容完全相同的重复图相似但构图不同的图不会被误杀。损坏图在 load() 抛异常时直接移除。额外注意这里把 train 和 val 放在同一个哈希集合里查重优先保留 train删掉 val 中重复的部分防止同一张图跨集出现导致验证分数虚高。清洗完以后你会看到总数比 4242 少几十张这是正常的。把清洗前后的数量记录在训练笔记里方便别人复现时知道差异是怎么来的。4. 训练一条可复现的基线迁移学习与完整参数清单目录就绪之后下一步不是调模型结构而是用一套成熟方案先拉出一条基线。这个数据集的分辨率和体量决定了从头训练一个卷积网络几乎必然过拟合迁移学习是性价比最高的选择。4.1 为什么选 ImageNet 预训练模型320x240 的输入全库只有 4242 张图随机初始化一个 ResNet 在五分类任务上训练几十轮验证集准确率通常也到不了 80%还要忍受长时间调参。换 ImageNet 预训练权重后模型已经具备基本的边缘、纹理和形状特征只需要学花朵的类别差异训练轮数也能大幅缩短。选型上ResNet18 和 ResNet50 都可以跑。ResNet18 在这个数据规模下训练速度更快最终准确率差异不大如果有 GPU 显存余量ResNet50 的鲁棒性通常略好。更重的模型如 EfficientNet-B4 以上对这个 4242 张的数据量而言容易过拟合不建议第一版就用。迁移学习的常规做法有两种只训练最后的全连接层或者全网络微调。只训练分类头速度快、上限有限全网络微调效果好但对学习率更敏感。我一般用两段式先冻结骨干训练 5 轮让分类头先收敛再放开全部参数用小学习率微调。这个顺序比一上来就全量微调稳定得多。4.2 DataLoader 与数据增强低分辨率下怎么裁剪不翻车低分辨率图片最容易翻车的点在 transform。如果直接按 ImageNet 的默认裁剪 scale(0.08, 1.0) 来做 RandomResizedCrop从 320x240 的图里随机裁到原面积的 8%很可能把花的主体直接裁出画面训练出来的模型只在局部纹理上做判断验证时一塌糊涂。from torchvision import datasets, transforms from torch.utils.data import DataLoader train_transform transforms.Compose([ transforms.Resize(256), transforms.RandomResizedCrop(size224, scale(0.7, 1.0), ratio(0.75, 1.333)), transforms.RandomHorizontalFlip(p0.5), transforms.ColorJitter(brightness0.2, contrast0.2, saturation0.2), 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), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) train_ds datasets.ImageFolder(flowers_split/train, transformtrain_transform) val_ds datasets.ImageFolder(flowers_split/val, transformval_transform) train_loader DataLoader(train_ds, batch_size32, shuffleTrue, num_workers4, pin_memoryTrue) val_loader DataLoader(val_ds, batch_size32, shuffleFalse, num_workers4, pin_memoryTrue)这里的关键改动是把 scale 的下限从 0.08 抬到 0.7。花的主体占画面比例通常较高裁一半可以裁到 8% 就是灾难。340x240 的原始尺寸经 Resize(256) 再裁剪到 224会有轻微放大但在没有统一分辨率的数据集里这已经是比较稳的通用方案。ColorJitter 对户外花朵图的颜色鲁棒性有帮助但幅度别太大0.2 左右即可因为花朵的类别差异很大程度上就靠颜色特征区分。验证集不做随机增强只做 CenterCrop避免 transform 的随机性干扰评估结果。这里要说一句很多调参翻车不是模型的问题而是训练和验证用了两套不一致的数据分布。4.3 训练脚本核心两段式微调与参数清单训练循环本身不复杂关键是优化器、损失函数和学习率的配合。我使用 AdamW 配余弦退火对中小型数据集效果稳定。损失函数直接用带标签平滑的交叉熵因为这份数据的标签本身存在“洋甘菊/雏菊”这种口径歧义标签平滑可以让模型不那么死磕单一条目。import torch, torch.nn as nn from torchvision import models def build_model(num_classes5): model models.resnet18(weightsmodels.ResNet18_Weights.IMAGENET1K_V1) in_features model.fc.in_features model.fc nn.Linear(in_features, num_classes) return model model build_model() # 阶段一冻结骨干只训分类头 for p in model.parameters(): p.requires_grad False for p in model.fc.parameters(): p.requires_grad True optimizer torch.optim.AdamW(model.fc.parameters(), lr3e-4, weight_decay1e-4) criterion nn.CrossEntropyLoss(label_smoothing0.1) # 阶段二恢复全网络梯度降低学习率微调 # for p in model.parameters(): # p.requires_grad True # optimizer torch.optim.AdamW(model.parameters(), lr1e-4, weight_decay1e-4)这组起点参数在这个数据集上比较稳参数取值说明backboneResNet18体量小、训练快分类头替换为5类输入尺寸224x224从256 resize后裁剪batch size32单卡12GB显存可容纳阶段一学习率3e-4只训练fc层AdamW阶段二学习率1e-4全网络微调降到阶段一的三分之一weight decay1e-4低数据量下抑制过拟合epochs30阶段一10轮阶段二20轮学习率调度余弦退火从初始值衰减到1e-6label smoothing0.1缓解标签噪声这些参数不是最优解是稳定起点。阶段二微调时如果发现训练集 loss 降得很低而验证集停滞优先调 weight decay 和数据增强不要继续堆 epoch。5. 常见问题与避坑数据集下载后最容易翻车的五个点把实际处理这个数据集时遇到的高频问题按“现象-原因-解决”列出来顺序按出现频率从高到低。5.1 解压到一半报 CRC 错误或者提示需要密码现象WinRAR 或 7-Zip 解压到中间弹窗报错跳过之后图片数量对不上另一种是压缩包提示需要密码但你确认来源没有加密。原因文件在传输过程中有字节损坏多数源于网络不稳定或存储介质问题。提示密码的情况大概率是伪加密打包工具把通用位标志里的加密位置了 1但没有实际加密内容。解决先用 zipfile.testzip() 检测并定位坏文件只有个别文件损坏就把能解压的部分解出来继续用坏的比例超过 1% 就重新下载。伪加密用脚本把加密标志位还原后再解压不要浪费时间猜密码。5.2 图片能打开但内容不是花或者标签对不上现象训练时 loss 降下去验证集准确率卡在 70% 左右抽查图片发现不少样本里没有花的主体或者“洋甘菊”类别里混着明显是雏菊的图。原因搜索引擎采集的结果本身混有低相关图片中文标签在不同检索词下口径不一致导致部分文件与类别描述不符。解决先按 3.3 的脚本剔除无法解码和重复的图再抽样人工核对按图片内容重建标签映射。花类识别这类数据清理一两百张离群图准确率通常能涨两三个点。5.3 默认 RandomResizedCrop 参数下验证集准确率上不去现象照着 ImageNet 的标准 transform 跑训练集拟合得很好验证集却波动巨大。原因默认 scale(0.08, 1.0) 允许裁到原图 8% 的面积。对 320x240 的图片来说这种裁剪经常会丢掉花朵主体等于用残缺图片训练。解决把 scale 下限改成 0.7ratio 范围保持默认。验证集固定用 CenterCrop保证评估一致性。这个改动往往比换网络结构带来的提升更明显。5.4 验证集指标虚高重复图片跨集泄漏现象验证集准确率某次跑到 95% 以上换一个随机种子重新切分后掉到 82%指标不稳定。原因搜索引擎采集的同一种花大量来自同一图源的不同尺寸或相似构图随机切分时同一视觉样本可能同时出现在训练集和验证集里。模型记的是图片特征拿到几乎一样的图自然“认得”。解决切分前先做跨集哈希去重优先保留训练集切分后从 train 和 val 各抽样若干张人工对比确认没有相似图跨集。5.5 把分类数据集直接当 YOLO 检测数据集用现象按目标检测教程把数据转成 YOLO 的 images/labels 结构后loss 巨大模型不收敛。原因这份数据集只有图像级类别标签没有物体框标注。目标检测的标签需要每个目标的边界框坐标不能从分类标签自动生成。解决这个数据集的正确用法是训练分类模型。如果确实需要检测常规做法是额外标注边界框或者用分类模型跑出高置信区域后再用标注工具修正。不要拿整张图当边界框去喂 YOLO。6. 进阶一步用混淆矩阵和学习曲线判断欠拟合还是欠数据当验证集准确率停在某个点不再上升时多数人的第一反应是继续调参这通常是在浪费时间。更快的方法是先看错误都出在哪里再判断瓶颈在数据还是模型。6.1 混淆矩阵找到最容易混的类别对import torch from sklearn.metrics import confusion_matrix device torch.device(cuda if torch.cuda.is_available() else cpu) model.eval() preds_all, labels_all [], [] with torch.no_grad(): for imgs, labels in val_loader: imgs imgs.to(device) preds model(imgs).argmax(dim1) preds_all.extend(preds.cpu().numpy()) labels_all.extend(labels.numpy()) cm confusion_matrix(labels_all, preds_all) print(cm) print(val_ds.classes)读混淆矩阵只看两件事对角线数字是否整齐以及最大的非对角元素落在哪一对类别上。常见的错误对是雏菊与蒲公英都是白色或黄色花瓣加黄色花心郁金香与玫瑰两者都是杯状多层结构。如果错误集中在某一对先去翻原始图片找规律多半是标签质量问题如果错误均匀散布才考虑增加数据和模型容量。6.2 学习曲线用少量数据训练一次就知道够不够用另一个实用技巧是学习曲线。从训练集里随机抽 10%、25%、50% 三个子集分别用同一套配置训练到收敛记录验证集准确率。如果准确率随数据量增加持续上升说明数据还有红利继续采集或增强有效如果三条线很快重合说明模型容量到顶加数据意义不大。# 伪代码示意 for frac in [0.1, 0.25, 0.5]: subset random.sample(train_files, int(len(train_files) * frac)) # 用子集训练同一套配置记录 val acc这套方法在 4242 张图片的规模下跑三次很快。对于这种五分类的花类数据集典型结果是 10% 到 50% 段准确率明显上升接近全量时曲线变平说明现有数据量已经基本够用继续提升精度应该转向清洗和增强而不是盲目换大模型。从那以后我每次拿到新数据集都强制走一遍这个流程先解压校验再统计尺寸和数量然后目录切分和清洗最后才谈训练。这份花类识别数据集虽然分辨率不高但类别清晰、体量适中足够跑通一条从数据到模型的完整链路。希望这份踩坑记录能帮你省下一些查错时间。本文还有配套的精品资源点击获取
返回列表