今夜的成都没有星星,没有月亮,压抑的高楼里,就着一盏台灯和香气飘渺的白咖啡,来吧打起精神,面对csdn,开启这篇会说话的博文。别问我这张图是哪里来的,图片是我行途中的收获,正如同我代码的精神。
受人之托投个广告:https://items.alitrip.com/item.htm?spm=181.8280233.0.0.66fa4b2bJblHfL&id=613533745335
相逢在博文里~~总感觉有另外的一个我在问自己,写博文是为了啥?为了总结码农的经验?还是为了让码农的经验变成博文?
现在看来都并不重要,重要的是以我的经验能给予你多大的帮助与认可,不求证可否,但同存共勉~~
当初在csdn里看到了某一人写的博文,博文讲述的是费曼学习方法,然后开启我本固执的博文精神。如期所至,重在我的执着与追随
好了,我们的主题是机器学习算法——决策树
一、简介
决策树--首先明确一点,决策树算法终极目标还是为了分类。
不同于之前的k-近邻算法之处在于k-近邻算法无法给出数据的内在含义,计算完成分类之后,得不到任何其他类似结论的信息展示出来,而决策树的优势在于能给人们把自己训练的结果甚至规则创建的过程以图形的方式展示出来,而且非常易于理解,一目了然。下面是我学习的例子中生成的一个决策树,后面会娓娓道来,学习此算法最终就是构造类似下面的决策树
二、构造决策树
在构造之前,要先把信息增益和划分数据集搞清楚,信息增益:在划分数据集之前和之后信息发生的变化成为信息增益,而划分数据集的意义在于在当前数据集上哪个特征在划分数据分类时起决定性作用,比如上面那颗决策树,第一个划分特征就是流眼泪的频率来区分你是否有近视,这个特征是作为第一个决策点是因为在计算信息增益的时候,它的信息增益最大。
那么该如何计算信息增益呢,就要提到一个人克劳德•香浓,集合信息的度量方式称为香浓熵或者简称熵(反正我是没有看大懂这句深奥的数学),书上说,如果看不明白什么事信息增益和熵,请不要着急,他们自诞生那天起,就注定令世人十分费解。
2.1 计算香浓熵
公式:
不要问我公式怎么来的,还是那句话,记住计算香浓熵是数据集的度量方式,通俗一点为了更好地划分数据集
p(xi)是选择该分类的概率,通过该公式计算所有可能值,其中n是分类的数目
def calcShannonEnt(dataSet):
"""
计算香浓熵
:param dataSet: 输入数据集
:return:
"""
numEntries = len(dataSet)
labelCounts = {}
for featVec in dataSet: # the the number of unique elements and their occurance
currentLabel = featVec[-1]
if currentLabel not in labelCounts.keys(): labelCounts[currentLabel] = 0
labelCounts[currentLabel] += 1
shannonEnt = 0.0
for key in labelCounts:
prob = float(labelCounts[key]) / numEntries
shannonEnt -= prob * log(prob, 2) # log base 2
return shannonEnt
第一个for循环计算出所有的目标值(类别)以及它出现的次数,第二个for循环使用所有类别出现的频率计算类别出现的概率,通过概率计算该数据集在无序的情况下的香浓熵。
def createDataSet():
dataSet = [[1, 1, 'yes'],
[1, 1, 'yes'],
[1, 0, 'no'],
[0, 1, 'no'],
[0, 1, 'no']]
labels = ['no surfacing', 'flippers']
# change to discrete values
return dataSet, labels
使用以上测试数据集去测试计算香浓熵的函数,计算结果如下
读者可以尝试修改dataSet中最后一列的目标值,比如修改其中一个yes为maybe,看看香浓熵是如何变化的
2.2 划分数据集
我们将对每个特征划分数据集的结果进行依次计算香浓熵,然后判断按照那个特征划分数据集是最好的划分方式,先来写一个函数该函数按照给定特征划分数据集
def splitDataSet(dataSet, axis, value):
"""
切分数据集
:param dataSet: 待划分的数据集
:param axis: 划分数据集的特征
:param value: 给定特征值
:return:
"""
retDataSet = []
for featVec in dataSet:
if featVec[axis] == value:
reducedFeatVec = featVec[:axis] # chop out axis used for splitting
reducedFeatVec.extend(featVec[axis + 1:])
retDataSet.append(reducedFeatVec)
return retDataSet
读者可自行对该函数行进测试,比如splitDataSet(dataSet, 0, 1)、splitDataSet(dataSet, 1, 1)等。当测试完成搞明白怎么回事之后再继续看下面的函数:遍历整个数据集,循环计算香浓熵和splitDataSet函数,找到最好的特征划分方式。香浓熵计算会告诉我们如何划分方式数据集是最好的划分方式。
def chooseBestFeatureToSplit(dataSet):
numFeatures = len(dataSet[0]) - 1 # the last column is used for the labels
baseEntropy = calcShannonEnt(dataSet)
bestInfoGain = 0.0;
bestFeature = -1
for i in range(numFeatures): # iterate over all the features
featList = [example[i] for example in dataSet] # create a list of all the examples of this feature
uniqueVals = set(featList) # get a set of unique values
newEntropy = 0.0
for value in uniqueVals:
subDataSet = splitDataSet(dataSet, i, value)
prob = len(subDataSet) / float(len(dataSet))
newEntropy += prob * calcShannonEnt(subDataSet)
infoGain = baseEntropy - newEntropy # calculate the info gain; ie reduction in entropy
if infoGain > bestInfoGain: # compare this to the best gain so far
bestInfoGain = infoGain # if better than current best, set to best
bestFeature = i
return bestFeature # returns an integer
函数首先拿到特征数量numFeatures和无序数据集的香浓熵baseEntropy,然后定义了两个变量bestInfoGain(最好的信息增益),bestFeature(划分数据集最好的特征)。for循环内部首先获取到所有不重复的特征集合,然后在遍历这个特征集合,内嵌for循环中按照每个特征划分数据来计算香浓熵,最后找到香浓熵最大的那个特征,返回数据集中该特征的索引。
2.3 抽个烟看下风景休息下
我不会告诉你,这是冷嘎措的
再次受人之托投个广告:https://items.alitrip.com/item.htm?spm=181.8280233.0.0.66fa4b2bJblHfL&id=611706315626
2.4 递归构造决策树
先来看一个函数
def majorityCnt(classList):
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]
该函数接受一个包含类标签的字典,然后统计每个标签类别的出现频数,使用operator操作键值排序字典,返回出现频数最大的类标签。
再看创建树的递归函数
def createTree(dataSet, labels):
classList = [example[-1] for example in dataSet]
if classList.count(classList[0]) == len(classList):
return classList[0] # stop splitting when all of the classes are equal
if len(dataSet[0]) == 1: # stop splitting when there are no more features in dataSet
return majorityCnt(classList)
bestFeat = chooseBestFeatureToSplit(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[:] # copy all of labels, so trees don't mess up existing labels
myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value), subLabels)
return myTree
不知道读者们能看懂这个函数不,反正我是看不懂,最终一步一步debug勉强看懂了,不管你们使用任何方式一定要搞清楚这个函数的运行轨迹,这是决策树的核心,重中之重。
第一步:确定两个递归结束条件,一是所有的类标签完全相同,直接返回改类标签;二是使用完了所有特征,仍然不能将数据集划分成仅包含唯一类别的分组,返回出现频数最多的类别。
第二步:得到列表包含的所有特征值,
第三步:遍历当前选择特征包含的所有属性值,在每个数据集划分上递归调用createTree(),得到返回值插入到字典变量myTree中。
三、测试决策树
使用上述实现的决策树预测隐形眼镜类型
添加如下代码就可测试
if __name__ == '__main__':
# 使用决策树预测隐形眼镜类型
fr = open('./data/lenses.txt')
lenses = [inst.strip().split('\t') for inst in fr.readlines()]
lensesLabels = ['age', 'prescript', 'astigmatic', 'tearRate']
lensesTree = createTree(lenses, lensesLabels)
print(lensesTree)
createPlot(lensesTree)
下面就是测试结果,博客开篇介绍的那副决策树的文档展示如下:
{'tearRate': {'reduced': 'no lenses', 'normal': {'astigmatic': {'yes': {'prescript': {'hyper': {'age': {'pre': 'no lenses', 'presbyopic': 'no lenses', 'young': 'hard'}}, 'myope': 'hard'}}, 'no': {'age': {'pre': 'soft', 'presbyopic': {'prescript': {'hyper': 'soft', 'myope': 'no lenses'}}, 'young': 'soft'}}}}}}
需要测试数据的可以点赞加评论!!!谢谢
参考资料:机器学习实战