1.4.代碼
1.4.1.實現代碼
machine learning in actions有詳細解釋。
from math import log
import operator
import matplotlib.pyplot as plt
from matplotlib.font_manager import FontProperties
def createDataSet_loan():
# 數據集
dataSet = [[0, 0, 0, 0, 'no'],
[0, 0, 0, 1, 'no'],
[0, 1, 0, 1, 'yes'],
[0, 1, 1, 0, 'yes'],
[0, 0, 0, 0, 'no'],
[1, 0, 0, 0, 'no'],
[1, 0, 0, 1, 'no'],
[1, 1, 1, 1, 'yes'],
[1, 0, 1, 2, 'yes'],
[1, 0, 1, 2, 'yes'],
[2, 0, 1, 2, 'yes'],
[2, 0, 1, 1, 'yes'],
[2, 1, 0, 1, 'yes'],
[2, 1, 0, 2, 'yes'],
[2, 0, 0, 0, 'no']]
# 分類屬性
labels = ['年齡', '有工作', '有自己的房子', '信貸情況']
# 返回數據集和分類屬性
return dataSet, labels
def createDataSet_fish():
"""
特徵-- 不浮出水面是否可以生存,以及是否有腳蹼,
類別--魚類和非魚類
:return:數據集(特徵+類別),特徵的意義
"""
dataSet = [[1, 1, 'yes'],
[1, 0, 'no'],
[0, 1, 'no'],
[0,0,'no']]
labels = ['no surfacing','flippers']
# change to discrete values
return dataSet, labels
def calcShannonEnt(dataSet):
"""
計算此時數據集的香農熵
:param dataSet:數據集,(fea1,fea2,...,label)
:return:int,香農熵
"""
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.0
for key in labelCounts:
prob = float(labelCounts[key])/numEntries # 計算該類別的信息
shannonEnt -= prob * log(prob,2) # log base 2,把每個類別的信息的期望相加
return shannonEnt
def splitDataSet(dataSet, axis, value):
"""
去掉下標爲axis的特徵,一整列
:param dataSet:數據集,(fea,fea,...,label)
:param axis: 某個特徵的下標
:param value: 該特徵(下標爲axis)的某個取值
:return: 特徵值=value的數據集,並且去除該特徵
"""
retDataSet = []
for featVec in dataSet:
if featVec[axis] == value: # 對特徵值=value的特徵進行刪除
reducedFeatVec = featVec[:axis] #
reducedFeatVec.extend(featVec[axis+1:])
retDataSet.append(reducedFeatVec)
return retDataSet
def chooseBestFeatureToSplit(dataSet):
"""
根據信息增益獲得信息增益最大的特徵的索引
:param dataSet:(fea,fea,...,label)
:return: 信息增益最大的特徵的索引值
"""
numFeatures = len(dataSet[0]) - 1 # 特徵數量
baseEntropy = calcShannonEnt(dataSet) # 計算數據集的香農熵
bestInfoGain = 0.0 # 信息增益
bestFeature = -1 # 最優特徵的索引值
for i in range(numFeatures): # 遍歷所有特徵
featList = [example[i] for example in dataSet] # 獲取dataSet的第i個特徵
uniqueVals = set(featList) # 去除重複特徵
newEntropy = 0.0
for value in uniqueVals: # 遍歷一個特徵的所有取值
# 特徵值爲value的子數據集,並且除去該特徵
subDataSet = splitDataSet(dataSet, i, value) # 數據集,第幾個特徵,該特徵的一個取值
prob = len(subDataSet)/float(len(dataSet))
newEntropy += prob * calcShannonEnt(subDataSet)
infoGain = baseEntropy - newEntropy # 信息增益
print("第%d個特徵的增益爲%.3f" % (i, infoGain))
if (infoGain > bestInfoGain):
bestInfoGain = infoGain # 更新信息增益,找到最大的信息增益
bestFeature = i # 記錄信息增益最大的特徵的索引值
return bestFeature #
def majorityCnt(classList):
"""
統計classList中出現次數最多的元素(類標籤)
服務於遞歸第兩個終止條件
:param classList: 類標籤列表
:return: 出現次數最多的元素(類標籤)
"""
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, featLabels):
"""
創建決策樹(ID3算法)
遞歸有兩個終止條件:
1、所有的類標籤完全相同,直接返回類標籤
2、用完所有標籤但是得不到唯一類別的分組,即特徵不夠用,挑選出現數量最多的類別作爲返回
:param dataSet:
:param labels: 特徵的意義
:param featLabels: 存儲選擇的最優特徵標籤
:return:
"""
classList = [example[-1] for example in dataSet] # 類別標籤,是否是魚類
if classList.count(classList[0]) == len(classList):
return classList[0] # 如果類別完全相同則停止繼續劃分
if len(dataSet[0]) == 1: # 如果數據集沒有別的特徵,停止劃分
return majorityCnt(classList) # 遍歷完所有特徵時返回出現次數最多的類標籤
bestFeat = chooseBestFeatureToSplit(dataSet) # 選擇最優特徵
bestFeatLabel = labels[bestFeat] # 最優特徵的名稱,意義
featLabels.append(bestFeatLabel)
myTree = {bestFeatLabel:{}} # 根據最優特徵的名稱生成樹
del(labels[bestFeat]) # 刪除已經使用的特徵標籤
featValues = [example[bestFeat] for example in dataSet]
uniqueVals = set(featValues)
# 遞歸建立決策樹
for value in uniqueVals: # 特徵值爲value的子數據集,並且除去該特徵
myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value),
labels, featLabels)
return myTree
def classify(inputTree,featLabels,testVec):
# 獲取決策樹結點
firstStr = next(iter(inputTree))
# 下一個字典
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
def storeTree(inputTree,filename):
import pickle
fw = open(filename,'wb')
pickle.dump(inputTree,fw)
fw.close()
def grabTree(filename):
import pickle
fr = open(filename, 'rb')
return pickle.load(fr)
def getNumLeafs(myTree):
"""
獲取決策樹葉子結點的數目
:param myTree: 決策樹
:return: None
"""
# 初始化葉子
numLeafs = 0
# python3中myTree.keys()返回的是dict_keys,不是list,所以不能用
# myTree.keys()[0]的方法獲取結點屬性,可以使用list(myTree.keys())[0]
# next() 返回迭代器的下一個項目 next(iterator[, default])
firstStr = next(iter(myTree))
# 獲取下一組字典
secondDict = myTree[firstStr]
for key in secondDict.keys():
# 測試該結點是否爲字典,如果不是字典,代表此節點爲葉子結點
if type(secondDict[key]).__name__ == 'dict':
numLeafs += getNumLeafs(secondDict[key])
else:
numLeafs += 1
return numLeafs
def getTreeDepth(myTree):
"""
獲取決策樹的層數
:param myTree: 決策樹
:return: 決策樹的層數
"""
# 初始化決策樹深度
maxDepth = 0
# python3中myTree.keys()返回的是dict_keys,不是list,所以不能用
# myTree.keys()[0]的方法獲取結點屬性,可以使用list(myTree.keys())[0]
# next() 返回迭代器的下一個項目 next(iterator[, default])
firstStr = next(iter(myTree))
# 獲取下一個字典
secondDict = myTree[firstStr]
for key in secondDict.keys():
# 測試該結點是否爲字典,如果不是字典,代表此節點爲葉子結點
if type(secondDict[key]).__name__ == 'dict':
thisDepth = 1 + getTreeDepth(secondDict[key])
else:
thisDepth = 1
# 更新最深層數
if thisDepth > maxDepth:
maxDepth = thisDepth
# 返回決策樹的層數
return maxDepth
def plotMidText(cntrPt, parentPt, txtString):
"""
標註有向邊屬性值
:param cntrPt: 用於計算標註位置
:param parentPt: 用於計算標註位置
:param txtString: 標註內容
:return: None
"""
# 計算標註位置(箭頭起始位置的中點處)
xMid = (parentPt[0]-cntrPt[0])/2.0 + cntrPt[0]
yMid = (parentPt[1]-cntrPt[1])/2.0 + cntrPt[1]
createPlot.ax1.text(xMid, yMid, txtString, va="center", ha="center", rotation=30)
def plotNode(nodeTxt, centerPt, parentPt, nodeType):
"""
繪製結點
:param nodeTxt: 結點名
:param centerPt: 文本位置
:param parentPt: 標註的箭頭位置
:param nodeType: 結點格式
:return: None
"""
# 定義箭頭格式
arrow_args = dict(arrowstyle="<-")
# 設置中文字體
font = FontProperties(fname=r"C:\Windows\Fonts\simsun.ttc", size=14)
# 繪製結點createPlot.ax1創建繪圖區
# annotate是關於一個數據點的文本
# nodeTxt爲要顯示的文本,centerPt爲文本的中心點,箭頭所在的點,parentPt爲指向文本的點
createPlot.ax1.annotate(nodeTxt, xy=parentPt, xycoords='axes fraction',
xytext=centerPt, textcoords='axes fraction',
va='center', ha='center', bbox=nodeType,
arrowprops=arrow_args, FontProperties=font)
def plotTree(myTree, parentPt, nodeTxt):
"""
繪製決策樹
:param myTree: 決策樹(字典)
:param parentPt: 標註的內容
:param nodeTxt: 結點名
:return: None
"""
# 設置結點格式boxstyle爲文本框的類型,sawtooth是鋸齒形,fc是邊框線粗細
decisionNode = dict(boxstyle="sawtooth", fc="0.8")
# 設置葉結點格式
leafNode = dict(boxstyle="round4", fc="0.8")
# 獲取決策樹葉結點數目,決定了樹的寬度
numLeafs = getNumLeafs(myTree)
# 獲取決策樹層數
depth = getTreeDepth(myTree)
# 下個字典
firstStr = next(iter(myTree))
# 中心位置
cntrPt = (plotTree.xoff + (1.0 + float(numLeafs)) / 2.0 / plotTree.totalW, plotTree.yoff)
# 標註有向邊屬性值
plotMidText(cntrPt, parentPt, nodeTxt)
# 繪製結點
plotNode(firstStr, cntrPt, parentPt, decisionNode)
# 下一個字典,也就是繼續繪製結點
secondDict = myTree[firstStr]
# y偏移
plotTree.yoff = plotTree.yoff - 1.0 / plotTree.totalD
for key in secondDict.keys():
# 測試該結點是否爲字典,如果不是字典,代表此結點爲葉子結點
if type(secondDict[key]).__name__ == 'dict':
# 不是葉結點,遞歸調用繼續繪製
plotTree(secondDict[key], cntrPt, str(key))
# 如果是葉結點,繪製葉結點,並標註有向邊屬性值
else:
plotTree.xoff = plotTree.xoff + 1.0 / plotTree.totalW
plotNode(secondDict[key], (plotTree.xoff, plotTree.yoff), cntrPt, leafNode)
plotMidText((plotTree.xoff, plotTree.yoff), cntrPt, str(key))
plotTree.yoff = plotTree.yoff + 1.0 / plotTree.totalD
def createPlot(inTree):
"""
創建繪圖面板
:param inTree: 決策樹(字典)
:return: None
"""
# 創建fig
fig = plt.figure(1, facecolor="white")
# 清空fig
fig.clf()
axprops = dict(xticks=[], yticks=[])
# 去掉x、y軸
createPlot.ax1 = plt.subplot(111, frameon=False, **axprops)
# 獲取決策樹葉結點數目
plotTree.totalW = float(getNumLeafs(inTree))
# 獲取決策樹層數
plotTree.totalD = float(getTreeDepth(inTree))
# x偏移
plotTree.xoff = -0.5 / plotTree.totalW
plotTree.yoff = 1.0
# 繪製決策樹
plotTree(inTree, (0.5, 1.0), '')
# 顯示繪製結果
plt.show()
def main():
dataSet, features = createDataSet_loan()
featLabels = []
myTree = createTree(dataSet, features, featLabels)
# 保存樹
storeTree(myTree, 'classifierStorage.txt')
# 加載樹
myTree = grabTree('classifierStorage.txt')
print(myTree)
# 測試數據
# testVec = [1, 1] # fish
testVec = [1, 1, 0, 0] # loan
result = classify(myTree, featLabels, testVec)
if result == 'yes':
print('是魚類')
if result == 'no':
print('不是魚類')
# print(myTree)
createPlot(myTree)
# print(dataSet)
# print(calcShannonEnt(dataSet))
# print("最優特徵索引值:" + str(chooseBestFeatureToSplit(dataSet)))
if __name__ == '__main__':
main()
1.4.2.sklearn代碼
class sklearn.tree.DecisionTreeClassifier(criterion=’gini’,
splitter=’best’,
max_depth=None,
min_samples_split=2,
min_samples_leaf=1,
min_weight_fraction_leaf=0.0,
max_features=None,
random_state=None,
max_leaf_nodes=None,
min_impurity_decrease=0.0,
min_impurity_split=None,
class_weight=None, presort=False)
參數:
(1)criterion:字符型,可選,規定了該決策樹所採用的的最佳分割屬性的判決方法,有兩種:“gini”,“entropy”,默認爲’gini’,
(2)splitter: 字符型,可選(默認=“best”)用於在每個節點選擇分割的策略,可填‘best’或’random’,前者在特徵的所有劃分點中找出最優的劃分點。後者是隨機的在部分劃分點中找局部最優的劃分點。默認的"best"適合樣本量不大的時候,而如果樣本數據量非常大,此時決策樹構建推薦"random"
(3)max_depth: int型或None,可選(默認=None)樹的最大深度,對於防止過擬合非常有用。如果不輸入的話,決策樹在建立子樹的時候不會限制子樹的深度。一般來說,數據少或者特徵少的時候可以不管這個值。如果模型樣本量多,特徵也多的情況下,推薦限制這個最大深度,具體的取值取決於數據的分佈。常用的可以取值10-100之間。
(4)min_samples_split: int型float型,可選(默認值=2)分割內部節點所需的最小樣本數量;如果是int型,則min_samples_split爲最小樣本數量。如果是float型,則min_samples_split是分數,ceil(min_samples_split * n_samples)是每個分割的最小樣本數。
(5)min_samples_leaf: int型float型,可選(默認值=1)葉節點上所需的最小樣本數。如果是int類型,則將min_samples_leaf作爲最小值。若是float,那麼min_samples_leaf是分數,ceil(min_samples_leaf * n_samples)是每個節點的最小樣本數。
(6)min_weight_fraction_leaf:浮點型,可選(默認=0)。葉節點上(所有輸入樣本的)權值之和的最小加權分數。當沒有提供 sample_weight時,樣品的重量相等。
(7)max_features: int, float, string or None, 可選(默認=None),在尋找最佳分割時需要考慮的特性數量;如果是int類型,則考慮每個分割處的max_features特性。如果是float,那麼max_features是一個分數,並且在每個分割中都考慮int(max_features * n_features)特性。如果“auto”,則max_features=sqrt(n_features)。如果“sqrt”,則max_features=sqrt(n_features)。如果“log2”,則max_features=log2(n_features)。如果沒有,則max_features=n_features。
(8)class_weight: 指定樣本各類別的的權重,主要是爲了防止訓練集某些類別的樣本過多,導致訓練的決策樹過於偏向這些類別。這裏可以自己指定各個樣本的權重,或者用“balanced”,如果使用“balanced”,則算法會自己計算權重,樣本量少的類別所對應的樣本權重會高。當然,如果你的樣本類別分佈沒有明顯的偏倚,則可以不管這個參數,選擇默認的"None"
(9)random_state: int型RandomState實例或None,可選(默認=None)如果是int, random_state是隨機數生成器使用的種子;如果是RandomState實例,random_state是隨機數生成器;如果沒有,隨機數生成器是np.random使用的RandomState實例。
方法:
apply(X[, check_input])
decision_path(X[, check_input])
fit(X, y[, sample_weight, check_input, …])
get_params([deep])
predict(X[, check_input])
predict_log_proba(X)
predict_proba(X[, check_input])
score(X, y[, sample_weight])
set_params(**params)
# coding: utf-8
# Author: shelley
# 2020/5/1210:34
# 1)sklearn實現二分類
from sklearn import tree
X = [[0, 0], [1, 1]]
Y = [0, 1]
clf = tree.DecisionTreeClassifier()
clf = clf.fit(X, Y)
print(clf.predict([[2, 2]])) # 預測屬於哪個類
print(clf.predict_proba([[2, 2]])) # 預測屬於每個類的概率
# 2)sklearn實現多分類
from sklearn.datasets import load_iris
from sklearn import tree
iris = load_iris()
clf = tree.DecisionTreeClassifier()
clf = clf.fit(iris.data, iris.target)
# 3)sklearn實現樹迴歸
# 注意:迴歸的時候y的值應該是浮點數,而不是整數值
from sklearn import tree
X = [[0, 0], [2, 2]]
y = [0.5, 0.5]
clf = tree.DecisionTreeRegressor()
clf = clf.fit(X, y)
print(clf.predict([[1, 1]]))
2.代碼和數據
鏈接:https://pan.baidu.com/s/1QbeG8g_9JhVwJRhERViTMg
提取碼:80su
github:
https://github.com/shelleyHLX/machine-learning
3.參考
機器學習實戰
一些博客,如有侵權,請聯繫。