偏差-方差分解与集成学习

回归分析中,经常需要评估模型的性能。一个重要的指标是期望均方误差(MSE),它可以分解为偏差、方差和噪声三个部分。偏差衡量模型预测与最佳可能模型(贝叶斯模型)预测之间的平均差异。方差衡量在相同问题的不同随机实例上拟合模型时预测的变异性。噪声则衡量数据本身变化导致的不可减少的误差部分。

左侧的图表展示了单个决策树在随机数据集上的训练结果。图中深红色曲线表示单个决策树的预测结果,而浅红色曲线则表示其他不同随机实例上训练的决策树的预测结果。直观上,方差对应于个体估计器预测的宽度,方差越大,对训练集的微小变化越敏感。偏差对应于估计器的平均预测与最佳可能模型之间的差异。在这个例子中,可以看到偏差非常低,而方差很大。

左侧的第二个图表显示了单个决策树的期望均方误差的逐点分解。它证实了偏差项很低,而方差项很大。它还展示了误差的噪声部分,如预期的那样,似乎是恒定的,大约为0.01。

右侧的图表对应于使用决策树集成的Bagging方法。在这两个图表中,可以看到偏差项比之前的情况要大。在右上角的图表中,平均预测与最佳可能模型之间的差异更大。在右下角的图表中,偏差曲线也略高于左侧的图表。然而,在方差方面,预测的光束更窄,这表明方差更低。确实,正如右下角的图表所确认的,方差项比单个决策树要低。总的来说,偏差-方差分解不再相同。Bagging方法的权衡更好:对数据集的自助复制拟合几个决策树略微增加了偏差项,但允许更大程度地减少方差,从而降低了整体均方误差。脚本输出也证实了这种直觉。Bagging集成的总误差低于单个决策树的总误差,这种差异主要源于方差的降低。

为了进一步了解偏差-方差分解,可以参考《统计学习元素》一书的第7.3节。

代码示例

以下是用于生成上述分析的Python脚本。该脚本使用matplotlib、numpy和sklearn库来演示单个决策树与Bagging集成在偏差-方差分解上的差异。

import matplotlib.pyplot as plt import numpy as np from sklearn.ensemble import BaggingRegressor from sklearn.tree import DecisionTreeRegressor # 设置 n_repeat = 50 # 计算期望的迭代次数 n_train = 50 # 训练集的大小 n_test = 1000 # 测试集的大小 noise = 0.1 # 噪声的标准差 np.random.seed(0) # 用于探索其他估计器的偏差-方差分解 estimators = [ ("Tree", DecisionTreeRegressor()), ("Bagging(Tree)", BaggingRegressor(DecisionTreeRegressor())), ] n_estimators = len(estimators) # 生成数据 def f(x): x = x.ravel() return np.exp(-(x**2)) + 1.5 * np.exp(-((x-2)**2)) def generate(n_samples, noise, n_repeat=1): X = np.random.rand(n_samples) * 10 - 5 X = np.sort(X) if n_repeat == 1: y = f(X) + np.random.normal(0.0, noise, n_samples) else: y = np.zeros((n_samples, n_repeat)) for i in range(n_repeat): y[:, i] = f(X) + np.random.normal(0.0, noise, n_samples) X = X.reshape((n_samples, 1)) return X, y X_train = [] y_train = [] for i in range(n_repeat): X, y = generate(n_samples=n_train, noise=noise) X_train.append(X) y_train.append(y) X_test, y_test = generate(n_samples=n_test, noise=noise, n_repeat=n_repeat) plt.figure(figsize=(10, 8)) # 循环比较估计器 for n, (name, estimator) in enumerate(estimators): # 计算预测 y_predict = np.zeros((n_test, n_repeat)) for i in range(n_repeat): estimator.fit(X_train[i], y_train[i]) y_predict[:, i] = estimator.predict(X_test) # 均方误差的偏差^2 + 方差 + 噪声分解 y_error = np.zeros(n_test) for i in range(n_repeat): for j in range(n_repeat): y_error += (y_test[:, j] - y_predict[:, i])**2 y_error /= n_repeat * n_repeat y_noise = np.var(y_test, axis=1) y_bias = (f(X_test) - np.mean(y_predict, axis=1))**2 y_var = np.var(y_predict, axis=1) print("{0}: {1:.4f} (error) = {2:.4f} (bias^2) + {3:.4f} (var) + {4:.4f} (noise)".format(name, np.mean(y_error), np.mean(y_bias), np.mean(y_var), np.mean(y_noise))) # 绘制图表 plt.subplot(2, n_estimators, n + 1) plt.plot(X_test, f(X_test), "b", label="$f(x)$") plt.plot(X_train[0], y_train[0], ".b", label="LS ~ $y = f(x)+noise$") for i in range(n_repeat): if i == 0: plt.plot(X_test, y_predict[:, i], "r", label=r"$\^y(x)$") else: plt.plot(X_test, y_predict[:, i], "r", alpha=0.05) plt.plot(X_test, np.mean(y_predict, axis=1), "c", label=r"$\mathbb{E}_{LS}\^y(x)$") plt.xlim([-5, 5]) plt.title(name) if n == n_estimators - 1: plt.legend(loc=(1.1, 0.5)) plt.subplot(2, n_estimators, n_estimators + n + 1) plt.plot(X_test, y_error, "r", label="$error(x)$") plt.plot(X_test, y_bias, "b", label="$bias^2(x)$"), plt.plot(X_test, y_var, "g", label="$variance(x)$"), plt.plot(X_test, y_noise, "c", label="$noise(x)$") plt.xlim([-5, 5]) plt.ylim([0, 0.1]) if n == n_estimators - 1: plt.legend(loc=(1.1, 0.5)) plt.subplots_adjust(right=0.75) plt.show()
沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485