机器学习实战——笔记(决策树)

决策树

目录
  • 决策树算法原理
  • 代码实现
一、决策树算法原理

机器学习实战——笔记(决策树)_第1张图片
直接上图,直观的了解下决策树是什么?
矩形:表示判断模块
椭圆形:表示终止模块
我相信上图大家都看得懂吧~

决策树
优点: 计算复杂度不高、输出结果易于理解,对中间值的缺失不敏感,可处理不相关特征数据
缺点: 可能会产生过度匹配问题
适用数据范围: 数值型和标称型

  • 信息增益
    决策树就是在当前数据集上依据某个决定性属性特征来划分数据集,并在划分的子集上重复该过程,直到所有具有相同类型的数据在一个数据子集内。

我们划分数据集是有个大原则的: 将无序的数据变得更加有序。
这里引出几个概念(遇到公式不要怕):
信息增益:划分数据集之前之后信息发生的变化称为信息增益。

知道如何计算信息增益,我们就可以计算每个特征值划分数据集获得的信息增益,获得信息增益最高的特征就是最好的选择。

:定义为信息的期望值。

如果待分类的事物可能划分在多个类之中,则符号 x i x_i xi的信息定义为:
l ( x i ) = − l o g 2 p ( x i ) l(x_i)=-log_2p(x_i) l(xi)=log2p(xi)
其中 p ( x i ) p(x_i) p(xi)是选择该分类的概率。

上面只是某个类别的信息,则是将所有类别求和:
H = − ∑ i = 1 n p ( x i ) l o g 2 p ( x i ) H=-\sum_{i=1}^{n}{p(x_i)log_2p(x_i)} H=i=1np(xi)log2p(xi)

代码实现

from math import log


# 计算香农熵
def calcShannonEnt(dataSet):
    numEntries = len(dataSet)   # 数组长度为5
    labelCounts = {}    # 类别标签数
    for featVec in dataSet:
        currentLabel = featVec[-1]  # 每一条最后一列,也就是'yes'和'no'
        print(currentLabel)
        if currentLabel not in labelCounts.keys():
            labelCounts[currentLabel] = 0   # 创建键
        labelCounts[currentLabel] += 1
    print(labelCounts)  # {'yes': 2, 'no': 3}
    shannonEnt = 0.0
    for key in labelCounts:
        prob = float(labelCounts[key])/numEntries   # 0.4 和 0.6 每个类别占总类别的比例
        shannonEnt -= prob*log(prob, 2)     # 每个类别香农熵减等,公式带减号
    return shannonEnt


# 测试数据集
def createDataset():
    # 3列对应3个特征:
    # 不浮出水面能否生存
    # 是否有脚蹼
    # 属于鱼类
    dataSet = [
        [1, 1, 'yes'],
        [1, 1, 'yes'],
        [1, 0, 'no'],
        [0, 1, 'no'],
        [0, 1, 'no']
    ]
    labels = ['no surfacing', 'flippers']
    return dataSet, labels


# 测试
myDat, labels = createDataset()
myEnt = calcShannonEnt(myDat)
print(myEnt)

得到熵之后,我们就可以按照获取最大信息增益的方法划分数据集,那么如何划分数据集以及如何度量信息增益呢?

插入python一个知识点:数组切片

test = [1, 2, 'yes']
# 取数组前n个
# test[:n]
# 如:test[:1]
# 结果:[1]

# 不要前n个
# test[n:]
# 如:test[2:]
# 结果:['yes']

另一个知识点:extend()与append()

>>> a = [1, 2, 3]
>>> b = [4, 5, 6]
>>> a.append(b)
>>> [1, 2, 3, [4, 5, 6]]
>>> a.extend(b)
>>> [1, 2, 3, 4, 5, 6]
# 按照给定特征划分数据集
# dataSet:待划分的数据集
# axis:数据集的第axis+1个特征,数组下标从0开始
# value:限定的特征值
# 如:axis=0,那就是第1个特征是否
# 等于你设定的要分类的value值
def splitDataset(dataSet, axis, value):
    retDataSet = []     # 重新分类好的数据集
    for featVec in dataSet:
        print(featVec[axis])
        if featVec[axis] == value:  # 过滤
            reducedFeatVec = featVec[:axis]     # 取前0个元素,这里是[]
            print(reducedFeatVec)
            reducedFeatVec.extend(featVec[axis+1:])		# 去掉前1个元素,这里剩后面两个
            print(featVec[axis+1:])
            retDataSet.append(reducedFeatVec)
    return retDataSet
# 结果
>>> retDataSet: [[1, 'yes'], [1, 'yes'], [0, 'no']]
>>> dataSet: [[1, 1, 'yes'], [1, 1, 'yes'], [1, 0, 'no'], [0, 1, 'no'], [0, 1, 'no']]
# 可以看出第一列特征为1的数据都被挑出

选择最好的数据集划分方式:

