再回到咱们上篇文章讲到的Adaboost算法,咱们要从Adaboost算法推导出GBDT。首先回顾一下上篇文章的Adaboost,主要思想就是把弱分类器集中起来获得一个强的分类器。首先第一次建造树的时候每个样本的权值都是同样的,以后的每一次训练只要有错误,那么这个错误就会被放大,而正确的权值就会被缩小,以后会获得每个模型的α,根据每个树的α把结果结合起来就获得须要的结果。
node
①为何Adaboost要弱类型的分类器?
分类器讲道理,应该是越强越好的,可是Adaboost相反,他只要弱的,强的不行。来看一下α,若是你的分类器很强,ξ基本就是equal 0了,这样的α是无穷大的,其余的分类器就没有意义了,这样就又回到了单分类器,而单分类器是作不到aggregation model的好处的,祥见aggregation model这篇文章。
git
咱们把这种称为autocracy,独裁。针对这两个缘由,咱们能够作剪枝,限制树的大小等等的操做,好比只使用一部分样本,这在sampling的操做中已经起到这类做用,由于必然有些样本没有被采样到。
因此,综上缘由,Adaboost经常使用的模型就是decision stump,一层的决策树。
事实上,若是树高为1时,一般较难遇到ξ=0的状况,且通常不采用sampling的操做,而是直接将权重u代入到算法中。这是由于此时的AdaBoost-DTree就至关因而AdaBoost-Stump,而AdaBoost-Stump就是直接使用u来优化模型的。前面的算法实现也是基于这些理论实现的算法。
回到正题,继续从optimization的角度讨论Adaboost,Adaboost权重计算以下:
github
②Adaboost的error
上式中咱们把voting score看作是s。对于0/1error,若是ys >= 0,证实是分类正确的,不惩罚err0/1 = 0,;若是ys < 0,证实分类错误,惩罚err0/1 = 1。对于Adaboost,咱们能够用算法
来表示,可能有些同窗以为这里不太对, 这里明明是u的表达式为何能够做为错误的衡量?这个表达式里面包含了y和s,正好能够用来表达Adaboost的error,是就使用了。
正好也是减少的,和咱们须要的err是同样的,要注意这里Adaboost咱们是反推,已知到效果和方法反推表达式,使用要用上一切可使用的。
和error0/1对比:
因此,Adaboost的error function是能够代替error0/1的,而且效果更好。
③首先要了解一些泰勒展开:
在通常的函数状况下,是有:
数据结构
①:
②:
①②式子代入上面:
这就是一阶Taylor expansion。待会要利用他们来求解。
用Taylor expansion处理一下Adaboost error function:
app
推导完了Adaboost,咱们接着推导Gradient Boosted Decision Tree,其实看名字就知道只不过是error function不太同样而已。前面Adaboost的推导总的能够归纳为:less
到这里,aggregation model基本就完成了。主要有三个方面:
①uniform:把g(x)平均结合。
②non-uniform:把g(x)线性组合。
③conditional:根据不一样条件作非线性组合。
uniform采用投票、求平均的形式更注重稳定性;而non-uniform和conditional追求的更复杂准确的模型,但存在过拟合的危险。
dom
主要的作法就是用{(x, (y - s))}作拟合就行了。
因为以前写的决策树结构设计的不太好,使用起来不方便因而从新写了一个,这里的CART树是用方差来衡量impurity的。函数
def loadDataSet(filename): ''' load dataSet :param filename: the filename which you need to open :return: dataset in file ''' dataMat = pd.read_csv(filename) for i in range(np.shape(dataMat)[0]): if dataMat.iloc[i, 2] == 0: dataMat.iloc[i, 2] = -1 return dataMat pass def split_data(data_array, col, value): '''split the data according to the feature''' array_1 = data_array.loc[data_array.iloc[:, col] >= value, :] array_2 = data_array.loc[data_array.iloc[:, col] < value, :] return array_1, array_2 pass def getErr(data_array): '''calculate the var ''' return np.var(data_array.iloc[:, -1]) * data_array.shape[0] pass def regLeaf(data_array): return np.mean(data_array.iloc[:, -1])
加载数据,分割数据,计算方差,计算叶子平均,其实就是计算拟合的类别了。学习
def get_best_split(data_array, ops = (1, 4)): '''the best point to split data''' tols = ops[0] toln = ops[1] if len(set(data_array.iloc[:, -1])) == 1: return None, regLeaf(data_array) m, n = data_array.shape best_S = np.inf best_col = 0 best_value = 0 S = getErr(data_array) for col in range(n - 1): values = set(data_array.iloc[:, col]) for value in values: array_1, array_2 = split_data(data_array, col, value) if (array_1.shape[0] < toln) or (array_2.shape[0] < toln): continue totalError = getErr(array_1) + getErr(array_2) if totalError< best_S: best_col = col best_value = value best_S = totalError if (S - best_S) < tols: return None, regLeaf(data_array) array_1, array_2 = split_data(data_array, best_col, best_value) if (array_1.shape[0] < toln) or (array_2.shape[0] < toln): return None, regLeaf(data_array) return best_col, best_value
获得最好的分类,这里相比以前的决策树加了一些条件限制,叶子数量不能少于4,和以前的同样,计算方差对比看看哪一个小。
class node: '''tree node''' def __init__(self, col=-1, value=None, results=None, gb=None, lb=None): self.col = col self.value = value self.results = results self.gb = gb self.lb = lb pass
叶子节点,col列,val划分的值,results结果,gb右子树,lb左子树。
def buildTree(data_array, ops = (1, 4)): col, val = get_best_split(data_array, ops) if col == None: return node(results=val) else: array_1, array_2 = split_data(data_array, col, val) greater_branch = buildTree(array_1, ops) less_branch = buildTree(array_2, ops) return node(col=col, value=val, gb=greater_branch, lb=less_branch) pass
创建一棵树。
def treeCast(tree, inData): '''get the classification''' if tree.results != None: return tree.results if inData.iloc[tree.col] > tree.value: return treeCast(tree.gb, inData) else: return treeCast(tree.lb, inData) pass def createForeCast(tree, testData): m = len(testData) yHat = np.mat(np.zeros((m, 1))) for i in range(m): yHat[i, 0] = treeCast(tree, testData.iloc[i]) return yHat
建立分类。
def GBDT_model(data_array, num_iter, ops = (1, 4)): m, n = data_array.shape x = data_array.iloc[:, 0:-1] y = data_array.iloc[:, -1] y = np.mat(y).T list_trees = [] yHat = None for i in range(num_iter): print('the ', i, ' tree') if i == 0: tree = buildTree(data_array, ops) list_trees.append(tree) yHat = createForeCast(tree, x) else: r = y - yHat data_array = np.hstack((x, r)) data_array = pd.DataFrame(data_array) tree = buildTree(data_array, ops) list_trees.append(tree) rHat = createForeCast(tree, x) yHat = yHat + rHat return list_trees, yHat
这里只是使用了回归问题的回归树,x和(y - s)作拟合以后加入预测集便可。
接下来就是画图了:
def getwidth(tree): if tree.gb == None and tree.lb == None: return 1 return getwidth(tree.gb) + getwidth(tree.lb) def getdepth(tree): if tree.gb == None and tree.lb == None: return 0 return max(getdepth(tree.gb), getdepth(tree.lb)) + 1 def drawtree(tree, jpeg='tree.jpg'): w = getwidth(tree) * 100 h = getdepth(tree) * 100 + 120 img = Image.new('RGB', (w, h), (255, 255, 255)) draw = ImageDraw.Draw(img) drawnode(draw, tree, w / 2, 20) img.save(jpeg, 'JPEG') def drawnode(draw, tree, x, y): if tree.results == None: # Get the width of each branch w1 = getwidth(tree.lb) * 100 w2 = getwidth(tree.gb) * 100 # Determine the total space required by this node left = x - (w1 + w2) / 2 right = x + (w1 + w2) / 2 # Draw the condition string draw.text((x - 20, y - 10), str(tree.col) + ':' + str(tree.value), (0, 0, 0)) # Draw links to the branches draw.line((x, y, left + w1 / 2, y + 100), fill=(255, 0, 0)) draw.line((x, y, right - w2 / 2, y + 100), fill=(255, 0, 0)) # Draw the branch nodes drawnode(draw, tree.lb, left + w1 / 2, y + 100) drawnode(draw, tree.gb, right - w2 / 2, y + 100) else: txt = str(tree.results) draw.text((x - 20, y), txt, (0, 0, 0))
以后就是运行主函数了:
if __name__ == '__main__': data = loadDataSet('../Data/LogiReg_data.txt') tree = buildTree(data) drawtree(tree, jpeg='treeview_cart.jpg') gbdt_results, y = GBDT_model(data, 10) print(y) for i in range(len(y)): if y[i] > 0: print('1') elif y[i] < 0: print('0')
效果:
效果貌似仍是能够的。aggregation model就到此为止了,几乎全部经常使用模型都讲完了。
最后符上GitHub全部代码:
https://github.com/GreenArrow2017/MachineLearning/tree/master/MachineLearning/GBDT