simple_decitionTree
作者:互联网
简单的决策树
1. 本篇文章介绍
这篇文章旨在介绍最简易的决策树以及其复现代码,让大家基本了解决策树的基本流程,而不是被各种其它的诸如数据处理,剪枝等操作给搞迷糊了。
我的学习流程就是喜欢先把最核心的过程和基础学懂,然后基于这个前提,再去深入学习相关的深入的知识,让自己对该知识点的学习更加全面。
2. 决策树的基本流程
我们从周志华老师的《机器学习》的 P74 页借鉴一下老师归纳的流程,非常精炼。
同时也推荐这本书。当然,这本书早就声名在外,无需我的推荐。
部分可能产生困惑的条件的解释:
- \(D\) 中样本在 \(A\) 上的取值相同:
即除了标签那一列,其他所有列的值在每一列上的取值都相同
例如有 100 个西瓜(\(D\)),他们的(\(A\)) ①颜色都是青绿;②根蒂都是蜷缩;③敲声都是沉闷;④纹理都是清晰,等等。但是有的西瓜是好瓜,有的西瓜是坏瓜(标签)
- \(D_v\) 表示 \(D\) 在 \(a_*\) 上取值为 \(a_*^v\) 的样本子集:
举个例子:\(A\) 有很多属性 [ 色泽(\(a_1\)),根蒂(\(a_2\)),敲声(\(a_3\)),纹理(\(a_4\)) ]
其中 色泽(\(a_1\)) 有很多种取值:青绿 \(a_1^1\) , 乌黑\(a_1^2\) ,浅白\(a_1^3\)
那么如果当前 for 循环遍历到的是 \(a_1\) ,则 \(D_1\) 表示当前 数据集中所有颜色为青绿 \(a_1^1\) 的瓜
- 以 \(TreeGenerate(D_v, A\backslash{a_*})\) 为分支节点
就是以该数据子集 (\(D_v\)) 当成下一层计算的总数据集
从以上流程图中可以看出,关键步骤就在于如何从剩余的 \(A\) 集合中选择一个属性,下面一个部分就会讲划分选择
3. 划分选择
- 基本思想阐述:
本部分只讲基于信息熵增益的选择方法,还有其他很多方法如:基尼指数、信息增益率等等,甚至你自己开心,可以自己研究一个划分选择的标准,创立一个自己的决策树(如果效果好,说不定还能发篇论文 /doge)。
基于不同的属性选择方法就衍生出不同的决策树,例如:ID3,C4.5 等,大家可以自己去了解区别。不要害怕这种看起来很抽象,很牛逼的样子的命名,其实很多不同的算法都是基于一些基础的算法,在关键步骤上进行不同的尝试而得到的。所以以后大家看各种很牛逼的文章或算法的时候,就去想这些算法和同类的算法有哪些关键步骤上的差异就能大概理解了。
划分选择的基本原则和思想就是通过一次基于某个属性的不同属性值将数据划分成几个子集后,对应子集的标签的杂乱度能够少一些。也就是说,一次划分,能让不同子集中的数据的标签(\(lable\))更有偏向性,那么我们就可以通过这个属性将数据进行一次大致的划分,让我们猜中不同子集中的 \(lable\) 的正确率更高。
举个栗子:100个西瓜放在一起,我们不知道哪些是好是坏,我们通过敲击后发出的声音(从小我老妈就教我这么买瓜 /doge)将西瓜分成浑浊和清脆听两个大类。那么显然,浑浊的那一类的西瓜成熟的概率更高,更有可能是好瓜,那我们从浑浊的那一堆西瓜中买,更有可能买到 “好瓜”。
-
信息熵:
在讲信息熵增益之前必须要将信息熵,不然谁知道增益的是个什么东西呢?
-
定义:
假定当前样本集合 \(D\) 中第 \(k\) 类( \(lable\) 为 \(k\) )样本所占的比例为 \(p_k\ (k=1,2,\dots,|y|)\) ,则 \(D\) 的信息熵定义为:
\(Ent(D)\) 的值越小,则 \(D\) 的纯度越高。
纯度就是属于某一个 \(lable\) 的概率
-
信息增益:
什么是信息增益呢?就是我们通过选择一个属性 ( \(a_i\) ) 将数据集 \(D\) 划分成几个小数据集 \(D^v\) (\(v=(1,2,\dots,V)\), 取决于该属性有多少种取值 ),每个 \(D^v\) 的信息熵通过加权平均起来比 \(D\) 的信息熵减小的量。数学定义式如下:
\(\frac{|D^v|}{|D|}\) 的意思就是 \(\frac{len(D^v)}{len(D)}\) 。这个就是对应子集的权 ,用来加权平均
我们的目标就是找到使 \(Gain(D, a_i)\) 最大的 \(a_i\)
- 总结
-
计算信息增益基本操作流程:
就是先数整个数据集中属于不同 \(lable\) 的数量,之后计算整体的熵;再数某个属性的不同取值的子集的属于不同 \(lable\) 的数量,计算每个子集的熵,再加权求和。
代码复现
一下是本人的代码复现,有一个自制的小数据集(只有几行数据),主要是为了测试决策树是否能正常生成。
里面有一些自己写代码过程中的碎碎念,也懒得删了。其实本人还挺喜欢那些代码中有些碎碎念的博主,让人感觉很接地气(不是自夸 /doge)。
对了,从不同数据源读取数据的部分本人还没有详细写完,不过这部分相信大家应该都会把(/doge)。都是非常常用的调用就能解决的。
"""
这是一个决策树的复现
包含 数据读取,熵计算,决策树生成与存储,决策树读取与使用几个模块
"""
import math
import pandas as pd
# import numpy as np
# 从不同数据源获取数据
def get_data(data_source, sourceclass):
'''
从不同的数据源读取数据并返回
'''
# 从 csv 读取数据
if sourceclass == "csv":
with open(data_source, "r") as csvfile:
data = pd.read_csv(csvfile)
return data
if sourceclass == "database":
pass
# 给定数据,计算对应熵
def count_entropy(data:pd.DataFrame):
"""
计算信息熵
"""
diff_lable_count = {} # 用来保存不同lable(key)对应的计数(value)
entropy = 0
data_size = len(data)
# for i in data:
# if i['lable'] in diff_lable_count:
# diff_lable_count[i['lable']] += 1
# else:
# diff_lable_count[i['lable']] = 1
# for i in data 的遍历是遍历 dataframe 的列,而不是行
# 想要遍历行,且取行中的数值得按照下面的方法:
for index, row in data.iterrows():
if row['lable'] in diff_lable_count:
diff_lable_count[row['lable']] += 1
else:
diff_lable_count[row['lable']] = 1
for key in diff_lable_count:
# lable_percent: 不同 lable 的占比
lable_percent = diff_lable_count[key]/data_size
entropy += -(lable_percent*math.log(lable_percent, 2))
return entropy
def is_same_on_attrs(data:pd.DataFrame, attrs:list):
'''
该函数主要用来判断是否 D 中样本 在 A(attrs)上取值都相同
也就是 是否所有样本在所有属性上取值相同,如果是则无法划分
'''
for attr in attrs:
if len(data[attr].unique()) == 1: # 如果 Series.unique() 的长度只为 1 的话表示该列只有一种取值
return True
return False
def select_split_attr(data:pd.DataFrame, attrs:list):
'''
data: 剩余还需要划分的数据
attrs: 剩余可选的属性
根据传入的数据结合信息熵增益计算,进行属性选择
最后返回
'''
origin_entropy = count_entropy(data)
splited_entropy = {} # 记录不同 attr 的 entropy
gain_entropy = {} # 记录不同 attr 的 gain of entropy
for attr in attrs: # 遍历不同属性,计算不同属性的熵以及对应的 'gain of entropy'
splited_entropy[attr] = 0
data_attr = data[[attr, 'lable']] # 取 对应属性 和 'lable'
data_attr_len = len(data_attr) # 为了等会的分量熵加权
for value in data_attr[attr].unique(): # 遍历某个属性的不同分裂值,计算对应熵
sub_data = data_attr[data_attr[attr] == value]
splited_entropy[attr] += (len(sub_data)/data_attr_len) * count_entropy(sub_data)
gain_entropy[attr] = origin_entropy - splited_entropy[attr]
res = max(gain_entropy, key=gain_entropy.get) # 选择最大的 gain of entropy
return res
# 生成决策树
def generate_tree(data:pd.DataFrame, attrs:list):
'''
data: 剩余还需要划分的数据
attrs: 剩余可选的属性
** 使用 df.columns() 当作 attrs 时记得去掉 'lable'!!**
** 因为判断是否所有样本在所有属性上的取值是否相同时是不能包含 'lable' 的 **
该函数负责生成并存储决策树,其中要做的事情包括:选择分裂节点;将每个分裂节点存储到字典中;
'''
if len(data['lable'].unique()) == 1:
return data['lable'].unique()[0]
if len(attrs) == 0 or is_same_on_attrs(data, attrs):
return data['lable'].mode()[0] # Series.mode() 是找到该 Series 的众数,返回 Series
split_attr = select_split_attr(data, attrs) # 选取了分裂属性
decition_tree = {split_attr:{}}
attrs.remove(split_attr) # 移除已经使用过的属性
for value in data[split_attr].unique():
if len(data[data[split_attr]==value]) == 0: # 当前分量无值
# data[data[split_attr]==value] 是 D_v 分量;也就是分裂属性的不同值的子集
decition_tree[split_attr][value] = data['lable'].mode()[0]
return decition_tree
else:
decition_tree[split_attr][value] = generate_tree(data[data[split_attr]==value], attrs)
# 这个return之前一直放在for里面,导致我一直没法递归的查每个value,
# 因为查完一个value,递归下去之后再返回就直接返回了,没法再遍历下一个value
return decition_tree
if __name__ == '__main__':
data = [
['USA', 'computer', 'male', '0'],
['USA', 'enviroment', 'female', '1'],
['USA', 'computer', 'female', '0'],
['US', 'computer', 'male', '1'],
['US', 'computer', 'female', '0'],
['CNA', 'computer', 'male', '1'],
['CNA', 'history', 'female', '1'],
]
df = pd.DataFrame(data, columns=['country', 'work', 'sex', 'lable'])
get_tree = generate_tree(df, ['country', 'work', 'sex'])
print(get_tree)
这是我第一次尝试写博客,有些不是很周到的地方希望大家见谅,也欢迎大家在评论区对我指正,我都会虚心接受。
最后,欢迎大家与我交流。QQ:826474723
标签:lable,decitionTree,attr,simple,entropy,attrs,data,属性 来源: https://www.cnblogs.com/StephenSpace/p/15412119.html