信息熵以及模拟使用信息熵来进行划分
信息熵
前文说到决策树的构建的关键问题就是每个节点在哪个维度做划分?以及某个维度在哪个值上做划分?
那么可以使用计算信息熵来解决这个问题
信息熵是什么,简单来说,在信息论中代表随机变量不确定度的度量,也就是说对于一组数据来说,越不确定越随机,那么信息熵就越大,反之,数据越确定,则熵就越小,总结一下就是,熵越大,数据的不确定性越高,熵越小,数据的不确定性越低
信息熵的计算公式中,pi表示一个系统中的k类信息的每一类信息的占比
如果说只有两类,一类占比为x,那么另一类占比为1-x,则公式可以变成
绘制一下这个函数的图像
(在notebook中)
加载好类库以后,创建信息熵的函数,代入公式即可(以两种数据为例),使用linspace来生成一个向量x,从0.01到0.99均匀取值(不能取0和1),取两百个值,然后绘制函数图像
def entropy(p):
return -p*np.log(p)-(1-p)*np.log(1-p)
x = np.linspace(0.01,0.99,200)
plt.plot(x,entropy(x))
图像如下
可以发现这个是以0.5为对称轴的,即0.5的时候取到了最大值,此时数据的不确定性最大
那么对应前面说的两个问题,在进行划分的时候,目的是让划分以后的信息熵降低,也就是说,划分以后让整个系统更加的稳定,找出每一个节点上的某一个维度的某一个取值,根据这个划分出来的信息熵是比用其他方式划分的信息熵的结果要好的,即找到最小的信息熵
使用信息熵寻找最优划分
通过信息熵可以看到当前数据的不确定度是什么样子的,对于决策树来说,根节点相当于有全部的数据,那么在根节点的基础上要找到一个维度一个域值,可以对根节点进行划分,希望在划分以后整体的信息熵是降低的,对于划分出来的节点,可以再进行相同的方法进行划分,使整体的信息熵继续减小,递归下去,这样就形成了决策树
具体实现
(在notebook中)
使用鸢尾花数据集,只保留两个维度的特征以便于可视化
import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets
iris = datasets.load_iris()
X = iris.data[:,2:]
y = iris.target
使用sklearn中的DecisionTreeClassifier,训练一个决策树的分类器
from sklearn.tree import DecisionTreeClassifier
dt_clf = DecisionTreeClassifier(max_depth=2,criterion="entropy")
dt_clf.fit(X,y)
使用绘制函数,并对图像进行绘制
from matplotlib.colors import ListedColormap
def plot_decision_boundary(model, axis):
x0,x1 = np.meshgrid(
np.linspace(axis[0],axis[1],int((axis[1]-axis[0])*100)).reshape(-1,1),
np.linspace(axis[2],axis[3],int((axis[3]-axis[2])*100)).reshape(-1,1)
)
X_new = np.c_[x0.ravel(),x1.ravel()]
y_predict = model.predict(X_new)
zz = y_predict.reshape(x0.shape)
custom_cmap = ListedColormap(['#EF9A9A', '#FFF59D', '#90CAF9'])
plt.contourf(x0, x1, zz, linewidth=5, cmap=custom_cmap)
plot_decision_boundary(dt_clf,axis=[0.5,7.5,0,3])
plt.scatter(X[y==0,0],X[y==0,1])
plt.scatter(X[y==1,0],X[y==1,1])
plt.scatter(X[y==2,0],X[y==2,1])
图像如下
使用信息熵的理论来进行划分
首先创建一个函数,进行划分,传入数据,维度,域值这几个参数,其是假设已经有了维度和域值,只有这个划分的功能,分为两块a和b,对于a来说,就是x这个样本数据在d这个维度上的值小于等于value对应的索引,结果是一个布尔向量,将其作为索引传入x和y中就可以找到满足条件的相应的数据,对b来说,就是大于value的数据的索引,然后返回分好类的X和y
def split(X,y,d,value):
index_a = (X[:,d] <= value)
index_b = (X[:,d] > value)
return X[index_a],X[index_b],y[index_a],y[index_b]
然后就可以具体的在全部数据上寻找d和value的值了,设置一个函数try_split,传入X和y,这个函数中的逻辑是,每一次尝试对数据进行划分的时候,就是在找一种划分可以使信息熵为最低,设置最好的信息熵的初始值为无穷,每次找到更好的就进行更新,同时也有这个时候的维度d和域值v,这两个初始值设为-1
然后就进行循环,对x中的每一个维度进行搜索,对于每一个维度,再进行域值的确定,对于这些域值可以选择的值是每两个样本点在d的维度上中间的值,那么就必须要对域值进行排序,排序以后在进行循环,从1到X的样本数都进行遍历,每一次找到的v的点就是对于X来说的索引的i-1这一行的d维度的列对应的值,然后加上i行d列的值,结果再除以二,这就作为候选的域值v,需要注意两个值不能相等,设置一个条件即可
然后开始划分,划分以后对于左右两个子树,计算系统的信息熵,信息熵的计算很简单,创建一个函数,传入y,调用counter类将值做成一个字典,设置结果初始值为0.0,然后遍历一下,对于每一个类别相应的含有多少的样本点,对于每一个num都计算一个p,然后套入信息熵公式,即可得到最后结果
如果熵比原先大,就不替换,如果信息熵比原先小,那么就进行替换,同时对d和v也进行替换,然后返回出最终的最好的信息熵,维度以及域值
from collections import Counter
from math import log
def entropy(y):
counter = Counter(y)
res = 0.0
for num in counter.values():
p = num/len(y)
res += -p * log(p)
return res
def try_split(X,y):
best_entropy = float('inf')
best_d,best_v = -1,-1
for d in range(X.shape[1]):
sorted_index = np.argsort(X[:,d])
for i in range(1,len(X)):
if X[sorted_index[i-1],d] != X[sorted_index[i],d]:
v = (X[sorted_index[i-1],d] + X[sorted_index[i],d]) / 2
X_l,X_r,y_l,y_r = split(X,y,d,v)
e = entropy(y_l) + entropy(y_r)
if e < best_entropy:
best_entropy,best_d,best_v = e,d,v
return best_entropy,best_d,best_v
调用这个函数,传入X,y,将结果打印出来
best_entropy,best_d,best_v = try_split(X,y)
print("best_entropy = ",best_entropy)
print("best_d = ",best_d)
print("best_v = ",best_v)
结果如下(和上图进行对比,可以发现符合这个结果)
可以存储上划分的结果,运行以后,看一下y1_l和y1_r的信息熵
X1_l,X1_r,y1_l,y1_r = split(X,y,best_d,best_v)
entropy(y1_l)
entropy(y1_r)
结果如下(与图像对比,符合)
可以继续对决策树进行划分,由于对左边没有需要划分了,可以只传入右边的数据,然后将结果打印出来
best_entropy2,best_d2,best_v2 = try_split(X1_r,y1_r)
print("best_entropy = ",best_entropy2)
print("best_d = ",best_d2)
print("best_v = ",best_v2)
结果如下
同样的存储上划分以后的数据情况,看一下y2_l和y2_r的信息熵
X2_l,X2_r,y2_l,y2_r = split(X1_r,y1_r,best_d2,best_v2)
entropy(y2_l)
entropy(y2_r)
结果如下
熵没有降为0,说明还可以继划分,由于没有真正建立起来决策树,所以没法继续分了,这就是模拟决策树使用信息熵进行划分