在介绍随机森林之前需要先介绍决策树与k折交叉验证法,这两个算法对于了解后续随机森林算法的实现很重要。
一. 决策树
决策树是一种机器学习的方法。决策树的生成算法有ID3, C4.5和C5.0等。决策树是一种树形结构,其中每个内部节点表示一个属性上的判断,每个分支代表一个判断结果的输出,最后每个叶节点代表一种分类结果。
决策树是一种十分常用的分类方法,需要监管学习,监管学习就是给出一堆样本,每个样本都有一组属性和一个分类结果,也就是分类结果已知,那么通过学习这些样本得到一个决策树,这个决策树能够对新的数据给出正确的分类。这里通过一个简单的例子来说明决策树的构成思路:
给出如下的一组数据,一共有十个样本(学生数量),每个样本有分数,出勤率,回答问题次数,作业提交率四个属性,最后判断这些学生是否是好学生。最后一列给出了人工分类结果。
学生编号 | 分数 | 出勤率 | 回答问题次数 | 作业提交率 | 分类:是否为好学生 |
1 | 99 | 80% | 5 | 90% | 是 |
2 | 89 | 100% | 6 | 100% | 是 |
3 | 69 | 100% | 7 | 100% | 否 |
4 | 50 | 60% | 8 | 70% | 否 |
5 | 95 | 70% | 9 | 80% | 否 |
6 | 98 | 60% | 10 | 80% | 是 |
7 | 92 | 65% | 11 | 100% | 是 |
8 | 91 | 80% | 12 | 85% | 是 |
9 | 85 | 80% | 13 | 95% | 是 |
10 | 85 | 91% | 14 | 98% | 是 |
然后用这一组附带分类结果的样本可以训练出多种多样的决策树,这里为了简化过程,我们假设决策树为二叉树,且类似于下图:
通过学习上表的数据,可以设置A,B,C,D,E的具体值,而A,B,C,D,E则称为阈值。当然也可以有和上图完全不同的树形,比如下图这种的:(根据不同的阈值以及要求可以生成许多不同的决策树)
所以决策树的生成主要分以下两步,这两步通常通过学习已经知道分类结果的样本来实现。
1. 节点的分裂:一般当一个节点所代表的属性无法给出判断时,则选择将这一节点分成2个
子节点(如不是二叉树的情况会分成n个子节点)
2. 阈值的确定:选择适当的阈值使得分类错误率最小 (Training Error)。
3. 属性的优先级:对于分数,出勤率,回答作业次数,作业提交率等属性的考虑中,在构建决策树时应先考虑那个属性后考虑那个属性对于决策树的构建比较好。
构建决策树需要提前了解的知识点:
1. 决策树的属性选择:划分属性时,在当前节点下,在属性集合中,选择最优的属性。
2. 决策树的深度 n 对学习器性能的影响: n 太小有欠拟合风险,太大有过拟合风险。
3. 划分选择算法: gini(基尼) 以及 entropy(熵)。
比较常用的决策树有ID3,C4.5和CART(Classification And Regression Tree),CART的分类效果一般优于其他决策树。下面介绍具体步骤。
1. ID3: 由增熵(Entropy)原理来决定属性的优先级,那个节点需要分裂。对于一组数据,熵越小说明分类结果越好。
分数,出勤率,回答作业次数,作业提交率,这么多的属性首先应该关注哪个呢?
为了回答上述问题,先看一个简单的游戏,即「20个问题」游戏,这个游戏是这样玩的:A 心里想着一个名人,B 问 A 20 个问题,A 只能回答「是」或「否」,20 个问题之后 B 要猜出 A 心里想的那个名人是谁。首先问一个可以最大程度压缩剩余选项数目的问题会使 B 占据极大优势,例如询问「是不是安吉丽娜·朱莉?」,最多剔除一个选项,而询问「这个名人是女人吗?」将消除大约一半的选项。就是说,「性别」特征相比「安吉丽娜·朱莉」、「西班牙人」、「喜欢足球」等其他特征更能区分名人数据集。这背后的道理与熵有关,下面介绍熵的概念。
熵是一个在物理、信息论和其他领域中广泛应用的重要概念,可以衡量获得的信息量。对于具有 N 种可能状态的系统而言,熵的定义如下:
其中,pi 是系统位于第 i 个状态的概率。熵可以描述为系统的混沌程度,熵越高,系统的有序性越差,反之亦然。熵将帮助我们高效的分割数据,类似帮助我们找出在「20个问题」游戏中先问什么问题较好。
假如是2分类问题,当A类和B类各占50%的时候,
S = - (0.5*log_2( 0.5)+0.5*log_2( 0.5))= 1
当只有A类,或只有B类的时候,
S = - (1*log_2( 1)+0)=0
所以当Entropy最大为1的时候,是分类效果最差的状态,当它最小为0的时候,是完全分类的状态。因为熵等于零是理想状态,一般实际情况下,熵介于0和1之间。
对于以横坐标为A类或B类状态的概率,纵坐标为S的曲线为在0-1之间两边低中间高的曲线。
import warnings
from matplotlib import pyplot as plt
import numpy as np
import pandas as pd
import seaborn as sns
sns.set()
warnings.filterwarnings('ignore')
plt.figure(figsize=(6, 4))
xx = np.linspace(0, 1, 50)
plt.plot(xx, [-x * np.log2(x) - (1-x) * np.log2(1 - x)
for x in xx], label='entropy')
plt.xlabel('p')
plt.ylabel('criterion')
plt.title('Criteria of quality as a function of p (binary classification)')
plt.legend()
运行结果:
熵的不断最小化,实际上就是提高分类正确率的过程。
比如上表中的4个属性:单一地通过以下语句分类:
1. 分数小于70为【不是好学生】:分错1个
2. 出勤率大于70为【好学生】:分错3个
3. 问题回答次数大于9为【好学生】:分错2个
4. 作业提交率大于80%为【好学生】:分错2个
最后发现 分数小于70为【不是好学生】这条分错最少,也就是熵最小,所以应该选择这条为父节点进行树的生成,当然分数也可以选择大于71,大于72等等,出勤率也可以选择小于60,65等等,总之会有很多类似上述1~4的条件,最后选择分类错最少即熵最小的那个条件。而当分裂父节点时道理也一样,分裂有很多选择,针对每一个选择,与分裂前的分类错误率比较,留下那个提高最大的选择,即熵减最大的选择。
2. C4.5:通过对ID3的学习,可以知道ID3存在一个问题,那就是越细小的分割分类错误率越小,所以ID3会越分越细,比如以第一个属性为例:设阈值小于70可将样本分为2组,但是分错了1个。如果设阈值小于70,再加上阈值等于95,那么分错率降到了0,但是这种分割显然只对训练数据有用,对于新的数据没有意义,这就是所说的过度学习(Overfitting)也叫做过拟合。
过拟合是学习器性能过好,把样本的一些特性当做了数据的一般性质,从而导致训练误差低但泛化误差高。学习曲线是判断过拟合的一种方式,同时可以判断学习器的表现。学习曲线包括训练误差(或精度)随样例数目的变化曲线与测试误差(或精度)随样例数目的变化曲线。
Random Forest 的学习曲线我们得到了,训练误差始终接近 0,而测试误差始终偏高,说明存在过拟合的问题。这个问题的产生是因为 Random Forest 算法使用决策树作为基学习器,而决策树的一些特性将造成较严重的过拟合。
分割太细了,训练数据的分类可以达到0错误率,但是因为新的数据和训练数据不同,所以面对新的数据分错率反倒上升了。决策树是通过分析训练数据,得到数据的统计信息,而不是专为训练数据量身定做。
所以为了避免分割太细,c4.5对ID3进行了改进,C4.5中,优化项要除以分割太细的代价,这个比值叫做信息增益率,显然分割太细分母增加,信息增益率会降低。在这种方法中通过信息增益率来对属性的优先级进行选择,除此之外,其他的原理和ID3相同。
由于熵实际上是系统混沌(或不确定)的程度,熵的下降被称为信息增益。数学上,基于变量Q所作的分割,得到的信息增益(IG)定义为:
其中,q 是分割的组数,Ni 是变量 Q 等于第 i 项时的样本数目。
3. CART:分类回归树
CART是一个二叉树,也是回归树,同时也是分类树,CART的构成简单明了。
CART只能将一个父节点分为2个子节点。CART用GINI指数来决定如何分裂:
GINI指数:总体内包含的类别越杂乱,GINI指数就越大(跟熵的概念很相似)。
a. 比如出勤率大于70%这个条件将训练数据分成两组:大于70%里面有两类:【好学生】和【不是好学生】,而小于等于70%里也有两类:【好学生】和【不是好学生】。
b. 如果用分数小于70分来分:则小于70分只有【不是好学生】一类,而大于等于70分有【好学生】和【不是好学生】两类。
比较a和b,发现b的凌乱程度比a要小,即GINI指数b比a小,所以选择b的方案。以此为例,将所有条件列出来,选择GINI指数最小的方案,这个和熵的概念很类似。
CART还是一个回归树,回归解析用来决定分布是否终止。理想地说每一个叶节点里都只有一个类别时分类应该停止,但是很多数据并不容易完全划分,或者完全划分需要很多次分裂,必然造成很长的运行时间,所以CART可以对每个叶节点里的数据分析其均值方差,当方差小于一定值可以终止分裂,以换取计算成本的降低。
CART和ID3一样,存在偏向细小分割,即过度学习(过度拟合的问题),为了解决这一问题,对特别长的树进行剪枝处理,直接剪掉。
这里通过函数图像对比GINI指数与熵。
代码:
import warnings
from matplotlib import pyplot as plt
import numpy as np
import pandas as pd
import seaborn as sns
sns.set()
warnings.filterwarnings('ignore')
plt.figure(figsize=(6, 4))
xx = np.linspace(0, 1, 50)
plt.plot(xx, [2 * x * (1-x) for x in xx], label='gini')
plt.plot(xx, [4 * x * (1-x) for x in xx], label='2*gini')
plt.plot(xx, [-x * np.log2(x) - (1-x) * np.log2(1 - x)
for x in xx], label='entropy')
plt.xlabel('p')
plt.ylabel('criterion')
plt.title('Criteria of quality as a function of p (binary classification)')
plt.legend()
运行结果:
二. k 折交叉验证(k-fold cross validation)
以上的决策树训练的时候,一般会采取Cross-Validation法:比如一共有10组数据:
第一次. 1到9做训练数据, 10做测试数据
第二次. 2到10做训练数据,1做测试数据
第三次. 1,3到10做训练数据,2做测试数据,以此类推
做10次,然后大平均错误率。这样称为 10 folds Cross-Validation。
比如 3 folds Cross-Validation 指的是数据分3份,2份做训练,1份做测试。
也就是k 折交叉验证(k-fold cross validation):将 D 划分 k 个大小相似的子集(每份子集尽可能保持数据分布的一致性:子集中不同类别的样本数量比例与 D 基本一致),其中一份作为测试集,剩下 k-1 份为训练集 T,操作 k 次。 例如 D 划分为 D1,D2,... ,D10,第一次使用 D1 作为测试集,第二次使用 D2,第三次使用 D3, ... , 第十次使用 D10 作为测试集。最后计算 k 次测试误差的平均值近似泛化误差。
k折交叉验证法也常用于参数调节,以及模型的评估与训练。
三. 集成学习
集成学习就是构建并结合多个个体学习器(称为基学习器)来完成学习任务。举一个例子。下表中 √ 表示分类正确,× 表示分类错误。
- | 学习器 1 | 学习器 2 | 学习器 3 |
---|---|---|---|
h1 | √ | √ | × |
h2 | × | √ | √ |
h3 | √ | × | √ |
集成(提升性能) | √ | √ | √ |
- | 学习器 1 | 学习器 2 | 学习器 3 |
---|---|---|---|
h1 | √ | √ | × |
h2 | √ | √ | × |
h3 | √ | √ | × |
集成(不起作用) | √ | √ | × |
- | 学习器 1 | 学习器 2 | 学习器 3 |
---|---|---|---|
h1 | × | × | √ |
h2 | × | √ | × |
h3 | √ | × | × |
集成(起负作用) | × | × | × |
由上面三个表,我们看到集成学习器是通过少数服从多数的原则来进行分类结果的最终选择,这就要求我们的基学习器具有第一个表的特性:性能好,并且不一样(即好而不同)。随着基学习器数目的增加,集成的错误率剧烈下降直至为 0。
四. 随机森林
森林,顾名思义就是由许多树组成的,而随机森林中的树就是决策树,随机森林以决策树为基学习器。但是属性选择与决策树不同。随机森林中,基决策树学习器在每个节点上,从该节点的属性集合中随机选择包含 K 个属性的子集,再从子集中选择最优属性用于划分。这就满足 “好而不同” 的条件。随机森林计算开销小,是现在机器学习算法当中水平较高的算法。
Python sklearn 参数调节
参数 | 特点 | |
---|---|---|
n_estimators | 基学习器数目(默认值 10) | 基本趋势是值越大精度越高 ,直到达到一个上限 |
criterion | 选择算法 gini 或者 entropy (默认 gini) | 视具体情况定 |
max_features | 2.2.3 节中子集的大小,即 k 值(默认 sqrt(n_features)) | |
max_depth | 决策树深度 | 过小基学习器欠拟合,过大基学习器过拟合。粗调节 |
max_leaf_nodes | 最大叶节点数(默认无限制) | 粗调节 |
min_samples_split | 分裂时最小样本数,默认 2 | 细调节, 越小模型越复杂 |
min_samples_leaf | 叶节点最小样本数,默认 2 | 细调节,越小模型越复杂 |
bootstrap | 是否采用自助法进行样本抽样(默认使用) | 决定基学习器样本是否一致 |
在以上参数中,只有 n_estimators 对精度的影响是单调的。粗调节表示参数选择跨度大,以 10、100 等为单位。细调节参数选择跨度小,以 1、2 等为单位。
五. 参数调节
参数调节分为k折交叉验证法调参与scikit-learn 自动调参函数 GridSearchCV,这里我们只介绍k折交叉验证法调参,另外一个可以下去自行学习。
交叉验证法调参
我们首先调节:n_estimators
,max_depth
。首先观察特征数目,这决定了 max_depth
等参数的范围。然后使用交叉验证法调参。
得到最优参数 n_estimators=100,max_depth=10
。
n_estimators调参代码:
def para_tune(para, X, y): #
clf = RandomForestClassifier(n_estimators=para) # n_estimators 设置为 para
score = np.mean(cross_val_score(clf, X, y, scoring='accuracy'))
return score
def accurate_curve(para_range, X, y, title):
score = []
for para in para_range:
score.append(para_tune(para, X, y))
plt.figure()
plt.title(title)
plt.xlabel('Paramters')
plt.ylabel('Score')
plt.grid()
plt.plot(para_range, score, 'o-')
return plt
g = accurate_curve([2, 10, 50, 100, 150], X, y, 'n_estimator tuning')
运行结果:
max_depth调参代码:
def para_tune(para, X, y):
clf = RandomForestClassifier(n_estimators=300, max_depth=para)
score = np.mean(cross_val_score(clf, X, y, scoring='accuracy'))
return score
def accurate_curve(para_range, X, y, title):
score = []
for para in para_range:
score.append(para_tune(para, X, y))
plt.figure()
plt.title(title)
plt.xlabel('Paramters')
plt.ylabel('Score')
plt.grid()
plt.plot(para_range, score, 'o-')
return plt
g = accurate_curve([2, 10, 20, 30, 40], X, y, 'max_depth tuning')
运行结果: