bias_variance_decomp: 分类和回归损失的偏差-方差分解

机器学习算法的偏差-方差分解对于各种损失函数。

> `from mlxtend.evaluate import bias_variance_decomp`

概述

研究人员经常使用偏差方差或“偏差-方差权衡”这两个术语来描述模型的性能——例如,你可能会看到有人在演讲、书籍或文章中提到一个模型具有高方差或高偏差。那么,这意味着什么呢?通常,我们可以说“高方差”与过拟合成正比,而“高偏差”与欠拟合成正比。

那么,我们为什么要进行这种偏差-方差分解呢?将损失分解为偏差和方差有助于我们理解学习算法,因为这些概念与欠拟合和过拟合相关联。

为了使用更正式的偏差和方差术语,假设我们有一个某些参数或函数$\hat{\theta}$的点估计。然后,偏差通常定义为估计值的期望与我们要估计的参数之间的差:

$$ \text{Bias} = E[\hat{\theta}] - \theta. $$

如果偏差大于零,我们还说这个估计量是正偏的;如果偏差小于零,估计量是负偏的;如果偏差恰好为零,估计量是无偏的。类似地,我们将方差定义为估计量的平方的期望值减去估计量的期望值的平方之间的差:

$$ \text{Var}(\hat{\theta}) = E\big[\hat{\theta}^2\big] - \bigg(E\big[\hat{\theta}\big]\bigg)^2. $$

请注意,在本次讲座的上下文中,写出方差的另一种形式会更方便:

$$ \text{Var}(\hat{\theta}) = E[(E[{\hat{\theta}}] - \hat{\theta})^2]. $$

为了更好地说明这个概念在机器学习中的应用……

假设有一个未知的目标函数或“真实函数”,我们想要对其进行逼近。现在,假设我们有不同的训练集,这些训练集是从一个定义为“真实函数+噪声”的未知分布中抽取的。以下图表显示了不同的线性回归模型,每个模型都拟合于一个不同的训练集。这些假设中没有一个能够很好地逼近真实函数,除了在两个点(大约x=-10和x=6)处。在这里,我们可以说偏差很大,因为真实值与预测值之间的差异在平均上(这里,平均是指“训练集的期望”而不是“训练集中示例的期望”)是很大的:

下一张图显示了不同的未修剪决策树模型,每个模型都拟合于一个不同的训练集。请注意,这些假设与训练数据非常接近。然而,如果我们考虑训练集的期望,平均假设将完美拟合真实函数(假设噪声无偏且期望值为0)。如我们所见,方差非常大,因为在平均的情况下,预测值与预测值的期望值差异很大:

平方损失的偏差-方差分解

我们可以将像平方损失这样的损失函数分解为三个部分:方差、偏差和噪声项(0-1损失的分解也是如此)。但是,出于简单考虑,我们将忽略噪声项。

在我们引入0-1损失的偏差-方差分解之前,让我们首先通过平方损失的分解作为一个简单的热身练习来熟悉整体概念。

上一部分已经列出了偏差和方差的常见正式定义,但为了方便起见,我们再次定义它们:

$$ \text{Bias}(\hat{\theta}) = E[\hat{\theta}] - \theta, \quad \text{Var}(\hat{\theta}) = E[(E[{\hat{\theta}}] - \hat{\theta})^2]. $$

请回忆在这些机器学习讲座(笔记)的上下文中,我们已定义

注意,除非另有说明,期望是针对训练集的!

要开始平方误差损失的偏差和方差分解,让我们进行一些代数运算,即添加和减去$\hat{y}$的期望值,然后使用二次公式展开这个表达式 $(a+b)^2 = a^2 + b^2 + 2ab)$:

$$ \begin{equation} \begin{split} S = (y - \hat{y})^2 \ (y - \hat{y})^2 &= (y - E[{\hat{y}}] + E[{\hat{y}}] - \hat{y})^2 \ &= (y-E[{\hat{y}}])^2 + (E[{\hat{y}}] - y)^2 + 2(y - E[\hat{y}])(E[\hat{y}] - \hat{y}). \end{split} \end{equation} $$

接下来,我们只需对两边使用期望值,我们就完成了:

$$ \begin{align} E[S] &= E[(y - \hat{y})^2] \ E[(y - \hat{y})^2] &= (y-E[{\hat{y}}])^2 + E[(E[{\hat{y}}] - \hat{y})^2]\ &= \text{[Bias]}^2 + \text{Variance}. \end{align} $$

你可能想知道当我们使用期望时“$2ab$”项($2(y - E[\hat{y}])(E[\hat{y}] - \hat{y})$)发生了什么。事实证明,它的期望值评估为零,因此在方程中消失,证明如下:

$$ \begin{align} E[2(y - E[{\hat{y}}])(E[{\hat{y}}] - \hat{y})] &= 2 E[(y - E[{\hat{y}}])(E[{\hat{y}}] - \hat{y})] \ &= 2(y - E[{\hat{y}}])E[(E[{\hat{y}}] - \hat{y})] \ &= 2(y - E[{\hat{y}}])(E[E[{\hat{y}}]] - E[\hat{y}])\ &= 2(y - E[{\hat{y}}])(E[{\hat{y}}] - E[{\hat{y}}]) \ &= 0. \end{align} $$

因此,这是平方误差损失对偏差和方差的经典分解。下一部分将讨论一些已提出的针对我们常用的分类准确性或错误的0-1损失的分解方法。

以下图是偏差和方差与训练误差和泛化误差的关系的示意图——高方差如何与过拟合相关,以及高偏差如何与欠拟合相关:

0-1损失的偏差-方差分解

请注意,将0-1损失分解为偏差和方差分量并不像平方误差损失那样简便。引用著名的机器学习研究者和华盛顿大学教授Pedro Domingos的话:

“几位作者提出了与零一损失相关的偏差-方差分解(Kong & Dietterich,1995;Breiman,1996b;Kohavi & Wolpert,1996;Tibshirani,1996;Friedman,1997)。然而,这些分解各有显著不足。” [1]

实际上,这个引用的论文在这一点上可能提供了最直观和最一般的表述。但是,为了简单起见,我们将首先讨论Kong & Dietterich关于0-1损失分解的表述 [2],这与Domingos的表述相同,但忽略了噪声项(出于简单考虑)。

下表总结了我们在平方损失中使用的相关术语与0-1损失相关。请记住,0-1损失$L$的值为0,如果类标签被正确预测;否则值为1。平方误差损失的主要预测值是简单地对预测值$E[\hat{y}]$求平均(期望是针对训练集的),对于0-1损失,Kong & Dietterich和Domingos将其定义为众数。也就是说,如果模型预测标签大于50%的时间为1(考虑所有可能的训练集),那么主要预测值为1,其他情况为0。

- 平方损失 0-1损失
单一损失 $(y - \hat{y})^2$ $L(y, \hat{y})$
期望损失 $E[(y - \hat{y})^2]$ $E[L(y, \hat{y})]$
主要预测 $E[\hat{y}]$ 平均(平均值) 众数
偏差$^2$ $(y-E[{\hat{y}}])^2$ $L(y, E[\hat{y}])$
方差 $E[(E[{\hat{y}}] - \hat{y})^2]$ $E[L(\hat{y}, E[\hat{y}])]$

因此,使用众数作为0-1损失的主要预测的结果是,如果主要预测与真实标签$y$不一致,则偏差为1,否则为0:

$$ Bias = \begin{cases} 1 \text{ 如果 } y \neq E[{\hat{y}}], \ 0 \text{ 否则}. \end{cases} $$

0-1损失的方差定义为预测标签与主要预测不匹配的概率:

$$ Variance = P(\hat{y} \neq E[\hat{{y}}]). $$

接下来,让我们看一下当偏差为0时损失发生了什么。根据损失的一般定义,损失 = 偏差 + 方差,如果偏差为0,那么我们将损失定义为方差:

$$ Loss = 0 + Variance = Loss = P(\hat{y} \neq y) = Variance = P(\hat{y} \neq E[\hat{{y}}]). $$

换句话说,如果一个模型具有零偏差,那么它的损失完全由方差定义,这在我们将方差视为与过拟合成正比的上下文中是直观的。

更令人惊讶的情况是,如果偏差等于1。如果偏差等于1,正如Pedro Domingos所解释的,增加方差可以减少损失,这是一种有趣的观察。通过首先将0-1损失函数重写为

$$ Loss = P(\hat{y} \neq y) = 1 - P(\hat{y} = y). $$

(注意,我们还没有做任何新的事情。)现在,如果我们查看之前的偏差方程,如果偏差为1,我们有 $ y \neq E[{\hat{y}}]$。如果$y$不等于主要预测,但是$y$也等于$\hat{y}$,那么$\hat{y}$必须等于主要预测。通过使用“逆”(“1减去”),我们可以将损失写为

$$ Loss = P(\hat{y} \neq y) = 1 - P(\hat{y} = y) = 1 - P(\hat{y} \neq E[{\hat{y}}]). $$

由于偏差为1,因此如果偏差为1,则损失由“损失 = 偏差 - 方差”定义(或“损失 = 1 - 方差”)。这在一开始可能会显得相当反直觉,但Kong、Dietterich和Domingos提供的解释是,如果模型具有非常高的偏差,以至于它的主要预测总是错误的,增加方差可能是有益的,因为增加方差将推动决策边界,这可能会导致偶然地获得一些正确的预测。换句话说,对于高偏差的情形,增加方差可以改善(减少)损失!

参考文献

示例 1 -- 决策树分类器的偏差方差分解

from mlxtend.evaluate import bias_variance_decomp
from sklearn.tree import DecisionTreeClassifier
from mlxtend.data import iris_data
from sklearn.model_selection import train_test_split


X, y = iris_data()
X_train, X_test, y_train, y_test = train_test_split(X, y,
                                                    test_size=0.3,
                                                    random_state=123,
                                                    shuffle=True,
                                                    stratify=y)



tree = DecisionTreeClassifier(random_state=123)

avg_expected_loss, avg_bias, avg_var = bias_variance_decomp(
        tree, X_train, y_train, X_test, y_test, 
        loss='0-1_loss',
        random_seed=123)

print('Average expected loss: %.3f' % avg_expected_loss)
print('Average bias: %.3f' % avg_bias)
print('Average variance: %.3f' % avg_var)

Average expected loss: 0.062
Average bias: 0.022
Average variance: 0.040

为了比较,bagging分类器的偏差-方差分解,直观上应该比单棵决策树具有更低的方差:

from sklearn.ensemble import BaggingClassifier

tree = DecisionTreeClassifier(random_state=123)
bag = BaggingClassifier(base_estimator=tree,
                        n_estimators=100,
                        random_state=123)

avg_expected_loss, avg_bias, avg_var = bias_variance_decomp(
        bag, X_train, y_train, X_test, y_test, 
        loss='0-1_loss',
        random_seed=123)

print('Average expected loss: %.3f' % avg_expected_loss)
print('Average bias: %.3f' % avg_bias)
print('Average variance: %.3f' % avg_var)

Average expected loss: 0.048
Average bias: 0.022
Average variance: 0.026

示例 2 -- 决策树回归器的偏差方差分解

from mlxtend.evaluate import bias_variance_decomp
from sklearn.tree import DecisionTreeRegressor
from mlxtend.data import boston_housing_data
from sklearn.model_selection import train_test_split


X, y = boston_housing_data()
X_train, X_test, y_train, y_test = train_test_split(X, y,
                                                    test_size=0.3,
                                                    random_state=123,
                                                    shuffle=True)



tree = DecisionTreeRegressor(random_state=123)

avg_expected_loss, avg_bias, avg_var = bias_variance_decomp(
        tree, X_train, y_train, X_test, y_test, 
        loss='mse',
        random_seed=123)

print('Average expected loss: %.3f' % avg_expected_loss)
print('Average bias: %.3f' % avg_bias)
print('Average variance: %.3f' % avg_var)

Average expected loss: 31.536
Average bias: 14.096
Average variance: 17.440

为了比较,下面展示了一个袋装回归器的偏差-方差分解,这直观上应该比单个决策树具有更低的方差:

from sklearn.ensemble import BaggingRegressor

tree = DecisionTreeRegressor(random_state=123)
bag = BaggingRegressor(base_estimator=tree,
                       n_estimators=100,
                       random_state=123)

avg_expected_loss, avg_bias, avg_var = bias_variance_decomp(
        bag, X_train, y_train, X_test, y_test, 
        loss='mse',
        random_seed=123)

print('Average expected loss: %.3f' % avg_expected_loss)
print('Average bias: %.3f' % avg_bias)
print('Average variance: %.3f' % avg_var)

Average expected loss: 18.620
Average bias: 15.461
Average variance: 3.159

示例 3 -- TensorFlow/Keras 支持

自 mlxtend v0.18.0 以来,bias_variance_decomp 现在支持 Keras 模型。请注意,原始模型在每一轮中被重置(在将其重新拟合到自助样本之前)。

from mlxtend.evaluate import bias_variance_decomp
from mlxtend.data import boston_housing_data
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error
import tensorflow as tf
import numpy as np


np.random.seed(1)
tf.random.set_seed(1)


X, y = boston_housing_data()
X_train, X_test, y_train, y_test = train_test_split(X, y,
                                                    test_size=0.3,
                                                    random_state=123,
                                                    shuffle=True)


model = tf.keras.Sequential([
    tf.keras.layers.Dense(32, activation=tf.nn.relu),
    tf.keras.layers.Dense(1)
  ])

optimizer = tf.keras.optimizers.Adam()
model.compile(loss='mean_squared_error', optimizer=optimizer)

model.fit(X_train, y_train, epochs=100, verbose=0)

mean_squared_error(model.predict(X_test), y_test)

32.69300595184836

请注意,强烈建议使用与原始训练集相同的训练周期数,以确保收敛:

np.random.seed(1)
tf.random.set_seed(1)


avg_expected_loss, avg_bias, avg_var = bias_variance_decomp(
        model, X_train, y_train, X_test, y_test, 
        loss='mse',
        num_rounds=100,
        random_seed=123,
        epochs=200, # 拟合参数
        verbose=0) # 拟合参数


print('Average expected loss: %.3f' % avg_expected_loss)
print('Average bias: %.3f' % avg_bias)
print('Average variance: %.3f' % avg_var)

Average expected loss: 32.740
Average bias: 27.474
Average variance: 5.265

API

bias_variance_decomp(estimator, X_train, y_train, X_test, y_test, loss='0-1_loss', num_rounds=200, random_seed=None, fit_params)

estimator : object A classifier or regressor object or class implementing both a fit and predict method similar to the scikit-learn API.

Returns

Examples

For usage examples, please see https://rasbt.github.io/mlxtend/user_guide/evaluate/bias_variance_decomp/