决策树算法的实现
1.基本流程
决策树是一种常见的机器学习方法,在以二分类任务为例,我们希望从给定的训练集学得一个模型用以对新示例进行分类,这个把样本分类的任务,可看作对“当前样本属于正类吗?“这个问题的决策或者判别的过程,顾名思义,决策树是基于树的结构
来进行决策的,是一种有监督的学习算法
。
2.决策树的构建
(1).特征选择:选取有较强分类能力
的特征
(2).决策树生成:典型的算法有ID3和C4.5
,ID3是采用信息增益作为特征选择度量,而C4.5采用信息增益率
(3).决策树剪枝:剪枝是决策树学习算法对付过拟合的主要手段
,在决策树学习中,为了尽可能正确分类训练样本,结点划分过程将不断重复,有时会造成决策树分支过多,这时就可能因为训练样本学的太好了,以至于把训练集自身的一些特点当作数据都具有的一般性质而导致过拟合
3.划分选择
属性选择度量
信息增益:
G
a
i
n
(
D
,
a
)
=
E
n
t
(
D
)
−
∑
v
=
1
V
∣
D
v
∣
∣
D
∣
E
n
t
(
D
v
)
Gain(D,a)=Ent(D)-\sum_{v=1}^{V} \frac{\mid D^{v} |}{|D|} Ent(D^{v})
Gain(D,a)=Ent(D)−∑v=1V∣D∣∣Dv∣Ent(Dv)
增益率:
G
a
i
n
r
a
t
i
o
(
D
,
a
)
=
G
a
i
n
(
D
,
a
)
I
V
(
a
)
Gainratio(D,a)=\frac{Gain(D,a)}{IV(a)}
Gainratio(D,a)=IV(a)Gain(D,a)
I
V
(
a
)
=
−
∑
v
=
1
V
∣
D
v
∣
∣
D
∣
log
2
∣
D
v
∣
∣
D
∣
IV(a)=-\sum_{v=1}^{V} \frac{|D^{v}| }{|D|} \log_{2}\frac{|D^{v}| }{|D|}
IV(a)=−∑v=1V∣D∣∣Dv∣log2∣D∣∣Dv∣
基尼指数:
G
i
n
i
(
D
)
=
1
−
∑
k
=
1
∣
y
∣
p
k
2
Gini(D)=1-\sum_{k=1}^{|y|} p_{k}^{2}
Gini(D)=1−∑k=1∣y∣pk2
G
i
n
i
i
n
d
e
x
(
D
,
a
)
=
∑
v
=
1
V
∣
D
v
∣
∣
D
∣
G
i
n
i
(
D
v
)
Gini_index(D,a)=\sum_{v=1}^{V} \frac{|D^{v} |}{|D|} Gini(D^{v} )
Giniindex(D,a)=∑v=1V∣D∣∣Dv∣Gini(Dv)
例1(周志华西瓜书)计算信息增益,增益率
import math
import numpy as np
# 创建西瓜数据集
def createData():
data = np.array([['青绿', '蜷缩', '浊响', '清晰', '凹陷', '硬滑']
, ['乌黑', '蜷缩', '沉闷', '清晰', '凹陷', '硬滑']
, ['乌黑', '蜷缩', '浊响', '清晰', '凹陷', '硬滑']
, ['青绿', '蜷缩', '沉闷', '清晰', '凹陷', '硬滑']
, ['浅白', '蜷缩', '浊响', '清晰', '凹陷', '硬滑']
, ['青绿', '稍蜷', '浊响', '清晰', '稍凹', '软粘']
, ['乌黑', '稍蜷', '浊响', '稍糊', '稍凹', '软粘']
, ['乌黑', '稍蜷', '浊响', '清晰', '稍凹', '硬滑']
, ['乌黑', '稍蜷', '沉闷', '稍糊', '稍凹', '硬滑']
, ['青绿', '硬挺', '清脆', '清晰', '平坦', '软粘']
, ['浅白', '硬挺', '清脆', '模糊', '平坦', '硬滑']
, ['浅白', '蜷缩', '浊响', '模糊', '平坦', '软粘']
, ['青绿', '稍蜷', '浊响', '稍糊', '凹陷', '硬滑']
, ['浅白', '稍蜷', '沉闷', '稍糊', '凹陷', '硬滑']
, ['乌黑', '稍蜷', '浊响', '清晰', '稍凹', '软粘']
, ['浅白', '蜷缩', '浊响', '模糊', '平坦', '硬滑']
, ['青绿', '蜷缩', '沉闷', '稍糊', '稍凹', '硬滑']])
label = np.array(['是', '是', '是', '是', '是', '是', '是', '是', '否', '否', '否', '否', '否', '否', '否', '否', '否'])
name = np.array(['色泽', '根蒂', '敲声', '纹理', '脐部', '触感'])
return data, label, name
# 使用lambda关键字来声明一个匿名函数,可以有很多的参数,但是只能有一个表达式
equalNums = lambda x,y: 0 if x is None else x[x==y].size
# 定义计算信息熵的函数
def singleEntropy(x):
# print(x)
x = np.asarray((x))
# print(x)
xValues = set(x) # 取x矩阵的不同值
# print(xValues)
entropy = 0
for xValue in xValues:
p = equalNums(x,xValue) / x.size
# print(equalNums(x,xValue))
entropy -= p * math.log(p, 2) # 将运算符前面的变量减去后面的值再赋值给前面的变量
return entropy
# 定义计算条件信息熵的函数
def conditionnalEntropy(feature, y):
print(feature)
feature = np.asarray(feature)
y = np.asarray(y)
print(feature)
featureValues = set(feature)
entropy = 0
for feat in featureValues:
p = equalNums(feature, feat) / feature.size
entropy += p * singleEntropy(y[feature == feat])
return entropy
# 定义信息增益
def infoGain(feature, y):
return singleEntropy(y) - conditionnalEntropy(feature, y)
# 定义信息增益率
def infoGainRatio(feature, y):
return 0 if singleEntropy(feature) == 0 else infoGain((feature,y))/ singleEntropy(feature)
xgData, xgLabel,xgname = createData()
print("书中Ent(D)为0.998,函数结果:" + str(round(singleEntropy(xgLabel), 4)))
print("书中Gain(D,色泽)为0.109,函数结果;"+str(round(infoGain(xgData[:, 0],xgLabel),4)))
print("书中Gain(D,根蒂)为0.143,函数结果;"+str(round(infoGain(xgData[:, 1],xgLabel),4)))
print("书中Gain(D,敲声)为0.141,函数结果;"+str(round(infoGain(xgData[:, 2],xgLabel),4)))
print("书中Gain(D,纹理)为0.381,函数结果;"+str(round(infoGain(xgData[:, 3],xgLabel),4)))
print("书中Gain(D,脐部)为0.289,函数结果;"+str(round(infoGain(xgData[:, 4],xgLabel),4)))
print("书中Gain(D,触感)为0.006,函数结果;"+str(round(infoGain(xgData[:, 5],xgLabel),4)))
ID3决策树
流程
1.先根据最大信息增益选取一个特征作为根节点
2.以根节点特征的取值作为分支递归生成节点,在递归中注意:
(1)每次取特征值时需要删除之前取过的数据
(2)当当前样本只有一类时,返回该类别作叶子结点,即分类结果
(3)当当前所有样本的特征值都一样时,选样本最多的类作为叶子结点
3.使用测试数据特征测试决策树的预测能力
基础代码实现
import pandas as pd
import numpy as np
#计算信息熵
def cal_information_entropy(data):
# print("计算信息熵")
data_label = data.iloc[:,-1]
# print(data_label)
label_class =data_label.value_counts() #总共有多少类
# print(label_class)
Ent = 0
for k in label_class.keys():
p_k = label_class[k]/len(data_label)
Ent += -p_k*np.log2(p_k)
# print(Ent)
return Ent
#计算给定数据属性a的信息增益
def cal_information_gain(data, a):
# print("计算给定数据属性a的信息增益")
Ent = cal_information_entropy(data)
feature_class = data[a].value_counts() #特征有多少种可能
# print(feature_class)
gain = 0
# print(feature_class.keys())
for v in feature_class.keys():
weight = feature_class[v]/data.shape[0]
# print(feature_class[0])
# print(feature_class[v])
# print(data.shape[0]) # 该数据集的行数
Ent_v = cal_information_entropy(data.loc[data[a] == v])
# print("zheli")
# print(data.loc[data[a] == v])
gain += weight*Ent_v
return Ent - gain
#获取标签最多的那一类
def get_most_label(data):
print("获取标签最多的那一列")
data_label = data.iloc[:,-1]
print(data_label)
label_sort = data_label.value_counts(sort=True)
print("er")
print(label_sort)
print("re")
print(label_sort.keys()[0])
return label_sort.keys()[0]
#挑选最优特征,即信息增益最大的特征
def get_best_feature(data):
#print("挑选最优特征,即信息增益最大的特征")
features = data.columns[:-1]
#print(features)
res = {}
for a in features:
#print(a)
temp = cal_information_gain(data, a)
res[a] = temp
print("res里面有什么东西")
print(res)
res = sorted(res.items(),key=lambda x:x[1],reverse=True)
print(res)
return res[0][0]
##将数据转化为(属性值:数据)的元组形式返回,并删除之前的特征列
def drop_exist_feature(data, best_feature):
attr = pd.unique(data[best_feature])
# print(attr)
new_data = [(nd, data[data[best_feature] == nd]) for nd in attr]
# print(new_data)
# for n in new_data:
# print(n[0])
# print(n[1].drop([best_feature], axis=1))
new_data = [(n[0], n[1].drop([best_feature], axis=1)) for n in new_data]
print(new_data)
return new_data
#创建决策树
def create_tree(data):
data_label = data.iloc[:,-1] # 输出最后一列
print(data)
print(data_label)
print(len(data_label.value_counts()))
if len(data_label.value_counts()) == 1: #只有一类
print("只有一类")
print(data_label.values[0])
return data_label.values[0]
if all(len(data[i].value_counts()) == 1 for i in data.iloc[:,:-1].columns): #所有数据的特征值一样,选样本最多的类作为分类结果
print("所有数据的特征值一样,选样本最多的类作为分类结果")
print("suoyou")
return get_most_label(data)
best_feature = get_best_feature(data) #根据信息增益得到的最优划分特征
print(best_feature)
Tree = {best_feature:{}} #用字典形式存储决策树
print(Tree)
exist_vals = pd.unique(data[best_feature]) #当前数据下最佳特征的取值
print(exist_vals)
print(column_count[best_feature])
if len(exist_vals) != len(column_count[best_feature]): #如果特征的取值相比于原来的少了
no_exist_attr = set(column_count[best_feature]) - set(exist_vals) #少的那些特征
for no_feat in no_exist_attr:
Tree[best_feature][no_feat] = get_most_label(data) #缺失的特征分类为当前类别最多的
print(data)
print(best_feature)
for item in drop_exist_feature(data,best_feature): #根据特征值的不同递归创建决策树
Tree[best_feature][item[0]] = create_tree(item[1])
return Tree
#{'纹理': {'清晰': {'根蒂': {'蜷缩': 1, '稍蜷': {'色泽': {'青绿': 1, '乌黑': {'触感': {'硬滑': 1, '软粘': 0}}}}, '硬挺': 0}}, '稍糊': {'触感': {'软粘': 1, '硬滑': 0}}, '模糊': 0}}
def predict(Tree , test_data):
print("预测结果")
print(Tree)
print(test_data)
first_feature = list(Tree.keys())[0]
print(first_feature)
second_dict = Tree[first_feature]
print(second_dict)
input_first = test_data.get(first_feature)
input_value = second_dict[input_first]
print(input_first)
print(input_value)
if isinstance(input_value , dict): #判断分支还是不是字典
class_label = predict(input_value, test_data)
else:
class_label = input_value
return class_label
if __name__ == '__main__':
#读取数据,将都逗号分离的值文件读入数据框架
data = pd.read_csv('西瓜数据集2.1.csv')
# print(data)
#统计每个特征的取值情况作为全局变量
column_count = dict([(ds, list(pd.unique(data[ds]))) for ds in data.iloc[:, :-1].columns])
# print(data.iloc[1:2, 2:6].columns) #索引切片操作
# print(data.iloc[:, :-1].columns)
# print(column_count)
#创建决策树
dicision_Tree = create_tree(data)
print(dicision_Tree)
#测试数据
test_data_1 = {'色泽':'青绿','根蒂':'蜷缩','敲声':'浊响','纹理':'稍糊','脐部':'凹陷','触感':'硬滑'}
test_data_2 = {'色泽': '乌黑', '根蒂': '稍蜷', '敲声': '浊响', '纹理': '清晰', '脐部': '凹陷', '触感': '硬滑'}
result = predict(dicision_Tree,test_data_1)
print('分类结果为')
print('分类结果为'+'好瓜'if result == 1 else '坏瓜')
决策树构建的词典:
{'纹理': {'清晰': {'根蒂': {'蜷缩': 1, '稍蜷': {'色泽': {'浅白': 1, '青绿': 1, '乌黑': {'触感': {'硬滑': 1, '软粘': 0}}}}, '硬挺': 0}}, '稍糊': {'触感': {'软粘': 1, '硬滑': 0}}, '模糊': 0}}
C4.5决策树
流程
信息增益的缺点在于对取值数目较多的属性有所偏好,而信息增益率会对可取值数目较小的特征有偏好,为了避免这个问题,C4.5并不是直接使用信息增益率的大小进行划分特征,而是从候选划分特征中找出信息增益高于平均水平的树栖性,再从中选择增益率最高的特征
基础代码实现
CART决策树
流程
根据训练数据集,从根结点开始,递归的对结点进行一下操作
(1)计算各特征的基尼指数,选择最优特征以及其最优切分点
基础代码实现
4.决策树剪枝
剪枝分为预剪枝和后剪枝
预剪枝在决策树生成过程中对每个节点先进行估计
,如果划分能带来准确率上升,则划分,否则不划分节点;
后剪枝则是先使用训练集生成一颗决策树,再使用测试集对其节点进行评估
,若将子树替换为叶子结点能带来准确率的提升则替换
5.连续值决策树,缺失值决策树
6.决策树的优缺点
优点:
1.速度快
,计算量相对较小,且容易转化成分类规则,只要沿着树根向下一直走到叶,沿途分裂的条件就能唯一
2.准确性高
,挖掘出的分类规则准确性高,便于理解,决策树可以清晰的显示哪些字段比较重要
3.非参数学习
,不需要设置参数
缺点:
1.决策树很容易过拟合
,很多即使进行后剪枝也无法避免过拟合的问题,所以可以设置树高或者叶结点的样本个树来进行预剪枝控制
2.决策树属于样本敏感型
,即使样本发生一点点改动,也会导致整个树结果的变化,可以通过集成算法来解决