决策树python实现和sklearn'实现
目录
1.概览
2.实现
2.1python实现
# -*- coding: utf-8 -*-
"""
Created on Sun Aug 26 11:17:27 2018
决策树-python实现
数据源:给定外貌特征实现男女分类
注意:
1.数学:
1.log函数 log以2为底,a为对数
import math
math.log(a,2)
2.去重
set(a) 获取a去重后所有的类别
3.去索引
value是series格式
continues_value=[i for i in value]
4,求字典的key最大值
final=max(t_ent,key=t_ent.get)
5.map(function(),iterator)
它接收一个函数 f 和一个 list,并通过把函数 f 依次作用在 list 的每个元素上,得到一个新的 list 并返回。
map(lambda x: x ** 2, [1, 2, 3, 4, 5]) # 使用 lambda 匿名函数
[1, 4, 9, 16, 25]
2.代码
1.信息熵计算
遍历每行数据,找到目标数据,计数存储到字典
循环字典,求信息熵
2.先对连续性字段进行二分法寻找最优切分点,找到后,对数据进行离散化(将数据以切分点进行分类,大于是一类,小于是一类)
3.难点:二叉有向树建树!
遍历二叉有向树
"""
"""生成示例数据
"""
import numpy as np
import pandas as pd
def create_data():
data_value = np.array([['long', 'thick', 175, 'no', 'man'], ['short', 'medium', 168, 'no', 'man'],
['short', 'thin', 178, 'yes', 'man'], ['short', 'thick', 172, 'no', 'man'],
['long', 'medium', 163, 'no', 'man'], ['short', 'thick', 180, 'no', 'man'],
['long', 'thick', 173, 'yes', 'man'], ['short', 'thin', 174, 'no', 'man'],
['long', 'thin', 164, 'yes', 'woman'], ['long', 'medium', 158, 'yes', 'woman'],
['long', 'thick', 161, 'yes', 'woman'], ['short', 'thin', 166, 'yes', 'woman'],
['long', 'thin', 158, 'no', 'woman'], ['short', 'medium', 163, 'no', 'woman'],
['long', 'thick', 161, 'yes', 'woman'], ['long', 'thin', 164, 'no', 'woman'],
['short', 'medium', 172, 'yes','woman']] )
columns = np.array(['hair', 'voice', 'height', 'ear_stud','labels'])
#print('data_value',data_value)
data=pd.DataFrame(data_value,columns=columns)
return data
data = create_data()
print(data)
"""计算信息熵:即根节点纯度(根节点:男性,女性)
"""
import math
def get_Ent(data):
num_sample=len(data)
label_counts={}
for i in range(num_sample):
each_data=data.iloc[i,:]
current_label=each_data['labels']
if current_label not in label_counts.keys():
label_counts[current_label]=0
label_counts[current_label]+=1
Ent=0
for key in label_counts:
prob=label_counts[key]/num_sample
Ent-=prob*math.log(prob,2)
return Ent
base_ent=get_Ent(data)
"""计算 以hair分类信息增益
"""
def get_gain(data,base_ent,feature):
feature_list=data[feature]
unique_value=set(feature_list)
feature_ent=0
for each_feature in unique_value:
temp_data=data[data[feature]==each_feature]
weight=len(temp_data)/len(feature_list)
temp_ent=weight*get_Ent(temp_data)
feature_ent+=temp_ent
gain=base_ent-feature_ent
return gain
"""连续值的划分点求信息增益
"""
def get_splitpoint(data,base_ent,feature):
value=data[feature].sort_values().astype(np.float64)
#print('value',value)
continues_value=[i for i in value]
print('continues_value',continues_value)
t_set=[]
t_ent={}
for i in range(len(continues_value)-1):
temp_t=(continues_value[i]+continues_value[i+1])/2
t_set.append(temp_t)
for each_t in t_set:
temp1_data=data[data[feature].astype(np.float64)>each_t]
temp2_data=data[data[feature].astype(np.float64)<each_t]
weight1=len(temp1_data)/len(data)
weight2=len(temp2_data)/len(data)
temp_ent=base_ent-weight1*get_Ent(temp1_data)-weight2*get_Ent(temp2_data)
t_ent[each_t]=temp_ent #t_ent {158.0: 0.1179805181500242
final_t=max(t_ent,key=t_ent.get)
return final_t
final_t=get_splitpoint(data,base_ent,'height')
#print('final',final)
"""数据预处理,将数据划分为倆类
在找到最佳划分点之后,将 <t<t 的值设为 0,将 >t>t 的值设为 1。
此处是将>t的标志位>t 相反:<t
"""
def choice_1(x, t):
if x > t:
return ">{}".format(t)
else:
return "<{}".format(t)
deal_data = data.copy()
# 使用lambda和map函数将 height 按照final_t划分为两个类别
deal_data["height"] = pd.Series(
map(lambda x: choice_1(int(x), final_t), deal_data["height"]))
print('deal_data',deal_data)
"""选择最优划分特征
遍历所有特征
"""
def choose_feature(data):
"""
参数:
data -- 数据集
返回:
best_feature -- 最优的划分特征
"""
num_features = len(data.columns) - 1 # 特征数量
base_ent = get_Ent(data)
best_gain = 0.0 # 初始化信息增益
best_feature = data.columns[0]
for i in range(num_features): # 遍历所有特征
temp_gain = get_gain(data, base_ent, data.columns[i]) # 计算信息增益
if (temp_gain > best_gain): # 选择最大的信息增益
best_gain = temp_gain
best_feature = data.columns[i]
return best_feature # 返回最优特征
print(choose_feature(deal_data))
"""构建决策树
"""
def create_tree(data):
"""
参数:
data -- 数据集
返回:
tree -- 以字典的形式返回决策树
"""
feature_list = data.columns[:-1].tolist()
label_list = data.iloc[:, -1]
if len(data["labels"].value_counts()) == 1:
leaf_node = data["labels"].mode().values
print('data["labels"].mode()',data["labels"].mode(),'leaf_node',leaf_node)
return leaf_node # 第一个递归结束条件:所有的类标签完全相同
if len(feature_list) == 1:
leaf_node = data["labels"].mode().values
return leaf_node # 第二个递归结束条件:用完了所有特征
print('len(data["labels"].value_counts())',len(data["labels"].value_counts()))
print('len(feature_list)',len(feature_list),'feature_list',feature_list)
best_feature = choose_feature(data) # 最优划分特征#height,ear_stud,voice,,
print('best_feature',best_feature)#
tree = {best_feature: {}}#1.{'height': {}} 2.{'ear_stud': {}}
print('tree',tree)
feat_values = data[best_feature]#series 1--<172.0 2-->172.0
print('feat_values',feat_values)
unique_value = set(feat_values)#1.{'<172.0', '>172.0'} 2.{'no', 'yes'}
print('unique_value',unique_value)
for value in unique_value:
temp_data = data[data[best_feature] == value]#1.过滤掉>172数据,剩下height<172.0的数据 2.过滤yes数据,剩下no的数据。。。。3遍历到voice时,只剩一个数据short thick man
print('temp_data-1',temp_data)
temp_data = temp_data.drop([best_feature], axis=1)#删除身高<172.0这一列,2.删除no这一列
print('temp_data-2',temp_data)
tree[best_feature][value] = create_tree(temp_data)#在这里递归,3voice/thick时候,tree-1 ['man'],然后遍历voice/medium,tree-1 ['man'],voice/thin['woman']
print('tree-1',tree[best_feature][value])#{'voice': {'thick': array(['man'], dtype=object), 'medium': array(['man'], dtype=object), 'thin': array(['woman'], dtype=object)}}
return tree
tree = create_tree(deal_data)
print(tree)
"""决策分类
"""
def classify(tree, test):
"""
参数:
data -- 数据集
test -- 需要测试的数据
返回:
class_label -- 分类结果
"""
first_feature = list(tree.keys())[0] # 获取根节点 ['height']
print('tree-keys',list(tree.keys()))
feature_dict = tree[first_feature] # 根节点下的树 {'<172.0': {'ear_stud': {'no':
print('feature_dict',feature_dict)
print('feature_dict.keys()',feature_dict.keys()) # dict_keys(['<172.0', '>172.0'])
labels = test.columns.tolist()
value = test[first_feature][0] # <172.0
print('value',value)
for key in feature_dict.keys():
if value == key:
if type(feature_dict[key]).__name__ == 'dict': # 判断该节点是否为叶节点
class_label = classify(feature_dict[key], test) # 采用递归直到遍历到叶节点
else:
class_label = feature_dict[key]
return class_label
test = pd.DataFrame({"hair": ["long"], "voice": ["thin"], "height": [163], "ear_stud": ["yes"]})
test["height"] = pd.Series(map(lambda x: choice_1(int(x), final_t), test["height"]))
print('111',test)
a=classify(tree,test)
print('222',a)
部分结果图
2.1.1构建决策树过程图例
2.2 sklearn实现
# -*- coding: utf-8 -*-
"""
Created on Mon Sep 3 21:48:42 2018
决策树分类--sklearn实现
数据:
学生成绩数据集 course-13-student.csv,一共有 395 条数据,26 个特征
http://labfile.oss.aliyuncs.com/courses/1081/course-13-student.csv
特征太多,随机筛选这些特征作为分类特征
school:学生所读学校(GP,MS) sex: 性别(F:女,M:男)
address: 家庭住址(U:城市,R:郊区)
Pstatus: 父母状态(A:同居,T:分居)
Pedu: 父母学历由低到高
reason: 选择这所学校的原因(home:家庭,course:课程设计,reputation:学校地位,other:其他)
guardian: 监护人(mother:母亲,father:父亲,other:其他)
studytime: 周末学习时长
schoolsup: 额外教育支持(yes:有,no:没有)
famsup: 家庭教育支持(yes:有,no:没有)
paid: 是否上补习班(yes:是,no:否)
higher: 是否想受更好的教育(yes:是,no:否)
internet: 是否家里联网(yes:是,no:否)
G1: 一阶段测试成绩
G2: 二阶段测试成绩
G3: 最终成绩
"""
"""导入数据集并预览
"""
import pandas as pd
stu_grade = pd.read_csv('course-13-student.csv')
print(stu_grade.head())
new_data = stu_grade.iloc[:, [0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 14, 15, 24, 25, 26]]
"""数据预处理 : 处理连续字段
成绩划分 : 成绩 G1,G2,G3 根据分数进行等级划分,将 0-4 划分为 bad,5-9 划分为 medium ,10-15 good,16--优秀
"""
def choice_2(x):
x = int(x)
if x < 5:
return "bad"
elif x >= 5 and x < 10:
return "medium"
elif x >= 10 and x < 15:
return "good"
else:
return "excellent"
stu_data = new_data.copy()
stu_data["G1"] = pd.Series(map(lambda x: choice_2(x), stu_data["G1"]))
stu_data["G2"] = pd.Series(map(lambda x: choice_2(x), stu_data["G2"]))
stu_data["G3"] = pd.Series(map(lambda x: choice_2(x), stu_data["G3"]))
"""对 Pedu (父母教育程度)也进行划分
"""
def choice_3(x):
x = int(x)
if x > 3:
return "high"
elif x > 1.5:
return "medium"
else:
return "low"
stu_data["Pedu"] = pd.Series(map(lambda x: choice_3(x), stu_data["Pedu"]))
print(stu_data.head())
"""特征值替换
字符特征转为数字0-1特征
"""
def replace_feature(data):
"""
参数:
data -- 数据集
返回:
data -- 将特征值替换后的数据集
"""
for each in data.columns: # 遍历每一个特征名称
feature_list = data[each]
unique_value = set(feature_list)
i = 0
for fea_value in unique_value:
data[each] = data[each].replace(fea_value, i)
i += 1
return data
stu_data = replace_feature(stu_data)
print('stu_data.head(10)',stu_data.head(10))
"""数据集划分: 7:3
x_train对应y_train, x_test,对应y_test
"""
from sklearn.model_selection import train_test_split
x_train, x_test, y_train, y_test = train_test_split(stu_data.iloc[:, :-1], stu_data["G3"],
test_size=0.3, random_state=5)
"""决策树构建
DecisionTreeClassifier(criterion=’gini’,random_state=None)
criterion :表示特征划分方法选择,默认为 gini (在后面会讲到),可选择为 entropy (信息增益)。
ramdom_state :表示随机数种子,当特征特别多时 sicit-learn 为了提高效率,随机选取部分特征来进行特征选择,
即找到所有特征中较优的特征。
"""
from sklearn.tree import DecisionTreeClassifier
dt_model = DecisionTreeClassifier(criterion='entropy', random_state=34)
"""模型预测
"""
dt_model.fit(x_train,y_train) # 使用训练集训练模型
y_predict = dt_model.predict(x_test) # 使用模型对测试集进行预测
y_predict
"""分类准确率计算
一致的/总共的
"""
def get_accuracy(test_labels, pred_lables):
"""
参数:
test_labels -- 测试集的真实值
pred_labels -- 测试集的预测值
返回:
accur -- 准确率
"""
correct = np.sum(test_labels == pred_lables) # 计算预测正确的数据个数
n = len(test_labels) # 总测试集数据个数
accur = correct/n
return accur
print('predict',get_accuracy(y_test, y_predict))
#"""展示决策树生成
#"""
#from sklearn.tree import export_graphviz
#import graphviz
#import numpy as np
#
#img = export_graphviz(
# dt_model, out_file=None,
# feature_names=stu_data.columns[:-1].values.tolist(), # 传入特征名称
# class_names=np.array(["bad", "medium", "good", "excellent"]), # 传入类别值
# filled=True, node_ids=True,
# rounded=True)
#
#graphviz.Source(img) # 展示决策树
部分结果图
3.CART决策树
分类与回归树(classification and regression tree, CART)同样也是应用广泛的决策树学习算法,CART 算法是按照特征划分,由树的生成和树的剪枝构成,既可以进行分类又可以用于回归,按照作用将其分为决策树和回归树,由于本实验设计为决策树的概念,所以回归树的部分有兴趣的同学可以自己查找相关资料进一步学习。
CART决策树的构建和常见的 ID3 和 C4.5 算法的流程相似,但在特征划分选择上CART选择了 基尼指数 作为划分标准。数据集 D 的纯度可用基尼值来度量:
基尼指数表示随机抽取两个样本,两个样本类别不一致的概率,基尼指数越小则数据集的纯度越高。同样对于每一个特征值的基尼指数计算,其和 ID3 、 C4.5 相似,定义为:
在进行特征划分的时候,选择特征中基尼值最小的作为最优特征划分点。
实际上,在应用过程中,更多的会使用 基尼指数 对特征划分点进行决策,最重要的原因是计算复杂度相较于 ID3 和 C4.5 小很多(没有对数运算)。