# 选择最好的数据集划分方式
# 选择信息增益大的特征来分类
def chooseBestFeatureToSplit(dataSet):
    numFeatures = len(dataSet[0]) - 1       # 去掉类别那一列,剩下2列
    baseEntropy = calcShannonEnt(dataSet)       # 香农熵
    bestInfoGain = 0.0      # 信息增益初始值
    bestFeature = -1
    for i in range(numFeatures):
        featList = [example[i] for example in dataSet]      # dataSet中每条数据的第i列数据取出来并拼成数组,如:i=0,得[1, 1, 1, 0, 0]
        # print(featList)
        uniqueVals = set(featList)      # 去重{0, 1}
        newEntropy = 0.0
        # print(uniqueVals)
        for value in uniqueVals:
            subDataSet = splitDataSet(dataSet, i, value)
            # print(subDataSet)
            prob = len(subDataSet) / float(len(dataSet))
            # print(newEntropy)
            newEntropy += prob * calcShannonEnt(subDataSet)     # 累加的过程
            # print(newEntropy)
        infoGain = baseEntropy - newEntropy  # calculate the info gain; ie reduction in entropy
        # print('infoGain:')
        # print(infoGain)
        if (infoGain > bestInfoGain):  # compare this to the best gain so far
            bestInfoGain = infoGain  # if better than current best, set to best
            bestFeature = i
            # print(bestFeature)
    return bestFeature  # returns an integer

总结一下上面代码的思想:
1. 首先,计算原始数据集的香农熵(baseEntropy)
2. 按特征划分数据集(如:按第1列的特征划分为两组数据集)并计算各自香农熵,最后累加求得该列特征的香农熵
3. 计算信息增益(baseEntropy减去该列特征的香农熵)
4. 与之前计算的信息增益比较下,谁大谁活下来,并记录大的那个的特征是哪一列,从0起算
5. 每列特征如此迭代

接下来正式创建树啦~

# 返回频数最多的类别
# 该方法在为创建树方法服务啦~
def majorityCnt(classList):
    classCount={}
    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)
    return sortedClassCount[0][0]

真正创建树的方法是这个(递归):

# 递归创建决策树
def createTree(dataSet,labels):
    print(dataSet)
    print(labels)
    classList = [example[-1] for example in dataSet]
    print(classList)
    if classList.count(classList[0]) == len(classList):
        print("类别相同呀~")
        return classList[0]     # 类别相同则停止继续划分
    if len(dataSet[0]) == 1:    # 如果每列只有一个元素,如:['no', 'yes'],那就取频数大的
        print("如果每列只有一个元素,那就取频数大的")
        return majorityCnt(classList)
    bestFeat = chooseBestFeatureToSplit(dataSet)
    print('bestFeat:')
    print(bestFeat)
    bestFeatLabel = labels[bestFeat]
    myTree = {bestFeatLabel: {}}
    del(labels[bestFeat])   # 删除labes数组某元素,如del(labels[1]),即删除该数组下标1的数
    featValues = [example[bestFeat] for example in dataSet]     # 取某列并拼接数组
    uniqueVals = set(featValues)    # 去重
    for value in uniqueVals:
        subLabels = labels[:]
        # print('a')
        # print(subLabels)
        myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value), subLabels)
    return myTree

递归的话,可能需要真正跑一跑程序才能理解深刻,这里列一下整体思路:

1. 首先,明确一下两个特殊情况:
  • 一是,类别都相同,如:[[‘yes’], [‘yes’]],那直接返回
  • 另一是,每列只有一个元素,那就取频数大的
2. 如果不符合1的条件,那就开始分,那怎么分?chooseBestFeatureToSplit这个方法告诉我们选哪个特征来分
3. splitDataSet按2步骤得出的特征,划分数据,如此递归,直至结束

好啦,简单的决策树就是这样啦~基本原理大家应该都清楚了吧哈、、ヾ(◍°∇°◍)ノ゙
这兄弟也很全哟~
https://blog.csdn.net/jiaoyangwm/article/details/79525237#311__83

补充:使用决策树执行分类
这才是我们的最终目的呀~
但是创建决策树过程还是有点慢的,假如你数据量大的话
那么,就得保存创建好的树咯~
来,上代码:

# 使用python模块pickle序列化对象
# 可在磁盘上保存对象,并在需要的时候读取出来
# 任何对象都可以执行序列化操作,字典对象也不例外
def storeTree(inputTree, filename):
    import pickle
    fw = open(filename, 'w')
    pickle.dump(inputTree, fw)
    fw.close()


def grabTree(filename):
    import pickle
    fr = open(filename)
    return pickle.load(fr)

测试分类:

def classify(inputTree, featLabels, testVec):
    firstStr = list(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

myTree = retrieveTree(0)
myDat, labels = createDataset()
class0 = classify(myTree, labels, [1, 0])
print(class0)
>>>no

你可能感兴趣的:(机器学习)