python实现决策树_机器学习之 决策树(Decision Tree)python实现

import numpy as np

import pandas as pd

import matplotlib.pyplot as plt

import seaborn as sns

from math import log

复制代码

我们使用西瓜书中的一个数据集,来进行我们的决策树的建立

data = pd.read_csv("decision_tree_data3.txt",names=['编号','色泽','根蒂','敲声','纹理','脐部','触感','密度','含糖率','好瓜' ])

data.head()

复制代码

编号

色泽

根蒂

敲声

纹理

脐部

触感

密度

含糖率

好瓜

0

1

青绿

蜷缩

浊响

清晰

凹陷

硬滑

0.697

0.460

1

2

乌黑

蜷缩

沉闷

清晰

凹陷

硬滑

0.774

0.376

2

3

乌黑

蜷缩

浊响

清晰

凹陷

硬滑

0.634

0.264

3

4

青绿

蜷缩

沉闷

清晰

凹陷

硬滑

0.608

0.318

4

5

浅白

蜷缩

浊响

清晰

凹陷

硬滑

0.556

0.215

首先,我们需要将数据集做下处理,将对应文字转换成离散型数据

# def initdata_feature(data):

# len_columns = data.shape[1]

# data_columns = data.columns

# for i in range(len_columns):

# if(i>0 and i

# str_values = data.iloc[:,i].unique()

# for j in range(len(str_values)):

# data.loc[data[data_columns[i]]==str_values[j],data_columns[i]] = j

def initdata_y(data):

data.loc[data.好瓜 == '是 ','好瓜'] = 1

data.loc[data.好瓜 == '否 ','好瓜'] = 0

复制代码

测试下看看

# initdata_feature(data)

initdata_y(data)

data

复制代码

编号

色泽

根蒂

敲声

纹理

脐部

触感

密度

含糖率

好瓜

0

1

青绿

蜷缩

浊响

清晰

凹陷

硬滑

0.697

0.460

1

1

2

乌黑

蜷缩

沉闷

清晰

凹陷

硬滑

0.774

0.376

1

2

3

乌黑

蜷缩

浊响

清晰

凹陷

硬滑

0.634

0.264

1

3

4

青绿

蜷缩

沉闷

清晰

凹陷

硬滑

0.608

0.318

1

4

5

浅白

蜷缩

浊响

清晰

凹陷

硬滑

0.556

0.215

1

5

6

青绿

稍蜷

浊响

清晰

稍凹

软粘

0.403

0.237

1

6

7

乌黑

稍蜷

浊响

稍糊

稍凹

软粘

0.481

0.149

1

7

8

乌黑

稍蜷

浊响

清晰

稍凹

硬滑

0.437

0.211

1

8

9

乌黑

稍蜷

沉闷

稍糊

稍凹

硬滑

0.666

0.091

0

9

10

青绿

硬挺

清脆

清晰

平坦

软粘

0.243

0.267

0

10

11

浅白

硬挺

清脆

模糊

平坦

硬滑

0.245

0.057

0

11

12

浅白

蜷缩

浊响

模糊

平坦

软粘

0.343

0.099

0

12

13

青绿

稍蜷

浊响

稍糊

凹陷

硬滑

0.639

0.161

0

13

14

浅白

稍蜷

沉闷

稍糊

凹陷

硬滑

0.657

0.198

0

14

15

乌黑

稍蜷

浊响

清晰

稍凹

软粘

0.360

0.370

0

15

16

浅白

蜷缩

浊响

模糊

平坦

硬滑

0.593

0.042

0

16

17

青绿

蜷缩

沉闷

稍糊

稍凹

硬滑

0.719

0.103

0

先把编号那个无用的特征去掉

data = data.drop('编号', axis = 1)

data

复制代码

色泽

根蒂

敲声

纹理

脐部

触感

密度

含糖率

好瓜

0

青绿

蜷缩

浊响

清晰

凹陷

硬滑

0.697

0.460

1

1

乌黑

蜷缩

沉闷

清晰

凹陷

硬滑

0.774

0.376

1

2

乌黑

蜷缩

浊响

清晰

凹陷

硬滑

0.634

0.264

1

3

青绿

蜷缩

沉闷

清晰

凹陷

硬滑

0.608

0.318

1

4

浅白

蜷缩

浊响

清晰

凹陷

硬滑

0.556

0.215

1

5

青绿

稍蜷

浊响

清晰

稍凹

软粘

0.403

0.237

1

6

乌黑

稍蜷

浊响

稍糊

稍凹

软粘

0.481

0.149

1

7

乌黑

稍蜷

浊响

清晰

稍凹

硬滑

0.437

0.211

1

8

乌黑

稍蜷

沉闷

稍糊

稍凹

硬滑

0.666

0.091

0

9

青绿

硬挺

清脆

清晰

平坦

软粘

0.243

0.267

0

10

浅白

硬挺

清脆

模糊

平坦

硬滑

0.245

0.057

0

11

浅白

蜷缩

浊响

模糊

平坦

软粘

0.343

0.099

0

12

青绿

稍蜷

浊响

稍糊

凹陷

硬滑

0.639

0.161

0

13

浅白

稍蜷

沉闷

稍糊

凹陷

硬滑

0.657

0.198

0

14

乌黑

稍蜷

浊响

清晰

稍凹

软粘

0.360

0.370

0

15

浅白

蜷缩

浊响

模糊

平坦

硬滑

0.593

0.042

0

16

青绿

蜷缩

沉闷

稍糊

稍凹

硬滑

0.719

0.103

0

下面就开始实现决策树算法,上篇文章已经知道,要根据样本集建立一棵决策树,具体算法流程如下:

下面我们来分模块进行实现。

首先,来看下给定数据集的信息熵的求解

#求对应数据集的信息熵

def getInfoEntropy(data):

#print('################################')

#print(data)

count_class = pd.value_counts(data.iloc[:,-1], sort=True) #根据输出值,统计不同样本个数

#print(count_class)

data_count = len(data.iloc[:,-1])

#print(data_count)

Entropy = 0.0

for i in range(len(count_class)):

p = count_class.iloc[i]/data_count

Entropy = Entropy + (-p * log(p,2))

#print('当前数据集的信息熵为:',Entropy)

return Entropy

复制代码

测试下看看,求取下整个样本集的信息熵

getInfoEntropy(data)

复制代码

0.9975025463691153

复制代码

下来我们实现下,根据对应特征,划分数据。需要分两种情况

离散型特征,直接根据离散的相应类别进行划分

连续型数据,需要参照数据离散化,选出最优的划分值。具体原理可查看上篇文章中的连续型特征处理

下面来看下具体代码实现

#离散型数据划分

def split_data(data, column):

splt_datas = pd.Series() #将划分的多个数据集保存在Series中

str_values = data.iloc[:,column].unique() #获取当前数据集中,对应特征的所有类别

for i in range(len(str_values)): #遍历对应类别,找出类别所对应的数据集

df = data.loc[data.iloc[:,column] == str_values[i]]

# print(df)

splt_datas[str(i)] = df

return splt_datas

#连续型数据划分

#返回划分后的左右两个数据集以及所对应的最优划分点,还有以此划分点进行划分数据后,对应的信息熵

def split_continuous(data,column):

splt_datas = pd.Series()

series_data_sort = data.iloc[:,column].sort_values() #对应对应连续型特征的在当前数据集中的所有值,进行从小到大排序

split_values = [] #创建一个list,用于存储所有的划分点

# print(series_data_sort)

for i in range(len(series_data_sort)-1): #求得所有划分点

split_values.append((series_data_sort.iloc[i] + series_data_sort.iloc[i+1])/2) # 注意,这块不能用series_data_sort[i]

best_split_value = 0. #所要找出的最佳划分点

minInfoGain = 100.

# print(split_values)

for i in range(len(split_values)): #遍历所有划分点

# print('split_values[i]==',split_values[i])

#根据对应划分点,将数据集划分为左右两个数据集(即二分)

left_data = data.loc[data.iloc[:,column]<=split_values[i]]

right_data = data.loc[data.iloc[:,column]>split_values[i]]

#求得对应信息熵

InfoGain = len(left_data)/len(data) * getInfoEntropy(left_data) + len(right_data)/len(data) * getInfoEntropy(right_data)

#print('InfoGain====',InfoGain)

if(InfoGain < minInfoGain): #找出最小的信息熵

minInfoGain = InfoGain

best_split_value = split_values[i]

left_data = data.loc[data.iloc[:,column]<=best_split_value]

right_data = data.loc[data.iloc[:,column]>best_split_value]

series = pd.Series()

series['0'] = left_data

series['1'] = right_data

return series,best_split_value,minInfoGain

复制代码

测试下看看

seris = split_data(data,0)

for i in range(len(seris)):

print (seris.iloc[i])

复制代码

色泽 根蒂 敲声 纹理 脐部 触感 密度 含糖率 好瓜

0 青绿 蜷缩 浊响 清晰 凹陷 硬滑 0.697 0.460 1

3 青绿 蜷缩 沉闷 清晰 凹陷 硬滑 0.608 0.318 1

5 青绿 稍蜷 浊响 清晰 稍凹 软粘 0.403 0.237 1

9 青绿 硬挺 清脆 清晰 平坦 软粘 0.243 0.267 0

12 青绿 稍蜷 浊响 稍糊 凹陷 硬滑 0.639 0.161 0

16 青绿 蜷缩 沉闷 稍糊 稍凹 硬滑 0.719 0.103 0

色泽 根蒂 敲声 纹理 脐部 触感 密度 含糖率 好瓜

1 乌黑 蜷缩 沉闷 清晰 凹陷 硬滑 0.774 0.376 1

2 乌黑 蜷缩 浊响 清晰 凹陷 硬滑 0.634 0.264 1

6 乌黑 稍蜷 浊响 稍糊 稍凹 软粘 0.481 0.149 1

7 乌黑 稍蜷 浊响 清晰 稍凹 硬滑 0.437 0.211 1

8 乌黑 稍蜷 沉闷 稍糊 稍凹 硬滑 0.666 0.091 0

14 乌黑 稍蜷 浊响 清晰 稍凹 软粘 0.360 0.370 0

色泽 根蒂 敲声 纹理 脐部 触感 密度 含糖率 好瓜

4 浅白 蜷缩 浊响 清晰 凹陷 硬滑 0.556 0.215 1

10 浅白 硬挺 清脆 模糊 平坦 硬滑 0.245 0.057 0

11 浅白 蜷缩 浊响 模糊 平坦 软粘 0.343 0.099 0

13 浅白 稍蜷 沉闷 稍糊 凹陷 硬滑 0.657 0.198 0

15 浅白 蜷缩 浊响 模糊 平坦 硬滑 0.593 0.042 0

复制代码

series,best_split_value,minInfoGain = split_continuous(data,6)

for i in range(len(series)):

print (series.iloc[i])

复制代码

色泽 根蒂 敲声 纹理 脐部 触感 密度 含糖率 好瓜

9 青绿 硬挺 清脆 清晰 平坦 软粘 0.243 0.267 0

10 浅白 硬挺 清脆 模糊 平坦 硬滑 0.245 0.057 0

11 浅白 蜷缩 浊响 模糊 平坦 软粘 0.343 0.099 0

14 乌黑 稍蜷 浊响 清晰 稍凹 软粘 0.360 0.370 0

色泽 根蒂 敲声 纹理 脐部 触感 密度 含糖率 好瓜

0 青绿 蜷缩 浊响 清晰 凹陷 硬滑 0.697 0.460 1

1 乌黑 蜷缩 沉闷 清晰 凹陷 硬滑 0.774 0.376 1

2 乌黑 蜷缩 浊响 清晰 凹陷 硬滑 0.634 0.264 1

3 青绿 蜷缩 沉闷 清晰 凹陷 硬滑 0.608 0.318 1

4 浅白 蜷缩 浊响 清晰 凹陷 硬滑 0.556 0.215 1

5 青绿 稍蜷 浊响 清晰 稍凹 软粘 0.403 0.237 1

6 乌黑 稍蜷 浊响 稍糊 稍凹 软粘 0.481 0.149 1

7 乌黑 稍蜷 浊响 清晰 稍凹 硬滑 0.437 0.211 1

8 乌黑 稍蜷 沉闷 稍糊 稍凹 硬滑 0.666 0.091 0

12 青绿 稍蜷 浊响 稍糊 凹陷 硬滑 0.639 0.161 0

13 浅白 稍蜷 沉闷 稍糊 凹陷 硬滑 0.657 0.198 0

15 浅白 蜷缩 浊响 模糊 平坦 硬滑 0.593 0.042 0

16 青绿 蜷缩 沉闷 稍糊 稍凹 硬滑 0.719 0.103 0

复制代码

接下来,我们要做的就是,给定一个数据集,我们需要求得最优的划分特征,先来来看下代码实现

# 查找当前数据集data 的 最优划分特征。返回对应最优特征在数据集data中的索引

def find_best_feature(data):

best_feature_index = 0 #用来保存最优划分特征的索引

minInfoGain = 100. #保存信息增益

samplenumber = len(data) #当前数据集的个数

best_split_value_return = 0. #如果最优划分特征是个连续类型的,则还需要保存对应的连续特征的最优划分点

best_split_value = 0.

best_Series = pd.Series()

for i in range(data.shape[1]-1): #遍历数据集的所有特征

InfoGain = 0.

series = pd.Series()

if(i < data.shape[1]-3): #离散型属性

series = split_data(data, i) #根据特征划分数据,将划分的多个数据集(DataFrame)保存在一个Series中返回

for j in range(len(series)):

df = series[j]

InfoGain = InfoGain +len(df)/samplenumber *(getInfoEntropy(df))

# print('InfoGain==',InfoGain,'----i=',i)

else: #连续型属性

series,best_split_value, InfoGain = split_continuous(data,i)

# print('InfoGain==',InfoGain,'----i=',i)

if(InfoGain < minInfoGain):

minInfoGain = InfoGain

InfoGain = 0.0

best_feature_index = i

best_Series = series

if(i >= data.shape[1]-3):

best_split_value_return = best_split_value

return data.columns[best_feature_index],best_Series,best_split_value_return

复制代码

测试下看看

find_best_feature(data)

复制代码

('纹理', 0 色泽 根蒂 敲声 纹理 脐部 触感 密度 含糖率 好瓜

0...

1 色泽 根蒂 敲声 纹理 脐部 触感 密度 含糖率 好瓜

6...

2 色泽 根蒂 敲声 纹理 脐部 触感 密度 含糖率 好瓜

1...

dtype: object, 0.0)

复制代码

接下来就要实现决策树的构建了

#创建树模型

def creat_Tree(data):

y_values = data.iloc[:,-1].unique() #得到当前数据集的y的类别

if(len(y_values) == 1): #如果只剩下一个类别,说明已经是纯净的数据,不需要再分,直接返回

return y_values[0]

flag = 0

for i in range(data.shape[1]-1): #当前节点中的所有样本在当前所剩属性集中取值相同,则直接返回当前数据集中类别样本多的那个类别

if(len(data.iloc[:,i].unique()) != 1):

flag = 1

break

if(flag == 0):

value_count = pd.value_counts(data.iloc[:,-1])

return value_count.index[0]

best_feature, best_Series,best_split_value = find_best_feature(data) #寻找最优特征,返回最优特征,划分后的数据集,已经如果是连续型特征,还需要划分点

Tree = {best_feature:{}} #用字典来模拟树

for j in range(len(best_Series)): #遍历划分后的数据集

split_data = best_Series.iloc[j]

value = ''

if(best_split_value == 0.0): #说明是离散型特征

value = split_data.loc[:,best_feature].unique()[0] #获取对应划分的特征中,对应的特征类别

split_data = split_data.drop(best_feature, axis = 1) #离散型型特征,用完后需要删除

else: #否则,说明是连续型特征

if(j == 0):

value = '<='+str(best_split_value)

else:

value = '>'+str(best_split_value)

Tree[best_feature][value] = creat_Tree(split_data) #采用递归思想,继续构建

return Tree

复制代码

Tree = creat_Tree(data)

Tree

复制代码

{'纹理': {'模糊': 0,

'清晰': {'密度': {'<=0.38149999999999995': 0, '>0.38149999999999995': 1}},

'稍糊': {'触感': {'硬滑': 0, '软粘': 1}}}}

复制代码

接下来,来实现下,使用训练出来的决策树,对新的数据进行分类

#预测一个样本

def classification_one(Tree, data):

print(Tree)

first_key = list(Tree.keys())[0] #获取根节点的key

first_value = Tree[first_key] #获取根节点对应的value

result = -1 #初始化,用来保存当前节点的结果

if('<' in list(first_value.keys())[0]): #连续型特征

#解释下下面两行代码,里面的 list(first_value.keys())[0] 获取来的就是‘<=0.38...’这种格式,需要从这个里面解析出0.38...这个分割点

left_key = list(first_value.keys())[0] #'<=分割点'

right_key = list(first_value.keys())[1] #'>分割点'

split_poit = float(''.join(list(left_key)[2:])) # '分割点'

if(data[first_key] <= split_poit): #如果属于左分支

if(isinstance(first_value[left_key], dict)): #判断是否是叶子节点,如果对应的value还是一个dict字典,则说明是个非叶子节点,继续递归

result = classification_one(first_value[left_key],data)

else: #如果是叶子节点,返回结果值

result = first_value[left_key]

else: #如果属于左分支

if(isinstance(first_value[right_key], dict)):

result = classification_one(first_value[right_key],data)

else:

result = first_value[right_key]

else: #离散型特征

if(isinstance(first_value[data[first_key]], dict)):

result = classification_one(first_value[data[first_key]],data)

else:

result = first_value[data[first_key]]

return result

#预测多个样本

def classification_more(Tree, data):

result_list = []

for i in range(data.shape[0]):

result = classification_one(Tree, data.iloc[i])

result_list.append(result)

return result_list

复制代码

classification_more(Tree,data)

复制代码

{'纹理': {'清晰': {'密度': {'<=0.38149999999999995': 0, '>0.38149999999999995': 1}}, '稍糊': {'触感': {'软粘': 1, '硬滑': 0}}, '模糊': 0}}

{'密度': {'<=0.38149999999999995': 0, '>0.38149999999999995': 1}}

{'纹理': {'清晰': {'密度': {'<=0.38149999999999995': 0, '>0.38149999999999995': 1}}, '稍糊': {'触感': {'软粘': 1, '硬滑': 0}}, '模糊': 0}}

{'密度': {'<=0.38149999999999995': 0, '>0.38149999999999995': 1}}

{'纹理': {'清晰': {'密度': {'<=0.38149999999999995': 0, '>0.38149999999999995': 1}}, '稍糊': {'触感': {'软粘': 1, '硬滑': 0}}, '模糊': 0}}

{'密度': {'<=0.38149999999999995': 0, '>0.38149999999999995': 1}}

{'纹理': {'清晰': {'密度': {'<=0.38149999999999995': 0, '>0.38149999999999995': 1}}, '稍糊': {'触感': {'软粘': 1, '硬滑': 0}}, '模糊': 0}}

{'密度': {'<=0.38149999999999995': 0, '>0.38149999999999995': 1}}

{'纹理': {'清晰': {'密度': {'<=0.38149999999999995': 0, '>0.38149999999999995': 1}}, '稍糊': {'触感': {'软粘': 1, '硬滑': 0}}, '模糊': 0}}

{'密度': {'<=0.38149999999999995': 0, '>0.38149999999999995': 1}}

{'纹理': {'清晰': {'密度': {'<=0.38149999999999995': 0, '>0.38149999999999995': 1}}, '稍糊': {'触感': {'软粘': 1, '硬滑': 0}}, '模糊': 0}}

{'密度': {'<=0.38149999999999995': 0, '>0.38149999999999995': 1}}

{'纹理': {'清晰': {'密度': {'<=0.38149999999999995': 0, '>0.38149999999999995': 1}}, '稍糊': {'触感': {'软粘': 1, '硬滑': 0}}, '模糊': 0}}

{'触感': {'软粘': 1, '硬滑': 0}}

{'纹理': {'清晰': {'密度': {'<=0.38149999999999995': 0, '>0.38149999999999995': 1}}, '稍糊': {'触感': {'软粘': 1, '硬滑': 0}}, '模糊': 0}}

{'密度': {'<=0.38149999999999995': 0, '>0.38149999999999995': 1}}

{'纹理': {'清晰': {'密度': {'<=0.38149999999999995': 0, '>0.38149999999999995': 1}}, '稍糊': {'触感': {'软粘': 1, '硬滑': 0}}, '模糊': 0}}

{'触感': {'软粘': 1, '硬滑': 0}}

{'纹理': {'清晰': {'密度': {'<=0.38149999999999995': 0, '>0.38149999999999995': 1}}, '稍糊': {'触感': {'软粘': 1, '硬滑': 0}}, '模糊': 0}}

{'密度': {'<=0.38149999999999995': 0, '>0.38149999999999995': 1}}

{'纹理': {'清晰': {'密度': {'<=0.38149999999999995': 0, '>0.38149999999999995': 1}}, '稍糊': {'触感': {'软粘': 1, '硬滑': 0}}, '模糊': 0}}

{'纹理': {'清晰': {'密度': {'<=0.38149999999999995': 0, '>0.38149999999999995': 1}}, '稍糊': {'触感': {'软粘': 1, '硬滑': 0}}, '模糊': 0}}

{'纹理': {'清晰': {'密度': {'<=0.38149999999999995': 0, '>0.38149999999999995': 1}}, '稍糊': {'触感': {'软粘': 1, '硬滑': 0}}, '模糊': 0}}

{'触感': {'软粘': 1, '硬滑': 0}}

{'纹理': {'清晰': {'密度': {'<=0.38149999999999995': 0, '>0.38149999999999995': 1}}, '稍糊': {'触感': {'软粘': 1, '硬滑': 0}}, '模糊': 0}}

{'触感': {'软粘': 1, '硬滑': 0}}

{'纹理': {'清晰': {'密度': {'<=0.38149999999999995': 0, '>0.38149999999999995': 1}}, '稍糊': {'触感': {'软粘': 1, '硬滑': 0}}, '模糊': 0}}

{'密度': {'<=0.38149999999999995': 0, '>0.38149999999999995': 1}}

{'纹理': {'清晰': {'密度': {'<=0.38149999999999995': 0, '>0.38149999999999995': 1}}, '稍糊': {'触感': {'软粘': 1, '硬滑': 0}}, '模糊': 0}}

{'纹理': {'清晰': {'密度': {'<=0.38149999999999995': 0, '>0.38149999999999995': 1}}, '稍糊': {'触感': {'软粘': 1, '硬滑': 0}}, '模糊': 0}}

{'触感': {'软粘': 1, '硬滑': 0}}

[1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0]

复制代码

我们还是使用的上面的那个数据集用来验证,可以看到,已经完全拟合了训练集,

但是,这样真的好吗?正如上篇文章说的,决策树如果不进行剪枝的话,肯定最后会一直分下去,直到所分的各个数据集完全是纯净的,这样,非常容易导致过拟合现象。所以,还需要进行剪枝操作。具体代码这块不演示了,只要掌握了整个决策树构建的原理,实现起来也很简单。

上面,演示的只是ID3算法的相关实现,还有决策的其他算法,例如C4.5决策树算法,主要区别就是最优划分特征的选择标准不同。流程基本都是一致的。

欢迎关注我的个人公众号 AI计算机视觉工坊,本公众号不定期推送机器学习,深度学习,计算机视觉等相关文章,欢迎大家和我一起学习,交流。

你可能感兴趣的:(python实现决策树)