4. 线性回归模型:OLS回归、正则化(Lasso/Ridge)、逐步回归、因子共线性处理
线性回归,说白了就是量化选股里最基础的武器。你想想看,我们做因子组合,本质上就是在找一堆因子和未来收益之间的线性关系。今天我就把这套东西掰开揉碎了讲清楚。
4.1 OLS回归:最朴素的解法
OLS,全称是普通最小二乘法。它的目标很简单:找到一条线,让所有样本点到这条线的垂直距离的平方和最小。
数学上,我们假设:
y = Xβ + ε
其中 y 是收益,X 是因子矩阵,β 是我们要估计的系数,ε 是误差项。
OLS的解是:
β̂ = (XᵀX)⁻¹Xᵀy
这个公式看着简单,但有个致命问题——它要求 XᵀX 是可逆的。我在项目中遇到过,当因子数量接近样本数量时,这个矩阵就变得病态了。
核心假设:OLS要求因子之间不能高度相关,否则系数估计会变得极不稳定。
4.2 因子共线性:量化里的隐形杀手
为什么会这样?我举个例子。假设你有两个因子:市盈率和市净率。它们都反映估值,相关性高达0.9。你用OLS去跑,会发现系数的标准误变得巨大,今天跑出来系数是正,明天就变成负了。
判断共线性的常用指标:
- VIF(方差膨胀因子):VIF > 10 就说明共线性严重
- 条件数:条件数 > 30 需要警惕
我曾经在做一个多因子模型时,VIF飙到了50多。嗯,当时没注意,结果回测曲线漂亮得很,实盘直接崩了。从那以后,我每次建模前必做VIF检查。
4.3 正则化:给系数戴上枷锁
既然OLS容易过拟合,那我们就给损失函数加点惩罚。这就是正则化的思路。
4.3.1 Ridge回归(L2正则化)
Ridge在损失函数里加上了系数平方和:
Loss = MSE + λ∑βᵢ²
λ是超参数,控制惩罚力度。λ越大,系数越趋向于0,但不会变成0。
Ridge的好处是:即使因子高度相关,它也能给出稳定的解。我习惯用交叉验证来选λ,一般从0.01到100之间搜索。
个人经验:Ridge特别适合因子数量多但每个因子都有一定预测能力的情况。它不会把因子完全剔除,而是让它们"缩水"。
4.3.2 Lasso回归(L1正则化)
Lasso用的是系数绝对值之和:
Loss = MSE + λ∑|βᵢ|
Lasso有个神奇的特性:它能把不重要的因子系数直接压缩到0。说白了,它帮你做特征选择。
你想想看,如果你有100个因子,但真正有用的可能只有10个。Lasso就能帮你把这10个挑出来。
注意:Lasso在因子高度相关时表现不稳定。它会随机选择其中一个,把另一个压缩到0。这时候我建议用Elastic Net——它结合了L1和L2。
4.4 逐步回归:手工筛选的艺术
在正则化流行之前,大家做因子筛选主要靠逐步回归。它有三种方式:
- 向前选择:从空模型开始,每次加入最显著的因子
- 向后剔除:从全模型开始,每次剔除最不显著的因子
- 双向逐步:结合两者,边加边剔
我刚开始做量化时,特别喜欢用逐步回归。但后来发现它有个坑:
逐步回归基于p值做决策,但p值本身就有问题。样本量一大,p值就变得极其敏感。我曾经用1000只股票做逐步回归,结果选出了30个因子,但换一个时间段,选出来的因子完全不同。
我的建议:逐步回归可以作为探索性工具,但不要完全依赖它做最终决策。现在更推荐用Lasso或随机森林做特征选择。
4.5 实战:用Python实现
下面是我常用的代码模板:
import numpy as np
import pandas as pd
from sklearn.linear_model import LinearRegression, Ridge, Lasso
from sklearn.model_selection import cross_val_score
from statsmodels.stats.outliers_influence import variance_inflation_factor
# 计算VIF
def calc_vif(X):
vif = pd.DataFrame()
vif["features"] = X.columns
vif["VIF"] = [variance_inflation_factor(X.values, i)
for i in range(X.shape[1])]
return vif
# Ridge回归调参
alphas = np.logspace(-3, 2, 50)
cv_scores = []
for alpha in alphas:
ridge = Ridge(alpha=alpha)
scores = cross_val_score(ridge, X_train, y_train,
cv=5, scoring='neg_mean_squared_error')
cv_scores.append(scores.mean())
best_alpha = alphas[np.argmax(cv_scores)]
print(f"最优alpha: {best_alpha}")
4.6 知识体系总览
下面这张图总结了本章的核心逻辑:
4.7 避坑指南
最后,我总结几个实战中容易踩的坑:
- 不要盲目相信R²:R²高不代表模型好,可能是过拟合。我见过有人把R²做到0.99,结果实盘亏得底朝天。
- 注意时间序列的依赖性:金融数据有自相关性,直接用OLS会低估标准误。我习惯用Newey-West调整标准误。
- 正则化参数要谨慎:λ选太大,模型欠拟合;选太小,过拟合。用时间序列交叉验证比随机交叉验证更靠谱。
- 因子标准化很重要:Lasso和Ridge对尺度敏感,记得先做标准化处理。
我的习惯:每次建模前,先跑VIF,剔除VIF > 10的因子。然后用Ridge做初步筛选,最后用Lasso做特征选择。这套流程在实盘中表现稳定。
嗯,线性回归看似简单,但用好了威力无穷。记住,模型只是工具,真正重要的是你对数据的理解和对风险的敬畏。
公众号:蓝海资料掘金营,微信deep3321