机器学习之决策树算法
决策树(decision tree)是一种基本的分类与回归方法,
决策树由结点(node)和有向边(directed edge)组成,
结点类型:根结点(root node),内部结点(internal node)和叶结点(leaf node)。
决策树的构建:(ID3、C4.5和CART)
一.特征选择
1.特征选择在于选取对训练数据具有分类能力的特征
2.特征选择的标准是信息增益(information gain)
二.决策树的生成
计算信息增益:
1.香农熵:计算所有类别所有可能值包含的信息期望值(数学期望)
2.经验熵:申请样本数据表中的数据为训练数据集D,表示其样本容量 (样本个数)。设有K个类Ck的样本个数,因此经验熵H(D)就可以写为:
3.经验条件熵:设特征A有n个不同的取值{a1,a2,···,an},根据特征A的取值将D划分为n个子集{D1,D2,···,Dn},|Di|为Di的样本个数。记子集Di中属于Ck的样本的集合为Dik,即Dik = Di ∩ Ck,|Dik|为Dik的样本个数。于是经验条件熵的公式可以写为:
4.信息增益:特征A对训练数据集D的信息增益g(D,A),定义为集合D的经验熵H(D)与特征A给定条件下D的经验条件熵H(D|A)之差。
ID3算法
ID3算法的核心:
在决策树各个结点上对应信息增益最大准则选择特征,递归地构建决策树。
ID3算法具体流程:
从根结点开始,对结点计算所有可能的特征的信息增益,选择信息增益最大的特征作为结点的特征,由该特征的不同取值建立子节点;再对子结点递归地调用以上方法,构建决策树。ID3相当于用最大似然法进行概率模型的选择。
ID3算法决策树递归停止条件:
递归创建决策树时,递归有两个终止条件:第一个停止条件是所有的类标签完全相同,则直接返回该类标签;第二个停止条件是使用完了所有特征,仍然不能将数据划分仅包含唯一类别的分组,即决策树构建失败,特征不够用。此时说明数据维度不够,由于第二个停止条件无法简单地返回唯一的类标签,挑选出现数量最多的类别作为返回值。
from math import log
import operator
#计算H(D)
def calcShannonEnt(dataSet):
numEntires = len(dataSet)
#返回数据集的行数
labelCounts = {}
#保存每个标签(Label)出现次数的字典
for featVec in dataSet:
#对每组特征向量进行统计
currentLabel = featVec[-1]
#提取标签(Label)信息
if currentLabel not in labelCounts.keys():
#如果标签(Label)没有放入统计次数的字典,添加进去
labelCounts[currentLabel] = 0
labelCounts[currentLabel] += 1
#Label计数
shannonEnt = 0.0
#经验熵(香农熵)
for key in labelCounts:
#计算香农熵
prob = float(labelCounts[key]) / numEntires
#选择该标签(Label)的概率
shannonEnt -= prob * log(prob, 2)
利用公式计算
return shannonEnt
#返回经验熵(香农熵)
def createDataSet():
dataSet = [
[0, 0, 0, 0, 'no'], #数据集
[0, 0, 0, 1, 'no'],
[0, 1, 0, 1, 'yes'],
[0, 1, 1, 0, 'yes'],
[0, 0, 0, 0, 'no'],
[1, 0, 0, 0, 'no'],
[1, 0, 0, 1, 'no'],
[1, 1, 1, 1, 'yes'],
[1, 0, 1, 2, 'yes'],
[1, 0, 1, 2, 'yes'],
[2, 0, 1, 2, 'yes'],
[2, 0, 1, 1, 'yes'],
[2, 1, 0, 1, 'yes'],
[2, 1, 0, 2, 'yes'],
[2, 0, 0, 0, 'no']]
labels = ['年龄', '有工作', '有自己的房子', '信贷情况']
#特征标签
return dataSet, labels
#返回数据集和分类属性
def splitDataSet(dataSet, axis, value):
retDataSet = []
#创建返回的数据集列表
for featVec in dataSet:
#遍历数据集
if featVec[axis] == value:
reducedFeatVec = featVec[:axis]
#去掉axis特征
reducedFeatVec.extend(featVec[axis+1:])
#将符合条件的添加到返回的数据集
retDataSet.append(reducedFeatVec)
return retDataSet
#返回划分后的数据集
def chooseBestFeatureToSplit(dataSet):
numFeatures = len(dataSet[0]) - 1
#特征数量
baseEntropy = calcShannonEnt(dataSet)
#计算数据集的香农熵
bestInfoGain = 0.0
#信息增益
bestFeature = -1
#最优特征的索引值
for i in range(numFeatures):
#遍历所有特征
#获取dataSet的第i个所有特征
featList = [example[i] for example in dataSet]
uniqueVals = set(featList)
#创建set集合{},元素不可重复
newEntropy = 0.0
#经验条件熵
for value in uniqueVals:
#计算信息增益
subDataSet = splitDataSet(dataSet, i, value) #subDataSet划分后的子集
prob = len(subDataSet) / float(len(dataSet))
#计算子集的概率
newEntropy += prob * calcShannonEnt(subDataSet)
#根据公式计算经验条件熵
infoGain = baseEntropy - newEntropy
#信息增益
# print("第%d个特征的增益为%.3f" % (i, infoGain))
#打印每个特征的信息增益
if (infoGain > bestInfoGain):
#计算信息增益
bestInfoGain = infoGain
#更新信息增益,找到最大的信息增益
bestFeature = i
#记录信息增益最大的特征的索引值
return bestFeature
#返回信息增益最大的特征的索引值
def majorityCnt(classList):
classCount = {}
for vote in classList:
#统计classList中每个元素出现的次数
classCount[vote]=classCount.get(vote,0)+1
sortedClassCount = sorted(classCount.items(), key = operator.itemgetter(1), reverse = True)
#根据字典的值降序排序
return sortedClassCount[0][0]
#返回classList中出现次数最多的元素
#构建决策树
def createTree(dataSet, labels, featLabels):
classList = [example[-1] for example in dataSet]
#取分类标签(是否放贷:yes or no)
if classList.count(classList[0]) == len(classList):
#如果类别完全相同则停止继续划分
return classList[0]
if len(dataSet[0]) == 1:
#遍历完所有特征时返回出现次数最多的类标签
return majorityCnt(classList)
bestFeat = chooseBestFeatureToSplit(dataSet)
#选择最优特征
bestFeatLabel = labels[bestFeat]
#最优特征的标签
myTree = {bestFeatLabel:{}}
#根据最优特征的标签生成树
featLabels.append(bestFeatLabel)
del(labels[bestFeat])
#删除已经使用特征标签
featValues = [example[bestFeat] for example in dataSet]
#得到训练集中所有最优特征的属性值
uniqueVals = set(featValues)
#去掉重复的属性值
for value in uniqueVals:
#遍历特征,创建决策树。
myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value), labels, featLabels)
return myTree
if __name__ == '__main__':
dataSet, labels = createDataSet()
featLabels = []#可变序列
myTree = createTree(dataSet, labels, featLabels)
print(myTree)
C4.5算法
通过信息增益率选择分裂属性,克服了ID3算法中通过信息增益倾向于选择拥有多个属性值的属性作为分裂属的不足。
能够处理离散型和连续型的属性类型,即将连续型的属性进行离散化处理。
能够处理具有缺失属性值的训练数据。
信息增益率的计算:
连续型属性的离散化处理 :
当属性类型为连续型,需要对数据进行离散化处理。C4.5算法针对连续属性的离散化处理的核心思想:将属性A的N个属性值按照升序排列;通过二分法将属性A的所有属性值分成两部分(共有N-1种划分方法,二分的阈值为相邻两个属性值的中间值);计算每种划分方法对应的信息增益,选取信息增益最大的划分方法的阈值作为属性A二分的阈值。
sklearn中的决策树
from sklearn.datasets import load_iris
from sklearn import tree
import graphviz
import os
#引入数据
iris=load_iris()
X=iris.data
y=iris.target
#训练数据和模型,采用C4.5训练
clf=tree.DecisionTreeClassifier(criterion='entropy')
clf=clf.fit(X,y)
#引入graphviz模块用来导出图,结果图如下所示
import graphviz
dot_data=tree.export_graphviz(clf,out_file=None,
feature_names=iris.feature_names,
class_names=iris.target_names,
filled=True,rounded=True,
special_characters=True)
os.chdir(r'C:\graphviz-2.38\release\bin')
graph=graphviz.Source(dot_data)
graph.view()
CART树算法
CART分类回归树是一种典型的二叉决策树,可以做分类或者回归。如果待预测结果是离散型数据,则CART生成分类决策树;如果待预测结果是连续型数据,则CART生成回归决策树。
作为分类决策树时,待预测样本落至某一叶子节点,则输出该叶子节点中所有样本所属类别最多的那一类;作为回归决策树时,待预测样本落至某一叶子节点,则输出该叶子节点中所有样本的均值。
CART用作分类树时采用基尼系数最小化原则,用作回归树时用平方误差最小化作为选择特征的准则,递归地生成二叉树。
CART树基尼系数(基尼不纯度):
创建分类树递归过程中,CART每次都选择当前数据集中具有最小Gini系数的特征作为结点划分决策树。
分类问题中,假设有K个类,样本属于第k类的概率为Pk,则概率分布的基尼系数定义为:
对于二分类问题,若样本点属于第1个类的概率是p,则概率分布的基尼系数为:
如果样本集合S根据特征A是否取到某一可能值i被分割成S1和S2两部分,则在特征A的条件下,基尼系数为:
预测房价回归树
from sklearn.datasets import load_boston
import graphviz
import numpy as np
from sklearn import tree
# 加载boston数据函数
boston = load_boston()
X = boston.data
y = boston.target
from sklearn.model_selection import train_test_split
data_train, data_test, target_train, target_test =train_test_split(X, y, test_size = 0.2, random_state = 42)
dtr = tree.DecisionTreeRegressor(random_state = 42)
dtr.fit(data_train, target_train)
dtr.score(data_test, target_test)
参数调优
from sklearn.model_selection import GridSearchCV
# 加载调优函数
# 设置参数可取值
gini_impure = np.linspace(0,0.01,10)
param_grid = {"min_impurity_decrease":gini_impure,"max_depth":range(2,10),"min_samples_split":range(2,50,2)}
# 设置参数网格
reg = GridSearchCV(tree.DecisionTreeRegressor(),param_grid)
# 建模
reg.fit(X,y)
# 拟合训练集数据
print(reg.best_params_)
print(reg.score(X,y))
# 打印结果
剪枝分为预剪枝和后剪枝:
1.预剪枝:就是在构建决策树的时候提前停止。比如指定树的深度最大为3, 那么训练出来决策树的高度就是3,预剪枝主要是建立某些规则限制决策树的生长,降低了过拟合的风险,降低了建树的时间,但是有可能带来欠拟合问题。
2.后剪枝:后剪枝是一种全局的优化方法,在决策树构建好之后,然后才开始进行剪枝。后剪枝的过程就是删除一些子树,这个叶子节点的标识类别通过大多数原则来确定,即属于这个叶子节点下大多数样本所属的类别就是该叶子节点的标识。选择减掉哪些子树时,可以计算没有减掉子树之前的误差和减掉子树之后的误差,如果相差不大,可以将子树减掉。一般使用后剪枝得到的结果比较好。