文章目录
相关文章:
1. 模型验证
模型验证
:(Model Validation)就是在选择模型和超参数之后,通过对训练数据进行学习,对比已知数据的预测值与实际值的差异。[1]
我们将首先通过一个简单方法实现模型验证,并告诉你为什么那样做行不通。之后,介绍如何用留出集(Holdout Set)与交叉验证(Cross-Validation)实现更可靠的模型验证。
1.1 错误的模型验证方法
我们将使用鸢尾花数据来演示一个简单的模型验证方法。
首先加载数据:
from sklearn.datasets import load_iris
iris = load_iris()
X = iris.data
y = iris.target
然后选择模型和超参数。这里使用一个 k k k近邻分类器,超参数为 n_neighbors=1。这是一个非常简单直观的模型,新数据的标签与其最接近的训练数据的标签相同:
from sklearn.neighbors import KNeighborsClassifier
model = KNeighborsClassifier(n_neighbors=1)
然后训练模型,并由他来预测已知标签的数据:
model.fit(X,y)
y_model = model.predict(X)
最后,计算模型的准确率:
from sklearn.metrics import accuracy_score
accuracy_score(y, y_model)
1.0
准确得分是1.0,也就是模型识别标签的正确率是 100% !但是这样测量的准确率可靠吗?我们真的有一个在任何时候准确率都是 100% 的模型吗?
你可能已经猜到了,答案是否定的。其实这个方法又个根本缺陷:它用同一套数据训练和评估模型
。另外,最近邻模型是一种与距离相关的评估器,只会简单地存储训练数据,然后把新数据与储存的已知数据进行对比来预测标签。在理想情况下,模型的准确率总是 100% 。
1.2 正确的模型验证方法
1.2.1 留出集
那么怎么样才能验证模型呢?其实留出集
(Holdout Set)可以更好地评估模型性能,也就是说,将数据分为两部分,一部分用来训练模型,另一部分用来验证模型性能。在Sklearn里用train_test_split
(Sklearn官方文档)工具就可以实现:
from sklearn.datasets import load_iris
iris = load_iris()
train_data = iris.data
test_data = iris.target
# 一半数据做为训练集,一半数据作为测试集(默认test_size = 0.75)
from sklearn.model_selection import train_test_split
X1, X2, y1, y2 = train_test_split(X, y, random_state=0, train_size=0.5)
# 用模型拟合训练数据
model.fit(X1, y1)
# 利用训练好的模型进行预测
y2_model = model.predict(X2)
# 在测试集中评估模型准确率
from sklearn.metrics import accuracy_score
accuracy_score(y2, y2_model)
0.9066666666666666
这样就可以获得更合理的结果了:最近邻分类器在这份留出集上的准确率是 90.67%。 这里的留出集类似于新数据,因为模型之前没有“接触”过它们。
1.2.2 交叉验证
用留出集进行模型验证有一个缺点,就是模型失去了一部分训练机会,在上面的模型里,有一半的数据没有为模型训练作出贡献。这显然不是最优解,而且可能还会出现问题——尤其是在数据集规模比较小的时候。
解决这个问题的方法是交叉验证
(Cross Validation),也就是做一组拟合,让数据的每个子集即是训练集,又是验证集。如下图所示,对数据进行两轮交叉验证:

