目前存在各种各样的机器学习算法,例如SVM、决策树、感知机等等。但是实际应用中,或者说在打比赛时,成绩较好的队伍几乎都用了集成学习(ensemble learning)的方法。集成学习的思想,简单来讲,就是“三个臭皮匠顶个诸葛亮”。集成学习通过结合多个学习器(例如同种算法但是参数不同,或者不同算法),一般会获得比任意单个学习器都要好的性能,尤其是在这些学习器都是"弱学习器"的时候提升效果会很明显。
弱学习器指的是性能不太好的学习器,比如一个准确率略微超过50%的二分类器。
下面看看西瓜书对此做的一个简单理论分析。
考虑一个二分类问题、真实函数以及奇数个犯错概率相互独立且均为的个体学习器(或者称基学习器)。我们用简单的投票进行集成学习,即分类结果取半数以上的基学习器的结果:
由Hoeffding不等式知,集成学习后的犯错(即过半数基学习器犯错)概率满足
式的证明是周志华《机器学习》的习题8.1,题解可参考此处
式指出,当犯错概率独立的基学习器个数很大时,集成后的犯错概率接近0,这也很符合直观想法: 大多数人同时犯错的概率是比较低的。
就如上面加粗字体强调的,以上推论全部建立在基学习器犯错相互独立的情况下,但实际中这些学习器不可能相互独立,而如何让基学习器变得“相对独立一些”,也即增加这些基学习器的多样性,正是集成学习需要考虑的主要问题。
按照每个基学习器之间是否存在依赖关系可以将集成学习分为两类:
Boosting系列算法里最著名算法主要有AdaBoost和提升树(Boosting tree)系列算法,本文只介绍最具代表性的AdaBoost。提升树、Bagging以及随机森林不在本文介绍范围内,有时间了再另外介绍。
随机森林是Bagging的一个实现,使用了决策树,Bagging也可使用其他分类器。
Boosting指的是一类集成方法,其主要思想就是将弱的基学习器提升(boost)为强学习器。具体步骤如下:
由此看出,Boosting算法是一个串行的过程。
Boosting算法簇中最著名的就是AdaBoost,下文将会详细介绍。
对于1.2节所述的Boosting算法步骤,需要回答两个问题:
AdaBoost(Adaptive Boosting, 自适应增强)算法采取的方法是:
Adaboost算法结构如下图(图片来源)所示。
下面先给出AdaBoost算法具体实现步骤,至于算法解释(为什么要这样做)将在下一大节阐述。
考虑如下形式的二分类(标准AdaBoost算法只适用于二分类任务)训练数据集:其中是一个含有个元素的列向量, 即;是标量,。
Adaboost算法具体步骤如下:
由式知,当基学习器的误差率时,,并且随着的减小而增大,即分类误差率越小的基学习器在最终集成时占比也越大。即AdaBoost能够适应各个弱分类器的训练误差率,这也是它的名称中"适应性(Adaptive)"的由来。
由式知, 被基学习器误分类的样本权值得以扩大,而被正确分类的样本的权值被得以缩小。
需要注意的是式中所有的的和并不为1(因为没有做一个softmax操作),的符号决定了所预测的类,其绝对值代表了分类的确信度。
有没有想过为什么AdaBoost算法长上面这个样子,例如为什么要用式那样计算?本节将探讨这个问题。
在解释AdaBoost算法之前,先来看看前向分步算法。
就以AdaBoost算法的最终模型表达式为例:可以看到这是一个“加性模型(additive model)”。我们希望这个模型在训练集上的经验误差最小,即
通常这是一个复杂的优化问题。前向分步算法求解这一优化问题的思想就是: 因为最终模型是一个加性模型,如果能从前往后,每一步只学习一个基学习器及其权重, 不断迭代得到最终的模型,那么就可以简化问题复杂度。具体的,当我们经过轮迭代得到了最优模型时,因为
所以此轮优化目标就为求解上式即可得到第个基分类器及其权重。
这样,前向分步算法就通过不断迭代求得了从到的所有基分类器及其权重,问题得到了解决。
上一小结介绍的前向分步算法逐一学习基学习器,这一过程也即AdaBoost算法逐一学习基学习器的过程。但是为什么2.2节中的公式为什么长那样还是没有解释。本节就证明前向分步算法的损失函数是指数损失函数(exponential loss function)时,AdaBoost学习的具体步骤就如2.2节所示。
指数损失函数即周志华《机器学习》p174有证明,指数损失函数是分类任务原本0/1损失函数的一致(consistent)替代损失函数,由于指数损失函数有更好的数学性质,例如处处可微,所以我们用它替代0/1损失作为优化目标。
将指数损失函数代入式,优化目标就为因为与优化变量和无关,如果令
这个其实就是2.2节中归一化之前的权重
那么式等价于
我们分两步来求解式所示的优化问题的最优解和:
由此可见,2.2节所述的AdaBoost算法步骤是可以经过严密推导得来的。总结一下,本节推导有如下关键点:
首先需要定义一个基学习器,它应该是一个弱分类器。
弱分类器使用库sklearn
中的决策树分类器DecisionTreeClassifier
, 可设置该决策树的最大深度为1。
# Fit a simple decision tree(weak classifier) first
clf_tree = DecisionTreeClassifier(max_depth = 1, random_state = 1)
然后就是完整AdaBoost算法的实现了,如下所示。
def my_adaboost_clf(Y_train, X_train, Y_test, X_test, M=20, weak_clf=DecisionTreeClassifier(max_depth = 1)):
n_train, n_test = len(X_train), len(X_test)
# Initialize weights
w = np.ones(n_train) / n_train
pred_train, pred_test = [np.zeros(n_train), np.zeros(n_test)]
for i in range(M):
# Fit a classifier with the specific weights
weak_clf.fit(X_train, Y_train, sample_weight = w)
pred_train_i = weak_clf.predict(X_train)
pred_test_i = weak_clf.predict(X_test)
# Indicator function
miss = [int(x) for x in (pred_train_i != Y_train)]
print("weak_clf_%02d train acc: %.4f"
% (i + 1, 1 - sum(miss) / n_train))
# Error
err_m = np.dot(w, miss)
# Alpha
alpha_m = 0.5 * np.log((1 - err_m) / float(err_m))
# New weights
miss2 = [x if x==1 else -1 for x in miss] # -1 * y_i * G(x_i): 1 / -1
w = np.multiply(w, np.exp([float(x) * alpha_m for x in miss2]))
w = w / sum(w)
# Add to prediction
pred_train_i = [1 if x == 1 else -1 for x in pred_train_i]
pred_test_i = [1 if x == 1 else -1 for x