>>>>>>>>>>>>>>>Sklearn虽然很好用,但是调包侠是没有出路的>>>>>>>>>>>>>>>>>>>>>>
简单的举一个例子根据头发和声音判断学生的性别:
数据如下:
头发 | 声音 | 性别 |
---|---|---|
长 | 粗 | 男 |
短 | 粗 | 男 |
短 | 粗 | 男 |
长 | 细 | 女 |
短 | 细 | 女 |
短 | 粗 | 女 |
长 | 粗 | 女 |
长 | 粗 | 女 |
根据ID3算法的思想,使用信息增益最大的特征对数据进行划分,因为朝着信息增益最大的地方划分,可以使得信息熵减小,信息熵越小,代表样本会越来越纯。
信息熵的公式为:
信息增益公式为:
也称为经验条件熵,事实上做信息增益就是两个信息熵之前做减法。
离散属性a有v个可能值,使用属性a对D划分会产生v个分支结点,第v个分支结点包含了D中所有在属性a上的取值为的样本,记为。
(1)根据数据表格可得,数据集的信息熵为:
8位同学中,男生有3位,女生有8位
(2)根据头发这一特征属性进行划分的信息增益计算如下:
通过头发属性进行划分的结果为:长头发中有1男3女,短头发中有2男2女
因此
(3)同理根据声音这一特征属性进行划分的信息增益计算如下:
根据声音这一属性划分的分类结果为:声音粗的有3男3女,声音细的有0男2女
因此
按照ID3算法的核心思想根据信息增益最大的属性来划分,因此选择使用声音这一特征属性来划分,能够使得决策树区分样本的能力更强,更具有代表性。
接下来使用python代码来实现ID3算法:
from math import log
import operator
def calcShannonEnt(dataSet): # 计算数据的信息熵(entropy)
numEntries = len(dataSet) # 数据条数
labelCounts = {}
for featVec in dataSet:
currentLabel = featVec[-1] # 每行数据的最后一个字(类别)
if currentLabel not in labelCounts.keys():
labelCounts[currentLabel] = 0 # 这一步其实就是在字典里面初始化每个类别的个数
labelCounts[currentLabel] += 1 # 统计有多少个类以及每个类的数量
shannonEnt = 0
for key in labelCounts:
prob=float(labelCounts[key])/numEntries # 计算单个类的熵值
shannonEnt -= prob*log(prob,2) # 累加每个类的熵值
return shannonEnt
def createDataSet1(): # 创造示例数据
dataSet = [['长', '粗', '男'],
['短', '粗', '男'],
['短', '粗', '男'],
['长', '细', '女'],
['短', '细', '女'],
['短', '粗', '女'],
['长', '粗', '女'],
['长', '粗', '女']]
features = ['头发','声音'] #两个特征
return dataSet,features
def splitDataSet(dataSet,axis,value): # 按某个特征分类后的数据
retDataSet = []
for featVec in dataSet:
if featVec[axis] == value:
reducedFeatVec = featVec[:axis]
reducedFeatVec.extend(featVec[axis+1:]) # 这两步的操作是没有包括划分的特征属性 很微妙!
retDataSet.append(reducedFeatVec)
return retDataSet
def chooseBestFeatureToSplit(dataSet): # 选择最优的分类特征
numFeatures = len(dataSet[0]) - 1 # 获得特征的个数 2个
baseEntropy = calcShannonEnt(dataSet) # 原始的信息熵
bestInfoGain = 0
bestFeature = -1
for i in range(numFeatures): # 遍历两个特征
featList = [example[i] for example in dataSet]
uniqueVals = set(featList) # 引入集合
newEntropy = 0
for value in uniqueVals:
subDataSet = splitDataSet(dataSet, i, value) # 根据某个特征分类后的数据集
prob = len(subDataSet) / float(len(dataSet))
newEntropy += prob*calcShannonEnt(subDataSet) # 按特征分类后的条件经验熵
infoGain = baseEntropy - newEntropy # 原始熵与按特征分类后的熵的差值 即按照这个特征划分后的信息增益
if (infoGain > bestInfoGain): # 若按某特征划分后,熵值减少的最大,则次特征为最优分类特征
bestInfoGain = infoGain
bestFeature = i
return bestFeature # 返回的是最优特征的索引
def majorityCnt(classList): # 按分类后类别数量排序,比如:最后分类为2男1女,则判定为男;
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)
#print(sortedClassCount)
return sortedClassCount[0][0]
# 构建决策树(ID3决策树)
def createTree(dataSet, labels):
classList = [example[-1] for example in dataSet] # 类别:男或女
if classList.count(classList[0]) == len(classList): # 最终叶子结点中都是一个类别的话就return那个类别
return classList[0]
if len(dataSet[0]) == 1:
return majorityCnt(classList)
bestFeat = chooseBestFeatureToSplit(dataSet) #选择最优特征的索引
bestFeatLabel = labels[bestFeat]
myTree = {bestFeatLabel:{}} # 分类结果以字典形式保存
del(labels[bestFeat]) # labels中只有头发这个属性了
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
if __name__=='__main__':
dataSet, labels = createDataSet1() # 创造示列数据
print(createTree(dataSet, labels)) # 输出决策树模型结果
最终代码的运行结果为:
{'声音': {'粗': {'头发': {'长': '女', '短': '男'}}, '细': '女'}}
将决策树可视化出来的效果图为:
总结:
(1)ID3算法存在的缺点:
1)ID3算法在选择根节点和内部节点中的分支属性时,采用信息增益作为评价标准。信息增益的缺点是倾向于选择取值较多是属性,在有些情况下这类属性可能不会提供太多有价值的信息。
2)ID3算法只能对描述属性为离散型属性的数据集构造决策树 。
(2)为了改进ID3算法的缺点,提出了ID4.5算法和CART算法
1)ID4.5算法使用信息增益率来划分属性,增益率准则对可取值数目较少的属性有所偏好
2)CART算法使用基尼指数(Gini index)来选择划分属性,CART算法既可以做分类也可以做回归,随机森林算法中的弱分类器就是使用了CART算法。
文章部分参考至:https://blog.csdn.net/csqazwsxedc/article/details/65697652