在机器学习中,决策树是一个预测模型,他代表的是对象属性与对象值之间的一种映射关系。Entropy = 系统的凌乱程度,使用算法ID3, C4.5和C5.0生成树算法使用熵。这一度量是基于信息学理论中熵的概念。
机器学习中,决策树是一个预测模型;他代表的是对象属性与对象值之间的一种映射关系。树中每个节点表示某个对象,而每个分叉路径则代表的某个可能的属性值,而每个叶结点则对应从根节点到该叶节点所经历的路径所表示的对象的值。决策树仅有单一输出,若欲有复数输出,可以建立独立的决策树以处理不同输出。数据挖掘中决策树是一种经常要用到的技术,可以用于分析数据,同样也可以用来作预测。
从数据产生决策树的机器学习技术叫做决策树学习, 通俗说就是决策树。
一个决策树包含三种类型的节点:
1. 决策节点:通常用矩形框来表示
2.机会节点:通常用圆圈来表示
3.终结点:通常用三角形来表示
决策树学习也是资料探勘中一个普通的方法。在这里,每个决策树都表述了一种树型结构,它由它的分支来对该类型的对象依靠属性进行分类。每个决策树可以依靠对源数据库的分割进行数据测试。这个过程可以递归式的对树进行修剪。 当不能再进行分割或一个单独的类可以被应用于某一分支时,递归过程就完成了。另外,随机森林分类器将许多决策树结合起来以提升分类的正确率。
决策树同时也可以依靠计算条件概率来构造。
决策树如果依靠数学的计算方法可以取得更加理想的效果。 数据库已如下所示: (x, y) = (x1, x2, x3…, xk, y) 相关的变量 Y 表示我们尝试去理解,分类或者更一般化的结果。 其他的变量x1, x2, x3 等则是帮助我们达到目的的变量。
剪枝是决策树停止分支的方法之一,剪枝有分预先剪枝和后剪枝两种。预先剪枝是在树的生长过程中设定一个指标,当达到该指标时就停止生长,这样做容易产生“视界局限”,就是一旦停止分支,使得节点N成为叶节点,就断绝了其后继节点进行“好”的分支操作的任何可能性。不严格的说这些已停止的分支会误导学习算法,导致产生的树不纯度降差最大的地方过分靠近根节点。后剪枝中树首先要充分生长,直到叶节点都有最小的不纯度值为止,因而可以克服“视界局限”。然后对所有相邻的成对叶节点考虑是否消去它们,如果消去能引起令人满意的不纯度增长,那么执行消去,并令它们的公共父节点成为新的叶节点。这种“合并”叶节点的做法和节点分支的过程恰好相反,经过剪枝后叶节点常常会分布在很宽的层次上,树也变得非平衡。后剪枝技术的优点是克服了“视界局限”效应,而且无需保留部分样本用于交叉验证,所以可以充分利用全部训练集的信息。但后剪枝的计算量代价比预剪枝方法大得多,特别是在大样本集中,不过对于小样本的情况,后剪枝方法还是优于预剪枝方法的。
最经典的决策树算法有ID3、C4.5、CART,其中ID3算法是最早被提出的,它可以处理离散属性样本的分类,C4.5和CART算法则可以处理更加复杂的分类问题。
一个西瓜具有色泽,根蒂,敲声,纹理,脐部,触感等特征,我们要通过这些特征来判断一个西瓜是否是好瓜,比如我们先以色泽作为第一次判别的特征,然后是声音,就能得到如下决策树:
如上所述,接下来以如下数据构建决策树模型
编号 | 色泽 | 根蒂 | 敲声 | 纹理 | 脐部 | 触感 | 好瓜 |
---|---|---|---|---|---|---|---|
1 | 青绿 | 蜷缩 | 浊响 | 清晰 | 凹陷 | 硬滑 | 是 |
2 | 乌黑 | 蜷缩 | 沉闷 | 清晰 | 凹陷 | 硬滑 | 是 |
3 | 乌黑 | 蜷缩 | 浊响 | 清晰 | 凹陷 | 硬滑 | 是 |
4 | 青绿 | 蜷缩 | 沉闷 | 清晰 | 凹陷 | 硬滑 | 是 |
5 | 浅白 | 蜷缩 | 浊响 | 清晰 | 凹陷 | 硬滑 | 是 |
6 | 青绿 | 稍蜷 | 浊响 | 清晰 | 稍凹 | 软粘 | 是 |
7 | 乌黑 | 稍蜷 | 浊响 | 稍糊 | 稍凹 | 软粘 | 是 |
8 | 乌黑 | 稍蜷 | 浊响 | 清晰 | 稍凹 | 硬滑 | 是 |
9 | 乌黑 | 稍蜷 | 沉闷 | 稍糊 | 稍凹 | 硬滑 | 否 |
10 | 青绿 | 硬挺 | 清脆 | 清晰 | 平坦 | 软粘 | 否 |
11 | 浅白 | 硬挺 | 清脆 | 模糊 | 平坦 | 硬滑 | 否 |
12 | 浅白 | 蜷缩 | 浊响 | 模糊 | 平坦 | 软粘 | 否 |
13 | 青绿 | 稍蜷 | 浊响 | 稍糊 | 凹陷 | 硬滑 | 否 |
14 | 浅白 | 稍蜷 | 沉闷 | 稍糊 | 凹陷 | 硬滑 | 否 |
15 | 乌黑 | 稍蜷 | 浊响 | 清晰 | 稍凹 | 软粘 | 否 |
16 | 浅白 | 蜷缩 | 浊响 | 模糊 | 平坦 | 硬滑 | 否 |
17 | 青绿 | 蜷缩 | 沉闷 | 稍糊 | 稍凹 | 硬滑 | 否 |
样本有多个属性,该先选哪个样本来划分数据集呢?原则是随着划分不断进行,我们希望决策树的分支节点所包含的样本尽可能属于同一分类,即“纯度”越来越高。
代码
#计算信息熵
def calcInformationEntropy(dataSet):
#dataSet最后一列是类别,前面是特征
dict = {}
m = len(dataSet)
for i in range(m):
#.get()函数:如果没有这个key,就返回默认值;如果有这个key,就返回这个key的value
dict[dataSet[i][-1]] = dict.get(dataSet[i][-1], 0) + 1;
ent = 0
for key in dict.keys():
p = float(dict[key]) / m
ent = ent - (p * np.math.log(p, 2))
return ent
#划分数据集
#dataSet:数据集
#axis: 要划分的列下标
#value: 要划分的列的值
def splitDataSet(dataSet, axis, value):
splitedDataSet = []
for data in dataSet:
if(data[axis] == value):
reduceFeatureVec = data[: axis]
reduceFeatureVec.extend(data[axis + 1 :])
splitedDataSet.append(reduceFeatureVec)
return splitedDataSet
#计算信息增益,然后选择最优的特征进行划分数据集
#信息增益的计算公式:西瓜书P75
def chooseBestFeatureToSplit(dataSet):
#计算整个集合的熵
EntD = calcInformationEntropy(dataSet)
mD = len(dataSet) #行
featureNumber = len(dataSet[0][:]) - 1 #列
maxGain = -1000
bestFeatureIndex = -1
for i in range(featureNumber):
#featureSet = set(dataSet[:][i]) #错误写法:dataSet[:][i]仍然是获取行
featureCol = [x[i] for x in dataSet] #取列表某列的方法!!
featureSet = set(featureCol)
splitedDataSet = []
for av in featureSet:
retDataSet = splitDataSet(dataSet, i, av)
splitedDataSet.append(retDataSet)
gain = EntD
for ds in splitedDataSet:
mDv = len(ds)
gain = gain - (float(mDv) / mD) * calcInformationEntropy(ds)
if(bestFeatureIndex == -1):
maxGain = gain
bestFeatureIndex = i
elif(maxGain < gain):
maxGain = gain
bestFeatureIndex = i
return bestFeatureIndex
#当所有的特征划分完了之后,如果仍然有叶子节点中的数据不是同一个类别,
# 则把类别最多的作为这个叶子节点的标签
def majorityCnt(classList):
dict = {}
for label in classList:
dict[label] = dict.get(label, 0) + 1
sortedDict = sorted(dict, dict.items(), key = operator.itemgetter(1), reversed = True)
return sortedDict[0][0]
#建立决策树
def createTree(dataSet,labels):
classification = targetClass(dataSet) #获取类别种类(集合去重)
if len(classification) == 1:
return list(classification)[0]
if len(labels) == 1:
return majorityRule(dataSet)#返回样本种类较多的类别
sequence = selectOptimalAttribute(dataSet,labels)
print(labels)
optimalAttribute = labels[sequence]
del(labels[sequence])
myTree = {optimalAttribute:{}}
attribute = set([element[sequence] for element in dataSet])
for value in attribute:
print(myTree)
print(value)
subLabels = labels[:]
myTree[optimalAttribute][value] = \
createTree(makeAttributeData(dataSet,value,sequence),subLabels)
return myTree
# #递归构建决策树
# def createTree(dataSet, labels):
# classList = [x[-1] for x in dataSet]
# # if(len(set(classList)) == 1):
# # return classList[0]
# if(classList.count(classList[0]) == len(classList)):
# return classList[0]
# elif(len(dataSet[0]) == 1): #所有的属性全部划分完毕
# return majorityCnt(classList)
# else:
# bestFeatureIndex = chooseBestFeatureToSplit(dataSet)
# bestFeatureLabel = labels[bestFeatureIndex]
# myTree = {bestFeatureLabel: {}}
# del(labels[bestFeatureIndex]) #使用完该属性之后,要删除
# featureList = [x[bestFeatureIndex] for x in dataSet]
# featureSet = set(featureList)
# for feature in featureSet:
# subLabels = labels[:] #拷贝一份,防止label在递归的时候被修改 (list是传引用调用)
# tmpDataSet = splitDataSet(dataSet, bestFeatureIndex, feature) #划分数据集
# myTree[bestFeatureLabel][feature] = createTree(tmpDataSet, subLabels)
# return myTree
#读取西瓜数据集2.0
def readWatermelonDataSet():
ifile = open("D:\\watermalon.txt",encoding = 'utf-8')
featureName = ifile.readline() #表头
labels = (featureName.split(' ')[0]).split(',')
lines = ifile.readlines()
dataSet = []
for line in lines:
tmp = line.split('\n')[0]
tmp = tmp.split(',')
dataSet.append(tmp)
return dataSet, labels
melonDataSet, melonLabels = readWatermelonDataSet()
print(melonLabels)
melonBestFeature = chooseBestFeatureToSplit(melonDataSet)
tree = createTree(melonDataSet, melonLabels)
print(tree)
熵和信息增益
设S是训练样本集,它包括n个类别的样本,这些方法用Ci表示,那么熵和信息增益用下面公式表示
信息熵
其中pi表示Ci的概率
其中Si表示根据属性A划分的S的第i个子集,S和Si表示样本数目
ID3中样本分布越均匀,它的信息熵就越大,所以其原则就是样本熵越小越好,也就是信息增益越大越好。
**代码 **
# 读取西瓜数据集
import numpy as np
import pandas as pd
df = pd.read_table(r'D:/watermalon.txt',encoding='utf8',delimiter=',',index_col=0)
df.head()
# 由于上面的数据中包含了中文汉字,所以需要对数据进一步处理
'''
属性:
色泽 1-3代表 浅白 青绿 乌黑 根蒂 1-3代表 稍蜷 蜷缩 硬挺
敲声 1-3代表 清脆 浊响 沉闷 纹理 1-3代表 清晰 稍糊 模糊
脐部 1-3代表 平坦 稍凹 凹陷 触感 1-2代表 硬滑 软粘
标签:
好瓜 1代表 是 0 代表 不是
'''
df['色泽']=df['色泽'].map({'浅白':1,'青绿':2,'乌黑':3})
df['根蒂']=df['根蒂'].map({'稍蜷':1,'蜷缩':2,'硬挺':3})
df['敲声']=df['敲声'].map({'清脆':1,'浊响':2,'沉闷':3})
df['纹理']=df['纹理'].map({'清晰':1,'稍糊':2,'模糊':3})
df['脐部']=df['脐部'].map({'平坦':1,'稍凹':2,'凹陷':3})
df['触感'] = np.where(df['触感']=="硬滑",1,2)
df['好瓜'] = np.where(df['好瓜']=="是",1,0)
#由于西瓜数据集样本比较少,所以不划分数据集,将所有的西瓜数据用来训练模型
Xtrain = df.iloc[:,:-1]
Xtrain = np.array(Xtrain)
Ytrain = df.iloc[:,-1]
# 调用sklearn内置的决策树的库和画图工具
from sklearn import tree
import graphviz
# 采用ID3算法,利用信息熵构建决策树模型
clf = tree.DecisionTreeClassifier(criterion="entropy")
clf = clf.fit(Xtrain,Ytrain)
# 绘制决策树的图形
feature_names = ["色泽","根蒂","敲声","纹理","脐部","触感"]
dot_data = tree.export_graphviz(clf
,feature_names=feature_names
,class_names=["好瓜","坏瓜"]
,filled=True
,rounded=True
)
graph = graphviz.Source(dot_data)
graph
C4.5算法的基本流程与ID3类似,但C4.5算法进行特征选择时不是通过计算信息增益完成的,而是通过信息增益比来进行特征选择。
** 信息增益比 **
通过上面的定义,我们可以将信息增益比理解为信息增益的标准化,特征之间的信息增益比可比较性更强一些。同时我们要注意区分关于类别的熵,关于特征的条件熵和关于特征的熵。
决策树构建
输入:训练数据D={(x1,y1),⋯,(xN,yN)},特征集AA,信息增益比阈值ϵϵ
输出:决策树T
step1 若D中样本特征为空,那么树T为一棵单结点树,将样本数最大的类别作为树的类别,返回T;否则,转到step2。
step2 计算训练集D关于所有特征的信息增益比,若信息增益比均小于阈值ϵ,则TT是一棵单结点树,将样本数最大的类别作为树的类别,返回T;否则,转到step3。
step3 选择信息增益比最大的特征Amax作为根结点特征,依据Amax的所有可能值建立相应子结点,若子结点的样本全部属于同一类别,则子结点为叶结点,将叶结点类别标记为该结点所有样本所属的类别,若所有子结点均为叶结点,返回T;否则,返回T并转到step4。
step4 对非叶子结点i,以Di为训练数据集,A=A−{Amax}为特征集,递归地调用step1-step3,返回子树Ti。
剪枝
通过信息增益比计算得到的决策树也需要进行剪枝,剪枝方法同ID3算法。
CART算法构造的是二叉决策树,决策树构造出来后同样需要剪枝,才能更好的应用于未知数据的分类。CART算法在构造决策树时通过基尼系数来进行特征选择。
** 基尼指数 **
如果样本集合D根据特征Am的取值可以分为两部分D1和D2,那么在特征Am的条件下,D的基尼指数如下:
基尼指数Gini(D)表征着数据集D的不确定性,而在特征Am的条件下,D的基尼指数则表征着在特征Am确定的条件下D的不确定性,因此基尼指数之差和信息增益及信息增益比一样,可以表征特征Am对数据集D的分类的能力。
决策树构建
输入: 训练数据集D,特征集A,结点样本数阈值δ,基尼指数阈值ϵ
输出: CART二叉决策树TT
step1 若样本特征集为空,则TT是一棵单节点数,其类别为D中样本数最多的类,返回T;否则,转到step2。
step2 对数据集D,计算特征集A中所有特征所有可能切分点的基尼指数,若基尼指数的值均小于给定阈值ϵ,则TT是一棵单节点数,其类别为D中样本数最多的类,返回T;否则,转到step3。
step3 选择基尼指数最小的特征Amin和相应切分点α作为根节点的特征值和切分标准,根据D中样本是否等于α(或≤α≤α,或≥α≥α)将D分为两个子集D1和D2,将D1和D2分别分配到两个子结点中,若子结点样本数均小于给定阈值δ,则该子结点是一个叶结点,若两个子结点均为叶结点,返回T;否则,返回T并转到step4。
step4 对于非叶子结点,令D等于该子结点所对应的数据集,特征集A=A−{Amin},递归的调用step1-step3,返回子树T。
剪枝
CART算法生成的决策树同样需要剪枝,剪枝的方式是先进行剪枝形成一系列子树,再用独立的验证数据集对这些子树进行验证,找到对于验证数据集基尼指数最小的树,将作为最优决策树。在那些结点剪枝是通过损失函数计算的,它是决策树对训练数据的分类误差,|T|是决策树结点总数,α是平衡两者的参数,α越大,优化后的决策树越简单(结点越少),α越小,优化后的决策树对训练数据的分类误差越小。 对于整体二叉决策树T的某个内部结点t,以t为单结点树的损失函数是Cα(t)=C(t)+α,以t为根结点的子树Tt的损失函数是Cα(Tt)=C(Tt)+α|Tt|,当α充分小时,有Cα(Tt)
Cα(t)。当Cα(Tt)=Cα(t)时,α=C(t)−C(Tt)|Tt|−1,此时,t与Tt有相同的损失函数,但t的结构更简单,因此剪掉子树Tt,构建子树序列T0,T1,⋯,TL的方法就是基于这种思想的。 α0=0,对决策树T0,对于其所有内部结点tt,计算 g(t)=C(t)−C(Tt)|Tt|−1 g(t)的最小值为α1,T0为区间[α0,α1)上的最优子树,g(t)取最小值处的结点为t1,剪掉以t1为根结点的子树Tt1即得到T1,T1为区间[α1,α2)上的最优子树。重复上述操作,直到得到的最优子树为一个根结点和两个叶结点构成的树,此时我们得到了一系列的αα值:0=α0<α1<⋯<αL,和区间[α0,α1),[α1,α2),⋯,[αL,∞)上的最优子树序列:T0,T1,⋯,TL。 然后利用验证数据集对最优子树序列进行测试,选择对于验证数据集有最小基尼指数的最优子树作为最终的最优决策树。若最终Tl为最优决策树,那么其所对应的αl就是损失函数的参数α。
输入: CART算法生成的决策树T
输出: 最优决策树Tα
step1 若T为单结点树或由一个根结点和两个叶结点构成的树,返回T;否则,k=0,α0=0,T0=T,转到step2。
step2 k=k+1,计算Tk−1所有内部结点t的g(t)值,g(t)的最小值为αk,g(t)取最小值处的结点为tk,剪掉以tk为根结点的子树Ttk,并以tk处类别最多的样本作为新的叶结点tk的类,得到[αk,αk+1)上的最优子树Tk。
step3 若Tk是由一个根结点和两个叶结点构成的树,转到step4;否则,转到step2。
step4 利用验证数据集对最优子树序列T0,T1,⋯,TL进行测试,选择对于验证数据集有最小基尼指数的最优子树作为最终的最优决策树Tα,返回Tα。