简介:基于LSTM的短期光伏预测算法Python实现源码与配套数据集,面向计算机相关专业学生、毕业设计及期末大作业场景,以园区真实数据为对象完成光伏出力预测建模。工程中除了主预测脚本外,还提供负荷预测LSTM、单变量预测、模拟程序等多个ipynb交互式模块,方便对照不同特征组合与模型结构的预测表现;同时附带储能框架相关思路,适合进一步拓展研究。压缩包共11个文件,以ipynb为核心分析文件,辅以py工具脚本、xlsx气象与SOC数据、jpg/png示意图及README文档,整体体积仅3.89MB,结构清晰、轻量易部署。项目评审分达95分以上,代码经过严格调试可稳定运行,既可作为课程设计、大作业的完整参考,也能帮助初学者快速理解LSTM时序预测的数据处理、模型搭建与可视化呈现流程。目前已有224人学习下载,具备较高的实际参考价值。
1. LSTM短期光伏预测:这份能拿高分的大作业源码值得逐行拆开看
光伏功率预测这个题,模型本身反而不是最稀缺的部分,网上随便就能找到十几种LSTM变体,真正拉开差距的是数据怎么组织、特征怎么对齐、误差怎么算。这个高分大作业压缩包我拆过之后,发现它把几个关键环节都串起来了:园区光伏功率时序数据、储能SOC数据、ClearOutside气象拉取脚本,外加单变量和多变量两套LSTM notebook。解开zip之后按顺序跑,单变量版本能直接输出功率预测曲线和RMSE/MAE评估指标;想冲答辩高分再把手伸向气象特征、滚动预测和储能规则集联动。适合三类人:期末大作业或毕设需要可运行源码的、刚接触时序预测想少走弯路的、拿现成基线对比自己模型效果的从业者。
2. 拆开压缩包:文件结构与数据链路怎么走通
2.1 文件地图:哪些是主线,哪些是辅助
先花五分钟把文件按「数据—模型—调度」分个组再动手。主线和辅助混在一堆Copy副本里,不梳理容易跑偏。
| 文件 | 角色 | 优先级 |
|---|---|---|
| 用园区的数据测试光伏预测LSTM.ipynb | 多变量光伏预测主notebook | 主线 |
| 用园区的数据测试光伏预测-单变量.ipynb | 仅用历史功率的baseline | 主线 |
| 用园区的数据测试负荷预测LSTM.ipynb | 同一套LSTM做负荷预测 | 对比参考 |
| 储能框架-基于规则集的.ipynb | 把预测结果接入储能充放电规则 | 进阶 |
| 规则集-第一版.png | 调度规则的可视化版本 | 辅助 |
| clearoutside_url.py | 拉取晴空模型气象数据 | 辅助 |
| SOC_1101-1107.xlsx | 储能荷电状态时序数据 | 数据 |
| 模拟程序.ipynb / 模拟程序-Copy1.ipynb | 生成模拟运行数据 | 辅助 |
| README.md | 运行顺序说明 | 先读 |
两个名字带Copy的模拟程序notebook就是手动备份,跑的时候用主副本即可,别在备份上浪费时间。0.jpg这种杂项文件是运行过程中输出的可视化截图,不影响流程。
README.md建议第一优先打开,里面会写明环境依赖和运行顺序。我通常拿到这种压缩包的习惯是:先看README,再看主notebook的cell顺序,最后才去看数据文件长什么样。
2.2 园区数据与SOC数据:字段、粒度、单位
园区光伏数据常见的表头结构是:时间戳、光伏功率(kW或MW)、负荷功率、辐照度、环境温度。SOC_1101-1107.xlsx里面存的是储能电池荷电状态序列,文件名带日期段或编号段,具体字段名以下载后的实际文件为准,不要照搬别人的列名硬套。
读取xlsx一定要用pd.read_excel而不是read_csv,这是最容易翻车的第一课:
import pandas as pd df = pd.read_excel("SOC_1101-1107.xlsx", parse_dates=["time"]) df = df.sort_values("time").reset_index(drop=True) print(df.columns.tolist()) print(df.isna().sum()) print(df["time"].diff().value_counts().head())逻辑说明:先把时间列解析成datetime类型,再按时间排序,这是时序建模的前置条件。isna().sum()看空值分布,diff().value_counts()看采样间隔是否均匀——光伏数据最常见的问题是采样间隔不固定,有的时间点隔五分钟有值,有的隔一小时才有值。
参数说明:parse_dates里的"time"是列名,如果你的表头叫"timestamp"或"date"就换成对应名字。采样是否均匀决定了后续滑动窗口要不要做重采样,如果diff()统计出来间隔杂乱,我一般会先按15分钟或1小时重采样再进模型,不然LSTM学到的"时间节奏"是乱的。
单位是另一个隐蔽的坑。园区表里功率可能混着kW和MW两种量纲,SOC是百分比。拿到数据先算一列的最大值和最小值,如果功率列最大值只有个位数,多半是MW单位,需要整体乘1000换算成kW以后再和别的特征合并。
2.3 气象数据补充:clearoutside_url.py怎么用
光伏预测里最有价值的外部特征是太阳辐照度,ClearOutside这类服务能提供指定经纬度的晴空辐照度预测曲线——晴空模型(clear sky model)解决的是"如果天上没云,这个时刻理论上能发多少电"的问题,LSTM在晴空曲线上做残差学习比直接学原始功率容易得多。
脚本的调用方式一般是命令行传经纬度和日期:
python clearoutside_url.py --lat 30.27 --lon 120.13 --date 2024-05-10逻辑说明:脚本内部会构造请求URL,把经纬度和日期拼进查询参数,拉回来的数据解析成辐照度时序后保存成csv或直接返回给notebook调用。经纬度建议用园区实际坐标,精度要求不高,小数点后两位就够。
这里要特别提醒时区问题。ClearOutside返回的时间普遍是UTC,园区本地数据如果是东八区,两者有8小时偏移,不换算直接合并,辐照度峰值会和功率峰值错开一个小时,模型输入特征对齐就是错的。处理方式很简单:拉回数据后先把时间列换算成园区本地时区,再去做merge。这一步做不对,后面加再多气象特征都是负优化。
3. LSTM模型与训练流程:单变量、多变量的实现要点
3.1 为什么短时光伏预测选LSTM
光伏功率的波动主要来自云层遮挡,这种波动不是纯随机噪声,而是有条件的短期记忆——一片云飘过来,功率会在几分钟到几十分钟内持续走低,飘走后再恢复。LSTM的遗忘门和输入门恰好适合捕捉这种"有条件的时间依赖":模型能学会在辐照度骤降时把状态保留几拍,而不是像普通前馈网络那样把每个时刻当独立样本处理。
对比一下其他方案:RNN存在梯度消失问题,序列超过十几步基本学不到远距离依赖;CNN也能做时间序列,但需要把窗口设计成卷积核形态,本质是在做局部特征提取,不如LSTM直接建模时序状态传递。ARIMA这类统计模型对非线性映射能力有限,阴天转晴这种突变场景拟合得很吃力。
需要说明的是,这里说的LSTM解决的是"15分钟级到小时级的短时预测"任务。如果要预测未来三天甚至一周的发电量,LSTM不一定比数值天气预报模型好用,这属于方法边界——大作业选题时把这点写在背景里反而加分。
3.2 数据预处理:滑动窗口、归一化与切分顺序
滑动窗口是把时序数据转成监督学习样本的核心操作。窗口长度取多少直接影响模型记忆范围:15分钟采样间隔下,24步窗口覆盖6小时,基本能包含一个上午或下午的光照变化趋势;如果采样间隔是1小时,24步窗口覆盖一天,又有点过长了。
import numpy as np def make_sequences(data, window_size=24, horizon=1): X, y = [], [] for i in range(len(data) - window_size - horizon + 1): X.append(data[i:i + window_size]) y.append(data[i + window_size + horizon - 1]) return np.array(X), np.array(y)逻辑说明:对每一个位置i,取从i开始的连续window_size个时间点作为模型输入,目标是窗口结束之后第horizon个时间点的功率值。horizon=1表示预测下一个采样点,horizon=6表示预测未来6步,比如15分钟采样间隔下就是预测未来90分钟。
参数说明:window_size和horizon是需要根据你的采样间隔和任务目标调的两个参数。做单步预测时horizon固定为1,做多步预测时可以先按horizon拆出多条样本,也可以让模型输出一个长度为horizon的向量,两种做法前者更简单,后者推理速度更快。
归一化用MinMaxScaler还是StandardScaler?光伏功率包括零值,MinMax会把功率压到0到1之间,对LSTM的tanh激活函数友好;StandardScaler不怕极端值,但如果原始数据里有异常尖峰,会把正常区间压缩得很窄。我一般用MinMaxScaler。
from sklearn.preprocessing import MinMaxScaler scaler = MinMaxScaler() train_scaled = scaler.fit_transform(train_df[["power"]].values) test_scaled = scaler.transform(test_df[["power"]].values)注意fit_transform只用在训练集上,测试集只用transform。这是时序预测里最容易犯的错误之一:如果把全量数据拿去fit,测试集的信息已经提前进入了归一化参数,评估指标会虚高,答辩时被问数据泄漏问题会很被动。
切分顺序同样关键。时间序列必须按时间先后切成训练集、验证集、测试集,不能随机打乱。随便使用train_test_split默认参数的话,训练集和测试集里会混入相邻时间点的样本,模型"看到过"测试时段的趋势,预测误差小得不真实。用sklearn的TimeSeriesSplit可以规范处理交叉验证,这个后面避坑章节会展开。
3.3 模型结构:LSTM层的输入输出怎么接
PyTorch里搭建一个用于一步预测的LSTM模型,核心结构很简洁:
import torch.nn as nn class LSTMPredictor(nn.Module): def __init__(self, input_size, hidden_size=64, num_layers=2, output_size=1): super().__init__() self.lstm = nn.LSTM(input_size, hidden_size, num_layers, batch_first=True) self.fc = nn.Linear(hidden_size, output_size) def forward(self, x): out, _ = self.lstm(x) return self.fc(out[:, -1, :])逻辑说明:输入x的形状是(batch_size, seq_len, input_size),seq_len就是前面滑动窗口的window_size,input_size是特征数——单变量时等于1,多变量时等于特征列数。nn.LSTM的hidden_size是隐藏状态维度,num_layers是堆叠层数。forward里取最后一个时间步的隐藏状态out[:, -1, :],再通过全连接层映射成单个功率预测值。
参数说明:batch_first=True表示把batch放在第一维,这个要和输入数据的shape对应。hidden_size在64到128之间属于经验安全区,太小拟合不了光照变化的非线性,太大小样本上容易过拟合。num_layers堆到两层以上要留意训练时间,园区数据量通常就几千到几万行,两层足够。
如果你要做多步预测,把return self.fc(out[:, -1, :])改成参考所有时间步的hidden state,或者保留最后一个时间步再解耦成多输出头,都能实现。代码骨架不变,只是输出维度从1变成horizon。
3.4 训练循环与早停:观察loss曲线判断模型状态
训练循环里有两个细节值得注意:时间序列的DataLoader不建议开shuffle,以及要保留验证集loss做早停判断。
import torch from torch.utils.data import DataLoader, TensorDataset model = LSTMPredictor(input_size=X_train.shape[2], hidden_size=64, num_layers=2) optimizer = torch.optim.Adam(model.parameters(), lr=0.001) loss_fn = nn.MSELoss() best_val_loss = float("inf") for epoch in range(150): model.train() for xb, yb in DataLoader( TensorDataset( torch.tensor(X_train, dtype=torch.float32), torch.tensor(y_train, dtype=torch.float32) ), batch_size=64, shuffle=False ): optimizer.zero_grad() pred = model(xb) loss = loss_fn(pred, yb) loss.backward() optimizer.step() model.eval() with torch.no_grad(): val_pred = model(torch.tensor(X_val, dtype=torch.float32)) val_loss = loss_fn(val_pred, torch.tensor(y_val, dtype=torch.float32)) if val_loss < best_val_loss: best_val_loss = val_loss torch.save(model.state_dict(), "best_lstm.pt")逻辑说明:每个epoch先在训练集上跑一遍反向传播,然后在验证集上计算loss。关键在shuffle=False——时间序列样本之间有重叠窗口,shuffle会导致模型轮流看向未来的样本区间,验证集指标失去参考意义。torch.save在验证集loss降到新低时保存权重,这就是最简单的早停机制。
参数说明:batch_size取64在几百到几千条样本的场景下是舒适区。学习率0.001是Adam的默认值,但光伏数据LSTM不一定能在这个学习率下稳定收敛,loss曲线震荡时先降到0.0005再试,这个下一章展开。epochs设150配合早停,比固定跑300个epoch更稳妥——loss不降了继续跑只有过拟合风险,没有收益。
训练完成后,把测试集预测值和真实值画在同一条时间轴上。先看两条曲线的整体跟不跟得上,再看尖峰处——阴晴转换的时段预测值和真实值偏移多大,往往比总体loss更能说明模型有没有学到实质规律。
4. 超参数与评估指标:预测曲线跟得上真实值才算数
4.1 评估指标:别让MAPE坑了你
很多大作业喜欢用MAPE(平均绝对百分比误差)作为唯一指标,光伏预测里这个选择有风险。MAPE在功率为零的时刻会算出无穷大,而光伏电站夜里功率就是零,直接把整体MAPE拉爆。答辩现场被问到"为什么MAPE这么高",解释起来很被动。
| 指标 | 公式 | 适合场景 | 坑点 |
|---|---|---|---|
| RMSE | sqrt(mean((y_true-y_pred)^2)) | 整体误差水平 | 对极端误差敏感 |
| MAE | mean(abs(y_true-y_pred)) | 可解释性强的基线 | 梯度平稳,优化慢 |
| MAPE | mean(abs((y_true-y_pred)/y_true))*100 | 业务侧汇报 | 零值时刻无穷大 |
| R² | 1 - SS_res/SS_tot | 模型解释力 | 非线性拟合时参考性一般 |
实际做法是分时段评估:全天用RMSE和MAE,白天有效光照时段单独算MAPE。筛选白天时段可以用辐照度阈值,也可以用功率阈值。
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score rmse = np.sqrt(mean_squared_error(y_true, y_pred)) mae = mean_absolute_error(y_true, y_pred) r2 = r2_score(y_true, y_pred) day_mask = y_true > 50 day_mape = np.mean(np.abs((y_true[day_mask] - y_pred[day_mask]) / y_true[day_mask])) * 100逻辑说明:y_true和y_pred先做反归一化,还原成原始功率单位后再计算指标。day_mask筛选出功率大于50的时段,50这个阈值是示例值,如果电站容量是MW级,阈值要按实际量纲调整到对应比例。
参数说明:day_mape是只对白天有效时段计算的MAPE,汇报时把全天RMSE和白天MAPE分开展示,比只报一个指标更有说服力。答辩时如果老师问夜间误差为什么大,直接说"夜间功率近似为零,MAPE定义上无意义,因此分时段评估"——这是标准答复,也是评判项目严谨性的加分点。
4.2 hidden_size和num_layers:先定层数再扫宽度
调参有个优先级:层数影响模型容量上限,宽度影响在这个容量下的拟合细腻程度。对于园区级光伏数据(几千到几万条样本),我的经验路径是:
- 固定num_layers=2,hidden_size从32开始跑一次;
- 验证集loss降得不够,hidden_size翻倍到64再跑;
- 继续翻到128,如果验证集loss没有明显下降甚至上升,说明已经过拟合,回到64;
- 尝试num_layers=1,对比训练时间和验证集指标,如果1层就能达到接近的效果,就选1层。
隐藏层参数和典型规模之间没有绝对公式,但有一条可复用的判断准则:训练集loss持续下降而验证集loss在某一步开始回升,说明模型开始背训练样本了,此时无论是加层数还是加宽度都只会加剧过拟合。
4.3 学习率与batch_size:震荡和收敛慢的排查顺序
光伏功率序列有很强的自相关性,相邻时间点数值接近,这使得训练loss的梯度方向比较一致,学习率稍微大一点就容易overshoot——loss曲线上下乱跳。
排查顺序我一般是这样的:先看loss曲线形状。曲线震荡剧烈但整体不下降,学习率调到0.0005或者0.0001;曲线平缓下降但速度极慢,学习率往上调回0.001。Adam的默认学习率适合很多视觉任务,但时间序列里0.001不一定是最优,光伏功率这种连续平滑序列往往在小学习率下更稳定。
batch_size的影响更容易被忽略。batch_size太大时,一个batch里平均掉了短时波动信息,模型学不到"云层飘过"这种分钟级变化;我实测下来64以下通常比128以上更稳。但batch_size太小会让每个step的梯度噪声变大,配合小学习率勉强能压住,训练时间会拉长。折中方案是batch_size=64配lr=0.0005起步,验证集上表现好就保留,不好再做单向调整。
4.4 多变量特征组合:辐照度、温度到底加不加
多变量版本的输入特征不再是单纯的功率历史序列,而是把辐照度、温度等特征一起拼进窗口。代码上只需要改动特征列选择和input_size:
feature_cols = ["power", "irradiance", "temp"] data_for_training = df[feature_cols].values X_train, y_train = make_sequences( data_for_training, window_size=24, horizon=1 ) model = LSTMPredictor(input_size=len(feature_cols), hidden_size=64, num_layers=2)逻辑说明:滑动窗口现在切出来的每个时间片都包含多个特征列,LSTM在每个时间步看到的输入是一个向量,而不是一个标量。如果园区数据里有辐照度传感器,多变量版本通常能比单变量版本在阴晴转换时段减少误差,因为模型可以从辐照度下降提前推断功率下降。
这里有个大坑要提前说清楚:多变量模型在训练时用真实辐照度没问题,但部署预测时,未来时刻的辐照度也是未知的。测试阶段如果直接把真实辐照度喂给模型,等于偷看了未来。正确做法是拿clearoutside_url.py拉回来的晴空预测辐照度作为输入,或者退一步,用当前时刻的辐照度做特征、预测未来一个采样点——只有这种情况才不违规。
单变量和多变量在答辩时适合做对比实验:同一份测试数据,单变量RMSE多少,加辐照度后RMSE降了多少,能降出具体数字,这个论文就是完整的。
5. 避坑:光伏预测项目里最常见的六个翻车现场
5.1 训练loss一路下降,预测曲线却整体滞后一拍
现象:训练过程很顺利,loss曲线平滑下降,测试集上画出来,预测曲线整体比真实曲线晚了一个采样点,看起来像把真实曲线往右平移了一格。
原因:光伏功率自相关性极强,前一时刻的功率本身是后一时刻功率的最强预测因子,LSTM很容易学到"输出上一时刻的值"这条捷径。训练loss被这种强自相关压得很低,但模型实际没有学到光照变化的因果关系。
解决:把滞后现象量化——计算预测值和真实值的错位相关性,如果最佳匹配发生在lag=1,说明模型确实在抄上一步。对策是往输入里加入辐照度、天气类型等外部特征,打破"纯历史功率预测未来功率"的自回归捷径;或者在数据预处理时对功率序列做一阶差分,让模型学的是变化量而非绝对值。
5.2 夜间零值让RMSE虚高,模型看起来"不准"
现象:全天评估RMSE很大,白天曲线对得还行,问题全出在夜里——真实功率是零,模型输出一个接近零的小数,这本来不算大错,但RMSE对误差取平方后,夜间每个采样点都在贡献误差。
原因:评估时段没有区分光照时段和夜间时段,把物理上没有发电意义的时段也纳入了误差统计。
解决:评估时分两套口径。全天RMSE保留用于说明整体水平,额外按辐照度阈值或功率阈值筛选出白天时段,单独算RMSE和MAPE。答辩汇报时把两张表都放出来,并口头说明"夜间功率为零,MAPE定义失效,因此有效时段单独评估"——这是评审老师最容易追问的点之一,提前准备好就不会卡壳。
5.3 随机切分数据集,模型"偷看"了未来
现象:测试集误差指标好得出奇,几乎接近训练集水平,但画出预测曲线后发现测试时段和训练时段的数据在时间上是交错的。
原因:train_test_split默认随机划分,时间序列的相邻样本被打散到训练集和测试集两边。窗口重叠让测试集里出现了与训练样本几乎相同的输入窗口,评估失去意义。
解决:时间序列切分严格遵守"训练集在最前,验证集居中,测试集最后"的次序。做交叉验证时用TimeSeriesSplit代替K-fold:
from sklearn.model_selection import TimeSeriesSplit tscv = TimeSeriesSplit(n_splits=5) for train_idx, val_idx in tscv.split(X): X_train, X_val = X[train_idx], X[val_idx] y_train, y_val = y[train_idx], y[val_idx]逻辑说明:TimeSeriesSplit保证每次划分的训练集都严格在验证集之前,不会出现未来数据混入训练集的情况。n_splits=5意味着做5折时序交叉验证,每一折都保持时间次序。
参数说明:n_splits不要取太大,时序数据做交叉验证时折数越多,最后一折的训练集越大、验证集越小,指标方差会变大。5到6折在多数园区数据量级上比较稳定。
5.4 气象数据时区没对齐,辐照度峰值超前或滞后一小时
现象:加入气象特征后预测误差不但没降,反而上升了,尤其是中午时段的预测值整体偏移。
原因:ClearOutside返回的时间基准是UTC,园区本地数据一般是UTC+8,两者相差8小时。没换算直接合并特征后,模型学到的"辐照度和功率的关系"被错误的时间戳打乱了相位。
解决:拉回气象数据后第一步先做时区换算,再按时间戳取整合并。代码逻辑就是在读取数据后,对时间列加上或减去8小时的偏移量,统一到园区本地时区。合并时用pandas的merge_asof按时间戳就近匹配——光伏数据和气象数据采样时刻往往不完全对齐,近邻匹配比精确相等更稳健。
5.5 同一份代码跑两次,结果完全不一样
现象:notebook重跑一遍,loss曲线和测试集指标都与之前不同,有时差距还不小。
原因:PyTorch和NumPy的随机初始化没有固定随机种子。LSTM权重初始化是随机的,数据加载顺序也可能有随机性,导致每次训练落在不同的局部最优附近。
解决:所有涉及随机性的库统一设seed:
import random import numpy as np import torch seed = 42 random.seed(seed) np.random.seed(seed) torch.manual_seed(seed)逻辑说明:seed是全局随机种子,固定后NumPy的随机数序列和PyTorch的权重初始化都会确定下来。如果代码里用了DataLoader的shuffle,还需要给DataLoader设置generator参数,否则数据加载顺序仍然不确定。
参数说明:seed取多少不影响最终效果,只影响复现一致性。答辩前用固定seed跑出最终结果,并把seed值写进README,这是保证项目可复现的底线。GPU环境下想完全锁死结果,还需要加上torch.backends.cudnn.deterministic相关设置,但CPU环境跑通seed固定已经够用了。
5.6 SOC数据泄漏到光伏预测里,指标虚高而不自知
现象:把SOC列直接加进多变量特征后,验证集和测试集指标都大幅提升,看起来是"特征工程做得好",实际埋了一个逻辑错误。
原因:储能SOC和光伏功率之间在运行数据里强相关——光伏发得多时SOC往往在升或已经充满,SOC里隐含了"过去的发电结果"。如果用同一时刻的SOC预测同一时刻的功率,模型相当于拿着答案在考试。
解决:SOC是储能侧状态的"果",不是光伏功率的"因"。把SOC用于储能调度规则(SOC低且预测功率高时充电)是合理用法,但它不能作为光伏功率预测的输入特征。如果想用SOC这类运行状态特征,只能取历史时刻的SOC值,并且明确说明特征与预测目标之间的物理因果链,不能只看相关性。
6. 进阶:让预测结果落在实际场景里的三个验证技巧
6.1 滚动预测与单步预测要分开评估
单步预测每一步都用真实观测值更新窗口,滚动预测把上一步的预测输出推进窗口、用来预测下一步。前者是训练时的理想状态,后者才是实际部署时的运行方式。滚动预测的误差会随时间累积,单步预测误差小不代表滚动预测稳定。验证时把两种模式都跑一遍,如果滚动预测在十几步内误差急剧放大,说明模型学到的时序动态不够稳,需要回到窗口长度和特征组合上找原因。
6.2 把预测曲线接进储能规则集做联动验证
这份资源里的储能框架notebook和规则集第一版png,价值在于把LSTM预测结果接到储能的充放电策略上。规则集的核心逻辑可以梳理成一张判定表:SOC低且预测光伏功率高时转入充电,SOC高且预测功率低时转入放电,正常区间保持当前状态。这个联动验证能证明预测结果不是停在图表里的曲线,而是能驱动调度决策的信号。答辩时把这条链路讲清楚,项目的完整度立刻上一个台阶。
6.3 答辩展示放哪几张图最有说服力
我一般建议放四张:真实值vs预测值的整段时序曲线、预测值与真实值的散点图、单变量与多变量指标的对比表、白天时段MAPE的分时段统计。这四张图覆盖了"模型效果、误差分布、特征贡献、评估严谨性"四个评审最常问的维度。从那以后,我每次拿到新数据集都会强制先跑一遍单变量baseline,再往上面叠特征——不这样分不清指标提升到底是模型的功劳还是特征的功劳。这个顺序也建议你保留,希望帮到你。
本文还有配套的精品资源,点击获取