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

  1. 机器学习 之线性回归
  2. 机器学习 之逻辑回归及python实现
  3. 机器学习项目实战 交易数据异常检测
  4. 机器学习之 决策树(Decision Tree)
  5. 机器学习之 决策树(Decision Tree)python实现
  6. 机器学习之 PCA(主成分分析)
  7. 机器学习之 特征工程
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from math import log
复制代码

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

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<len_columns-3):
# 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
复制代码

测试下看看app

# 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

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

下面咱们来分模块进行实现。学习

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

#求对应数据集的信息熵
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
复制代码

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

getInfoEntropy(data)
复制代码
0.9975025463691153
复制代码

下来咱们实现下,根据对应特征,划分数据。须要分两种状况3d

  1. 离散型特征,直接根据离散的相应类别进行划分
  2. 连续型数据,须要参照数据离散化,选出最优的划分值。具体原理可查看上篇文章中的连续型特征处理

下面来看下具体代码实现code

#离散型数据划分
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计算机视觉工坊,本公众号不按期推送机器学习,深度学习,计算机视觉等相关文章,欢迎你们和我一块儿学习,交流。

相关文章
相关标签/搜索