文章目录
模型评估与改进
本章学习模型评估和参数选择。重点介绍监督方法,因为无监督学习中模型的评估与选择是一个非常定性的过程。
为了评估我们的监督模型,使用train_test_split函数将数据集划分为训练集和测试集,在训练集上调用fit方法来构建模型,并在测试集上用score方法来评估模型。
对于分类问题而言,是计算正确分类的样本所占的比例。
from sklearn.datasets import make_blobs
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
# 创建一个模拟数据集
X,y = make_blobs()
# 将数据集分为训练集和测试机
X_train,X_test,y_train,y_test = train_test_split(X,y,random_state=0)
# 将模型实例化 并用它来拟合训练集
logreg = LogisticRegression().fit(X_train,y_train)
# 在测试机上评估该模型
print("test set score:{:.3f}".format(logreg.score(X_test,y_test)))
运行结果:
test set score:0.840
交叉验证
交叉验证是一种评估泛化性能的统计学方法,她比单次划分训练集和测试测的方法更加稳定、全面。在交叉验证中,数据被多次划分,并且需要训练多个模型。最常用的是K折交叉验证,其中k是由用户指定的数字,通常取5或者10.在执行5折交叉验证时,先将数据划分为大致相等的5部分,每一部分叫折。训练一系列模型。使用第一折作为测试机、其他折作为训练集来训练第一个模型。利用2~5折中的数据来构建模型,然后在1折上评估精度。之后构建另一个模型,这次使用 2 折作为测试集,1、3、4、5 折中的数据作为训练集。利用 3、4、5 折作为测试集继续重复这一过程。对于将数据划分为训练集和测试集的这 5 次划分,每一次都要计算精度。最后我们得到了 5 个精度值。
mglearn.plots.plot_cross_validation()
scilit-learn中的交叉验证
scikit-learn 是利用 model_selection 模块中的 cross_val_score 函数来实现交叉验证的。cross_val_score 函数的参数是我们想要评估的模型、训练数据与真实标签。我们在 iris数据集上LogisticRegression 进行评估。
from sklearn.model_selection import cross_val_score
from sklearn.datasets import load_iris
from sklearn.linear_model import LogisticRegression
iris = load_iris()
logreg = LogisticRegression()
scores = cross_val_score(logreg, iris.data, iris.target)
print("Cross-validation scores: {}".format(scores))
运行结果:
Cross-validation scores: [0.96666667 1. 0.93333333 0.96666667 1. ]
默认情况下,该函数执行3次交叉验证,返回3个精度值,可以通过修改CV参数来改变折数。
# 改变折数
scores = cross_val_score(logreg, iris.data, iris.target, cv=5)
print("Cross-validation scores: {}".format(scores))
print("Average cross-validation score: {:.2f}".format(scores.mean()))
运行结果:
Cross-validation scores: [0.96666667 1. 0.93333333 0.96666667 1. ]
Average cross-validation score: 0.97
总结交叉验证精度的一种常用方法是计算平均值。
我们可以观察到,折与折之间精度有较大的变化,这意味模型强烈依赖于某个折用于训练,但是也可能只是数据集的数据量太小。
交叉验证的优点
使用交叉验证不是将数据单次划分为训练集和测试集,每个样例都会刚好在测试集中出现一次:每个样例位于一个折中,而每个折都在测试集中出现一次。因此,模型需要对数据集中所有样本的泛化能力都好,才能让所有的交叉验证得分都很高。对数据进行多次划分,还可以提供我们的模型对训练集选择的敏感度,对于之前的实验,我们观察到精度在90%到100%之间,他告诉我们将模型应用在新数据时最坏情况和最好情况的表现。与数据的单次划分相比,交叉验证的另一个优点是对数据的使用更加高效。缺点是增加了计算成本,我们要训练k个模型而不是单个模型。
交叉验证的目的只是评估给定算法在特定数据集上训练后的泛化性能的好坏。
分层k折交叉验证和其他策略
from sklearn.datasets import load_iris
iris = load_iris()
print("Iris labels:\n{}".format(iris.target))
运行结果:
Iris labels:
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 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 2 2 2 2 2 2 2 2 2 2 2
2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
2 2]
将数据集划分为k折时,从数据的前k分之一开始划分可能不是好主意。想象一下在这个数据集上进行 3 折交叉验证。第 1 折将只包含类别 0,所以在数据的第一次划分中,测试集将只包含类别 0,而训练集只包含类别 1 和 2。由于在 3 次划分中训练集和测试集中的类别都不相同,因此这个数据集上的 3 折交叉验证精度为 0。这没什么帮助,因为我们在 iris 上可以得到比 0% 好得多的精度。
scikit-learn 在分类问题中不使用这种策略,而是使用分层 k 折交叉验证(stratified k-fold cross-validation)。在分层交叉验证中,我们划分数据,使每个折中类别之间的比例与整个数据集中的比例相同。
# 使用分层k折交叉验证
mglearn.plots.plot_stratified_cross_validation()
使用分层K折交叉验证而不是K折交叉验证来评估一个分类器,可以对泛化性能做出更可靠的估计。对于回归问题,scikit-learn 默认使用标准 k 折交叉验证。也可以尝试让每个折表示回归目标的不同取值,但这并不是一种常用的策略,也会让大多数用户感到意外。
对交叉验证更多的控制
scikit-learn运行提供一个交叉验证分离器作为cv参数,来对数据划分过程进行更精细的控制。
# 交叉验证分离器
from sklearn.model_selection import KFold
kfold = KFold(n_splits=5)
# 将分离器作为cv参数传入
print("Cross-validation scores:\n{}".format(cross_val_score(logreg, iris.data, iris.target, cv=kfold)))
# 使用三折
kfold = KFold(n_splits=3)
print("Cross-validation scores:\n{}".format(cross_val_score(logreg, iris.data, iris.target, cv=kfold)))
'''
将数据打乱来代替分层,以打乱样本按标签的排序。可以通过将 KFold 的shuffle 参数设为 True 来实现这一点
如果我们将数据打乱,那么还需要固定 random_state 以获得可重复的打乱结果
'''
kfold = KFold(n_splits=3, shuffle=True, random_state=0)
print("Cross-validation scores:\n{}".format(cross_val_score(logreg, iris.data, iris.target, cv=kfold)))
运行结果:
Cross-validation scores:
[1. 1. 0.86666667 0.93333333 0.83333333]
Cross-validation scores:[0. 0. 0.]
Cross-validation scores:[0.98 0.96 0.96]
留一法交叉验证
将留一法交叉验证看作是没折只包含单个样本的k折交叉验证。对于每次划分,选择单个数据点作为测试集,这种方法可能非常耗时,特别是对于大数据集来说,但是在小型数据集上有时可以有更好的估计结果。
# 留一法交叉验证
from sklearn.model_selection import LeaveOneOut
loo = LeaveOneOut()
scores = cross_val_score(logreg, iris.data, iris.target, cv=loo)
print("Number of cv iterations: ", len(scores))
print("Mean accuracy: {:.2f}".format(scores.mean()))
运行结果:
Number of cv iterations: 150
Mean accuracy: 0.97
打乱划分交叉验证
在打乱划分交叉验证中,每次划分为训练集取样 train_size 个点,为测试集取样 test_size 个(不相交的)点。将这一划分方法重复 n_iter 次。
mglearn.plots.plot_shuffle_split()
可以将train_size和test_size设为整数来表示这两个集合的绝对大小,也可以设置浮点数来表示占整个数据集的比例。
from sklearn.model_selection import ShuffleSplit
# 将数据集划分为 50% 的训练集和 50% 的测试集,共运行 10 次迭代
shuffle_split = ShuffleSplit(test_size=.5, train_size=.5, n_splits=10)
scores = cross_val_score(logreg, iris.data, iris.target, cv=shuffle_split)
print("Cross-validation scores:\n{}".format(scores))
运行结果:
Cross-validation scores:
[0.97333333 0.98666667 0.92 0.96 0.98666667 0.97333333
0.97333333 0.97333333 0.98666667 0.93333333]
打乱交叉验证可以子啊训练集和测试集大小之外独立控制迭代的次数,还允许在每次迭代中仅使用部分数据,通过设置trian_size和test_size 之和不等于1来实现。用这种方法对数据进行二次采样可能对大型数据上的试验很有用。
分组交叉验证
当数据中的分组高度相关时候使用分组交叉沿验证。构建一个分类器,能够正确识别未包含在数据集中的人的情感,使用分层交叉验证来度量分类器的性能,同一个人的照片可能会同时出现在训练集和测试集合中。为了准确评估模型对新的人脸的泛化能力,我们必须确保训练集和测试集中包含不同的人的图像。
'''
使用 GroupKFold,它以 groups 数组作为参数,可以用来说明照片中对应的是哪个人
这里的 groups 数组表示数据中的分组,在创建训练集和测试集的时候不应该将其分开,也不应该与类别标签弄混
'''
'''
由 groups 数组指定分组的模拟数据集。这个数据集包含 12 个数据点,
且对于每个数据点,groups 指定了该点所属的分组(想想病人的例子)。一共分成 4个组,
前 3 个样本属于第一组,接下来的 4 个样本属于第二组,以此类推
'''
from sklearn.model_selection import GroupKFold
# 创建模拟数据集
X, y = make_blobs(n_samples=12, random_state=0)
# 假设前3个样本属于同一组,接下来的4个属于同一组,以此类推
groups = [0, 0, 0, 1, 1, 1, 1, 2, 2, 3, 3, 3]
scores = cross_val_score(logreg, X, y, groups, cv=GroupKFold(n_splits=3))
print("Cross-validation scores:\n{}".format(scores))
运行结果:Cross-validation scores:
[ 0.75 0.8 0.667]
样本不需要按分组进行排序,只是为了便于说明。
基于对标签计算得到划分,对于每次划分,每个分组都是整体出现在训练集或测试集中。
scikit-learn 中还有很多交叉验证的划分策略,适用于更多的使用场景。
网格搜索
现在学习通过调参来提升模型的泛化性能,在尝试调参之前最重要的是理解参数的含义,找到一个模型的最要参数的取值是一项非常棘手的事情,但是对于几乎所有的模型和数据集来说都是必要的。最常用的方法是网格搜索,主要是指尝试我们关心的参数的所有可能的组合。
简单网格搜索
在2个参数上使用for循环,对每种参数分别训练并评估一个分类器
# 简单的网格搜索实现
from sklearn.model_selection import train_test_split
from sklearn.svm import SVC
from sklearn.datasets import load_iris
iris = load_iris()
X_train, X_test, y_train, y_test = train_test_split(iris.data, iris.target, random_state=0)
print("Size of training set: {} size of test set: {}".format(X_train