光伏功率预测这个方向,做得久了会发现一个规律:真正拉不开差距的往往不是模型结构本身,而是相似日聚类、模态分解这类"数据侧"和"信号侧"的处理有没有做扎实。这篇发表在电力领域核心期刊上的研究,标题里塞了三个关键词——相似日聚类、模态分解、短期光伏发电功率组合预测,乍看是把三个热点拼在一起,实际读下来会发现它背后有一条很清晰的因果链:先用聚类解决"拿哪些历史样本训练"的问题,再用分解解决"信号本身不平稳"的问题,最后用组合预测解决"单一模型偏差无法互补"的问题。这套思路对做新能源功率预测的算法工程师、电力系统方向的研究生、打算拿论文做毕设复现的本科生都挺友好,门槛不高,但细节坑不少。下面我按自己的理解把这篇论文的思路拆开,再给一套可以跑起来的仿真复现路径,包括参数怎么定、代码怎么写、哪里最容易翻车。
1. 先把问题拆开:这篇论文到底在解什么
1.1 光伏出力预测难在三个地方
聊复现之前得先明白痛点在哪,否则代码抄完也不知道每一步为什么存在。光伏出力的短期预测,难点基本集中在三处。第一处是非平稳性,晴天的功率曲线是一个漂亮的钟形,多云天是锯齿状来回跳,阵雨天则是断崖式塌陷再反弹,同一套模型参数很难同时拟合这三种形态。第二处是样本分布漂移,冬天和夏天的出力曲线在幅值、峰谷时刻、爬坡速率上完全不同,把全年数据一股脑喂给模型,等于让模型去学一个平均意义上的"四不像"。第三处是误差来源分散,趋势项预测偏了、高频抖动没跟上、天气突变没识别出来,这三类误差性质完全不同,用一个模型一把梭,往往是按下葫芦浮起瓢。
这篇论文的三个关键词,恰好一一对应了这三个痛点。相似日聚类对付的是样本分布漂移,模态分解对付的是非平稳性,组合预测对付的是误差来源分散。理解了这个映射关系,后面看论文的流程图就不会觉得是在堆砌方法。
1.2 相似日聚类解决的是"拿哪些样本训练"
这是我认为整篇论文里最容易被低估的一步。很多复现者会直接跳过聚类,把预测日之前所有历史数据都拿来训练,然后抱怨模型精度上不去。实际上短期预测的样本量本来就不大,一个电站一年的有效白天样本也就三千多条,扣掉阴雨、检修、限电的异常日,能用的更少。如果还不做筛选,模型学到的大部分是跟预测日天气形态无关的信息。
相似日聚类的逻辑说白了就是"找长得像的历史天"。做法通常是把每一天压缩成一个特征向量,向量的维度包括日发电量、峰值功率、峰谷差、功率曲线的日积分、日均辐照、日最高温、天气类型编码等,然后用距离度量找出与预测日最接近的一批历史日。有的论文直接用欧氏距离取 Top-N,有的先用 K-means 或谱聚类把历史日分成若干簇,再取预测日所在簇的全部样本。后者更常见,因为它能顺带把"典型晴天""典型多云""典型阴雨"这几类天气形态显式地切出来。
注意:聚类标签里如果不加入天气类型这个离散特征,聚类结果很容易退化成"按季节划分",因为温度和太阳高度角对功率曲线的影响太大,会掩盖掉天气形态的差异。这一点在复现时特别关键,我见过不少复现结果里第一簇全是七八月,第二簇全是一二月,那说明聚类只是在分季节,并没有在分天气。
1.3 模态分解解决的是"信号本身不平稳"
模态分解这一层,论文用的是变分模态分解(VMD)。它的作用是把原始功率序列拆成若干个中心频率不同的本征模态分量(IMF),低频分量承载日周期趋势和整体出力水平,中频分量承载云层遮挡引起的缓变,高频分量承载快速波动和噪声。拆开之后分别建模,每个模型只需要拟合自己那一段频带的规律,负担就轻了。
这一步的思想其实和傅里叶变换一脉相承,区别在于 VMD 是自适应的、非递归的,不需要预先指定基函数。相比经验模态分解(EMD),VMD 最大的好处是没有模态混叠——EMD 在遇到间歇性信号时,同一个频率成分会跑到两个 IMF 里去,或者一个 IMF 里混着两个完全不同的频率,这在光伏这种突变频繁的序列上特别明显。VMD 通过把分解问题转化成一个变分约束问题求解,各分量的中心频率在频域上是分离的,混叠现象少得多。
1.4 组合预测解决的是"模型偏差互补"
组合预测这个词容易引起误解,很多人以为是把 LSTM、XGBoost、SVR 各跑一遍然后加权平均。这篇论文里的"组合"其实是两个层次的组合:一层是分量级组合,即不同 IMF 用不同的模型或不同的超参数去预测,因为低频分量平滑,用简单模型就够,高频分量随机性强,可能需要更强的非线性拟合能力;另一层是模型级组合,即同一分量用多个模型预测后加权。论文正文里通常会给出权重确定的公式,常见的有等权、误差倒数加权、最优权重(最小化组合方差)。
我个人的经验是,分量级组合带来的增益通常比模型级组合更明显,也更值得花时间调。因为不同频带的信号特性差异是客观存在的,而同一分量上不同模型的预测结果往往高度相关,加权能带来的方差下降有限。
2. 整体框架与每一步的输入输出
2.1 数据流全景
把论文的流程图翻译成工程语言,大概是这样一条链路:原始数据进入后先做清洗和特征构造,得到"日-特征"矩阵;用这个矩阵做相似日聚类,针对每个预测日输出一个相似日样本集;对样本集里的功率序列做 VMD 分解,得到 K 个 IMF;每个 IMF 单独构造监督学习样本(滑动窗口切分),分别训练预测模型;预测日当天,先对已知的历史功率段做分解,再逐分量外推,最后把各分量预测值相加得到总功率预测。
这里有一个非常关键的工程细节:分解应该在哪一步做。如果对整个序列(包括待预测时段)先分解再切分,那就等于用了未来信息,属于典型的数据泄漏。正确做法有两种,一种是滚动分解,即每次预测只对预测日之前的历史窗口做分解,预测完再把真实值并入窗口重新分解;另一种是把分解当成特征工程的一部分,只在训练集上拟合分解参数,预测时对历史段分解后外推。前者严谨但慢,后者快但需要小心处理边界。
2.2 相似日聚类的技术路线选型
论文里给的通常是 K-means,因为实现简单、解释性强。但复现时你会发现 K-means 有几个现实问题:它对初始中心敏感,对特征尺度敏感,而且假设簇是凸的球形分布。光伏日特征向量里,"日发电量"的量级可能是几万,"天气类型编码"只有 0 到 3,不归一化的话距离计算会被发电量完全主导。
我的建议是复现时至少准备两套方案做对比。一套是标准化后的 K-means,另一套是基于动态时间规整(DTW)距离的层次聚类或谱聚类。DTW 的好处是它能对齐两条功率曲线在时间轴上的错位,比如同样是晴天,夏天峰值出现在 13:00,冬天出现在 12:00,欧氏距离会认为它们差别很大,DTW 则能识别出形态相似。代价是计算量大,样本上千时要做下采样或者用快速 DTW 近似。
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 标准化 + K-means | 快、易实现、可解释 | 对簇形状有假设,需手动定 K | 样本量大、特征维度低 |
| 谱聚类 | 能处理非凸簇 | 相似度矩阵内存开销大 | 样本几百到一两千 |
| DTW + 层次聚类 | 能对齐时序错位 | 计算复杂度高 | 样本量小、形态差异是主要矛盾 |
| 直接按天气类型分组 | 零成本、可解释性最强 | 依赖天气预报准确率 | 有可靠天气分类数据的场景 |
2.3 模态分解的选型:为什么是 VMD
前面提过 VMD 相对 EMD 的优势,这里补充一个实操层面的理由:VMD 的参数是显式的、可调的,K(模态数)和 alpha(带宽惩罚)都有明确的物理含义,调参时有方向感。EMD 完全没有参数,分解结果完全由数据决定,出了问题你无从下手。CEEMDAN 加了自适应噪声,缓解了模态混叠,但噪声幅值和集成次数又成了新的调参负担,而且计算量翻好几倍。
VMD 的代价是需要预先给定 K。K 太小,高频细节被压缩进低频分量,等于没分解;K 太大,会出现中心频率相近的冗余分量,预测时各分量误差高度相关,组合反而放大噪声。所以 K 的选择是复现里最需要花心思的一步,后面第 5 节会详细讲怎么定。
2.4 组合层:分量级组合与模型级组合
分量级组合的典型做法是低频用线性模型或浅层网络,高频用树模型或深一点的网络。理由是低频分量近似一条平滑曲线,线性回归、ARIMA 就能拟合得不错,上复杂模型容易过拟合;高频分量随机性强、样本噪声大,树模型的分段常数特性反而能起到一定的去噪作用。
模型级组合的权重,论文里常见三种:等权平均、误差倒数加权、以及基于最小二乘的最优权重。误差倒数加权的公式是 $w_i = \frac{1/e_i}{\sum_j 1/e_j}$,其中 $e_i$ 是第 i 个模型在验证集上的误差。这个方法简单但对误差估计的可靠性很敏感,验证集小的时候权重会剧烈波动。最优权重要求解一个带约束的二次规划,复现时用scipy.optimize.minimize几行就能写完,但要注意约束权重非负且和为 1,否则会出现负权重这种没有物理意义的解。
3. 数据准备与预处理实操
3.1 数据字段与时间分辨率
光伏功率预测的标准数据配置是:功率序列(15 分钟或 1 小时分辨率)、水平面总辐照、组件温度或环境温度、环境湿度、风速、天气类型编码。如果做超短期(未来 4 小时以内),通常不需要气象预报数据;如果做短期(未来 1 到 3 天),就必须接入数值天气预报的输出。
复现时如果没有实测数据,可以用公开的气象再分析数据加上一个简化的光伏出力模型自己造一套。简化的思路是:以辐照度为主驱动,叠加温度修正和逆变器效率曲线,再乘以一个随机扰动的云遮挡系数。这样造出来的数据在统计特性上接近真实序列,用来验证算法流程完全够用。但要注意,自己造的数据往往比真实数据"干净",噪声模型设得太简单,会导致复现出来的精度虚高。
时间分辨率的选择直接影响样本量。15 分钟分辨率下,一天有效发电时段(比如 6:00 到 19:00)有 52 个点,一年下来一万八千多条,做 LSTM 训练是够的。如果只有 1 小时分辨率,一天 13 个点,序列太短,滑动窗口切出来的样本维度很低,LSTM 的优势发挥不出来,这时候用 XGBoost 之类的模型反而更合适。
3.2 异常值清洗与缺失补齐
光伏数据里最常见的四类脏数据:夜间功率不为零(传感器零漂)、白天功率超过装机容量(辐照异常或量测错误)、连续多点为同一常数(通讯中断后的保持值)、以及限电导致的削峰(功率曲线被水平截断)。
处理方式我一般这么排优先级:夜间零漂直接置零,这个不影响预测,只是让曲线好看;超容量异常按装机容量的 1.1 倍截断;常数保持段先用滑动方差检测出来标记为缺失,再用前后正常段做线性插值或者用同类型相似日的同时刻值填补;限电段最麻烦,因为它是真实发生过的功率损失,不能简单插值,最好结合调度记录剔除,实在拿不到记录就按"功率曲线的二阶差分突变 + 持续水平段"来启发式识别。
注意:缺失填补和相似日聚类的顺序不能反。如果先聚类再填补,聚类时用的特征向量里含有缺失值,距离计算会失真。正确顺序是先清洗填补,再构造日特征,最后聚类。
3.3 气象特征构造与归一化
日特征向量的构造有几个容易忽略的点。第一,辐照度不要用瞬时值,要用日积分或者日峰值,因为瞬时值受云层瞬时遮挡影响太大。第二,温度要有日均值和日最高值两个维度,日均值反映季节背景,最高值反映当天是否有极端高温导致的效率下降。第三,天气类型要做独热编码而不是序数编码,因为"晴、多云、阴、雨"之间没有大小关系,用 0 到 3 编码会让距离计算误以为"晴"和"雨"的差异是"晴"和"多云"的三倍。
归一化方面,功率序列建议用装机容量做归一化,这样不同电站的数据可以放在一起训练,也让评价指标(比如归一化 RMSE)有统一的量纲。气象特征用 min-max 或 z-score 都行,但要注意训练集和测试集的归一化参数必须一致,且只能用训练集的统计量去变换测试集,否则又是泄漏。
4. 核心代码实现
4.1 相似日聚类与样本筛选
import numpy as np import pandas as pd from sklearn.preprocessing import StandardScaler from sklearn.cluster import KMeans # df: 索引为日期,列为 [energy, peak, valley_diff, irr_sum, temp_mean, temp_max, weather_code] def build_daily_features(df_power, df_weather, cap): daily = pd.DataFrame(index=df_power.index.normalize().unique()) daily['energy'] = df_power.groupby(df_power.index.date).sum() / cap daily['peak'] = df_power.groupby(df_power.index.date).max() / cap daily['valley_diff'] = (df_power.groupby(df_power.index.date).max() - df_power.groupby(df_power.index.date).min()) / cap daily = daily.join(df_weather) return daily.dropna() def similar_day_split(daily_feat, target_date, n_clusters=4, top_n=None): cols = ['energy', 'peak', 'valley_diff', 'irr_sum', 'temp_mean', 'temp_max'] X = StandardScaler().fit_transform(daily_feat[cols]) km = KMeans(n_clusters=n_clusters, n_init=10, random_state=42).fit(X) daily_feat = daily_feat.assign(cluster=km.labels_) target_label = daily_feat.loc[target_date, 'cluster'] pool = daily_feat[daily_feat['cluster'] == target_label].index pool = pool[pool < target_date] # 严格只用历史日 if top_n is not None: d = np.linalg.norm(X[daily_feat.index.isin(pool)] - X[daily_feat.index.get_loc(target_date)], axis=1) pool = pool[np.argsort(d)[:top_n]] return sorted(pool)两个细节值得强调。第一是pool < target_date这一行,必须严格卡死在预测日之前,包括验证集和测试集的构造也要遵守这个约束,否则聚类时用到了未来样本的信息。第二是聚类模型本身也应该只用训练期数据拟合,测试期来一个新的预测日时,用已拟合的 KMeans 去 predict 它的簇标签,而不是重新聚类。
4.2 VMD 分解的正确姿势
from vmdpy import VMD def vmd_decompose(signal, K=5, alpha=2000, tau=0.0, DC=0, init=1, tol=1e-7): signal = np.asarray(signal, dtype=float) u, u_hat, omega = VMD(signal, alpha, tau, K, DC, init, tol) return u # shape: (K, len(signal)) def rolling_decompose_predict(history, horizon, predictor, K=5): """滚动分解:只用预测日之前的历史段做分解,预测后再并入真实值""" u = vmd_decompose(history, K=K) preds = [] for k in range(K): preds.append(predictor[k].predict_next(u[k], horizon)) return np.sum(preds, axis=0)vmdpy的返回值u是 K 个分量的二维数组,omega是各分量的中心频率,可以顺手打印出来看看频率是否单调分离。如果两个分量的中心频率挤在一起,说明 K 取大了,应该往下调。
这里给出的是滚动分解的写法,严谨但慢。如果数据量大跑不动,可以退一步用"训练集整体分解 + 测试集分段分解"的折中方案:训练阶段对整段训练数据做一次 VMD 拿到 K 个分量,训练 K 个预测器;测试阶段对每个测试样本的历史窗口单独做一次 VMD,保留与原分量中心频率最接近的那一组分量顺序。这个折中的代价是分量顺序可能对不齐,需要按中心频率排序后再匹配。
4.3 分量预测模型搭建
import xgboost as xgb def make_supervised(series, window=16): X, y = [], [] for i in range(len(series) - window): X.append(series[i:i + window]) y.append(series[i + window]) return np.array(X), np.array(y) def train_per_component(u_train, window=16): models = [] for k in range(u_train.shape[0]): X, y = make_supervised(u_train[k], window) m = xgb.XGBRegressor(n_estimators=200, max_depth=4, learning_rate=0.05, subsample=0.8, colsample_bytree=0.8, random_state=42) m.fit(X, y) models.append(m) return models def recursive_forecast(model, last_window, horizon): w = list(last_window) out = [] for _ in range(horizon): p = model.predict(np.array(w[-len(last_window):]).reshape(1, -1))[0] out.append(p) w.append(p) return np.array(out)用 XGBoost 而不是 LSTM,主要是为了复现成本低。论文原文可能用的是 LSTM 或 GRU,但复现时如果用 LSTM,训练时间会翻好几倍,而且超参数调起来很麻烦。我的建议是先用 XGBoost 把整条链路跑通,确认精度在合理区间,再换 LSTM 做对比。如果换 LSTM 后精度反而下降,八成是窗口长度或者批大小没调好,不是模型本身不行。
recursive_forecast用的是递归多步预测,即拿预测值继续喂进窗口。这种方式的误差会累积,预测步长越长越明显。如果预测跨度只有几个小时(比如 4 小时,16 个点),误差累积还在可接受范围;如果是 24 小时以上的跨度,建议改成直接多输出,或者用 seq2seq 结构一次性输出整段。
4.4 组合与评价指标
from sklearn.metrics import mean_squared_error, mean_absolute_error def weighted_combine(preds, weights): return np.tensordot(weights, preds, axes=(0, 0)) def calc_metrics(y_true, y_pred, cap): rmse = np.sqrt(mean_squared_error(y_true, y_pred)) mae = mean_absolute_error(y_true, y_pred) nrmse = rmse / cap acc = (1 - nrmse) * 100 return {'RMSE': rmse, 'MAE': mae, 'nRMSE': nrmse, 'ACC(%)': acc} def error_inverse_weights(errors): inv = 1.0 / np.array(errors, dtype=float) return inv / inv.sum()评价指标这里有个必须提醒的点:不要用 MAPE。光伏功率序列在夜间是零,MAPE 的分母会出现零或者极小值,算出来的结果要么是无穷大要么是几万百分比,毫无参考意义。行业里更常用的是归一化 RMSE 和准确率(1 减去 nRMSE),前者衡量整体偏差,后者直接对应调度考核里常见的口径。
另外,评价的时候要分时段统计。整体 RMSE 好看不代表模型可用,很可能白天中段拟合得很好,早晚爬坡段一塌糊涂。建议按"爬坡段(早晨 6-9 点、傍晚 16-19 点)"和"平稳段(10-15 点)"分别算误差,这样能看出模型到底在哪一段弱。
| 指标 | 公式 | 适用性 | 注意事项 |
|---|---|---|---|
| RMSE | $\sqrt{\frac{1}{n}\sum(y-\hat{y})^2}$ | 通用,对大误差敏感 | 单位与功率一致 |
| MAE | $\frac{1}{n}\sum | y-\hat{y} | $ |
| nRMSE | RMSE / 装机容量 | 跨电站可比 | 需注明容量基准 |
| 准确率 | $(1-\text{nRMSE})\times 100%$ | 对接调度考核 | 与考核口径需一致 |
| MAPE | $\frac{1}{n}\sum | y-\hat{y} | /y$ |
5. 参数选择与调优的细节
5.1 VMD 的 K 与 alpha 怎么定
K 的确定,论文里通常用中心频率法:从 K=2 开始逐步增加,观察各分量中心频率的分布,当出现两个分量的中心频率非常接近(比如相差不到 5%)时停止,取前一个 K 值。这个方法直观,但需要人工判断。更客观的做法是看重构误差,K 增大到一定程度后重构误差趋于饱和,再增大就没有意义了。
我实际用下来,光伏日功率序列取 K=4 到 6 比较合适,15 分钟分辨率下 K=5 是个不错的起点。K 太小(比如 3)会把云遮挡引起的缓变和快速波动混在一起,高频分量不再"纯";K 太大(比如 8 以上)会出现幅值极小、频率极高的冗余分量,这些分量基本是噪声,预测它们等于在预测随机数,误差反而上升。
alpha 控制的是带宽惩罚,值越大各分量的带宽越窄、中心频率越集中。默认 2000 对大多数场景够用,如果发现分解出来的分量过于平滑、丢失了细节,可以往下调到 500 到 1000;如果分量之间频率重叠明显,往上调到 3000 到 5000。tau 一般设 0(无噪声容限),除非信号噪声特别大才设 0.1 到 0.3。
5.2 聚类数 K 的确定
聚类数用肘部法则或者轮廓系数都行。光伏日特征的聚类,K 取 3 到 5 基本能覆盖主要天气形态。K=3 大致对应"晴、多云、阴雨",K=4 会把多云再细分成"少云"和"厚云",K=5 以上意义不大,因为每一簇的样本数会少到不足以训练模型。
这里有个反直觉的经验:聚类数不是越大越好,因为聚类是为了筛选样本,不是为了分类准确。如果某一簇只有十几个样本,你拿这十几个样本去训练一个带几百个参数的模型,过拟合是必然的。我一般会加一个约束:每一簇的样本数不少于总样本的 8%,达不到就合并相邻簇。
5.3 滑动窗口与训练集划分
窗口长度决定了模型能看到多长的历史上下文。15 分钟分辨率下,一个完整的日周期是 52 个点,窗口取 32 到 48 比较合理,能覆盖大半个白天。窗口太短(比如 8)模型看不到趋势,只能学到局部抖动;窗口太长(比如 96,接近两天)会引入前一天的完整曲线,而前一天的天气形态跟今天未必相关,反而成为噪声。
训练集划分要用时间序列的方式,不能随机打乱。常见做法是前 70% 做训练、接下来 15% 做验证(调超参)、最后 15% 做测试。如果要做多折交叉验证,用时间序列的 expanding window 或者 sliding window,绝对不能用 KFold 随机切分,那等于让模型在训练时见过未来的样本。
还有一点容易被忽略:相似日筛选和训练集划分的交互。如果你的训练集是前 70% 的时间段,那么筛选相似日时也必须在训练集内部筛选,不能跨到验证集和测试集。我见过有复现代码先对全年数据聚类,再按时间切分,这就已经泄漏了。
提示:复现论文时,凡是涉及"从历史中选样本"的步骤,都要在心里默念一遍"这个操作在真实上线时能不能做到"。做不到的,就是泄漏。
6. 常见问题与排查记录
6.1 分解边界效应导致预测尾部发散
VMD 在序列两端会有边界效应,分解出来的分量在首尾若干点会出现虚假振荡。这个问题在训练时看不出来,因为训练用的是完整序列,但预测时你只对历史窗口做分解,窗口右端正好是预测起点,边界效应会直接污染第一个预测值,然后通过递归多步预测被放大。
排查方法很简单:把历史窗口向右延长一段(比如延长 20%),分解后只取左半部分的分解结果参与训练和预测,右端延长的部分用来吸收边界效应。这个技巧叫镜像延拓或者端点延拓,实现上不复杂,但对精度的改善相当明显,尤其是预测跨度超过 4 小时的时候。
6.2 分量误差累积导致组合后反而变差
理论上分解再组合应该优于直接预测,但实际复现时经常出现相反的结果——分解后的精度还不如不分解。原因通常是各分量的预测误差在求和时同向叠加了。低频分量如果系统性偏低,高频分量也系统性偏低,加起来偏差就翻了倍。
定位方法是分别看每个分量的预测误差和总误差。如果总误差显著大于各分量误差的均方根,说明误差在叠加。解决办法有两个:一是对低频分量单独做偏差校正,用验证集上的均值偏差做补偿;二是放弃全分量组合,只对能量占比最高的前几个分量做预测,把幅值极小的尾部分量直接置零。后者更简单,实测下来效果也不差,因为那些分量本来贡献的能量就不到 1%。
6.3 常见问题速查表
| 现象 | 可能原因 | 排查动作 | 处理办法 |
|---|---|---|---|
| 测试集精度远高于预期 | 数据泄漏 | 检查聚类、归一化、分解是否用了未来数据 | 严格按时间切分,所有拟合只在训练集做 |
| 精度远低于论文 | 缺失填补或异常清洗不到位 | 画出去噪前后的曲线对比 | 先处理常数值段和限电削峰 |
| 各分量预测误差高度相关 | K 取值过大产生冗余分量 | 打印中心频率分布 | 减小 K 或提高 alpha |
| 预测曲线整体偏低 | 低频分量系统偏差 | 统计各分量残差均值 | 对低频分量做偏置校正 |
| 早晚爬坡段误差大 | 窗口太短或特征缺温度 | 分时段统计误差 | 加长窗口、补充温度特征 |
| MAPE 出现天文数字 | 夜间零值 | 检查真值是否含零 | 换 nRMSE 或只看白天时段 |
| 聚类结果全按季节划分 | 特征未标准化或未加入天气编码 | 查看各簇的月份分布 | 标准化后再聚类,加入天气独热 |
7. 踩完坑之后的一些体会
复现这类论文,最忌讳的是一上来就照着公式写代码。我自己的习惯是先拿笔在纸上把每一步的输入输出画出来,标清楚哪些数据是"预测时刻已知的"、哪些是"预测时刻未知的",画完再动手。这一步花二十分钟,能省掉后面两天的调试。
还有一个体会是关于基线的。论文里给的对比模型通常是 BP、SVR、单一 LSTM 这几种,你复现的时候一定要把这几个基线都跑出来,哪怕代码多写一点。因为组合预测的增益往往不大,如果基线本身就没跑对(比如 LSTM 只训练了 20 轮就停了),你会误以为组合方法很厉害。我见过最典型的翻车现场是:直接预测的 RMSE 是 0.08,分解组合后是 0.075,看着提升了,结果把基线模型的训练轮数调够之后,直接预测的 RMSE 降到了 0.068——组合方法其实是负贡献。
最后分享一个我觉得挺实用的小技巧。在跑完整链路之前,先做一个"完美分解"的 sanity check:假设每个分量的预测完全准确,只把分量加起来看能不能还原原序列。如果还原误差超过 0.5%,说明分解环节或者分量对齐环节有问题,这时候去调预测模型是没有意义的。这个检查写起来就三行代码,但能挡掉一大半的冤枉路。
这套东西后续还能往几个方向扩:一是把相似日筛选从欧氏距离换成基于形态相似度的度量,比如考虑爬坡段的形状匹配;二是把分解从固定的 VMD 换成自适应选择,根据天气类型决定要不要分解、分解几个模态;三是把组合权重从静态改成动态,根据预测日的天气预报置信度调整各模型的权重。这几个方向都不算新,但落地成本低,适合在复现基础上继续做小论文或者工程优化。