下面给大家介绍一个最经典的监督学习算法:决策树
决策树,就是我们之前接触过的判断树,和流程图也有点类似。决策树虽然实现起来麻烦一点,但是理解起来很容易,而且在很多场景下都合适。
决策树的生成,主要是根节点的选取和叶子节点的确定。根节点主要选择信息增益最大的那个属性作为当前树的根节点。当达到停止条件的时候,要将当前节点作为叶子节点。合适的根节点和停止条件,可以防止树过大和数据的过度拟合。同时也有很多前剪枝和后剪枝算法来对树进行优化。
这里结合一个具体的例子,看下怎么构造一个决策树。
下面简单介绍下信息增益的计算代码和数据
young false false fair No
young false false good No
young true false good Yes
young true true fair Yes
young false false fair No
middle false false fair No
middle false false good No
middle false true good Yes
middle true true excellent Yes
middle false true excellent Yes
old false true excellent Yes
old false true good Yes
old true false good Yes
old true false excellent Yes
old false false fair No
#encoding=utf-8
import math
raw_data=open("raw_data")
list_raw=raw_data.readlines()
list_map=[]
for ele in list_raw:
list_split=ele.strip().split(" ")#尽量不要用字符串,用数字代替;尽量不要用tab,用空格;
list_map.append(list_split)
#开始计算信息增益
#计算类别属性的熵
yes_no=0
no_no=0
for ele in list_map:
if ele[4].strip() == "Yes":
yes_no = yes_no + 1
if ele[4].strip() == "No":
no_no= no_no + 1
yes_no=float(yes_no)
no_no=float(no_no)
yes_and_no=yes_no + no_no
d_entropy= -(yes_no/yes_and_no)*math.log(yes_no/yes_and_no,2) - (no_no/yes_and_no)*math.log(no_no/yes_and_no,2)
#计算年龄的信息增益
list_age=[]#获取age有多少不同的值
set_age=()#获取age有多少不同的值
for ele in list_map:
list_age.append((ele[0]).strip())#将所有记录中的age字段中的内容放入age_list
set_age=set(list_age)
age_yes_no= {}#将age的每个值对应的记录属于yes类的总数,保存在字典中
age_no_no = {}
for age in set_age:#开始统计每个年龄段的yes和no的总数
age=age.strip()
age_yes_no[age] = 0
age_no_no[age] = 0
for ele in list_map:
if (ele[4]).strip() == "Yes" and ele[0].strip() == age:
age_yes_no[age] = age_yes_no[age] + 1
if (ele[4]).strip() == "No" and ele[0].strip() == age:
age_no_no[age] = age_no_no[age] + 1
#开始计算age各个年龄段的熵
age_entropy={}
for age in set_age:
age=age.strip()
age_yes_no[age] = float(age_yes_no[age])
age_no_no[age]=float(age_no_no[age])
total=age_yes_no[age] + age_no_no[age]
yes_rate=age_yes_no[age] / total
no_rate=age_no_no[age] / total
if (yes_rate == 0.0 or no_rate == 0.0):
age_entropy[age] = 0
age_entropy[age] = -yes_rate* math.log(yes_rate,2) - no_rate*math.log(no_rate,2)
#开始计算age总的熵
age_rates={}#age各个年龄段占总age的比例
age_length=float(len(list_age))
for age in set_age:
age=age.strip()
age_rates[age]=float(list_age.count(age)) / age_length
age_d_entropy=0.0#age字段的熵
for age in set_age:
age=age.strip()
age_d_entropy= age_d_entropy + age_rates[age]*age_entropy[age]
age_gain=d_entropy - age_d_entropy
print age_gain
使用决策树的scala代码
// 训练样本
val train=spark.sql("select XXX from XXX where XXX").map(row=>{ // 从hive表读取相关属性以及特征标签
XXX // 其他属性
val fea=new org.apache.spark.ml.linalg.DenseVector(fea_arr) // 特征属性 fea_arr 是一个数组,存储特征
val label=row.getAs[Int]("XX") // 样本标签
(XXX)
}).toDF("XX","XX","XX","XX","XX")
// 测试样本
val test=spark.sql("select XXX from XXX where XXX").map(row=>{
XXX
val fea=new org.apache.spark.ml.linalg.DenseVector(fea_arr)
val label=row.getAs[Int]("XXX")
(XXX)
}).toDF("XX","XX","XX","XX","XX")
// 模型 参数自己调
val dt = new DecisionTreeClassifier().setImpurity("entropy").setMaxBins(20).setMaxDepth(20).setMinInfoGain(0.005).setMinInstancesPerNode(10).setCacheNodeIds(true).setMaxMemoryInMB(2048).setSeed(123456)
// 训练
val model2=dt.fit(train)
// 预测
val predictions: DataFrame =model2.transform(test)
// 存储结果
MLModelFactory.saveDataFrame(predictions,"XXX") // 将dataframe存储到hive表
// 拿到预测为1的概率
val pre_1_prob=spark.read.table("XXX").map(row=>{
val userid=row.getAs[Long]("userid")
val mixsongid=row.getAs[Long]("mixosngid")
val probability=row.getAs[org.apache.spark.ml.linalg.Vector]("probability").toArray
(userid,mixsongid,rank,label,probability(1))
}).toDF("XX","XX","XX","XX","XX")
MLModelFactory.saveDataFrame(pre_1_prob,"XXX")
计算每个节点的重要性以及多个特征的选择
特征选择方法中,有一种方法是利用随机森林,进行特征的重要性度量,选择重要性较高的特征。下面对如何计算重要性进行说明。
1 特征重要性度量
计算某个特征X的重要性时,具体步骤如下:
1)对每一颗决策树,选择相应的袋外数据(out of bag,OOB)计算袋外数据误差,记为errOOB1.
所谓袋外数据是指,每次建立决策树时,通过重复抽样得到一个数据用于训练决策树,这时还有大约1/3的数据没有被利用,没有参与决策树的建立。这部分数据可以用于对决策树的性能进行评估,计算模型的预测错误率,称为袋外数据误差。
这已经经过证明是无偏估计的,所以在随机森林算法中不需要再进行交叉验证或者单独的测试集来获取测试集误差的无偏估计。
2)随机对袋外数据OOB所有样本的特征X加入噪声干扰(可以随机改变样本在特征X处的值),再次计算袋外数据误差,记为errOOB2。
3)假设森林中有N棵树,则特征X的重要性=∑(errOOB2-errOOB1)/N。这个数值之所以能够说明特征的重要性是因为,如果加入随机噪声后,袋外数据准确率大幅度下降(即errOOB2上升),说明这个特征对于样本的预测结果有很大影响,进而说明重要程度比较高。
2 特征选择
在特征重要性的基础上,特征选择的步骤如下:
1)计算每个特征的重要性,并按降序排序
2)确定要剔除的比例,依据特征重要性剔除相应比例的特征,得到一个新的特征集
3)用新的特征集重复上述过程,直到剩下m个特征(m为提前设定的值)。
4)根据上述过程中得到的各个特征集和特征集对应的袋外误差率,选择袋外误差率最低的特征集。
在使用过程中,要注意 哪些问题:
1 划分选择
总的准则是划分度高,也就是纯度高。
ID3决策树以信息增益Gain(D,a)为准则来选择划分属性。缺点是信息增益准则对可能取值数目较多的属性有所偏好。Gain(D,a)越大,说明使用属性a来对数据进行划分所得的纯度提升越大。
C4.5决策树选择增益率Gain rate来选择最优划分属性Gain_rate(D,a)=Gain(D,a)/IV(a). 分母表示属性的固有值。属性的可选值越多,固有值越大。这就中和了取值数目的影响。但是又会对取值数目少的属性有所偏好。所以C4.5并不是直接增益率最大属性,而是采用了一个启发式算法:先从候选划分属性中找出信息增益高于平均水平的属性,再从中选择增益率最高的属性。
CART决策树使用基尼指数(gini index)来选择划分属性。Gini(D)反映了从数据集D中随机抽取两个样本,其类别标记不同的概率。显然,gini(D)越小,D的纯度越高。
gini系数的计算方法
https://blog.youkuaiyun.com/weixin_41843918/article/details/90485566
2 剪枝处理
剪枝是决策树解决过拟合的主要手段。在决策树学习中,为了尽可能正确分裂训练样本,节点划分过程将不断重复,有时会造成决策树分支过多,就是学得太好了,以至于把训练集本身的一些特点当作所有数据都具有的一般性质而导致过拟合。因此,要通过主动的剪枝来降低过拟合的风险。决策树剪枝的主要策略有预剪枝(prepruning)和后剪枝(postpruning)。
预剪枝是指在决策树生成过程中,对每个节点在划分前先进行估计,若当前节点的划分不能带来决策树泛化性能提升,就停止划分并将当前节点标记为叶子节点。
后剪枝则是先生成一个决策树,然后自底向上地对非叶节点进行考察,若将该节点对应的子树替换为叶节点能带来决策树泛化性能提升,则将该子树替换为叶节点。
泛化能力的评价,可以使用测试集合。从训练机中划分出来一部分,作为测试集。看模型在测试集的效果。
3 连续值处理
对于属性a,在数据集D上有n个不同的取值。我们可以某个元素作为划分点,将数据分为两部分。
4 缺失值处理
某个训练样本的某个属性值为空,面临两个问题
首先,如何在属性值确实的情况下进行划分属性选择?其次,给定划分属性,若该样本在该属性上的值缺失,如何对样本进行划分?
对于第一个问题,可以将该属性值缺失的样本过滤,得到一个子样本集合,在该子集合上计算划分值的计算。当然,得到结果后,还要加个权值,反映该子集合与全集的关系。
对于第二个问题,根据先验概率,将该样本以不同的概率加入到不同的节点。
5 决策树的优缺点
常见模型的优缺点比较
http://www.youkuaiyun.com/article/2015-12-02/2826374
http://www.csuldw.com/2016/02/26/2016-02-26-choosing-a-machine-learning-classifier/
6 特征对模型的影响
http://www.cnblogs.com/zhizhan/p/5007522.html
7 样本数量对模型的影响
没有找到合适的答案,我的理解如下:
样本数量先满足泛化性,能够代表所有的情况。这就需要有足够的样本量。但是如果太多,可能导致效率太低。