ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

时序预测实战:从DBSCAN异常检测到CNN模型构建的完整流程解析

时序预测实战:从DBSCAN异常检测到CNN模型构建的完整流程解析 1. 从竞赛题目到实战项目一次预测模型搭建的完整复盘最近在整理过去的项目笔记翻到了2021年参与MathorCup高校数学建模挑战赛B题时搭建预测模型的全过程记录。虽然比赛已经过去几年但其中关于数据处理、模型选型、特征工程以及从TensorFlow到PyTorch的思考对于今天想入门时序预测或者参加类似数据竞赛的朋友来说依然有很强的参考价值。当时的B题是一个典型的基于时序数据的预测问题核心目标是利用历史数据构建模型来预测未来某个指标。这听起来很常规但魔鬼藏在细节里——如何从一堆看似杂乱的数据中提取有效特征如何选择合适的模型架构以及如何避免过拟合和评估陷阱每一步都考验着对问题的理解和对工具的掌握。今天我就以那次竞赛为蓝本抛开比赛本身的限制系统地拆解一个预测模型从零搭建的完整流程。我会重点分享几个关键决策点背后的思考为什么在初步探索时选择了DBSCAN进行数据清洗而不是简单的阈值法为什么在深度模型上尝试了卷积神经网络CNN来处理序列数据它和循环神经网络RNN的考量是什么以及在TensorFlow和PyTorch之间我们当时为何选择了前者而今天如果再做一次我的选择又会有什么不同这篇文章不仅是一份技术总结更是一次方法论的重构希望能为你提供一个清晰、可复现的实战框架。2. 问题定义与数据初探理解你要预测什么任何预测项目的第一步绝不是急着打开Jupyter Notebook写代码而是彻底理解你要解决的问题和数据。在MathorCup B题中题目会提供一份数据集通常包含多个维度的历史观测值。你的目标是预测未来一段时间内某个或多个关键变量的值。2.1 明确预测任务的核心要素首先我们需要将抽象的“预测”具体化这需要明确以下几个要素预测目标Target你要预测的具体是什么是一个连续值如销售额、温度还是一个离散类别如是否下雨、用户等级在B题中通常是连续值的回归预测问题。预测范围Forecast Horizon是预测下一个时间点单步预测还是未来多个时间点多步预测多步预测又可以分为递归式预测用上一个预测值再预测下一个和直接多输出预测策略不同模型设计也不同。输入特征Features你有哪些数据可以用来做预测这些数据包括目标变量的历史值最重要的特征即“自回归”项。比如用过去7天的销量预测第8天。相关时序特征日期、星期几、是否节假日、月份等。这些特征能捕捉周期性和季节性。外部协变量Exogenous Variables其他可能影响目标变量的因素。例如预测销量时是否有促销活动、天气情况、竞争对手价格等。衍生特征Derived Features通过对原始数据进行计算得到的特征如滑动窗口统计量过去3天的均值、标准差、差分序列消除趋势、同比/环比等。在拿到竞赛数据后我做的第一件事就是用pandas进行彻底的描述性统计和可视化。查看数据的基本形状、缺失值情况、各列的分布直方图、以及目标变量随时间变化的趋势折线图。这个阶段一个简单的移动平均线就能帮你看出数据是否存在明显的趋势和季节性。2.2 数据质量的“体检”异常值检测与处理真实数据很少是完美的异常值Outliers的存在会严重干扰模型的学习特别是对线性模型和基于距离的模型。常见的异常值检测方法有基于标准差3σ原则、箱线图IQR等。但在处理时空数据或复杂分布的数据时这些方法可能不够鲁棒。这时我们引入了DBSCANDensity-Based Spatial Clustering of Applications with Noise算法。DBSCAN的核心思想是“物以类聚”它通过样本点的密度来划分簇并将低密度区域的点标记为噪声即潜在的异常值。它的优势在于不需要预先指定簇的个数。能发现任意形状的簇对非球状分布的数据友好。能有效区分噪声。在预测任务中我们并不总是要删除异常值。有些“异常”可能是真正的业务事件如“双十一”的销量暴增。DBSCAN可以帮助我们识别出那些远离主要数据分布、且密度很低的“孤立点”。这些点很可能是记录错误或极小概率事件对模型泛化无益可以考虑剔除或修正。实操示例用DBSCAN清洗数据假设我们有一维的销量数据sales_sequence。import numpy as np from sklearn.cluster import DBSCAN import matplotlib.pyplot as plt # 假设 sales_data 是一个包含时间序列的numpy数组或DataFrame的列 # 为了使用DBSCAN需要将一维序列转化为二维坐标索引 值 X np.array(list(enumerate(sales_sequence))) # 构造时间索引 销量的二维点 # 初始化DBSCAN eps是邻域半径 min_samples是核心点所需的最小样本数 # 这两个参数需要根据数据尺度调整通常通过可视化或经验设定 dbscan DBSCAN(eps5.0, min_samples10) labels dbscan.fit_predict(X) # 标签为-1的点被识别为噪声异常值 core_samples_mask np.zeros_like(labels, dtypebool) core_samples_mask[dbscan.core_sample_indices_] True noise_mask labels -1 print(f“识别出的异常值数量 {np.sum(noise_mask)}”) # 可视化 plt.figure(figsize(12, 6)) plt.scatter(X[~noise_mask, 0], X[~noise_mask, 1], c‘b’, marker‘o’, label‘正常点’) plt.scatter(X[noise_mask, 0], X[noise_mask, 1], c‘r’, marker‘x’, label‘异常点噪声’) plt.xlabel(‘时间索引’) plt.ylabel(‘销量’) plt.legend() plt.title(‘DBSCAN异常值检测’) plt.show() # 处理异常值这里选择用前后正常值的线性插值来替换 from scipy import interpolate if np.any(noise_mask): normal_indices np.where(~noise_mask)[0] normal_values X[~noise_mask, 1] # 创建插值函数 f_interp interpolate.interp1d(normal_indices, normal_values, kind‘linear’, fill_value“extrapolate”) # 替换所有位置的值包括正常点但正常点值不变 sales_sequence_cleaned f_interp(np.arange(len(sales_sequence)))注意DBSCAN的参数eps和min_samples对结果非常敏感。建议先通过可视化如K距离图辅助选择eps并结合业务理解判断异常点是否合理。对于多维特征DBSCAN同样适用但可能需要先进行标准化因为不同维度的量纲不同。3. 特征工程为模型注入“洞察力”数据清洗后就进入了预测模型的“炼金术”阶段——特征工程。好的特征能让简单模型表现优异坏的特征则会让复杂模型一无所获。对于时序预测特征工程有其特定的套路。3.1 构建时序相关特征这是最核心的部分目的是让模型“看到”时间模式。滞后特征Lag Features创建目标变量在过去几个时间点的值作为特征。例如lag_1,lag_2, ...,lag_7分别代表前1天到前7天的值。这是捕捉短期自相关性的关键。滑动窗口统计特征Rolling Window Statistics计算过去一个窗口期内的统计量如均值、标准差、最大值、最小值、分位数等。例如“过去7天的平均销量”可以平滑短期波动反映近期水平。扩展窗口统计特征Expanding Window Statistics计算从序列开始到当前时点的统计量如历史累计均值。这对捕捉长期趋势变化有帮助。时间戳分解特征从日期时间中提取有意义的成分。周期性特征小时、星期几、月份、季度。建议使用循环编码sin/cos来处理其周期性因为月份12和月份1是相邻的但简单的整数编码1,2,...,12会认为它们相距很远。df[‘month_sin’] np.sin(2 * np.pi * df[‘month’]/12) df[‘month_cos’] np.cos(2 * np.pi * df[‘month’]/12)事件特征是否节假日、是否周末、是否促销日。用0/1布尔值表示。差分与变换如果序列有强烈的趋势或季节性可以先进行差分当前值减去前一时刻的值使其平稳。对于方差随时间增大的序列可以对数变换来稳定方差。3.2 引入外部特征与交互特征如果数据提供了其他相关变量务必充分利用。例如在销售预测中价格、广告投入、天气数据都是强相关特征。此外可以创建一些交互特征比如“节假日且促销”、“周末的平均温度”等这些组合特征有时能捕捉到单独特征无法揭示的模式。特征工程后的重要一步是特征筛选。不是所有构造出来的特征都有用。我们可以使用相关性分析计算特征与目标变量的相关性剔除相关性极低的。递归特征消除RFE结合一个基础模型如线性回归递归地剔除最不重要的特征。基于模型的特征重要性使用树模型如随机森林、XGBoost训练后查看特征重要性排序。在竞赛中特征工程往往是拉开差距的关键。我们当时构建了近百个特征但最终通过筛选只保留了30多个进入模型。4. 模型选型与实战从传统方法到深度学习特征准备好后就到了选择模型的十字路口。没有“最好”的模型只有“最适合”当前数据规模和问题特性的模型。我们的策略是先快跑一个基线模型再用更复杂的模型进行提升。4.1 基线模型XGBoost回归对于结构化特征即我们刚才构建的表格型数据梯度提升树模型如XGBoost, LightGBM, CatBoost通常是首选基线。它们能自动处理特征间的非线性关系对缺失值不敏感且不容易过拟合通过正则化。为什么选XGBoost作为起点性能与精度在众多数据竞赛中XGBoost是夺冠热门其精度通常很高。训练速度相比深度学习它训练更快调参反馈周期短。可解释性可以提供特征重要性帮助我们理解哪些特征在起作用。无需复杂预处理对数据尺度不敏感不需要像神经网络那样必须标准化。一个简单的XGBoost回归预测流程import xgboost as xgb from sklearn.model_selection import TimeSeriesSplit, GridSearchCV from sklearn.metrics import mean_squared_error, mean_absolute_error # 假设 X_train, y_train, X_test, y_test 已经准备好注意时序数据不能随机划分要按时间顺序 # 创建DMatrix是XGBoost的高效数据格式 dtrain xgb.DMatrix(X_train, labely_train) dtest xgb.DMatrix(X_test, labely_test) # 设置初始参数 params { ‘objective’: ‘reg:squarederror’, # 回归任务 ‘max_depth’: 6, # 树的最大深度控制模型复杂度 ‘eta’: 0.1, # 学习率 ‘subsample’: 0.8, # 每棵树随机采样的样本比例防止过拟合 ‘colsample_bytree’: 0.8, # 每棵树随机采样的特征比例 ‘seed’: 42, ‘eval_metric’: ‘rmse’ # 评估指标 } # 训练并早停 evals [(dtrain, ‘train’), (dtest, ‘eval’)] model_xgb xgb.train(params, dtrain, num_boost_round1000, evalsevals, early_stopping_rounds50, verbose_eval50) # 预测 y_pred_xgb model_xgb.predict(dtest) mse_xgb mean_squared_error(y_test, y_pred_xgb) print(f“XGBoost测试集MSE {mse_xgb:.4f}”) # 查看特征重要性 importance model_xgb.get_score(importance_type‘weight’) # 可以绘制重要性条形图辅助特征筛选实操心得XGBoost的关键参数除了max_depth和eta还有n_estimators树的数量用早停自动确定、gamma节点分裂所需的最小损失下降用于剪枝、lambda和alphaL2和L1正则化项。建议使用TimeSeriesSplit进行交叉验证来调参因为普通K-Fold会破坏时序依赖性。4.2 进阶模型卷积神经网络CNN用于序列预测当特征工程做到极致XGBoost性能可能遇到瓶颈时或者当数据具有更复杂的局部依赖模式时可以尝试深度学习模型。很多人一提到时序预测就想到RNN或LSTM但CNN也是一个非常强大且常被低估的选择。为什么用CNN做时序预测捕捉局部依赖CNN的卷积核能高效地提取序列中局部窗口如过去几天的特征模式。对于许多时序问题近期数据的影响远大于远期CNN天然适合这种特性。并行计算训练快相比RNN的串行结构CNN可以并行处理整个序列训练速度通常更快。结构简单不易梯度消失/爆炸比RNN更容易训练和调试。多变量输入友好可以轻松处理多维度特征多个时间序列将其视为多通道Channels的输入就像图像的RGB通道一样。我们可以将时序预测任务构建为一个多变量输入、单变量输出的监督学习问题。把过去N个时间步的所有特征包括目标变量的滞后值和其他协变量堆叠成一个(N, feature_dim)的矩阵作为CNN的输入去预测下一个时间步的目标值。使用TensorFlow/Keras搭建一个简单的时序CNN模型import tensorflow as tf from tensorflow import keras from tensorflow.keras import layers def build_cnn_model(input_shape): “”“ 构建一个用于时序预测的CNN模型。 input_shape: (time_steps, feature_dim) ”“” model keras.Sequential([ # 输入层 layers.Input(shapeinput_shape), # 第一个卷积层捕捉局部模式 layers.Conv1D(filters64, kernel_size3, activation‘relu’, padding‘causal’), # Causal Padding确保预测只依赖于过去和当前不泄露未来信息对时序预测至关重要 layers.BatchNormalization(), layers.MaxPooling1D(pool_size2), # 第二个卷积层 layers.Conv1D(filters128, kernel_size3, activation‘relu’, padding‘causal’), layers.BatchNormalization(), layers.MaxPooling1D(pool_size2), # 将特征图展平 layers.Flatten(), # 全连接层进行综合判断 layers.Dense(units50, activation‘relu’), layers.Dropout(0.3), # Dropout防止过拟合 # 输出层预测一个连续值 layers.Dense(units1) ]) return model # 假设我们使用过去30个时间步time_steps30每个时间步有20个特征feature_dim20 time_steps 30 feature_dim X_train.shape[1] # 特征维度 # 需要将数据重塑为 (samples, time_steps, feature_dim) 的3D张量 # 这里假设你已经有一个函数 create_sequences 来从2D特征表创建3D序列样本 # X_train_3d, y_train_seq create_sequences(X_train.values, y_train.values, time_steps) # X_test_3d, y_test_seq create_sequences(X_test.values, y_test.values, time_steps) model build_cnn_model((time_steps, feature_dim)) model.summary() # 编译模型 model.compile(optimizerkeras.optimizers.Adam(learning_rate0.001), loss‘mse’, # 回归任务用均方误差损失 metrics[‘mae’]) # 监控平均绝对误差 # 训练模型使用验证集监控 history model.fit(X_train_3d, y_train_seq, epochs100, batch_size32, validation_split0.2, callbacks[keras.callbacks.EarlyStopping(patience10, restore_best_weightsTrue)], verbose1) # 评估 test_loss, test_mae model.evaluate(X_test_3d, y_test_seq, verbose0) print(f“CNN测试集MAE {test_mae:.4f}”)注意使用CNN处理时序数据Causal Padding或‘same’ padding with shifting是必须的这确保了卷积操作不会使用未来的信息。此外池化层Pooling会降低时间维度分辨率需要根据序列长度和预测需求谨慎使用。对于非常长的序列可以结合空洞卷积Dilated Convolutions来增大感受野。4.3 TensorFlow vs. PyTorch一个持续的选择题2021年我们选择TensorFlow主要是Keras API因为它生态成熟文档丰富部署相对容易对于快速原型开发非常友好。而PyTorch则以动态图、更Pythonic的编程风格和卓越的调试体验著称在研究社区更受欢迎。2024年的今天如何选择对于初学者和教学场景PyTorch的优势可能更大。它的API设计更直观错误信息更友好调试起来就像在写普通的Python代码。你想知道张量在哪个操作出错了直接打印中间变量就行。这对于理解深度学习底层运作机制非常有帮助。TensorFlow 2.x虽然也采用了Eager Execution但一些历史包袱和稍显复杂的API层次tf.keras, tf.*可能让新手感到困惑。对于快速实现已知架构和部署TensorFlow特别是通过Keras在快速搭建标准网络如CNN、LSTM方面依然简洁。其SavedModel格式和TensorFlow Serving、TFLite等部署工具链非常成熟对于生产端部署很多企业环境仍有惯性优势。对于前沿研究和自定义复杂模型PyTorch几乎是学术界和AI实验室的默认选择。其动态计算图使得实现新想法如新的注意力机制、图神经网络更加灵活自由。我的建议是不必纠结两者都值得了解。可以从PyTorch入门因为它能给你更扎实的理解。当需要快速完成一个项目或考虑特定平台部署时TensorFlow/Keras依然是可靠的工具。两者的核心概念层、损失函数、优化器是相通的学会一个再学另一个会很快。5. 模型评估与融合避免“纸上谈兵”模型训练好了在测试集上指标也不错是不是就大功告成了远非如此。对于预测任务尤其是时序预测评估环节的陷阱最多。5.1 时序交叉验证与评估指标绝对不能使用随机划分的交叉验证因为时序数据具有依赖性未来的数据不能用来“预测”过去。必须使用时序交叉验证Time Series Cross-Validation例如TimeSeriesSplit。from sklearn.model_selection import TimeSeriesSplit from sklearn.metrics import mean_absolute_error, mean_absolute_percentage_error tscv TimeSeriesSplit(n_splits5) fold_scores [] for train_index, val_index in tscv.split(X): X_train_fold, X_val_fold X.iloc[train_index], X.iloc[val_index] y_train_fold, y_val_fold y.iloc[train_index], y.iloc[val_index] # 在每个fold上训练和评估模型以XGBoost为例 model.fit(X_train_fold, y_train_fold) y_pred_fold model.predict(X_val_fold) score mean_absolute_error(y_val_fold, y_pred_fold) fold_scores.append(score) print(f“各fold的MAE {fold_scores}”) print(f“平均MAE {np.mean(fold_scores):.4f} (/- {np.std(fold_scores):.4f})”)选择合适的评估指标MAE平均绝对误差直观解释性强。例如MAE10意味着平均预测误差为10个单位。MAPE平均绝对百分比误差相对误差适合比较不同量级的数据。但当真实值很接近0时MAPE会无限大不稳定。RMSE均方根误差对大的误差惩罚更重。如果你的业务更关心避免大的预测偏差RMSE更合适。SMAPE对称平均绝对百分比误差解决了MAPE的一些问题但也不是完美的。在竞赛中务必使用官方指定的评估指标。在实际业务中需要和业务方沟通什么样的误差是可以接受的。5.2 模型融合策略单一模型可能在某些模式上表现好在其他模式上表现差。模型融合集成是提升预测稳定性和精度的有效手段。我们当时采用了加权平均融合。基础模型我们训练了三个表现较好的模型XGBoost、LightGBM和一个简单的多层感知机MLP。确定权重不是简单平均。我们在一个预留的验证集上以最小化验证集误差为目标搜索最优权重。可以简单使用网格搜索# 假设 val_pred_xgb, val_pred_lgb, val_pred_mlp 是三个模型在验证集上的预测 # y_val 是验证集真实值 best_score float(‘inf’) best_weights None # 简单网格搜索权重权重和为1 for w1 in np.linspace(0, 1, 11): for w2 in np.linspace(0, 1-w1, 11): w3 1 - w1 - w2 if w3 0: continue val_pred_ensemble w1*val_pred_xgb w2*val_pred_lgb w3*val_pred_mlp score mean_absolute_error(y_val, val_pred_ensemble) if score best_score: best_score score best_weights (w1, w2, w3) print(f“最优权重 {best_weights}, 验证集MAE {best_score}”)应用融合用找到的最优权重对三个模型在测试集上的预测结果进行加权平均得到最终预测。更高级的融合方法包括Stacking用初级模型的预测作为特征训练一个次级模型但需要注意避免过拟合且计算成本更高。6. 从竞赛到实战那些容易踩的坑与心得回顾整个项目有几个地方是新手甚至是有经验的从业者都容易栽跟头的地方。坑一数据泄露Data Leakage这是时序预测的“头号杀手”。常见泄露方式包括使用了未来信息在构造特征时不小心使用了当前时间点之后的数据。例如用“当天的平均值”作为特征而这个平均值包含了当天的值。确保所有特征在预测时都是已知的。不恰当的标准化如果在划分训练集和测试集之前对整个数据集进行标准化如减去均值、除以标准差那么测试集的信息就“泄露”给了训练过程。正确的做法是只用训练集的数据计算均值和标准差然后用它们去转换测试集。验证集划分错误如前所述必须使用时序交叉验证。坑二过度依赖复杂模型忽视基础分析和特征工程一上来就调参LSTM、Transformer结果可能还不如一个精心调参的XGBoost。我的工作流永远是数据分析 - 基线模型如线性回归、XGBoost - 特征工程迭代 - 尝试复杂模型。复杂模型是“锦上添花”而不是“雪中送炭”。坑三忽略预测结果的可解释性与业务对接模型预测出一个数字这个数字意味着什么为什么高为什么低在竞赛中可能只看指标但在实际业务中你必须能向非技术人员解释。XGBoost的特征重要性、SHAP值都是很好的工具。即使使用“黑盒”的神经网络也可以通过构建简单的代理模型如线性模型对预测结果进行局部解释。坑四没有考虑预测的不确定性模型给出的只是一个点估计Point Forecast。但在很多决策场景下知道预测的置信区间例如有90%的把握销量在100-120之间比只知道一个预测值110更有用。对于树模型可以通过计算每个样本在集成中所有树的预测分布来近似置信区间。对于深度学习模型可以使用MC Dropout或贝叶斯神经网络等方法。最后我想说的是预测模型的搭建是一个迭代和探索的过程。没有一劳永逸的银弹。从MathorCup这样的竞赛项目入手强迫自己走完从数据清洗、特征工程、模型构建到评估优化的全流程是提升数据科学和机器学习实战能力的最佳途径之一。希望这篇基于真实项目复盘的长文能为你下次面对一个预测问题时提供一张清晰的路线图和一份实用的避坑指南。
返回列表