过拟合与模型泛化

从前面使用决策树模型分类的经验中,我们可以发现,决策树模型最后几乎总是能100%预测准确!这说明决策树模型非常强大,复杂的任务也可以准确预测出来。但是我们也会有个疑惑,这样一个100%准确预测的模型应用到实际数据中,真的能全预测对吗?

模型的过拟合

为了验证上面的猜测,我们继续回到鸢尾花分类任务。

为了模拟实际情况,我们把数据集随机分为两份,一份我们可以用来训练模型,这部分数据通常是我们收集回来的,一份我们当做要解决的实际问题中的数据。比如,我们要做一个人脸识别系统,我们会先收集一批人脸数据,而在系统做好后,实际使用时,模型识别的数据都不在之前收集的数据里面。在机器学习领域,前一个集合我们用来训练模型的数据叫训练集,后一个数据集合我们叫测试集

使用sklearn的 train_test_split 函数,我们可以轻松完成训练集和测试集的划分。

X代表用来预测的属性,在机器学习中我们叫特征;y代表要预测的类别,在机器学习中我们叫标签。test_size参数是划分的测试集比例,0.5即代表划分50%的样本当测试集。

from sklearn.model_selection import train_test_split
from sklearn.datasets import load_iris 

iris = load_iris()
X, y = iris.data,  iris.target
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.5)

现在,我们现在训练集上训练一个决策树模型

from sklearn.tree import DecisionTreeClassifier

# 创建决策树分类模型
clf = DecisionTreeClassifier()
# .fit函数执行了决策树分类算法,从数据集中学到了最优的分裂规则
clf.fit(X_train, y_train)

训练好了之后,我们可以调用.predict函数预测训练集中的每个样本的类别,然后计算准确率。

import numpy as np

# 预测训练集的标签
y_pred = clf.predict(X_train)
# 计算准确率,结果应该是1,100%预测准确
print(np.mean(y_pred == y_train))

接着我们再在测试集上计算准确率,结果发现准确率远低于1,只有0.5左右!

# 预测训练集的标签
y_pred = clf.predict(X_test)
# 计算准确率,结果应该是0.534
print(np.mean(y_pred == y_test))

这是为什么呢?如果我们仔细分析上述流程,我们发现,模型是在训练集合上学到的知识,但是在测试集上有很多之前没见过的数据,所以就识别错了!这种现象,在机器学习里叫做过拟合!相反,如果模型在测试集上的性能非常好,跟训练集上几乎一样,那么我们就说模型的泛化能力强。

在机器学习中,过拟合现象是指模型在训练数据上表现得非常好,几乎完美地拟合了训练数据的特征和规律,但在面对新的、未见过的数据时,却表现得很差,预测准确性大幅下降。

具体来说,当模型过度专注于学习训练数据中的细节、噪声和特定模式,而忽略了数据中更普遍、更本质的特征和关系时,就容易发生过拟合。这可能导致模型变得过于复杂,包含了过多不必要的参数或规则。

关于过拟合,有个非常形象的类比。我们上学时经常遇到,平常做了很多习题,但是考试时遇到没做过的题还是不会做的情况,这就是过拟合!

过拟合的原因通常包括:训练数据量不足,使得模型无法学习到真正有代表性的模式;模型的复杂度太高,例如层数过多、参数过多等;训练过程中过度追求在训练数据上的高准确率,而没有考虑到模型的泛化到没有见过数据上的能力。

过拟合的解决方法

决策树模型可以通过限制树的深度来限制模型的复杂度,避免过拟合。通常更深的决策树模型(下图)更加复杂,也容易过拟合。限定决策树深度的方法很简单,我们在创建决策树模型的时候,指定 max_depth参数即可!

此外,我们还可以限定分裂的停止准则stop_critera,限定叶子节点样本数不能太少,这可以通过指定参数min_samples_leaf 来实现。

# 限定决策树最大深度是3,叶子节点上最少的样本数是5
clf = DecisionTreeClassifier(max_depth=3, min_samples_leaf=5)  

最优参数搜索

上面我们说到,可以通过设置最大深度 max_depth 和 叶子节点最少样本数 min_samples_leaf 两个参数来控制模型过拟合。那么问题来了,我们怎么确定这些参数的值,或者说确定最佳值?

很简单,直接搜索。以最大深度为例,我们尝试系列深度值,从中挑选一个最优的就好了。最优的评估标准是,模型在测试集上的准确率最高。

如果有多个参数,那么我们就做多重遍历即可。以max_depth和min_samples_leaf 的参数搜索为例,算法如下:

best_params = (None, None)  # 保存最优参数
best_acc = -inf                          # 保存最优参数对应的准确率
best_clf = None                        # 保存最优模型

for max_depth in range(1,10):
    for min_samples_leaf in [1,3,5,10]:
        clf = DecisionTreeClassifier(max_depth=max_depth, min_samples_leaf=min_samples_leaf)
        # 在训练集上训练
        clf.fit(X_train, y_train)
        # 在测试集上评估模型的准确率
        y_pred = clf.predict(X_test)
        acc = np.mean(y_pred == y_test)

        # 以测试集上的准确率来选择最优模型
        if acc > best_acc:
            best_params = (max_depth, min_samples_leaf)
            best_acc = acc
            best_clf = clf

通常我们为了搜索效率,会只选择一部分可能得值和有限的范围去搜索。

上述搜索方法,如果把max_depth,min_samples_leaf放到坐标轴里,就像是在一个网格上去找最优参数组合,因此也被称作网格搜索(Gridsearch)。

sklearn软件包中提供了 sklearn.model_selection.GridSearchCV 可以帮助我们更高效地实现网格搜索。我们用这个函数改写上面的搜索算法如下:

from sklearn.datasets import load_iris
from sklearn.model_selection import GridSearchCV
from sklearn.tree import DecisionTreeClassifier

iris = load_iris()

dtc = DecisionTreeClassifier()
# 设置参数的取值
params = {
  "max_depth": range(1,10),
  "min_samples_leaf": [1,3,5,10]
}

# 调用网格搜索
clf = GridSearchCV(dtc, params, cv=5)
clf.fit(iris.data, iris.target)

GridSearchCV第一个参数是模型,第二个参数是参数配置,参数配置是一个字典,key是参数名,value是参数的所有可能的取值list。cv参数一个k-折叠数参数,通常选3-5即可。

k-折叠是一种比我们前面简单划分训练集和测试集更科学的划分方式。如下图所示,是一个5-折叠划分示意图。一次划分(split)会将一个完整的数据集均匀分成5分,然后用其中4分用来训练模型,剩下的1分作为测试,用来评估模型的准确率。由于测试集有5种选择,所以可以跑5次评估流程,取个平均值来衡量这个参数组合的准确率,这样可以减少一定的随机性波动。

动手试试

改造一下之前的决策树模型,解决过拟合问题。然后验证解决过拟合问题后的模型,在测试集上的预测准确率更高!

0 0 投票数
文章评分
订阅评论
提醒
guest
0 评论
内联反馈
查看所有评论
0
希望看到您的想法,请您发表评论x