对于决策树的发展看其分化指标的变化即可:
信息熵(决策树)–》信息增益(ID3)–》信息增益率(C4.5)–》基尼指数(CART)
指标计算的对象是每个特征
决策树对连续型数据特征的处理:
一种属性取值是连续的,我们可以将其表示为比如(X>a)或(X<=a)的形式,将其离散化。比如我们可以把年龄分成【0,18】,【19,28】,【29,38】等,
可是一般,我没有分桶啊,为什么直接训练了
假如现在有一个母亲给女儿找相亲对象的这么一段对话:
女儿:多大年纪了?
母亲:26。
女儿:长的帅不帅?
母亲:挺帅的。
女儿:收入高不?
母亲:不算很高,中等情况。
女儿:是公务员不?
母亲:是,在税务局上班呢。
女儿:那好,我去见见。
以上这个就是决策树的原理。通过不断的划分条件来进行分类。其中决策树最关键的,是找出那些对结果影响最大的条件,放到前面。比如以上如果年龄是女儿最看重的,那么就应该把年龄放到最前面,这样可以节省查找的次数
信息熵是1948年美国数学家香农提出的概念,他是用来表示随机数据不确定性的度量。信息熵越大,也就意味着越不稳定,信息熵越小,说明数据是越稳定(越接近或者类似)。
信息熵的公式是:信息熵的公式里面为什么有log2,因为log函数符合信息熵的定义:信息熵越大,也就意味着越不稳定,信息熵越小,说明数据是越稳定(越接近或者类似)。
p在(0,1)之间,信息熵是log函数取负值,所以p趋近于0,则log趋向于无穷大;p趋近于1,则log趋向于0;表现出了信息熵越大,也就意味着越不稳定,信息熵越小,说明数据是越稳定。
其中H(x)
就表示信息熵,P(F1,F2...)
表示数据在整个数据集中出现的概率。
比如有两组数据:
A = [1,1,1,1,1,1,1,1,2,2]
B = [1,2,3,4,5,6,7,8,9,10]
我们先不去算信息熵,从数据上就能看出,A
是更稳定的,B
的数据更杂,猜想A
的信息熵比B
的信息熵要小。那么通过计算信息熵来确定是不是真的:
H(A) = -(8/10*log(8/10)+2/10*log(2/10)) = 0.7219280948873623
H(B) = -(1/10*log(1/10)*10) = 3.321928094887362
可以看出B
的信息熵明显比A
的信息熵大很多。
信息熵越小,越容易分类,就越重要,越放在前面分类
1位=8比特
。log2
的理解:我们先看下log2
的图:0,如果x越大,那么信息熵也就越小,而x越小,则信息熵越大。正好是和我们期望的是一样的。
信息增益表示特征X
使得类Y
的不确定性减少的程度。因此信息增益越大,那么表示对不确定性减少得越多,也就越能更清晰的分类。特征A
对训练数据集D的信息增益记作g(D,A)
,定义为集合D
的信息熵H(D)
与特征A
给定条件下D
的经验条件熵H(D|A)之差,即公式为:
g(D,A)=H(D)−H(D∣A)
# 其中的H(D|A),A特征中可能有N种值(A1,A2...),计算方式如下
H(D|A) = P(A1)*H(D|A1) + P(A2)*H(D|A2)...
根据信息增益的准则的特征选择方法是:对于训练数据集D,计算其每个特征的信息增益,并比较它们的信息增益,选择信息增益最大的特征。
以上数据集中,有年龄
、是否有工作
、是否有房子
、信贷情况
4个特征来判断是否应该给这个用户贷款,当时放到决策树中,我们应该要把哪个特性放到前面,哪个特性放到后面,就应该通过计算信息增益来决定。
H(贷款) = -(6/15*log2(6/15)+9/15*log2(9/15)) = 0.97
H(贷款|年龄) = H(贷款) - H(贷款|青年) - H(贷款|中年) - H(贷款|老年)
H(贷款|青年) = -5/15*(2/5*log2(2/5) + 3/5*log2(3/5)) = 0.32
H(贷款|中年) = -5/15*(2/5*log2(2/5) + 3/5*log2(3/5)) = 0.32
H(贷款|老年) = -5/15*(1/5*log2(1/5) + 4/5*log2(4/5)) = 0.24
因此H(贷款|年龄) = 0.97 - (0.32+0.32+0.24) = 0.09
同理我们也可以计算出是否有工作
的信息增益为0.32
、是否有房子的信息增益为0.42
、信贷情况的信息增益为0.363
。因此在建立决策树的时候就可以根据信息增益的从大到小排序了。
ID3
算法采用的是信息增益来进行决策树的创建的。信息增益虽然效果不错,但是他是偏向选择分支多的属性,会导致过度拟合,因此一般不会采用此算法。
比如 把ID作为特征属性,会导致ID信息增益最大,但事实ID属性在分类上,基本没有作用
ID3
会导致过度拟合,那么我们能想到的解决办法自然就是对分支过多的情况进行惩罚。于是我们有了信息增益比,或者说信息增益率,信息增益率:
gr = 某特征的信息增益/某特征的信息熵
gr=(H(c)−H(c|X))/H(X)
从上述公式我们可以看到,如果某特征分类特别多,那么会导致信息熵会非常大,从而信息增益也会变得比较大,因此最后得出的信息增益率会变小,这样就对他进行了有效抑制。
C4.5
已经是比较好的构建决策树的算法了,但是他计算的时候需要不断的求对数(log),对算法的效率有一定的影响,因此在CART
中采用了一个叫做基尼(Gini)系数的东西来进行分类。Gini
系数是一种与信息熵类似的做特征选择的方式,可以用来数据的不纯度。Gini
系数的计算方式如下:
Gini(p)=1−(P1^2+P2^2+...)
其中P(n)
代表的是第几个特征出现的概率。
基尼指数越小越好
树的层级和叶子节点不能过于复杂,如果过于复杂,那么容易导致过拟合现象(过拟合:在训练的时候得分很高,但是测试的时候得分很低)。而预剪枝和后剪枝手段,都是为了防止决策树太复杂的手段:
预剪枝就是在决策树的建立过程中不断的调节,可以调节的条件有:
以上的参数,都需要在建模的过程中,不断的调节,来达到最优。
预剪枝可以有效的降低过拟合现象,并且因为是在决策树建立的过程中进行调节,因此显著的减少了训练时间开销和测试时间的开销。另外因为预剪枝是通过限制一些建树的条件来实现的,这种方式容易导致(欠拟合:模型训练得不够好)的现象。
后剪枝就是在决策树建立完成后再进行的,根据以下公式:
C = gini*samples + α*叶子节点个数
C
表示损失,C
越大,表示损失越多,我们要选择损失小的。
其中的α
我们是可以调节的,如果α
越大,那么叶子结点个数越多的,损失越大。因此α
值越大,那么偏向叶子节点少的。α
越小,那么偏向叶子节点多的。
后剪枝通常比预剪枝保留更多的分支,因此欠拟合风险比预剪枝要小,但是因为后剪枝是在树建立完成后再自底向上对所有非叶子节点进行逐一考察,因此训练时间开销比预剪枝要大得多。
在sklearn
中,可以通过sklearn.tree.DecisionTreeClassifier
来实现决策树,这个类有以下参数:
gini
或者entropy
的方式。splitter
:best
或random
。best
是在所有特种中找最好的切分点,random
是随机的找一些特征来进行切分(数据量大的时候用random
)min_weight_fraction_leaf
:叶子节点所有样本权重和的最小值。如果小于这个值,则会和兄弟节点一起被剪枝,默认是0,也就是不考虑权重的问题。一般来说,如果我们有较多样本有缺失值,或者分类树样本的分布类别偏差很大,就会引入样本权重,这时我们就要注意这个值了。None
,即不限制叶子节点的个数。如果设置了这个值,那么在决策树建立的过程中优化叶子节点的个数。如果特征不多,可以不考虑这个值,但是如果特征分多的话,可以加以限制。class_weight
:指定样本各特征的权重,主要是为了方式某些特征的样本过多导致偏向这些特征。默认是balance
,也就是算法会自动的调节权重。import pandas as pd
from sklearn.tree import DecisionTreeClassifier,export_graphviz
from sklearn.model_selection import train_test_split
from sklearn.feature_extraction import DictVectorizer
titanic = pd.read_csv("data/titanic.txt")
features = titanic[['pclass','age','sex']]
# 处理年龄的缺失值,用平均年龄来代替
features['age'].fillna(features['age'].mean(),inplace=True)
targets = titanic['survived']
targets.head()
X_train,X_test,y_train,y_test = train_test_split(features,targets,test_size=0.25)
# 特征抽取:字典的特征抽取
vect = DictVectorizer()
X_train = vect.fit_transform(X_train.to_dict(orient="records"))
X_test = vect.fit_transform(X_test.to_dict(orient="records"))
print(vect.get_feature_names())
classifier = DecisionTreeClassifier()
# fit方法只能处理数值类型
classifier.fit(X_train,y_train)
classifier.score(X_test,y_test)
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import train_test_split
from sklearn.feature_extraction import DictVectorizer
import numpy as np
titanic = pd.read_csv("data/titanic.txt")
features = titanic[['pclass','age','sex']]
features['age'].fillna(features['age'].mean(),inplace=True)
targets = titanic['survived']
X_train,X_test,y_train,y_test = train_test_split(features,targets,test_size=0.25)
# 特征抽取:字典的特征抽取
vect = DictVectorizer(sparse=False)
X_train = vect.fit_transform(X_train.to_dict(orient='records'))
X_test = vect.transform(X_test.to_dict(orient="records"))
tree = DecisionTreeClassifier()
tree.fit(X_train,y_train)
tree.score(X_test,y_test)
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import train_test_split
from sklearn.feature_extraction import DictVectorizer
import numpy as np
titanic = pd.read_csv("data/titanic.txt")
features = titanic[['pclass','age','sex']]
features['age'].fillna(features['age'].mean(),inplace=True)
targets = titanic['survived']
X_train,X_test,y_train,y_test = train_test_split(features,targets,test_size=0.25)
vect = DictVectorizer(sparse=False)
# X_train.to_dict
X_train = vect.fit_transform(X_train.to_dict(orient='records'))
X_test = vect.transform(X_test.to_dict(orient="records"))
tree = DecisionTreeClassifier()
tree.fit(X_train,y_train)
tree.score(X_test,y_test)
X_train.to_dict(orient=‘records’):
决策树训练完成后,如果能直观的看到树的结构,那么会帮助我们更好的理解树的构建情况以及帮助我们做一些剪枝的操作。在sklearn
中,可以通过sklearn.tree.export_graphviz
来将树结构导出为.dot
文件,然后再到这个网站:https://www.graphviz.org/download/
下载graphviz
软件,这个软件可以将.dot
文件转为png
格式。graphviz
安装完成后,记得把安装路径下的bin
目录,添加到环境变量PATH
中。然后再在cmd终端使用命令dot -Tpng 生成的dot文件.dot -o tree.png
将dot
文件转换为png
文件。
其中sklearn.tree.export_graphviz
函数有许多参数,我们了解下以下几个参数:
decision_tree
:决策树,就是DecisionTreeClassifier
的对象。out_file
:生成.dot
文件的路径。feature_names
:特征名称,方便在生成树的时候能在节点看到特征名称。通过get_feature_names
来获取,不能乱填。class_names
:分类的类名。如果设置了,他是按照target
分类的数值从小到大进行排序,所以在指定具体名称的时候,应该根据排序的顺序来。示例代码如下:
export_graphviz(classifier,"tree.dot",feature_names=['age', '1st', '2nd', '3rd', 'female', 'male'],class_names=['die','live'])
集成算法包含(bagging-袋/boosting-增强/stacking-堆叠)在机器学习中,随机森林是一个包含多个决策树的分类器,并且其输出的类别是由个别树输出的类别的众数而定。利用相同的训练数搭建多个独立的分类模型,然后通过投票的方式,以少数服从多数的原则作出最终的分类决策。例如, 如果你训练了5
个树, 其中有4
个树的结果是True
, 1个数的结果是False
, 那么最终结果会是True
.
在前面的决策当中我们提到,一个标准的决策树会根据每维特征对预测结果的影响程度进行排序,进而决定不同的特征从上至下构建分裂节点的顺序,如此以来,所有在随机森林中的决策树都会受这一策略影响而构建的完全一致,从而丧失的多样性。所以在随机森林分类器的构建过程中,每一棵决策树都会放弃这一固定的排序算法,转而随机选取特征。
N
来表示训练用例(样本)的个数,M
表示特征数目。m
,用于确定决策树上一个节点的决策结果;其中m
应远小于M
。N
个训练用例(样本)中以有放回抽样的方式,取样N
次,形成一个训练集(即bootstrap
取样),并用未抽到的用例(样本)作预测,评估其误差。m
个特征,决策树上每个节点的决定都是基于这些特征确定的。根据这m
个特征,计算其最佳的分裂方式。如果不进行随机抽样,每棵树的训练集都一样,那么最终训练出的树分类结果也是完全一样的
如果不是有放回的抽样,那么每棵树的训练样本都是不同的,都是没有交集的,这样每棵树都是“有偏的”,都是绝对“片面的”(当然这样说可能不对),也就是说每棵树训练出来都是有很大的差异的;而随机森林最后分类取决于多棵树(弱分类器)的投票表决。
sklearn
实现随机森林:class sklearn.ensemble.RandomForestClassifier(n_estimators=10, criterion=’gini’,max_depth=None, bootstrap=True, random_state=None)
参数介绍:
n_estimators
:integer
,optional(default = 10)
森林里的树木数量。criteria
:string
,可选(default =“gini”)分割特征的测量方法。max_depth
:integer
或None
,可选(默认=无)树的最大深度 。bootstrap
:boolean
,optional(default = True)
是否在构建树时使用放回抽样。import pandas as pd
from sklearn.tree import DecisionTreeClassifier,export_graphviz
from sklearn.model_selection import train_test_split
from sklearn.feature_extraction import DictVectorizer
from sklearn.ensemble import RandomForestClassifier
titanic = pd.read_csv("data/titanic.txt")
features = titanic[['pclass','age','sex']]
# 处理年龄的缺失值,用平均年龄来代替
features['age'].fillna(features['age'].mean(),inplace=True)
targets = titanic['survived']
X_train,X_test,y_train,y_test = train_test_split(features,targets,test_size=0.25)
vect = DictVectorizer()
X_train = vect.fit_transform(X_train.to_dict(orient="records"))
X_test = vect.fit_transform(X_test.to_dict(orient="records"))
rf = RandomForestClassifier(n_estimators=100)
rf.fit(X_train,y_train)
rf.score(X_test,y_test)