01-开篇-时间序列预测到底难在哪
时间序列预测入门到实战一· 开篇时间序列预测到底难在哪本文是「码海寻道」《时间序列预测入门到实战》系列的第 1 篇。整个系列会从 ARIMA 一路讲到时序大模型覆盖统计、机器学习、深度学习、大模型四个时代。这一篇不讲任何具体模型只做两件事把时序为什么难讲清楚以及立下贯穿全系列的规矩——一个统一的数据集和一套统一的评估框架。后面每一篇的模型都会在同一份数据、同一套指标上跑让你能横向对比同一个问题16 种解法各自的精度和成本。一个让人栽跟头的场景你用 XGBoost 做过用户流失预测、点击率预估、风控评分效果都不错。有一天业务找过来帮忙预测下个月每天的服务器负载吧好提前扩容。你很自然地把它当成一个回归问题把日期拆成年、月、日、星期几当特征把 CPU 负载当标签train_test_split一切XGBoost 一跑验证集 R² 高达 0.95。你很满意地上线了。然后线上预测惨不忍睹。问题出在哪不是模型不行是你从一开始就用错了范式。train_test_split默认会把数据打乱随机切分——于是你的模型在训练时见过了 5 月 20 日之后的数据再回头去预测 5 月 15 日。验证集 0.95 是用未来预测过去刷出来的假象这在时序里有个专门的名字数据泄露data leakage。时间序列预测的第一课就是先忘掉一部分你在普通监督学习里养成的习惯。一、时序预测 vs 普通回归本质差在哪普通监督学习有一条几乎不言自明的假设样本独立同分布i.i.d.。每一行数据是独立的一次观测行与行之间没关系所以你可以随便打乱、随便切分、随便做交叉验证。时间序列把这条假设彻底打破了普通回归时间序列样本关系相互独立顺序即信息前后强相关打乱数据无所谓直接毁掉信号X 和 y边界清晰模糊——过去的 y 就是未来的 X划分数据集随机切分必须按时间切目标拟合 X→y 的映射建模历史如何影响未来其中最要命、也最常被工程师忽视的一条是数据集划分。时序里训练集必须严格在测试集之前否则就是拿未来的信息训练、回头预测过去指标虚高上线打脸。fromsklearn.model_selectionimporttrain_test_split# ❌ 错误随机打乱未来数据泄露进训练集X_tr,X_te,y_tr,y_tetrain_test_split(X,y,test_size0.2,shuffleTrue)# ✅ 正确按时间顺序切训练集永远在测试集之前nlen(df)traindf.iloc[:int(n*0.7)]valdf.iloc[int(n*0.7):int(n*0.85)]testdf.iloc[int(n*0.85):]记住这条铁律时序里任何形式的随机打乱、随机 K 折交叉验证默认都是错的。后面讲评估时我们会介绍时序专用的滚动回测那才是正确姿势。二、时序到底难在哪三大挑战如果只是不能打乱那还不算难。真正让时间序列成为一个独立研究领域的是下面三个特性。挑战 1时间依赖自相关今天的值和昨天的值高度相关昨天又和前天相关——这叫自相关。它带来一个普通回归没有的麻烦误差会沿时间传递并累积。预测未来 1 步通常不难难的是预测未来 30 步。因为第 2 步的输入里往往包含了第 1 步的预测值第 1 步错一点第 2 步在错误的基础上继续错误差像滚雪球一样放大。这就是为什么后面我们会专门区分单步预测和多步预测以及为什么多步预测有好几种不同的策略。挑战 2非平稳分布随时间漂移平稳大致是指序列的统计特性均值、方差、自相关结构不随时间改变。现实世界的数据几乎都是非平稳的有趋势用户量逐年增长均值在漂移有方差变化促销季波动剧烈平时很平有结构突变一次改版、一场疫情整个规律都变了。非平稳的致命之处在于你在训练集上学到的分布到了测试集可能已经不成立了。这也是为什么传统统计方法如 ARIMA第一步往往是做差分把序列变平稳以及为什么概念漂移在时序里是个绕不开的话题。挑战 3季节性与周期用电量白天高、深夜低日周期工作日高、周末低周周期夏冬两季空调开满年周期。这些周期还经常叠加在一起——一条真实的用电量曲线往往同时含有日、周、年三个周期。季节性本身不难难的是它和趋势、噪声混在一起你得先把它们拆开才能看清。这正是下一篇要讲的时序分解也是 Prophet、TimesNet 等模型的核心思想来源。一句话总结自相关让误差累积非平稳让分布失效季节性让信号纠缠。三者叠加才有了时间序列这门学问。用几行代码把三个挑战看在眼里空口无凭这三个挑战全都能直接画出来。正式的数据体检留到下一篇这里先用几行代码建立直觉importmatplotlib.pyplotaspltfromstatsmodels.graphics.tsaplotsimportplot_acf sdf[OT]# 挑战②·非平稳30 天滚动均值/标准差在漂移说明分布不稳定rolls.rolling(24*30)roll.mean().plot(label滚动均值)roll.std().plot(label滚动标准差)plt.legend();plt.title(非平稳统计量随时间漂移);plt.show()# 挑战③·季节性按小时聚合看日周期按星期聚合看周周期s.groupby(s.index.hour).mean().plot(markero,title日周期24 小时内的规律)plt.show()s.groupby(s.index.dayofweek).mean().plot(markero,title周周期周一到周日)plt.show()# 挑战①·自相关ACF 一眼看出“过去影响未来”能延伸多远plot_acf(s,lags72)# 看 72 小时内的自相关plt.show()跑完你会看到ACF 在滞后 24、48 小时处规律性地冒出尖峰——这正是今天几点和昨天、前天同一时刻强相关的铁证。三大挑战不是抽象概念它们明明白白写在数据里。三、动手之前先想清楚你要解决哪类问题拿到一个时序任务别急着套模型。先用三个维度给它定位——这直接决定你该选什么模型、怎么评估。维度 1单步 vs 多步单步只预测下一个时刻下一小时的负载。多步预测未来 H 个时刻未来 7 天每天的负载。H 叫预测长度 / horizon。多步预测要难得多光怎么一次预测多个点就有递归、直接、多输出三种策略我们会在讲机器学习和深度学习时反复碰到它。维度 2单变量 vs 多变量单变量只用目标序列自己的历史只看负载的过去预测负载的未来。多变量还引入其他协变量温度、是否节假日、促销计划……。这里有个容易被忽略的细分协变量又分过去才知道的如实际气温和未来已知的如下周的促销日历、法定节假日。能不能把未来已知信息喂进模型是很多模型的分水岭——这正是后面 TFT 那一篇的主角。维度 3点预测 vs 概率预测点预测给一个数——“明天负载 8000”。概率预测给一个区间或分布——“明天负载有 90% 概率落在 7000~9000”。工业界里区间往往比点更有用。做容量规划时你要的是最坏情况下要准备多少资源一个光秃秃的点估计给不了这个答案。DeepAR、TFT 这些模型之所以重要很大程度就是因为它们输出的是分布而非点。四、立规矩贯穿全系列的数据集与评估框架这是本篇最实的部分也是整个系列的地基。从下一篇开始所有模型都在这套框架上跑。4.1 数据集ETT电力变压器温度我们全程使用公开数据集ETTElectricity Transformer Temperature具体用小时级的ETTh1。选它的理由很实在公开可下载来自论文 InformerAAAI 2021GitHub 仓库一键获取规模适中约 2 年、17420 个小时级采样点笔记本就能跑周期丰富目标列油温OT有清晰的日周期和年周期适合演示季节性单变量、多变量都能做除OT外还有 6 个负荷相关的协变量HUFL、HULL、MUFL等想做单变量就只用OT想做多变量就把 6 个负荷一起喂进去是学术 benchmark后面讲 Informer、Autoformer、PatchTST、时序大模型时我们跑出来的数字能直接和论文对照心里有底。importpandasaspd# ETTh1.csv 可从 https://github.com/zhouhaoyi/ETDataset 下载dfpd.read_csv(ETTh1.csv,parse_dates[date]).set_index(date)print(df.shape)# (17420, 7)print(df.columns.tolist())# [HUFL, HULL, MUFL, MULL, LUFL, LULL, OT]# 我们的预测目标是 OT油温统一的切分方式按时间绝不打乱nlen(df)traindf.iloc[:int(n*0.6)]# 前 60% 训练valdf.iloc[int(n*0.6):int(n*0.8)]# 中间 20% 验证testdf.iloc[int(n*0.8):]# 最后 20% 测试先画一眼对数据有个感性认识——能明显看到一天一个来回的日周期importmatplotlib.pyplotasplt df[OT].iloc[:24*14].plot(figsize(12,3))# 前 14 天plt.title(ETTh1 · 油温 OT 前 14 天);plt.ylabel(温度)plt.tight_layout();plt.show()4.2 评估指标四个数字各有脾气我们统一汇报四个指标。它们不是越多越好而是各自照亮一个侧面MAE平均绝对误差最直观和原数据同量纲平均差 200 度一句话就懂。RMSE均方根误差对大误差惩罚更重先平方你在意偶尔错得离谱就看它。MAPE平均绝对百分比误差相对误差跨量级可比。但有坑真实值接近 0 时会爆炸有 0 值时直接除零。sMAPE对称 MAPE用预测和真实的均值做分母缓解了 MAPE 的除零和不对称问题。一个可复用的评估函数后面每篇import它就行importnumpyasnpdefevaluate(y_true,y_pred):y_true,y_prednp.asarray(y_true),np.asarray(y_pred)erry_pred-y_true maenp.mean(np.abs(err))rmsenp.sqrt(np.mean(err**2))# MAPE屏蔽真实值接近 0 的点避免除零爆炸masknp.abs(y_true)1e-6mapenp.mean(np.abs(err[mask]/y_true[mask]))*100# sMAPE分母用 |真实||预测|天然规避除零smapenp.mean(2*np.abs(err)/(np.abs(y_true)np.abs(y_pred)1e-6))*100return{MAE:mae,RMSE:rmse,MAPE(%):mape,sMAPE(%):smape}选指标的经验同量纲看 MAE/RMSE跨序列比较看 MAPE/sMAPE数据里有 0 或接近 0 的值果断放弃 MAPE 改用 sMAPE 或 MAE。4.3 一定要有的基准线别一上来就上大模型这是整个系列我最想让你养成的习惯任何模型都要先和一个naive 基准比。如果你的深度学习模型累死累活还打不过照抄上一个值那它就没有存在的意义。最朴素的基准有两个都不需要训练**Naive持续性**用最后一个值预测未来所有点“下一刻和此刻一样”**Seasonal Naive季节性**用上一个周期同相位的值“这个周二和上个周二一样”。哪个更强不能想当然必须实测——很多人默认有周期就该用 Seasonal Naive但这得看数据的脾气。defnaive(series,horizon):Persistence用最后一个观测值预测未来所有点最朴素的基准。returnnp.full(horizon,series[-1])defseasonal_naive(series,horizon,period24):用一个周期前同相位的值预测。period24 即日周期小时级数据。histseries[-period:]returnnp.array([hist[i%period]foriinrange(horizon)])# 端到端在 test 上同时评估两个基准线看差距有多大targettest[OT].values period,horizon24,24historytarget[:period]# 用“昨天”这 24 小时y_truetarget[period:periodhorizon]# 预测“今天”这 24 小时forname,y_predin[(Naive,naive(history,horizon)),(SeasonalNaive,seasonal_naive(history,horizon,period)),]:print(f{name:14s},evaluate(y_true,y_pred))上面只是单个窗口偶然性很大。真正该做的是在整个测试集上滚动评估——用一个个不重叠的 24 小时窗口逐段预测再汇总完整的滚动回测第 16 篇细讲。在 ETTh1 上滚动 145 个窗口真实结果是基准MAERMSEsMAPENaive1.672.2426.1%Seasonal Naive1.712.2430.5%意外吧Naive 反而赢了 Seasonal Naive。因为 ETTh1 的油温是缓变量、日周期很弱和上一小时一样比和昨天此刻一样更靠谱。这上了宝贵的一课基准线必须实测别被有周期就用季节基准的直觉骗了。所以本系列的及格线就取两者中更强的NaiveMAE 1.67。你会惊讶于它有多难打败——下一篇 ARIMA 就来试试看能不能撬动这个看似平平无奇的数字。全系列路线图本篇建立了认知、立好了规矩接下来我们沿着方法演化史往下走阶段篇目关键词打地基2平稳性、季节性分解统计方法3–4ARIMA、指数平滑机器学习5–7特征工程、LightGBM、Prophet深度学习8–14LSTM、TCN/TimesNet、Transformer 家族、DLinear/PatchTST/iTransformer、N-BEATS/N-HiTS、DeepAR、TFT大模型15TimesFM、Chronos、Moirai收官16选型决策树 工程落地小结时序预测不是普通回归样本不独立绝不能随机打乱、随机切分否则数据泄露、指标虚高。它难在三点自相关让误差累积、非平稳让分布失效、季节性让信号纠缠。动手前先给任务定位单步/多步、单变量/多变量、点预测/概率预测——三个维度决定选型。全系列立了规矩数据集用ETTh1指标用MAE/RMSE/MAPE/sMAPE并且任何模型都要先打败 Naive 基准线MAE 1.67。下一篇我们给数据做一次体检如何判断平稳性、如何用 STL 把一条曲线拆成趋势、季节和残差——这是所有时序建模的第一步。思考题为什么随机 K 折交叉验证在时序里是错的而滚动回测是对的如果你已经有答案那你已经理解了本篇的一半。看到这里说明你是认真的。关注公众号「码海寻道」我会把这 16 篇一路更完——从 ARIMA 到时序大模型用同一份数据、同一套指标带你把时间序列彻底走一遍。下一篇见。