决策树及对某个数据集选择某个特征进行分裂,由此对数据进行分类
特征选择:从训练数据的特征中选择一个特征作为当前节点的分裂标准
决策树生成:根据所选特征评估标准,从上之下递归地生成子节点,直到数据集不可分则停止决策树生长
剪枝:如果决策树层次过于深,就会造成过拟合,需要剪枝来缩小树的结构和规模(包括预剪枝和后剪枝)
注意:决策树的生成是一个递归地过程,在决策树的基本算法中,有三种情况会导致递归返回:
1.当前节点包含的样本权属于同一类别,不需要划分了
2.当前属性集为空,或是所有样本在所有属性上取值相同,无法划分
3.当前节点包含的样本集为空不能划分
ID3 | 通过信息增益 |
---|---|
C4.5 | 通过信息增益率 |
CART | 通过基尼系数 |
信息熵 | |
---|---|
信息增益 |
实例:
按年龄 | |
---|---|
按收入 | |
按学生 | |
按信用 | |
最后比较信息增益 | 结果第一次按照年龄进行分裂 |
第三步确定第二次分裂的属性:
分裂过程与第一次相同,基于年龄的数据集之上
直到最后节点只有同类的数据就停止分裂
决策树优点 | 概念简单,计算复杂度不高,可解释性强,输出结果易于理解;数据的准备工作简单;对中间值的缺失不敏感;应用范围广 |
---|---|
决策树的缺点 | 可能会产生过度匹配的问题;信息缺失时处理起来比较困难;信息增益来度量会偏向于取值较多的属性作为分类属性 |
改进1 | 用信息增益率代替信息增益来选择属性,克服了用信息增益选择属性时偏向选择取值多的属性不足: |
---|---|
改进2 | 能够完成对连续值属性的离散化处理 |
改进3 | 能处理属性值缺失的情况 |
改进4 | 在决策树构造完成之后进行剪枝:预剪枝:事先设定需要生成几个层次的树,在完全正确分类训练集之后就停止树的生长;后剪枝:先让整棵树生成,然后再剪枝 |
实例:
按有房情况 | |
---|---|
按婚姻状况 |
连续值的分裂:
# -*- coding: utf-8 -*-
"""
Created on Sat Mar 7 11:58:40 2020
@author: DELL
"""
#定义函数,构造函数数据集
import operator
from math import log
import subprocess
from sklearn import tree
def createDataSet():
labels =["头发","声音"]#两个特征
dataSet=[['长','粗','男'],
['短','粗','男'],
['短','粗','男'],
['长','粗','女'],
['长','细','女'],
['短','粗','女'],
['短','细','女'],
['长','粗','女']]
return dataSet,labels
#获取函数类型的类别
def createTree(dataSet,lables):
#获取每个类别男生数量,女生数量
classlist=[example[-1] for example in dataSet]
#print(classlist)
#统计每个类别的人数classlist.count(classlist)
#判断 是否停止树的计算
#第一种情况,如果类别人数和总人数相等,不进行下述计算
if classlist.count(classlist[0])==len(classlist):
return classlist[0]
#第二种情况,如果数据集中只有一个人 也不进行计算
if len(dataSet[0]) == 1:
return majorityCnt(classlist)
#选择分裂类型
bestFeat = chooseBestFeatureTosplit(dataSet)
bestFeatlable = lables[bestFeat]
myTree = {bestFeatlable:{}}
del(lables[bestFeat])
featValues=[example[bestFeat] for example in dataSet]
uniqueVals=set(featValues)
for value in uniqueVals:
subLabels=lables[:]
myTree[bestFeatlable][value]=createTree(splitDataSet(dataSet,bestFeat,value),subLabels)
return myTree
def chooseBestFeatureTosplit(dataSet):
#获取特征列的个数("长",“粗”)两列
numFeature = len (dataSet[0])-1
#对整个数据集 计算 信息熵
baseEntropy = calcShannonEnt(dataSet)
#对每一列计算条件信息熵
bestInfoGain = 0
bestFeature = -1
#把两列属性获取出来
for i in range (numFeature):
featlist = [example [i] for example in dataSet]
#print(featlist)
'''['长', '短', '短', '长', '长', '短', '短', '长']
['粗', '粗', '粗', '粗', '细', '粗', '细', '粗']'''
#获取每个特征里面 有的属性值 长的一列 粗的一列
uniqueVals = set(featlist)
#print(uniqueVals)
'''{'长', '短'}
{'粗', '细'}'''
newEntroy = 0
#统计每个类别的 样本的 数量--->得出信息熵
for value in uniqueVals:
subDataSet = splitDataSet(dataSet,i,value)#对哪个数据集,哪个类别,哪个属性
#计算 条件信息熵
prob = len(subDataSet)/float(len(dataSet))
newEntroy += prob*calcShannonEnt(subDataSet)
#信息增益 信息熵-条件信息熵
infoGain= baseEntropy - newEntroy
#比较两列属性的信息增益
#比较出最大的信息增益
if(infoGain > bestInfoGain):
bestInfoGain=infoGain
bestFeature = i
return bestFeature
#计算信息熵
#按照某个特征值 对数据集进行划分,希望获取某个类的总个数
#返回和某个类别一样的所有数据“长”, 去除掉 当前的属性列 所有数据集
def splitDataSet(dataSet,i,value):
retDataSet=[]
for vote in dataSet:
if vote[i]==value:
reduceFeatVec = vote[:i]
reduceFeatVec.extend(vote[i+1:])
retDataSet.append(reduceFeatVec)
return retDataSet
def calcShannonEnt(dataSet):
classCount = {}
#遍历整个 数据集 各个类别的人数统计出来
for vote in dataSet:
currentLabel = vote [-1]
if currentLabel not in classCount.keys():
classCount[currentLabel]=0
classCount[currentLabel] += 1
#得到 (男,3) (女,4)这种类别
#根据 信息熵的计算方式 得到信息熵
shannonEnt = 0
for key in classCount:
#获取整个数据集的大小-----14
numEntries = len(dataSet)
prob = float(classCount[key])/numEntries
shannonEnt -= prob*log(prob,2)#基础的信息熵
return shannonEnt
def majorityCnt(classlist):
classcount ={}
#在classlist男和女进行遍历
for vote in classlist:
if vote not in classcount.keys():
#如果没有男或女这个标签就添加并加一
classcount[vote] = 0
classcount[vote] += 1
sortedClasscount=sorted(classcount.items(),key=operator.itemgetter(1),reverse=True)
#print (sortedClasscount[0][0])
return sortedClasscount[0][0]
if __name__=='__main__':
dataSet,lables = createDataSet()
#print(dataSet)
print(createTree(dataSet,lables))