决策树思想非常朴素,类似程序设计中的if–else结构,最早的决策树就是利用这类结构分类数据。
思考:最先筛选的条件意味着什么?阈值是否合理?
信息熵用来描述信源的不确定度,熵在信息论中代表随机变量不确定度的度量。熵越大,数据的不确定性越高。熵越小,数据的不确定性越低。
1.信息熵越小,则说明数据越稳定
2.决策树的分类依据:信息熵 (推荐)、基尼系数
3.同等了数量情况下,概率均等的数据,它的信息熵最大
Entropy代表信息熵,i 表示数据总类别数,P(i) 表示类别 i 样本数量占所有样本的比例。
# {1/2 , 1/2} 采用二分法,猜二分类,在样本平均时信息熵为1
H = -(1 / 2 * np.log2(1 / 2) + 1 / 2 * np.log2(1 / 2))
print(H)
# 样本不平均时信息熵会减少
H = -(3/ 4 * np.log2(3 / 4) + 1 / 4 * np.log2(1 / 4))
print(H)
# {1/3,1/3,1/3} ==> 1.5849
H = -(1 / 3 * np.log2(1 / 3) + 1 / 3 * np.log2(1 / 3) + 1 / 3 * np.log2(1 / 3))
print(H)
# {1,0,0} 信息熵为0 则代表100%确定
H = -(1 * np.log2(1 / 1))
print(H)
# 32支球队在夺冠机会均等的情况下, 信息熵为5,也可以理解采用二分法,拆5次可以拆到冠军
H = - (1 / 32 * np.log2(1 / 32)) * 32
结论:分类的质量依赖信息熵,信息熵越小则说明分类的效果越好。当然理论上来说我们可以通过决策树进行无线划分,但是这样又会导致过拟合,因此我需要把握划分的度即可。
鸢尾花包含三个花的品种(Iris setosa(山鸢尾),Iris virginica(北美鸢尾),Iris versicolor(变色鸢尾))每个品种各50个样本,每个样本四个特征参数(萼片长度和宽度、花瓣长度和宽度)
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormap
from sklearn import datasets
iris = datasets.load_iris()
# (150, 4) (150,)
print(iris['data'].shape, iris['target'].shape)
# 可视化方便,我们只保留两列特征
# iris.data[:,:2] 前面两个特征相对不容易区分
X = iris.data[:,2:]
# y是目标值 0,1,2
y = iris['target']
# print(X[y==0].shape,X[y==0][0],X[y==0,0])
plt.scatter(X[y == 0, 0], X[y == 0, 1])
# 采用目标值来筛选样本,吧样本的第0个和第1个特征值分别设置为散点图的x与y轴
plt.scatter(X[y == 1, 0], X[y == 1, 1])
plt.scatter(X[y == 2, 0], X[y == 2, 1])
plt.show()
# 采用sklearn二叉树进行分类
from sklearn.tree import DecisionTreeClassifier,export_graphviz
# 决策树,max_depth 最高的深度,先设置2后面在调整,criterion:默认基尼系数, 建议采用交叉熵
dt_clf = DecisionTreeClassifier(max_depth=2, criterion='entropy',random_state=2)
# 此demo样本数量较少,而且只是为了演示二叉树原理因此未进行测试集与训练集划分
dt_clf.fit(X, y)
print(dt_clf.score(X,y)) # 0.96
# 输出dot文件,并且采用第三方库:graphviz把dot转化为png图片
export_graphviz(decision_tree=dt_clf,out_file="../data/iris.dot",feature_names=['A','B'])
将graphviz安装目录下的bin文件夹添加到Path环境变量中, 官方的安装路径:https://graphviz.gitlab.io/_pages/Download/Download_windows.html
进入windows命令行界面,输入 dot -version,然后按回车,如果显示graphviz的相关版本信息,则安装配置成功
C:\Users\Administrator>dot -version
dot - graphviz version 2.38.0 (20140413.2041)
libdir = "D:\graphviz\bin"
Activated plugin library: gvplugin_dot_layout.dll
Using layout: dot:dot_layout
-Tv Set output format to 'v'
-ofile Write output to 'file'
通过命令把dot转化为png图片格式
C:\Users\Administrator>dot C:\Users\Administrator\Desktop\pypro\data\tree.dot -Tpng -o image.png
# 采用等高线刻画出分类图,此部分与二叉树原理无关,感兴趣同事可以参考:np.meshgrid np.c_ plt.contourf API
def plot_decistion_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_decistion_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])
plt.show()
"""
numpy手写决策树
--2023-03-29
可以把决策树的功能进行拆分,然后封装到不同的功能模块。
1.如何获取最优阈值
2.得到阈值如何对样本进行分隔
3.拆分样本后计算信息熵
在实现时按照逆序进行
"""
import numpy as np
# 编写求矩阵信息熵的函数
## 返回的信息熵越小,则说明分类效果越好
from collections import Counter
def entropy(y):
"""
:param y:
:return:
"""
counter = Counter(y)
res = 0.0
for num in counter.values():
p = num/len(y)
res +=-p*np.log2(p)
return res
# 采用阈值对特征列进行分割的函数
## 拆分阈值函数
## 采用bool值进行筛选
def split(x,y,d,v):
'''
:param x:
:param y:
:param d:特征维度
:param v:阈值
:return:
'''
bool_left = x[:,d]<v
bool_right = x[:,d]>v
return x[bool_left],x[bool_right],y[bool_left],y[bool_right]
#搜索最佳列的最佳阈值¶
## 先排序,然后采用二分法或者相邻两数的平均值作为阈值,本次采用后者方法
def try_split(X,y):
#存储最低的信息熵
best_entropy = np.inf
best_d,best_v = -1,-1
for d in range(X.shape[1]):
# 进行排序,np.argsort()返回的是元素值从小到大排序后的索引值的数组
sort_index = np.argsort(X[:,d])
#内循环,找到维度的每一个特征值
for num in range(1,len(X)):
# 如果两个值相等,则阈值就等于他们平均值,这样没有意义
if X[sort_index[num - 1], d] != X[sort_index[num], d]:
v = (X[sort_index[num - 1], d] + X[sort_index[num], d]) / 2.0
# 阈值永远取当前列,两个值的平均数 (2.0 可以保留小数)
X_l, X_r, y_l, y_r = split(X, y, d, v)
# 计算当前阈值的信息熵
e = entropy(y_l) + entropy(y_r)
if best_entropy > e:
best_entropy = e
best_d, best_v = d, v
return best_entropy, best_d, best_v
# 手动测试鸢尾花数据集
from sklearn.datasets import load_iris
if __name__ == '__main__':
iris = load_iris()
X = iris.data[:, 2:]
# y是目标值 0,1,2
y = iris['target']
# 第一次二叉树
best_entropy, best_d, best_v = try_split(X, y)
print("best_d=", best_d)
print("best_v=", best_v)
print("best_entropy=", best_entropy)
# 把特征与目标值,然后把获得维度,和当前维度的阈值传入,可以得到信息熵
X1_1, X1_r, y1_1, y1_r = split(X, y, best_d, best_v)
print(entropy(y1_1)) # 0.0
print(entropy(y1_r)) # 1.0
# 第二次二叉树, 返回维度和维度的阈值
best_entropy2, best_d2, best_v2 = try_split(X1_r, y1_r)
print("best_d2=", best_d2)
print("best_v2=", best_v2)
print("best_entropy2=", best_entropy2)
# 把特征与目标值,然后把获得维度,和当前维度的阈值传入,可以得到信息熵
X2_1, X2_r, y2_1, y2_r = split(X1_r, y1_r, best_d2, best_v2)
print(entropy(y2_1))
entropy(y2_r)
print(entropy(y2_r))