经验误差,即训练误差,是指学习器在训练集上的误差。与之相对的,学习器在新样本上的误差被称为泛化误差。
学习器总是期望得到最小的泛化误差,但由于新样本的不可知性,我们只能通过在一定程度上降低经验误差的情况下来减小泛化误差。但经验误差也不是越小越好的,在训练集上越准确,经验误差越小,在多数情况下学习器的泛化能力就越有可能下降,反而使泛化误差大幅提高。这就是学习器的过拟合问题。
过拟合:学习器将训练样本中一些自身特点当作所有样本都有的一般性质进行了学习,使得模型的泛化能力下降。
导致过拟合的原因有很多种,最常见的原因是学习器的学习能力太强。过拟合的问题没有办法被彻底解决,只能被优化,如决策树剪枝、正则化等。
要获得比较好的学习器参数,就需要对学习器的泛化能力进行评估,选择泛化误差最小的模型。但因为过拟合的存在,我们不能用经验误差作为标准去推断泛化误差的大小,必须要使用训练集以外的数据作为新样本测试模型的泛化误差。
我们往往只有一个数据集,既要进行训练,又要进行测试,应该怎么做呢?直观上我们应该将数据集划分成训练集和测试集两个部分。那么转化为下面的问题:假设有一个包含m个样本的数据集 D={(x1,y1),(x2,y2),...,(x3,y3)} D = { ( x 1 , y 1 ) , ( x 2 , y 2 ) , . . . , ( x 3 , y 3 ) } ,对数据集进行怎样的划分才能使训练集能尽可能把模型训练好,并且测试集对模型的评估尽可能准确呢?
留出法将数据集D划分为两个互斥集合,其中一个作为训练集S,另一个作为测试集T。在进行训练集和测试集划分的过程中,要注意划分的过程要尽可能保持数据分布在两个集上一致。从采样的角度来说,应该使用分层采样。
划分的过程本身会给训练数据带来不稳定性,因此在使用留出法进行评估时,需要多次随机划分,重复进行实验,将每次实验的评估结果的平均值作为模型的评估结果。
进行划分时,一般取数据集的2/3~4/5的样本作为训练集,通常保留30%的数据作为测试集。
交叉验证法将数据集D划分为k个大小相似的互斥子集,每个子集的数据分布尽可能一致,即如留出法一样使用分层采样的方式进行划分。在进行评估时,每次取其中k-1个子集的并集作为训练集,将剩下的子集作为测试集,最终返回k次测试结果的均值。
交叉验证的稳定性和保真性很大程度上取决于k的取值,因此交叉验证法又被称为k-fold cross validation,k折交叉验证。k最常见的取值为10,即把数据集分为10个子集。
与留出法相似的是,子集在划分的过程中会引入不确定性,因此同样需要进行多次随机划分。如常见的10次10折交叉验证需要进行10*10次实验。
当k=m时,是交叉验证法的特例留一法(Leave-One-Out)。将包含m个样本的数据集D划分为m个互斥子集,有且仅有一种划分方法,即每个子集包含1个样本。留一法在训练的结果上更接近于直接将数据集全部用来训练的结果,其评估结果往往被认为是比较准确的。但留一发在面对大数据集时的性能是极其低的,且留一法的评估结果的准确性也未必远高于其他评估方法。
留出法和交叉验证法在训练集的数量上都小于数据集 D D ,这可能会引入由于规模不同而产生的估计偏差;而留一法的计算复杂度太高,在数据集比较大的时候会消耗大量的计算性能。
自助法是一种以自助采样法为基础的估计方法。自助采样,即可放回采样、可重复采样。使用自助采样法获取数据集 D′ D ′ 的过程如下:每次随机从 D D 中取出一个样本,将其复制到 D′ D ′ 中,然后将该样本放回 D D 中,重复执行m次。可以计算得到,某一个样本在一次采样中没有被取到的概率是 1−1m 1 − 1 m ,在完整自助采样结束后,没有被取到的概率是 (1−1m)m ( 1 − 1 m ) m ,这是一个递增序列,取极限得到
即通过自助采样法,一个样本没有被取到的概率最大为0.368,大约占总样本数的1/3。我们将自助采样得到的数据集 D′ D ′ 作为训练集,没有在 D′ D ′ 中出现的 D−D′ D − D ′ 作为测试集。这样估计的结果也叫做“包外估计”。
自助法适用于数据集比较小、难以有效划分训练集和测试集的时候使用;能够生成多个训练集,在集成训练时比较有效。但自助法在采样过程中与原始数据集的数据分布不一致,引入了估计偏差,因此在数据量不太小的时候,留出法和交叉验证法更加好一些。
均方误差是回归任务中最常用的性能度量,是每个样本的真实值与预测值之间差的平方的平均数:
准确率和召回率是分类任务中常用的两个度量。
预测正例 | 预测负例 | |
---|---|---|
真实正例 | 真正例TP | 假负例FN |
真实负例 | 假正例FP | 真负例TN |
上表是混淆矩阵,其中,预测正确的正例即为真正例True Positive,预测为正例实际为负例则为假正例False Positive;同理有假负例False Negative和真负例True Negative。通过混淆矩阵,用TP、FP、FN、TN来表示准确率和召回率的公式如下:
只使用准确率一项往往是不能代表模型的性能的。考虑以下场景:我们要从一堆水果中尽可能多地挑出好的水果(正例),其中坏掉的水果(负例)比较多;在准确率较高的前提下,可能会发生这种情况:模型将大部分水果都预测为坏水果。在这种情况下,模型其实并没有达到我们的要求:尽管大部分预测都准确了,但没有挑出最多的好水果。这种场景下就要引入召回率这一性能度量方式,使模型能够尽可能多的选出正确的正例。
在大部分场景下,准确率和召回率呈现负相关。
通常来说,除非某个模型在准确率和召回率上都优于另一个模型,否则很难衡量两个模型到底哪个模型更好。因此引入了一个由准确率和召回率计算得到的度量方式F1度量。F1度量是准确率和召回率的调和平均数:
当我们需要在准确率和召回率之间有所倾向时,可以使用F1度量的一般形式 Fβ F β :
其中,当 β<1 β < 1 时,更倾向于召回率;当 β>1 β > 1 时,更倾向于准确率;当 β=1 β = 1 时,退化为F1值。
当我们对一个模型进行多次训练、测试,或是在多个数据集上进行训练、测试,就会得到多个混淆矩阵,希望能够在n个混淆矩阵上综合度量模型的性能。
宏度量是在每个混淆矩阵上分别计算出准确率和召回率,再计算整体的F1值:
微度量是先将所有混淆矩阵的对应元素进行平均,再计算整体的准确率、召回率和F1值:
当模型发生欠拟合时,模型未能很好的学习到数据集的特征,在进行预测时偏差主导了泛化误差;当模型发生过拟合时,模型学习了一般性质以外的数据集自身特点,在进行预测时方差主导了泛化误差。
使用留出法,对简单线性回归数据集进行划分,并对测试集计算均方误差
import numpy as np
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error
from sklearn.model_selection import train_test_split
# 生成y=2x+1直线附近的随机200个点
rng=np.random.RandomState(42)
x=10*rng.rand(200)
y=2*x-1+rng.randn(200)
X=x[:,np.newaxis]
#使用留出法将数据集划分为训练集和测试集,训练集为数据集的70%
x_train,x_test,y_train,y_test=train_test_split(X,y,train_size=0.7)
#训练一个线性回归模型
model=LinearRegression(fit_intercept=True)
model.fit(x_train,y_train)
#使用训练好的模型预测测试集的数据
y_pred=model.predict(x_test)
#计算测试集预测结果的均方误差
error=mean_squared_error(y_test,y_pred)
model.coef_ #斜率
model.intercept_ #截距
error #均方误差
使用留出法对iris数据集使用决策树模型进行训练和测试
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report
from sklearn.datasets import load_iris
import numpy as np
#引入iris数据集
X,y=load_iris(return_X_y=True)
#使用留出法对数据集进行切分
x_train,x_test,y_train,y_test=train_test_split(X,y,train_size=0.7)
#训练一个决策树分类模型
model=DecisionTreeClassifier()
model.fit(x_train,y_train)
#使用训练好的模型预测测试集的数据
y_pred=model.predict(x_test)
#使用classification_report打印出每一个类的准确率、召回率和F1值
print(classification_report(y_test,y_pred))
# precision recall f1-score support
#
# 0 1.00 1.00 1.00 15
# 1 0.94 0.94 0.94 18
# 2 0.92 0.92 0.92 12
#
#avg / total 0.96 0.96 0.96 45
使用交叉验证法,可以直接使用cross_val_score进行训练,不再需要手动切分并一一训练了:
from sklearn.model_selection import cross_val_score
scores=cross_val_score(model,X,y,cv=5)