计算机系统应用教程网站

网站首页 > 技术文章 正文

「周末AI课堂」非参模型进阶(代码篇)机器学习你会遇到的“坑”

btikc 2024-10-11 11:20:53 技术文章 5 ℃ 0 评论

AI课堂开讲,就差你了!

很多人说,看了再多的文章,可是没有人手把手地教授,还是很难真正地入门AI。为了将AI知识体系以最简单的方式呈现给你,从这个星期开始,芯君邀请AI专业人士开设“周末学习课堂”——每周就AI学习中的一个重点问题进行深度分析,课程会分为理论篇和代码篇,理论与实操,一个都不能少!

来,退出让你废寝忘食的游戏页面,取消只有胡吃海塞的周末聚会吧。未来你与同龄人的差异,也许就从每周末的这堂AI课开启了!

后台回复“周末AI课堂”,查阅相关源代码

全文共1728字,预计学习时长4分钟


我们在前面的文章中从理论上解决了决策树的三个问题,分别是如何处理连续的属性值,如何处理连续的target,以及如何防止过拟合。

事实上,在《非参数模型初步(代码篇)》中已经见到过连续的属性值处理办法,就是对属性值进行分割(常见于二分法),然后对每个二分点所产生的信息增益进行搜索比较,找出最小信息增益所对应的二分点。而我们使用的sklearn的DecisionTreeClassifier类对IRIS数据,就已经使用了二分法:

如图,我们可以看出,对于IRIS数据,每个节点的判定条件均是大于或者小于某个特定值,每个属性的分叉都是2,其实就对应着二分法的结果。

我们可以使用另外一种属性连续更为明显的样例数据:

import matplotlib.pyplot as plt

import seaborn as sns

from sklearn import datasets

X,y=datasets.make_moons(200,noise=0.2,random_state=0)

sns.set(style='darkgrid')

for i,v,l in [[0,'r','class_0'],[1,'b','class_1']]:

plt.scatter(X[y==i][:,0],X[y==i][:,1],c=v,label=l)

plt.legend()

plt.show()

用简单的决策树来拟合它,并得到树状图:

.......

clf=DTC(criterion='entropy')

clf.fit(X,y)

dot_data=tree.export_graphviz(clf, out_file=None,

feature_names=['F_1','F_2'],

class_names=['class_0','class_2'],

filled=True, rounded=True,

special_characters=True)

graph=graphviz.Source(dot_data)

graph.render('MOON')

.....

如图,可以注意到每个节点的分叉与划分几乎都和IRIS相同,但是新数据只有两个特征,却形成了一颗非常深的树,部分原因在于采用二分法对连续属性值进行处理时,子节点可以和父节点都是同一个属性,但这离散属性值上,父节点出现的特征,子节点不就不会出现;更重要的原因在于,我们可能发生了过拟合。

进一步地观察决策边界:

如图,我们可以看到决策边界精确地绕过了每一个点。

接下来,我们考虑如何降低决策树的过拟合,正如在《非参数模型进阶(理论篇)》中所说的那样,过拟合一般会体现在三个方面:

? 树的深度。随着划分次数的增加,空间被越分越小,预测值也会越来越精确,另一方面,递归的次数也越多,树也越深。

? 叶节点的数目。叶节点就是决策树最末端的节点,它和树的深度一起表示了树的复杂程度。

? 叶节点包含的样本个数。如果它包含的样本太少,说明决策树为少量的样本创建了规则,强行使某一个样本被划分正确,很有可能就出现了过拟合。

事实上,对其进行剪枝的过程,就是对这三个方面进行限制的过程,所以完全可以通过限制树的深度,叶节点的数目和叶节点包含的样本个数来降低决策树的过拟合风险。以叶节点包含的样本个数为超参数,利用10折交叉验证,观察测试集和训练集上的准确率来观察这一限制对过拟合的限制作用:

import numpy as np

import matplotlib.pyplot as plt

import seaborn as sns

from sklearn.model_selection import cross_validate

from sklearn import datasets

from sklearn.tree import DecisionTreeClassifier as DTC

from sklearn import tree

X,y=datasets.make_moons(1000,noise=0.2,random_state=0)

test_mse=[]

train_mse=[]

numbers=range(1,20)

for d in numbers:

clf =DTC(criterion='entropy',min_samples_leaf=d)

clf_dict=cross_validate(clf,X,y,cv=10,scoring='accuracy')

test_mse.append(clf_dict['test_score'].mean())

train_mse.append(clf_dict['train_score'].mean())

