決策樹
決策樹又稱為判定樹,是運用於分類的一種樹結構,其中的每個內部節點代表對某一屬性的一次測試,每條邊代表一個測試結果,葉節點代表某個類或類的分布。
決策樹的決策過程需要從決策樹的根節點開始,待測數據與決策樹中的特征節點進行比較,並按照比較結果選擇選擇下一比較分支,直到葉子節點作為最終的決策結果。
決策樹的學習過程
- 特征選擇:從訓練數據的特征中選擇一個特征作為當前節點的分裂標准(特征選擇的標准不同產生了不同的特征決策樹算法)。
- 決策樹生成:根據所選特征評估標准,從上至下遞歸地生成子節點,直到數據集不可分則停止決策樹停止聲場。
- 剪枝:決策樹容易過擬合,需要剪枝來縮小樹的結構和規模(包括預剪枝和后剪枝)。
實現決策樹的算法包括ID3、C4.5算法等。
ID3算法
ID3算法是由Ross Quinlan提出的決策樹的一種算法實現,以信息論為基礎,以信息熵和信息增益為衡量標准,從而實現對數據的歸納分類。
ID3算法是建立在奧卡姆剃刀的基礎上:越是小型的決策樹越優於大的決策樹(be simple簡單理論)。
奧卡姆剃刀(Occam's Razor, Ockham's Razor),又稱“奧坎的剃刀”,是由14世紀邏輯學家、聖方濟各會修士奧卡姆的威廉(William of Occam,約1285年至1349年)提出,他在《箴言書注》2卷15題說“切勿浪費較多東西,去做‘用較少的東西,同樣可以做好的事情’。簡單點說,便是:be simple。
算法缺陷
ID3算法可用於划分標准稱型數據,但存在一些問題:
- 沒有剪枝過程,為了去除過渡數據匹配的問題,可通過裁剪合並相鄰的無法產生大量信息增益的葉子節點;
- 信息增益的方法偏向選擇具有大量值的屬性,也就是說某個屬性特征索取的不同值越多,那么越有可能作為分裂屬性,這樣是不合理的;
- 只可以處理離散分布的數據特征
屬性選擇
ID3算法的核心思想是以信息增益度量屬性選擇,選擇分裂后信息增益最大的屬性進行分裂。
信息熵(entropy)是用來衡量一個隨機變量出現的期望值。如果信息的不確定性越大,熵的值也就越大,出現的各種情況也就越多。
其中,S為所有事件集合,p為發生概率,c為特征總數。注意:熵是以2進制位的個數來度量編碼長度的,因此熵的最大值是log2C。
信息增益(information gain)是指信息划分前后的熵的變化,也就是說由於使用這個屬性分割樣例而導致的期望熵降低。也就是說,信息增益就是原有信息熵與屬性划分后信息熵(需要對划分后的信息熵取期望值)的差值,具體計算法如下:
其中,第二項為屬性A對S划分的期望信息。
基本思想
- 初始化屬性集合和數據集合
- 計算數據集合信息熵S和所有屬性的信息熵,選擇信息增益最大的屬性作為當前決策節點
- 更新數據集合和屬性集合(刪除掉上一步中使用的屬性,並按照屬性值來划分不同分支的數據集合)
- 依次對每種取值情況下的子集重復第二步
- 若子集只包含單一屬性,則為分支為葉子節點,根據其屬性值標記。
- 完成所有屬性集合的划分
注意:該算法使用了貪婪搜索,從不回溯重新考慮之前的選擇情況。
代碼實現
#!/usr/bin/env python
# encoding:utf-8
from math import log
def calEntropy(dataSet):
"""calcuate entropy(s)
@dateSet a training set
"""
size = len(dataSet)
laberCount = {}
for item in dataSet:
laber = item[-1]
if laber not in laberCount.keys():
laberCount[laber] = 0
laberCount[laber] += 1
entropy = 0.0
for laber in laberCount:
prob = float(laberCount[laber])/size
entropy -= prob * log(prob, 2)
return entropy
def splitDataSet(dataSet, i, value):
"""split data set by value with a laber
@dataSet a training sets
@i the test laber axis
@value the test value
"""
retDataSet = []
for item in dataSet:
if item[i] == value:
newData = item[:i]
newData.extend(item[i+1:])
retDataSet.append(newData)
return retDataSet
def chooseBestLaber(dataSet):
"""choose the best laber in labers
@dataSet a traing set
"""
numLaber = len(dataSet[0]) - 1
baseEntropy = calEntropy(dataSet)
maxInfoGain = 0.0
bestLaber = -1
size = len(dataSet)
for i in range(numLaber):
uniqueValues = set([item[i] for item in dataSet])
newEntropy = 0.0
for value in uniqueValues:
subDataSet = splitDataSet(dataSet, i, value)
prob = float(len(subDataSet))/size
newEntropy += prob * calEntropy(subDataSet)
infoGain = baseEntropy - newEntropy
if infoGain > maxInfoGain:
maxInfoGain = infoGain
bestLaber = i
return bestLaber
class Node:
"""the node of tree"""
def __init__(self, laber, val):
self.val = val
self.left = None
self.right = None
self.laber = laber
def setLeft(self, node):
self.left = node
def setRight(self, node):
self.right = node
signalNode = []
def generateNode(lastNode, dataSet, labers):
leftDataSet = filter(lambda x:x[-1]==0, dataSet)
rightDataSet = filter(lambda x:x[-1]==1, dataSet)
print "left:", leftDataSet
print "right:", rightDataSet
print "labers:", labers
if len(leftDataSet) == 0 and len(rightDataSet) == 0:
return
next = 0
print "%s ->generate left"%lastNode.laber
if len(leftDataSet) == 0:
print ">>> pre:%s %d stop no"%(lastNode.laber, 0)
lastNode.setLeft(Node("no", 0))
elif len(leftDataSet) == len(dataSet):
print ">>> pre:%s %d stop yes"%(lastNode.laber, 0)
lastNode.setLeft(Node("yes", 0))
else:
laber = chooseBestLaber(leftDataSet)
if laber == -1:
print ">>> can't find best one"
laber = next
next = (next + 1)%len(labers)
print ">>> ",labers[laber]
leftLabers = labers[:laber] + labers[laber+1:]
leftDataSet = map(lambda x:x[0:laber] + x[laber+1:], leftDataSet)
node = Node(labers[laber], 0)
lastNode.setLeft(node)
generateNode(node, leftDataSet, leftLabers)
print "%s ->generate right"%lastNode.laber
if len(rightDataSet) == 0:
print ">>> pre:%s %d no"%(lastNode.laber, 1)
lastNode.setRight(Node("no", 1))
elif len(rightDataSet) == len(dataSet):
print ">>> pre:%s %d yes"%(lastNode.laber, 1)
lastNode.setRight(Node("yes", 1))
else:
laber = chooseBestLaber(rightDataSet)
if laber == -1:
print ">>> can't find best one"
laber = next
next = (next + 1)%len(labers)
print ">>> ",labers[laber]
rightLabers = labers[:laber] + labers[laber+1:]
rightDataSet = map(lambda x:x[0:laber] + x[laber+1:], rightDataSet)
node = Node(labers[laber], 0)
lastNode.setRight(node)
generateNode(node, rightDataSet, rightLabers)
def generateDecisionTree(dataSet, labers):
"""generate a decision tree
@dataSet a training sets
@labers a list of feature laber
"""
root = None
laber = chooseBestLaber(dataSet)
if laber == -1:
print "can't find a best laber in labers"
return None
print ">>>> ",labers[laber]
root = Node(labers[laber], 1)
labers = labers[:laber] + labers[laber+1:]
dataSet = map(lambda x:x[0:laber] + x[laber+1:], dataSet)
generateNode(root, dataSet, labers)
return root
"""
price size color result
---- ---- ---- ----
cheap big white like
cheap small white like
expensive big white like
expensive small white like
cheap small black don't like
cheap big black don't like
expensive big black don't like
expensive small black don't like
"""
dataSet = [
[0, 1, 1, 1],
[0, 0, 1, 1],
[1, 1, 1, 1],
[1, 0, 1, 1],
[0, 0, 0, 0],
[0, 1, 0, 0],
[1, 1, 0, 0],
[1, 0, 0, 0]]
labers = ["price", "size", "color"]
if __name__ == "__main__":
generateDecisionTree(dataSet, labers)
C4.5算法
C4.5算法是ID3算法的一種改進。
改進
- 用信息增益率來選擇屬性,克服了用信息增益選擇屬性偏向選擇多值屬性的不足
- 在構造樹的過程中進行剪枝
- 對連續屬性進行離散化
- 能夠對不完整的數據進行處理
信息增益率
設樣本集S按離散屬性F的c個不同的取值划分為c個子集,則這c個子集的信息熵為:
信息增益率是信息增益與信息熵的比例,如下:
離散化處理
將連續型的屬性變量進行離散化處理形成決策樹的訓練集:
- 將需要處理的樣本(對應根節點)或樣本子集(對應子樹)按照連續變量的大小從小到大進行排序
- 假設該屬性對應不同的屬性值共N個,那么總共有N-1個可能的候選分割值點,每個候選的分割閾值點的值為上述排序后的屬性值中兩兩前后連續元素的中點
- 用信息增益選擇最佳划分
不完整數據處理
處理缺少屬性值的一種策略是賦給該節點所有對應訓練實例中該屬性最常見的值,另一種復雜的情況是為該節點每個可能出現的值賦予一個概率。
參考:
http://www.cnblogs.com/leoo2sk/archive/2010/09/19/decision-tree.html
http://blog.csdn.net/google19890102/article/details/28611225
http://www.cnblogs.com/biyeymyhjob/archive/2012/07/23/2605208.html