举例简述决策树的工作原理
决策树可以实现二分类等问题,举个例子:
一大批志愿者100人,
目标:判断某一个人是否具有“A病”
志愿者属性:年龄;性别;生活地区经纬度;家中长辈是否有A病;本人是否有A病
我们可以通过构建一大堆的ifelse来对数据进行切割,假如数据构成里,是如下结构:
if 家中是否遗传{
#70人
if 长辈里有得病的{
#30人
if 生活经纬度是重工业污染区{
#25人
if 是否大于40岁 {
#20人
if 男人{
有病人群!!!!
}else{
10人没病
}
}else{
5人没病
}
}else{
5人没病
}
}else{
40人无病
}
}else{
10人无病
}
以上我们看到了一批混沌的数据,根据属性的限制而不断的分裂,从100人的群体逐渐找到了10个病患的特征。
即:家中长辈遗传A病+长辈有病+生活在重工业污染区+40岁以上的+男性会得A病
通过以上例子,我们会发现:
- 所有的数据最终都会走向叶子节点
- 沿用以上思路,我们既可以进行
逻辑回归
,也可以进行线性回归
。(如本例中的是否有病,或者预测房屋价格等)
决策树特点和构建原则
- 每个决策的判断先后顺序对结果的影响非常大
- 海选阶段就应该有很强的区分效果,之后的决策可以慢慢细化分裂
- 决策树模型的目的就是通过计算机计算出合理的决策判断内容以及先后顺序
- 决策树应用广泛,可以扩展出各种森林,也可通用于逻辑回归和线性回归
构建算法
那么问题来了,在海选阶段如何找到区分能力强的决策标准?首先引入概念:
“熵”和“Gini系数”
熵
理科的同学们应该印象深刻一点吧,说白了就是描述某事物的混乱程度
,电影《信条》也蹭了一波热度。
⚠️:有的科学计算库用的log,有的ln,其实差不多。
公式简单解析:
- P为决策一次后产生某一结果的概率,因此值在0~1之间。
- 因此lnx为单调增函数,在(0,1]区间时y值从-∞到0,而P取值恰巧是[0,1]区间。
- 公式前面添加负号,图像翻转。即(0,1]区间时y值从+∞到0的单调减函数,正好对应了概率越大时值越小,熵不就是越规矩(概率越大),熵越小嘛...
- 每一个i代表一个类别,把每个类别熵计算出来后再累加,就能得到最终的熵。
我们可以通过判断熵的大小计算一次分裂过后某棵子树的混乱程度,以此来评估决策标准的好坏。
信息增益(ID3)
假设场景(和上述数据无关):
14个人,9人有病,5人无病
A:早上只吃<菜>的人里:2人有病,3人无病
B:早上只吃<肉>的人里:有四个人全有病
C:早上只喝<水>的人里:三人有病,两人无病
A的熵为:
B的熵为:
C的熵为:
所以“早上吃饭”这个属性所带来的熵的计算为:
而在数据未进行决策时的熵为:
那么信息增益(熵下降)为:
以上是在有一个属性作为决策条件的时候算出的信息增益,如果有多个属性,那么分别算出所有属性的信息增益,然后比大小,哪个属性信息增益大,谁先决策。
信息增益率(C4.5)
假设在信息增益模块所介绍的数据场景里,有一个属性,14个人,每个人都不一样或特征及其稀疏(如个人身份证号码),以此属性决策时会出现一课子节点非常多的子树,并且此子树的熵为0,那么按照上述逻辑,身份证的信息增益应该是最大的,然而在实际情况中,身份证号码对于决策一个人是否生病没什么卵用!此时利用信息增益决定决策的先后顺序就会出现问题,因此提出信息增益率
来进行算法改进。
首先算一下此属性自身熵(假设是身份证号码)为:
信息增益率为:,可以看出,一个给定的数除以无穷,那么他是很小很小很小的......。
GINI系数标准(CART)
由此可知,概率越大,Gini越接近0
连续型随机变量该如何进行决策?
现将连续数值排序,将值劈开!如分成age<=20和age>20两大块,分别计算出信息增益,然后再去别的段去劈,直到找到最大的信息增益。
决策树剪枝
决策树在构建过程中,经常遇到过拟合的情况
- 预剪枝:(推荐)建立决策树的同时完成剪枝操作。
- 后剪枝:当建立完决策树之后进行剪枝操作。
预剪枝的方式:限制深度,叶子节点个数,叶子节点样本数,信息增益量
后剪枝衡量标准:,值越大,越不好!
⚠️注意:
- 如果希望数据不用太精确,那么也不会容易过拟合,可以把a设置大一些。
- 如果想要精确数据,可以把a设置小一些,但是有过拟合风险!
加利福尼亚房价数据集决策树分析示例
这里只使用经度纬度使用决策树简单分析
from sklearn import tree
import pydotplus
from IPython.display import Image
from sklearn.datasets.california_housing import fetch_california_housing
housing = fetch_california_housing()
print(housing.DESCR)
print(housing.data.shape)
'''参数说明
1.criterion gini or entropy
2.splitter best or random 前者是在所有特征中找最好的切分点 后者是在部分特征中(数据量大的时候)
3.max_features None(所有),log2,sqrt,N 特征小于50的时候一般使用所有的
4.max_depth 数据少或者特征少的时候可以不管这个值,如果模型样本量多,特征也多的情况下,可以尝试限制下
5.min_samples_split 如果某节点的样本数少于min_samples_split,则不会继续再尝试选择最优特征来进行划分如果样本量不大,不需要管这个值。如果样本量数量级非常大,则推荐增大这个值。
6.min_samples_leaf 这个值限制了叶子节点最少的样本数,如果某叶子节点数目小于样本数,则会和兄弟节点一起被剪枝,如果样本量不大,不需要管这个值,大些如10W可是尝试下5
7.min_weight_fraction_leaf 这个值限制了叶子节点所有样本权重和的最小值,如果小于这个值,则会和兄弟节点一起被剪枝默认是0,就是不考虑权重问题。一般来说,如果我们有较多样本有缺失值,或者分类树样本的分布类别偏差很大,就会引入样本权重,这时我们就要注意这个值了。
8.max_leaf_nodes 通过限制最大叶子节点数,可以防止过拟合,默认是"None”,即不限制最大的叶子节点数。如果加了限制,算法会建立在最大叶子节点数内最优的决策树。如果特征不多,可以不考虑这个值,但是如果特征分成多的话,可以加以限制具体的值可以通过交叉验证得到。
9.class_weight 指定样本各类别的的权重,主要是为了防止训练集某些类别的样本过多导致训练的决策树过于偏向这些类别。这里可以自己指定各个样本的权重如果使用“balanced”,则算法会自己计算权重,样本量少的类别所对应的样本权重会高。
10.min_impurity_split 这个值限制了决策树的增长,如果某节点的不纯度(基尼系数,信息增益,均方差,绝对差)小于这个阈值则该节点不再生成子节点。即为叶子节点 。
n_estimators:要建立树的个数
'''
dtr = tree.DecisionTreeRegressor(max_depth = 2) #设置决策树最大深度是2
dtr.fit(housing.data[:, [6, 7]], housing.target) #处理所有数据,每条数据只处理第6和7列
#安装 graphviz 工具,可以将导出的决策树数据展示成图片
dot_data = \
tree.export_graphviz(
dtr,
out_file = None,
feature_names = housing.feature_names[6:8],
filled = True,
impurity = False,
rounded = True
)
graph = pydotplus.graph_from_dot_data(dot_data)#读入决策树数据
graph.get_nodes()[7].set_fillcolor("#FFF2DD")
Image(graph.create_png()) #导出图片
graph.write_png("dtr_white_background.png")
#超参数选择
from sklearn.model_selection import train_test_split
from sklearn.model_selection import GridSearchCV
from sklearn.ensemble import RandomForestRegressor
data_train, data_test, target_train, target_test = \
train_test_split(housing.data, housing.target, test_size = 0.9, random_state = 42)
tree_param_grid = { 'min_samples_split': list((3,6,9)),'n_estimators':list((10,50,100))}#这里设置超参数待选项
grid = GridSearchCV(RandomForestRegressor(),param_grid=tree_param_grid, cv=2)#cv=5为交叉验证轮数
grid.fit(data_train, target_train)
'''
means = grid_result.cv_results_['mean_test_score']
params = grid_result.cv_results_['params']
'''
print(grid.cv_results_)
print("=======")
print(grid.best_params_)
print("=======")
print(grid.best_score_)