CNN火灾识别实战:数据集、模型训练与部署调优全指南

发布时间:2026/9/23 9:45:43
CNN火灾识别实战:数据集、模型训练与部署调优全指南 简介一套基于PyTorch框架的卷积神经网络火灾识别项目面向深度学习初学者与计算机视觉开发者提供包含完整数据集和训练代码的落地参考可直接用于图像分类学习或火灾检测场景扩展。压缩包内共有250个文件含200张PNG图片、44张JPG图片以及3个TXT说明文件和3个Python脚本整体约173.55MB其中图片构成多类别火灾样本集文本记录图片路径与标签脚本覆盖数据预处理、模型训练与界面展示的完整流程。代码内置数据增强策略通过对图片的较短边增加灰边将非正方形图像补齐为正方形并结合多角度旋转扩充样本集以提升模型泛化能力。依次运行三个脚本即可完成训练数据生成、模型训练和可视化识别训练过程会保存模型权重并记录每个训练轮次的验证集损失值与准确率便于分析收敛效果。目前已有170人下载学习对于希望快速上手图像分类项目的开发者这套代码在数据处理和训练日志方面的设计具有实用参考价值。1. 基于CNN深度学习的火灾识别这个项目包里最值钱的不是模型代码是数据集“基于CNN深度学习的火灾识别-含数据集.zip”这串名字放到下载列表里很多人第一眼扫过去注意力全在“CNN”和“火灾识别”上觉得这就是个图像分类Demo。但把这个项目真正跑过一遍之后会发现最难的不是网络结构而是数据集的分布和质量。火灾识别做的是单帧画面判断——有火还是没火最多再加一个烟雾类别。听起来比目标检测简单实际操作时正样本数量少、类火物体干扰多、负样本难收集这三件事几乎决定了项目的成败。这个方案适合两类人一类是做课设或毕设、需要在一到两周内跑通一个完整项目的学生另一类是安防集成、消防预警场景里需要一个离线哨兵模型的技术人员。2. 火灾识别任务建模与 CNN 选型先想清楚模型要学什么2.1 第一道选择题二分类还是多分类数据集解压之后常见的组织方式是 fire 和 no_fire 两个目录复杂一点的会多一个 smoke 目录。这两个设计方案的差别很大。fire/no_fire 二分类训练难度低标注成本也低判定“有没有火”恰好覆盖大部分值班场景fire/smoke/no_fire 三分类更贴近消防预期但训练时模型很容易在烟火之间摇摆因为浓烟和火焰在纹理与颜色上是连续的边界本身就很模糊。我的习惯是先做二分类。如果实际场景里烟雾警报和火焰警报需要分开处理再考虑扩展成多分类而不是一上来就三分类。因为二分类输出的是一个概率阈值可以直接调多分类输出三个概率分布调一个类别时会牵动另外两个部署阶段的阈值逻辑变得复杂。对值班系统来说单概率阈值更简单可靠。如果你手里这个数据集里 smoke 样本只有几十张干脆并入 no_fire 或 fire不要硬撑三类——类别样本数差太多模型大概率会把 smoke 学成噪声。2.2 CNN 为什么适合这个任务边缘、纹理、颜色的层次抽象传统火灾检测靠颜色阈值在 RGB 或 HSV 空间里划定火焰的颜色范围对静态图基本无能为力对复杂背景更是误报频发靠帧差法只能检测运动目标摄像头轻微晃动就失效光流法对安装稳定性和算力要求都很高。CNN 解决的核心问题是把“火焰长得像什么”转化为可学习的层次特征浅层卷积核关注边缘和色块中层关注火焰的不规则轮廓深层关注“整团东西是不是火”。火灾图像恰好具备这种层次性火焰边缘破碎、内焰外焰颜色渐变、与背景对比强烈。CNN 的平移不变性让火焰出现在画面任何位置都能被识别这也是它替代传统视觉方法的关键。但副作用也要清楚——模型如果强依赖颜色特征就会把夕阳、红色车尾灯、工地安全帽这类区域激活。CNN 卷积神经网络代码骨架千篇一律真正的差别全在数据准备上这个项目最花时间的也是这一步。2.3 从 ResNet18 起步选型理由与替代方案模型建议从 ResNet18 起步而不是 VGG16。理由有三条火灾识别任务本身不复杂VGG 的参数量级在这里只会加速过拟合残差结构让梯度传递更稳定在不大的数据集上也更容易收敛torchvision 里有预训练权重迁移学习起步很快。训练脚本几乎可以照抄标准图像分类模板只改最后的全连接层输出维度。如果图像分辨率在 224×224 以下ResNet18 足够用如果数据集里大量存在“火焰只占画面一角”的远火场景可以把输入分辨率提到 320×320 并换 ResNet34。但分辨率每提高一档训练显存和时间都成倍增长不要盲目上大网络。后续要是部署到边缘设备换成 MobileNetV3 或 ShuffleNetV2训练脚本只改模型实例化那两行其他都不用动。2.4 识别之外什么时候该升级到目标检测标题里写的是“识别”本质是图像分类输出“有火”或“没火”。但如果你遇到的是多火源、需要判断火灾蔓延方向、或者要在监控大屏上圈出火焰位置分类模型做不到。这时应该切换到目标检测路线YOLO 训练自己的数据集是目前工程里最主流的做法检测框能直接打到监控画面上这是分类模型给不了的。不要期待分类模型输出火焰位置。很多项目做到一半发现“知道着火但不知道在哪”回头把分类任务硬改成检测数据集标注方式、损失函数、后处理全部要重来。所以拿到这个项目包时先确认自己的需求边界只要报警分类够用要定位直接改用检测方案别在分类模型上浪费时间。3. 数据集的组织与预处理从解压到可训练样本3.1 解压后第一件事扫描目录、统计类别、识别坏图拿到项目包第一件事不是打开训练脚本而是先扫描一遍数据集。常见问题是从网络收集的图片有的文件不完整、有的是 RGB 转 CMYK 导致读取异常、还有的混入了透明通道的 PNG。这些问题会在训练到一半时才暴露很难排查。先跑一段脚本做体检。import os from collections import Counter from PIL import Image root fire_dataset exts {.jpg, .jpeg, .png, .bmp} stats Counter() broken [] for dirpath, _, files in os.walk(root): for f in files: ext os.path.splitext(f)[1].lower() if ext not in exts: continue p os.path.join(dirpath, f) stats[dirpath.split(os.sep)[-1]] 1 try: img Image.open(p) img.verify() except Exception: broken.append(p) print(类别统计, stats) print(损坏图片数, len(broken)) for p in broken: print(p)这段脚本做三件事按目录统计图片数量、用 PIL 的 verify 方法校验文件完整性、把损坏图片的路径打印出来。参数注意点在于 exts 集合——把常见格式放进去但 .gif 和 .webp 建议直接忽略它们在训练框架里可能被当作多通道动画处理后续会出幺蛾子。broken 列表里的文件直接移出数据集目录。如果损坏图片数量超过几十张说明数据集来源混杂要警惕整体质量。这一步的产出不只是干净数据还顺带确认了目录结构。PyTorch 的 ImageFolder 机制要求 train 目录下按类别分子文件夹比如 train/fire、train/no_fire类别名就是文件夹名。如果你的数据集是 CSV 标签或 VOC 格式要在这里就转换成这种目录结构别拖到配置数据加载器时再改。3.2 按视频片段划分 train/val避免数据泄漏这个坑几乎每个火灾识别项目都会踩一次。很多火灾数据集是从监控视频里抽帧得到的同一个片段的连续帧几乎一模一样。如果直接把所有图片随机打散成训练集和验证集验证集会包含大量训练集的“近亲”离线精度虚高。部署到现场时模型面对的全是新场景马上被打回原形。正确做法是按来源分组。文件名里通常有场景标识比如“scene07_frame0123.jpg”这种规律按 scene 字段分组整组切分import os import random random.seed(0) src fire_dataset/fire groups {} for name in os.listdir(src): gid name.split(_)[0] groups.setdefault(gid, []).append(name) gids list(groups.keys()) random.shuffle(gids) val_gids set(gids[: int(len(gids) * 0.2)]) val_files [f{gid}_{fn} for gid in val_gids for fn in groups[gid]]关键在“分组”而不是“分图”。gid 取文件名第一段作为场景标识按组切分保证同一个视频片段的所有帧要么全在训练集、要么全在验证集。val_gids 取 20% 的场景组这一组里所有图片进验证集。如果数据集命名没有这种规律就按拍摄目录、按日期目录划分原则是“同一来源的图片不能同时出现在两边”。划分完成后用脚本检查一下训练集和验证集的源标识是否有重合确认没有文件级重叠再进入训练。这一步的成本只有几分钟却能避免后面精读虚高、返工数天的尴尬。3.3 数据增强的边界别把火焰颜色增强没了火灾识别里颜色是强特征所以数据增强要刻意克制。下面这组 transform 是我常用的起点from torchvision import transforms train_transform transforms.Compose([ transforms.Resize((224, 224)), transforms.RandomHorizontalFlip(p0.5), transforms.RandomRotation(degrees10), transforms.ColorJitter(brightness0.2, contrast0.2, saturation0.2, hue0), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ])Resize 统一输入尺寸水平翻转对火焰有效火焰没有方向性RandomRotation 限制在 ±10 度超过这个范围火焰形态会失真也不符合固定摄像头画面里火源基本在水平方向蔓延的常识。ColorJitter 这里刻意把 hue 设为 0——色调偏移会把火焰从红黄变成紫绿模型会学到“颜色多变的才是火”这种错误概念。brightness、contrast、saturation 的小幅扰动可以模拟不同光照和摄像头参数但幅度控制在 0.2 以内。如果数据集本身曝光差异很大再放开到 0.3 左到顶。验证集和测试集不要加任何随机增强只做 Resize、ToTensor 和 Normalize。注意 Normalize 用的 mean 和 std 是 ImageNet 的统计量因为迁移学习加载的预训练权重就是在这个分布上训练的。如果你从头训练模型这里要改成数据集自己的统计量否则输入分布不匹配。4. 训练脚本与参数调优从能跑通到收敛4.1 最小可运行训练脚本模型加载、数据加载与训练循环这段代码是 CNN 卷积神经网络代码的标准骨架。假设第 3 章的 transform 已经定义好了训练部分这样写import torch import torch.nn as nn from torchvision import models from torch.utils.data import DataLoader, ImageFolder device cuda if torch.cuda.is_available() else cpu model models.resnet18(weightsDEFAULT) model.fc nn.Linear(512, 2) model model.to(device) dataset ImageFolder(dataset/train, transformtrain_transform) loader DataLoader(dataset, batch_size32, shuffleTrue, num_workers4) loss_fn nn.CrossEntropyLoss() optimizer torch.optim.Adam(model.parameters(), lr0.001)逻辑说明ImageFolder 按目录结构自动生成标签train 文件夹下两个子目录 fire、no_fire 分别对应类别 0 和 1。ResNet18 的 fc 层替换成输出维度 2 的全连接层前面的卷积层直接加载预训练权重。CrossEntropyLoss 是分类任务的标准选择。Adam 优化器 0.001 学习率起步但要收敛到更低 loss 需要配合学习率衰减。训练循环本身不复杂核心是每个 epoch 计算 loss 和 accuracy验证阶段关掉梯度。数据量只有几千张时在预训练权重基础上全量微调 10 到 20 个 epoch 基本就能收敛。学习率衰减建议用 StepLR每 5 个 epoch 乘 0.1或者用 ReduceLROnPlateau 监控验证集 loss 自动降学习率。从头训练整个 ResNet18 在几千张图上不太现实也容易过拟合迁移学习是这个任务的最优解。4.2 五个必调参数学习率、批量大小、冻结层数、轮数与阈值训练阶段需要细调的参数并不多大部分用默认值就能跑通但这五个会直接影响结果。参数起点值调节建议学习率0.0001-0.001预训练模型建议 0.0001 起从头训练可用 0.001batch size32显存允许越大越好BN 层要求 batch 大于 8冻结层数预训练模型不冻结数据量小于五千张时冻结前几层训练轮数20-30配合早停以验证集 loss 为准分类阈值0.5火灾识别建议 0.3-0.4降低漏报代价这五个参数里学习率和 batch size 的关系最密切。学习率过大第一个 epoch 的 loss 直接飙升到十几甚至 NaNbatch size 太小BatchNorm 的统计量不稳定loss 曲线会像锯齿一样跳动。冻结层数适合数据量不足的场合比如只有两三千张图可以把 layer1 到 layer3 全部冻结只训练 layer4 和 fc 层收敛更快也更稳。训练轮数不要死板地定 30配早停看验证集 loss 才是正路。分类阈值严格来说是部署参数不是训练参数但很多人在测试脚本里忽略它。默认 0.5 的阈值意味着只有超过 50% 概率才报警。火灾场景里宁可误报让值班人员去确认也不能漏报让火情扩大所以阈值下调到 0.3 甚至 0.25 是合理操作。误报的代价是人工确认漏报的代价是事故两者权衡没有悬念。4.3 Checkpoint 与早停给训练上后悔药火灾数据集往往不大网络在训练后期波动明显。常见做法是每个 epoch 结束后保存一次权重验证集 loss 连续几个 epoch 不降就提前停止best_loss float(inf) patience 5 bad_epochs 0 for epoch in range(30): train_loss train_one_epoch(loader, model, loss_fn, optimizer) val_loss evaluate(val_loader, model, loss_fn) if val_loss best_loss: best_loss val_loss torch.save(model.state_dict(), best_fire.pth) bad_epochs 0 else: bad_epochs 1 if bad_epochs patience: print(fearly stop at epoch {epoch}) breakpatience 设为 5意思是连续 5 个 epoch 验证集 loss 没有创新低就停止避免在过拟合区间继续无效消耗。best_fire.pth 保存的是验证集最优的模型参数而不是最后一轮的参数。这个文件就是训练过程的后悔药——断电、显存溢出、学习率设错都能从最近一次保存点恢复不至于从头再来。训练脚本里每一轮都覆盖 best_fire.pth 的开销可以忽略但收益是实打实的。另外建议每隔几个 epoch 额外存一个带轮数的 checkpoint便于对比中间结果。5. 火灾识别训练翻车排查五个高频坑与处理思路5.1 训练 loss 不下降前几个 epoch 卡在 0.69 附近现象loss 初始值在 0.69 上下徘徊多个 epoch 过去没有任何下降趋势。原因二分类交叉熵的随机基准确是 ln2约等于 0.693。卡在这个位置说明模型完全没有学到任何模式。最常见的原因是学习率过大导致梯度震荡其次是数据没归一化或者标签顺序错乱。解决先确认数据管道里真的执行了 ToTensor 和 Normalize很多自定义 Dataset 里漏了这一步输入值域完全不对。然后学习率从 0.001 降到 0.0001看 loss 是否开始下降。这两个动作能解决九成以上的“不收敛”问题。5.2 验证集精度 95%实拍场景把红色车尾灯当成火灾现象离线指标非常漂亮到了真实场景红色灯光、夕阳、深色背景里的红色衣物全被报警。原因训练集正样本太“干净”。公开的火灾图片大多是清晰的明火负样本里缺少“颜色像火但不是火”的困难样本。模型学到的是颜色特征而不是火的形态学特征。解决收集类火负样本——夕阳、红色广告牌、建筑外墙灯带、停车场红色车灯标注为 no_fire 加入训练集。这类困难负样本不需要多每个场景十几张就能显著降低误报。数据增强里适度放开 brightness 和 contrast模拟不同环境的光照差异。5.3 三分类里烟雾类把火焰类吞掉现象fire/smoke/no_fire 三分类中fire 的召回率低大量火焰样本被分到 smoke。原因烟雾和火焰在形态上连续浓烟里带火、火焰周围有烟标注边界本来就模糊。加上 smoke 样本量远大于 fire类别不均衡放大了混淆。解决给 CrossEntropyLoss 传 class_weight按样本比例的倒数设置。如果还是压不住换成 Focal Loss它对难分类样本更敏感但要额外调 gamma 参数。在小数据集上先用 class_weight成本最低效果也最直观。5.4 同一视频的连续帧同时出现在训练集和验证集精度虚高现象训练精度和验证精度都超过 98%实拍视频测试时检测结果频繁闪烁同一场景隔几帧就误报一次。原因数据泄漏。同一个视频片段的相邻帧被随机划分到了两边验证集和训练集高度相似等于开卷考试。解决回到第 3.2 节按场景分组划分数据集。划分后手动抽查验证集文件名的来源标识确认与训练集没有重叠。精度虚高比精度低更危险因为它会给你虚假的信心直接部署。5.5 显存溢出把 batch size 调到 1模型直接失控现象OOM 报错后把 batch size 降到 1loss 剧烈震荡且不可复现验证集精度随机波动。原因BatchNorm 在 batch1 时统计量失去意义均值和方差由单张图片决定模型数值极不稳定。解决不要用 batch size 为 1 训练带 BN 的网络。显存不够时优先降低输入分辨率比如从 224 降到 160或者开启梯度累积把多个小 batch 的梯度攒在一起更新。另一个思路是把 BN 层换成 GroupNorm不依赖 batch 维度的统计特性但改网络结构要谨慎评估。6. 用 Grad-CAM 验证模型到底在看什么落地前最后一步6.1 类激活热力图把 CNN 的黑匣子摊开看火灾识别是责任敏感场景模型说“着火了”值班人员要能信得过它。CNN 是黑匣子验证集精度高不代表模型逻辑对。Grad-CAM 能直观看到模型做判断时盯着画面哪个区域这是发现模型“学偏了”最直接的手段。features {} def hook_fn(module, inp, out): features[feat] out model.layer4[-1].register_forward_hook(hook_fn) out model(image.unsqueeze(0)) one_hot torch.zeros_like(out) one_hot[0, 0] out[0, 0] # 类别 0 是 fire out.backward(one_hot) grads features[feat].grad weights grads.mean(dim(2, 3), keepdimTrue) cam (weights * features[feat]).sum(dim1, keepdimTrue) cam torch.relu(cam)逻辑说明对类别 0 的得分做反向传播取最后一个卷积层的特征图和对应的梯度。梯度做全局平均得到每个通道的权重加权求和后经过 ReLU 得到热力图再缩放到输入图尺寸叠加显示。对火灾识别来说热力图高亮区域如果落在火焰核心或烟雾扩散区域说明模型学的特征正确如果高亮在墙壁、桌面、天空上说明模型学到的是场景背景特征之前再高的精度数字都要打个问号。6.2 落地前的三件小事第一把分类阈值从 0.5 下调到 0.3 到 0.4宁可误报让值班人员手动确认不能漏报。第二单独测夜间和低照度场景火灾数据集白天图片占比高夜间火光在暗背景下的颜色表现完全不同没有夜问样本的模型在晚上基本失效。第三部署到算力有限的边缘设备时换 MobileNet 并做 INT8 量化兼顾速度和功耗的收益远大于几个点的精度损失。我自己在这个项目上吃过亏。以前赶一个现场项目验证集精度做到 96%跳过 Grad-CAM 直接部署结果保洁阿姨穿红色制服经过走廊系统连续报警三次。后来每次训练完都先跑一轮热力图再谈上线现场翻车浪费的时间比做可视化多十倍。希望帮到你。本文还有配套的精品资源点击获取