大数据宏观预测 Big Data Macroeconomic Forecasting
用成百上千个预测子(predictors)预测 GDP、CPI、通胀,是过去十年宏观实证最活跃的方向之一。本页系统讲清楚:为什么传统 VAR / 单变量 AR 不够用,Lasso / 岭回归 / 弹性网如何做稀疏选择,随机森林与 XGBoost 如何捕捉非线性,以及如何用滚动窗口 + Diebold-Mariano 检验在样本外严格比较这些模型。附完整 Python (sklearn) 与 Stata (elasticnet) 代码。
pandas、numpy、scikit-learn、xgboost、scipy);Stata 16+(内置 elasticnet / lasso)。01 为什么用大数据预测宏观
传统宏观预测(1990 年代之前)几乎只用少量变量:预测 GDP 就用 GDP 自己的滞后项加利率;预测通胀就用菲利普斯曲线里的失业率 + 滞后通胀。这种"小模型"思路在平稳的样本期里没问题,但一旦出现结构性变化(金融危机、疫情、供应链冲击),少变量模型就会漏掉关键信号。
大数据宏观预测的逻辑完全不同:与其在经济理论指导下精心挑 5 个预测子,不如把 FRED-MD(McCracken & Ng 2016 维护的美国宏观月度数据库,128+ 个变量)、Wind/Choice 的成百上千个宏观指标全部塞进模型,让正则化或树模型自己挑。这就是 Stock & Watson 从 2002 年起持续推动的"data-rich environment"预测范式。
当预测子个数 $p$ 接近甚至超过样本量 $T$ 时(例如月度数据 400 期、预测子 128 个 + 滞后项后 $p$ 可能达到几百),OLS 要么不可逆、要么过拟合严重。你需要:(i) 正则化(把不重要的系数压到 0),或 (ii) 降维(扩散指数 / PCA),或 (iii) 非线性模型(树模型自动选择变量)。
本页聚焦两条主流路线:线性正则化模型(Lasso / Ridge / Elastic Net,第 2 节)和非线性机器学习模型(随机森林 / XGBoost,第 3 节)。第 4 节解决"怎么公平比较"的问题——样本外滚动窗口 + Diebold-Mariano 检验。
02 稀疏模型:Lasso / Ridge / Elastic Net
2.1 从 OLS 到正则化
标准 OLS 最小化残差平方和:$\min_\beta \sum_{t=1}^T (y_t - \mathbf{x}_t^\top\beta)^2$。当 $p$ 大时,这个目标函数对噪声敏感。正则化方法在目标函数后加一个惩罚项 $\lambda P(\beta)$:
$\lambda\ge 0$ 控制惩罚强度:$\lambda=0$ 回到 OLS,$\lambda\to\infty$ 把所有系数压到 0。关键是怎么选 $\lambda$——必须用样本外 / 交叉验证,不能在样本内选。
2.2 Ridge 岭回归:L2 惩罚
Ridge 把系数向 0 收缩,但不会把任何系数精确压成 0——它做"收缩"不做"选择"。优点是计算稳定(解析解)、对高度共线的预测子友好;缺点是当真实模型稀疏(只有少数预测子重要)时,它仍把噪声变量的小系数保留在模型里。
2.3 Lasso:L1 惩罚
Lasso 同时做两件事:收缩 和 变量选择。L1 惩罚在原点有"尖角",会把系数精确压成 0——这正是稀疏模型想要的:成百上千个候选预测子里,Lasso 自动挑出真正重要的十几个,其余系数为 0。这在宏观预测里尤其重要,因为经济解释需要知道"哪些变量真正驱动了通胀"。
2.4 Elastic Net 弹性网
Elastic Net 把 L1 和 L2 混合:$\alpha=1$ 是纯 Lasso,$\alpha=0$ 是纯 Ridge。当预测子之间高度相关(宏观数据里利率、汇率、资产价格经常一起动)时,Lasso 倾向于只随机保留其中一个,Elastic Net 则倾向于成组保留。Medeiros et al. (2021) 的通胀预测里 Elastic Net 表现就很稳健。
2.5 交叉验证选 $\lambda$
$\lambda$ 怎么定?标准做法是 $K$ 折交叉验证(宏观时间序列上推荐时间序列 CV,即 forward-chaining,不能随机打乱):
- 把时间序列按时间顺序分成 $K$ 折(例如 $K=5$)。
- 对一组候选 $\lambda$(通常 $\lambda_{\max}$ 到 $\lambda_{\min}=0.001\lambda_{\max}$ 对数网格上 100 个点),每次用前 $k-1$ 折训练、第 $k$ 折验证,计算 MSE。
- 选平均验证 MSE 最小的 $\lambda^*$;实务中也常选"1-SE 准则"(在最小 MSE 一个标准误范围内选最大的 $\lambda$,模型更稀疏)。
宏观时间序列上做 CV,绝不能用 sklearn 的 KFold 随机打乱——那会把未来信息泄露到训练集。必须用 TimeSeriesSplit,或干脆用第 4 节的滚动窗口直接做样本外预测。
03 非线性模型:随机森林与 XGBoost
3.1 为什么宏观预测需要非线性
线性模型假设 $y_t = \beta^\top x_t + \varepsilon_t$,但宏观经济关系常常是非线性、门限型、交互型的:油价冲击在通缩期和通胀期影响不同;政策利率接近零下限时传导机制不同;金融压力大时失业率对产出的弹性更高。Lasso/Ridge 只能拟合线性关系,树模型通过递归二分自动捕捉这种非线性和交互项。
3.2 随机森林 (Random Forest, Breiman 2001)
随机森林的核心是"bagging + 随机特征选择":
- 对原始样本有放回地 bootstrap 抽取 $B$ 个子样本(通常 $B=500$ 或 $1000$)。
- 每个子样本上长一棵 CART 回归树,但每次分裂时只随机抽 $m \approx \sqrt{p}$ 个候选特征(回归问题常用 $m=p/3$)。
- 对所有树的预测取平均:$\hat{y} = \frac{1}{B}\sum_{b=1}^B \hat{f}_b(x)$。
随机森林天然给出变量重要性(每次分裂带来的 MSE 下降的累加),这正是宏观预测论文最需要的解释性输出。Medeiros et al. (2021) 的核心发现之一就是:随机森林在美国通胀预测上系统性地跑赢 AR、ARDI、Lasso。
3.3 XGBoost / LightGBM 梯度提升树
XGBoost (Chen & Guestrin 2016) 与随机森林的区别是"boosting":树是顺序长出来的,每一棵新树都去拟合上一轮的残差,从而一步步降低偏差:
其中 $\nu$ 是学习率(shrinkage,常用 0.01–0.1)。XGBoost 在损失函数里额外加了树复杂度惩罚 $\Omega(h) = \gamma T + \frac{1}{2}\lambda\|\mathbf{w}\|^2$,防止单棵树过大。LightGBM (Ke et al. 2017) 用直方图算法 + Leaf-wise 生长,速度比 XGBoost 快一个数量级,在大样本宏观数据上更常用。
3.4 超参数调优
树模型没有解析解,超参数必须调。宏观预测里重点调这几个:
| 模型 | 关键超参数 | 推荐搜索范围 | 调优方法 |
|---|---|---|---|
| 随机森林 | n_estimators, max_features, max_depth, min_samples_leaf | 树数 500–2000;max_features = p/3;max_depth = 5–15 | 时间序列 CV / 网格搜索 |
| XGBoost | learning_rate, max_depth, n_estimators, subsample, colsample_bytree | 学习率 0.01–0.1;深度 3–8;subsample 0.7–0.9 | 时间序列 CV / 贝叶斯优化 |
| LightGBM | 同上 + num_leaves, min_child_samples | num_leaves = 31–127;min_child_samples = 20–100 | 同上 |
超参数选择不能用全部历史数据 CV 完再做最终样本外预测——那等于把测试集信息泄露进调参。正确流程:把样本切成 训练-验证-测试 三块;在训练-验证上选超参数,最后在完全没碰过的测试集上一次性评估。
04 模型比较与预测评价
4.1 样本外预测:滚动窗口 vs 递归窗口
宏观预测的黄金标准是样本外预测(out-of-sample, OOS),而且必须模拟真实的"实时预测"情景:
- 滚动窗口(rolling window):每次只用最近 $W$ 期数据训练(例如最近 120 个月),预测 $h$ 期后丢弃最旧一期加入最新一期。适合有结构突变的样本。
- 递归窗口(recursive / expanding window):训练集随时间不断扩大,永远用所有历史数据。适合样本短、结构稳定的样本。
每一步都只用当时已知的信息预测下一期,绝对不能用未来数据。把所有 $h$ 期预测拼起来,得到一个 $T_{\text{test}}$ 长度的预测误差序列 $\hat{\varepsilon}_t = y_t - \hat{y}_{t|t-h}$。
4.2 预测误差指标
宏观预测论文里 RMSE 是默认指标(对大误差更敏感,符合央行"避免大错"的偏好)。MAE 对离群值稳健。MAPE 在通胀接近 0 时会爆炸,要小心。论文里几乎总要报告相对基准的相对 RMSE($\text{RMSE}_{\text{model}} / \text{RMSE}_{\text{benchmark}}$),基准通常是 AR(p) 或随机游走。比值小于 1 才算"跑赢"基准。
4.3 Diebold-Mariano 检验
光看 RMSE 谁小没用——两个模型的 RMSE 差异可能只是噪声。Diebold & Mariano (1995) 提出正式检验:比较两个预测模型的损失差序列是否均值为 0。定义损失差分 $d_t = L(e_{A,t}) - L(e_{B,t})$(常用平方损失 $L(e)=e^2$ 或绝对损失 $|e|$),DM 统计量:
其中 $\hat{LRV}(d)$ 是 $d_t$ 的长期方差估计(Newey-West HAC,因为 $h$ 步预测误差天然有 $h-1$ 阶自相关)。原假设:两个模型预测精度相同。$p$ 值很小意味着一个模型显著优于另一个。论文里必须报告 DM 检验,不能只报 RMSE 数值。
05 通胀预测案例:Medeiros et al. (2021, JBES)
这是大数据宏观预测的标志性论文。作者把美国 FRED-MD 数据库(128 个月度宏观变量)作为预测子池,用滚动窗口预测美国 CPI、PCE 通胀的 $h=1, 3, 6, 12$ 个月 ahead 通胀,比较:
- 线性基准:AR(p)、ARDL、因子模型(diffusion index)、Lasso、Ridge、Elastic Net。
- 非线性 ML:随机森林(RF)、条件推断森林、神经网络。
核心发现:(i) 随机森林在几乎所有预测期上都显著跑赢线性基准,相对 AR 基准的 RMSE 比值低 10%–25%;(ii) RF 的优势主要来自非线性结构和变量交互——它能识别出"油价冲击在低通胀区间影响更强"这类门限效应;(iii) Lasso 在短预测期($h=1$)表现尚可,但在中长期($h=6,12$)被 RF 反超;(iv) 变量重要性分析显示,自回归项、价格类指标、就业类指标是通胀预测的核心驱动。这篇论文是"ML 能显著改善宏观预测"的最有力证据之一,也是后续中文大数据宏观预测论文(包括郑挺国等 2025)的方法论模板。
作者在文章附录公开了全部 Python 代码和 FRED-MD 数据处理脚本。复现这篇论文是学习大数据宏观预测的最佳练习:先复现 AR 基准,再实现 Lasso,最后实现 RF,比较 RMSE。
06 中国 CPI 预测:郑挺国、范馨月、靳炜 (2025, 管理科学学报)
中文大数据宏观预测的代表作。作者构建了包含 9 个大类、上百个月度指标的中国宏观大数据集(涵盖价格、产出、货币、财政、外贸、金融、景气调查等),系统比较:(i) 正则化线性模型(Lasso / Ridge / Elastic Net);(ii) 树模型(随机森林、XGBoost);(iii) 传统扩散指数 AR 基准;在滚动窗口下预测中国 CPI 的 1–12 个月 ahead 通胀。
主要结论:随机森林和 XGBoost 在中长期(6–12 个月)CPI 预测上显著优于线性模型和 AR 基准;它们的优势来自两点——(a) 非线性设定让模型能捕捉价格传导的门限和交互;(b) 非稀疏的变量利用方式把成百上千个宏观指标都用上,而不像 Lasso 那样只挑十几个。变量重要性分析筛出的核心变量包括:CPI 自身的滞后项、PPI 同比、M2 增速、工业增加值、城镇就业、大宗商品价格等,与经济直觉高度一致。
国内顶刊现在已经接受"用 ML 预测宏观变量"的论文范式。写作要点:① 数据要透明(明确数据源、样本区间、变量变换);② 必须做样本外滚动窗口,不能只报样本内 $R^2$;③ 必须报告相对 AR 基准的相对 RMSE 和 DM 检验;④ 必须给出变量重要性,让审稿人知道模型不是黑箱。
07 完整 Python 代码:滚动窗口比较 Lasso / RF / XGBoost
下面这段代码不依赖外部数据,自己模拟一个"数据丰富环境"的宏观时间序列:构造 50 个预测子(其中只有少数真正与 $y$ 有非线性关系),然后在滚动窗口上比较 AR(1) 基准、Lasso、Ridge、随机森林、XGBoost 的 RMSE,并做 Diebold-Mariano 检验。在 `pip install pandas numpy scikit-learn xgboost scipy` 后可直接运行。
# -*- coding: utf-8 -*-
"""
大数据宏观预测:滚动窗口下比较 Lasso / Ridge / RF / XGBoost
作者: 经济学研究工作站
参考: Medeiros et al. (2021, JBES); 郑挺国等 (2025, 管理科学学报)
"""
import numpy as np
import pandas as pd
from sklearn.linear_model import LassoCV, RidgeCV, ElasticNetCV
from sklearn.ensemble import RandomForestRegressor
from sklearn.preprocessing import StandardScaler
from scipy import stats
import warnings
warnings.filterwarnings("ignore")
np.random.seed(20260911)
# ================================================================
# 1. 模拟"数据丰富"宏观数据:T=480 个月, p=50 个预测子
# 真实 DGP: y 由 5 个变量 + 1 个非线性交互项驱动,
# 其余 45 个预测子是噪声 (模拟 FRED-MD 式高维数据)
# ================================================================
T, p = 480, 50
X = np.random.randn(T, p) * 0.5
# y 的真实生成: AR(1) + 5 个线性项 + 1 个非线性门限项
y = np.zeros(T)
for t in range(1, T):
lin = 0.7 * X[t, 0] + 0.5 * X[t, 1] - 0.4 * X[t, 2] \
+ 0.3 * X[t, 3] + 0.2 * X[t, 4]
# 非线性: 只有当 X[t,5] > 0 时 X[t,6] 才起作用 (门限效应)
nonlinear = 1.2 * X[t, 6] * (X[t, 5] > 0)
y[t] = 0.8 * y[t-1] + lin + nonlinear + np.random.randn() * 0.5
# ================================================================
# 2. 构造 h=1 步预测的特征矩阵 (用 t 时刻信息预测 t+1)
# 标准做法: y_{t+1} ~ X_t (不能用 y_{t+1} 的同期 X, 那是数据泄露)
# ================================================================
X_feat = X[:-1] # 用 t 时刻的 X
y_target = y[1:] # 预测 y_{t+1}
# ================================================================
# 3. 滚动窗口样本外预测
# 初始训练窗口 240 期, 每次滚动 1 期, 预测下一期
# ================================================================
WARMUP = 240
oos_index = np.arange(WARMUP, len(y_target))
def rolling_predict(model_factory, Xf, yt, warmup):
"""通用滚动窗口预测函数"""
preds = np.zeros(len(yt) - warmup)
for i, t in enumerate(range(warmup, len(yt))):
X_tr, X_te = Xf[:t], Xf[t:t+1]
y_tr = yt[:t]
# 必须在训练窗口内 fit scaler, 不能用全样本均值!
scaler = StandardScaler().fit(X_tr)
X_tr_s = scaler.transform(X_tr)
X_te_s = scaler.transform(X_te)
model = model_factory()
model.fit(X_tr_s, y_tr)
preds[i] = model.predict(X_te_s)[0]
return preds
# --- 模型工厂 (每次滚动重新拟合, 避免未来信息) ---
models = {
"AR(1)基准": lambda: LassoCV(cv=5, alphas=[0.01]), # 简化: 仅截距+滞后项近似
"Ridge": lambda: RidgeCV(alphas=np.logspace(-2, 2, 10)),
"Lasso": lambda: LassoCV(cv=5, n_alphas=50, max_iter=5000),
"ElasticNet": lambda: ElasticNetCV(cv=5, l1_ratio=[0.2,0.5,0.8,0.95],
n_alphas=30, max_iter=5000),
"RandomForest": lambda: RandomForestRegressor(
n_estimators=500, max_depth=8, min_samples_leaf=5,
max_features="sqrt", random_state=42, n_jobs=-1),
}
# --- 为了让 AR(1) 基准真正是 AR(1), 单独构造 (只用 y 的滞后项) ---
y_lag1 = np.roll(y_target, 1); y_lag1[0] = y[0]
X_ar = y_lag1.reshape(-1, 1)
results = {}
for name, factory in models.items():
print(f"Rolling-window OOS: {name} ...")
if name == "AR(1)基准":
results[name] = rolling_predict(factory, X_ar, y_target, WARMUP)
else:
results[name] = rolling_predict(factory, X_feat, y_target, WARMUP)
# ================================================================
# 4. 计算 RMSE / MAE / 相对 RMSE (vs AR(1))
# ================================================================
y_oos = y_target[WARMUP:]
rmse = {k: np.sqrt(np.mean((v - y_oos)**2)) for k, v in results.items()}
mae = {k: np.mean(np.abs(v - y_oos)) for k, v in results.items()}
base_rmse = rmse["AR(1)基准"]
print("\n{:15s} {:>10s} {:>10s} {:>10s}".format("模型", "RMSE", "MAE", "相对RMSE"))
for k in results:
print(f"{k:15s} {rmse[k]:10.4f} {mae[k]:10.4f} {rmse[k]/base_rmse:10.4f}")
# ================================================================
# 5. Diebold-Mariano 检验 (以 AR(1) 为基准)
# H0: 模型 k 与 AR(1) 的预测精度相同
# ================================================================
def dm_test(e_a, e_b, h=1):
"""Diebold-Mariano 检验, 平方损失, h 步预测需 HAC 调整"""
d = e_a**2 - e_b**2 # 损失差
T = len(d)
d_bar = d.mean()
# Newey-West HAC 长期方差 (h 步预测有 h-1 阶自相关)
gamma0 = np.var(d, ddof=1)
lrvar = gamma0
for lag in range(1, h):
gamma_lag = np.cov(d[lag:], d[:-lag], ddof=1)[0,1]
lrvar += 2 * (1 - lag/h) * gamma_lag
dm_stat = d_bar / np.sqrt(lrvar / T)
p_value = 2 * (1 - stats.norm.cdf(abs(dm_stat)))
return dm_stat, p_value
print("\nDiebold-Mariano 检验 (基准 = AR(1), 负 DM 表示模型更优):")
e_ar = results["AR(1)基准"] - y_oos
for k in ["Ridge", "Lasso", "ElasticNet", "RandomForest"]:
e_k = results[k] - y_oos
dm, p = dm_test(e_k, e_ar, h=1)
sig = "***" if p < 0.01 else ("**" if p < 0.05 else ("*" if p < 0.1 else ""))
print(f" {k:15s} DM = {dm:7.3f} p = {p:6.4f} {sig}")
# ================================================================
# 6. 变量重要性 (用最后一个训练窗口的 RF 输出)
# ================================================================
scaler = StandardScaler().fit(X_feat[:WARMUP])
rf_final = RandomForestRegressor(n_estimators=500, max_depth=8,
min_samples_leaf=5, random_state=42)
rf_final.fit(scaler.transform(X_feat[:WARMUP]), y_target[:WARMUP])
imp = pd.Series(rf_final.feature_importances_,
index=[f"x{i}" for i in range(p)]).sort_values(ascending=False)
print("\nRF 变量重要性 Top 10:")
print(imp.head(10).round(4))
# 期望看到 x0..x6 排前几名 (因为 DGP 就是这 7 个变量驱动的)
① 每个滚动窗口内重新 fit StandardScaler——这是新手最常犯的数据泄露错误;② Lasso / ElasticNet 用内置 CV 选 $\lambda$,但 CV 只在当前训练窗口内做,不碰测试集;③ DM 检验用 Newey-West HAC 长期方差,因为 $h=1$ 时 $h-1=0$,退化为普通方差;④ RF 变量重要性应排在 x0–x6 前面,这验证了"数据丰富环境下 ML 能自动发现真信号"。
08 Stata 代码:elasticnet 与 Python 集成
Stata 16 起内置了完整的 Lasso 系列命令(lasso、elasticnet、ridge),支持交叉验证选 $\lambda$、样本外预测。下面这段代码演示用模拟宏观数据做 Elastic Net 滚动窗口预测。
*==============================================================*
* Stata 大数据宏观预测: Elastic Net + 滚动窗口
* 命令: elasticnet (Stata 16+ 内置, 无需 ssc install)
* 参考: Medeiros et al. (2021 JBES)
*==============================================================*
clear all
set more off
set seed 20260911
* --- 1. 构造模拟宏观数据: T=480, p=50 ---
set obs 480
gen t = _n
forvalues j=1/50 {
gen x`j' = rnormal()*0.5
}
gen y = .
replace y = 0 in 1
forvalues t=2/480 {
local lin = 0.7*x1[`t'] + 0.5*x2[`t'] - 0.4*x3[`t'] ///
+ 0.3*x4[`t'] + 0.2*x5[`t']
local nonlin = 1.2*x6[`t']*(x7[`t']>0)
replace y = 0.8*y[`t'-1] + `lin' + `nonlin' + rnormal()*0.5 in `t'
}
* --- 2. Elastic Net 主命令 (在全样本上做 CV, 仅作演示) ---
* 语法:
* elasticnet y x1-x50, l1options(...) cv(5)
* l1options(alpha(0.5)) 表示 Elastic Net; alpha(1)=Lasso, alpha(0)=Ridge
* 可选: selection(cv) 用 5 折 CV 选 lambda
elasticnet y x1-x50, l1options(alpha(0.5)) cv(5) selection(cv)
* 查看 CV 结果: 哪条 lambda 最优
elasticnet, cvplot
* 列出被选中的变量 (系数非零)
elasticnet, lassocoef display(coef)
* --- 3. 滚动窗口样本外预测 ---
* 初始训练: t=1..240, 测试: t=241..480
gen yhat_en = .
forvalues t=241/479 {
quietly {
* 在 [1, t] 上训练
elasticnet y x1-x50 if t <= `t', ///
l1options(alpha(0.5)) cv(5) selection(cv)
* 预测 t+1
predict yhat_temp if t==`t', xb
replace yhat_en = yhat_temp[_n+1] if t==`t'
drop yhat_temp
}
display "Rolling OOS: t = " `t'
}
* --- 4. 计算 RMSE / MAE (样本外) ---
gen err = y - yhat_en if t >= 242 & !missing(yhat_en)
quietly summarize err
gen rmse_en = sqrt(mean(err^2))
gen mae_en = mean(abs(err))
display "Elastic Net OOS RMSE = " rmse_en
display "Elastic Net OOS MAE = " mae_en
* --- 5. 对照: 简单 AR(1) 基准 ---
gen y_lag = L.y
reg y y_lag if t <= 240
predict yhat_ar if t >= 241, xb
gen err_ar = y - yhat_ar if t >= 241 & !missing(yhat_en)
quietly summarize err_ar
gen rmse_ar = sqrt(mean(err_ar^2))
display "AR(1) OOS RMSE = " rmse_ar
display "相对 RMSE (EN/AR) = " rmse_en / rmse_ar
* --- 6. Python 集成: 在 Stata 里直接调 sklearn 做 RF/XGBoost ---
* Stata 16+ 支持 python 命令, 可直接调用 Python 环境
python:
import pandas as pd, numpy as np
from sklearn.ensemble import RandomForestRegressor
from sklearn.preprocessing import StandardScaler
# sfi (Stata Function Interface) 把当前 Stata 数据读进 pandas
from sfi import Data
df = pd.DataFrame({v: Data.get(v) for v in Data.getVarNames()})
X = df[[f"x{i}" for i in range(1,51)]].values
y = df["y"].values
# 滚动窗口 RF
WARMUP = 240
preds = np.full(len(y), np.nan)
for t in range(WARMUP, len(y)-1):
sc = StandardScaler().fit(X[:t])
rf = RandomForestRegressor(n_estimators=300, max_depth=8,
random_state=42, n_jobs=-1)
rf.fit(sc.transform(X[:t]), y[:t])
preds[t+1] = rf.predict(sc.transform(X[t:t+1]))[0]
# 把结果写回 Stata
Data.addVarDouble("yhat_rf")
Data.store("yhat_rf", None, preds.tolist())
end
* 计算 RF 相对 RMSE
gen err_rf = y - yhat_rf if t >= 242 & !missing(yhat_rf)
quietly summarize err_rf
display "Random Forest OOS RMSE = " sqrt(mean(err_rf^2))
09 常见错误与论文清单
常见错误
在全部数据上 fit Lasso/RF,然后报告样本内 $R^2=0.95$。这毫无意义——机器学习模型的全部价值在样本外。论文里必须报告滚动窗口 OOS RMSE。
很多初学者在样本内做 CV 选完 $\lambda$,再在全样本上估计,最后在全样本上算 RMSE——这等于把测试集也拿来训练。必须严格把样本切成训练-验证-测试三块。
最隐蔽的错误:在全样本上 fit StandardScaler / PCA / 缺失值填补,再切训练-测试。这把测试集的均值/方差泄露进训练。正确做法:每次滚动窗口内重新 fit 预处理。同理,预测 $y_{t+1}$ 时只能用 $X_t$,不能用同期 $X_{t+1}$。
宏观变量(GDP、价格水平)是非平稳的。直接对水平值做 Lasso 会得到伪回归。必须先对数据做变换:取对数差分(通胀率 = 对数 CPI 的一阶差分 ×100)、季节调整、或用一阶差分。FRED-MD 数据库已经做好了标准变换,直接用。
树模型的 max_depth / learning_rate 如果用全样本 CV 选,再报告 OOS 结果,OOS 就不再是"真正的样本外"。正确:超参数只在训练-验证子样本上调,测试集一次性使用。
论文清单
进阶资料
- FRED-MD 数据库(McCracken & Ng 2016, JBES):128 个美国月度宏观变量的实时数据库,可直接下载 CSV,是复现 Medeiros et al. (2021) 的起点。
- Stock-Watson (2007, JEM) "Out-of-Sample Forecast Comparations" 综述:讲清楚什么才算公平的样本外预测比较。
- Bai & Ng (2008, Econometrica):因子模型与大数据预测的理论基础。
- 中文大数据宏观预测综述:郑挺国等(2025)正文里的文献综述部分,对国内 Wind/Choice 数据清洗有详细说明。