news 2026/10/11 6:33:57

林业害虫图像分类数据集实战:从数据划分到ResNet50基线训练

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
林业害虫图像分类数据集实战:从数据划分到ResNet50基线训练

简介:这份资源面向从事林业害虫识别、生态监测或图像分类实践的研究者与开发者,提供一套已划分完毕的森林有害昆虫图像分类数据集,可直接用于深度学习模型训练与验证。包内共1884个文件,以1855张jpg图像为主,辅以少量png、jpeg、gif样本,另附1个py可视化脚本与1个json字典文件,压缩包约105MB。数据按train与test两个目录组织,训练集1537张、测试集344张,覆盖寒蝉、甲壳虫等99类林业害虫,并配有99类分类字典文件,使用ImageFolder即可直接加载,无需额外清洗或重命名。资源还提供随机抽取4张图片并保存到当前目录的可视化脚本,无需修改即可运行,便于快速核验类别与图像质量。目前已有761人学习下载,适合作为图像分类入门练手、课程实验或yolov5分类任务的现成数据基础。

1. 林业害虫图像分类数据集:从“拿到手”到“跑出第一组指标”的距离

林业害虫识别这件事,真正卡住人的往往不是模型结构,而是数据。你手上有一堆林间拍摄的昆虫照片,光照忽明忽暗、背景全是树皮和叶片、虫体还特别小,想训一个能用的分类器,第一步就卡在“标注、清洗、划分”上。这份森林有害昆虫、害虫图像分类数据集,核心价值就在于它已经把最耗人力的划分环节做完了——训练集、验证集、测试集按目录分好,标签直接对应文件夹名,拿到手就能接ImageFolder或tf.keras.utils.image_dataset_from_directory。它适合三类人:刚入门图像分类想找一个非玩具级数据集练手的、做林业虫情监测需要快速验证模型可行性的、以及想拿真实场景数据对比 ResNet、ViT 等骨干网络表现的从业者。下面我按“这份数据长什么样 → 怎么接进训练流程 → 哪里容易翻车 → 怎么把指标做扎实”的顺序拆一遍。

2. 数据集结构与标签体系:先看清目录再动手

2.1 目录组织与类别命名

这类已划分的数据集,常见做法是顶层放train、val、test三个目录,每个目录下再按类别建子文件夹,子文件夹名就是类别标签。图像文件直接躺在对应类别目录里。这种结构的好处是 PyTorch 的ImageFolder和 TensorFlow 的image_dataset_from_directory都能零配置读取,标签自动从文件夹名映射成整数索引。

拿到数据后第一件事不是写模型,而是把目录结构打印出来确认。我一般会跑一段脚本统计每个类别的样本数,顺便检查有没有空文件夹或者命名不一致的情况。

import os from collections import Counter root = "insect_dataset" # 数据集根目录 for split in ["train", "val", "test"]: split_dir = os.path.join(root, split) if not os.path.isdir(split_dir): print(f"[跳过] {split} 目录不存在") continue classes = sorted(os.listdir(split_dir)) print(f"\n=== {split} | 类别数: {len(classes)} ===") counter = Counter() for cls in classes: cls_dir = os.path.join(split_dir, cls) if not os.path.isdir(cls_dir): continue # 只统计常见图像后缀,避免把 .DS_Store 之类算进去 n = len([f for f in os.listdir(cls_dir) if f.lower().endswith((".jpg", ".jpeg", ".png", ".bmp"))]) counter[cls] = n for cls, n in counter.most_common(): print(f" {cls:<30} {n}") print(f" 合计: {sum(counter.values())}")

这段脚本做三件事:遍历三个划分目录、按类别统计图像数量、打印排序后的分布。参数上,root换成你解压后的实际路径即可;后缀过滤那行是关键,很多数据集里混着缩略图缓存文件,不过滤会虚高计数。跑完你就能一眼看出类别是否均衡——如果某个类别只有个位数样本,后面训练策略就得单独处理。