sns.set(style='darkgrid')

plt.plot(depths,train_mse,'b-',label='Train Accuracy')

plt.plot(depths,test_mse,'r-',label='Test Accuracy')

plt.xlabel(' minimum number of samples required to be at a leaf node')

plt.ylabel('Accuracy')

plt.legend()

plt.show()

如图,随着叶节点包含样本数的增加,树的复杂度的降低,训练集上的准确率下降,但测试集的准确率却在上升,说明我们在上文所采用的决策树方法出现了过拟合,并且我们通过限制叶节点样本数的方法提高了泛化能力,也就是进行了剪枝,因为当节点包含的样本太少时,决策树就会在这一分支停止生成。

决策树的回归是否也会存在过拟合呢,我们采用一个正弦函数作为例子,选取不同的树的深度,先来观察回归树在样本空间的形式:

import numpy as np

from sklearn.tree import DecisionTreeRegressor

import matplotlib.pyplot as plt

rng = np.random.RandomState(1)

X = np.linspace(0, 6, 100)[:, np.newaxis]

y = np.sin(X).ravel() + np.sin(6 * X).ravel() + rng.normal(0, 0.1, X.shape[0])

regr_1 = DecisionTreeRegressor(max_depth=2)

regr_2 = DecisionTreeRegressor(max_depth=5)

regr_3 = DecisionTreeRegressor(max_depth=10)

regr_1.fit(X, y)

regr_2.fit(X, y)

regr_3.fit(X, y)

y_1 = regr_1.predict(X)

y_2 = regr_2.predict(X)

y_3 = regr_3.predict(X)

plt.figure()

plt.scatter(X, y, s=20, edgecolor="black",

c="darkorange", label="data")

plt.plot(X, y_1, color="cornflowerblue",

label="max_depth=2", linewidth=2)

plt.plot(X, y_2, color="yellowgreen", label="max_depth=5", linewidth=2)

plt.plot(X,y_3, color='r',label='max_depth=10',linewidth=2)

plt.xlabel("data")

plt.ylabel("target")

plt.title("Decision Tree Regression")

plt.legend()

plt.show()

如图,回归树对特征划分是递归进行的,回归树在样本空间形成的回归曲线类似于分类树在特征空间中的决策边界,并且随着树的加深,递归划分的次数也在增加,在树的最大深度为10的时候,决策边界似乎变得不是轴平行了,其实只是因为空间被划分的太多。

继续对回归树采取分集测试:

import numpy as np

from sklearn.tree import DecisionTreeRegressor as DTR

import matplotlib.pyplot as plt

from sklearn import datasets

from sklearn.model_selection import cross_validate

import seaborn as sns

rng = np.random.RandomState(1)

X = np.linspace(0, 6, 100)[:, np.newaxis]

y = np.sin(X).ravel() + np.sin(6 * X).ravel() + rng.normal(0, 0.1, X.shape[0])

test_mse=[]

train_mse=[]

depths=range(2,5)

for d in depths:

clf =DTR(criterion='mse',max_depth=d)

clf_dict=cross_validate(clf,X,y,cv=10,scoring='neg_mean_squared_error')

test_mse.append(np.abs(clf_dict['test_score'].mean()))

train_mse.append(np.abs(clf_dict['train_score'].mean()))

sns.set(style='darkgrid')

plt.plot(depths,train_mse,'b-',label='Train MSE')

plt.plot(depths,test_mse,'r-',label='Test MSE')

plt.xlabel(' Max Depth')

plt.ylabel('MSE')

plt.legend()

plt.show()

随着树的深度的增加,测试集的误差在增加,训练集的误差却在减小。

根据对过拟合的限制,我们还可以选取测试集表现最好对应的叶节点的样本数,来画出相应的树状图:

......

clf =DTC(criterion='entropy',min_samples_leaf=8)

......

可以明显看出树的复杂度已经降低了,按照同样的方式,可以画出回归树的图:

读芯君开扒

课堂TIPS

? 回归树中的度量选取MSE,也可以选取很多回归任务中的性能指标,比如MAE,而不用担心有些性能指标难以优化的问题,因为在回归树中,所定义的优化函数的目的只是用来比较,进一步来确定划分点。

? 树的深度、叶节点的数目、叶节点包含的样本个数对应着剪枝过程,但这三者并非是相互独立的,往往限制了其中一个,另一个也会被限制,所以在实际使用过程中只需要限制其中一个。

作者:唐僧不用海飞丝

如需转载,请后台留言,遵守转载规范

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表