機器學習——回歸樹


  線性回歸創建模型需要擬合所有的樣本點(局部加權線性回歸除外)。當數據擁有眾多特征並且特征之間關系十分復雜的時候,構建全局模型的想法就顯得太難了,也略顯笨拙。而且,實際生活中很多問題都是非線性的,不可能使用全局限性模型來擬合任何數據。

  一種可行的方法是將數據集切分成很多份易建模的數據,然后再利用線性回歸技術來建模。如果首次切分之后仍然難以擬合線性模型就繼續切分。

  決策樹是一種貪心算法,它要在給定時間內做出最佳選擇,但是並不關心能否達到全局最優

 

CART(classification and regression trees,分類回歸樹)

之前使用過的分類樹構建算法是ID3ID3決策樹學習算法是以信息增益為准則來選擇划分屬性。ID3的做法是每次選取當前最佳的特征來分割數據,並按照該特征的所有可能取值來切分。也就是說,如果一個特征有4種取值,那么數據將被切成4份。一旦按某特征切分后,該特征在之后的算法執行過程中將不會再起作用,所以所以有觀點認為這種切分方式過於迅速。另外一種方法是二元切分法,即每次把數據集切成兩份。如果數據的某特征值等於切分所要求的值,那么這些數據就進入樹的左子樹,反之則進入樹的右子樹。

  ID3算法還存在另一個問題,它不能直接處理連續性數據。只有事先將連續特征轉換成離散型,才能在ID3算法中使用。

  CART算法使用二元切分來處理連續型變量。對CART稍作修改就可以處理回歸問題。CART決策樹使用“基尼指數”來選擇划分屬性,基尼值是用來度量數據集的純度

 

 

from numpy import *

def loadDataSet(fileName):      #general function to parse tab -delimited floats
	dataMat = []                #assume last column is target value
	fr = open(fileName)
	for line in fr.readlines():
		curLine = line.strip().split('\t')
		fltLine = map(float,curLine) #map all elements to float()
		dataMat.append(fltLine)
	return dataMat
	
def plotBestFit(file):				#畫出數據集
	import matplotlib.pyplot as plt
	dataMat=loadDataSet(file)		#數據矩陣和標簽向量
	dataArr = array(dataMat)		#轉換成數組
	n = shape(dataArr)[0] 
	xcord1 = []; ycord1 = []		#聲明兩個不同顏色的點的坐標
	#xcord2 = []; ycord2 = []
	for i in range(n):
		xcord1.append(dataArr[i,0]); ycord1.append(dataArr[i,1])
	fig = plt.figure()
	ax = fig.add_subplot(111)
	ax.scatter(xcord1, ycord1, s=30, c='green', marker='s')
	#ax.scatter(xcord2, ycord2, s=30, c='green')
	plt.xlabel('X1'); plt.ylabel('X2');
	plt.show()

def binSplitDataSet(dataSet, feature, value):	#該函數通過數組過濾方式將數據集合切分得到兩個子集並返回
	mat0 = dataSet[nonzero(dataSet[:,feature] > value)[0],:][0]
	mat1 = dataSet[nonzero(dataSet[:,feature] <= value)[0],:][0]
	return mat0,mat1

def regLeaf(dataSet):			#建立葉節點函數,value為所有y的均值
	return mean(dataSet[:,-1])

def regErr(dataSet):			#平方誤差計算函數
	return var(dataSet[:,-1]) * shape(dataSet)[0]	#y的方差×y的數量=平方誤差

def chooseBestSplit(dataSet, leafType=regLeaf, errType=regErr, ops=(1,4)):	#最佳二元切分方式
	tolS = ops[0]; tolN = ops[1]		#tolS是容許的誤差下降值,tolN是切分的最少樣本數
	#如果剩余特征值的數量等於1,不需要再切分直接返回,(退出條件1)
	if len(set(dataSet[:,-1].T.tolist()[0])) == 1:		
		return None, leafType(dataSet)
	m,n = shape(dataSet)
	#the choice of the best feature is driven by Reduction in RSS error from mean
	S = errType(dataSet)		#計算平方誤差
	bestS = inf; bestIndex = 0; bestValue = 0
	for featIndex in range(n-1):
		#循環整個集合
		for splitVal in set(dataSet[:,featIndex]):	#每次返回的集合中,元素的順序都將不一樣
			mat0, mat1 = binSplitDataSet(dataSet, featIndex, splitVal)		#將數據集合切分得到兩個子集
			#如果划分的集合的大小小於切分的最少樣本數,重新划分
			if (shape(mat0)[0] < tolN) or (shape(mat1)[0] < tolN): continue
			newS = errType(mat0) + errType(mat1)	#計算兩個集合的平方誤差和
			#平方誤差和newS小於bestS,進行更新
			if newS < bestS: 
				bestIndex = featIndex
				bestValue = splitVal
				bestS = newS
	#在循環了整個集合后,如果誤差減少量(S - bestS)小於容許的誤差下降值,則退出,(退出條件2)
	if (S - bestS) < tolS: 
		return None, leafType(dataSet)
	mat0, mat1 = binSplitDataSet(dataSet, bestIndex, bestValue)	#按照保存的最佳分割來划分集合
	#如果切分出的數據集小於切分的最少樣本數,則退出,(退出條件3)
	if (shape(mat0)[0] < tolN) or (shape(mat1)[0] < tolN):
		return None, leafType(dataSet)
	#返回最佳二元切割的bestIndex和bestValue
	return bestIndex,bestValue

def createTree(dataSet, leafType=regLeaf, errType=regErr, ops=(1,4)):#assume dataSet is NumPy Mat so we can array filtering
	feat, val = chooseBestSplit(dataSet, leafType, errType, ops)	#采用最佳分割,將數據集分成兩個部分
	if feat == None: return val 	#遞歸結束條件
	retTree = {}					#建立返回的字典
	retTree['spInd'] = feat
	retTree['spVal'] = val
	lSet, rSet = binSplitDataSet(dataSet, feat, val)	#得到左子樹集合和右子樹集合
	retTree['left'] = createTree(lSet, leafType, errType, ops)		#遞歸左子樹
	retTree['right'] = createTree(rSet, leafType, errType, ops)		#遞歸右子樹
	return retTree

 

mian.py

# coding:utf-8
# !/usr/bin/env python

import regTrees
import matplotlib.pyplot as plt
from numpy import *

if __name__ == '__main__':
	myDat = regTrees.loadDataSet('ex00.txt')
	myMat = mat(myDat)
	print myMat.T
	Tree = regTrees.createTree(myMat)
	print Tree
	regTrees.plotBestFit('ex00.txt')

 

結果只是切分成兩個子樹

再查看原來的數據集的分布

如果換一個數據集的話

則子樹的數量變多,再查看原來數據集的分布

 

 

一棵樹如果節點過多,表示該模型可能對數據進行了“過擬合”。通過降低決策樹的復雜度來避免過擬合的過程稱為剪枝(pruning)

剪枝分為預剪枝(prepruning)和后剪枝(postpruning)

預剪枝是指在決策樹生成過程中,對每個節點在划分前先進行估計,若當前節點的划分不能帶來決策樹泛化性能的提升,則停止划分並將當前節點記為葉節點(上面的程序已經使用了預剪枝);

后剪枝則是先在訓練集生成一棵完整的決策樹,然后自底向上地對非葉節點進行考察,若將該節點對應的子樹替換為葉節點能帶來決策樹泛化性能提升,則將該子樹替換為葉節點

使用后剪枝方法需要將數據集分成測試集和訓練集。首先指定參數,使得構建出的樹足夠大、足夠復雜,便於剪枝。接下來從上而上找到葉節點,用測試集來判斷將這些葉節點合並是夠能降低測試誤差。如果是的話就進行合並。

 

#####################回歸樹剪枝函數#####################
def isTree(obj):				#該函數用於判斷當前處理的是否是葉節點
	return (type(obj).__name__=='dict')

def getMean(tree):				#從上往下遍歷樹,尋找葉節點,並進行塌陷處理(用兩個孩子節點的平均值代替父節點的值)
	if isTree(tree['right']): tree['right'] = getMean(tree['right'])
	if isTree(tree['left']): tree['left'] = getMean(tree['left'])
	return (tree['left']+tree['right'])/2.0
    
def prune(tree, testData):			#后剪枝,tree是待剪枝的樹,testData是用於剪枝的測試參數
	#如果測試集為空,進行塌陷處理,最后將會剩下兩個葉節點
	if shape(testData)[0] == 0:
		return getMean(tree)
	#如果測試集非空,按照保存的回歸樹對測試集進行切分
	if (isTree(tree['right']) or isTree(tree['left'])):
		lSet, rSet = binSplitDataSet(testData, tree['spInd'], tree['spVal'])
	if isTree(tree['left']):
		tree['left'] = prune(tree['left'], lSet)
	if isTree(tree['right']):
		tree['right'] =  prune(tree['right'], rSet)
    #左右子樹都是葉子節點,對合並前后的誤差進行比較,如果合並后的誤差比不合並的誤差小就進行合並操作,否則直接返回
	if not isTree(tree['left']) and not isTree(tree['right']):
		lSet, rSet = binSplitDataSet(testData, tree['spInd'], tree['spVal'])
		errorNoMerge = sum(power(lSet[:,-1] - tree['left'],2)) + sum(power(rSet[:,-1] - tree['right'],2))	#不合並的誤差
		treeMean = (tree['left']+tree['right'])/2.0
		errorMerge = sum(power(testData[:,-1] - treeMean,2))	#合並的誤差
		if errorMerge < errorNoMerge:							#比較
			print "merging"
			return treeMean
		else: return tree
	else: return tree

 大量的節點已經被剪枝掉了,但是並沒有像預期的那樣剪枝成兩個部分,說明后剪枝可能不如預剪枝有效。一般地,為了尋求最佳模型可以同時使用兩種剪枝技術。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM