思想:
将原始数据集根据决定性特征划分为几个数据子集,这些数据子集会分布在第一个决策点的所有分支上,如果某个分支下的数据属于同一类型,则表示到达终止模块,可以得到结论,无需进一步对数据集进行分割;如果子集内的数据不属于同一类型,则需重复划分数据子集,直到所有具有相同类型的数据均在一个数据子集内。
但是应该怎样划分数据呢,显然是根据决定性特征,这里引进一个度量标准--信息增益(划分数据集之前之后信息发生的变化),我们可以计算每个特征值划分划分数据集获得的信息增益,获得信息增益最高的特征就是最好的选择,然后过滤掉该特征,去其他特征中找到他们中最优的,以此递归,知道最后一个特征。
数据:
no surfacing(不浮出水面是否可以生存) | flippers(是否有脚蹼) | file(是否为鱼类) |
1 | 1 | yes |
1 | 1 | yes |
1 | 0 | no |
0 | 1 | no |
0 | 1 | no |
说明:如果对于香农熵和信息增益不懂得话:信息熵https://blog.csdn.net/class_brick/article/details/79651173 和信息增益https://blog.csdn.net/olenet/article/details/46433297
,这是一个特定的信息量,要想获取整个信息量:
,为啥要乘以P(x)呢?
维基百科一句话就让我明白了:
-logp 就是一种可能性的信息量,一个事件总的信息量就是每一种可能的情况的信息量乘以它们发生的概率,其实就是信息量的数学期望。
代码
python3版本代码(对于小白,在每次测试时候,可以打断点到测试那里,更容易理解,对于print过多,自行删除):
from math import log
import operator
# 创建数据集,自己可以定义
def creatDataSet():
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 = creatDataSet()
# 测试
print("dataSet:")
print(myDat)
print("labels:")
print(labels)
print("----------------" * 10)
################################################################################################################
# 计算给定数据集的香农熵
"""
1、计算各个分类出现的次数
2、计算各个分类出现的概率
3、根据香农公式:信息熵H(X)=−∑p(xi)*log2(p(xi)),
其中p(xi)为各个分类的期望,计算香农熵,其中信息量h(x)=-log2(p(xi)).
"""
def calcShannonEnt(dataSet):
"""
:param dataSet: 数据集
:return: 香农熵
"""
numEntries = len(dataSet) # 数据集的总数
labelCounts = {} # 字典,{类别:数量}---{'yes':1}
# 1、为所有可能分类创建字典,计算各个分类出现的次数
for featVec in dataSet:
currentLabel = featVec[-1] # 数组下表-1表示该数据的最后一个,也就是'yes'或者'no'
if currentLabel not in labelCounts.keys(): # 如果没有改分类,则创建该字典
labelCounts[currentLabel] = 0
labelCounts[currentLabel] += 1 # 累加分类
# 显示该字典数据
print("此时该字典数据:", labelCounts)
# 2、3、计算概率、计算香农熵,公式:H(X)=−∑p(xi)*log2(p(xi)),其中p(xi)为各个分类的期望
shannonEnt = 0.0 # 初始化香农熵
for key in labelCounts:
prob = float(labelCounts[key]) / numEntries # 计算该分类的期望值,即概率
shannonEnt -= prob * log(prob, 2) # 以2为底求对数
# 显示计算后的香农熵
print("此时香农熵:", shannonEnt)
return shannonEnt
# 测试
calcShannonEnt(myDat)
##################################################################################################################
# 先讲一下extend和append的区别
print("--------------------" * 10)
a = [1, 2, 3]
b = [4, 5, 6]
a.extend(b)
print("extend:先将自己分解成元素,然后加到其他元素后面")
print(a)
A = [1, 2, 3]
B = [4, 5, 6]
A.append(B)
print("append:不分解,直接加到其他元素后面,是什么样还是什么样")
print(A)
print("--------------------" * 10)
###################################################################################################################
# 按照给定特征划分数据集
def splitDataSet(dataSet, axis, value):
"""
:param dataSet: 待划分的数据集
:param axis: 划分数据集的特征列,就是第几列
:param value: 特征列的值,就是第几列的值
:return: 返回的是与特征列值匹配后,并且去除特征列的数组对象
"""
retDataSet = [] # 创建新的list对象,这个list里面各个元素也都是list
for featVec in dataSet:
if featVec[axis] == value: # 如果该特征与值对应
reducedFeatVec = featVec[:axis] # 该特征列前面列
reducedFeatVec.extend(featVec[axis + 1:]) # 该特征列+1后面的列,extend是将自身变为元素再追加到数组元素后面
retDataSet.append(reducedFeatVec) # 直接追加到list对象中
return retDataSet
# 测试
print(myDat)
print(splitDataSet(myDat, 0, 1))
print(splitDataSet(myDat, 0, 0))
print("--------------------" * 10)
###################################################################################################################
# 选择最好的数据集划分方式
"""
1、记录原始熵
2、对每个特征进行循环计算各个特征的熵:
2.1获取每个特征所有值
2.2对每个特征值循环进行划分计算熵
3、计算信息增益=信息熵-条件熵,公式:gain(S ,A) = Entropy(S) - ∑ (|Sv|/|S|*Entropy(Sv)),
其中,S为整个集合,v为特征A的值,A表示特征,Sv为特征为A的值为v的子集,Entropy()为计算熵值函数,
其中信息熵:H(S)=Entropy(S),条件熵H(sv)=∑ (|Sv|/|S|*Entropy(Sv))
4、获取最大信息增益,并获取该特征列
"""
def chooseBestFeature(dataSet):
"""
:param dataSet: 待分类的数据集
:return: 返回最好的特征列
"""
numFeatures = len(dataSet[0]) - 1 # 特征总数,最后那个分类不算
print("原始香农熵:")
# 1、计算原始香农熵
baseEntropy = calcShannonEnt(dataSet)
print("给定特征之后,进入for循环:\n", "-------" * 14)
# 2、对每个特征进行循环计算各个特征的熵
bestInfoGain = 0.0 # 最好的信息增益,初始化
beatFeature = -1 # 最好的特征,初始化
for i in range(numFeatures):
print("第一层for循环:")
# 2.1、获取第i个特征,所有的可能取值
featList = [example[i] for example in dataSet]
print("原始数据集:", dataSet)
print("第%d个特征列的所有值:" % (i), featList)
uniqueVals = set(featList) # set集合,重复的元素会合并,也就是uniqueVals是{0,1}
newEntropy = 0.0 # 新的香农熵
# 2.2、计算每一种划分方式的香农熵
for value in uniqueVals:
print("第二层for循环:", "-------" * 5)
subDataSet = splitDataSet(dataSet, i, value) # 按照给定特征划分数据集,subDataSet是与特征列值匹配后,并且去除特征列的数组对象.即Sv
print("此时给定特征划分后的数据集为:", subDataSet)
prob = len(subDataSet) / float(len(dataSet)) # 子集在原始集的期望:prob=|Sv|/|S|
newEntropy += prob * calcShannonEnt(subDataSet) # 子集的香农熵*期望,其中此处是∑(prob*Entropy(Sv))
# 3、计算最好的信息增益
print("计算最好的信息增益:", "-----" * 8)
infoGain = baseEntropy - newEntropy
print("原始香农熵:", baseEntropy)
print("新分类后的香农熵:", newEntropy)
print("相减之后:", infoGain)
# 4、获取最大的特征信息熵,并记录是哪个特征
if (infoGain > bestInfoGain):
bestInfoGain = infoGain
beatFeature = i
print("-------" * 10)
print("最好的特征列:", beatFeature)
print("-------------------------" * 10)
return beatFeature
# 测试
chooseBestFeature(myDat)
# 下面就是决策树,进入正题,对于前面的要理解清楚,后面的都是调用前面的方法,将前面可以单独运行
##########################################################################################################
# 特征用完仍不能将数据集划分为只包含唯一类别的分组,返回出现次数最多的
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):
classList = [example[-1] for example in dataSet] # 获取数据集的每一个类别,-1表示倒数第一个,即yes,no
print("classList", classList)
if classList.count(classList[0]) == len(classList): # 终止条件之一:所有数据的类别的都相同
return classList[0]
if len(dataSet[0]) == 1: # 终止条件之二:特征用完仍不能将数据集划分为只包含唯一类别的分组,返回出现次数最多的。
return majorityCnt(classList)
bestFeat = chooseBestFeature(dataSet) # 选择最佳划分特征,0或1
bestFeatLabel = labels[bestFeat] # 对应的标签,'no surfacing'或 'flippers'
myTree = {bestFeatLabel: {}} # 用字典来存储树
print("myTree", myTree)
del (labels[bestFeat]) # 删除最好的标签留下其他标签
featValues = [example[bestFeat] for example in dataSet] # 跟上个函数一样,不赘述
print("featValues", featValues)
uniqueVals = set(featValues) # uniqueVals为0,1
for value in uniqueVals:
subLabels = labels[:]
print("subLabels :", subLabels)
# 按照给定特征划分数据集,在数据集上递归调用,splitDataSet()函数是去除现在的特征,在后面的特征中找到下一个最优特征
myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value),subLabels)
return myTree
# 测试
print(createTree(myDat, labels))
结果:{'no surfacing': {0: 'no', 1: {'flippers': {0: 'no', 1: 'yes'}}}}
图片形式: