背景
cart樹作爲決策樹的一種,在非常多的地方被使用。既可以用於分類問題,也可以用於迴歸問題。分類問題則非常容易理解,利用gini係數較大的特徵進行樣本分裂,從而構建一顆分類樹。 今天我們要探討的是迴歸樹。
迴歸樹cart簡介
迴歸樹,則目標函數是平方差,也就是說,分完之後形成left和right子樹,
每個子樹對label,也就是y,進行平方差的計算。最後左右子樹的平方差之和則是評估標準。 我們的目標則是選擇平方差之和比較小的特徵來進行劃分。 停止條件則是,沒有可劃分的,或者誤差之和非常小。
練手實現
如果明白了上面的定義,其實就兩個點:選特徵,目標平方差最小;
分裂; 然後繼續直到結束。
def loadDataSet():
dataSet = []
f = open('regData.txt')
fr = f.readlines()
for line in fr:
line = line.strip().split('\t')
linef = [float(li) for li in line]
dataSet.append(linef)
dataSetMat = mat(dataSet)
return dataSetMat
def calcErr(dataSetMat):
'''
dataSetMat[line,col]
:param dataSetMat:
:return:
'''
label = dataSetMat[:,-1]
#print(type(label),len(label))
sqrtvar = var(label)* len(label)
#print(sqrtvar)
return sqrtvar
def chooseBestFeatVal2Split(dataSetMat):
t = dataSetMat[:, -1].T.tolist()[0]
if(len(set(t))==1):
return None,None
# print(t)
# print(type(t))
bestFeature = 0
bestValue = 0
lowestErr = 100000
totalErr = calcErr(dataSetMat)
for feature in range(shape(dataSetMat)[1] - 1): #2個屬性
allValues = [d[feature] for d in dataSetMat.tolist()] #每個屬性下面的所有值
values = set(allValues) #理論上排個序比較好
for value in values:
leftChild, rightChild = splitByFeatVal(feature, value, dataSetMat)
if (shape(leftChild)[0] == 0 or shape(rightChild)[0] == 0): continue
curErr = calcErr(leftChild) + calcErr(rightChild)
if (curErr < lowestErr):
bestFeature = feature
bestValue = value
lowestErr = curErr
# 如果誤差減少很小,停止
if (totalErr - lowestErr < 1): return None, None
return bestFeature, bestValue
def splitByFeatVal(feature, value, dataSetMat):
#左子樹的值大於根節點的值
rg = dataSetMat[:, feature] > value
lineIndex = nonzero(rg)[0] #獲取滿足條件的index
leftChild = dataSetMat[lineIndex, :]
rg = dataSetMat[:, feature] <= value
lineIndex = nonzero(rg)[0] # 獲取滿足條件的index
#右子樹的值小於等於根節點的值
rightChild = dataSetMat[lineIndex, :]
return leftChild, rightChild
def createRegTree(dataSetMat):
feature, value = chooseBestFeatVal2Split(dataSetMat)
print("best feature:",feature,"loss:",value)
# 如果無法分割,那麼返回葉節點的值,即所有dataSetMat的均值
if feature == None: return mean(dataSetMat[:, -1])
# 如果可以繼續分割,那麼繼續創建新的子樹
regTree = {}
regTree['featIndex'] = feature
regTree['value'] = value
leftChild, rightChild = splitByFeatVal(feature, value, dataSetMat)
regTree['leftChild'] = createRegTree(leftChild)
regTree['rightChild'] = createRegTree(rightChild)
return regTree
會得到一棵樹:
{'featIndex': 1, 'value': 10.0, 'leftChild': {'featIndex': 1, 'value': 20.0, 'leftChild': , 'rightChild': {'featIndex': 1, 'value': 15.0, 'leftChild': 15.266, 'rightChild': 10.27}}, 'rightChild': {'featIndex': 1, 'value': 5.0, 'leftChild': 5.07, 'rightChild': 0.078}}
sklearn實踐
def sklearn_regressiontree(dataSetMat):
X = dataSetMat[:,0:2]
y = dataSetMat[:,-1]
clf = DecisionTreeRegressor()
clf = clf.fit(X,y)
dot_data = export_graphviz(clf, out_file=None)
graph = graphviz.Source(dot_data)
print(clf)
graph.render("regressiontree")
得到的結果如下:
數據regData.txt
5.23 1 0.1
5.23 2 0.12
5.23 3 0.02
5.23 4 0.03
5.23 5 0.12
5.23 6 5.0
5.23 7 5.2
5.23 8 5.1
5.23 9 5.02
5.23 10 5.03
5.23 11 10.8
5.23 12 10.06
5.23 13 10.03
5.23 14 10.02
5.23 15 10.44
5.23 16 15.88
5.23 17 15.06
5.23 18 15.04
5.23 19 15.30
5.23 20 15.05
5.23 21 20.8
5.23 22 20.16
5.23 23 20.24
5.23 24 20.05
5.23 25 20.09
總結
- cart的核心在於選擇特徵
- 評估的指標是每個分支的y的平方差,也就是var*n
- 自己操作一下可以對特徵理解深化
- sklearn裏就是decisiontreeregressionor.fit(X,y)
- 可以利用graphviz可以轉成dot並render存儲。
- 注意conda安裝是conda install python-graphviz
參考文獻
https://scikit-learn.org/stable/modules/tree.html#regression