ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

随机森林温度预测模型优化:从特征工程到时序验证与残差建模

随机森林温度预测模型优化:从特征工程到时序验证与残差建模

1. 温度预测为什么值得用随机森林,它到底卡在哪儿

做气象相关的数据活儿,温度预测是最容易上手、也最容易翻车的一类任务。说它容易,是因为温度数据本身连续、噪声相对可控、历史数据积累得厚;说它容易翻车,是因为很多人拿随机森林跑一遍,MAE 看着挺漂亮(2℃ 上下),一上线发现连续三天的高温过程全错,凌晨的降温幅度永远估不准,遇到冷空气过境直接崩盘。我在几个温度预测的小项目里反复踩过这个坑,后来才想明白一件事:随机森林温度预测模型优化的难点从来不在"换个更强的算法",而在于你怎么定义特征、怎么切验证集、怎么看待那 2℃ 的平均误差背后藏着的长尾错误。

这篇内容适合三类人看:一是刚用机器学习做气象/能耗/农业相关预测,想找个靠谱基线的;二是已经在用随机森林,但发现效果卡在某个瓶颈上不动的;三是不做气象但做其他时序回归,想借鉴一下思路的。我不打算讲随机森林的数学推导——那些教科书上都有,我讲的是实操中真正影响结果的那几个决策点:滞后特征怎么构造、验证集怎么切才不算作弊、参数搜索的空间怎么定、残差怎么二次利用。

先给个结论垫底:在中小规模结构化气象特征(站点观测 + 常规要素)上,调好的随机森林通常能把 24 小时内的逐小时温度预测 MAE 压到 1.2~1.8℃ 区间,比起线性回归和单纯的历史均值持续性预报有明显提升;但如果你只喂了"前一个小时温度"这一个强特征,模型会退化成"昨天多少度今天多少度"的懒惰预测器,短期指标好看,长期完全没用。这个陷阱后面会细讲,它是这个任务里最大的坑。

注意:本文所有案例、站点、数据来源均为虚构代称,不涉及任何真实机构与真实站点。

2. 数据准备:特征工程才是这类模型真正的上限

2.1 原始观测数据的清洗与时间对齐

温度预测的第一步不是建模,是把数据对齐。真实拿到的站点观测数据通常长这样:逐小时温度、湿度、气压、风速风向、云量、能见度、降水量,字段名可能是缩写,单位可能混着(摄氏/华氏、m/s 和 km/h),时间戳可能是 UTC 也可能是本地时间,缺测值可能是 NaN、可能是 999、可能是空字符串、可能是 -9999。我一般会先写一段固定的"体检脚本",把每个字段的缺失率、取值范围、异常跳变列出来,人工过一遍再往下走。

具体做法上有几个细节值得说。时间戳统一:所有来源都先转成同一时区、统一为整点,重采样用resample('1H').mean()比interpolate更稳妥,因为插值会凭空造出平滑数据,掩盖真实的观测中断。缺测处理:连续缺测超过 3 小时的整段丢掉,单点缺测用前后各 2 个点的均值补,不要用前向填充——前向填充会让温度曲线出现阶梯状平台,模型会学到虚假的"温度不变"模式。异常值:温度的单点跳变(比如相邻小时差 15℃ 以上且前后都对不上)基本是传感器错误,直接置空再按缺测处理。

import pandas as pd import numpy as np def clean_station_data(df: pd.DataFrame, tz: str = "Asia/Shanghai") -> pd.DataFrame: df = df.copy() df["time"] = pd.to_datetime(df["time"]).dt.tz_localize("UTC").dt.tz_convert(tz) df = df.set_index("time").sort_index() # 统一异常值并转为 nan df = df.replace([999, 9999, -999, -9999, ""], np.nan).astype(float) # 逐小时重采样,同时保留观测计数用于判断可信度 cnt = df["temp"].resample("1H").count() df = df.resample("1H").mean() # 单点缺测:前后各两点均值补 df["temp"] = df["temp"].interpolate(limit=1, limit_direction="both") # 连续缺测超 3 小时整段剔除 bad = df["temp"].isna().rolling(3).sum() >= 3 df.loc[bad, :] = np.nan df = df.dropna(subset=["temp"]) return df

这段代码不长,但它决定了后面所有指标的可信度。我见过不止一次,图省事用ffill把缺失全填了,最后模型在缺失率高的月份表现异常好——因为那段时间的数据是被"复制"出来的,验证集和训练集几乎一样,等于自己给自己放水。数据清洗阶段偷的懒,一定会在验证阶段以"效果虚高"的形式还回来。

