05 重采样方法 (Resampling Methods)
核心问题:没有独立测试集时,如何评估模型?
在统计学习中,我们反复遇到同一个困境:
- 模型评估:已训练好的模型,在新数据上表现如何?
- 模型选择:多个候选模型中,哪个泛化能力最强?
重采样方法(Resampling Methods)的核心思想:
用已有数据自身反复构造”伪新数据”,来估计模型在真正新数据上的表现。
本章介绍两大核心工具:交叉验证 与 自助法。
为什么训练误差不能用于模型评估?
训练误差总是会低估模型在新数据上的真实表现:
- 过拟合的本质:模型不仅学习了数据中的真实信号,还记住了随机噪声
- 训练误差随模型复杂度单调递减,但测试误差呈U形曲线
- 用训练误差选模型 → 总是选最复杂的模型 → 泛化能力最差
A股市场的典型陷阱:
- 量化基金用100个技术指标拟合过去5年的沪深300 → 训练 \(R^2 = 0.95\)
- 实盘运行3个月 → 实际 \(R^2 < 0.01\)
- 原因:模型过拟合了历史市场结构,而市场是非平稳的
测试误差的核心概念
测试误差(Test Error / Generalization Error):
\[ \large{ \text{Err} = E\left[L(Y, \hat{f}(X))\right] } \tag{1}\]
其中期望是对新的、未见过的数据点 \((X, Y)\) 取的。
| 训练误差 |
在训练集上的平均损失 |
模型拟合 |
严重低估真实误差 |
| 测试误差 |
在新数据上的平均损失 |
模型评估 |
需要独立测试数据 |
| CV估计 |
对测试误差的估计 |
模型选择 |
是一种近似 |
核心矛盾:我们需要测试误差,但没有真正的新数据 → 重采样方法正是为了解决这个矛盾而生。
为什么训练误差不能用于模型评估?
训练误差总是会低估模型在新数据上的真实表现:
- 过拟合的本质:模型不仅学习了数据中的真实信号,还记住了随机噪声
- 训练误差随模型复杂度单调递减,但测试误差呈U形曲线
- 用训练误差选模型 → 总是选最复杂的模型 → 泛化能力最差
A股市场的典型陷阱:
- 量化基金用100个技术指标拟合过去5年的沪深300 → 训练 \(R^2 = 0.95\)
- 实盘运行3个月 → 实际 \(R^2 < 0.01\)
- 原因:模型过拟合了历史市场结构,而市场是非平稳的
测试误差的核心概念
测试误差(Test Error / Generalization Error):
\[ \large{ \text{Err} = E\left[L(Y, \hat{f}(X))\right] } \tag{2}\]
其中期望是对新的、未见过的数据点 \((X, Y)\) 取的。
| 训练误差 |
在训练集上的平均损失 |
模型拟合 |
严重低估真实误差 |
| 测试误差 |
在新数据上的平均损失 |
模型评估 |
需要独立测试数据 |
| CV估计 |
对测试误差的估计 |
模型选择 |
是一种近似 |
核心矛盾:我们需要测试误差,但没有真正的新数据 → 重采样方法正是为了解决这个矛盾而生。
重采样方法的历史与直觉
重采样方法的发展历程:
- 1931年:Mahalanobis首次提出交叉验证的雏形
- 1968年:Lachenbruch & Mickey 系统化留一交叉验证(LOOCV)
- 1974年:Stone 提出广义交叉验证理论
- 1979年:Bradley Efron 发明Bootstrap——被《美国统计学家》评为20世纪最重要的统计创新之一
核心直觉:
“如果我能回到市场重新收集一组数据,结果会有多大不同?”
重采样方法巧妙地回答了这个问题——不需要真正回到市场。
重采样 vs. 信息准则:两条不同的路径
除了重采样,还有另一类方法估计测试误差——信息准则:
| 重采样方法 |
CV, Bootstrap |
直接模拟新数据 |
通用,任何模型 |
| 信息准则 |
AIC, BIC, \(C_p\) |
惩罚训练误差 |
参数模型 |
AIC(赤池信息准则):
\[ \large{ \text{AIC} = -2\ln(L) + 2p } \tag{3}\]
- \(L\):似然函数值,\(p\):参数个数
- 本质:训练误差 + 模型复杂度惩罚
为什么还需要CV?
- 信息准则只适用于参数模型
- 对于随机森林、神经网络等非参数模型,只有CV可用
- CV对模型假设的依赖更少,更加稳健
重采样方法的历史与直觉
重采样方法的发展历程:
- 1931年:Mahalanobis首次提出交叉验证的雏形
- 1968年:Lachenbruch & Mickey 系统化留一交叉验证(LOOCV)
- 1974年:Stone 提出广义交叉验证理论
- 1979年:Bradley Efron 发明Bootstrap——被《美国统计学家》评为20世纪最重要的统计创新之一
核心直觉:
“如果我能回到市场重新收集一组数据,结果会有多大不同?”
重采样方法巧妙地回答了这个问题——不需要真正回到市场。
重采样 vs. 信息准则:两条不同的路径
除了重采样,还有另一类方法估计测试误差——信息准则:
| 重采样方法 |
CV, Bootstrap |
直接模拟新数据 |
通用,任何模型 |
| 信息准则 |
AIC, BIC, \(C_p\) |
惩罚训练误差 |
参数模型 |
AIC(赤池信息准则):
\[ \large{ \text{AIC} = -2\ln(L) + 2p } \tag{4}\]
- \(L\):似然函数值,\(p\):参数个数
- 本质:训练误差 + 模型复杂度惩罚
为什么还需要CV?
- 信息准则只适用于参数模型
- 对于随机森林、神经网络等非参数模型,只有CV可用
- CV对模型假设的依赖更少,更加稳健
在海康威视数据上的具体实现
数据说明:
- 海康威视(002415.XSHE):中国安防龙头,总部位于杭州
- 取最近500个交易日的前复权日收盘价
- 计算日收益率:\(r_t = (P_t - P_{t-1}) / P_{t-1}\)
- 预测任务:用前一日收益率(\(r_{t-1}\))预测当日收益率(\(r_t\))
- 尝试1-5阶多项式回归
为什么选择海康威视?
- 长三角代表性科技企业(杭州,浙江省)
- 日均交易量大,数据质量高
- 股价波动适中,适合展示回归模型的性能差异
在海康威视数据上的具体实现
数据说明:
- 海康威视(002415.XSHE):中国安防龙头,总部位于杭州
- 取最近500个交易日的前复权日收盘价
- 计算日收益率:\(r_t = (P_t - P_{t-1}) / P_{t-1}\)
- 预测任务:用前一日收益率(\(r_{t-1}\))预测当日收益率(\(r_t\))
- 尝试1-5阶多项式回归
为什么选择海康威视?
- 长三角代表性科技企业(杭州,浙江省)
- 日均交易量大,数据质量高
- 股价波动适中,适合展示回归模型的性能差异
验证集方法的致命缺陷:高方差
# 10次不同随机切分,每次计算1-5阶多项式的验证集MSE
fig, ax = plt.subplots(figsize=(10, 5)) # 创建画布
polynomial_degrees = range(1, 6) # 多项式阶数从1到5
for split_iteration in range(10): # 循环10次不同的随机切分
np.random.seed(split_iteration) # 每次使用不同的随机种子
total_samples = len(feature_x) # 总样本数
shuffled_indices = np.random.permutation(total_samples) # 生成随机排列的索引
split_point = total_samples // 2 # 对半分割点
train_indices = shuffled_indices[:split_point] # 前一半作为训练集索引
validation_indices = shuffled_indices[split_point:] # 后一半作为验证集索引
mse_per_degree = [] # 存储各阶多项式的MSE
for degree in polynomial_degrees: # 遍历每个多项式阶数
poly_transformer = PolynomialFeatures(degree=degree, include_bias=False) # 构造多项式特征转换器
x_train_poly = poly_transformer.fit_transform(feature_x[train_indices]) # 对训练集生成多项式特征
x_val_poly = poly_transformer.transform(feature_x[validation_indices]) # 对验证集生成同样的多项式特征
model = LinearRegression().fit(x_train_poly, target_y[train_indices]) # 拟合线性回归模型
val_predictions = model.predict(x_val_poly) # 在验证集上预测
mse_per_degree.append(mean_squared_error(target_y[validation_indices], val_predictions)) # 计算并记录MSE
ax.plot(list(polynomial_degrees), mse_per_degree, marker='o', alpha=0.6) # 绘制当次切分的MSE曲线
ax.set_xlabel('多项式阶数 (Polynomial Degree)', fontsize=12) # X轴标签
ax.set_ylabel('验证集 MSE', fontsize=12) # Y轴标签
ax.set_title('验证集方法的高方差:10次随机切分结果差异显著', fontsize=14) # 标题
ax.grid(True, alpha=0.3) # 添加网格线
plt.tight_layout() # 调整布局
plt.show() # 显示图形
高方差的根本原因分析
验证集方法高方差的三重来源:
1. 训练集的随机性
- 不同切分 → 不同训练集 → 不同模型 \(\hat{f}\)
- 训练集的差异直接导致模型参数的差异
2. 验证集的随机性
- 同一模型在不同验证集上的MSE不同
- 验证集中”容易预测”和”难以预测”的观测比例随机
3. 训练集大小不足
- 只用50%数据训练 → 模型估计精度下降
- 不同切分产生的”弱模型”差异更大
类比:
就像只考一道题来评价学生水平——成绩波动极大,因为这道题恰好是擅长/不擅长的概率各占一半。
从验证集法到交叉验证的思维跃迁
验证集法的改进方向:
| 只评估一次 |
多次评估取平均 |
k折交叉验证 |
| 训练集太小 |
最大化训练数据使用 |
LOOCV |
| 验证集随机 |
让每个点都做验证 |
k折CV / LOOCV |
核心升级:
- 验证集法:一次随机切分 → 一个 MSE 估计
- k折CV:k次系统切分 → k个 MSE → 取平均 → 更稳定
\[ \large{ \text{方差降低:} \text{Var}(\overline{X}) = \frac{\text{Var}(X)}{k} } \]
如果k个MSE是独立的,方差降为原来的 \(1/k\)
实际上它们不完全独立,但方差仍然显著降低
高方差的根本原因分析
验证集方法高方差的三重来源:
1. 训练集的随机性
- 不同切分 → 不同训练集 → 不同模型 \(\hat{f}\)
- 训练集的差异直接导致模型参数的差异
2. 验证集的随机性
- 同一模型在不同验证集上的MSE不同
- 验证集中”容易预测”和”难以预测”的观测比例随机
3. 训练集大小不足
- 只用50%数据训练 → 模型估计精度下降
- 不同切分产生的”弱模型”差异更大
类比:
就像只考一道题来评价学生水平——成绩波动极大,因为这道题恰好是擅长/不擅长的概率各占一半。
从验证集法到交叉验证的思维跃迁
验证集法的改进方向:
| 只评估一次 |
多次评估取平均 |
k折交叉验证 |
| 训练集太小 |
最大化训练数据使用 |
LOOCV |
| 验证集随机 |
让每个点都做验证 |
k折CV / LOOCV |
核心升级:
- 验证集法:一次随机切分 → 一个 MSE 估计
- k折CV:k次系统切分 → k个 MSE → 取平均 → 更稳定
\[ \large{ \text{方差降低:} \text{Var}(\overline{X}) = \frac{\text{Var}(X)}{k} } \]
如果k个MSE是独立的,方差降为原来的 \(1/k\)
实际上它们不完全独立,但方差仍然显著降低
LOOCV:留一交叉验证,消除随机性
留一交叉验证(Leave-One-Out Cross-Validation)的思路:每次只留出1个观测做验证,其余所有数据做训练。
\[ \large{ \text{CV}_{(n)} = \frac{1}{n}\sum_{i=1}^{n}\text{MSE}_i } \tag{7}\]
- 重复 \(n\) 次(\(n\) = 样本量),每次留出不同的观测
- \(\text{MSE}_i\):第 \(i\) 个观测被留出时的预测误差
- 训练集大小:每次用 \(n-1\) 个观测,偏差极小
- 结果确定:没有随机性,每次运行结果完全相同
快捷公式(仅适用于线性回归 / 多项式回归):
\[ \large{ \text{CV}_{(n)} = \frac{1}{n}\sum_{i=1}^{n}\left(\frac{y_i - \hat{y}_i}{1 - h_i}\right)^2 } \tag{8}\]
其中 \(h_i\) 是帽子矩阵的第 \(i\) 个对角元素(杠杆值),衡量第 \(i\) 个观测对自身预测的影响力。
LOOCV快捷公式的推导
补充说明:杠杆值 \(h_i\) 的含义与快捷公式推导
在线性回归中,预测值可以写为:
\[ \large{ \hat{\mathbf{y}} = \mathbf{H}\mathbf{y}, \quad \mathbf{H} = \mathbf{X}(\mathbf{X}^T\mathbf{X})^{-1}\mathbf{X}^T } \tag{9}\]
\(\mathbf{H}\) 称为帽子矩阵(Hat Matrix),\(h_i = H_{ii}\) 是其第 \(i\) 个对角元素。
\(h_i\) 的性质:
- \(0 \leq h_i \leq 1\),且 \(\sum_i h_i = p\)(\(p\) 为参数个数)
- \(h_i\) 越大 → 第 \(i\) 个观测对自身预测的影响力越大
- \(h_i\) 大的点是高杠杆点——在特征空间中远离其他数据点
快捷公式的直觉:
\[ \large{ \text{LOOCV}_i = \left(\frac{y_i - \hat{y}_i}{1 - h_i}\right)^2 } \]
- 分子 \(y_i - \hat{y}_i\):全量模型的残差
- 分母 \(1 - h_i\):对杠杆值的修正——高杠杆点的残差被放大
LOOCV的优势与代价
优势:
- ✓ 无随机性:结果完全确定,可复现
- ✓ 低偏差:每次用 \(n-1\) 个观测训练,接近全量数据
- ✓ 有快捷公式时,计算高效(如线性模型)
代价:
- ✗ 高方差(反直觉!):\(n\) 个训练集之间高度重叠(只差1个观测)
- 各折模型几乎相同 → MSE 高度相关 → 平均后方差不降反升
- ✗ 计算成本:对于复杂模型,需拟合 \(n\) 次
- 深度学习模型:\(n = 10000\) 意味着训练10000次!
- ✗ 不适合时间序列数据(打破时间顺序)
LOOCV快捷公式的推导
补充说明:杠杆值 \(h_i\) 的含义与快捷公式推导
在线性回归中,预测值可以写为:
\[ \large{ \hat{\mathbf{y}} = \mathbf{H}\mathbf{y}, \quad \mathbf{H} = \mathbf{X}(\mathbf{X}^T\mathbf{X})^{-1}\mathbf{X}^T } \tag{10}\]
\(\mathbf{H}\) 称为帽子矩阵(Hat Matrix),\(h_i = H_{ii}\) 是其第 \(i\) 个对角元素。
\(h_i\) 的性质:
- \(0 \leq h_i \leq 1\),且 \(\sum_i h_i = p\)(\(p\) 为参数个数)
- \(h_i\) 越大 → 第 \(i\) 个观测对自身预测的影响力越大
- \(h_i\) 大的点是高杠杆点——在特征空间中远离其他数据点
快捷公式的直觉:
\[ \large{ \text{LOOCV}_i = \left(\frac{y_i - \hat{y}_i}{1 - h_i}\right)^2 } \]
- 分子 \(y_i - \hat{y}_i\):全量模型的残差
- 分母 \(1 - h_i\):对杠杆值的修正——高杠杆点的残差被放大
LOOCV的优势与代价
优势:
- ✓ 无随机性:结果完全确定,可复现
- ✓ 低偏差:每次用 \(n-1\) 个观测训练,接近全量数据
- ✓ 有快捷公式时,计算高效(如线性模型)
代价:
- ✗ 高方差(反直觉!):\(n\) 个训练集之间高度重叠(只差1个观测)
- 各折模型几乎相同 → MSE 高度相关 → 平均后方差不降反升
- ✗ 计算成本:对于复杂模型,需拟合 \(n\) 次
- 深度学习模型:\(n = 10000\) 意味着训练10000次!
- ✗ 不适合时间序列数据(打破时间顺序)
k折交叉验证:偏差与方差的最佳权衡
\[ \large{ \text{CV}_{(k)} = \frac{1}{k}\sum_{i=1}^{k}\text{MSE}_i } \tag{11}\]
为什么 k=5 或 k=10 是最优选择?
偏差-方差权衡的数学分析:
设真实测试误差为 \(\text{Err}\),CV估计为 \(\widehat{\text{Err}}\)
偏差:\(\text{Bias} = E[\widehat{\text{Err}}] - \text{Err}\)
- \(k\) 越大 → 训练集大小 \(n(k-1)/k\) 越接近 \(n\) → 偏差越小
- \(k=5\):训练集为 \(0.8n\);\(k=10\):训练集为 \(0.9n\)
方差:\(\text{Var}(\widehat{\text{Err}}) = \text{Var}\left(\frac{1}{k}\sum_{i=1}^k \text{MSE}_i\right)\)
- 如果各折MSE独立:\(\text{Var} = \frac{\sigma^2}{k}\)(\(k\) 越大越好)
- 但实际上各折MSE正相关(训练集重叠)
- \(k\) 越大 → 重叠越大 → 相关性越强 → 方差增大
Hastie等人(2009)的结论:\(k = 5\) 或 \(k = 10\) 在大量实验中表现最稳定。
为什么 k=5 或 k=10 是最优选择?
偏差-方差权衡的数学分析:
设真实测试误差为 \(\text{Err}\),CV估计为 \(\widehat{\text{Err}}\)
偏差:\(\text{Bias} = E[\widehat{\text{Err}}] - \text{Err}\)
- \(k\) 越大 → 训练集大小 \(n(k-1)/k\) 越接近 \(n\) → 偏差越小
- \(k=5\):训练集为 \(0.8n\);\(k=10\):训练集为 \(0.9n\)
方差:\(\text{Var}(\widehat{\text{Err}}) = \text{Var}\left(\frac{1}{k}\sum_{i=1}^k \text{MSE}_i\right)\)
- 如果各折MSE独立:\(\text{Var} = \frac{\sigma^2}{k}\)(\(k\) 越大越好)
- 但实际上各折MSE正相关(训练集重叠)
- \(k\) 越大 → 重叠越大 → 相关性越强 → 方差增大
Hastie等人(2009)的结论:\(k = 5\) 或 \(k = 10\) 在大量实验中表现最稳定。
k的选择:偏差-方差权衡
| k=2 (验证集法) |
高 |
高 |
最低 |
不推荐 |
| k=5 |
适中偏高 |
低 |
低 |
推荐 |
| k=10 |
适中偏低 |
适中 |
适中 |
推荐 |
| k=n (LOOCV) |
最低 |
最高 |
最高 |
特殊场景 |
核心直觉:
- \(k\) 越大 → 训练集越大 → 偏差越小(模型拟合得更充分)
- \(k\) 越大 → 各折训练集重叠越多 → 预测误差之间相关性更强 → 方差越大
- 经验法则:\(k=5\) 或 \(k=10\) 是最优平衡点
实证:LOOCV与10折CV在海康威视数据上的对比
Code
from sklearn.model_selection import cross_val_score, KFold, LeaveOneOut # 导入交叉验证工具
fig, axes = plt.subplots(1, 2, figsize=(14, 5)) # 创建1行2列子图布局
# 左图:LOOCV
loocv_mse_per_degree = [] # 存储LOOCV各阶MSE
for degree in range(1, 6): # 遍历1到5阶多项式
poly_transformer = PolynomialFeatures(degree=degree, include_bias=False) # 多项式特征转换器
x_poly_features = poly_transformer.fit_transform(feature_x) # 生成多项式特征
loocv_splitter = LeaveOneOut() # 创建LOOCV分割器
neg_mse_scores = cross_val_score(LinearRegression(), x_poly_features, target_y, cv=loocv_splitter, scoring='neg_mean_squared_error') # 计算LOOCV负MSE
loocv_mse_per_degree.append(-neg_mse_scores.mean()) # 取负得到正MSE并记录
axes[0].plot(range(1, 6), loocv_mse_per_degree, 'b-o', linewidth=2, markersize=8) # 绘制LOOCV曲线
axes[0].set_xlabel('多项式阶数', fontsize=12) # X轴标签
axes[0].set_ylabel('LOOCV MSE', fontsize=12) # Y轴标签
axes[0].set_title('LOOCV(结果唯一确定)', fontsize=14) # 标题
axes[0].grid(True, alpha=0.3) # 网格线
# 右图:9次不同随机种子的10折CV
for cv_iteration in range(9): # 重复9次10折CV
kfold_mse_per_degree = [] # 存储当次实验各阶MSE
for degree in range(1, 6): # 遍历1到5阶多项式
poly_transformer = PolynomialFeatures(degree=degree, include_bias=False) # 多项式特征转换器
x_poly_features = poly_transformer.fit_transform(feature_x) # 生成多项式特征
kfold_splitter = KFold(n_splits=10, shuffle=True, random_state=cv_iteration) # 10折分割器,每次不同随机种子
neg_mse_scores = cross_val_score(LinearRegression(), x_poly_features, target_y, cv=kfold_splitter, scoring='neg_mean_squared_error') # 计算10折CV负MSE
kfold_mse_per_degree.append(-neg_mse_scores.mean()) # 取负得到正MSE
axes[1].plot(range(1, 6), kfold_mse_per_degree, marker='o', alpha=0.6) # 绘制当次曲线
axes[1].set_xlabel('多项式阶数', fontsize=12) # X轴标签
axes[1].set_ylabel('10折 CV MSE', fontsize=12) # Y轴标签
axes[1].set_title('10折CV(9次不同切分,方差远低于验证集法)', fontsize=14) # 标题
axes[1].grid(True, alpha=0.3) # 网格线
plt.tight_layout() # 调节布局间距
plt.show() # 显示图表
<Figure size 960x480 with 0 Axes>
实证结果的关键发现
从海康威视的CV实证中,我们观察到:
发现一:多项式阶数对MSE影响微弱
- 1阶到5阶多项式的CV误差差异很小
- 说明前一日收益率对次日收益率的非线性预测能力有限
- 这与金融学中的弱式有效市场假说一致
发现二:LOOCV和10折CV给出一致结论
- 两种方法的排序结果几乎相同
- 但10折CV的计算速度远快于LOOCV
发现三:10折CV的稳定性远优于验证集法
- 验证集法的10条曲线差异显著
- 10折CV的9条曲线几乎重叠
- 这就是k折CV被广泛采用的原因
实际含义:对于海康威视的日收益率预测,简单的线性模型(1阶)已经足够。
实证结果的关键发现
从海康威视的CV实证中,我们观察到:
发现一:多项式阶数对MSE影响微弱
- 1阶到5阶多项式的CV误差差异很小
- 说明前一日收益率对次日收益率的非线性预测能力有限
- 这与金融学中的弱式有效市场假说一致
发现二:LOOCV和10折CV给出一致结论
- 两种方法的排序结果几乎相同
- 但10折CV的计算速度远快于LOOCV
发现三:10折CV的稳定性远优于验证集法
- 验证集法的10条曲线差异显著
- 10折CV的9条曲线几乎重叠
- 这就是k折CV被广泛采用的原因
实际含义:对于海康威视的日收益率预测,简单的线性模型(1阶)已经足够。
金融数据的特殊陷阱:时间序列交叉验证
金融时间序列数据不能随机打乱! 随机切分会造成严重的数据泄露(Data Leakage)。
时间序列CV的Python实现要点
scikit-learn中的TimeSeriesSplit:
from sklearn.model_selection import TimeSeriesSplit
tscv = TimeSeriesSplit(n_splits=5)
两种策略对比:
| 扩展窗口 |
不断增长 |
数据利用率高 |
早期数据可能已过时 |
| 滚动窗口 |
固定大小 |
适应市场变化 |
浪费早期数据 |
A股市场的经验法则:
- 牛熊转换周期约3-5年 → 训练窗口至少覆盖一个完整周期
- 滚动窗口更适合A股(市场结构变化快)
- 测试窗口不宜太长(1-3个月为佳)
- 考虑在训练集和测试集之间留一个间隔期(Gap),避免信息泄露
时间序列CV的Python实现要点
scikit-learn中的TimeSeriesSplit:
from sklearn.model_selection import TimeSeriesSplit
tscv = TimeSeriesSplit(n_splits=5)
两种策略对比:
| 扩展窗口 |
不断增长 |
数据利用率高 |
早期数据可能已过时 |
| 滚动窗口 |
固定大小 |
适应市场变化 |
浪费早期数据 |
A股市场的经验法则:
- 牛熊转换周期约3-5年 → 训练窗口至少覆盖一个完整周期
- 滚动窗口更适合A股(市场结构变化快)
- 测试窗口不宜太长(1-3个月为佳)
- 考虑在训练集和测试集之间留一个间隔期(Gap),避免信息泄露
分类问题的交叉验证
对于分类问题,交叉验证使用分类错误率替代MSE:
\[ \large{ \text{CV}_{(k)} = \frac{1}{k}\sum_{i=1}^{k}\text{Err}_i, \quad \text{Err}_i = \frac{1}{n_i}\sum_{j \in C_i} I(y_j \neq \hat{y}_j) } \tag{14}\]
其中 \(C_i\) 是第 \(i\) 折的验证集,\(I(\cdot)\) 是指示函数。
海康威视涨跌预测实例:
- 目标:预测海康威视次日涨跌(二分类)
- 特征:Lag1(前1日收益率)、Lag2(前2日收益率)
- 模型:逻辑回归(Logistic Regression)
- 评估方法:10折交叉验证的分类错误率
此任务的训练误差往往远低于CV误差,因为模型会过度适配训练数据中的噪声。
分类CV中的评价指标选择
分类错误率不总是最佳选择:
| 错误率 |
\(\frac{\text{FP}+\text{FN}}{n}\) |
各类别平衡 |
| AUC-ROC |
ROC曲线下面积 |
各类别不平衡 |
| F1-Score |
\(\frac{2 \cdot P \cdot R}{P + R}\) |
关注少数类 |
| Log Loss |
\(-\frac{1}{n}\sum[y\log\hat{p}+(1-y)\log(1-\hat{p})]\) |
概率校准 |
金融应用中的指标选择:
- 信用违约预测:违约率仅1% → 用AUC-ROC或F1
- 涨跌预测:类别大致平衡 → 用错误率即可
- 量化选股:关注排序能力 → 用AUC-ROC
CV与过拟合诊断
如何用CV识别过拟合?
关键信号:训练误差远低于CV误差。
| 训练≈CV,两者都低 |
低 |
低 |
✅ 良好拟合 |
| 训练≈CV,两者都高 |
高 |
高 |
⚠️ 欠拟合 |
| 训练≪CV |
很低 |
高 |
❌ 过拟合 |
| 训练≈0 |
~0 |
很高 |
❌ 严重过拟合 |
A股量化策略的典型模式:
- 在样本内(2018-2022)年化收益40%,最大回撤5%
- 10折CV显示年化收益仅8%,最大回撤30%
- 结论:策略严重过拟合历史数据
分类CV中的评价指标选择
分类错误率不总是最佳选择:
| 错误率 |
\(\frac{\text{FP}+\text{FN}}{n}\) |
各类别平衡 |
| AUC-ROC |
ROC曲线下面积 |
各类别不平衡 |
| F1-Score |
\(\frac{2 \cdot P \cdot R}{P + R}\) |
关注少数类 |
| Log Loss |
\(-\frac{1}{n}\sum[y\log\hat{p}+(1-y)\log(1-\hat{p})]\) |
概率校准 |
金融应用中的指标选择:
- 信用违约预测:违约率仅1% → 用AUC-ROC或F1
- 涨跌预测:类别大致平衡 → 用错误率即可
- 量化选股:关注排序能力 → 用AUC-ROC
CV与过拟合诊断
如何用CV识别过拟合?
关键信号:训练误差远低于CV误差。
| 训练≈CV,两者都低 |
低 |
低 |
✅ 良好拟合 |
| 训练≈CV,两者都高 |
高 |
高 |
⚠️ 欠拟合 |
| 训练≪CV |
很低 |
高 |
❌ 过拟合 |
| 训练≈0 |
~0 |
很高 |
❌ 严重过拟合 |
A股量化策略的典型模式:
- 在样本内(2018-2022)年化收益40%,最大回撤5%
- 10折CV显示年化收益仅8%,最大回撤30%
- 结论:策略严重过拟合历史数据
自助法的核心思想:用”已有”模拟”未知”
Bootstrap的统计学原理
Bootstrap的理论基础:插值原理(Plug-in Principle)
- 总体分布 \(F\) 未知 → 用经验分布 \(\hat{F}_n\) 替代
- 经验分布把概率 \(1/n\) 均匀分配到每个观测 \(z_i\) 上
- 从 \(\hat{F}_n\) 抽样 = 有放回地从原始样本中抽取
形式化表述:
设统计量 \(\hat{\theta} = s(\mathbf{Z})\) 是关于数据 \(\mathbf{Z}\) 的函数。
- 理想世界:从 \(F\) 反复抽取新样本,得到 \(\hat{\theta}\) 的抽样分布
- Bootstrap世界:从 \(\hat{F}_n\) 反复抽取,得到 \(\hat{\theta}^*\) 的Bootstrap分布
\[ \large{ \hat{F}_n \xrightarrow{n \to \infty} F \implies \text{Bootstrap分布} \xrightarrow{n \to \infty} \text{真实抽样分布} } \]
大样本理论保证:当 \(n \to \infty\),Bootstrap分布一致收敛到真实抽样分布。
Bootstrap样本的构造过程
有放回抽样的具体操作:
设原始样本有 \(n = 5\) 个观测:\(\{z_1, z_2, z_3, z_4, z_5\}\)
| \(Z^{*1}\) |
\(\{z_2, z_1, z_2, z_5, z_2\}\) |
\(z_1, z_2, z_5\) |
\(z_2\)出现3次 |
| \(Z^{*2}\) |
\(\{z_3, z_3, z_1, z_4, z_3\}\) |
\(z_1, z_3, z_4\) |
\(z_3\)出现3次 |
| \(Z^{*3}\) |
\(\{z_5, z_2, z_4, z_2, z_1\}\) |
\(z_1, z_2, z_4, z_5\) |
\(z_2\)出现2次 |
关键特征:
- 每个Bootstrap样本大小与原始样本相同(都是 \(n\))
- 某些观测可能出现多次,某些观测可能不出现
- 平均每个样本包含原始数据的 63.2% 独特观测
投资组合优化案例:寻找最小方差配比
问题:将资金投资于海康威视(X)和宁波银行(Y),如何确定最优配比 \(\alpha\) 使投资组合风险最小?
\[ \large{ \alpha = \frac{\sigma_Y^2 - \sigma_{XY}}{\sigma_X^2 + \sigma_Y^2 - 2\sigma_{XY}} } \tag{15}\]
- \(\sigma_X^2 = \text{Var}(X)\):海康威视收益率方差
- \(\sigma_Y^2 = \text{Var}(Y)\):宁波银行收益率方差
- \(\sigma_{XY} = \text{Cov}(X, Y)\):两资产收益率协方差
- \(\alpha\):投资在海康威视上的比例,\(1-\alpha\) 投资在宁波银行
关键问题:\(\hat{\alpha}\) 的不确定性有多大?仅凭100天数据估计的 \(\alpha\),置信区间是多少?
→ 这正是 Bootstrap 大显身手的场景!
最小方差投资组合的推导
补充说明:\(\alpha\) 最优解的推导过程
投资组合收益率:\(R_p = \alpha X + (1-\alpha)Y\)
投资组合方差:
\[ \large{ \text{Var}(R_p) = \alpha^2\sigma_X^2 + (1-\alpha)^2\sigma_Y^2 + 2\alpha(1-\alpha)\sigma_{XY} } \tag{16}\]
对 \(\alpha\) 求导并令导数为零:
\[ \large{ \frac{d\text{Var}(R_p)}{d\alpha} = 2\alpha\sigma_X^2 - 2(1-\alpha)\sigma_Y^2 + 2(1-2\alpha)\sigma_{XY} = 0 } \]
整理得:
\[ \large{ \alpha(\sigma_X^2 + \sigma_Y^2 - 2\sigma_{XY}) = \sigma_Y^2 - \sigma_{XY} } \]
\[ \large{ \alpha^* = \frac{\sigma_Y^2 - \sigma_{XY}}{\sigma_X^2 + \sigma_Y^2 - 2\sigma_{XY}} } \]
直觉解读:
- 如果 \(\sigma_X^2 = \sigma_Y^2\) 且 \(\sigma_{XY} = 0\) → \(\alpha^* = 0.5\)(等权)
- \(Y\) 波动越大 → \(\alpha\) 越大(更多投海康威视)
- 两资产正相关越强 → 分散化效果越差
最小方差投资组合的推导
补充说明:\(\alpha\) 最优解的推导过程
投资组合收益率:\(R_p = \alpha X + (1-\alpha)Y\)
投资组合方差:
\[ \large{ \text{Var}(R_p) = \alpha^2\sigma_X^2 + (1-\alpha)^2\sigma_Y^2 + 2\alpha(1-\alpha)\sigma_{XY} } \tag{17}\]
对 \(\alpha\) 求导并令导数为零:
\[ \large{ \frac{d\text{Var}(R_p)}{d\alpha} = 2\alpha\sigma_X^2 - 2(1-\alpha)\sigma_Y^2 + 2(1-2\alpha)\sigma_{XY} = 0 } \]
整理得:
\[ \large{ \alpha(\sigma_X^2 + \sigma_Y^2 - 2\sigma_{XY}) = \sigma_Y^2 - \sigma_{XY} } \]
\[ \large{ \alpha^* = \frac{\sigma_Y^2 - \sigma_{XY}}{\sigma_X^2 + \sigma_Y^2 - 2\sigma_{XY}} } \]
直觉解读:
- 如果 \(\sigma_X^2 = \sigma_Y^2\) 且 \(\sigma_{XY} = 0\) → \(\alpha^* = 0.5\)(等权)
- \(Y\) 波动越大 → \(\alpha\) 越大(更多投海康威视)
- 两资产正相关越强 → 分散化效果越差
Bootstrap实证:海康威视与宁波银行
Code
# 读取两只股票的收盘价序列并对齐交易日
if 'order_book_id' in stock_price_data.index.names: # 判断多层索引
haikang_prices = stock_price_data.xs('002415.XSHE', level='order_book_id').sort_index()['close'] # 提取海康威视收盘价
ningbo_bank_prices = stock_price_data.xs('002142.XSHE', level='order_book_id').sort_index()['close'] # 提取宁波银行收盘价
else: # 普通索引分支
stock_reset = stock_price_data.reset_index() # 重置索引
haikang_prices = stock_reset[stock_reset['order_book_id'] == '002415.XSHE'].set_index('date')['close'] # 筛选海康威视
ningbo_bank_prices = stock_reset[stock_reset['order_book_id'] == '002142.XSHE'].set_index('date')['close'] # 筛选宁波银行
# 对齐交易日期并计算日收益率
merged_prices = pd.concat([haikang_prices, ningbo_bank_prices], axis=1, join='inner').dropna() # 内连接对齐并删除缺失值
merged_prices.columns = ['haikang_close', 'ningbo_close'] # 重命名列
daily_returns = merged_prices.pct_change().dropna() # 计算日收益率
daily_returns.columns = ['X_haikang', 'Y_ningbo'] # X=海康威视收益率,Y=宁波银行收益率
sample_returns = daily_returns.iloc[-100:] # 取最近100个交易日作为样本
print(f'样本量: {len(sample_returns)} 个交易日') # 输出样本量
print(f'海康威视日均收益: {sample_returns["X_haikang"].mean():.6f}') # 海康威视平均日收益
print(f'宁波银行日均收益: {sample_returns["Y_ningbo"].mean():.6f}') # 宁波银行平均日收益
# 定义最优投资比例α的计算函数(马科维茨最小方差公式)
def compute_optimal_alpha(returns_data, sample_indices): # 定义计算最优α的函数
'''根据给定索引的收益率子集,计算使投资组合方差最小的α值'''
x_returns = returns_data.iloc[sample_indices, 0].values # 海康威视收益率子集
y_returns = returns_data.iloc[sample_indices, 1].values # 宁波银行收益率子集
variance_x = np.var(x_returns, ddof=1) # 海康威视收益率方差(无偏估计)
variance_y = np.var(y_returns, ddof=1) # 宁波银行收益率方差(无偏估计)
covariance_xy = np.cov(x_returns, y_returns, ddof=1)[0, 1] # 两资产协方差
optimal_alpha = (variance_y - covariance_xy) / (variance_x + variance_y - 2 * covariance_xy) # 马科维茨最优配比
return optimal_alpha # 返回最优投资比例
# 用完整样本计算α的点估计
observation_count = len(sample_returns) # 样本观测数
original_alpha_estimate = compute_optimal_alpha(sample_returns, range(observation_count)) # 原始样本的α估计
print(f'原始α估计值: {original_alpha_estimate:.4f}') # 输出α的点估计
print(f'解读: 约{original_alpha_estimate*100:.1f}%配置海康威视,{(1-original_alpha_estimate)*100:.1f}%配置宁波银行') # 配比解读
原始α估计值: 0.3322
解读: 约33.2%配置海康威视,66.8%配置宁波银行
解读Bootstrap实证结果
从数据中学到了什么?
- \(\hat{\alpha} \approx 0.33\):约1/3资金配置海康威视,2/3配置宁波银行
- 直觉:海康威视(科技股)波动率高于宁波银行(银行股),所以应少配
Bootstrap揭示的不确定性:
- 标准误 \(\text{SE}(\hat{\alpha}) \approx 0.07\)
- 95%近似置信区间:\(\hat{\alpha} \pm 1.96 \times \text{SE} \approx [0.19, 0.47]\)
- 含义:投资比例可能在19%到47%之间波动!
对基金经理的启示:
- 点估计 \(\hat{\alpha} = 0.33\) 看似精确
- 但Bootstrap告诉我们:仅凭100天数据,这个估计很不稳定
- 如果要更精确,需要更多数据或额外约束(如行业配置限制)
解读Bootstrap实证结果
从数据中学到了什么?
- \(\hat{\alpha} \approx 0.33\):约1/3资金配置海康威视,2/3配置宁波银行
- 直觉:海康威视(科技股)波动率高于宁波银行(银行股),所以应少配
Bootstrap揭示的不确定性:
- 标准误 \(\text{SE}(\hat{\alpha}) \approx 0.07\)
- 95%近似置信区间:\(\hat{\alpha} \pm 1.96 \times \text{SE} \approx [0.19, 0.47]\)
- 含义:投资比例可能在19%到47%之间波动!
对基金经理的启示:
- 点估计 \(\hat{\alpha} = 0.33\) 看似精确
- 但Bootstrap告诉我们:仅凭100天数据,这个估计很不稳定
- 如果要更精确,需要更多数据或额外约束(如行业配置限制)
1000次Bootstrap揭示α的不确定性
Code
np.random.seed(42) # 固定随机种子确保可复现
# 执行1000次Bootstrap重采样
bootstrap_iterations = 1000 # Bootstrap重复次数
bootstrap_alpha_values = [] # 存储每次Bootstrap的α估计
for _ in range(bootstrap_iterations): # 循环1000次
resampled_indices = np.random.choice(range(observation_count), size=observation_count, replace=True) # 有放回抽样100个索引
bootstrap_alpha = compute_optimal_alpha(sample_returns, resampled_indices) # 在重抽样数据上计算α
bootstrap_alpha_values.append(bootstrap_alpha) # 记录本次Bootstrap的α
bootstrap_alpha_values = np.array(bootstrap_alpha_values) # 转换为NumPy数组
bootstrap_se = np.std(bootstrap_alpha_values, ddof=1) # Bootstrap标准误 = α估计值们的标准差
print(f'Bootstrap标准误: {bootstrap_se:.4f}') # 输出标准误
# 生成模拟"真实"分布作为对比基准(教学用途:假设已知总体参数)
simulated_alpha_values = [] # 存储模拟分布的α
population_mean = sample_returns.mean() # 用样本均值近似总体均值
population_cov = sample_returns.cov() # 用样本协方差近似总体协方差
for _ in range(1000): # 从"已知总体"中模拟1000次抽样
simulated_data = np.random.multivariate_normal(population_mean, population_cov, observation_count) # 从多元正态分布抽取新样本
simulated_df = pd.DataFrame(simulated_data) # 转换为DataFrame
sim_alpha = compute_optimal_alpha(simulated_df, range(observation_count)) # 计算模拟样本的α
simulated_alpha_values.append(sim_alpha) # 追加记录
simulated_alpha_values = np.array(simulated_alpha_values) # 转换为NumPy数组
# 绘制三张对比图
fig, axes = plt.subplots(1, 3, figsize=(18, 5)) # 创建1行3列子图
# 左图:模拟的"真实"抽样分布
axes[0].hist(simulated_alpha_values, bins=40, alpha=0.7, color='#3498DB', edgecolor='black') # 绘制直方图
axes[0].axvline(np.mean(simulated_alpha_values), color='red', linestyle='--', label=f'均值: {np.mean(simulated_alpha_values):.3f}') # 标记均值
axes[0].set_xlabel('α', fontsize=12) # X轴标签
axes[0].set_title('模拟抽样分布\n(上帝视角基准)', fontsize=13) # 标题
axes[0].legend() # 图例
axes[0].grid(True, alpha=0.3) # 网格
# 中图:Bootstrap分布
axes[1].hist(bootstrap_alpha_values, bins=40, alpha=0.7, color='#E74C3C', edgecolor='black') # 绘制直方图
axes[1].axvline(original_alpha_estimate, color='blue', linewidth=2, label=f'原始估计: {original_alpha_estimate:.3f}') # 标记原始估计
axes[1].set_xlabel('α', fontsize=12) # X轴标签
axes[1].set_title(f'Bootstrap分布\n(SE = {bootstrap_se:.3f})', fontsize=13) # 标题
axes[1].legend() # 图例
axes[1].grid(True, alpha=0.3) # 网格
# 右图:箱线图对比
axes[2].boxplot([simulated_alpha_values, bootstrap_alpha_values], labels=['模拟分布', 'Bootstrap'], patch_artist=True, boxprops=dict(facecolor='lightblue')) # 箱线图
axes[2].set_title('分布对比', fontsize=13) # 标题
axes[2].grid(True, alpha=0.3, axis='y') # Y轴网格
plt.tight_layout() # 调整布局
plt.show() # 显示图表
Bootstrap置信区间的三种构造方法
| 正态近似法 |
\(\hat{\theta} \pm z_{\alpha/2} \cdot \text{SE}_B\) |
最简单 |
假设正态分布 |
| 百分位法 |
\([\hat{\theta}^*_{(\alpha/2)}, \hat{\theta}^*_{(1-\alpha/2)}]\) |
不假设正态 |
样本量要求高 |
| BCa法 |
偏差校正加速 |
最精确 |
计算复杂 |
百分位法的实现:
# 1000个Bootstrap估计值
ci_lower = np.percentile(bootstrap_alphas, 2.5)
ci_upper = np.percentile(bootstrap_alphas, 97.5)
BCa法(Bias-Corrected and Accelerated):
- 校正Bootstrap分布的偏斜和偏差
- 在小样本和非对称分布下更准确
- Python中的
scipy.stats.bootstrap 已内置BCa方法
Bootstrap重采样次数B如何确定?
B的选择指南:
| 标准误估计 |
B = 200-1000 |
SE收敛较快 |
| 95%置信区间 |
B ≥ 1000 |
尾部百分位需要更多样本 |
| 99%置信区间 |
B ≥ 5000 |
极端百分位需要更多样本 |
| 假设检验p值 |
B ≥ 10000 |
p值精度要求高 |
收敛性检验:
- 在不同B值下重复运行Bootstrap
- 如果SE估计不再显著变化,说明B已足够
- 实践经验:B = 1000 对大多数应用已足够
计算时间的权衡:
- \(B = 1000\),简单统计量:< 1秒
- \(B = 1000\),复杂模型(如随机森林):可能数小时
- 此时可考虑并行Bootstrap:各次重采样独立,天然支持并行计算
Bootstrap置信区间的三种构造方法
| 正态近似法 |
\(\hat{\theta} \pm z_{\alpha/2} \cdot \text{SE}_B\) |
最简单 |
假设正态分布 |
| 百分位法 |
\([\hat{\theta}^*_{(\alpha/2)}, \hat{\theta}^*_{(1-\alpha/2)}]\) |
不假设正态 |
样本量要求高 |
| BCa法 |
偏差校正加速 |
最精确 |
计算复杂 |
百分位法的实现:
# 1000个Bootstrap估计值
ci_lower = np.percentile(bootstrap_alphas, 2.5)
ci_upper = np.percentile(bootstrap_alphas, 97.5)
BCa法(Bias-Corrected and Accelerated):
- 校正Bootstrap分布的偏斜和偏差
- 在小样本和非对称分布下更准确
- Python中的
scipy.stats.bootstrap 已内置BCa方法
Bootstrap重采样次数B如何确定?
B的选择指南:
| 标准误估计 |
B = 200-1000 |
SE收敛较快 |
| 95%置信区间 |
B ≥ 1000 |
尾部百分位需要更多样本 |
| 99%置信区间 |
B ≥ 5000 |
极端百分位需要更多样本 |
| 假设检验p值 |
B ≥ 10000 |
p值精度要求高 |
收敛性检验:
- 在不同B值下重复运行Bootstrap
- 如果SE估计不再显著变化,说明B已足够
- 实践经验:B = 1000 对大多数应用已足够
计算时间的权衡:
- \(B = 1000\),简单统计量:< 1秒
- \(B = 1000\),复杂模型(如随机森林):可能数小时
- 此时可考虑并行Bootstrap:各次重采样独立,天然支持并行计算
为什么Bootstrap有效?63.2%定律
每个Bootstrap样本大约包含原始数据的63.2%独特观测。
数学推导:对于第 \(i\) 个观测点:
- 单次抽取中未被选中的概率:\(1 - \frac{1}{n}\)
- \(n\) 次抽取中始终未被选中:\(\left(1 - \frac{1}{n}\right)^n\)
- 当 \(n \to \infty\):\(\left(1 - \frac{1}{n}\right)^n \to \frac{1}{e} \approx 0.368\)
因此:
\[ \large{ P(\text{被选中}) = 1 - \left(1 - \frac{1}{n}\right)^n \approx 1 - \frac{1}{e} \approx 0.632 } \tag{18}\]
袋外数据(OOB):未被选中的约36.8%的观测可作为天然验证集,无需显式数据切分。
63.2%定律的数值验证
理论推导回顾:
\[ \large{ \lim_{n \to \infty}\left(1 - \frac{1}{n}\right)^n = \frac{1}{e} \approx 0.3679 } \]
不同样本量下的实际比例:
| 5 |
0.3277 |
0.6723 |
| 10 |
0.3487 |
0.6513 |
| 50 |
0.3642 |
0.6358 |
| 100 |
0.3660 |
0.6340 |
| 1000 |
0.3677 |
0.6323 |
| \(\infty\) |
0.3679 |
0.6321 |
即使 \(n\) 很小(如50),近似也已经很好了。
63.2%定律的数值验证
理论推导回顾:
\[ \large{ \lim_{n \to \infty}\left(1 - \frac{1}{n}\right)^n = \frac{1}{e} \approx 0.3679 } \]
不同样本量下的实际比例:
| 5 |
0.3277 |
0.6723 |
| 10 |
0.3487 |
0.6513 |
| 50 |
0.3642 |
0.6358 |
| 100 |
0.3660 |
0.6340 |
| 1000 |
0.3677 |
0.6323 |
| \(\infty\) |
0.3679 |
0.6321 |
即使 \(n\) 很小(如50),近似也已经很好了。
Bootstrap的适用范围与局限性
| 适用范围 |
几乎任何统计方法的标准误和置信区间 |
| 核心优势 |
不依赖强分布假设,实现简单 |
| 局限1:小样本 |
样本量过小时,经验分布不能很好代表总体 |
| 局限2:非iid数据 |
时间序列需用分块Bootstrap (Block Bootstrap) |
| 局限3:极端统计量 |
对极值统计量(如最大值)效果不佳 |
| 局限4:计算成本 |
大数据集 + 复杂模型时计算开销显著 |
金融领域特别注意:
- 股票收益率存在自相关和波动率聚集(GARCH效应)
- 标准Bootstrap假设iid可能不成立
- 建议使用移动分块Bootstrap或野生Bootstrap (Wild Bootstrap)
本章知识体系总结