这里进行了两轮验证实验,轮流用一组数据作为留出集。所以我们可以对1.2.1的例子实现交叉验证:
y2_model = model.fit(X1, y1).predict(X2)
y1_model = model.fit(X2, y2).predict(X1)
accuracy_score(y1, y1_model), accuracy_score(y2, y2_model)
(0.96, 0.9066666666666666)
这样就得到了两个准确率,将二者结合(例如求均值)获得一个更为准确的模型总性能,这种形式的交叉验证被称为两轮交叉验证——将数据集分为两个子集,依次将每个子集作为验证集。
因此,也可以利用Sklearn的交叉验证函数cross_val_score
(Sklearn官方文档)实现两轮交叉验证
from sklearn.model_selection import cross_val_score
cross_val_score(model, X, y, cv=2)
array([0.94666667, 0.94666667])
交叉验证不是一种构建可以应用于新数据的模型的方法
:交叉验证不会返回一个模型。在调用cross_val_score
时,内部会构建多个模型,但交叉验证的目的只是评估给定算法在特定数据集上训练后的泛化性能的好坏。(若采用交叉验证来评价模型,则可以利用所有样本来训练模型,而无需单独留出验证集)
1.2.3 K折交叉验证
K折交叉验证(KFold Cross Validation)及在交叉验证的基础上,将数据划分为 k k k组(cv=k),其中 k − 1 k-1 k−1组进行训练,剩下1组作为验证集,重复进行 k k k次,如下图所示:

我们可以将1.2.2的数据分为5组,进行5轮交叉检验:
from sklearn.model_selection import cross_val_score
cross_val_score(model, X, y, cv=5)
array([0.96666667, 0.96666667, 0.93333333, 0.93333333, 1. ])
1.2.4 留一法 LOO
当我们交叉验证的轮数与样本数相同时,即每次只有一个样本做测试,其他样本全用于训练,这种交叉验证类型被称为留一法(LOO, Leave One Out),如下所示:
from sklearn.model_selection import LeaveOneOut
scores = cross_val_score(model, X, y, cv=LeaveOneOut())
print(scores)
scores.mean()
[1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.
1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.
1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 0. 1.
0. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 0. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.
1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 0. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 0.
1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 0. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.
1. 1. 1. 1. 1. 1.]
0.96
2. 偏差-方差
2.1 泛化误差、偏差及方差
泛化误差
的含义,其实就是想看看训练后的模型是否具有代表性。我们使用偏差(bias)和方差(variance)来描述。
偏差
是什么?给定了一些新的样本,我们使用训练好的模型对这个新样本进行估值,这个估值与真实值的差距就是偏差。
方差
是什么?在不同的训练集上,即使是同一模型我们可能也会得到不同的参数,那么不同训练集上得到的假设函数对新样本的所做出的估值就是不同的。我们用这些不同估值的期望作为最终这个模型对新样本的估值,而方差就是估值与不同训练集得到的估值之间的离散程度。
这和我们统计上的期望与方差是类似的,可以对比来看。我们希望最终的估值与实际值相差不大,而且得到的模型也要相对稳定,这是就可以说该模型的通用型比较强,也就是泛化。[2]

2.2 泛化误差与偏差及方差的关系
我们刚才说偏差和方差可以衡量一个模型是否具有代表性,那么当我们在验证集上得到了泛化误差后怎么评价这个模型呢?我们来看一下泛化误差的构成:
泛 化 误 差 = 偏 差 2 + 方 差 + 噪 声 2 泛化误差=偏差^2+方差+噪声^2 泛化误差=偏差2+方差+噪声2
为了推导出这个公式,首先定义几个概念:
-
在训练集 d d d上,我们训练后的模型为 f d ( x ) f_d(x) fd(x)
-
该模型对数据 x x x的预测输出: f ( x ) ˉ = E d [ f d ( x ) ] \bar{f(x)}=E_d[f_d(x)] f(x)ˉ=Ed[fd(x)]
-
验证集样本的真实值: y y y,标签值: y d y_d yd
-
噪声为样本的标签值与真实值的出入: ε = y − y d \varepsilon=y-y_d ε=y−yd,服从均值为0的高斯分布 ε ∼ N ( 0 , σ 2 ) \varepsilon \thicksim N(0, \sigma^2) ε∼N(0,σ2)
-
偏差为预测输出与样本标签的差值: b i a s = y − f ( x ) ˉ bias=y-\bar{f(x)} bias=y−f(x)ˉ
-
方差为预测输出与不同测试集差的离散程度: v a r = E d [ ( f d ( x ) − f ( x ) ˉ ) 2 ] var=E_d[(f_d(x)-\bar{f(x)})^2] var=E