2.2 滞后特征与滑动窗口:最强特征也是最大陷阱

温度预测里,temp_lag1(前 1 小时温度)几乎必然是特征重要性排第一的变量,相关性通常在 0.98 以上。很多人到这一步就收工了,觉得模型效果已经很不错。问题是:如果模型只靠 lag1,它实际上学到的是"当前温度 ≈ 上一小时温度",这跟持续性预报(persistence forecast)没有本质区别,而气象业务里持续性预报是最基本的对照基线,打败它才叫有价值。

我踩过的坑是这样的:第一版模型只用了 lag1、lag2、lag3 和几个气象要素,24 小时预测 MAE 0.9℃,看起来很漂亮。但仔细一看预测曲线,它几乎是实测曲线整体右移一小时,温度转折点永远滞后。原因就是树模型无法外推,它把 lag 特征当成了主导,其他特征的作用被压得极低。后来我把 lag 特征的窗口拉长、同时加入变化率特征(lag1 - lag2、3 小时滑动均值 - 6 小时滑动均值),模型的转折点捕捉能力才起来。

我常用的特征组大致如下:

特征组具体构造作用注意点
短时滞后lag1~lag6 温度、湿度、气压捕捉惯性不要只留 lag1
长时滞后lag12、lag24、lag48 温度日周期与日际变化lag24 与时刻特征高度共线
滑动统计3/6/12/24h 的均值、极差、标准差刻画趋势强度极差比标准差更能反映突变
变化率diff1、diff3、diff6分离"升/降温过程"是抬升转折点精度的关键
时刻周期hour 的 sin/cos 编码日变化节律不要直接喂原始 hour 整数
日历月份 sin/cos、是否节假日(如适用)季节漂移节假日对气象无用,对能耗有用
其他要素露点差、风速、云量、日照时数辐射与平流信息露点差比湿度更稳定

时刻编码这块值得展开一句。直接把hour当数值喂进去,树模型会认为 23 点和 0 点差得很远,实际它们只差一小时。用sin(2πh/24)和cos(2πh/24)编码后,0 点和 23 点在特征空间里是相邻的,模型的切分逻辑才对得上真实物理规律。月份同理,sin/cos编码比 one-hot 更省维度,也更适合树模型做阈值切分。

def build_features(df: pd.DataFrame, target: str = "temp") -> pd.DataFrame: out = df.copy() # 短时滞后 for lag in [1, 2, 3, 6, 12, 24, 48]: out[f"{target}_lag{lag}"] = out[target].shift(lag) # 滑动统计 for w in [3, 6, 12, 24]: out[f"{target}_mean{w}"] = out[target].shift(1).rolling(w).mean() out[f"{target}_range{w}"] = out[target].shift(1).rolling(w).max() - out[target].shift(1).rolling(w).min() # 变化率 out[f"{target}_diff1"] = out[target].shift(1) - out[target].shift(2) out[f"{target}_diff3"] = out[target].shift(1) - out[target].shift(4) # 周期编码 out["hour_sin"] = np.sin(2 * np.pi * out.index.hour / 24) out["hour_cos"] = np.cos(2 * np.pi * out.index.hour / 24) out["month_sin"] = np.sin(2 * np.pi * out.index.month / 12) out["month_cos"] = np.cos(2 * np.pi * out.index.month / 12) # 露点差 if {"temp", "dewpoint"}.issubset(out.columns): out["dewpoint_dep"] = out["temp"] - out["dewpoint"] return out.dropna()

这里所有的shift(1)都是刻意的:滑动窗口如果包含了当前时刻,就等于把答案塞进了特征里。这种泄漏极其隐蔽,因为代码跑得通、指标还特别好,只有当你把它部署到真实提前量预测的场景里才会发现全错。规则很简单:任何特征都只能用 t-1 及之前的信息,预测 t 时刻的值。

2.3 评估指标怎么定,比模型参数重要十倍

温度预测如果只看一个总体 MAE,你会被平均数骗得很惨。我的做法是至少拆成三档来看:全时段 MAE、极端温度时段(如当日最高温的 90 分位以上)MAE、转折时段(3 小时内变化超过 3℃ 的样本)MAE。实际经验是,一个总体 MAE 1.5℃ 的模型,在转折时段的 MAE 可能是 3.5℃,而恰恰是这段时间的业务价值最高——升降温过程的预报准不准,决定了模型到底有没有用。

另外要明确预测提前量。提前 1 小时和提前 24 小时是完全不同的任务,前者可以重度依赖 lag 特征,后者必须依赖气象要素的物理关系。我一般会做 1h、6h、24h 三个提前量的模型,或者干脆用"直接多步"的方式为每个提前量单独训练一个模型,而不是训练一个模型然后滚动递归——递归预测会把误差一步步放大,24 步之后基本没法看。

还有一点容易被忽略:持续性基线的对照。任何温度模型都应该和"用 t 时刻的观测值直接当作 t+h 时刻的预测"比一比。如果你的模型打不过持续性基线,说明你的特征工程没有提供任何新信息,只是把 lag1 学了一遍。我见过不少所谓"优化后"的模型,在 1 小时提前量上确实赢了持续性基线一小截,但在 6 小时提前量上输得很难看,这就是典型的过度依赖滞后特征。

3. 随机森林的参数调优:别调不该调的,调对了就够用

3.1 四个真正影响结果的参数

随机森林参数不少,但真正常用的就四个,而且它们对温度这类平滑连续目标的影响方向很明确。

n_estimators(树的数量):这个参数属于"够用就行"。从 100 加到 300,验证误差通常有明显下降;300 到 800 收益递减;超过 800 基本是白烧算力。我的习惯是按数据量定:样本 1 万以内取 300,1 万到 10 万取 500,再往上 800。这里有个计算上的小参考——如果单棵树训练耗时约 0.05 秒,500 棵树串行需要 25 秒,开n_jobs=-1用 8 核并行大约 3~4 秒,这个量级做 50 组参数搜索是完全可接受的。

min_samples_leaf(叶子最小样本数):这是温度预测里最关键的正则化参数。温度是连续量,如果叶子节点只放 1 个样本,模型会去拟合观测噪声(传感器抖动、瞬时扰动),表现为训练集误差极低、验证集误差反而上升。实测下来,min_samples_leaf取 2~5 通常最优,数据噪声大时取到 8 甚至 10 也可能更好。它和max_depth有一定替代关系,一般先固定max_depth=None,只调min_samples_leaf,效率更高。

max_features(每次分裂的候选特征数):回归任务默认是1.0(即全部特征),但我更推荐设成 0.3~0.6 之间或者sqrt。原因是温度特征之间共线性极强(lag1、lag2、mean3 彼此高度相关),如果每次分裂都能看到全部特征,模型会反复选那几个强特征,树的多样性下降,集成效果打折。把候选特征限制在 30%~50%,相当于强制模型去看不同的特征组合,方差下降更明显。

max_depth:我通常不设上限,靠min_samples_leaf控制复杂度。设了固定深度反而容易出现"所有树长得一样"的问题,因为温度任务里强特征太突出,深度限制会让树在同一个位置反复提前停止。

3.2 时序数据不能用随机 KFold,这是硬伤

这一条我单独拎出来说,因为它是新手最容易犯、后果最严重的错误。cross_val_score(model, X, y, cv=5)默认是随机切分,对温度这种强自相关的时序数据,随机切分会让训练集里包含验证集时刻的前后邻居——模型相当于偷看了答案。这种情况下交叉验证分数可能比真实效果高 30%~50%,你以为调出个好模型,上线一测全是水分。

正确的做法是TimeSeriesSplit,或者更贴近业务的做法:按时间块切分。比如用 1 月到 9 月训练、10 月验证、11 到 12 月测试;或者做滚动窗口验证(前 6 个月训练,预测下 1 个月,窗口往后滑)。后一种更贴近实际部署场景,也能看出模型在不同季节的稳定性。

from sklearn.model_selection import TimeSeriesSplit, RandomizedSearchCV from sklearn.ensemble import RandomForestRegressor from scipy.stats import randint, uniform tscv = TimeSeriesSplit(n_splits=5, gap=24) # gap=24 留出一天缓冲,避免边界泄漏 param_dist = { "n_estimators": randint(300, 800), "min_samples_leaf": randint(1, 10), "max_features": uniform(0.2, 0.6), "min_samples_split": randint(2, 20), } rf = RandomForestRegressor(random_state=42, n_jobs=-1) search = RandomizedSearchCV( rf, param_dist, n_iter=60, cv=tscv, scoring="neg_mean_absolute_error", random_state=42, n_jobs=-1, verbose=1 ) search.fit(X_train, y_train) print(search.best_params_, -search.best_score_)

gap=24这个参数很关键。TimeSeriesSplit默认训练集和验证集是紧邻的,边界处的样本几乎完全相关,容易让分数虚高。加一天(24 个样本)的间隔,模拟真实的"用历史预测未来"的间隔感,分数会略降但更可信。我一般会跑两遍,gap=0和gap=24对比一下,如果两者差距很大(比如超过 15%),说明模型对近期样本的依赖过强,泛化能力存疑。

3.3 特征重要性别只看不纯度,要看置换重要性

随机森林自带的feature_importances_是基于不纯度减少算的,有个众所周知的偏差:它会高估取值多的连续特征。温度里的 lag 特征、滑动均值特征全是连续值,天然占便宜,而云量、风向这类离散或类别特征会被系统性低估,很容易误判"这个特征没用"然后删掉。

置换重要性(permutation importance)更靠谱,做法是把某一列特征的值随机打乱,看验证集误差涨多少,涨得越多说明越重要。代价是要跑多次,但针对温度任务,验证集不大,跑 10 次重复也就几十秒。实操上我会做两件事:一是按置换重要性排序,删掉重要性接近 0 甚至为负的特征(这些特征在随机化之后误差反而降低了,说明纯粹是噪声);二是按特征组看重要性,而不是按单列。比如"变化率组"合计贡献 18%,"周期组"贡献 12%,这比单看某一列更有决策价值。

4. 从基线到提升:一次完整的优化实操记录

4.1 基线模型:先把"什么都不做"的水平立住

我习惯先跑三个基线,把地板线钉死,后面所有优化都要跟它们比。

第一个是气候态基线:用历史同期(比如过去 30 天同一时刻)的均值作为预测。第二个是持续性基线:直接用 t 时刻观测值预测 t+h。第三个是线性回归基线:用同一套特征跑 Ridge 回归。

这一步的意义在于校准预期。某次我做的项目里,持续性基线在 1 小时提前量上的 MAE 只有 0.6℃,比大部分人的随机森林模型还好。这说明在超短期温度预测上,随机森林的优势空间本来就小,你花大力气优化的边际收益很低;但到了 6 小时、12 小时提前量,持续性基线迅速崩到 2.5℃ 以上,随机森林的价值才体现出来。先搞清楚任务的可优化空间,再决定投入多少精力调参。

我那次的项目,各基线在 6 小时提前量上的表现大致是:气候态 3.9℃、持续性 2.6℃、Ridge 回归(带全部特征)1.9℃、未调参随机森林 1.6℃、调参后随机森林 1.42℃。可以看到,从 Ridge 到随机森林的增益(0.48℃)比从默认参数到调优参数(0.18℃)更大,说明特征和非线性建模能力的价值高于参数精度。这也是我一直劝人不要一上来就死磕网格搜索的原因。

4.2 残差分析:把错误摊开看,才知道往哪优化

模型跑完,别急着看总体指标,先把残差(预测 - 实测)画出来,按几个维度切开看。

我一般会看四张图:残差随时间的散点(看有没有系统性偏移)、残差随预测值的散点(看是不是在某一段温度区间系统性偏低或偏高)、残差按小时的箱线图(看时刻相关的偏差)、残差按天气过程分组的分布。实操中经常能看出规律性的东西。

举个真实碰到的例子:残差按小时看,凌晨 4~6 点的残差均值是 -0.8℃(预测偏低),午后 14~16 点是 +0.6℃(预测偏高)。意思是模型把日较差压小了。原因在于树模型的预测是叶子节点样本的均值,而均值天生趋向中间、压缩极值,所以模型对"非常热"和"非常冷"的估计都会往中间收。这是随机森林的结构性缺陷,靠调参没法彻底解决,得靠后处理或者换建模方式。

另一个常见现象是残差和风速、云量相关。晴朗无风的夜晚降温快,多云或有风的夜晚降温慢,如果模型里没有明确的"辐射冷却"代理特征(比如云量 + 风速的交互项、露点差),夜间温度预测就会系统性偏。我后来加了cloud_cover * wind_speed的交互特征和dewpoint_dep之后,凌晨时段的 MAE 从 1.3℃ 降到 1.05℃。

4.3 四项针对性优化,按性价比排序

第一项:极端样本加权。温度分布本身是近似正态的,中间温度的样本多,极端温度的样本少。随机森林在损失函数上是所有样本等权,结果就是对中间温度拟合好、对极端温度拟合差。解决办法是给样本加权,让极端温度样本的权重更高。权重的算法我用得比较多的是按目标值的经验分位数来定:

def extreme_weight(y, low_q=0.1, high_q=0.9, boost=2.5): lo, hi = np.quantile(y, [low_q, high_q]) w = np.ones_like(y, dtype=float) w[(y <= lo) | (y >= hi)] = boost return w sample_w = extreme_weight(y_train.values, boost=2.5) rf.fit(X_train, y_train, sample_weight=sample_w)

boost这个值不要设太大,2~3 之间比较稳妥。设到 5 以上,整体 MAE 会明显变差,因为模型为了照顾少量极端样本牺牲了大部分常规样本的精度。这种取舍要看业务需求:如果高温预警是核心诉求,那就值得牺牲一点平均精度;如果只是做日常温度曲线展示,加权带来的收益有限。

第二项:残差二次建模。这是性价比最高的一招。做法是先用主模型预测,得到残差,再用一个轻量的模型(比如min_samples_leaf=20的随机森林或者 GBDT)去学残差和特征之间的关系,最后预测值 = 主模型预测 + 残差模型预测。本质上是让模型分两阶段学:第一阶段学主要规律,第二阶段学系统性偏差。

residual = y_train - rf.predict(X_train) rf_res = RandomForestRegressor(n_estimators=200, min_samples_leaf=20, max_features=0.5, random_state=42, n_jobs=-1) rf_res.fit(X_train, residual) final_pred = rf.predict(X_test) + rf_res.predict(X_test)

注意残差模型的min_samples_leaf一定要设大,因为残差本身信噪比低,如果让它也自由拟合,会把训练集的噪声学进去,等于变相过拟合。我这一招在 6 小时提前量的任务上通常能再降 0.08~0.15℃ 的 MAE,在 24 小时提前量上降幅可以到 0.2℃ 左右,因为提前量越长,系统性偏差越明显、残差模型越有用。

第三项:分位数随机森林做区间预测。只给一个点预测在很多场景不够用(比如农业防冻需要知道"最坏情况会降到几度")。RandomForestRegressor支持quantile参数(需要 sklearn 1.1+),或者更通用的做法是用分位数损失拟合多个模型。实操上我用GradientBoostingRegressor(loss="quantile", alpha=q)做 0.1 和 0.9 分位,得到 80% 预测区间,再检查区间的覆盖率是否接近 80%。如果覆盖率只有 65%,说明区间偏窄,需要放宽;如果覆盖率 92%,说明区间太保守,对决策没帮助。区间校准这件事经常被忽略,但实际业务里它比点预测的 MAE 更重要。

第四项:预测平滑与后处理。树模型的输出是阶梯状的(因为叶子节点值是离散的),相邻时刻的预测可能在几个值之间跳变。我一般会做一次轻度的指数平滑:pred_smooth[t] = 0.8 * pred[t] + 0.2 * pred[t-1]。系数 0.8 是我试出来的经验值,太大会削弱转折点的响应速度,太小又平滑不掉跳变。另外一个后处理是偏差校正:按小时统计验证集上的平均残差,然后从预测里减掉。这一招简单粗暴,但对时段性系统偏差特别有效,通常能降 0.05~0.1℃。

优化前后的对比我整理成了一张表,横向是不同提前量,纵向是各版本:

模型版本1h MAE6h MAE24h MAE转折时段 MAE极端温度 MAE
持续性基线0.612.583.923.104.21
Ridge 回归0.721.902.452.622.88
随机森林(默认参数)0.681.602.182.412.73
+ 时序交叉验证调参0.641.422.022.202.55
+ 极端样本加权0.651.442.052.182.21
+ 残差二次建模0.611.331.882.052.10
+ 分位数区间 + 后处理0.591.301.851.982.06

表里的数字是我在某次虚构数据集上的记录,具体值会随数据不同浮动,但相对关系有参考价值:加权在极端温度上收益最大(2.73→2.21),但在常规指标上甚至有轻微回退(0.64→0.65),这是预期内的取舍;残差二次建模则是全面提升,几乎没有副作用。

5. 实操中的常见问题与排查实录

5.1 常见问题速查表

下面这些是我和同行交流、以及自己踩坑过程中反复遇到的,按出现频率排序:

