优点: 计算复杂度不高,输出结果易于理解,对中间值的缺失不敏感,可处理不相关特征数据。
缺点: 易过拟合
ID3:
特征选择准则:信息增益
缺失值处理:没有考虑
优缺点:
C4.5:
特征选择准则:信息增益率
缺失值处理:可处理
优缺点:
CART:
特征选择准则:基尼系数
以下示例来自于《机器学习实战》,使用 ID3 算法:
ID3 通过计算信息增益来得出哪个特征值用于划分数据集(信息增益最高的特征值就是最好的选择)
信息增益与信息熵:
from math import log
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
def calcShannonEnt(dataSet):
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 ## 记录每个类别的出现次数
##print(labelCounts) ## 加入一行查看labelCounts字典
shannonEnt = 0.0
for key in labelCounts:
prob = float(labelCounts[key])/numEntries
shannonEnt -= prob * log(prob,2) #log base 2
return shannonEnt
Mydata, labels = createDataSet() ## 调用
calcShannonEnt(Mydata)
输出:
Out[18]: 0.9709505944546686
查看labelCounts字典
{'yes': 2, 'no': 3}
熵越高,数据越乱
我们可以在数据集中添加更多分类观察熵的变化:
Mydata[0][-1] = 'maybe'
Mydata
查看修改后Mydata
[[1, 1, 'maybe'], [1, 1, 'yes'], [1, 0, 'no'], [0, 1, 'no'], [0, 1, 'no']]
重新计算
calcShannonEnt(Mydata)
分类由两个变为三个,熵也随之变大
1.3709505944546687
得到熵后,我们就可以按照获取最大信息增益的方法划分数据集。
对每个特征划分数据集的结果计算一次信息熵,然后判断按照哪个特征划分数据集是最好的划分方式。
def splitDataSet(dataSet, axis, value):
retDataSet = [] ## 新建列表(Python在函数传递中是列表引用,在函数中的操作会直接影响原列表)
for featVec in dataSet:
if featVec[axis] == value:
reducedFeatVec = featVec[:axis] #chop out axis used for splitting(切掉用于分割的轴)(python包头不包尾)
reducedFeatVec.extend(featVec[axis+1:])
retDataSet.append(reducedFeatVec) ## 用append添加列表形成没有使用过的特征的新的数据集
return retDataSet
调用splitDataSet()函数:
splitDataSet(Mydata, 0, 1)
输出:
[[1, 'yes'], [1, 'yes'], [0, 'no']]
来看一下当前Mydata:
splitDataSet(dataSet, axis, value)
splitDataSet(Mydata, 0, 1)返回的是第0个位置的特征值属性为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(建个列表把第i个特征值和所有可能值存进去)(相当于按特征值的一列存)
# print("featList = {}".format(featList))
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(返回整数)(返回最好特征划分的索引值)
chooseBestFeatureToSplit(Mydata)
Out: 0
思路大概是:先算出当前结点的香农熵,然后遍历所有特征值,把遍历当前的特征值 i 的所有值存到新的List(特征值所对应的那一列的所有数值),并提取出唯一值(比如11122,取1,2),嵌套遍历每一个唯一值并用唯一值划分(例如:就是找到该特征值全是1的个数,然后去算熵),累加每个唯一值的信息熵,跳出嵌套循环,求得当前特征值 i 的信息增益,并于当前最大信息增益做判断,保存最大值,并保存当前特征值的序号,最后跳出所有特征值的遍历,返回最大信息增益的特征值位置。
## 创建树
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) ## 若使用完了所有的特征但仍没有划分完成,调用majorityCnt()函数,返回出现次数最多的类别
## 开始创建树
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
调用
mytree = createTree(Mydata, labels)
mytree
输出:
{'no surfacing': {0: 'no', 1: {'flippers': {0: 'no', 1: 'yes'}}}}