2.2 标签映射与类别不平衡的初步判断

ImageFolder会按文件夹名的字母序生成class_to_idx,这个映射顺序在不同机器上只要文件夹名一致就是稳定的,但你要把它存下来,推理阶段必须用同一套映射,否则预测结果会张冠李戴。常见做法是训练完把dataset.classes和dataset.class_to_idx一起写进 json。

类别不平衡是这类林业数据集的常态。林间某些害虫出现频率天然就低,采集到的图像自然少。判断标准很简单:最多类样本数除以最少类样本数,超过 5 就值得警惕,超过 10 基本必须做重采样或加权。处理方式我一般分两步走:先算类别权重传给损失函数,再考虑对少数类做增强。权重公式用总样本数 / (类别数 * 该类样本数),这是 sklearn 里compute_class_weight的默认逻辑,自己实现也就几行。

import json import numpy as np from torchvision import datasets train_ds = datasets.ImageFolder(os.path.join(root, "train")) counts = np.bincount([label for _, label in train_ds.samples]) class_weights = len(train_ds) / (len(counts) * counts) print("类别权重:", dict(zip(train_ds.classes, class_weights.round(3)))) # 保存映射,推理时复用 with open("class_mapping.json", "w", encoding="utf-8") as f: json.dump({"classes": train_ds.classes, "class_to_idx": train_ds.class_to_idx}, f, ensure_ascii=False, indent=2)

counts是每个类别的样本数数组,class_weights直接可以转成 tensor 传给CrossEntropyLoss(weight=...)。注意权重只作用于训练集统计,别拿验证集或测试集的分布去算,那属于信息泄漏。存映射这步看着琐碎,但我见过太多人训完模型忘了存,换台机器推理时类别顺序对不上,排查半天才发现是字母序变了。

3. 接进训练流程:从 DataLoader 到第一轮 baseline

3.1 数据增强与预处理管道

林业害虫图像有几个特点:虫体占比小、背景纹理复杂、拍摄角度和光照差异大。预处理管道的设计要围绕这几点。训练阶段我一般用随机裁剪加缩放、水平翻转、轻度颜色抖动;验证和测试阶段只做缩放和中心裁剪,保持确定性。输入尺寸上,ResNet 系列常用 224,ViT 常用 224 或 384,如果你显存够且虫体确实小,可以上 384 试试,但要注意小样本类别在放大后可能更糊。

