【维克】回归诊断:如何知道你的模型是不是“坏了“?

年化20%量化笔记 · 第36篇

上一篇我们聊了时间序列分析中回归模型的建立,但建完模型只是第一步------更关键的是:你怎么知道这个模型到底靠不靠谱?今天我们就来聊聊回归诊断这件事。

WHY:为什么回归诊断这么重要?

一个血泪教训

去年我花了两周时间搭了一个多因子选股模型,回测结果漂亮得不像话------年化收益42%,夏普比率2.1。当时激动得差点直接上实盘。

但后来做了一轮完整的回归诊断,才发现这个模型"坏了":残差存在严重的自相关性,意味着模型没有真正提取到所有的信息,那些所谓的"超额收益"不过是模型对某些系统性风险的误判。

修正之后,年化收益降到了18%,但这才是一个真实可信的数字。

模型"坏了"是什么意思?

在统计学里,一个线性回归模型要满足四个基本假设(也叫Gauss-Markov条件):

  1. 线性关系 :因变量与自变量之间存在线性关系

  2. 残差独立性 :残差之间相互独立,不存在自相关

  3. 残差同方差性 :残差的方差是恒定的(同方差)

  4. 残差正态性 :残差服从正态分布

当这些假设被违反时,模型的估计结果就不再是最优的,甚至可能是完全错误的。这就像你用一把刻度不准的尺子去量东西------量出来的数字再精确,也没有意义。

在量化交易中尤其关键

对于量化交易而言,回归模型的"坏"可能带来更严重的后果:

虚假信号 :如果残差存在自相关,你可能会把噪声当成信号,产生大量虚假的交易信号

风险低估 :如果存在异方差(heteroscedasticity),你的风险估计会严重失真------在市场波动大的时候低估风险,在市场平静的时候过度保守

错误定价 :如果模型没有捕捉到非线性关系,你的因子暴露计算就是错的

所以,回归诊断不是"锦上添花",而是"生死攸关"。

HOW:回归诊断的具体方法

一、残差分析------最基础也最重要

残差(residual)是模型预测值与实际值之间的差距。如果模型是好的,残差应该像"白噪声"一样------随机的、无规律的、均值为零的。

1.1 残差图(Residual Plot)

最直观的 diagnose 方法就是画残差图:

// python
import matplotlib.pyplot as plt
import numpy as np

假设 y_pred 是模型预测值,residuals 是残差

fig, axes = plt.subplots(1, 3, figsize=(15, 4))

残差 vs 预测值

axes0.scatter(y_pred, residuals, alpha=0.5)
axes0.axhline(y=0, color='r', linestyle='--')
axes0.set_title('Residuals vs Fitted')
axes0.set_xlabel('Fitted Values')
axes0.set_ylabel('Residuals')

残差的QQ图

from scipy import stats
stats.probplot(residuals, dist="norm", plot=axes1)
axes1.set_title('Normal Q-Q Plot')

残差的直方图

axes2.hist(residuals, bins=30, edgecolor='black', alpha=0.7)
axes2.set_title('Residual Distribution')
plt.tight_layout()
plt.show()

怎么看这些图?

残差 vs 预测值图 :如果残差均匀散布在零线两侧,说明模型假设基本满足。如果出现了"漏斗形"(一端散一端聚),说明存在异方差问题;如果出现了曲线趋势,说明可能遗漏了非线性关系。

QQ图 :如果残差完美服从正态分布,所有点应该在一条直线上。如果尾部偏离,说明残差存在厚尾(fat tails)------这在金融数据中非常常见。

直方图 :直观看残差的分布形状,是否接近正态。

1.2 残差的自相关检测

对于时间序列数据,残差的自相关性是一个特别常见的问题。

Durbin-Watson 检验

// python
from statsmodels.stats.stattools import durbin_watson
dw_stat = durbin_watson(residuals)
print(f"Durbin-Watson统计量: {dw_stat:.4f}")

DW值接近2 → 无自相关

DW值显著小于2 → 正自相关

DW值显著大于2 → 负自相关

Ljung-Box 检验

// python
from statsmodels.stats.diagnostic import acorr_ljungbox
lb_test = acorr_ljungbox(residuals, lags=10, 15, 20, return_df=True)
print(lb_test)

p值小于0.05 → 拒绝"无自相关"的原假设

1.3 异方差检测

Breusch-Pagan 检验

// python
from statsmodels.stats.diagnostic import het_breuschpagan
bp_test = het_breuschpagan(residuals, X)

bp_test 返回: (LM统计量, LM的p值, F统计量, F的p值)

print(f"BP检验p值: {bp_test1:.4f}")

p值小于0.05 → 存在异方差

White 检验

// python
from statsmodels.stats.diagnostic import het_white
white_test = het_white(residuals, X)
print(f"White检验p值: {white_test1:.4f}")

二、异常值检测------别让极端值毁了你的模型

2.1 标准化残差法

最简单的方法:标准化残差超过±3的观测点,可以视为异常值。

// python
from statsmodels.stats.outliers_influence import OLSInfluence
influence = OLSInfluence(model_results)
standardized_residuals = influence.resid_studentized_internal

找出异常值

outliers = np.abs(standardized_residuals) > 3
print(f"发现 {outliers.sum()} 个异常值")

2.2 Cook's Distance------衡量影响力的综合指标

有些数据点不仅是"异常"的,它们还对模型参数产生了巨大的影响力。Cook's Distance 就是衡量这种影响力的指标。

// python
cooks_d = influence.cooks_distance0

常用阈值: 4/n 或 0.5

threshold = 4 / len(X)
influential_points = cooks_d > threshold
plt.figure(figsize=(10, 4))
plt.stem(range(len(cooks_d)), cooks_d, markerfmt=',', use_line_collection=True)
plt.axhline(y=threshold, color='r', linestyle='--', label=f'Threshold (4/n={threshold:.4f})')
plt.xlabel('Observation Index')
plt.ylabel("Cook's Distance")
plt.title("Cook's Distance Plot")
plt.legend()
plt.show()

在量化交易中,这些"高影响力"的观测点往往对应着极端市场行情------比如2020年3月的熔断、2015年的A股暴跌。你需要决定:这些是应该被模型学习的极端事件,还是应该被排除的异常?

2.3 杠杆值(Leverage)

杠杆值衡量的是一个数据点在自变量空间中的"偏离程度"。高杠杆点不一定是异常值,但它有潜力对模型产生很大影响。

// python
leverage = influence.hat_matrix_diag
high_leverage = leverage > 2 * X.shape1 / len(X)
print(f"高杠杆点数量: {high_leverage.sum()}")

三、多重共线性检测------因子之间的"内耗"

在量化多因子模型中,因子之间的高度相关性是一个常见问题。如果两个因子高度相关,模型就无法区分它们各自的贡献。

3.1 VIF(方差膨胀因子)

// python
from statsmodels.stats.outliers_influence import variance_inflation_factor
vif_data = pd.DataFrame()
vif_data"feature" = X.columns
vif_data"VIF" = variance_inflation_factor(X.values, i) for i in range(X.shape\[1)]
print(vif_data.sort_values("VIF", ascending=False))

VIF > 10 → 严重多重共线性

VIF > 5 → 需要关注

3.2 相关矩阵热力图

// python
import seaborn as sns
corr_matrix = X.corr()
plt.figure(figsize=(10, 8))
sns.heatmap(corr_matrix, annot=True, cmap='RdBu_r', center=0,
vmin=-1, vmax=1, square=True)
plt.title('Feature Correlation Matrix')
plt.show()

四、模型整体诊断

4.1 RESET检验------检查模型设定是否正确

Ramsey's RESET检验可以检测模型是否遗漏了重要的非线性项。

// python
from statsmodels.stats.diagnostic import linear_reset
reset_test = linear_reset(model_results, power=2, test_type='fitted')
print(f"RESET检验p值: {reset_test.pvalue:.4f}")

p值小于0.05 → 模型可能遗漏了重要的非线性关系

4.2 交叉验证------模型的"实战检验"

// python
from sklearn.model_selection import cross_val_score
from sklearn.linear_model import LinearRegression
model = LinearRegression()
cv_scores = cross_val_score(model, X, y, cv=5, scoring='r2')
print(f"交叉验证R²: {cv_scores.mean():.4f} ± {cv_scores.std():.4f}")

如果不同折的R²差异很大,说明模型不稳定

五、一套完整的诊断流程

在我的日常工作中,我会按照以下顺序对每个回归模型进行诊断:

Step 1: 看R²和调整R² → 模型整体解释力

Step 2: 画残差图 → 线性假设是否成立

Step 3: QQ图 + Shapiro-Wilk检验 → 残差正态性

Step 4: Durbin-Watson / Ljung-Box → 残差自相关性

Step 5: Breusch-Pagan / White → 异方差性

Step 6: VIF → 多重共线性

Step 7: Cook's Distance → 异常值和强影响点

Step 8: RESET检验 → 模型设定

Step 9: 交叉验证 → 模型稳定性

每一步如果发现问题,就需要相应地修正模型:

• 非线性 → 加入多项式项或使用非线性模型

• 自相关 → 使用广义最小二乘法(GLS)或加入滞后项

• 异方差 → 使用稳健标准误(Robust Standard Errors)

• 多重共线性 → 去除冗余因子或使用PCA降维

• 异常值 → 稳健回归(Robust Regression)或 Winsorize 处理

WHAT:诊断之后你能得到什么?

一张回归诊断"体检报告"

完成上述诊断后,你应该能输出一份清晰的模型健康报告:

|----------|--------------------|------------------|----------|--------|
| 诊断项目 | 检验方法 | 判定标准 | 本例结果 | 状态 |
| 整体拟合 | R² / Adj R² | R² > 0.3 | 0.45 | ✅ |
| 线性假设 | 残差图 | 无明显模式 | 通过 | ✅ |
| 残差正态性 | QQ图 / Shapiro-Wilk | p > 0.05 | p=0.03 | ⚠️ 厚尾 |
| 残差独立性 | Durbin-Watson | 1.5 < DW < 2.5 | 1.82 | ✅ |
| 同方差性 | Breusch-Pagan | p > 0.05 | p=0.01 | ❌ 异方差 |
| 多重共线性 | VIF | VIF < 5 | max=3.2 | ✅ |
| 异常值 | Cook's D | 无超高影响点 | 2个 | ⚠️ 需检查 |
| 模型设定 | RESET | p > 0.05 | p=0.12 | ✅ |
| 稳定性 | CV R²标准差 | std < 0.1 | 0.04 | ✅ |

根据诊断结果,形成明确的行动方案:

  1. 如果残差正态性不满足(厚尾)

◦ 使用 t 分布替代正态分布进行假设检验

◦ 或采用稳健标准误

◦ 量化影响:标准误可能低估 15-30%

  1. 如果存在异方差

◦ 使用 HC(Heteroscedasticity-Consistent)标准误

◦ 或使用 WLS(加权最小二乘法)

◦ 量化影响:t检验的显著性判断可能出错,导致保留无效因子或丢弃有效因子

  1. 如果发现高影响力异常点

◦ 对比剔除前后的模型参数变化

◦ 如果变化超过20%,需要谨慎决策

◦ 考虑使用 Huber 回归等稳健方法

  1. 如果多重共线性严重

◦ 逐步去除VIF最高的变量

◦ 或使用 Ridge 回归

◦ 量化影响:共线性存在时,单个因子的系数估计可能完全不可信

一个真实的量化案例

我曾经的一个多因子模型,诊断前后的对比:

|--------|--------------|-------------|
| 指标 | 诊断前(未修正) | 诊断后(修正) |
| 年化收益率 | 35.2% | 19.8% |
| 夏普比率 | 1.85 | 1.12 |
| 最大回撤 | -28.5% | -15.3% |
| 因子数量 | 12 | 8 |
| R² | 0.68 | 0.42 |
| 样本外表现 | 大幅衰减 | 稳定 |

看起来"退步"了?其实这才是真实的表现。诊断前那个漂亮的35%年化,不过是模型在"自欺欺人"------它把噪声当成了信号,把过拟合当成了alpha。

修正后虽然收益降了,但风险更可控,样本外表现更稳定。这才是能真正帮你赚钱的模型。

可量化的收益

坚持做回归诊断,根据我的经验:

减少虚假信号 :避免30-50%的虚假因子进入模型

提升样本外稳定性 :样本内外收益差距从50%+缩小到20%以内

降低极端损失 :通过识别和修正异常值,最大回撤通常能改善20-30%

提高决策信心 :你知道模型的每一个假设都经过了验证,每一笔交易都有统计依据

最后

回归诊断就像是给模型做一次"体检"。你不会开一辆从来没做过检测的车上高速,那为什么要把没有经过诊断的模型放到真金白银的市场里?

记住:一个好的量化模型,不是回测最漂亮的那个,而是经过严格诊断后依然稳健的那个。

下一篇我们将进入正则化的世界,聊聊如何通过Lasso和Ridge来防止模型过拟合------这其实也是回归诊断发现问题后的一个重要解决手段。敬请期待!

下一篇:【维克】正则化回归:从Lasso到Ridge------防止过拟合的数学魔法

相关推荐
数字化老赵1 小时前
有没有什么好用的AI工具可以用在设备巡检管理的?
大数据·人工智能·设备管理·设备
cxr8281 小时前
第6章 强制执行令牌
人工智能·智能体
vibecoding771 小时前
GitHub 2026 年 7 月热榜:累计 Star 总榜与月度飙星榜
人工智能·github·ai编程
anscos1 小时前
面向人工智能安全和 ISO/PAS 8800 的自动化 C/C++ 测试路线图
人工智能·parasoft
<-->1 小时前
Megatron-LM 深度学习与源码分析文档
人工智能·深度学习
aqi001 小时前
15天学会AI应用开发(十八)使用LangGraph实现精确记忆功能
人工智能·python·大模型·ai编程·ai应用
Michaelliu_dev1 小时前
RoPE通俗讲解
人工智能·llm·位置编码·多模态大模型·rope·旋转位置编码·mllm
呆呆敲代码的小Y2 小时前
5 分钟上手 OpenMontage:把 AI 编程助手变成视频工作室
人工智能·aigc·音视频·ai视频生成·claude code·openmontage
ShallWeL2 小时前
【机器学习】(30)—— 嵌入空间
人工智能·神经网络·机器学习·embedding