这几天完成了树回归的相关学习,这一部分内容挺多,收获也挺多,刚刚终于完成了全部内容,非常开心。
树回归这一章涉及了CART,CART树称作(classify and regression tree) 分类与回归树,既可以用于分类,也可以用于回归。这正是前面决策树没有说到的内容,在这里补充一下。正好也总结一下我们学的3种决策树。
ID3:用信息增益来选择特性进行分类,只能处理分类问题。缺点是往往偏向于特性种类多的特性进行分解,比如特性A有2种选择,特性B有3种选择,混乱度差不多的情况下,ID3会偏向特性B进行选择,这样会使得树变得复杂,增加了建树难度,节点的增加也使得过拟合情况的出现。因为这个缺点,我们发明了ID3的优化版C4.5。
C4.5:用信息增益率来选择特性进行分类,信息增益和特性种类的个数多少正好抵消,信息增益率可以客观的表示选择某一特性标签的混乱程度。
CART:用基尼指数(类似于熵的概念)来选择特性进行分类。用总方差进行回归预测。上述两种算法分割特性是一旦选择了该特性,该特性便不再起作用,由于该分割过于‘迅速’,不能更好的利用特性的价值,CART算法横空出世。
在以上的ID3和C4.5,只能处理分类问题,在遇到标签为连续型数值便无法处理,所以CART的优势便体现出来,CART可以进行回归分析,预测回归值,称作树回归,CART可以进行剪枝操作避免过拟合,分为预剪枝和后剪枝,后剪枝后面会有,预剪枝为修改函数的参数ops。也可以把叶节点的回归数值改成线性回归,称作模型树。回归效果略优于简单的线性回归(最后的三种回归结果我们可以看到)。
step1:
读取数据:
#读取数据
def loadDataSet(filename):
feanum = len(open(filename).readline().strip().split('\t'))
datamat = []
f = open(filename)
for i in f.readlines():
line = i.strip().split('\t')
l = []
for j in range(feanum):
l.append(float(line[j]))
datamat.append(l)
return datamat
step2:
递归创建回归树:
#创建树(ops两个参数第一个表示最小允许误差,第二个为样本最小容量)
def createTree(dataset, leaftype = regLeaf, errortype = regError, ops = [1, 4]):
tree = {}
fea, val = chooseBestFeatrue(dataset, leaftype, errortype, ops)
if fea == None:
return val
tree['fea'] = fea
tree['val'] = val
lefttree, righttree = splitDataSet(dataset, fea, val)
tree['left'] = createTree(lefttree, leaftype, errortype, ops)
tree['right'] = createTree(righttree, leaftype, errortype, ops)
return tree
step3:
回归树中的分割子集函数:
#分类函数
def splitDataSet(Data, featrue, val):
mat0 = []
mat1 = []
data = mat(Data)
m = shape(data)[0]
for i in range(m):
if data[i, featrue] > val:
mat0.append(data[i].tolist()[0])
else:
mat1.append(data[i].tolist()[0])
return mat0, mat1
step4:
回归树中的查找最佳特性函数:
停止条件:
1:分类的子集数量小于4个
2:误差值降低量小于最小允许值
3:标签值都为重复值,无需再次分解,返回为叶子节点。
#寻找最佳分类特性
def chooseBestFeatrue(dataset, leaftype = regLeaf, errortype = regError, ops = [1, 4]):
d = mat(dataset)
leastnum = ops[1]
leasterr = ops[0]
m, n = shape(d)
if len(set(d[ : , -1].T.tolist()[0])) == 1:
return None, leaftype(d)
errorsum = errortype(d)
bestfea = -1
bestval = -1
besterror = inf
for i in range(n - 1):
for temp in set(d[ : , i].T.tolist()[0]):
mat0, mat1 = splitDataSet(d, i, temp)
if shape(mat0)[0] < leastnum or shape(mat1)[0] < leastnum:
continue
tempsumerror = errortype(mat0) + errortype(mat1)
if tempsumerror < besterror:
bestfea = i
bestval = temp
besterror = tempsumerror
if (errorsum - besterror) < leasterr:
print errorsum, besterror
return None, leaftype(d)
mat0, mat1 = splitDataSet(d, bestfea, bestval)
if shape(mat0)[0] < leastnum or shape(mat1)[0] < leastnum:
return None, leaftype(d)
return bestfea, bestval
step5:
相关函数:
#计算平方误差
def regError(dataset):
t = mat(dataset)
return var(t[ : , -1]) * shape(t)[0]
#计算叶子节点的值(叶子节点的平均值)
def regLeaf(dataset):
t = mat(dataset)
return mean(t[ : , -1])
#判断节点是否为树
def isTree(tree):
return isinstance(tree, dict)
#节点值的平均值
def getMean(tree):
if isTree(tree['left']):
tree['left'] = getMean(tree['left'])
if isTree(tree['right']):
tree['right'] = getMean(tree['right'])
return (tree['right'] + tree['left']) / 2.0
我们看一下效果:
图1:
结果为:
图2:
结果为:
我们看到符合我们的预期。
step6:
回归树的剪枝:这里的剪枝和算法中的剪枝不同,算法中的剪枝为剪去不必要的节点,这里的剪枝为把繁琐的节点合并。目的位避免CART过拟合。分为预剪枝和后剪枝,
此为后剪枝,预剪枝为修改函数的参数ops。
这里有两个细节:
1:当没有数据进入树节点的时候,我们把这个树合并为一个节点。
2:当该节点的左节点和右节点都为值时,我们判断是分开好还是合并好。
为取得更好的剪枝效果,应该预剪枝和后剪枝一起用。
#回归树剪枝
def pruneTree(tree, testdata):
if shape(testdata)[0] == 0:
return getMean(tree)
if isTree(tree['left']) or isTree(tree['right']):
lset, rset = splitDataSet(testdata, tree['fea'], tree['val'])
if isTree(tree['left']):
tree['left'] = pruneTree(tree['left'], lset)
if isTree(tree['right']):
tree['right'] = pruneTree(tree['right'], rset)
if (not isTree(tree['left'])) and (not isTree(tree['right'])):
l, r = splitDataSet(testdata, tree['fea'], tree['val'])
lset = mat(l)
rset = mat(r)
e1 = 0.0
e2 = 0.0
#书中忽略了这一点可能会有一个集合没有元素
if shape(lset)[1] != 0:
e1 = sum(power(lset[ : , -1] - tree['left'], 2))
if shape(rset)[1] != 0:
e2 = sum(power(rset[ : , -1] - tree['right'], 2))
errornomerge = e1 + e2
average = (tree['left'] + tree['right']) / 2.0
test = mat(testdata)
errormerge = sum(power(test[ : , -1] - average, 2))
if errormerge < errornomerge:
print 'merging'
return average
else:
return tree
else:
return tree
step7:
模型树:
修改了回归树中的参数函数和加入了叶节点的线性回归函数。
#模型树线性回归函数(简单的线性回归函数,矩阵逆不存在时出错)
def lineSolve(data):
linedata = mat(data)
m, n = shape(linedata)
x = mat(ones((m, n)))
x[ : , 1 : n] = linedata[ : , 0 : n - 1]
y = linedata[ : , -1]
xtemp = x.T * x
if linalg.det(xtemp) == 0.0:
print 'error,没有逆矩阵'
return
w = xtemp.I * x.T * y
return x, y, w
#模型树的误差
def modError(dataset):
x, y, w = lineSolve(dataset)
yhat = x * w
return sum(power(yhat - y, 2))
#模型树的叶子节点
def modLeaf(dataset):
x, y, w = lineSolve(dataset)
return w
图为:
结果为:
叶子节点的值为w,x * w得到y的预测值。
step8:
树回归的预测函数:
#回归值(树回归)
def regValue(value, dataset):
return value
#回归值(模型树回归)
def modValue(value, dataset):
data = mat(dataset)
n = shape(data)[1]
x = mat(ones((1, n + 1)))
x[ : , 1 : n + 1] = data
yhat = x * value
return yhat
#树回归预测
def predictTree(tree, t, valuetype = regValue):
testdata = mat(t)
m, n = shape(testdata)
yhat = mat(zeros((m, 1)))
for i in range(m):
yhat[i, 0] = predictValue(tree, testdata[i], valuetype)
return yhat
#回归预测值
def predictValue(tree, test, valuetype = regValue):
if not isTree(tree):
return valuetype(tree, test)
if test[tree['fea']] > tree['val']:
return predictValue(tree['left'], test, valuetype)
else:
return predictValue(tree['right'], test, valuetype)
接下来我们进行3种回归的比较:
图为:
相关系数越接近1,相关性越大。
1:CART树回归:
相关系数为:
2:模型树:
相关系数为:
3:线性回归
相关系数为:
可见大小顺序为 模型树 > 回归树 > 线性回归
终于完成了监督学习的相关学习部分,SVM还不懂,有机会学习一下SVM的应用,明天开始学习无监督学习,先学习聚类算法。加油!