from torchvision import transforms train_tf = transforms.Compose([ transforms.RandomResizedCrop(224, scale=(0.6, 1.0)), # 容忍虫体大小差异 transforms.RandomHorizontalFlip(p=0.5), transforms.ColorJitter(brightness=0.3, contrast=0.3, saturation=0.2), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), ]) eval_tf = 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]), ])

RandomResizedCrop的scale下限设到 0.6 而不是默认的 0.08,是因为林间图像裁太狠容易把虫体整个裁掉,留下纯背景反而制造噪声标签。颜色抖动的幅度也别开太大,害虫的体色有时是分类依据,抖过头等于把判别特征抹了。归一化参数用的是 ImageNet 统计量,这是迁移学习的标准做法,除非你的数据分布和 ImageNet 差得离谱,否则不用改。

3.2 用 ResNet50 跑通第一个 baseline

选 ResNet50 做 baseline 的理由很实际:预训练权重好找、结构稳定、社区踩坑记录多,出问题容易搜到答案。加载时把weights设成预训练版本,然后把最后的全连接层换成你的类别数。训练策略上,先冻结骨干只训分类头几轮,再解冻整体微调,这样收敛更稳。

import torch import torch.nn as nn from torch.utils.data import DataLoader from torchvision import models, datasets train_ds = datasets.ImageFolder(os.path.join(root, "train"), transform=train_tf) val_ds = datasets.ImageFolder(os.path.join(root, "val"), transform=eval_tf) train_loader = DataLoader(train_ds, batch_size=32, shuffle=True, num_workers=4) val_loader = DataLoader(val_ds, batch_size=32, shuffle=False, num_workers=4) model = models.resnet50(weights=models.ResNet50_Weights.IMAGENET1K_V2) num_classes = len(train_ds.classes) model.fc = nn.Linear(model.fc.in_features, num_classes) # 替换分类头 model = model.cuda() criterion = nn.CrossEntropyLoss(weight=torch.tensor(class_weights, dtype=torch.float).cuda()) optimizer = torch.optim.AdamW(model.parameters(), lr=1e-4, weight_decay=1e-4) scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=20)

batch_size设 32 是 8G 显存下的稳妥值,显存大可以往上加,但学习率要同步调。AdamW的weight_decay设 1e-4 是微调场景的常用起点。损失函数带上了前面算的类别权重,这是应对不平衡最省事的一招。训练循环里每轮跑完验证集算准确率和 macro-F1,macro-F1 比准确率更能反映少数类的表现,别只看 accuracy 就以为模型没问题。

3.3 训练循环与指标记录

训练循环本身不复杂,关键是记录要全。我习惯每轮存训练损失、验证损失、验证准确率、验证 macro-F1 四个数,最后画曲线判断是过拟合还是欠拟合。验证损失开始上升而训练损失还在降,就是过拟合信号,该上早停或加正则了。

from sklearn.metrics import f1_score best_f1 = 0.0 for epoch in range(20): model.train() for imgs, labels in train_loader: imgs, labels = imgs.cuda(), labels.cuda() optimizer.zero_grad() loss = criterion(model(imgs), labels) loss.backward() optimizer.step() scheduler.step() model.eval() preds, gts = [], [] with torch.no_grad(): for imgs, labels in val_loader: out = model(imgs.cuda()) preds.extend(out.argmax(1).cpu().tolist()) gts.extend(labels.tolist()) f1 = f1_score(gts, preds, average="macro") print(f"epoch {epoch+1} | val macro-F1: {f1:.4f}") if f1 > best_f1: best_f1 = f1 torch.save(model.state_dict(), "best_resnet50.pth")

average="macro"表示每个类别等权,少数类表现差会直接拉低分数,这正是我们想监控的。保存最优权重按 macro-F1 而不是准确率来选,能避免模型偏向多数类。这套流程跑下来,你至少能得到一个可复现的 baseline,后面换 ViT 或者调参都有参照。

4. 避坑与排查:林业害虫数据集上最容易翻车的五件事

4.1 训练集和验证集出现同一只虫的连拍

现象:验证准确率异常高,换测试集直接掉一大截。原因:林间采集常对同一只虫连拍多张,划分时如果按图像随机分,同一只虫的不同角度可能同时进了训练和验证,模型等于提前见过。解决:划分要按“个体”或“拍摄批次”分组,同一只虫的所有照片只能进同一个划分。拿到已划分数据集时,先抽查验证集里有没有和训练集视觉上高度相似的图,有的话说明划分可能有问题。

4.2 背景成了分类依据

现象:模型在测试集上表现不错,但换一片林子拍的照片就崩。原因:某些类别的样本恰好都在相似背景(比如某类总在树干上拍,另一类总在叶片上拍),模型学的是背景不是虫。解决:加强背景随机化增强,或者用 Grad-CAM 可视化看模型关注区域是不是落在虫体上。如果热力图集中在背景,就得考虑重新采样或做前景分割。

4.3 图像尺寸和通道不统一

现象:DataLoader 报错或者训练时 loss 变 NaN。原因:数据集里混着灰度图、RGBA 图、超大分辨率图,ToTensor之后通道数或尺寸对不上。解决:写一个清洗脚本统一转成 RGB 三通道,超过一定尺寸的等比缩小。下面这段可以批量处理。

from PIL import Image import os def clean_images(folder, max_side=1024): for dirpath, _, files in os.walk(folder): for fn in files: if not fn.lower().endswith((".jpg", ".jpeg", ".png", ".bmp")): continue fp = os.path.join(dirpath, fn) try: img = Image.open(fp).convert("RGB") # 强制三通道 if max(img.size) > max_side: img.thumbnail((max_side, max_side)) img.save(fp) except Exception as e: print(f"损坏文件 {fp}: {e}")

convert("RGB")把灰度、RGBA 统一成三通道,thumbnail等比缩放不拉伸。损坏文件直接打印出来人工确认,别默默删掉,万一是关键类别样本。

4.4 类别权重设了但忘了同步到验证

现象:训练 loss 正常下降,但验证 macro-F1 一直很低。原因:损失函数带了类别权重,模型被推着去拟合少数类,但验证时如果还用带权重的指标或者错误的评估方式,数字会误导你。解决:验证阶段用不带权重的 macro-F1 和每类召回率,单独看少数类到底有没有被救回来。权重只影响训练,不影响评估口径。

4.5 测试集被反复用来调参

现象:测试集指标很好看,上线后一塌糊涂。原因:调参过程中反复看测试集结果,等于把测试集当验证集用了,模型对测试集过拟合。解决:测试集只在最终确定模型后跑一次。调参全程只看验证集。这份数据集已经分好了三个划分,但用的时候要守住这条线,否则划分就白做了。

5. 把指标做扎实:混淆矩阵、Grad-CAM 与推理脚本

5.1 用混淆矩阵定位具体错在哪

macro-F1 只给你一个总数,真正要改模型得知道哪两类在互相混。混淆矩阵按行归一化之后,对角线是召回率,非对角线是误分去向。林业害虫里形态相近的物种最容易混,比如某些蛾类,看矩阵就能定位。

import matplotlib.pyplot as plt from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay cm = confusion_matrix(gts, preds, normalize="true") disp = ConfusionMatrixDisplay(cm, display_labels=train_ds.classes) fig, ax = plt.subplots(figsize=(10, 10)) disp.plot(ax=ax, xticks_rotation=90, cmap="Blues") plt.tight_layout() plt.savefig("confusion_matrix.png", dpi=150)

normalize="true"按真实标签归一化,每行加起来是 1,读起来就是“这个类有多少比例被分对了”。如果某两类互相误分比例都高,说明特征区分度不够,可以考虑加细粒度增强或者换更强的骨干。

5.2 Grad-CAM 确认模型看的是虫不是背景

Grad-CAM 不需要改模型结构,挂个 hook 就能出热力图。重点看热力图高亮区域是否落在虫体上。如果高亮在背景,前面说的背景依赖问题就坐实了。

from pytorch_grad_cam import GradCAM from pytorch_grad_cam.utils.image import show_cam_on_image target_layers = [model.layer4[-1]] # ResNet50 最后一个残差块 cam = GradCAM(model=model, target_layers=target_layers) # 取一张验证集图像,预处理后加 batch 维度 input_tensor = eval_tf(Image.open("sample.jpg").convert("RGB")).unsqueeze(0).cuda() grayscale_cam = cam(input_tensor=input_tensor)[0] visualization = show_cam_on_image(original_img_float, grayscale_cam, use_rgb=True)

target_layers选最后一个卷积阶段,分辨率够定位又不至于太粗。如果热力图散在整张图,说明模型没学到聚焦特征,可能需要加注意力模块或者换 ViT 试试。

5.3 推理脚本要带类别映射

最后落一个能直接用的推理脚本,关键是加载模型时同步加载class_mapping.json,保证输出标签和训练时一致。

import json import torch from PIL import Image from torchvision import transforms, models import torch.nn as nn with open("class_mapping.json", encoding="utf-8") as f: mapping = json.load(f) idx_to_class = {v: k for k, v in mapping["class_to_idx"].items()} model = models.resnet50() model.fc = nn.Linear(model.fc.in_features, len(mapping["classes"])) model.load_state_dict(torch.load("best_resnet50.pth")) model.eval().cuda() tf = transforms.Compose([ transforms.Resize(256), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]), ]) img = Image.open("test.jpg").convert("RGB") with torch.no_grad(): prob = torch.softmax(model(tf(img).unsqueeze(0).cuda()), dim=1)[0] top3 = prob.topk(3) for score, idx in zip(top3.values, top3.indices): print(f"{idx_to_class[idx.item()]}: {score.item():.4f}")

