一颗决策树通常包括一个根节点,若干内部节点,若干叶节点。叶节点对应决策结果,其他节点则对应一个属性测试,每个节点包含的样本集合根据属性测试划分到子节点当中;根节点包含全部样本集。从根节点到每个叶节点的路劲对应了一个判定序列。其策略为“分而治之“。
1 划分选择
1.1 信息增益
信息熵:度量样本集合纯度的一种常用指标,假定当前样本集合D中第类样本所占的比列为
,则D的信息熵定义为
Ent(D)的值越小,则表示D的纯度越高。
信息增益:假定离散属性a有V个可能的取值,若使用a来对样本集D进行划分,则会产生V个分支,其中第v个分支节点包含了D中所有在属性a上取值为
的样本,记为
.可以根据上式计算出
的信息熵,考虑到不同节点所包含的样本数不同,给分支点赋予权重
,样本数越多分支结点影响越大,于是可计算出用属性a对样本集D进行划分所获得的”信息增益“
一般而言,信息增益越大,则意味着使用属性a来划分所获得的”纯度提升“越大,因此选择属性.
ID3决策树学习算法就是基于信心增益准则来划分属性的。
代码如下:
计算每个属性中取值的个数,比如色泽中青绿、乌黑、浅白的数量:
def calculateLable(label_arr):
label_count={}
for label in label_arr:
if label in label_count:
label_count[label] += 1
else:
label_count[label] = 1
return label_count
计算信息熵
'''
InfoEnt(label_arr)计算信息熵
'''
def InfoEnt(label_arr):
n = len(label_arr)
ent = 0
label_count = calculateLable(label_arr)
for key in label_count:
ent -= (label_count[key]/n) * (np.log2(label_count[key]/n))
return ent
计算信息增益
#计算信息增益
def InfoGain(df,index):
info_gain = InfoEnt(df.values[:,-1])
divide_value = 0
n = len(df[index]) #获取总数 eg:17
if df[index].dtype == "float64":
#初熵�化一熵字典用来存放二分之后的值所对应的信熵增益
sub_info_ent = {}
#指定熵 按找值进行排熵
df = df.sort_values([index],ascending=True)
df = df.reset_index(drop=True)
data_arr = df[index]
labele_arr = df[df.columns[-1]]
for i in range(n-1):
div = (data_arr[i] + data_arr[i + 1]) / 2
sub_info_ent[div] = ((i+1) / n * InfoEnt(labele_arr[0:i+1])) + ((n-i-1) / n * InfoEnt(labele_arr[i+1:-1]))
divide_value , sub_info_min = min(sub_info_ent.items(),key = lambda x: x[1])
info_gain -= sub_info_min
else:
#离散性变量的信息增益计算方式
atrribute_value = calculateLable(df[index]) #计算集合在属性上各个取值结果的熵熵 eg:{"青绿"熵6,"乌黑"熵6熵""浅白"熵5}
data_arr = df[index] #获得属性上的所有熵
classlabel = df[df.columns[-1]] #获取最后一列类型熵
for key in atrribute_value:
value = classlabel[data_arr == key] #获取数据集在某一属性上的取值的类别标熵?
info_gain -= (atrribute_value[key]/n)*InfoEnt(value) # 计算信息增益
return info_gain, divide_value
比较各个属性的信息增益,选择信息增益值最大的属熵
def optimalAtrribute(df):
info_gain = 0
for index in df.columns[1:-1]:
info_gain_temp, div_value_tmp = InfoGain(df,index)
if info_gain_temp > info_gain:
optAtrribute = index
info_gain = info_gain_temp
div_value = div_value_tmp
return optAtrribute, div_value
1.2增益率
信息增益准则对可取值数目比较多的属性有所偏好,C4.5决策树算法使用增益率来选择最优划分属性。增益率表示为
其中成为属性a的固有值。属性a的可能取值数目越多,则IV(a)的值通常会越大,增益率准则对取值数目比较少的属性有所偏好,C4.5算法不是直接选择增益率最大的候选划分属性,而是使用一个启发式:先从候选划分属性中找出信息增益高于平均水平的属性,在从中选择增益率最高的。
1.3 基尼指数
CART决策树采用”基尼指数“来选择划分属性,数据集D的纯度可以用基尼值来度量:
直观来说,基尼指数反映了从数据集D中随机抽取两个样本,其类别不一致的概率,因此,基尼指数越小,则数据集D的纯度越高。
属性a的基尼指数定义为
代码如下:
def Gini(arr):
gini = 1
n = len(arr)
label_count = calculate(arr)
for key in label_count:
gini -= (label_count[key]/n) * (label_count[key]/n)
return gini
def Gini_index(df, attr_id):
gini_index = 0
div_value = 0
data_arr = df[attr_id]
n = len(data_arr)
if df[attr_id].dtype == float:
sub_gini = {} # store the div_value (div) and it's subset gini value
df = df.sort([attr_id], ascending=1) # sorting via column
df = df.reset_index(drop=True)
data_arr = df[attr_id]
label_arr = df[df.columns[-1]]
for i in range(n-1):
div = (data_arr[i] + data_arr[i+1]) / 2
sub_gini[div] = ( (i+1) * Gini(label_arr[0:i+1]) / n ) \
+ ( (n-i-1) * Gini(label_arr[i+1:-1]) / n )
# our goal is to get the min subset entropy sum and it's divide value
div_value, gini_index = min(sub_gini.items(), key=lambda x: x[1])
else:
label_arr = df[df.columns[-1]]
data_count = calculate(data_arr)
for key in data_count:
key_label_arr = label_arr[data_arr == key]
gini_index = data_count[key]/n * Gini(key_label_arr)
return gini_index, div_value
决策树算法
class Node(object):
def __init__(self, attr_init=None, label_init=None, attr_down_init={} ):
self.attr = attr_init
self.label = label_init
self.attr_down = attr_down_init
# 生成决策树
def generateTree(df):
#生成父节点
new_node = Node(None,None,{})
# 类别
label_arr = df[df.columns[-1]]
#计算各个类别的数量,用字典存
label_count = calculateLable(label_arr)
if label_count: # assert the label_count isn's empty
new_node.label= max(label_count, key=label_count.get)
# end if there is only 1 class in current node data
# end if attribution array is empty
if len(label_count) == 1 or len(label_arr) == 0:
return new_node
# get the optimal attribution for a new branching
new_node.attr, div_value = optimalAtrribute(df)
# recursion
if div_value == 0: # categoric variable
value_count = calculateLable(df[new_node.attr])
for value in value_count:
df_v = df[ df[new_node.attr].isin([value]) ] # get sub set
# delete current attribution
df_v = df_v.drop(new_node.attr, 1)
new_node.attr_down[value] = generateTree(df_v)
else: # continuous variable # left and right child
value_l = "<=%.3f" % div_value
value_r = ">%.3f" % div_value
df_v_l = df[ df[new_node.attr] <= div_value ] # get sub set
df_v_r = df[ df[new_node.attr] > div_value ]
new_node.attr_down[value_l] = generateTree(df_v_l)
new_node.attr_down[value_r] = generateTree(df_v_r)
return new_node
2 剪枝处理
剪枝处理是决策树算法对付过拟合的主要手段。基本策略有”预剪枝“和”后剪枝“。
预剪枝是值在决策树生成过程中,对每个结点在划分的前进行估计,若当前结点的划分不能带来决策树泛化性能提升,则停止划分并将当前结点标记为叶节点。不仅降低了过拟合的风险,还显著减少了训练时间和测时间,但是预剪枝基于贪心本质禁止这些分支展开,给预剪枝带来欠拟合的风险。
后剪枝则是先从训练集生成一颗完成的决策树,然后自底向上地对非叶结点进行考察,若该结点对应的子树替换为叶节点能带来决策树泛化能力的提升,则将该子树替换为叶结点。后剪枝欠拟合风险较小,泛化性能往往优于预剪枝决策树,但是后剪枝决策树实在完全决策树之后进行的,并且要自底向上对所有非叶节点注意考察,训练时间要比预剪枝大得多。
3 连续值和缺失值
3.1连续值
可以把连续属性离散化,例如二分法对连续值进行处理。
给定样本集D和连续属性a,假定a在D上出现了n个不同的取值,将这些值从小到大进行排序,记为,基于划分点
可将样本集D分为子集
和
,分别表示那些在属性a上大于和小于
的样本,对于连续属性a可考察包含 n-1 个元素的候选划分点集合
即把区间的中位点作为候选点。
3.2 缺失值
对于缺失值我们需要解决两个问题
(1)如何在属性值缺失的情况下进行划分属性
(2)给定划分属性,若该样本在属性上的值缺失,如何对样本进行划分?
对于问题(1),给定训练集和属性
,令
表示
上没有缺失值的样本子集,假定属性
有
个可取值
,令
表示
中属性
上取值为
的样本子集,
表示
中属于第k类的样本子集。假定我们为每个样本
赋予一个权重
,并且定义:
对于属性a,
表示无缺失值样本所占的比列
对于属性a,
表示无缺失值样本中第k类所占的比列
对于属性a,
表示无缺失值样本中属性a上取值为
的样本所占的比例。
基于上述定义,将信息增益计算式推广为,其中
。
对于问题(2)若样本在划分属性a上的取值未知,则将
同时划分进所有子结点,且样本权值在与属性值
对应的子节点中调整为
,直观来看这就是让用一个样本以不同的概率划入不同的子结点中。
4 多变量决策树
多变量决策树不再是仅对某个属性,而是对属性的线性组合进行测试,换言之,每个非叶节点都是一个线性分类器。