决策树
决策树思想来源于程序设计中的分支结构if-else
,最早的决策树就是利用这类结构分割数据的一种分类学习方法。其本质上是一颗由多个判断节点组成的树,在已知各种情况发生的概率基础上,通过生成决策树来求取净现值期望大于或等于0的概率,属于监督学习。
大致结构如下:
class Decision Tree:
data; 分类数据
rule; # 分割条件
children; # 记录子节点栈
1. 决策树规则该如何选取?
1.1 熵
决策规则应该是能让信息分歧最大化的(只有这样的分割从贪心程度上来说才是比较正确的)。这里我们引入**熵(Entropy)**的概念。
在物理上,熵是混乱程度的度量,熵值越高,系统越分散,越不稳定。而在信息论中,给出了如下描述:
- 从信息的完整性上进行的描述
当系统有序状态一致时,数据越集中的地方熵值越小。 - 从信息的有序性上进行的描述
当数据量一致时,系统越有序熵值越低。
1948年,香农对此进行了总结归纳,提出了信息熵的概念[即未知事件可能含有的信息量]。
假设事件A的分类划分是,每部分发生的概率是,那么信息熵则定义为:
其本质是一个随机变量信息量的数学期望。
至于为什么是?我们知道,一件事情内含的信息越清楚,那么他的随机可能就越少。举个例子,小明在云南上学。这句话给的信息量比较大,小明是学生还是老师?在云南哪个地方上学?上哪个阶段的学?这些我们都不知道,所以小明可能在云南大学读研究生,有可能在云师大附中教书。再看这句话:小明在云南大学地球科学学院地理信息科学系读大四,哦,原来小明是云大大四学生啊。瞬间感觉没有探索欲望了。那么反映到数据上,如何清楚一个事件呢?那就是组成该事件各个基本事件发生的概率。这个概率越高,这个事件就越无趣。那么怎么去量化?不难想到,可以用这个概率做文章。比如。但这样会有些问题呀,概率很小的时候倒数会特别大。我们想让曲线平滑一点可以吗,当然可以。香农发现了,函数很好地满足了这一点。它是一个恒正、递减的函数,甚至还巧合地满足!这能很好地分割独立事件~
于是,一个随机变量的信息熵就可以定义为:
这个对数的底选取是随意的,那为什么要选2?其实以树形结构就能很好地解释。为了方便表达,我们举一个例子,32只球队,有一个是冠军,至少要问几次才能问出这个冠军?
我们就不抠字眼了,假设一次只能问出一个条件,时间复杂度的解法是:
for i in range(1,32):
if check(i):
return i
return 32
而利用二分法,可以将时间复杂度压缩到:
# 实际上是问是否在1~16之间,这样就分割成两个了
# 我们假设check为: [0,0,0,0,1,1,1,1,1]
# 所需找的值就是第一个1出现的位置
# 有点类似第一个错误版本
def check(i):
if i<tar: return 0
return 1
l,r=0,32
while l<r:
mid=l+((r-l)>>1)
if check(mid):
r=mid
else:
l=mid+1
return l
于是,再对一个随机事件的各个随机变量平均后(依靠概率权重),最终的结果就是我们看到的那样啦!
import numpy as np
import pandas as pd
import collections
from math import log
class Entroy(object):
def __init__(self,dataset:pd.DataFrame)->None:
self.dataset=np.array(dataset)
def ent(self,data:np.array=None)->float:
# 计算熵
# 用频率估计概率
if not data:
data=self.dataset
size=len(data)
label=collections.defaultdict(int)# 用于统计频率的字典
for i in range(size):
# 默认最后一列属性是label
lab=data[i][-1]
label[lab]+=1
# 接着计算熵
return -sum([(_/size)*log((_/size),2) for _ in label.values()])
1.2 信息增益
首先来看概念:信息增益就是以某特征划分数据集前后的熵差值。可以看做得知特征x的信息而使得类Y信息熵减少的程度。
换句话说,我现在知道了小明去上学的信息熵是8,当我明确知道小明要去坐车,此时小明去上学的信息熵是5。而当我知道小明背着书包时,此时小明去上学的信息熵为7。不难发现,小明背书包能够让数据集更加稳定(8-7=1<3),换言之,也就是以该特征划分数据集,能够让数据的纯度更高。所以我们会选择信息增益更高的特征(以该特征划分的数据更加稳定)。
这个值越大呢,就表示更改后的系统信息量越多。
我们给出严格定义:
特征对于训练数据集的信息增益,定义为集合的信息熵与特征给定条件下的信息条件熵之差。
其中,的计算公式为:
表示某个类别的样本数,这是以频率作为概率的似然估计。
而条件熵的计算公式为:
这也是ID3决策树的分式算法。但是它只能对离散属性数据集构造决策树,且偏向取值较多的属性。以下是算法实现:
def condition_ent(self,data=None,axis=0)->int:
# 条件熵,现在是以特征axis进行划分
# 每种axis都有对应的数据
# 我们统计这些数据对应的label即可
# 实际上,就是先依据特征划分,再对每个特征计算label的信息熵
# 如此便是条件概型
# 也能看出特征y对于目标x的影响程度
# axis是第几个维度
if not data:
data=self.dataset
size=len(data)
Feature=collections.defaultdict(list)
for i in range(size):
feature=data[i][axis]
Feature[feature].append(data[i])
# 这时候不是负号了
# 负号在单个特征内已经计算过了
return sum([(len(_)/size)*self.ent(_) for _ in Feature.values()])
@staticmethod
def KLIC(a,b):
# 信息增益
return a-b
不难发现,这玩意的空间复杂度是级别,也就是对于连续数据难以处理,不仅是数学层次。
此时,我们已经实现了决策树的rule
模块,其完整内容如下:
"""
@author:かぐらなな
The Time of Creating:23 23:10
File Name: untitled2 - Ent
"""
import numpy as np
import pandas as pd
import heapq
import collections
from math import log
class Entroy(object):
def __init__(self,dataset:pd.DataFrame,featurename=None):
self.dataset=np.array(dataset)
self.featurename=featurename
# small heap
self.heap=[]
self.entroy=self.ent()
def ent(self,data:np.array=None)->float:
# calculate the entroy
# estimate the possibility by using frequency
if not data:
data=self.dataset
size=len(data)
label=collections.defaultdict(int)# a dictionary of statistical frequencies
for i in range(size):
# default the last col is label
lab=data[i][-1]
label[lab]+=1
# now we can work out the ent
return -sum([(_/size)*log((_/size),2) for _ in label.values()])
def condition_ent(self,data=None,axis=0)->int:
# condition entroy,which is divided by "axis-feature"
# each axis has corresponding data
# the following steps:
# 1. classfication the data by feature
# 2. statistics the frequency of label and work out the c-e of each feature
# 3. sum and give weight
if not data:
data=self.dataset
size=len(data)
Feature=collections.defaultdict(list)
for i in range(size):
feature=data[i][axis]
Feature[feature].append(data[i])
return sum([(len(_)/size)*self.ent(_) for _ in Feature.values() ])
@staticmethod
def KLIC(a,b):
# information divergence
return a-b
def Create_node(self)->None:
# create a small heap to record c-e
if self.dataset.size==0:
raise Exception("None Date!")
# features
feature_count=len(self.dataset[0])-1
# recreate
self.heap=[]
for i in range(feature_count):
# calculate the c-e
tem=self.condition_ent(axis=i)
# handle c-e by small heap
# we just need smallest one
heapq.heappush(self.heap,(tem,i))
def getNode(self):
if self.heap:
# i: c-e
# j: index
i,j=heapq.heappop(self.heap)
if self.featurename:
j=self.featurename[j]
return (j,self.entroy-i)
raise Exception("You haven't created a tree yet!")
用一组实例去测试:
labels = ["地区", "楼层", "装修","离学校距离","售价"]
datasets = pd.DataFrame([
["呈贡", "低", "毛坯", '10KM','30'],
["呈贡", "中", "精装", '2KM','50'],
["呈贡", "高", "精装", '<1KM','80'],
["盘龙", "高", "毛坯", '2KM','50'],
["西山", "低", "精装", '10KM','50'],
["盘龙", "中", "精装", '10KM','80'],
["五华", "高", "毛坯", '<1KM','80']
],columns=labels)
A=Entroy(datasets)
A.Create_node()
# simulate the steps of extracting nodes each time
while A.heap:
print("特征 %s 的信息增益最大,为:%s,故选为当前结点"%(A.getNode()))
结果如下:
通过比对我们可以发现,基本上与预期的一致。(数据是特意针对填写的)
[小根堆和收集器能够让均摊时间复杂度的步骤执行时间更短,而选用Python语言主要是为了接下来的数据分析和特征工程]
1.3 信息增益率