基于VGG的自然灾害图像分类:迁移学习与Grad-CAM实战
简介这份资源面向图像识别与机器学习方向的初学者及进阶开发者聚焦自然灾害场景的自动分类任务帮助读者理解如何用VGG卷积神经网络完成从数据预处理到模型训练与评估的完整流程。压缩包共29个文件约1.54MB包含5个Python脚本、2个Jupyter Notebook、7张示例图片以及配置文件、CSV数据表、日志与说明文档等覆盖数据清洗、可视化、单通道分类、模型评估指标记录等模块目录结构清晰便于按环节查阅。已有70人学习下载。读者可从中获得一套可复用的灾害图像分类实践方案包括数据增强与归一化思路、VGG特征提取与CNN分类器搭建方法、召回率与Kappa等评估指标的记录方式以及数据可视化脚本适合用于课程设计、项目练手或灾害识别相关课题的快速起步。1. 基于 VGG 的自然灾害图像分类从一张航拍图说起你手里有一批灾后航拍图想快速分出哪些区域是洪水、哪些是山火、哪些是地震废墟。人工一张张看眼睛看花了也标不完几千张。基于 VGG 的自然灾害图像分类要解决的就是这件事拿一个在 ImageNet 上预训练过的 VGG 网络用少量标注好的灾害图像做迁移学习让模型自动把输入图片分到对应灾种。它适合有几百到几千张标注图、算力只有一张消费级显卡的团队也适合想入门图像分类算法的新手拿它练手。森林图像分类、遥感灾损评估这类场景用 VGG 做基线往往比从头训一个 CNN 更稳。下面把我实际做这套方案时踩过的路讲清楚。2. VGG 为什么还值得拿来分类自然灾害图像2.1 VGG 的结构特点与选它的理由VGG 的核心设计非常规整全部用 3×3 小卷积核堆叠每经过一段卷积就接一次最大池化把特征图缩小一半通道数从 64 一路翻倍到 512。这种「深而窄」的结构带来的好处是特征提取层次清晰——浅层卷积抓边缘和纹理深层卷积抓语义块。自然灾害图像里洪水的水面反光、山火的烟雾纹理、地震废墟的碎块边缘恰好都是这种层次化特征能覆盖的。选 VGG 而不是更新的模型理由有三条。第一它的结构简单到可以手写出来调试时每一层的输出尺寸都能心算出问题好定位。第二ImageNet 预训练权重到处都有迁移学习时冻结卷积层只训分类头几百张图就能出效果。第三它的参数量集中在最后三个全连接层想压缩模型时改这里最直接。最新的图像分类模型比如 ConvNeXt、ViT 精度确实更高但在灾害图像这种样本量不大、类别间差异明显的任务上VGG 的性价比反而突出。2.2 迁移学习冻结哪些层、替换哪一层直接拿 VGG 的原始分类头1000 类来用是不行的必须换成自己的类别数。常见做法是保留全部卷积层和第一个全连接层把最后那个 1000 维输出层换成 N 维N 是你的灾种数。冻结策略上我一般先冻结所有卷积层只训分类头 5 到 10 个 epoch等 loss 降下来再解冻最后两个卷积块做微调。这样做的原因是卷积层学到的通用特征边缘、纹理对灾害图像同样有效而分类头需要重新学「哪些特征组合对应洪水」。import torch import torch.nn as nn from torchvision import models # 加载预训练 VGG16weights 用默认的 ImageNet 权重 vgg models.vgg16(weightsmodels.VGG16_Weights.DEFAULT) # 冻结所有卷积层参数迁移学习初期只训分类头 for param in vgg.features.parameters(): param.requires_grad False # 替换分类头原为 1000 类改为 4 类洪水/山火/地震/正常 vgg.classifier[6] nn.Linear(4096, 4) # 只把分类头的参数交给优化器 optimizer torch.optim.Adam(vgg.classifier.parameters(), lr1e-3)这段代码的关键在requires_grad False和classifier[6]两处。冻结卷积层后反向传播不会更新它们的权重显存占用和训练时间都大幅下降。classifier[6]是 VGG 分类器的最后一层输入维度 4096输出改成你的类别数。学习率设 1e-3 是因为只训分类头可以激进一点等解冻卷积层微调时要降到 1e-4 甚至 1e-5否则预训练权重会被破坏。2.3 数据准备灾害图像的目录结构与增强策略数据按类别分文件夹放这是 PyTorchImageFolder能直接读的格式。每个类别至少准备 150 到 200 张太少的话分类头学不动。图像统一缩放到 224×224这是 VGG 的标准输入尺寸不改的话全连接层的维度对不上。# 目录结构示例 dataset/ ├── flood/ # 洪水约 300 张 ├── wildfire/ # 山火约 250 张 ├── earthquake/ # 地震废墟约 200 张 └── normal/ # 正常场景约 300 张增强策略上灾害图像不适合用太激进的随机裁剪因为灾种的关键特征可能就在图像边缘。我一般用随机水平翻转、小角度旋转±15 度、颜色抖动亮度/对比度 0.2。颜色抖动对洪水识别有帮助因为不同光照下水面颜色差异大。验证集只做缩放和归一化不做增强否则评估结果不可比。from torchvision import transforms train_transform transforms.Compose([ transforms.Resize((224, 224)), transforms.RandomHorizontalFlip(p0.5), transforms.RandomRotation(15), transforms.ColorJitter(brightness0.2, contrast0.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((224, 224)), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ])归一化用的均值和标准差是 ImageNet 的统计值因为预训练权重就是在这个分布上学的保持一致才能让卷积层输出的特征分布匹配。这一步不做的话训练 loss 会震荡得厉害收敛慢很多。3. 训练流程与关键参数怎么定3.1 两阶段训练先训头再微调整个训练分两个阶段。第一阶段冻结卷积层只训分类头学习率 1e-3跑 10 个 epoch。第二阶段解冻最后两个卷积块features[24:]学习率降到 1e-4再跑 15 到 20 个 epoch。这样安排是因为分类头随机初始化时梯度很大如果同时更新卷积层预训练权重会被冲乱。# 第一阶段结束后解冻最后两个卷积块 for param in vgg.features[24:].parameters(): param.requires_grad True # 重新设置优化器学习率降一个数量级 optimizer torch.optim.Adam([ {params: vgg.features[24:].parameters(), lr: 1e-4}, {params: vgg.classifier.parameters(), lr: 1e-4} ])features[24:]是 VGG16 的第四个和第五个卷积块这两块学到的特征更偏向语义微调它们能让模型适应灾害图像的特殊纹理。前面的浅层卷积保持冻结因为边缘和纹理特征是通用的没必要动。3.2 学习率、batch size 与 epoch 的取值依据学习率方面第一阶段 1e-3 是 Adam 的常用起点如果 loss 在前 3 个 epoch 不降降到 5e-4。第二阶段 1e-4 是微调预训练模型的安全值再高容易把卷积核权重带偏。batch size 取决于显存VGG16 在 224×224 输入下8GB 显存大概能跑 batch size 1612GB 能跑 32。batch size 太小比如 4会让 batch norm 统计不准loss 波动大。epoch 数不是固定的看验证集准确率。我一般设 early stopping验证集 loss 连续 5 个 epoch 不降就停。实际跑下来20 到 30 个 epoch 基本收敛。如果验证集准确率卡在某个值上不去先检查数据标注有没有错再考虑加数据或换更强的增强。# 训练循环核心逻辑 for epoch in range(30): vgg.train() for imgs, labels in train_loader: imgs, labels imgs.to(device), labels.to(device) optimizer.zero_grad() outputs vgg(imgs) loss criterion(outputs, labels) loss.backward() optimizer.step() # 验证阶段 vgg.eval() correct, total 0, 0 with torch.no_grad(): for imgs, labels in val_loader: imgs, labels imgs.to(device), labels.to(device) outputs vgg(imgs) _, predicted torch.max(outputs, 1) total labels.size(0) correct (predicted labels).sum().item() print(fEpoch {epoch}, Val Acc: {correct/total:.4f})验证阶段必须调vgg.eval()否则 batch norm 会用当前 batch 的统计量导致评估结果不稳定。torch.no_grad()关掉梯度计算省显存也加速。3.3 类别不平衡的处理加权损失与重采样灾害图像天然不平衡——正常场景的图好找地震废墟的图难凑。如果正常类有 1000 张而地震类只有 150 张模型会倾向于把大多数图预测成正常。解决办法有两个给损失函数加类别权重或者对少数类过采样。# 按类别样本数计算权重样本越少权重越高 class_counts [300, 250, 200, 300] # 对应 flood/wildfire/earthquake/normal total sum(class_counts) weights [total / (len(class_counts) * c) for c in class_counts] class_weights torch.tensor(weights, dtypetorch.float).to(device) criterion nn.CrossEntropyLoss(weightclass_weights)权重的计算逻辑是总样本数除以类别数乘以该类样本数这样少数类的 loss 会被放大模型不敢忽略它。过采样则是在 DataLoader 里用WeightedRandomSampler让每个 batch 里各类别比例接近。两种方法选一种就行同时用可能过拟合少数类。4. 避坑与排查我踩过的五个坑4.1 验证集准确率虚高实际预测全错现象训练时验证集准确率到 95%但拿新图预测结果乱七八糟。原因验证集和训练集来自同一批数据只是随机划分图像之间高度相似比如同一场洪水的连续航拍帧模型记住了特定场景而不是灾种特征。解决按场景或地理位置划分数据集同一场灾害的图要么全在训练集要么全在验证集。如果数据来源单一至少做 5 折交叉验证看方差。4.2 Loss 降到 0.2 就不动了准确率卡在 60%现象训练 loss 正常下降但验证准确率死活上不去。原因学习率在第二阶段没降下来或者解冻的层太多。VGG16 有 13 个卷积层如果全部解冻而学习率还是 1e-3预训练权重会被破坏。解决只解冻最后两个卷积块学习率降到 1e-4。如果还不行检查数据标注——我遇到过把「洪水退去后的泥地」标成「洪水」的情况模型学混了。4.3 显存不够batch size 只能设 4现象8GB 显存跑 VGG16batch size 设 8 就 OOM。原因VGG16 的全连接层参数量巨大第一个全连接层是 25088×4096显存主要耗在这里。解决把图像尺寸从 224 降到 192 或 160全连接层输入维度会相应减小。或者用梯度累积——batch size 设 4累积 4 次梯度再更新等效 batch size 16。# 梯度累积示例 accum_steps 4 optimizer.zero_grad() for i, (imgs, labels) in enumerate(train_loader): outputs vgg(imgs) loss criterion(outputs, labels) / accum_steps loss.backward() if (i 1) % accum_steps 0: optimizer.step() optimizer.zero_grad()4.4 预测结果全是同一类现象模型对任何输入都输出「洪水」。原因类别权重没设或者少数类样本太少导致模型直接放弃。解决先打印训练集各类别数量如果某类少于 100 张要么补数据要么用加权损失。另外检查 DataLoader 的 shuffle 是否开启不 shuffle 的话模型会按顺序学到类别分布。4.5 推理速度太慢单张图要 200ms现象部署时发现 VGG16 推理太慢满足不了实时需求。原因全连接层计算量大且没有做推理优化。解决把模型转成 ONNX 或 TorchScript用torch.jit.trace导出。如果还慢考虑把 VGG 的全连接层换成全局平均池化参数量从 1.2 亿降到 1500 万精度掉 1 到 2 个点但速度翻倍。# 用 TorchScript 加速推理 vgg.eval() example torch.randn(1, 3, 224, 224).to(device) traced torch.jit.trace(vgg, example) traced.save(vgg_disaster.pt) # 加载时直接跑不用再建模型 loaded torch.jit.load(vgg_disaster.pt) output loaded(example)5. 进阶技巧用 Grad-CAM 看模型到底在看哪里模型训完了准确率也还行但你怎么知道它是在看洪水的水面还是在看图片角落的水印Grad-CAM 能把模型决策时关注区域热力图叠回原图这是验证模型是否学到正确特征的最直接手段。我一般会在验证集里挑几张预测正确的和几张预测错误的图分别跑 Grad-CAM对比热力图分布。import cv2 import numpy as np # 注册钩子拿目标层的梯度 gradients [] activations [] def save_gradient(module, grad_in, grad_out): gradients.append(grad_out[0]) def save_activation(module, input, output): activations.append(output) # 挂在最后一个卷积块上 target_layer vgg.features[28] target_layer.register_forward_hook(save_activation) target_layer.register_full_backward_hook(save_gradient) # 前向 反向 vgg.eval() output vgg(img_tensor) pred_class output.argmax(dim1).item() vgg.zero_grad() output[0, pred_class].backward() # 计算 Grad-CAM grad gradients[0].cpu().data.numpy()[0] # (512, 14, 14) act activations[0].cpu().data.numpy()[0] # (512, 14, 14) weights np.mean(grad, axis(1, 2)) # (512,) cam np.sum(weights[:, None, None] * act, axis0) cam np.maximum(cam, 0) cam cv2.resize(cam, (224, 224)) cam cam / cam.max()这段代码的关键在weights np.mean(grad, axis(1, 2))——对每个通道的梯度在空间维度上取平均得到该通道的重要性权重再用权重对特征图加权求和。np.maximum(cam, 0)是 ReLU只保留对目标类有正贡献的区域。最后归一化到 0 到 1叠回原图看。热力图出来后重点看两件事。第一高亮区域是否落在灾害主体上——洪水图应该亮在水面山火图应该亮在火焰和烟雾。如果亮在天空或地面说明模型学到了虚假相关。第二预测错误的图热力图往往亮在背景上这说明模型被背景带偏了需要补充背景多样的训练数据。我自己的习惯是每训完一个版本固定抽 20 张验证图跑 Grad-CAM存成对比图。如果新版本的热力图比旧版本更集中在灾害主体上即使准确率只涨了 1 个点我也认为这个版本更可靠。反过来准确率涨了但热力图散了多半是过拟合了训练集的背景。这套流程帮我省了很多「准确率虚高但上线翻车」的后悔药。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

JDK11核心新特性与升级实战:语法、API及GC全面解析

JDK11核心新特性与升级实战:语法、API及GC全面解析

1. 为什么说JDK11是继JDK8之后最值得升级的版本JDK11确实是一个非常特殊的存在。作为Oracle在2018年9月发布的LTS版本,它既是Java 8之后第一个真正意义上的长期支持版本,又是Oracle调整Java版本发布节奏后的关键节点。对于做Java开发的同学来说&#xff…

2026/10/12 0:31:15 阅读更多 →
Python+OpenCV答题卡自动批改:检测、切分、考号识别与选择题评分

Python+OpenCV答题卡自动批改:检测、切分、考号识别与选择题评分

简介:这份源码包面向计算机、数学、电子信息等专业的学生与开发者,聚焦答题卡自动识别与批改场景,可用于课程设计、期末大作业、毕设项目或初期项目立项演示。项目基于Python实现答题卡检测、试题切分、学生考号识别与选择题自动批改&#xf…

2026/10/12 0:31:15 阅读更多 →
YOLO金鱼疾病检测数据集构建与训练调参实战

YOLO金鱼疾病检测数据集构建与训练调参实战

简介:这份资源是面向深度学习开发者、水产养殖研究者与鱼类爱好者的金鱼及疾病目标检测数据集,覆盖健康金鱼、腹水病、白点病与败血症等类别,可用于训练YOLO系列模型,实现家庭或商业养鱼场中金鱼疾病的自动识别与及时干预&#xf…

2026/10/12 0:31:15 阅读更多 →

最新新闻

JanusGraph 核心能力与存储后端选型:从超大规模图处理到 CAP 权衡

JanusGraph 核心能力与存储后端选型:从超大规模图处理到 CAP 权衡

图数据库分布式数据库后端 【免费下载链接】janusgraph JanusGraph: an open-source, distributed graph database 项目地址: https://gitcode.com/gh_mirrors/ja/janusgraph 点击查看 免费下载 导读:本文围绕 JanusGraph 官方文档《The Benefits of Ja…

2026/10/12 2:03:07 阅读更多 →
Langchain01_框架之模型的创建与调用

Langchain01_框架之模型的创建与调用

模型创建3种方式 1.使用特定的Model Class(最直接,但不好用) LangChain为一些大模型供应商提供了专门的Model类,导入对应的具体类(如 ChatOpenAI、ChatAnthropic、ChatDeepSeek、ChatOllama、ChatHunyuan、ChatTongy…

2026/10/12 2:03:07 阅读更多 →
ET高级定制版与睿排引擎:从智能排版到可打印的完整工程实践

ET高级定制版与睿排引擎:从智能排版到可打印的完整工程实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 2:03:07 阅读更多 →
SQL练习题全解析:从建表到嵌套查询的避坑指南

SQL练习题全解析:从建表到嵌套查询的避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 2:03:07 阅读更多 →
MySQL存储引擎深度对比:InnoDB与MyISAM的差异、调优与迁移实践

MySQL存储引擎深度对比:InnoDB与MyISAM的差异、调优与迁移实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 2:03:07 阅读更多 →
PaperSpine 执行效率方法论:精确复用、昂贵操作凭证与有界失败恢复的工程实践

PaperSpine 执行效率方法论:精确复用、昂贵操作凭证与有界失败恢复的工程实践

AI 技能AI 写作人工智能深度研究AI 应用 【免费下载链接】PaperSpine PaperSpine5 — local-first, evidence-bound paper research, writing, figures, review and delivery. Download: https://wubing2023.github.io/PaperSpine/v5/ 项目地址: https://gitcode.co…

2026/10/12 2:02:07 阅读更多 →

日新闻

复古胶片颗粒感噪点合成器:Canvas ImageData 像素高斯杂色注入算法

复古胶片颗粒感噪点合成器:Canvas ImageData 像素高斯杂色注入算法

在数码相机、高清显示屏与现代矢量图形技术高度发达的今天,画面可以做到绝对的锐利、平滑与无瑕。然而,当一张秋日手账插画或拍立得照片过于“平整无瑕”时,往往会散发出一种冰冷生硬的“数码塑料感(Digital Plasticity&#xff0…

2026/10/12 0:00:59 阅读更多 →
活字印刷古籍线装排版:Canvas 竖排文字与栏线自适应算法

活字印刷古籍线装排版:Canvas 竖排文字与栏线自适应算法

在现代网页与移动端设计中,横排(Horizontal Layout)早已经成为了绝对的主流。然而,当我们翻开泛黄的线装古籍、宋版木刻诗集,或是欣赏一张茶道雅集的手写便签时,那种**自上而下纵向书写、自右向左逐列铺展&…

2026/10/12 0:00:59 阅读更多 →
周日晚间的“精神松绑减震器”:无压力情绪倾倒箱与温和轻声陪伴

周日晚间的“精神松绑减震器”:无压力情绪倾倒箱与温和轻声陪伴

每到周日的晚上八点到十点,很多人心里都会悄悄亮起一盏警示灯。 在心理学上,这种现象有一个专门的称谓——“周日夜晚焦虑症(Sunday Scaries)”。明天又是周一,闹钟又要重新在七点响彻卧房;脑海里仿佛有一个…

2026/10/12 0:00:59 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/12 0:16:30 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/12 0:16:38 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/12 0:16:43 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 10:45:37 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 14:36:53 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 14:36:54 阅读更多 →