机器学习(四):CART分类树(基础篇)
相关的决策树文章:
- 机器学习(四)ID3决策树
- 机器学习(四)C4.5决策树
- 机器学习(四)CART回归树
- 机器学习(四)决策树绘图
- 机器学习(四)剪枝技术
大家都知道,著名的决策树有三种:ID3,C4.5,CART。前面两棵树只适用于分类,前面两个数的优点我们都知道了,那么问题来了,我们为什么要学习CART树呢?CART树又叫分类,回归树,顾名思义,它既可以做分类又可以做回归。是不是感觉很神奇?我们看看他的实现原理。
CRAT树度量指标
1.回归树
纯度:回归方差
其中,I为i可以取的值,
x
i
x_i
xi表示取i的时候的x的值,
m
u
mu
mu表示平均值。
归回方差越大,节点数据越分散(不纯)。故选择划分的依据为回归方差和的大小。我们要保证回归方差最小化:
这一章,我们主要介绍CART决策树的分类作用。上面的知识可以先了解一下。
2.分类树
2.1 Gini(基尼系数):
分类问题中,假设有k个类,样本点属于第k类的概率为 p k p_k pk,则概率分布的基尼指数定义为:
G i n i ( D ) = ∑ k = 1 ∣ y ∣ ∑ k ′ ! = k p k p k ′ = 1 − ∑ k = 1 ∣ y ∣ p k 2 Gini(D) = \displaystyle\sum_{k=1}^{|y|}\displaystyle\sum_{k'!=k}p_kp_{k'}=1-\displaystyle\sum_{k=1}^{|y|}p_k^2 Gini(D)=k=1∑∣y∣k′!=k∑pkpk′=1−k=1∑∣y∣pk2
ps: p k p_k pk表示选中的样本属于k类别的概率,则这个样本被分错的概率 p k ′ p_k' pk′为 ( 1 − p k ) (1-p_k) (1−pk)。
用法:对于给定的样本集合D,其基尼指数为:
G
i
n
i
=
1
−
∑
k
=
1
K
(
∣
C
k
∣
∣
D
∣
)
2
Gini=1-\displaystyle\sum_{k=1}^{K}(\frac{|C_k|}{|D|})^2
Gini=1−k=1∑K(∣D∣∣Ck∣)2
ps:这里
C
k
C_k
Ck是D中属于第k类的样本自己,K是类的个数。
2.2 属性a的基尼系数:
G
i
n
i
i
n
d
e
x
(
D
,
a
)
=
∑
v
=
1
v
∣
D
v
∣
∣
D
∣
G
i
n
i
(
D
v
)
Gini_index(D,a)=\displaystyle\sum_{v=1}^{v}\frac{|D^v|}{|D|}Gini(D^v)
Giniindex(D,a)=v=1∑v∣D∣∣Dv∣Gini(Dv)
于是,我们在候选属性集合A中,选择那个使得划分后基尼系数最小的属性作为最优划分属性。
用法:如果样本集合D根据特征A是否取某一可能值a被分割成D1和D2两部分,则:
则在特征A的条件下,集合D的基尼系数定义为:
基尼系数Gini(D)表示集合D的不确定性,基尼系数Gini(D,A)表示经A=a分割后集合D的不确定性。基尼指数越大,样本集合的不确定性也就越大,这一点跟熵很相似。。
3.算法步骤
输入:训练数据集D,停止计算的条件
输出:CART决策树
根据训练数据集,从根节点开始,递归地对每一个节点进行以下操作,构建二叉树:
Step1:设结点地训练数据集为D,计算现有特征对该数据集地基尼指数。此时,对每一个特征A,对其可能取的每一个值a,根据样本点A=a的测试为“是”或“否”将d分割为D1和D2两部分,利用上式Gini(A)来计算A=a时的基尼系数。
Step2:在所有可能的特征A以及他们所有可能的切分点a中,选择基尼系数最小的特征及其对应可能的切分点作为最优特征与最优切分点。依靠最优特征与最优切分点,从现结点生成两个子节点,将训练数据集依特征分配到两个子节点中去。
Step3:对两个子节点递归地调用Step1,Step2,直至满足条件
Step4:生成CART决策树
算法停止计算地条件:
- 结点中地样本个数小于预定阈值
- 样本集地基尼指数小于预定阈值
- 没有更多地特征
4.代码实现
from math import log
import operator
import sys
import plotTrees
sys.path.append(r'D:\python\QG\machine-learning\decide tree\plotTree')
def calcProbabilityEnt(dataSet):
"""
样本点属于第1个类的概率p,即计算2p(1-p)中的p
@param dataSet: 数据集
@return probabilityEnt: 数据集的概率
"""
numEntries = len(dataSet) # 数据条数
feaCounts = 0
fea1 = dataSet[0][len(dataSet[0]) - 1]
for featVec in dataSet: # 每行数据
if featVec[-1] == fea1:
feaCounts += 1
probabilityEnt = float(feaCounts) / numEntries
return probabilityEnt
def splitDataSet(dataSet, index, value):
"""
划分数据集,提取含有某个特征的某个属性的所有数据
@param dataSet: 数据集
@param index: 属性值所对应的特征列
@param value: 某个属性值
@return retDataSet: 含有某个特征的某个属性的数据集
"""
retDataSet = []
for featVec in dataSet:
# 如果该样本该特征的属性值等于传入的属性值,则去掉该属性然后放入数据集中
if featVec[index] == value:
reducedFeatVec = featVec[:index] + featVec[index + 1:] # 去掉该属性的当前样本
retDataSet.append(reducedFeatVec) # append向末尾追加一个新元素,新元素在元素中格式不变,如数组作为一个值在元素中存在
return retDataSet
def chooseBestFeatureToSplit(dataSet):
"""
选择最优特征
@param dataSet: 数据集
@return bestFeature: 最优特征所在列
"""
numFeatures = len(dataSet[0]) - 1 # 特征总数
if numFeatures == 1: # 当只有一个特征时
return 0
bestGini = 1 # 最佳基尼系数
bestFeature = -1 # 最优特征
for i in range(numFeatures):
uniqueVals = set(example[i] for example in dataSet) # 去重,每个属性值唯一
feaGini = 0 # 定义特征的值的基尼系数
# 依次计算每个特征的值的熵
for value in uniqueVals:
subDataSet = splitDataSet(dataSet, i, value) # 根据该特征属性值分的类
# 参数:原数据、循环次数(当前属性值所在列)、当前属性值
prob = len(subDataSet) / float(len(dataSet))
probabilityEnt = calcProbabilityEnt(subDataSet)
feaGini += prob * (2 * probabilityEnt * (1 - probabilityEnt))
if (feaGini < bestGini): # 基尼系数越小越好
bestGini = feaGini
bestFeature = i
return bestFeature
def majorityCnt(classList):
"""
对最后一个特征分类,出现次数最多的类即为该属性类别,比如:最后分类为2男1女,则判定为男
@param classList: 数据集,也是类别集
@return sortedClassCount[0][0]: 该属性的类别
"""
classCount = {}
# 计算每个类别出现次数
for vote in classList:
try:
classCount[vote] += 1
except KeyError:
classCount[vote] = 1
sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True) # 出现次数最多的类别在首位
# 对第1个参数,按照参数的第1个域来进行排序(第2个参数),然后反序(第3个参数)
return sortedClassCount[0][0] # 该属性的类别
def createTree(dataSet, labels):
"""
对最后一个特征分类,按分类后类别数量排序,比如:最后分类为2同意1不同意,则判定为同意
@param dataSet: 数据集
@param labels: 特征集
@return myTree: 决策树
"""
classList = [example[-1] for example in dataSet] # 获取每行数据的最后一个值,即每行数据的类别
# 当数据集只有一个类别
if classList.count(classList[0]) == len(classList):
return classList[0]
# 当数据集只剩一列(即类别),即根据最后一个特征分类
if len(dataSet[0]) == 1:
return majorityCnt(classList)
# 其他情况
bestFeat = chooseBestFeatureToSplit(dataSet) # 选择最优特征(所在列)
bestFeatLabel = labels[bestFeat] # 最优特征
del (labels[bestFeat]) # 从特征集中删除当前最优特征
uniqueVals = set(example[bestFeat] for example in dataSet) # 选出最优特征对应属性的唯一值
myTree = {bestFeatLabel: {}} # 分类结果以字典形式保存
for value in uniqueVals:
subLabels = labels[:] # 深拷贝,拷贝后的值与原值无关(普通复制为浅拷贝,对原值或拷贝后的值的改变互相影响)
myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value), subLabels) # 递归调用创建决策树
return myTree
import pandas as pd
import numpy as np
def loadDataSet(filename):
"""
函数说明:从文件中下载数据,并将分离除连续型变量和标签变量
:parameter:
data - Iris数据集
attributes - 鸢尾花的属性
type - 鸢尾花的类别
sl-花萼长度 , sw-花萼宽度, pl-花瓣长度, pw-花瓣宽度
:return:
"""
iris_data = pd.read_csv(filename) #打开文件
iris_data = pd.DataFrame(data=np.array(iris_data), columns=['sl', 'sw', 'pl', 'pw', 'type'], index=range(149)) #给数据集添加列名,方便后面的操作
dataSet = iris_data.values.tolist()
return dataSet
if __name__ == '__main__':
# # 数据集
# dataSet = [('青年', '否', '否', '一般', '不同意'),
# ('青年', '否', '否', '好', '不同意'),
# ('青年', '是', '否', '好', '同意'),
# ('青年', '是', '是', '一般', '同意'),
# ('青年', '否', '否', '一般', '不同意'),
# ('中年', '否', '否', '一般', '不同意'),
# ('中年', '否', '否', '好', '不同意'),
# ('中年', '是', '是', '好', '同意'),
# ('中年', '否', '是', '非常好', '同意'),
# ('中年', '否', '是', '非常好', '同意'),
# ('老年', '否', '是', '非常好', '同意'),
# ('老年', '否', '是', '好', '同意'),
# ('老年', '是', '否', '好', '同意'),
# ('老年', '是', '否', '非常好', '同意'),
# ('老年', '否', '否', '一般', '不同意')]
# # 特征集
# labels = ['年龄', '有工作', '有房子', '信贷情况']
# 创建树
dataSet =[['sunny', 'hot', 'high', 'FALSE', '25'],
['sunny', 'hot', 'high', 'TRUE', '30'],
['vercast', 'hot', 'high', 'FALSE', '46'],
['rainy ', 'mild', 'high', 'FALSE', '45'],
['rainy', 'cool', 'normal', 'FALSE', '52'],
['rainy', 'cool', 'normal', 'TRUE', '23'],
['overcast', 'cool', 'normal', 'TRUE', '43'],
['sunny', 'mild', 'high', 'FALSE', '35'],
['sunny ', 'cool', 'normal', 'FALSE', '38'],
['rainy', 'mild', 'normal', 'FALSE', '46'],
['sunny', 'mild', 'normal', 'TRUE', '48'],
['overcast', 'mild', 'high ', 'TRUE ', '52'],
['overcast', 'hot', 'normal ', 'FALSE ', '44'],
['rainy', 'mild', 'high', 'TRUE', '30']]
labels= ['weather','temp', 'hight', 'weatherplay']
tree = createTree(dataSet, labels) # 输出决策树模型结果
plotTrees.createPlot(tree)
我们看看结果:
很好的生成了一个决策树。在这里CART决策树属于分类树,之后面的文章中,我会继续讲解一下回归树的原理和运用。