机器学习之决策树和集成方法

811 阅读9分钟
原文链接: github.com

传统机器学习方法中,大量的研究是基于信息论的。其中决策树是一种非常有效的监督方法,目的是将一个数据集按照要预测的变量划分为尽可能均匀的组。它接受一组分类数据作为输入,并输出一个类似于方向图的树,其中每个端点(叶子)是一个决策(类),每个非叶子节点(内部)代表一个测试。每个叶子表示属于一组数据的分类,这些数据通过从根到叶子的所有测试。

信息论

决策树中的ID3和c4.5算法是基于香农信息论。信息熵是什么?
香农熵:给定可能性分布P和实例S, 那么信息熵可以如下计算:
$Entropie(P) =-\sum_{i=1}^{n}p_i * log(p_i)$

信息增益G(P, T): 决策树通过对不同特征进行切分判断来进行分类。那怎么判断什么哪个特征比较合适。可以用信息增益来测量所有样本特征的混合程度,选择比较混乱的特征(熵比较大), 经过叫少的判断即可区分。计算公式如下:
$Gain(p, T)  = Entropie(P) - \sum_{i=1}^{n}(p_i * Entropie(p_i))$
pi表示T特征中发生的可能性。

下面通过一个例子来进行简单计算:

In [2]:
import numpy as np
import pandas as pd
In [24]:
outlook = np.array(['sun', 'sun', 'overcast', 'rain', 
                    'rain','rain','overcast','sun',
                    'sun','rain', 'sun',  'overcast',
                    'overcast', 'rain'])
temperature = np.array(['hot', 'hot', 'hot', 'sweet', 
                        'cold','cold', 'cold', 'sweet',
                        'cold', 'sweet','sweet','sweet',
                        'hot', 'sweet'])
humidity = np.array(['high', 'high', 'high', 'high', 
                    'normal', 'normal', 'normal', 'high',
                    'normal', 'normal', 'normal', 'high',
                    'normal', 'high'])
wind = np.array(['low', 'high', 'low', 'low', 
                'low', 'high', 'high', 'low', 
                'low', 'low', 'low', 'high', 
                'low', 'high'])
label_play = np.array(['no', 'no', 'yes', 'yes', 
                      'yes', 'no', 'yes', 'no',
                      'yes','yes','yes','yes',
                      'yes', 'no'])
tree_df = pd.DataFrame({
    'outlook':outlook,
    'temperature':temperature,
    'humidity':humidity,
    'wind':wind,
    'play':label_play,
})
tree_df = tree_df[['outlook', 'temperature','humidity', 'wind', 'play']]
tree_df
Out[24]:
outlook temperature humidity wind play
0 sun hot high low no
1 sun hot high high no
2 overcast hot high low yes
3 rain sweet high low yes
4 rain cold normal low yes
5 rain cold normal high no
6 overcast cold normal high yes
7 sun sweet high low no
8 sun cold normal low yes
9 rain sweet normal low yes
10 sun sweet normal low yes
11 overcast sweet high high yes
12 overcast hot normal low yes
13 rain sweet high high no

如上所示: 根据每天的天气情况来判断是够应该出去play。 可以根据上述两个公式来计算每个特征的熵和信息增益。

信息增益

In [25]:
# 定义简单的熵计算函数:
def entropy(label_set):
    return -np.sum(label_set * np.log2(label_set))
In [30]:
# 全集S的熵:
entropy_s = entropy([9/14, 5/15]) # 两个类别
entropy_s
Out[30]:
0.9380972111121206
In [31]:
# 计算每个特征的信息增益:
tree_df[['outlook', 'play']]
Out[31]:
outlook play
0 sun no
1 sun no
2 overcast yes
3 rain yes
4 rain yes
5 rain no
6 overcast yes
7 sun no
8 sun yes
9 rain yes
10 sun yes
11 overcast yes
12 overcast yes
13 rain no

如上,outlook特征分为3个类别,其出现的概率分别为5/14, 4/14, 5/14。

In [43]:
for attr in ['outlook', 'temperature', 'humidity', 'wind']:
    print(tree_df.groupby([attr, 'play']).size())
    print('-' * 30)
outlook   play
overcast  yes     4
rain      no      2
          yes     3
sun       no      3
          yes     2
dtype: int64
------------------------------
temperature  play
cold         no      1
             yes     3
hot          no      2
             yes     2
sweet        no      2
             yes     4
dtype: int64
------------------------------
humidity  play
high      no      4
          yes     3
normal    no      1
          yes     6
dtype: int64
------------------------------
wind  play
high  no      3
      yes     2
low   no      2
      yes     7
dtype: int64
------------------------------
In [46]:
# 其中每个类别对应的play情况如上, 因此信息增益如下计算:
gain_s_outlook = entropy_s - 4/14 * entropy([1]) - 5/14*entropy([2/5, 3/5]) - 5/14*entropy([3/5, 2/5])
# 同样方法可以计算出另外两个特征的信息增益
gain_s_temperature = entropy_s - 4/14*entropy([1/4, 3/4]) - 4/14 * entropy([2/4, 2/4]) - 6/14*entropy([2/6, 4/6])
gain_s_humidity = entropy_s - 7/14 * entropy([4/7, 3/7]) - 7/14 * entropy([1/7, 6/7])
gain_s_wind = entropy_s - 5/14*entropy([3/5, 2/5]) - 9/14 * entropy([2/9, 7/9])
In [48]:
# 4个特征的信息增益计算完毕,选组最大的特征作为根节点进行切分
gain_s_outlook, gain_s_humidity, gain_s_wind, gain_s_temperature
Out[48]:
(0.24456107221592882,
 0.14964675380383113,
 0.10005481605134026,
 0.027033818100444362)

选择outlook作为根节点切分之后,分为3个分支。其中overcast分支不用再进行判断。其余两个分支按照同样方法进行切分,直到不可再分。 这就是ID3算法的计算过程。 缺点:如上计算,假设outlook中共计有14个类别, 其信息增益当然最大, 应该选择作为根节点切分,但此时并不一定是最好的切分特征,因为不具有泛化能力。此时选择信息增益比来进行改进。

信息增益比

可以使用如下公式计算:
$gainratio(p, T) = \frac{Gain(P, T)}{splitinfo(p, T)}$
$splitinfo(p, T) = -\sum_{j=1}^{n}p^`(\frac{j}{p}) * log(p^`(\frac{j}{p}))$
上面的过程已经计算除了分子,下面主要是分母的计算,如下:

In [87]:
splitinfo_s_outlook = entropy([5/14, 5/14, 4/14])
splitinfo_s_temperature = entropy([4/14, 4/ 14, 6/14])
splitinfo_s_humidity = entropy([7/14, 7/14])
splitinfo_s_wind = entropy([5/14, 9/14])

有了如上的分裂信息度量(表示的是特征内部的不确定性,如果不确定性越大,就越难以区分,因此就趋向于不去选择它), 算是对上述情况的一个补偿

In [88]:
print(gain_s_outlook / splitinfo_s_outlook)
print(gain_s_temperature / splitinfo_s_temperature)
print(gain_s_humidity / splitinfo_s_humidity)
print(gain_s_wind / splitinfo_s_wind)
0.15504000134556406
0.017366589544657203
0.14964675380383113
0.10640892286937578

选择信息增益比高的特征进行类别切分,其后过程和ID3算法类似。

经过以上计算,上述两种决策树还有已下特点:

  1. 缺失值的处理 如果某些特征有少量缺失值,决策树通过计算信息增益比,比如上述outlook特征中某个值缺失,但计算的结果相差不大, 因此可以处理少量缺失值的数据。
  2. 连续行特征 与ID3只能处理类别数据不同,C4.5还可以处理连续数据。方法是去连续数据中间的值,将该特征分为两类,进而计算信息增益比,也是选则最大值进行切分。
  3. 剪枝 根据训练集生成决策树,通常会对数据过拟合,并且对一些噪声值比较敏感。为了提高正确率, 需要对树进行剪枝。剪枝是一种通过删除树中提供很少分类实例的部分来减少决策树的大小的一种方法。
    决策树的剪枝

ID3 和C4.5算法的对比

ID3算法基于熵和信息增益来选择最优切分特征去构造树。而C4.5与ID3类似,但添加了部分特点:

  • 可以使用连续型数据
  • 可以包含少量缺失值
  • 能够使用不同权重的属性
  • 创建树之后进行后剪枝(悲观预测误差0.5 , 子树上升)
In [ ]:

CART

与前两种算法不同,CART分类树算法使用基尼系数来代替信息增益比,基尼系数代表了模型的不纯度,基尼系数越小,则不纯度越低,特征越好。这和信息增益(比)是相反的。 在分类问题中,假设有n个类别,概率为pn,计算方法如下:
$Gini(p)=\sum_{i=1}^{n}(p_i * (1 - p_i)) = 1 - \sum_{i=1}^{n}p_i^2$
特别的,假设样本D根据某个特征A将其分为两部分,其基尼系数如下:
$Gini(D,A) = \frac{|D_1|}{|D|}Gini(D_1) + \frac{|D_2|}{|D|}Gini(D_2)$

因此,CART使用二叉树来构建决策树。 可以利用上述数据计算基尼系数,如下:

In [94]:
def gini(attr_list):
    return 1 - np.sum(np.power(attr_list, 2))
In [112]:
for attr in ['outlook', 'temperature', 'humidity', 'wind']:
    print(tree_df.groupby([attr]).size())
    print('-' * 30)
outlook
overcast    4
rain        5
sun         5
dtype: int64
------------------------------
temperature
cold     4
hot      4
sweet    6
dtype: int64
------------------------------
humidity
high      7
normal    7
dtype: int64
------------------------------
wind
high    5
low     9
dtype: int64
------------------------------
In [116]:
# 可以看到不同的特征分为两类, 计算基尼系数,选择较小的值进行切分:
gini_s_outlook = gini([5/14, 5/14,5/14])
gini_s_temperature = gini([4/14, 4/14, 6/14])
gini_s_humidity = gini([7/14, 7/14])
gini_s_wind = gini([5/14, 9/14])
In [117]:
gini_s_outlook, gini_s_humidity, gini_s_wind, gini_s_temperature
Out[117]:
(0.6173469387755102, 0.5, 0.4591836734693877, 0.653061224489796)

因此选择最小的特征wind,构建利用上述分为两部分分别计算gini系数,从而确定分类类别,构建二叉树进行决策。

In [ ]:

集成方法

很容易想到,相比较于依赖一棵决策树并期望它能在每一个分支点都做出正确的决定,集成方法允许我们同时参考一堆决策树样本,计算出在每个分支点应该使用哪些特征、提出什么问题,并根据这些决策树样本汇总的结果来做出最后的预测。集成方法有bagging,boosting, stack等等。

bagging 和随机森林

随机森林是用随机方式建立,包含多个决策树的集成分类器。其输出由多个分类器投票决定,回归则取平均。假设样本总数n, 每个样本特征数为a, 生成过程如下:

  1. 从原始样本中有放回抽样,选择n个样本。
  2. 对n个样本随机选择a个特征中的k个,建立决策树。
  3. 重复m次,获得m棵决策树
  4. 进行投票预测。

其随机性主要体现在有放回的随机抽样和待选特征的随机选取,上述两种方法是的随机森林中的决策树都够彼此不同,提升系统的多样性,从而提升分类性能。

boosting和GBDT

boosting方法常见的有adaboosting和GBDT。其中adaboost在预测中,通过提升训练错误实例的重要性,带着这个重要性进行下次训练的方法。而GBDT而是利用之前树训练得到的残差进行下次训练,最后将所有结果相加得到最后的结果。GBDT中的决策树都是回归树而不是分类树, 适用面比较广,缺点在于若分类器的串行以来,难以并行训练。 一般boosting算法都是一个迭代的过程,每一次新的训练都是为了改进上次的结果,是为提升。

随机森林和GBDT的比较

  1. 随机森林使用bagging思想,而GBDT使用boosting思想,这两种方法都是booststrap思想的运用,都是有放回的随机抽样。区别在于:bagging采用有放回的均匀取样,而boosting根据错误率来取样(Boosting初始化时对每一个训练样例赋相等的权重1/n,然后用该算法对训练集训练t轮,每次训练后,对训练失败的样例赋以较大的权重),因为精度要好于bagging,也有了并行和串行的分别。
  2. 组成随机森林的可以是回归树和分类树;而GBDT只能由回归树组成。
  3. 组成随机森林的树可以并行生成;而GBDT只能是串行生成。
  4. 对于最终的输出结果而言,随机森林采用多数投票等;而GBDT则是将所有结果累加起来,或者加权累加起来。
  5. 随机森林对异常值不敏感;GBDT对异常值非常敏感。
  6. 随机森林对训练集一视同仁;GBDT是基于权值的弱分类器的集成。
  7. 随机森林是通过减少模型方差提高性能;GBDT是通过减少模型偏差提高性能。
In [ ]: