数据分类
数据分类就是建立模型把具有某种共同属性或特征的数据归并在一起,通过其类别的属性或特征来对数据进行区别。这个模型称为分类器,预测分类的(无序,离散)类标号。
数据分类通常分为两个过程,学习阶段(模型构建)和分类阶段(预测类标号)。例如下图我们预测客户是否会购买计算机
a) 学习过程 b) 分类过程
决策树归纳
决策树(Decision Tree)是在已知各种情况发生概率的基础上,通过构成决策树来求取净现值的期望值大于等于零的概率,评价项目风险,判断其可行性的决策分析方法,是直观运用概率分析的一种图解法。由于这种决策分支画成图形很像一棵树的枝干,故称决策树。在机器学习中,决策树是一个预测模型,他代表的是对象属性与对象值之间的一种映射关系。Entropy = 系统的凌乱程度,使用算法ID3, C4.5和C5.0生成树算法使用熵。这一度量是基于信息学理论中熵的概念。
决策树是一种树形结构,其中每个内部节点表示一个属性上的测试,每个分支代表一个测试输出,每个叶节点代表一种类别。
分类树(决策树)是一种十分常用的分类方法。他是一种监管学习,所谓监管学习就是给定一堆样本,每个样本都有一组属性和一个类别,这些类别是事先确定的,那么通过学习得到一个分类器,这个分类器能够对新出现的对象给出正确的分类。这样的机器学习就被称之为监督学习。
决策树是一种树形结构,其中每个内部节点表示一个属性上的测试,每个分支代表一个测试输出,每个叶节点代表一种类别。
分类树(决策树)是一种十分常用的分类方法。他是一种监管学习,所谓监管学习就是给定一堆样本,每个样本都有一组属性和一个类别,这些类别是事先确定的,那么通过学习得到一个分类器,这个分类器能够对新出现的对象给出正确的分类。这样的机器学习就被称之为监督学习。
特征选择
特征选择问题希望选取对训练数据具有良好分类能力的特征,这样可以提高决策树学习的效率。如果利用一个特征进行分类的结果与随机分类的结果没有很大差别,则称这个特征是没有分类能力的(对象是否喜欢打游戏应该不会成为关键特征吧,也许也会……)。为了解决特征选择问题,找出最优特征,先要介绍一些信息论里面的概念。
1.熵(entropy)
熵是表示随机变量不确定性的度量。设XX是一个取有限个值的离散随机变量,其概率分布为
熵越大,随机变量的不确定性就越大。从定义可验证
2.条件熵(conditional entropy)
设有随机变量(X,Y)(X,Y),其联合概率分布为
pi=P(X=xi),i=1,2,⋯,n.
3.信息增益(information gain)
信息增益表示得知特征XX的信息而使得类YY的信息的不确定性减少的程度。特征AA对训练数据集D的信息增益g(D,A)g(D,A),定义为集合DD的经验熵H(D)H(D)与特征AA给定条件下DD的经验条件熵H(D|A)H(D|A)之差,即
根据信息增益准则的特征选择方法是:对训练数据集(或子集)计算其每个特征的信息增益,选择信息增益最大的特征。
计算信息增益的算法如下:
输入:训练数据集 DD 和特征 AA ;
输出:特征 AA 对训练数据集 DD 的信息增益 g(D,A)g(D,A) .
(1)计算数据集 DD 的经验熵 H(D)H(D)
(3)计算信息增益
相对应地,以信息增益作为划分训练数据集的特征的算法称为ID3算法,后面会讲述。
4.信息增益比(information gain ratio)
特征AA对训练数据集DD的信息增益比gR(D,A)gR(D,A)定义为其信息增益g(D,A)g(D,A)与训练数据集DD关于特征AA的值的熵HA(D)HA(D)之比,即
A取值的个数。
相应地,用信息增益比来选择特征的算法称为C4.5算法。
决策树的生成
此处主要介绍两种决策树学习的生成算法:ID3和C4.5。
1.ID3算法
ID3算法由Ross Quinlan发明,建立在“奥卡姆剃刀”的基础上:越是小型的决策树越优于大的决策树(be simple简单理论)。ID3算法中根据信息增益评估和选择特征,每次选择信息增益最大的特征作为判断模块建立子结点。ID3算法可用于划分标称型数据集,没有剪枝的过程,为了去除过度数据匹配的问题,可通过裁剪合并相邻的无法产生大量信息增益的叶子节点(例如设置信息增益阀值)。使用信息增益的话其实是有一个缺点,那就是它偏向于具有大量值的属性。就是说在训练集中,某个属性所取的不同值的个数越多,那么越有可能拿它来作为分裂属性,而这样做有时候是没有意义的,另外ID3不能处理连续分布的数据特征,于是就有了C4.5算法。CART算法也支持连续分布的数据特征。
算法步骤如下:
Python实现:
def chooseBestFeatureToSplitByID3(dataSet):
''' 选择最好的数据集划分方式 :param dataSet:数据集 :return: 划分结果 '''
numFeatures = len(dataSet[0]) - 1 # 最后一列yes分类标签,不属于特征向量
baseEntropy = calcShannonEnt(dataSet)
bestInfoGain = 0.0
bestFeature = -1
for i in range(numFeatures): # 遍历所有特征
infoGain = calcInformationGain(dataSet, baseEntropy, i) # 计算信息增益
if (infoGain > bestInfoGain): # 选择最大的信息增益
bestInfoGain = infoGain
bestFeature = i
return bestFeature # 返回最优特征对应的维度
def majorityCnt(classList):
''' 采用多数表决的方法决定叶结点的分类 :param: 所有的类标签列表 :return: 出现次数最多的类 '''
classCount={}
for vote in classList: # 统计所有类标签的频数
if vote not in classCount.keys():
classCount[vote] = 0
classCount[vote] += 1
sortedClassCount = sorted(classCount.iteritems(), key=operator.itemgetter(1), reverse=True) # 排序
return sortedClassCount[0][0]
def createTree(dataSet,labels):
''' 创建决策树 :param: dataSet:训练数据集 :return: labels:所有的类标签 '''
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 = chooseBestFeatureToSplitByID3(dataSet) # 最优划分特征
bestFeatLabel = labels[bestFeat]
myTree = {bestFeatLabel:{}} # 使用字典类型储存树的信息
del(labels[bestFeat])
featValues = [example[bestFeat] for example in dataSet]
uniqueVals = set(featValues)
for value in uniqueVals:
subLabels = labels[:] # 复制所有类标签,保证每次递归调用时不改变原始列表的内容
myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value),subLabels)
return myTree
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
这里使用Python语言的字典类型存储树的信息,简单方便。当然也可以定义一个新的数据结构存储树。
2.可视化
程序如下:
import matplotlib.pyplot as plt
import tree
# 定义文本框和箭头格式
decisionNode = dict(boxstyle="round4", color='#3366FF') # 定义判断结点形态
leafNode = dict(boxstyle="circle", color='#FF6633') # 定义叶结点形态
arrow_args = dict(arrowstyle="<-", color='g') # 定义箭头
#计算叶结点数
def getNumLeafs(myTree):
numLeafs = 0
firstStr = list(myTree.keys())[0]
secondDict = myTree[firstStr]
for key in secondDict.keys():
if type(secondDict[key]).__name__=='dict':# 测试结点的数据类型是否为字典
numLeafs += getNumLeafs(secondDict[key])
else: numLeafs +=1
return numLeafs
# 计算树的深度
def getTreeDepth(myTree):
maxDepth = 0
firstStr = list(myTree.keys())[0]
secondDict = myTree[firstStr]
for key in secondDict.keys():
if type(secondDict[key]).__name__=='dict':# 测试结点的数据类型是否为字典
thisDepth = 1 + getTreeDepth(secondDict[key])
else: thisDepth = 1
if thisDepth > maxDepth: maxDepth = thisDepth
return maxDepth
# 绘制带箭头的注释
def plotNode(nodeTxt, centerPt, parentPt, nodeType):
createPlot.ax1.annotate(nodeTxt, xy=parentPt, xycoords='axes fraction',
xytext=centerPt, textcoords='axes fraction',
va="center", ha="center", bbox=nodeType, arrowprops=arrow_args )
# 在父子结点间填充文本信息
def plotMidText(cntrPt, parentPt, txtString):
xMid = (parentPt[0]-cntrPt[0])/2.0 + cntrPt[0]
yMid = (parentPt[1]-cntrPt[1])/2.0 + cntrPt[1]
createPlot.ax1.text(xMid, yMid, txtString, va="center", ha="center", rotation=30)
def plotTree(myTree, parentPt, nodeTxt):
numLeafs = getNumLeafs(myTree) # 计算宽与高
depth = getTreeDepth(myTree)
firstStr = list(myTree.keys())[0]
cntrPt = (plotTree.xOff + (1.0 + float(numLeafs))/2.0/plotTree.totalW, plotTree.yOff)
plotMidText(cntrPt, parentPt, nodeTxt)
plotNode(firstStr, cntrPt, parentPt, decisionNode) # 标记子结点属性值
secondDict = myTree[firstStr]
plotTree.yOff = plotTree.yOff - 1.0/plotTree.totalD # 减少y偏移
for key in secondDict.keys():
if type(secondDict[key]).__name__=='dict':
plotTree(secondDict[key],cntrPt,str(key)) #recursion
else: #it's a leaf node print the leaf node
plotTree.xOff = plotTree.xOff + 1.0/plotTree.totalW
plotNode(secondDict[key], (plotTree.xOff, plotTree.yOff), cntrPt, leafNode)
plotMidText((plotTree.xOff, plotTree.yOff), cntrPt, str(key))
plotTree.yOff = plotTree.yOff + 1.0/plotTree.totalD
#if you do get a dictonary you know it's a tree, and the first element will be another dict
def createPlot(inTree):
fig = plt.figure(1, facecolor='white')
fig.clf()
axprops = dict(xticks=[], yticks=[])
createPlot.ax1 = plt.subplot(111, frameon=False, **axprops)
plotTree.totalW = float(getNumLeafs(inTree))
plotTree.totalD = float(getTreeDepth(inTree))
plotTree.xOff = -0.5/plotTree.totalW; plotTree.yOff = 1.0;
plotTree(inTree, (0.5,1.0), '')
plt.show()
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
我们使用贷款申请样本数据表作为测试(使用找对象数据也没问题,反正随自己编):
# 导入数据
def createDataSet():
dataSet = [['youth', 'no', 'no', 1, 'refuse'],
['youth', 'no', 'no', '2', 'refuse'],
['youth', 'yes', 'no', '2', 'agree'],
['youth', 'yes', 'yes', 1, 'agree'],
['youth', 'no', 'no', 1, 'refuse'],
['mid', 'no', 'no', 1, 'refuse'],
['mid', 'no', 'no', '2', 'refuse'],
['mid', 'yes', 'yes', '2', 'agree'],
['mid', 'no', 'yes', '3', 'agree'],
['mid', 'no', 'yes', '3', 'agree'],
['elder', 'no', 'yes', '3', 'agree'],
['elder', 'no', 'yes', '2', 'agree'],
['elder', 'yes', 'no', '2', 'agree'],
['elder', 'yes', 'no', '3', 'agree'],
['elder', 'no', 'no', 1, 'refuse'],
]
labels = ['age', 'working?', 'house?', 'credit_situation']
return dataSet, labels
# 测试代码
if __name__ == "__main__":
myDat, labels = tree.createDataSet()
myTree = tree.createTree(myDat, labels)
print(myTree)
createPlot(myTree)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
绘图效果如下:
3.C4.5算法
C4.5算法用信息增益率来选择属性,继承了ID3算法的优点。并在以下几方面对ID3算法进行了改进:
- 克服了用信息增益选择属性时偏向选择取值多的属性的不足;
- 在树构造过程中进行剪枝;
- 能够完成对连续属性的离散化处理;
- 能够对不完整数据进行处理。
C4.5算法产生的分类规则易于理解、准确率较高;但效率低,因树构造过程中,需要对数据集进行多次的顺序扫描和排序。也是因为必须多次数据集扫描,C4.5只适合于能够驻留于内存的数据集。在实现过程中,C4.5算法在结构与递归上与ID3完全相同,区别只在于选取决决策特征时的决策依据不同,二者都有贪心性质:即通过局部最优构造全局最优。以下是算法步骤:
Python实现如下:
def chooseBestFeatureToSplitByC45(dataSet):
''' 选择最好的数据集划分方式 :param dataSet: :return: 划分结果 '''
numFeatures = len(dataSet[0]) - 1 # 最后一列yes分类标签,不属于特征变量
baseEntropy = calcShannonEnt(dataSet)
bestInfoGainRate = 0.0
bestFeature = -1
for i in range(numFeatures): # 遍历所有维度特征
infoGainRate = calcInformationGainRatio(dataSet, baseEntropy, i) # 计算信息增益比
if (infoGainRate > bestInfoGainRate): # 选择最大的信息增益比
bestInfoGainRate = infoGainRate
bestFeature = i
return bestFeature # 返回最佳特征对应的维度
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
其他部分都相同,只是改变一下调用选择特征的函数。画出来的决策树并没有变化:
5.执行分类
构造了决策树之后,我们就可以将它用于实际数据的分类,在执行分类时,需要输入决策树和用于构造树的所有类标签向量。然后,程序比较测试数据与决策树上的数值,递归执行该过程直到进入叶结点;最后将测试数据定义为叶结点所属的类型。Python实现如下:
def classify(inputTree,featLabels,testVec):
''' 利用决策树进行分类 :param: inputTree:构造好的决策树模型 :param: featLabels:所有的类标签 :param: testVec:测试数据 :return: 分类决策结果 '''
firstStr = inputTree.keys()[0]
secondDict = inputTree[firstStr]
featIndex = featLabels.index(firstStr)
key = testVec[featIndex]
valueOfFeat = secondDict[key]
if isinstance(valueOfFeat, dict):
classLabel = classify(valueOfFeat, featLabels, testVec)
else: classLabel = valueOfFeat
return classLabel
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
测试实例如下: