import numpy 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')#去掉开头和结尾的空格(包括以下字符:'\t','\n','\r',' '),并且以制表符‘\t’进行分割数据 fltLine = list(map(float, curLine)) # map all elements to float(),这里使用的是map函数直接把数据转化成为float类型 dataMat.append(fltLine)#把数据放在dataMat中 return dataMat def binSplitDataSet(dataSet, feature, value): mat0 = dataSet[numpy.nonzero(dataSet[:, feature] > value)[0], :]#numpy.nonzero(dataSet[:, feature] > value)[0]满足条件的行的数 #nonzero函数是numpy中用于得到数组array中非零元素的位置(数组索引)的函数。 #索引值数组的每一个array均是从一个维度上来描述其索引值。比如,如果a是一个二维数组, #则索引值数组有两个array,第一个array从行维度来描述索引值;第二个array从列维度来描述索引值。 mat1 = dataSet[numpy.nonzero(dataSet[:, feature] <= value)[0], :] #想要切分的是数据,所以后面[0]是多余的[0]代表数据的第一个元素 return mat0, mat1 # 负责生成叶节点,当chooseBestSplit()函数确定不再对数据进行切分时, # 将调用该regLeaf()函数来得到叶节点的模型,在回归树中,该模型其实就是目标变量的均值 def regLeaf(dataSet): # returns the value used for each leaf,负责生成叶节点 return numpy.mean(dataSet[:, -1])#返回最后一列的算术平均值 # 误差估计函数,该函数在给定的数据上计算目标变量的平方误差,这里直接调用均方差函数var # 因为这里需要返回的是总方差,所以要用均方差乘以数据集中样本的个数 def regErr(dataSet):#计算目标变量的平方误差 return numpy.var(dataSet[:, -1]) * numpy.shape(dataSet)[0]#返回最后一列的总方差,计算该列的混乱度 # dataSet: 数据集合 # leafType: 给出建立叶节点的函数 # errType: 误差计算函数 # ops: 包含树构建所需其他参数的元组 #createTree是一个递归函数 # 将数据集分成两个部分,若满足停止条件,chooseBestSplit将返回None和某类模型的值 # 若构建的是回归树,该模型是个常数。若是模型树,其模型是一个线性方程。 # 若不满足停止条件,chooseBestSplit()将创建一个新的Python字典,并将数据集分成两份, # 在这两份数据集上将分别继续递归调用createTree()函数 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) # choose the best split if feat == None: return val # if the splitting hit a stop condition return val分裂到达截止条件,返回val(建立的叶节点的类型值) retTree = {}#创建一个空字典 retTree['spInd'] = feat retTree['spVal'] = val lSet, rSet = binSplitDataSet(dataSet, feat, val)#按照得到的特征值进行分割 retTree['left'] = createTree(lSet, leafType, errType, ops)#再把分割好的数调用createTree进行处理 retTree['right'] = createTree(rSet, leafType, errType, ops) return retTree#返回该字典 def chooseBestSplit(dataSet, leafType=regLeaf, errType=regErr, ops=(1, 4)):#第一个是数据,第二个,第三个是对上面函数的引用, # 最后一个是用户输入的参数 # tolS是容许的误差下降值 # tolN是切分的最小样本数 tolS = ops[0]#第一个参数 tolN = ops[1]#第二个参数 # if all the target variables are the same value: quit and return value # 如果剩余特征值的数目为1,那么就不再切分而返回 if len(set(dataSet[:, -1].T.tolist()[0])) == 1: # exit cond 1,把矩阵变为列表,并且取第一个位置 return None, leafType(dataSet) m, n = numpy.shape(dataSet) # the choice of the best feature is driven by Reduction in RSS error from mean S = errType(dataSet)#当前数据集的误差 bestS = numpy.inf bestIndex = 0 bestValue = 0 for featIndex in range(n - 1):#遍历n-1个特征 for splitVal in set((dataSet[:, featIndex].T.A.tolist())[0]):#划分值遍历每个特征下面所有的数 mat0, mat1 = binSplitDataSet(dataSet, featIndex, splitVal)#计算当前情况下的分割的两部分 if (numpy.shape(mat0)[0] < tolN) or (numpy.shape(mat1)[0] < tolN): continue#判断切分的两部分样本是否小于最小样本,如果小于,则退出此次循环 newS = errType(mat0) + errType(mat1)#计算切片1和切片2的误差之和 if newS < bestS:#如果新的误差小于最小的误差,那么替换它 bestIndex = featIndex#把特征值赋给它 bestValue = splitVal#最好的切分值赋给 bestS = newS#最好的误差 # if the decrease (S-bestS) is less than a threshold don't do the split if (S - bestS) < tolS:#如果初试的误差和现在的误差之差小于某一容忍度,证明切分数据效果不大,那么不应进行切分数据操作,而应该直接创建叶节点 return None, leafType(dataSet) # exit cond 2+直接创建叶节点 mat0, mat1 = binSplitDataSet(dataSet, bestIndex, bestValue)#否则利用最好的进行切分 if (numpy.shape(mat0)[0] < tolN) or (numpy.shape(mat1)[0] < tolN): # exit cond 3子集大小不满足用户指定的 return None, leafType(dataSet) #最后如果这些终止条件都不满足,那么返回切片特征和特征值 return bestIndex, bestValue # returns the best feature to split on # and the value used for that split def isTree(obj): """ Desc: 测试输入变量是否是一棵树,即是否是一个字典 Args: obj -- 输入变量 Returns: 返回布尔类型的结果。如果 obj 是一个字典,返回true,否则返回 false """ 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是由训练集生成的 if numpy.shape(testData)[0] == 0: return getMean(tree) # if we have no test data collapse the tree判断测试集是否为空,不为空则对树进行塌陷处理 if (isTree(tree['right']) or isTree(tree['left'])): # if the branches are not trees try to prune them判断树的分支是不是树 lSet, rSet = binSplitDataSet(testData, tree['spInd'], tree['spVal'])#对测试集进行切分 if isTree(tree['left']): tree['left'] = prune(tree['left'], lSet)#循环对测试集进行切分,(创建树的时候,每个部分的切分特征值和特征都是已知的,所以prune时候直接调用) if isTree(tree['right']): tree['right'] = prune(tree['right'], rSet) # if they are now both leafs, see if we can merge them # 上面的一系列操作本质上就是将测试数据集按照训练完成的树拆分好,对应的值放到对应的节点 # 如果左右两边同时都不是dict字典,也就是左右两边都是叶节点,而不是子树了,那么分割测试数据集。 # 1. 如果正确 # * 那么计算一下总方差 和 该结果集的本身不分枝的总方差比较 # * 如果 合并的总方差 < 不合并的总方差,那么就进行合并 # 注意返回的结果: 如果可以合并,原来的dict就变为了 数值 if not isTree(tree['left']) and not isTree(tree['right']):#如果左右两边都不是字典 ##用测试集来判断是否能降低误差,如果可以,那么对原来的树进行合并,测试集只是用来判断的!!! lSet, rSet = binSplitDataSet(testData, tree['spInd'], tree['spVal'])#对测试集进行切分 # # power(x, y)表示x的y次方 errorNoMerge = sum(numpy.power(lSet[:, -1] - tree['left'], 2)) + \ sum(numpy.power(rSet[:, -1] - tree['right'], 2))#不合并的总方差 treeMean = (tree['left'] + tree['right']) / 2.0 errorMerge = sum(numpy.power(testData[:, -1] - treeMean, 2))#合并的总方差 if errorMerge < errorNoMerge:#判断合并的总方差是否小于不合并的总方差 print("merging") return treeMean#返回合并的平均值 else: return tree#否则,返回原来的树 else: return tree #输入数据,拟合出直线方程的权重系数 def linearSolve(dataSet): # helper function used in two places m, n = numpy.shape(dataSet)#输入数据的形状 X = numpy.mat(numpy.ones((m, n)))#矩阵初始化全为1 Y = numpy.mat(numpy.ones((m, 1))) # create a copy of data with 1 in 0th postion X[:, 1:n] = dataSet[:, 0:n - 1]#第2列到最后一列赋予特征值,第一列保持为1 Y = dataSet[:, -1] # and strip out Y xTx = X.T * X#n * n的矩阵 if numpy.linalg.det(xTx) == 0.0:#判断是否是奇异的 raise NameError('This matrix is singular, cannot do inverse,\n\ try increasing the second value of ops') ws = xTx.I * (X.T * Y)#计算权重 return ws, X, Y#返回权重,X,Y #调用上面的函数,返回权重 def modelLeaf(dataSet): # create linear model and return coeficients ws, X, Y = linearSolve(dataSet) return ws #来计算数据和拟合直线之间的误差 def modelErr(dataSet): ws, X, Y = linearSolve(dataSet) yHat = X * ws return sum(numpy.power(Y - yHat, 2)) def regTreeEval(model, inDat):# 回归树预测 """ Desc: 对 回归树 进行预测 Args: model -- 指定模型,可选值为 回归树模型 或者 模型树模型,这里为回归树 inDat -- 输入的测试数据 Returns: float(model) -- 将输入的模型数据转换为 浮点数 返回 """ return float(model)#返回树的叶节点的浮点型 # 模型树测试案例 # 对输入数据进行格式化处理,在原数据矩阵上增加第0列,元素的值都是1, # 也就是增加偏移值,和我们之前的简单线性回归是一个套路,增加一个偏移量 """ Desc: 对 模型树 进行预测 Args: model -- 输入模型,可选值为 回归树模型 或者 模型树模型,这里为模型树模型 inDat -- 输入的测试数据 Returns: float(X * model) -- 将测试数据乘以 回归系数 得到一个预测值 ,转化为 浮点数 返回 """ def modelTreeEval(model, inDat):# 模型树预测 n = numpy.shape(inDat)[1] m=numpy.shape(inDat)[0] X = numpy.mat(numpy.ones((1, n + 1)))#我认为这儿是有问题的,应该是创建m*(n+1)维矩阵 X[:, 1:n + 1] = inDat#保留第一列,将n个特征值放到后面 return float(X * model)#将测试数据乘以 回归系数 得到一个预测值 ,转化为 浮点数 返回 #在给定树结构的情况下,treeForeCast会返回一个浮点值 #调用treeForeCast需要指定树的类型,以便在叶节点上能够调用合适的模型 # 计算预测的结果 # 在给定树结构的情况下,对于单个数据点,该函数会给出一个预测值。 # modelEval是对叶节点进行预测的函数引用,指定树的类型,以便在叶节点上调用合适的模型。 # 此函数自顶向下遍历整棵树,直到命中叶节点为止,一旦到达叶节点,它就会在输入数据上 # 调用modelEval()函数,该函数的默认值为regTreeEval() def treeForeCast(tree, inData, modelEval=regTreeEval): """ Desc: 对特定模型的树进行预测,可以是 回归树 也可以是 模型树 Args: tree -- 已经训练好的树的模型 inData -- 输入的测试数据 modelEval -- 预测的树的模型类型,可选值为 regTreeEval(回归树) 或 modelTreeEval(模型树),默认为回归树 Returns: 返回预测值 """ #这段代码其实就是对输入某一个数据inData,利用tree进行预测,最终返回某个和inData数值最近的叶节点的浮点型数 if not isTree(tree): return modelEval(tree, inData)#如果输入的训练模型不是树,返回预测树的模型类型 if inData[tree['spInd']] > tree['spVal']:#tree['spInd']特征值,找出inData某个特征值中大于某个阈值tree['spVal']的数据 if isTree(tree['left']):#树的左半部分是树 return treeForeCast(tree['left'], inData, modelEval)#继续进行分割 else: return modelEval(tree['left'], inData) else: if isTree(tree['right']): return treeForeCast(tree['right'], inData, modelEval) else: return modelEval(tree['right'], inData) def createForeCast(tree, testData, modelEval=regTreeEval): m = len(testData)#得到测试数据的长度 yHat = numpy.mat(numpy.zeros((m, 1)))#定义一个存储yHat数据的矩阵 for i in range(m): yHat[i, 0] = treeForeCast(tree, numpy.mat(testData[i]), modelEval)#对每个测试数据进行预测 return yHat 命令框:
import regTrees import numpy testMat=numpy.mat(numpy.eye(4)) mat0,mat1=regTrees.binSplitDataSet(testMat, 2, 0.5) print(mat0) print(mat1) myDat=regTrees.loadDataSet('ex00.txt') myMat=numpy.mat(myDat) print(regTrees.createTree(myMat)) myDat1=regTrees.loadDataSet('ex09.txt')#由于和以前章节的文件名重复,本人把文件名变为ex09 myMat1=numpy.mat(myDat1) print(regTrees.createTree(myMat1)) print(regTrees.createTree(myMat1,ops=(0,1))) myDat2=regTrees.loadDataSet('ex2.txt') myMat2=numpy.mat(myDat2) print(regTrees.createTree(myMat2)) mytree=regTrees.createTree(myMat2,ops=(0,1)) mydatTest=regTrees.loadDataSet('ex2test.txt') myMat2Test=numpy.mat(mydatTest) print(regTrees.prune(mytree,myMat2Test)) myMat2=numpy.mat(regTrees.loadDataSet('exp2.txt')) print(regTrees.createTree(myMat2,regTrees.modelLeaf,regTrees.modelErr,(1,10))) trainMat=numpy.mat(regTrees.loadDataSet('bikeSpeedVsIq_train.txt')) testMat=numpy.mat(regTrees.loadDataSet('bikeSpeedVsIq_test.txt')) myTree=regTrees.createTree(trainMat,ops=(1,20)) yHat=regTrees.createForeCast(myTree,testMat[:,0]) print(numpy.corrcoef(yHat,testMat[:,1],rowvar=0)[0,1])
9.71用tkinter创建GUI import tkinter import numpy import regTrees import matplotlib import matplotlib matplotlib.use('TkAgg') from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg from matplotlib.figure import Figure root = tkinter.Tk() myLabel = tkinter.Label(root,text='hello world!') myLabel.grid() root.mainloop() def reDraw(tolS,tolN): pass def drawNewTree(): pass root=tkinter.Tk() tkinter.Label(root,text="Plot Place Holder").grid(row=0,columnspan=3) tkinter.Label(root,text="tolN").grid(row=1,column=0)#加入的标签 tolNentry=tkinter.Entry(root)#Entry:单行文本输入框 tolNentry.grid(row=1,column=1)#文本的位置 tolNentry.insert(0,'10') tkinter.Label(root,text="tolS").grid(row=2,column=0)#加入的标签 tolSentry=tkinter.Entry(root) tolSentry.grid(row=2,column=1)#文本的位置 tolSentry.insert(0,'1.0') tkinter.Button(root,text="ReDraw",command=drawNewTree).grid(row=1,column=2,rowspan=3) chkBtnVar=tkinter.IntVar() chkBtn=tkinter.Checkbutton(root,text="Model Tree", variable=chkBtnVar)#复制按钮 chkBtn.grid(row=3,column=0,columnspan=2) reDraw.rawDat=numpy.mat(regTrees.loadDataSet('sine.txt')) reDraw.testDat=numpy.arange(min(reDraw.rawDat[:,0]),max(reDraw.rawDat[:,0]),0.01) reDraw(1.0,10) root.mainloop()
9.72还没有实现(待后续回来补充)
import numpy import tkinter import regTrees import matplotlib matplotlib.use('TkAgg') from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg from matplotlib.figure import Figure def reDraw(tolS, tolN): reDraw.f.clf() # clear the figure reDraw.a = reDraw.f.add_subplot(111) if chkBtnVar.get(): if tolN < 2: tolN = 2#保证tolN>=2 myTree = regTrees.createTree(reDraw.rawDat, regTrees.modelLeaf, \ regTrees.modelErr, (tolS, tolN)) yHat = regTrees.createForeCast(myTree, reDraw.testDat, \ regTrees.modelTreeEval) else: myTree = regTrees.createTree(reDraw.rawDat, ops=(tolS, tolN))#创建树 yHat = regTrees.createForeCast(myTree, reDraw.testDat)#计算出预测值 reDraw.a.scatter(reDraw.rawDat[:, 0], reDraw.rawDat[:, 1], s=5) # use scatter for data set绘制出真实值 reDraw.a.plot(reDraw.testDat, yHat, linewidth=2.0) # use plot for yHat绘制出预测值 reDraw.canvas.show()#显示出来 #试图理解用户的输入,防止崩溃,tolS 期望的输入是浮点数,tolN期望的输入是整数 #如果python可以把输入文本解析成整数就继续执行,如果不能识别则输出错误消息,同时清空框并恢复默认值 def getInputs():#得到输入框的值 try: tolN = int(tolNentry.get()) except: tolN = 10 print("enter Integer for tolN") tolNentry.delete(0, tkinter.END) tolNentry.insert(0, '10') try: tolS = float(tolSentry.get()) except: tolS = 1.0 print("enter Float for tolS") tolSentry.delete(0, tkinter.END) tolSentry.insert(0, '1.0') return tolN, tolS def drawNewTree(): tolN, tolS = getInputs() # get values from Entry boxes调用getInputs得到输入框的值,利用该值调用reDraw生成一个漂亮的图 reDraw(tolS, tolN) root = tkinter.Tk() #用画布来替换绘制占位符,并删掉对应标签,即下面的Plot Place Holder那句 reDraw.f = Figure(figsize = (5,4),dpi = 100) reDraw.canvas = FigureCanvasTkAgg(reDraw.f, master=root) reDraw.canvas.show() reDraw.canvas.get_tk_widget().grid(row = 0,columnspan = 3) #待删除,只是代替图片占一个位置 #Label(root,text='Plot Place Holder').grid(row = 0, columnspan = 3) #tolN tkinter.Label(root,text = 'tolN').grid(row = 1, column = 0) tolNentry = tkinter.Entry(root) #Entry:单行文本输入框 tolNentry.grid(row=1, column = 1) tolNentry.insert(0,'10') #默认值为10 #tolS tkinter.Label(root,text = 'tolS').grid(row = 2, column = 0) tolSentry = tkinter.Entry(root) tolSentry.grid(row=2, column = 1) tolSentry.insert(0,'1.0') #默认值为1.0 #按钮 tkinter.Button(root,text = 'ReDraw',command = drawNewTree).grid(row = 1, column = 2,rowspan = 3) #按钮整数值 chkBtnVar = tkinter.IntVar() #复选按钮 chkBtn = tkinter.Checkbutton(root,text = 'Model Tree',variable = chkBtnVar) chkBtn.grid(row = 3, column = 0,columnspan = 2) #初始化数据 reDraw.rawDat = numpy.mat(regTrees.loadDataSet('sine.txt')) reDraw.testDat = numpy.arange(min(reDraw.rawDat[:,0]),max(reDraw.rawDat[:,0]),0.01) reDraw(1.0,10) root.mainloop()