输出 top-3 而不是只给一个标签,是因为林间图像模糊时模型犹豫很正常,给出候选让使用者判断更实用。idx_to_class的反转映射必须来自训练时保存的文件,不能重新按字母序生成,这是保证一致性的最后一道关。

从那以后我每次拿到新数据集,第一件事都是先跑一遍目录统计和类别分布,再抽几张图肉眼过一遍,确认没有连拍泄漏和背景依赖,才敢开训。这套习惯帮我省下了大量返工时间。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/11 6:33:05

软考高项备考:每日5题拆解挣值管理与关键路径

3月12日&#xff0c;距离上半年软考高项&#xff08;信息系统项目管理师&#xff09;考试还有两个多月。这天晚上&#xff0c;我照例在备考群里发完当天的“每日5题”&#xff0c;顺手把解析整理到了个人笔记里。没想到五道题里有一道挣值管理的基础判断题&#xff0c;四个人错…

作者头像 李华
网站建设 2026/10/11 6:32:08

大模型多版本本地共存的终端配置工程实践

1. 为什么“多版本本地部署”不是炫技&#xff0c;而是真实工作流里的刚需我第一次在某高校实验室看到那台被贴满便签纸的旧工作站时&#xff0c;就意识到&#xff1a;所谓“大模型本地跑”&#xff0c;从来不是单选题。那台机器上同时挂着三个终端窗口——左边是ollama run ll…

作者头像 李华
网站建设 2026/10/11 6:31:28

Qwen3.8 Flash Next轻量部署实战:AutoDL+Strata低延迟推理方案

1. 项目概述&#xff1a;这不是一次普通的大模型部署&#xff0c;而是一次面向生产级推理的轻量化实战“从零开始部署Qwen3.8 Flash Next | AutoDL | Strata”——这个标题里藏着三个关键坐标&#xff1a;一个刚发布的轻量级大语言模型变体&#xff08;Qwen3.8 Flash Next&…

作者头像 李华
网站建设 2026/10/11 6:30:37

YOLOv9 + Triton 部署实战:从 ONNX 导出到生产级推理服务

简介&#xff1a;本资源是一套面向AI算法工程师与深度学习部署实践者的YOLOv9目标检测模型生产级部署方案&#xff0c;聚焦Triton Inference Server在工业场景中的落地应用&#xff0c;解决模型从训练到服务化推理的关键断点问题。压缩包共16个文件&#xff0c;含7个核心Python…

作者头像 李华
网站建设 2026/10/11 6:30:00

AI 编程的“永久记忆“:AOCI-CODE 让 Agent 一次读懂你的百万行代码库

文章目录 一、为什么你的 AI Agent 总是"失忆"? 二、AOCI-CODE 到底是什么? 三、四大核心能力:它到底能帮你做什么? 3.1 能力一:在 Agent 里持续迭代大型系统 3.2 能力二:一键理解已有系统,直接接手开发 3.3 能力三:换人、换 Agent、换对话,认知不丢 3.4 能…

作者头像 李华
网站建设 2026/10/11 6:27:03

Linux 日志增量统计:inode + offset 方案(不丢不重)

背景 我给 Nginx 缓存命中率写了个统计脚本&#xff0c;每 5 分钟跑一次&#xff0c;读 /var/log/nginx/dashboard_cache.log&#xff0c;统计 HIT/MISS 数量写进 MariaDB。 第一版逻辑很简单&#xff1a; tail -n 100 /var/log/nginx/dashboard_cache.log | awk {...}跑了两天…

作者头像 李华