基于大数据与深度学习的火灾风险预测:LSTM+CNN实战

发布时间:2026/9/19 12:08:22
基于大数据与深度学习的火灾风险预测:LSTM+CNN实战 简介面向具备Python基础、数据分析及机器学习背景的研发人员和技术人员这份关于重庆火灾点分析与预测的完整方案依托数据处理、时间序列分析与深度学习技术系统解决从多源数据整合到火灾趋势预测的实际问题。资源包仅含1个docx文档大小18KB内容组织紧凑便于快速查阅与实践。文档详细给出数据导入与预处理、逐年逐月火点频次统计与可视化、气象相关性分析等操作步骤并重点展示结合注意力机制与CNN的LSTM模型构建、训练和预测方法同时覆盖准确率验证与火险等级评估附有具体代码示例和逐段解释。通过该文档读者可学习到完整的大数据火灾预测流程并掌握根据实际数据集灵活调整预处理和超参数的方法适合作为自然灾害预警系统研发的参考材料。资源已有56人浏览学习可作为同类时间序列预测任务的速查与入门指南。1. 为什么火灾预测要同时上大数据处理和深度学习重庆的火灾记录一旦累积到十年以上就是千万行级别的时空数据每条记录带精确到分钟的时间戳、经纬度坐标、火灾类型和扑救时长。面对这样规模的数据传统用 Excel 或者单机 Pandas 聚合统计的做法在第一轮清洗就会卡死。更麻烦的是火灾的发生在时间和空间上都不是独立事件某个片区前两小时的火情会显著影响下一时段的风险概率而相邻网格之间的火势蔓延又构成空间依赖。线性回归和决策树这类经典机器学习方法无法同时捕捉这两种结构。深度学习在这里的价值不是“听起来高级”而是它能把时间序列依赖和空间邻域关系揉进同一个网络里。LSTM 擅长抓住“过去几小时火情对现在的惯性影响”CNN 擅长提取“周边网格的火险信号如何扩散到当前区域”两者叠加就能逼近火灾风险的真实生成过程。这篇文章按照一线工程师落地这套方案的顺序来讲先解决千万行数据的清洗和特征加工再搭 LSTM 基线和时空联合模型接着处理时序训练里最容易被忽视的切分与调参问题最后把模型部署成每日更新的区域风险热力图。2. 大数据处理从千万行原始记录到可供训练的特征矩阵2.1 原始数据的字段设计与清洗方向火灾数据通常来自消防救援部门的接警记录或城市应急管理平台导出的历史档案。统一后的核心字段一般包含接警时间、火灾发生地点、经度、纬度、火灾类型建筑/森林/车辆/电气等、出动车辆数、扑救时长、过火面积。有些记录里还附带当天的温度、湿度、风速这部分如果有缺失可以从公开的气象数据平台按日期和经纬度最近站点补全。拿到原始 CSV 后的第一步不是建模而是把数据质量先盘清楚。我一般会执行四个清洗动作时间字段统一格式常见的坑是“2023/1/5 14:32”和“2023-01-05 14:32:00”混在同一列里直接 to_datetime 会报错或产生 NaT经纬度剔除异常值重庆的中心城区在经度 106.4 到 106.6、纬度 29.4 到 29.6 之间超出这个范围但在重庆市内的记录多半是坐标偏移或录入错误需要结合行政边界做反向过滤重复记录去重同一场火灾可能被多次录入依据“时间经纬度类型”三列联合去重采样对齐气象数据是小时级的火灾记录是分钟级的后续做特征拼接前必须先把火灾数据聚合到小时展示一段 Dask 清洗代码解决“单机 Pandas 读不动千万行”的问题。import dask.dataframe as dd # 用 Dask 延迟加载不一次性吃进内存 df dd.read_csv( chongqing_fire_*.csv, parse_dates[report_time], blocksize64MB, assume_missingTrue ) # 时间字段统一为小时粒度便于后续与气象数据对齐 df[hour] df[report_time].dt.floor(h) df[date] df[report_time].dt.date # 坐标范围过滤106.0~107.0 经度、28.5~30.0 纬度覆盖重庆主要城区 df df[(df[longitude] 106.0) (df[longitude] 107.0)] df df[(df[latitude] 28.5) (df[latitude] 30.0)] # 去重后落盘转回 Pandas 时数据量已经大幅缩减 df_unique df.drop_duplicates(subset[date, longitude, latitude, fire_type]) df_unique df_unique.compute()参数字段说明blocksize控制 Dask 每个分区的读取大小64MB 是一个兼顾并行度和内存占用的常规值assume_missingTrue是为了让 Dask 在个别文件缺少表头时仍能继续读取。dt.floor(h)会把分钟级的接警时间归一到整点这样和小时级气象特征对齐时不会产生大量 NaN。drop_duplicates的 subset 必须包含 fire_type因为同一地点在同一个小时内可能确实发生两起不同类型火灾这类记录不应该被去重。2.2 空间网格化把经纬度切成模型能吃的地理单元深度学习模型不能直接吃连续的经纬度坐标。常见做法是把重庆主城区按照经纬度划分成均匀网格每个网格代表一个空间单元模型预测的是“未来 N 小时内这个网格是否会发生火灾”或“发生多少起火灾”。网格大小需要权衡网格太小则正样本几乎为零模型学不到信号网格太大则空间粒度丢失热力图失去实用价值。重庆主城区大约跨越 0.5 个经度和 0.5 个纬度。以约 1 公里见方为参考把经度按 0.01 度、纬度按 0.01 度切分大约产生 50×50 的网格矩阵。每一行原始记录都被映射到它所在的网格坐标上。import numpy as np import pandas as pd LON_MIN, LON_MAX 106.0, 107.0 LAT_MIN, LAT_MAX 28.5, 30.0 GRID_SIZE 0.01 def lonlat_to_grid(longitude, latitude): col int((longitude - LON_MIN) / GRID_SIZE) row int((latitude - LAT_MIN) / GRID_SIZE) return row, col df[row], df[col] zip(*df.apply( lambda r: lonlat_to_grid(r[longitude], r[latitude]), axis1 )) # 按 网格 小时 聚合统计每个网格每小时的火灾次数 ts_df df.groupby([date, hour, row, col]).agg( fire_count(fire_type, count), avg_duration(duration_minutes, mean) ).reset_index()聚合后的ts_df就是后续深度学习模型的样本基础每一行是“某个网格在某个小时的状态”。用groupby里同时聚合火灾次数和平均扑救时长前者作为预测目标后者可以作为特征输入扑救时间长往往意味着火情严重对邻域后续风险有正向提示作用。2.3 特征工程时空上下文如何变成张量模型输入不能直接用二维的“网格-小时”表格需要构造三维张量(样本数, 时间步长, 空间网格数, 特征数)。这里时间步长取过去 24 小时意味着用过去一天的历史数据预测未来 1 小时或未来 6 小时的火险等级。从聚合表中构造这个张量的方式是把每个网格看成一条独立时间序列然后按时间窗口滑窗切片。代码里给出特征构造的核心逻辑def build_tensor(ts_df, look_back24, pred_horizon1): # 先把每个网格的时间序列转成宽表 pivot_fire ts_df.pivot_table( indexhour, columns[row, col], valuesfire_count, aggfuncsum ).fillna(0) # 时间特征小时的正弦/余弦编码 hours pd.to_datetime(pivot_fire.index).hour hour_sin np.sin(2 * np.pi * hours / 24) hour_cos np.cos(2 * np.pi * hours / 24) X, y [], [] times sorted(pivot_fire.index) for i in range(look_back, len(times) - pred_horizon): # 过去 look_back 个时刻的网格火情 x_hist pivot_fire.iloc[i - look_back:i].values # 把时间特征广播到每个网格上 x_time np.stack([ np.full(pivot_fire.shape, hour_sin[i - look_back:i, None, None]), np.full(pivot_fire.shape, hour_cos[i - look_back:i, None, None]) ], axis-1) x_combined np.concatenate([x_time, x_hist[..., None]], axis-1) X.append(x_combined) # 预测目标未来 pred_horizon 小时内该网格最大火情 y_window pivot_fire.iloc[i:i pred_horizon].values y.append((y_window.sum(axis0) 0).astype(int)) return np.array(X), np.array(y)注意x_hist[..., None]这一步把原始的 24 个时间步 × 网格数矩阵扩出一个通道维目的是让模型能区分“火情数值”和“时间编码”。如果不把时间特征纳入输入模型会丢失“凌晨两点和下午两点火险含义完全不同”这个基本常识。y的目标用0转成二分类标签在消防场景里预测“是否发生”比预测“发生几起”更稳定因为少数网格在一次火灾后大概率不会在同小时再次起火连续数值目标大部分是零回归任务极易退化。2.4 正负样本不均衡的预处理火灾是稀有事件。重庆主城区 2500 个网格、每天 24 个小时一年就是接近 2200 万个网格-小时样本其中发生过火灾的往往不到万分之一。如果直接拿原始数据训练二分类模型模型会学成“永远预测 0”的极端偏置。两个实用处理手段叠加使用一是对无火网格-小时做欠采样保留所有正样本负样本按比例随机抽取二是给损失函数加类别权重。欠采样比例一般控制在正负比 1:10 到 1:50保留足够的负样本让模型学到“什么情况下不容易起火”又不至于让梯度完全被负样本主导。下面的代码把 2.3 生成的y做索引过滤pos_idx np.where(y.reshape(len(y), -1).sum(axis1) 0)[0] neg_idx np.where(y.reshape(len(y), -1).sum(axis1) 0)[0] # 负样本数量控制为正样本的 30 倍超过部分随机丢弃 np.random.seed(42) neg_sample np.random.choice(neg_idx, sizelen(pos_idx) * 30, replaceFalse) sample_idx np.concatenate([pos_idx, neg_sample]) X_balanced X[sample_idx] y_balanced y[sample_idx]replaceFalse保证负样本不会被重复采样避免模型在同样的负样本上反复学习而降低泛化能力。30 倍的比例是经验起点实际应用中可以根据验证集召回率上下调整。3. 深度学习方法LSTM 基线与时空联合预测模型3.1 为什么先用 LSTM 搭建基线模型火灾的时序特征非常显著一个网格在过去 12 小时内的火灾次数对当前风险有持续影响。这种影响不是简单的线性叠加而是带衰减的。LSTM 的门控机制恰好能学到“多久之前的火灾还值得记住、什么样的历史事件应该遗忘”。不要一开始就上复杂模型先用 LSTM 在单一网格或者忽略空间结构的数据上跑通得到的性能数字就是后面所有改进的参照线。基线任务定义为输入每个网格过去 24 小时的火灾次数序列输出未来 1 小时该网格是否会发生火灾。这个任务刻意忽略空间邻域信息让 LSTM 先把时间维度上的预测能力发挥到极致。3.2 用 Keras 搭建 LSTM 基线模型的完整代码import tensorflow as tf from tensorflow.keras import layers, models def build_lstm_baseline(input_shape, lstm_units64): model models.Sequential([ layers.Input(shapeinput_shape), # 返回序列让第二层 LSTM 能继续处理时间步 layers.LSTM(lstm_units, return_sequencesTrue), layers.Dropout(0.3), layers.LSTM(lstm_units // 2, return_sequencesFalse), layers.Dense(32, activationrelu), # 输出所有网格的火灾概率 layers.Dense(input_shape[1], activationsigmoid) ]) model.compile( optimizertf.keras.optimizers.Adam(learning_rate1e-3), lossbinary_crossentropy, metrics[accuracy] ) return model # input_shape 是 (时间步长, 网格数)channel 维度在数据处理时压平 baseline build_lstm_baseline((24, X_balanced.shape[2]))return_sequencesTrue是两层 LSTM 堆叠时的关键参数第一层必须把完整的隐藏状态序列传给第二层否则第二层只能拿到最后一个时间步的输出时间信息会被过早压缩。最后一层sigmoid的输出维度等于网格数相当于对 2500 个网格同时做二元分类每个输出值代表该网格未来 1 小时发生火灾的概率。这里没有用Flatten展平因为时间维度在 LSTM 内部已经被处理掉输出直接对应每个网格。损失函数用binary_crossentropy而不是mse因为这是一个多标签二分类问题交叉熵对概率分布的梯度更平稳训练初期的 loss 下降速度也更快。3.3 时空增强CNN-LSTM 把空间邻域信息加进来纯 LSTM 基线的问题是每个网格独立建模完全看不见周边网格的火情。火灾在空间上高度自相关某网格起火后热量和飞火可能导致相邻网格在接下来几小时内起火。要学习这种“扩散效应”就必须把空间结构编码进输入张量。CNN 提取空间特征LSTM 综合时间演化这种组合在实践中被证明有效。做法是把每个时间步的网格数据重排成二维空间矩阵类似图像让卷积层沿着网格的上下左右做特征提取再把提取后的空间特征序列喂给 LSTM。实现时利用TimeDistributed包装卷积层def build_cnn_lstm(grid_rows50, grid_cols50, look_back24, feature_dim3): inputs layers.Input(shape(look_back, grid_rows, grid_cols, feature_dim)) # 每个时间步独立做 2D 卷积提取空间特征 x layers.TimeDistributed( layers.Conv2D(16, kernel_size(3, 3), paddingsame, activationrelu) )(inputs) x layers.TimeDistributed( layers.MaxPooling2D(pool_size(2, 2), paddingsame) )(x) # 把空间特征压平进入 LSTM 层学习时间依赖 x layers.TimeDistributed(layers.Flatten())(x) x layers.LSTM(64, return_sequencesFalse)(x) # 输出层需要映射回每个网格 x layers.Dense(grid_rows * grid_cols, activationsigmoid)(x) model models.Model(inputs, x) return model结构上最值得注意的是卷积层与 LSTM 的衔接方式。TimeDistributed保证卷积核在每个时间步上使用相同的权重独立滑动提取的是“某个时刻的空间模式”Flatten之后的序列再进 LSTM学到的是“这种空间模式如何随时间演化”。CNN 部分相当于自动学习了“哪些邻域组合构成高风险信号”不再依赖人工构造距离特征。kernel_size(3,3)使得每个网格在卷积时能看到周围的 8 个邻居这是空间蔓延建模的最小感受野。3.4 不平衡标签下的损失函数选择即便做了负样本欠采样正样本占比依然不超过 3%。此时binary_crossentropy仍然偏向多数类模型会倾向于输出低概率。换用带权重的交叉熵可以直接从损失函数层面压制这种偏置。def weighted_binary_crossentropy(weight_pos, weight_neg): def loss(y_true, y_pred): # 二值化权重矩阵正样本位置乘权 weights tf.where(y_true 0.5, weight_pos, weight_neg) bce tf.keras.losses.binary_crossentropy(y_true, y_pred) return tf.reduce_mean(weights * bce) return loss model.compile( optimizeradam, lossweighted_binary_crossentropy(weight_pos10.0, weight_neg1.0) )这里tf.where的作用是把损失函数变成逐样本加权。正样本的 loss 被放大 10 倍模型每把一个真实火灾网格预测成无火都会付出比误报高 10 倍的代价。权重比例跟负采样比例呈反向关系如果之前负采样做了 1:30这个权重比可以适当回落到 5 而不是 10否则正样本在训练初期主导梯度模型会走向另一个极端——把所有网格都报成有火。4. 模型训练、调参与评估时序数据切分的三个关键细节4.1 绝对不能随机打乱数据按时间顺序切分很多初学者直接调用sklearn.model_selection.train_test_split默认参数下会随机打乱样本。这在时序预测里是灾难性的模型在训练时看到了“未来”的火灾样本验证集上的高分完全是数据泄漏造成的假象。正确的切分方式是在时间轴上设定一个硬边界比如用 2015 到 2021 年的数据训练用 2022 年全年作为验证集把 2023 年留作测试集。代码实现上非常简单# ts_df 增加一列 hour_timestamp保证边界判断准确 train_mask (ts_df[hour_timestamp] 2022-01-01) val_mask ( (ts_df[hour_timestamp] 2022-01-01) (ts_df[hour_timestamp] 2023-01-01) ) train_df ts_df[train_mask] val_df ts_df[val_mask] # 根据切分后的数据重新调用 2.3 中的 build_tensor X_train, y_train build_tensor(train_df) X_val, y_val build_tensor(val_df)训练集、验证集、测试集必须独立调用build_tensor不能先把全部数据做成张量再切分。原因在于滑窗切片的起点不同会导致同一段历史同时出现在训练和验证样本里破坏时间上的纯净性。另外要保证补全后的气象特征不依赖未来值例如“当日平均温度”这个特征只能在当天结束后计算不能把包含预测时刻之后的数据混入特征列。4.2 EarlyStopping 与学习率衰减的推荐参数时序模型比图像模型更容易过拟合因为相邻时间步的样本高度相似模型很容易记住训练集尾部的模式而不是学到泛化规律。EarlyStopping和ReduceLROnPlateau是两个必备回调参数设置直接影响训练效果。from tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau early_stop EarlyStopping( monitorval_loss, patience8, restore_best_weightsTrue, min_delta1e-4 ) reduce_lr ReduceLROnPlateau( monitorval_loss, factor0.5, patience3, min_lr1e-6 ) history model.fit( X_train, y_train, validation_data(X_val, y_val), batch_size256, epochs100, callbacks[early_stop, reduce_lr], verbose1 )参数选择的逻辑是patience8意味着验证集 loss 连续 8 个 epoch 没有下降就停止训练这个值在火灾这类高噪声数据上偏保守但能防止训练后期在验证集上反复震荡min_delta1e-4防止 loss 在极小幅度内波动时误判为“没有改善”而过早停止ReduceLROnPlateau的factor0.5让学习率每次减半patience3是说验证集停滞 3 个 epoch 就降一次学习率。学习率不应设成固定常数训练后期固定学习率往往导致 loss 在最优值附近来回抖动无法收敛到平坦区域。4.3 用 PR 曲线而不是准确率评估模型在火灾预测场景里准确率是严重失真的指标。如果负样本占 97%模型永远输出“无火”就能拿到 97% 的准确率但这个模型没有任何实用价值。正确的评估方式是 Precision-Recall 曲线和 F1 分数因为这两个指标不受负样本占比影响能直接反映出模型在稀有的少数类上的表现。from sklearn.metrics import precision_recall_curve, f1_score # y_pred 是模型输出的概率需要先从张量变成一维向量 y_true_flat y_val.reshape(-1) y_pred_flat y_val_pred.reshape(-1) # 计算不同阈值下的精确率和召回率 precision, recall, thresholds precision_recall_curve(y_true_flat, y_pred_flat) # 找到 F1 最高的阈值 f1_scores 2 * precision * recall / (precision recall 1e-9) best_threshold thresholds[np.argmax(f1_scores)] print(f最佳阈值: {best_threshold:.3f}, 对应 F1: {f1_scores.max():.3f})最佳阈值通常远大于默认的 0.5常见范围内是 0.3 到 0.7 之间。消防场景可以在 F1 最优阈值基础上适当下调一点例如向低阈值方向移动 0.1换取更高的召回率——代价是多出一些需要人工核实的误报但漏报才是更危险的错误。实际部署时这个阈值应该设计成可配置项由一线的应急人员根据季节和当前火险形势动态调整。4.4 训练显存不足时怎么降批量大小一批样本的形状是(256, 24, 50, 50, 3)单块 12GB 显存的 GPU 往往拉不满。合理做法是先把批量降到 64如果仍显存不足就把input_shape里的网格数从 50×50 切分成四个 25×25 的子区域分块训练。分块训练还有一个附加收益模型能针对不同片区的火险规律做差异化调参例如渝中半岛的商业区与周边山林的火灾模式差异极大分块后每个模型只需要学一类模式。5. 把模型部署成每日更新的火险热力图5.1 导出模型并做滚动预测模型训练完成后导出为 SavedModel 格式推理时直接加载。火险预测是一个典型的滚动预测场景每天凌晨用过去 24 小时的数据预测未来 24 小时的风险。由于网格数据是持续到达的不能等一天结束后再统一处理实际部署采用每小时更新一次的滚动窗口。import tensorflow as tf import numpy as np # 训练结束后导出 model.save(fire_risk_model) # 部署环境加载 loaded tf.keras.models.load_model(fire_risk_model) def predict_next_24h(latest_24h_tensor): latest_24h_tensor: shape (1, 24, 50, 50, 3) 返回 shape (24, 50, 50) 的概率序列 results [] window latest_24h_tensor.copy() for _ in range(24): # 预测下一个小时的火灾概率 next_prob loaded.predict(window, verbose0)[0] results.append(next_prob) # 滚动窗口丢掉最早时刻把预测结果作为新一帧填入 new_frame window[:, -1, :, :, :].copy() new_frame[..., 0] next_prob window np.concatenate([window[:, 1:, :, :, :], new_frame[:, None, :, :, :]], axis1) return np.stack(results)循环里每次预测后要把结果拼接回窗口这就把模型变成了自回归模式第 2 小时的预测会依赖第 1 小时的预测结果。自回归滚动预测在 24 小时尺度上误差会累积如果发现第 12 小时之后的输出明显失真可以改为只做未来 6 小时的滚动预测超过 6 小时的部分用静态特征重新输入模型。5.2 预测结果转成空间网格的实用技巧模型的输出是 50×50 的网格概率矩阵直接落库到 PostGIS 后可以用 QGIS 或任意热力图组件渲染。为了节省数据库写入开销可以把连续概率值离散化成 0 到 4 五个等级这样存储量直接压缩几十倍前端渲染速度也更快。def probability_to_level(prob): if prob 0.8: return 4 elif prob 0.6: return 3 elif prob 0.4: return 2 elif prob 0.2: return 1 else: return 0 # 24 个小时的预测结果批量转换 for hour_idx, prob_mat in enumerate(predicted_sequence): level_mat np.vectorize(probability_to_level)(prob_mat) # 写入时可以按 (网格行, 网格列, 等级) 三元组批量插入 hotpoints np.argwhere(level_mat 2) # 只导出中高风险网格 print(fHour {hour_idx 1}: {len(hotpoints)} 个中高风险网格)只导出等级大于等于 2 的网格能在数据库和前端两个环节一起做裁剪。实际运营中发现等级 1 的网格数量过多在地图上渲染出来会占据大半个屏幕让应急人员无从下手。把低风险网格在导出时就过滤掉保留 50 到 200 个高价值热点网格这种可视化效果和决策辅助价值远高于全量渲染。5.3 跨区域迁移时冻结底层卷积层如果这套方案要从重庆迁移到成都或昆明直接在新城市数据上从头训练成本很高而且新城市的历史火灾记录往往不足一年。这时可以采用迁移学习加载重庆训练好的模型冻结底层的卷积层只重新训练 LSTM 层和输出层。低层卷积学到了通用的空间纹理特征例如“高密度建筑区与火灾的关联”这些特征在不同城市间是共享的而 LSTM 层学到的时序演化规律带有明显的地域气候和城市结构印记需要重新拟合。冻结操作在 Keras 里只需layer.trainable False关键是要保存一份配置好的优化器状态否则重新编译后旧模型的优化器动量会丢失迁移训练初期 loss 会有一个明显的回升过程。做迁移时新城市的数据量如果少于 3 个月建议直接把 LSTM 层也冻结只训练最后的全连接层用极小的学习率 1e-4 做微调就可以上线。另外可以给推理服务加一个简单的缓存层相邻两次预测之间如果输入的 24 小时特征没有变化直接返回上一次的预测结果避免对同一批数据做重复计算。虽然看起来省不了多少时间但在接入实时流式数据之后这个缓存能把模型推理的 QPS 需求降一个数量级让单机部署也能支撑小时级的全城更新。本文还有配套的精品资源点击获取