一、概述
对训练数据的预测效果很好,但对验证数据(没有用于训练的数据)的预测效果不好的现象叫作过拟合。
在有监督学习中,防止过拟合是一个重要课题。仅仅考查之前介绍的各指标,并不足以判断模型的好坏。重要的是在解决实际问题时,模型对未知数据的预测精度。模型对这种未知数据的预测能力叫作泛化能力。即使模型对训练数据的均方误差很小,如果发生过拟合,泛化能力也会很低。过拟合和超参数的设置是分类问题和回归问题的共同挑战。
二、防止过拟合的方法
有监督学习的特征值和目标变量是作为训练数据预先给出的。前面介绍了对训练数据的性能评估方法。但在使用有监督学习解决实际问题时,除了评估模型对训练数据的性能之外,评估模型对不包括在训练数据中的数据(未知数据)的性能也是非常重要的。以乳腺癌数据集为例,“患者的身体数据”(特征)和“恶性/良性”(目标变量)是训练数据。
在实际应用中,对于“恶性/良性”不明的患者,重要的是能否通过患者的体检数据预测出“恶性/良性”。一个模型如果对训练数据的预测精度很高,但对未知数据不能进行很好的预测,那就不能说它是一个好模型。防止过拟合的方法有几种,以下是一些有代表性的方法。
1. 将数据分为训练数据和验证数据
防止过拟合的一个代表性的方法是将数据分为训练数据和验证数据。换言之,这种方法不使用事先给定的所有数据进行训练,而是留出一部分数据用于验证,不用于训练。使用scikit-learn 的train_test_split 函数,我们可以很容易地分割数据。
from sklearn.datasets import load_breast_cancer
data = load_breast_cancer()
X = data.data
y = data.target
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3)
·训练用的特征值:X_train
·验证用的特征值:X_test
·训练用的目标变量:y_train
·验证用的目标变量:y_test
我们将数据分为训练数据和验证数据,其中70%用于训练,30%用于验证(图4-5)。这个分割比例设为多少是没有明确规定的。如果数据集很大,有足够的数据用于训练,将分割比例设置为6∶4也是可行的;反之,如果数据集太小,不能很好地进行训练,可以将分割比例设置为8∶2等。另外要注意的是,每次运行时train_test_split的结果都是不同的,如果想保持结果固定,需要设置random_state参数。
下面使用训练数据和验证数据来进行算法的学习,并创建模型。
from sklearn.svm import SVC
model_svc = SVC()
model_svc.fit(X_train, y_train)
y_train_pred = model_svc.predict(X_train)
y_test_pred = model_svc.predict(X_test)
from sklearn.metrics import accuracy_score
print(accuracy_score(y_train, y_train_pred))
print(accuracy_score(y_test, y_test_pred))
如果与对训练数据的正确率相比,对验证数据的正确率要低很多,就说明数据发生了过拟合。上面代码中的模型对未知数据的正确率约为60%。下面使用另一个模型RandomForestClassifier来试一下。
from sklearn.ensemble import RandomForestClassifier
model_rfc = RandomForestClassifier()
model_rfc.fit(X_train, y_train)
y_train_pred = model_rfc.predict(X_train)
y_test_pred = model_rfc.predict(X_test)
from sklearn.metrics import accuracy_score
print(accuracy_score(y_train, y_train_pred))
print(accuracy_score(y_test, y_test_pred))
这次虽然对验证数据的正确率依然比对训练数据的低,却是约为96%的高正确率。由于模型对验证数据的正确率也很高,所以可以说防止了过拟合。从这些模型的结果来看,也许我们应该使用RandomForestClassifier。在选择模型时,如果没有分割数据,只看对训练数据的正确率,我们可能会选择SVC。通过观察对验证数据的正确率,我们能够避免使用出现了过拟合的模型。
2. 交叉验证
即使在将数据分为训练数据和验证数据后进行评估,也依然可能发生过拟合。可以想到的原因是使用的训练数据和验证数据碰巧非常相似。反过来也有可能出现训练数据和验证数据非常不相似的情况。为了避免这种数据分割的误差,可以使用不同的分割方案进行多次验证,这就是所谓的交叉验证(cross validation)。
本文以将数据分割5次,其中80%的数据用于训练,20%的数据用于验证的情况为例进行说明。如图所示,每次获取不同的20%的数据作为验证数据,重复5次。在这个例子中,20%的数据是按分组顺序分别分割的,但在实际应用中,作为验证数据的20%的数据是随机抽取的。
以下代码非常轻松地将数据分成了5块,即运行5次,每次留下20%的数据用于训练后的验证。
from sklearn.model_selection import cross_val_score
from sklearn.model_selection import KFold
cv = KFold(5, shuffle=True)
model_rfc_1 = RandomForestClassifier()
cross_val_score(model_rfc_1, X, y, cv=cv, scoring='accuracy')
这时正确率会被输出5次。我们可以看到正确率有时很高,有时很低。在选择模型时,需要考虑所有正确率的均值和方差。
另外,我们也可以输出F值的评估结果。通过将cross_val_score函数的scoring参数定义为f1,就可以输出F值,代码如下所示。
cross_val_score(model_rfc_1, X, y, cv=cv, scoring="f1")
3. 搜索超参数
前面介绍了如何使用分割得到的数据来选择不会出现过拟合的模型。如果在此基础上仔细地选择超参数,就可以进一步提高模型的性能。就像前面在“超参数的设置”部分介绍的那样,通过反复设置一个超参数并检查其性能,最终可以得到更好的超参数。但是多个超参数的组合数量非常多,逐一设置每个超参数的过程非常耗时。
使用网格搜索选择超参数
网格搜索是一种自动搜索超参数的方法。如图所示,这是一种对各个超参数组合进行穷尽搜索的方法。需要注意的是,要搜索的超参数必须事先确定。
下面是使用scikit-learn的 GridSearchCV进行RandomForestClassifier超参数搜索的示例代码。GridSearchCV一边关注对验证数据的性能,一边执行超参数的搜索。首先加载数据。这里要做的是对分类任务进行网格搜索,所以要重新加载美国威斯康星州乳腺癌数据集。
示例代码:
from sklearn.datasets import load_breast_cancer
data = load_breast_cancer()
X = data.data
y = 1 - data.target
# 反转标签的0和1
X = X[:, :10]
接下来进行网格搜索
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import GridSearchCV
from sklearn.model_selection import KFold
cv = KFold(5, shuffle=True)
param_grid = {'max_depth': [5, 10, 15], 'n_estimators': [10, 20, 30]}
model_rfc_2 = RandomForestClassifier()
grid_search = GridSearchCV(model_rfc_2, param_grid, cv=cv, scoring='accuracy')
grid_search.fit(X, y)
上面的代码为max_depth准备了3个值,为n_estimators也准备了3个值,对二者所有的组合,即3×3=9种情况进行了评估。下面输出所得到的最好的得分及相应的超参数值。
print(grid_search.best_score_)
print(grid_search.best_params_)
防止过拟合的各种方法
前面介绍了通过调整超参数等来防止过拟合的方法。除此之外,还有其他一些方法可以防止过拟合。下面列出防止过拟合的主要方法的名称。在应对过拟合时,也可以考虑这些方法。·增加训练数据
·减少特征值
·正则化
·Early Stopping
·集成学习