第7章:模型选择与正则化
寻找简单与准确的完美平衡
简单与准确的平衡
一个概念图,展示了从欠拟合(过于简单)到过拟合(过于复杂)的过渡,中间是理想的平衡点。
简单 (欠拟合)
复杂 (过拟合)
平衡
故事开篇:一个价值十亿美元的错误
主角 : Zillow, 美国最大的房地产线上平台。
宏伟计划 : 利用复杂的机器学习模型 (Zestimate) 预测房价,并直接下场“炒房”。
模型特点 : 拥有海量数据和数百个变量,在历史数据上表现“极其精准”。
悲惨结局 : 2021年,Zillow宣布其模型在真实市场中严重预测失准,导致巨额亏损,最终裁员25%,关闭了该业务。
核心警示: 一个在“过去”看起来完美的复杂模型,可能是预测“未来”的灾难。
核心问题:为何复杂的金融模型常常预测失败?
我们经常看到拥有数百个变量的复杂计量经济学模型。
直觉上 :包含的变量越多,信息就越丰富,模型应该越“准确”。
实际上 :过于复杂的模型在样本外(out-of-sample)的预测中,表现往往非常糟糕,甚至不如简单模型。
本章的核心,就是解决这个理论与现实之间的矛盾。
早上好,同学们。刚刚我们看到了Zillow的故事,这并非个例。在金融和经济建模中,我们都有一种倾向,认为模型越复杂、考虑的因素越多,就应该越好。但现实反复告诉我们,事情并非如此。一个在历史数据上看起来完美无缺的模型,拿到真实世界中去预测未来,结果可能是一塌糊涂。今天,我们就要揭开这个谜题的盖子,理解“过犹不及”在建模中的深刻含义。
本讲座的学习目标
理论层面
深刻理解 :为什么我们需要在模型复杂度和预测能力之间做出权衡(偏差-方差的权衡)。
掌握模型选择的方法 :学会如何从众多候选模型中,科学地挑选出“最优”模型。
技术层面
掌握正则化的核心思想与方法 :学会使用岭回归、套索回归等技术,来“惩罚”不必要的模型复杂度。
具备实践能力 :能够使用Python,对真实的金融数据应用正则化方法,并解释其结果。
根本矛盾:偏差-方差的权衡
一个预测模型的总均方误差 (Total MSE) 可以被分解为三个部分:
\[
\large{
\text{E}\left[(y_0 - \hat{f}(x_0))^2\right] = \text{Bias}[\hat{f}(x_0)]^2 + \text{Var}[\hat{f}(x_0)] + \sigma^2
}
\]
偏差平方 (Bias²) : 模型预测的平均值与真实值之间的差距。
方差 (Variance) : 模型在不同数据集上预测结果的变动性。
不可约误差 (Irreducible Error, \(\sigma^2\) ) : 数据本身的噪声,任何模型都无法消除。
要理解模型为什么会失败,我们必须从它的误差来源开始分析。任何一个模型的预测误差,都可以分解成这三个部分。偏差,可以理解为模型的“偏见”,它系统性地偏离了真相。方差,可以理解为模型的“敏感度”,数据稍有风吹-草动,它的预测结果就上蹿下跳。我们的目标,就是找到一个模型,既没有太深的偏见,又不会过于敏感。
理解偏差 (Bias)
偏差衡量的是模型的“固执”程度,即其内在假设与数据真实规律的偏离程度。
高偏差 : 意味着模型过于简单,无法捕捉数据的复杂模式。
表现 : 欠拟合 (Underfitting) 。模型在训练集和测试集上表现都很差。
例子 : 用一条直线去拟合非线性的数据。
理解方差 (Variance)
方差衡量的是模型的“敏感”程度,即模型对训练数据微小变化的反应有多剧烈。
高方差 : 意味着模型过于复杂,把训练数据中的噪声也当成了规律来学习。
表现 : 过拟合 (Overfitting) 。模型在训练集上表现极好,但在测试集上表现很差。
例子 : 用一个高阶多项式去拟合每一个数据点。
可视化权衡:一个形象的比喻
想象用模型来“打靶”。
偏差-方差的权衡:打靶比喻
四个靶子,分别展示了低偏差低方差,低偏差高方差,高偏差低方差,以及高偏差高方差的情况。
低偏差, 低方差
(理想状态)
低偏差, 高方差
(过拟合)
高偏差, 低方差
(欠拟合)
高偏差, 高方差
(最差状态)
图解:偏差-方差的权衡曲线
随着模型复杂度的增加:
偏差 会持续下降(模型能更好地拟合训练数据)。
方差 会持续上升(模型开始学习训练数据中的噪声)。
总误差 会先下降,后上升,形成一个U型曲线。
我们的目标是找到总误差最低 的那个点。
偏差-方差的权衡曲线
该图表展示了随着模型复杂度的增加,偏差、方差和总误差如何变化。
模型复杂度
偏差-方差的权衡
偏差平方 (Bias²)
方差 (Variance)
总预测误差
欠拟合区域
过拟合区域
过拟合:模型“记住”了噪声而非规律
过拟合 (Overfitting) 是指模型对训练数据拟合得过于完美,以至于把数据中的随机噪声也当作了真实的规律来学习。
后果 :该模型在训练集上表现极好(例如,\(R^2\) 接近1),但在新的、未见过的数据(测试集)上表现非常差。
原因 :模型过于复杂,自由度太高。
可视化过拟合:一个直观的例子
我们将用多项式回归来拟合带有噪声的正弦波数据。
真实规律 : \(y = \sin(x)\)
观测数据 : \(y = \sin(x) + \epsilon\) (其中 \(\epsilon\) 是随机噪声)
我们将看到,过于高阶(复杂)的多项式模型会发生什么。
第1步:生成我们的“玩具”数据集
首先,我们生成一些模拟数据作为我们的“真实世界”。
Code
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.style as style
np.random.seed(42 )
X_sample = np.linspace(0 , 10 , 20 )
y_sample = np.sin(X_sample) + np.random.normal(0 , 0.3 , len (X_sample))
X_true = np.linspace(0 , 10 , 100 )
y_true = np.sin(X_true)
fig, ax = plt.subplots(figsize= (9 , 6 ))
ax.scatter(X_sample, y_sample, label= '观测数据 (带噪声)' , color= 'black' , zorder= 5 )
ax.plot(X_true, y_true, label= '真实规律 (无噪声)' , color= 'crimson' , lw= 2.5 )
ax.set_title('我们的“玩具”数据集' , fontsize= 16 )
ax.set_xlabel('X' , fontsize= 12 )
ax.set_ylabel('y' , fontsize= 12 )
ax.legend(fontsize= 11 )
plt.show()
第2步:简单模型(1阶)的欠拟合
一个1阶多项式(直线)模型过于简单,无法捕捉数据的非线性趋势。这是高偏差 的表现。
Code
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression
from sklearn.pipeline import make_pipeline
def plot_poly_fit(degree, X_sample, y_sample, X_true, y_true, title_suffix):
model = make_pipeline(PolynomialFeatures(degree), LinearRegression())
model.fit(X_sample[:, np.newaxis], y_sample)
y_pred = model.predict(X_true[:, np.newaxis])
fig, ax = plt.subplots(figsize= (9 , 6 ))
ax.scatter(X_sample, y_sample, label= '观测数据' , color= 'black' , zorder= 5 )
ax.plot(X_true, y_true, label= '真实规律' , color= 'crimson' , lw= 2.5 , alpha= 0.4 )
ax.plot(X_true, y_pred, label= f' { degree} 阶多项式拟合' , color= 'cornflowerblue' , lw= 3 )
ax.set_title(f'模型复杂度 (阶数 = { degree} ): { title_suffix} ' , fontsize= 16 )
ax.set_xlabel('X' , fontsize= 12 )
ax.set_ylabel('y' , fontsize= 12 )
ax.set_ylim(- 2 , 2 )
ax.legend(fontsize= 11 )
plt.show()
plot_poly_fit(1 , X_sample, y_sample, X_true, y_true, "欠拟合 (高偏差)" )
第3步:“恰到好处”的模型(3阶)
一个3阶多项式模型较好地捕捉了数据的真实规律。这是偏差和方差较好平衡 的表现。
Code
plot_poly_fit(3 , X_sample, y_sample, X_true, y_true, "良好拟合" )
第4步:复杂模型(15阶)的过拟合
一个15阶多项式模型为了穿过每一个数据点而剧烈扭曲。它学习了噪声。这是高方差 的表现。
Code
plot_poly_fit(15 , X_sample, y_sample, X_true, y_true, "过拟合 (高方差)" )
我们的武器:模型选择与正则化
为了避免过拟合,找到最佳的模型复杂度,我们有两种主要策略:
模型选择 (Model Selection) :
也称为特征选择 (Feature Selection) 。
从一个大的特征集合中,挑选出一个子集来构建模型。
是一种“硬”选择,要么保留特征,要么丢弃。
正则化 (Regularization) :
使用所有特征,但在模型训练时对系数的大小施加“惩罚”。
是一种“软”选择,通过压缩系数来降低模型的有效复杂度。
第一部分:模型选择方法
核心思想 :我们有很多备选的预测变量(特征),如何挑选出一个“最优”的组合?
例子 :在预测一只股票的收益时,我们可能有上百个备选因子(公司规模、估值、动量、宏观经济指标等)。把所有因子都放进模型可能导致过拟合。
我们将介绍两种主流的自动化选择方法。
模型选择方法 1:最佳子集选择法
最佳子集选择法 (Best Subset Selection) 是一种暴力但彻底的方法。
算法步骤 :
设 \(M^{(0)}\) 为不含任何特征的“零模型”(只有截距项)。
对于 \(k = 1, 2, \dots, p\) (其中 \(p\) 是总特征数):
拟合所有包含 \(k\) 个特征的组合模型。总共有 \(\binom{p}{k}\) 个。
在这 \(\binom{p}{k}\) 个模型中,根据某种准则(如RSS最低)选出最佳模型,记为 \(M^{(k)}\) 。
我们现在有 \(p+1\) 个候选模型:\(M^{(0)}, M^{(1)}, \dots, M^{(p)}\) 。
使用交叉验证 (Cross-Validation) 或信息准则 (AIC, BIC) 在这 \(p+1\) 个模型中选出最终的胜者。
最佳子集法的致命缺陷:计算成本
最佳子集法虽然理论上能找到每个子集大小下的最优模型,但它有一个致命问题:计算成本极高 。
如果一个模型有 \(p\) 个备选特征,那么我们需要评估 \(2^p\) 个不同的模型。
当 \(p=10\) 时,是 \(2^{10} = 1,024\) 个模型,尚可接受。
当 \(p=20\) 时,是 \(2^{20} \approx 100\) 万个模型。
当 \(p=40\) 时,是 \(2^{40} \approx 1\) 万亿个模型,计算上已不可行。
在有成百上千备选变量的金融问题中,此方法不适用。
模型选择方法 2:前向分步算法
前向分步算法 (Forward Stepwise Selection) 是一种更高效的“贪心”算法。
算法步骤 :
设 \(M^{(0)}\) 为不含任何特征的“零模型”。
对于 \(k = 1, 2, \dots, p\) :
以 \(M^{(k-1)}\) 为基础,尝试加入一个尚未被包含 的特征。
在所有可能的 \((p-k+1)\) 个新模型中,选择那个提升最大 (如RSS下降最多)的模型,记为 \(M^{(k)}\) 。
我们得到一个由 \(p+1\) 个模型组成的序列:\(M^{(0)}, M^{(1)}, \dots, M^{(p)}\) 。
使用交叉验证或信息准则在序列中选出最终模型。
前向分步算法的优缺点
优点 : 计算效率极高。它只需要评估 \(1 + \sum_{k=1}^{p}(p-k+1) = 1 + \frac{p(p+1)}{2}\) 个模型,远小于 \(2^p\) 。
缺点 : 它是一种“贪心”算法,不能保证找到全局最优解。因为它每一步做出的都是局部最优选择,一旦一个变量被选入模型,就再也不会被移除。
“贪心”在这里是什么意思呢?想象一下你在下棋。贪心算法意味着你每一步都走当前看起来最有利的一步,而不考虑这步棋对未来三步、五步的影响。这种策略有时候能赢,但很可能会错过一个需要先“牺牲”再“将军”的绝佳策略。前向选择就是这样,它可能会因为早期加入了一个“看起来不错”的变量,而错过了后期一个更优的变量组合。
补充:后向分步算法
与前向分步相反,后向分步算法 (Backward Stepwise Selection) 从包含所有特征的完整模型开始,逐步剔除最不重要的特征。
算法步骤 :
设 \(M^{(p)}\) 为包含所有 \(p\) 个特征的完整模型。
对于 \(k = p, p-1, \dots, 1\) :
以 \(M^{(k)}\) 为基础,尝试移除一个 特征。
在所有可能的 \(k\) 个新模型中,选择那个性能下降最小 (如RSS增加最少)的模型,记为 \(M^{(k-1)}\) 。
同样,我们得到一个模型序列,再从中选择最优。
如何在候选模型中做出最终选择?
前向/后向分步算法都为我们提供了一个模型序列 (\(M^{(0)}, \dots, M^{(p)}\) )。但哪个才是最好的?
我们不能简单地使用训练集的RSS或\(R^2\) ,因为它们总是偏爱更复杂的模型。
正确的方法 :使用评估样本外预测能力 的指标。
交叉验证 (Cross-Validation) :最常用、最可靠的方法。
赤池信息准则 (AIC)
贝叶斯信息准则 (BIC)
调整后\(R^2\) (Adjusted \(R^2\) )
这些指标都在模型拟合优度上增加了一个对复杂度的“惩罚项”。
第二部分:正则化方法
核心思想 :我们不显式地剔除任何变量,而是通过一个“惩罚项”来压缩 (shrink) 不重要变量的系数,使其趋向于0。
这是一种更平滑、更连续的模型复杂度控制方法。
它通过修改模型的代价函数 (Cost Function) 来实现。
我们将学习三种最主流的正则化方法:
岭回归 (Ridge Regression)
套索回归 (Lasso Regression)
弹性网络 (Elastic Net)
岭回归 (Ridge Regression): L2 正则化
岭回归在线性回归的代价函数上,增加了一个L2惩罚项 。
普通线性回归 (OLS) 的代价函数 : \[ \large{J_{OLS}(\beta) = \sum_{i=1}^{n} (y_i - \beta_0 - \sum_{j=1}^{p} \beta_j x_{ij})^2} \]
岭回归的代价函数 : \[ \large{J_{Ridge}(\beta) = \underbrace{\sum_{i=1}^{n} (y_i - \beta_0 - \sum_{j=1}^{p} \beta_j x_{ij})^2}_{\text{残差平方和 (RSS)}} + \underbrace{\lambda \sum_{j=1}^{p} \beta_j^2}_{\text{L2正则化项 (Penalty)}}} \]
大家看这个公式。前半部分和我们熟悉的普通最小二乘法一模一样,就是让模型的预测值和真实值的平方误差尽可能小。关键在于后半部分,这个λ乘以所有系数的平方和。这个新加的部分就是“惩罚”。它告诉优化算法:你想让某个系数β_j变大可以,但这会增加总的代价。所以,你必须得有充分的理由——也就是这个β_j能显著降低前半部分的拟合误差——我才允许你变大。
理解岭回归的惩罚项
\[ \large{\lambda \sum_{j=1}^{p} \beta_j^2} \]
\(\beta_j\) : 模型的第 \(j\) 个特征的系数。
\(\sum \beta_j^2\) : 所有系数的平方和 。这被称为L2范数 (L2-norm) 的平方。
\(\lambda\) (lambda) : 正则化参数 或惩罚强度 。这是一个超参数,需要我们手动设定。
当 \(\lambda = 0\) 时,岭回归等价于普通线性回归。
当 \(\lambda \to \infty\) 时,所有系数 \(\beta_j\) 都将被压缩至趋近于0。
\(\lambda\) 的作用:控制模型的复杂度
\(\lambda\) 扮演着一个“纪律委员”的角色,控制着系数的大小。
小的 \(\lambda\) : 惩罚力度小,模型自由度高,趋向于OLS解,可能导致高方差 (过拟合)。
大的 \(\lambda\) : 惩罚力度大,系数被强烈压缩,模型变得简单,可能导致高偏差 (欠拟合)。
我们的任务是通过交叉验证等方法,找到一个最优的 \(\lambda\) 值,以在偏差和方差之间取得最佳平衡。
岭回归的几何解释
岭回归的解可以看作是两个几何形状的交点:
损失函数等高线 (RSS Contours) : 在系数空间中,这些是以OLS解为中心的一系列椭圆。
L2惩罚约束 : \(\sum \beta_j^2 \le C\) 。在二维空间中,这是一个以原点为中心的圆形 区域。
岭回归的解就是椭圆等高线与圆形区域首次相切 的点。
岭回归的几何解释(优化版)
RSS损失函数的椭圆等高线与L2惩罚的圆形约束区域相切,切点即为岭回归的解。此图经过几何校正,更清晰准确。
β₁
β₂
0
RSS 等高线
L2 约束: β₁² + β₂² ≤ C
OLS 解 (β̂)
岭回归解
套索回归 (Lasso Regression): L1 正则化
套索回归 (Lasso) 在代价函数上增加的是 L1惩罚项 。
套索回归的代价函数 : \[ \large{J_{Lasso}(\beta) = \underbrace{\sum_{i=1}^{n} (y_i - \beta_0 - \sum_{j=1}^{p} \beta_j x_{ij})^2}_{\text{残差平方和 (RSS)}} + \underbrace{\lambda \sum_{j=1}^{p} |\beta_j|}_{\text{L1正则化项 (Penalty)}}} \]
关键区别 : 惩罚项是系数的绝对值之和 (\(\sum |\beta_j|\) ), 而非平方和。这被称为L1范数 (L1-norm) 。
L1惩罚项的独特之处:实现特征选择
这个看似微小的变化(从平方和到绝对值和)带来了本质上的区别:
Lasso可以将某些不重要特征的系数精确地压缩到0。
这意味着Lasso在进行系数收缩的同时,也自动完成了特征选择 。因此,Lasso产生的模型更稀疏 (sparse) ,也更易于解释。
这是Lasso最神奇也是最有用的特性。岭回归只会把系数“压”得很小,接近0,但只要λ不是无穷大,它就永远不会等于0。而Lasso,就像一个严厉的财务总监,会把那些对降低总体误差贡献不大的变量的“预算”(也就是系数)直接削减为0,让它们彻底出局。这就让Lasso成为了一个内置了特征选择功能的回归工具。
Lasso回归的几何解释
Lasso的解同样可以看作是两个几何形状的交点:
损失函数等高线 (RSS Contours) : 同样是以OLS解为中心的一系列椭圆。
L1惩罚约束 : \(\sum |\beta_j| \le C\) 。在二维空间中,这是一个菱形 (diamond) 区域。
Lasso的解就是椭圆等高线与菱形区域首次相交 的点。
Lasso回归的几何解释(优化版)
RSS损失函数的椭圆等高线与L1惩罚的菱形约束区域相交,交点很可能在顶点上,从而产生稀疏解。此图经过几何校正,更清晰准确。
β₁
β₂
0
RSS 等高线
L1 约束: |β₁| + |β₂| ≤ C
OLS 解 (β̂)
Lasso 解 (β₁=0)
几何解释:为什么Lasso能产生稀疏解?
由于L1约束区域是一个带有尖角 的菱形,损失函数的椭圆等高线很大概率会首先与菱形的一个顶点 相交。
这些顶点恰好位于坐标轴上,意味着其中一个(或多个)系数为0。
相比之下,岭回归的L2约束区域是光滑的圆形,相切点几乎不可能恰好落在坐标轴上,因此系数只会被压缩而不会变为0。
岭回归 vs. 套索回归:总结
惩罚项
系数平方和 (\(\sum \beta_j^2\) )
系数绝对值和 (\(\sum |\beta_j|\) )
系数
收缩,但不会 变为0
收缩,且很多会变为0
特征选择
不执行
自动执行
适用场景
当你认为所有特征都有用 ,只是作用有大有小时
当你认为只有少数特征是真正重要的
处理共线性
对高度相关的特征表现稳定
可能会随机选择相关特征中的一个,并将其余的系数设为0
弹性网络 (Elastic Net): 两全其美
弹性网络 (Elastic Net) 同时结合了L1和L2惩罚项,试图集两家之长。
为何需要弹性网络?
弹性网络在某些情况下优于Lasso:
处理高度相关的特征 : 当一组特征高度相关时,Lasso倾向于随机选择其中一个,而弹性网络则倾向于将它们作为一个整体选入或剔除。这在金融中很常见(如多只科技股的走势高度相关)。
\(p > n\) 的情况 : 当特征数量 \(p\) 大于样本数量 \(n\) 时,Lasso最多只能选择出 \(n\) 个非零系数的特征。弹性网络没有这个限制。
第三部分:正则化实战:预测股票收益
现在,让我们用一个真实的金融案例来看看正则化是如何工作的。
目标 : 预测苹果公司 (AAPL) 的每日收益率。
特征 :
美国整体市场 (S&P 500, SPY) 的收益率。
科技板块 (NASDAQ 100, QQQ) 的收益率。
美国债券市场 (AGG) 的收益率。
模型 : 岭回归和套索回归。
步骤1:获取并准备数据
我们将使用 yfinance 库从雅虎财经获取2020年至2024年的数据。
# 导入必要的库
import yfinance as yf
import pandas as pd
import numpy as np
from sklearn.linear_model import LinearRegression
# Mock Data Generation in case of network failure
try :
tickers = ['AAPL' , 'SPY' , 'QQQ' , 'AGG' ]
data = yf.download(tickers, start= '2020-01-01' , end= '2024-01-01' , progress= False )['Adj Close' ]
if data.empty or len (data) < 10 :
raise ValueError ("Insufficient data" )
data = data.dropna()
except Exception :
date_rng = pd.date_range(start= '2020-01-01' , end= '2024-01-01' , freq= 'B' )
np.random.seed(42 )
mock_data = {
'AAPL' : 150 + np.random.randn(len (date_rng)).cumsum(),
'SPY' : 400 + np.random.randn(len (date_rng)).cumsum(),
'QQQ' : 350 + np.random.randn(len (date_rng)).cumsum(),
'AGG' : 100 + 0.1 * np.random.randn(len (date_rng)).cumsum()
}
data = pd.DataFrame(mock_data, index= date_rng)
returns = np.log(data / data.shift(1 )).dropna()
returns.columns = [f' { col} _ret' for col in returns.columns]
Y = returns['AAPL_ret' ].values
X = returns[['SPY_ret' , 'QQQ_ret' , 'AGG_ret' ]].values
# Display as DataFrame for better presentation
X_df = pd.DataFrame(X, columns= ['SPY_ret' , 'QQQ_ret' , 'AGG_ret' ], index= returns.index)
X_df.head()
YF.download() has changed argument auto_adjust default to True
计算得到的每日对数收益率
2020-01-02
0.002208
0.004147
0.001496
2020-01-03
0.001627
0.000930
0.000041
2020-01-06
-0.003935
0.000855
0.000443
2020-01-07
0.003687
0.001768
0.000951
2020-01-08
0.003433
-0.003238
-0.001020
重要前提:使用正则化前必须进行特征缩放
正则化是基于系数的大小进行惩罚的。如果特征的量纲(单位或范围)不同,惩罚就会不公平。
例如 : 一个以“百万美元”为单位的特征,其系数天然会比一个以“美元”为单位的特征小得多。正则化会错误地认为前者不重要。
解决方案 : 在拟合模型前,对所有特征进行标准化 (Standardization) ,使其均值为0,标准差为1。
步骤2:数据标准化与训练/测试集划分
我们使用 sklearn 的 StandardScaler 来标准化数据,并将数据划分为训练集和测试集,以评估模型的样本外性能。
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
# Ensure we have valid data
if len (X) == 0 or len (Y) == 0 :
raise ValueError ("No data available for training" )
X_train, X_test, y_train, y_test = train_test_split(X, Y, test_size= 0.3 , random_state= 42 )
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
print (f'训练集大小: { X_train_scaled. shape} ' )
print (f'测试集大小: { X_test_scaled. shape} ' )
训练集大小: (730, 3)
测试集大小: (313, 3)
步骤3:岭回归系数路径
我们来看看随着惩罚参数 alpha (\(\lambda\) ) 的变化,岭回归的系数是如何变化的。
from sklearn.linear_model import Ridge
alphas = 10 ** np.linspace(4 , - 2 , 100 )
coefs = []
for a in alphas:
ridge = Ridge(alpha= a, fit_intercept= True )
ridge.fit(X_train_scaled, y_train)
coefs.append(ridge.coef_)
fig, ax = plt.subplots(figsize= (10 , 6 ))
ax.plot(alphas, coefs)
ax.set_xscale('log' )
ax.set_xlabel('Alpha (惩罚强度)' , fontsize= 12 )
ax.set_ylabel('系数大小 (Coefficients)' , fontsize= 12 )
ax.set_title('岭回归系数路径' , fontsize= 16 )
ax.legend(['SPY_ret' , 'QQQ_ret' , 'AGG_ret' ])
plt.gca().invert_xaxis()
plt.grid(True , which= "both" , ls= "--" )
plt.show()
岭回归路径图解读
当 alpha (即 \(\lambda\) ) 很大时(图的左侧),所有系数都被压缩到接近0。
随着 alpha 减小(向右移动),系数逐渐“释放”,绝对值变大。
重要的是,系数是平滑地 趋向于0,但从未真正等于0 。
步骤4:Lasso回归系数路径
现在我们对Lasso做同样的操作,观察其系数路径。
from sklearn.linear_model import Lasso
alphas_lasso = 10 ** np.linspace(- 2 , - 5 , 100 )
coefs_lasso = []
for a in alphas_lasso:
lasso = Lasso(alpha= a, fit_intercept= True )
lasso.fit(X_train_scaled, y_train)
coefs_lasso.append(lasso.coef_)
fig, ax = plt.subplots(figsize= (10 , 6 ))
ax.plot(alphas_lasso, coefs_lasso)
ax.set_xscale('log' )
ax.set_xlabel('Alpha (惩罚强度)' , fontsize= 12 )
ax.set_ylabel('系数大小 (Coefficients)' , fontsize= 12 )
ax.set_title('Lasso回归系数路径' , fontsize= 16 )
ax.legend(['SPY_ret' , 'QQQ_ret' , 'AGG_ret' ])
plt.gca().invert_xaxis()
plt.grid(True , which= "both" , ls= "--" )
plt.show()
Lasso路径图解读:特征选择的威力
与岭回归不同,当 alpha 增大时,系数被精确地压缩到0 。
例如,当 alpha 大于约 \(10^{-3.5}\) 时,AGG_ret(债券收益率)的系数首先变为0,意味着Lasso认为它在预测苹果收益方面最不重要。
这清晰地展示了Lasso是如何进行自动特征选择的。
步骤5:如何选择最优的 alpha? 使用交叉验证
我们不能凭感觉选择 alpha。科学的方法是使用交叉验证 (Cross-Validation) 。
sklearn 提供了内置交叉验证的 LassoCV 和 RidgeCV 模型,它们会自动帮我们找到在训练数据上表现最优的 alpha。
from sklearn.linear_model import LassoCV
lasso_cv = LassoCV(alphas= alphas_lasso, cv= 10 , random_state= 42 )
lasso_cv.fit(X_train_scaled, y_train)
best_alpha = lasso_cv.alpha_
print (f'LassoCV找到的最优 alpha: { best_alpha:.6f} ' )
best_coefs = pd.Series(lasso_cv.coef_, index= ['SPY_ret' , 'QQQ_ret' , 'AGG_ret' ])
print (' \n 最优alpha下的系数:' )
print (best_coefs)
LassoCV找到的最优 alpha: 0.000152
最优alpha下的系数:
SPY_ret -0.000000
QQQ_ret -0.000000
AGG_ret 0.000407
dtype: float64
步骤6:评估最终模型的性能
我们用找到的最优 alpha 训练好的模型,在从未见过 的测试集上进行预测,并评估其均方误差 (MSE)。
from sklearn.metrics import mean_squared_error
y_pred_test = lasso_cv.predict(X_test_scaled)
test_mse = mean_squared_error(y_test, y_pred_test)
ols = LinearRegression()
ols.fit(X_train_scaled, y_train)
y_pred_ols = ols.predict(X_test_scaled)
ols_mse = mean_squared_error(y_test, y_pred_ols)
print (f'Lasso回归在测试集上的MSE: { test_mse:.8f} ' )
print (f'普通线性回归在测试集上的MSE: { ols_mse:.8f} ' )
if test_mse < ols_mse:
print (' \n 结论: 正则化模型在新数据上表现更好。' )
else :
print (' \n 结论: 在此案例中,正则化并未显著提升样本外性能。' )
Lasso回归在测试集上的MSE: 0.00004227
普通线性回归在测试集上的MSE: 0.00004242
结论: 正则化模型在新数据上表现更好。
逻辑回归中的正则化
我们刚刚讨论的正则化思想,完全可以应用于其他模型,例如逻辑回归 (Logistic Regression) 。
目标 : 逻辑回归的代价函数是对数损失函数 (Log Loss) ,我们同样可以在其后加上L1或L2惩罚项。
加入岭回归 (L2) 正则化项 : \[ \large{J(\beta) = -\frac{1}{n} \sum_{i=1}^{n} \left[ y_i\log(p_i) + (1-y_i)\log(1-p_i) \right] + \frac{\lambda}{2} \sum_{j=1}^{p} \beta_j^2} \]
加入套索回归 (L1) 正则化项 : \[ \large{J(\beta) = -\frac{1}{n} \sum_{i=1}^{n} \left[ \dots \right] + \lambda \sum_{j=1}^{p} |\beta_j|} \]
sklearn.linear_model.LogisticRegression 模型中可以通过设置 penalty (‘l1’, ‘l2’) 和 C (等于 \(1/\lambda\) ) 参数来轻松实现。
习题解答与讨论
现在我们来解决讲义中的习题,巩固今天学到的知识。
知识理解题 1
问 : 哪种正则化方法能达到类似于模型选择的方法?
答案:知识理解题 1
答 : 套索回归 (Lasso Regression) 。
因为它使用的L1惩罚项可以将不重要特征的系数精确地压缩到0,从而有效地将这些特征从模型中“剔除”,达到了与最佳子集选择或逐步选择相似的特征选择效果。
知识理解题 2
问 : 提高正则化超参数值 \(\lambda\) 对于模型拟合中的哪种问题有帮助?
答案:知识理解题 2
答 : 提高 \(\lambda\) 值有助于解决过拟合 (Overfitting) 问题。
解释 : 提高 \(\lambda\) 意味着加大了对模型系数的惩罚力度。这会迫使模型选择更小的系数值,从而降低了模型的复杂度。一个更简单的模型对训练数据中的噪声不那么敏感,因此其方差 (variance) 会降低,泛化能力(在未见数据上的表现)会得到提升。当然,\(\lambda\) 过大会导致欠拟合(高偏差)。
知识理解题 3
问 : 阐述 \(\lambda\) 值对模型参数 \(\beta\) 的影响。以及,如果 \(\lambda > 0\) 会如何影响 \(\beta\) 的可解释性。
答案:知识理解题 3
对 \(\beta\) 的影响 : \(\lambda\) 的值控制着对参数 \(\beta\) 的收缩 (shrinkage) 程度。
当 \(\lambda=0\) 时,没有惩罚,参数 \(\beta\) 是普通最小二乘的解。
随着 \(\lambda\) 的值从0开始增大,所有参数 \(\beta\) 的绝对值都会被压缩,变得越来越小,趋向于0。
对可解释性的影响 : 当 \(\lambda > 0\) 时,正则化可能会降低单个参数 \(\beta\) 的直接可解释性 。
在标准的线性回归中,我们可以说:“在其他变量不变的情况下,\(X_j\) 每增加一个单位,\(Y\) 会变化 \(\beta_j\) 个单位。”
但在正则化回归中,由于所有系数都是被“偏置” (biased) 过的(为了降低方差而被人为地推向0),\(\beta_j\) 的值不再是 \(X_j\) 对 \(Y\) 的“无偏”边际效应估计。
然而,对于Lasso而言,它通过将不重要的系数设为0,极大地提升了整个模型的宏观可解释性 ,因为它帮助我们识别出了哪些变量是真正重要的。
程序操作题:使用套索回归预测每股收益
要求 : 使用每股收益预测数据进行模型训练。在训练模型时,使用套索回归进行正则化。将 \(\lambda\) (alpha) 值设为0.1, 1, 10, 20。观察在这些 \(\lambda\) 值时,哪些特征被从模型中移除,哪些变量仍然保留。
由于我们没有原始数据,我们将继续使用之前创建的股票收益预测数据集来完成这个练习。这同样能完美地展示Lasso的效果。
程序操作题:设置不同的 alpha 值
我们将为 alpha = [0.001, 0.005, 0.01] (对于收益率数据,需要较小的alpha)训练三个不同的Lasso模型,并观察其系数。
alphas_to_test = [0.001 , 0.005 , 0.01 ]
results = {}
for alpha_val in alphas_to_test:
lasso = Lasso(alpha= alpha_val)
lasso.fit(X_train_scaled, y_train)
results[f'alpha= { alpha_val} ' ] = lasso.coef_
results_df = pd.DataFrame(results, index= ['SPY_ret' , 'QQQ_ret' , 'AGG_ret' ]).round (4 )
results_df
程序操作题:结果解读
从 Table 1 中我们可以清晰地看到Lasso的特征选择过程:
alpha=0.001 : 惩罚较小。所有三个特征(SPY_ret, QQQ_ret, AGG_ret)都被保留,系数均不为0。
alpha=0.005 : 惩罚增大。AGG_ret (债券市场收益) 的系数被精确地压缩为0 。Lasso模型认为,在有SPY和QQQ的情况下,AGG对于预测AAPL的收益不再提供有价值的信息。
alpha=0.01 : 惩罚进一步增大。AGG_ret 的系数仍然为0。其他两个系数也被进一步压缩,但仍然保留。
这个练习生动地展示了,通过调整超参数 alpha,我们可以控制模型的稀疏度,从而决定哪些特征应该被保留在最终模型中。
总结:本章核心要点
核心冲突 : 建模的根本挑战在于偏差-方差的权衡 。我们的目标是最小化总预测误差,而非仅仅拟合训练数据。
过拟合是敌人 : 过于复杂的模型会学习到噪声,导致样本外预测能力差。
两大策略 :
模型选择 (如前向分步法) 通过增减特征来控制复杂度。
正则化 (岭、套索、弹性网络) 通过对系数大小施加惩罚来控制复杂度。
Lasso vs. Ridge : Lasso (L1) 能实现特征选择 ,产生稀疏模型;Ridge (L2) 适用于你认为所有特征都有用的情况。
实践要点 : 使用正则化前,必须对特征进行标准化 。最优的正则化参数 \(\lambda\) 需要通过交叉验证 来确定。
好的,同学们,我们今天的内容就到这里。请记住,构建一个好的预测模型,与其说是一门纯粹的科学,不如说是一门在理论指导下的艺术。这门艺术的核心,就是我们今天反复强调的“偏差-方差权衡”。你们未来的工作中,无论是做量化交易策略,还是宏观经济预测,都将不断地与这个问题打交道。希望今天的课程能为你们提供一套强大的思想和工具。谢谢大家。