决策树在机器学习中有着广泛的应用,主要是因为它易于解释。例如,现在要做一个决策:周末是否要打球,我们可能要考虑下面几个因素。第一,天气因素,如果是晴天,我们就打球,如果是雨天我们就不打球。第二,球场是否满员,如果满员,我们就不打球,如果不满员我们就打球。第三,是否需要加班,如果加班则不打球,如果不需要加班则打球。这样我们就形成了一个决策树,如图 1 所示。
可以将这个决策过程抽象一下,每个决策点称为分支节点,如图 2 所示。
看起来很简单,但是有一个问题,如何确定决策点的顺序呢?例如在是否打球的决策树中,我们为什么会选择天气因素作为第一个决策点,而不将球场是否满员作为第一个决策点呢?
这是一个很有意思的问题,在构造决策树时,需要将那些起决定性作用的特征作为首要的决策点。所以我们要评估每一个特征,然后将它们按作用大小排列。
而这个决定性作用应该如何衡量呢?首先,要了解一个概念——“熵”。熵是指信息的期望值。信息熵的公式为:
假设我们有如下数据,如表 3 所示。
- In [1]: import numpy as np
- ...: import pandas as pd
- In [2]: data = [
- ...: [1, 0, 0, '打球'],
- ...: [1, 0, 1, '打球'],
- ...: [0, 1, 1, '打球'],
- ...: [0, 0, 1, '不打球'],
- ...: [1, 1, 1, '打球']
- ...: ]
In [3]: df=pd.DataFrame(data)
- In [4]: data_length = len(data)
- ...: data_length
- Out[4]: 5
In [5]: target = df.iloc[:,-1]
- In [6]: label_counts = target.value_counts()
- ...: label_counts
- Out[6]:
- 打球 4
- 不打球 1
- Name: 3, dtype: int64
- In [7]: label_dict = label_counts.to_dict()
- ...: label_dict
- Out[7]: {'打球': 4, '不打球': 1}
-
In [8]: entropy = 0
- In [9]: for key in label_dict:
- ...: prob = float(label_dict[key])/data_length
- ...: entropy -= prob * np.log2(prob)
10) 查看结果
- In [10]: entropy
- Out[10]: 0.7219280948873623
知道了如何计算信息熵,接下来就是要计算信息增益了,信息增益就是信息熵的差值。在计算信息增益之前要对数据进行分割。
In [1]: import pandas as pd
- In [2]: data = [
- ...: [1, 0, 0, '打球'],
- ...: [1, 0, 1, '打球'],
- ...: [0, 1, 1, '打球'],
- ...: [0, 0, 1, '不打球'],
- ...: [1, 1, 1, '打球']
- ...: ]
可以看到默认 columns是 0,1,2,3,他们分别代表了 0:天气因素,1:是否满员,2:是否加班,3:目标变量。
- In [3]: df = pd.DataFrame(data)
- ...: df
- Out[3]:
- 0 1 2 3
- 0 1 0 0 打球
- 1 1 0 1 打球
- 2 0 1 1 打球
- 3 0 0 1 不打球
- 4 1 1 1 打球
- In [4]: selector = df.loc[:,0]==1
- ...: selector
- Out[4]:
- 0 True
- 1 True
- 2 False
- 3 False
- 4 True
- Name: 0, dtype: bool
这里筛选的是特征 0 中数值为 1 的实例。
- df_split = df[selector]
- df_split
- Out[5]:
- 0 1 2 3
- 0 1 0 0 打球
- 1 1 0 1 打球
- 4 1 1 1 打球
我们挑选出来了特征 0 中数值为 1 的实例,如表 2 所示。
同样的道理,我们可以挑选出特征 0 中数值为 0 的实例,如表 3 所示。
我们将通过信息增益来评价每个特征的重要程度。首先,将上述信息熵与分割数据的代码封装成函数,因为在计算信息增益过程中要反复用到。
- In [1]: import numpy as np
- ...: import pandas as pd
- In [2]: data = [
- ...: [1, 0, 0, '打球'],
- ...: [1, 0, 1, '打球'],
- ...: [0, 1, 1, '打球'],
- ...: [0, 0, 1, '不打球'],
- ...: [1, 1, 1, '打球']
- ...: ]
- In [3]: def ent(data):
- ...: df=pd.DataFrame(data)
- ...: data_length = len(data)
- ...: target = df.iloc[:,-1]
- ...: label_counts = target.value_counts()
- ...: label_dict = label_counts.to_dict()
- ...: entropy = 0
- ...: for key in label_dict:
- ...: prob = float(label_dict[key])/data_length
- ...: entropy -= prob * np.log2(prob)
- ...: return entropy
- In [4]: ent(data)
- Out[4]: 0.7219280948873623
- In [5]: def split(data,feature_rank):
- ...: df = pd.DataFrame(data)
- ...: groups = df.groupby(by=feature_rank)
- ...: data_group = {}
- ...: for key in groups.groups.keys():
- ...: data_group[key] = df.loc[groups.groups[key], :]
- ...: return data_group
- In [6]: data_group=split(data,0)
- In [7]: data_group.keys()
- Out[7]: dict_keys([0, 1])
- In [8]: data_group[0]
- Out[8]:
- 0 1 2 3
- 2 0 1 1 打球
- 3 0 0 1 不打球
- In [9]: data_group[1]
- Out[9]:
- 0 1 2 3
- 0 1 0 0 打球
- 1 1 0 1 打球
- 4 1 1 1 打球
以上步骤主要是函数的封装,下面正式进入信息增益的计算。
In [10]: init_ent = ent(data)
这里选用第 1 列特征。
In [11]: feature_rank = 0
In [12]: data_group = split(data,feature_rank)
13) 初始化将要计算的熵的值
In [13]: new_ent = 0
- In [14]: for key in data_group:
- ...: prob = len(data_group[key])/len(data)
- ...: new_ent += prob*ent(data_group[key])
- In [15]: info_gain=init_ent-new_ent
- ...: info_gain
- Out[15]: 0.3219280948873623
接下来,通过实例看一下如何计算特征 0 的信息增益。首先选择表 4 中虚线框内的列。
根据该列特征所包含的值将原表分割成表 5 和表 6。然后分别计算表 5 和 6 的信息熵,再用二者的熵求整个特征的熵。
同样的道理,我们还可以计算出其他特征的信息熵,最后的结果如表 7 所示。
根据特征的得分可以看到,特征 0(也就是天气)得分最高,所以我们把它作为第 1 个分支节点,同样的道理可以递归找寻下一层的分支节点。