现象最可能的原因排查与解决
验证集指标远好于线上随机 KFold 导致时序泄漏改用 TimeSeriesSplit,加 gap
训练集 MAE 0.2,验证集 2.5树太深、叶子样本太少提 min_samples_leaf 到 3~8
预测曲线整体滞后一小时过度依赖 lag1加变化率特征,考虑剔除或衰减 lag1 权重
日较差被明显压缩树模型均值化倾向极端样本加权、残差建模、分位数预测
夜间温度系统性偏低缺辐射冷却相关特征加云量×风速、露点差
换季时效果突然变差分布漂移,模型未更新按月重训或加月份特征做分组训练
特征重要性里风向排第一不纯度重要性偏差改用置换重要性重新评估
不同批次预测结果不一致特征计算版本不一致特征工程固化为同一份代码与配置
预测值出现重复阶梯叶子节点值离散轻度指数平滑后处理
极端高温全部低估样本稀疏 + 回归均值加权 + 单独训练高温子模型

5.2 特征管线一致性:最容易在部署时翻车的地方

模型调得再好,如果训练时和推理时的特征算法不一致,线上效果一定崩。这种问题在温度预测里特别常见,因为特征里有大量滚动窗口和滞后项,只要时间对齐错了半小时,lag1就变成了lag2,整个模型的输入分布就偏了。

我的做法是把特征构造写成一个带状态的类,训练时fit记住最后的窗口状态,推理时喂新数据就能续上,而不是每次重新算全量历史。另外把特征列表和顺序也固定下来,加载模型时校验一下feature_names_in_,对不上直接报错,别让模型静默地用错列顺序——随机森林不会报错,它只会给你一个莫名其妙的结果。

模型体积也得留意。500 棵树、深度不限、样本几万条的随机森林,用 joblib 存下来可能到几十 MB 甚至上百 MB,如果要做边缘端部署(比如站点本地推理),得把n_estimators降下来或者做树剪枝。我一般会用max_depth限制到 20 以内,配合min_samples_leaf,体积能压到原来的三分之一,精度损失通常在 0.03℃ 以内。

5.3 模型会衰减,别指望一次训练管一年

温度预测模型有个特点:它对季节的适应性会随时间漂移。你在春秋季训练出来的模型,到了盛夏的高温期,特征分布对不上,效果会明显下滑。我用同一套模型做过连续 12 个月的跟踪,发现夏季月份的 MAE 比全年平均高 25%~35%,冬季次之,春秋最好。原因不难理解:夏季温度日较差大、对流活动强、突发性强,而训练样本里这类极端形态占比低。

对应的策略有三条,按投入从低到高:一是按月重训,用最近 12 个月的数据滚动训练,成本可控,效果稳定;二是分段建模,按季节(或按温度区间)分别训练模型,用月份特征做路由,模型复杂度上升但精度更好;三是在线更新样本权重,让近期样本权重更高,用sample_weight = exp(-Δt / τ)衰减,τ取 30~60 天。

我个人更倾向第一种,因为简单、可解释、好维护。分段建模听起来高级,实际调起来问题不少:分段边界怎么定、样本少的季节模型稳不稳、切换点会不会跳变,都是麻烦。滚动重训配合固定的特征管线,是我用下来最省心的方案。

还有一点提醒:重训之后一定要做新旧模型对比,别直接覆盖。用同一段回测期把新旧模型的指标都跑一遍,如果新模型在转折时段变差了,哪怕总体 MAE 略好也要慎重——转折时段的业务价值更高。我给自己定的规则是:总体 MAE 提升超过 3% 且转折时段没有退化(退化不超过 2%),才允许替换线上模型。

6. 几个我认为最值钱的经验

如果只能记三条,我会挑这三条。

第一条,先打败持续性基线,再谈优化。随机森林在超短期温度预测上的优势没有想象中大,很多时候费半天劲调出来的模型,跟"上一小时多少度就报多少度"打平。把基线立住,你会更清楚优化的方向在哪里,也不容易被虚高的指标自我感动。

第二条,验证集切分方式决定你看到的数字是真是假。时序数据的随机切分是最普遍的隐性作弊,而且它给出的分数特别诱人。我现在的习惯是,任何时序模型的第一行代码就是写TimeSeriesSplit,宁可一开始分数难看,也不要后面返工。

第三条,残差里藏着最多的信息。调参的边际收益会很快见底,而按小时、按温度区间、按天气类型切开看残差,几乎每次都能发现一两个能补的特征或能做的后处理。它比调参枯燥,但性价比高得多。

最后分享一个小技巧:把每次实验的配置、特征版本、验证分数、残差图路径都记在一个固定的表格里,包括失败的尝试。我一开始懒得记,后来发现"这个参数组合好像试过"这种记忆模糊的情况太常见,重复劳动浪费的时间远超记录的成本。哪怕只是在一个文本文件里按日期追加几行,长期看都值。

返回列表