[机器学习] Adaboost原理及实现

mac2024-04-01  34

Boost

  Adaboost(adaptive boosting)是一种集成学习算法,基于boosting的框架,因此首先简单说一下boosting算法。boosting算法主要的目的是将“弱分类器”提升为“强分类器”。其工作机制如下:

在原始训练数据上训练出一个基分类器;for t=2,3,…,T: 根据前一个分类器的分类结果调整样本的分布,使得被分错的样本受到更多的关注; 从调整后的训练样本训练下一个基分类器;得到T个基分类器后对他们进行加权得到强分类器。

Adaboost原理

  Adaboost是boost算法中最常用也是最著名的一个,其通过对基分类器进行线性组合得到最终的强分类器。Adaboost的实现流程如下图所示:

  在训练每个基分类器的时候,每个训练样本会被赋予一个权重。初始每个样本权重相同 D 1 ( x ) = 1 / m \mathcal{D}_{1}(x)=1/m D1(x)=1/m m m m 表示训练集样本数量,接下来训练分类器的时候会根据前一个分类器的分类结果调整样本的权重,前面分类错误的样本权重增加,而分类正确的样本权重会降低,这样在之后训练中就会更多地考虑之前分错的样本。此外每个基分类器会根据其分类准确率(错误率)得到一个权重,最终根据这个权重对所有的分类器进行线性组合得到性能更好的分类器。

  上图中 ϵ t \epsilon_{t} ϵt 就是第 t t t 个分类器在训练样本上的错误率,根据这个错误率,分类器的权重通过以下式子计算: α t = 1 2 ln ⁡ ( 1 − ϵ t ϵ t ) (1) \alpha_{t}=\frac{1}{2} \ln \left(\frac{1-\epsilon_{t}}{\epsilon_{t}}\right) \tag{1} αt=21ln(ϵt1ϵt)(1)得到第 t t t 个分类器的权重之后,再根据分类结果就可以更新每个训练样本的权重。如图中第7步所示,如果某个样本 x x x 被第 t t t 个分类器正确分类,那么在训练第 t + 1 t+1 t+1 个分类器的时候,其权重变为: D t + 1 ( x ) = D t ( x ) e − α t Z t (2) \mathcal{D}_{t+1}(x)=\frac{\mathcal{D}_{t}(x)e^{-\alpha_t}}{Z_{t}} \tag{2} Dt+1(x)=ZtDt(x)eαt(2) 否则,若样本被错误分类,则: D t + 1 ( x ) = D t ( x ) e α t Z t (3) \mathcal{D}_{t+1}(x)=\frac{\mathcal{D}_{t}(x)e^{\alpha_t}}{Z_{t}} \tag{3} Dt+1(x)=ZtDt(x)eαt(3) 式(2)和(3)中, Z t Z_t Zt 为规范化因子,在更新了所有样本的权重之后, Z t Z_t Zt 可以这样计算: Z t = ∑ x D t + 1 ( x ) Z_t=\sum_{x}\mathcal{D}_{t+1}(x) Zt=xDt+1(x) 得到所有的 T T T 个基分类器后,最终的分类器可以表示为下面这样: H ( x ) = ∑ t = 1 T α t h t ( x ) (4) H(\boldsymbol{x})=\sum_{t=1}^{T} \alpha_{t} h_{t}(\boldsymbol{x}) \tag{4} H(x)=t=1Tαtht(x)(4) 显然,它是所有的 T T T 个基分类器的线性组合。   还有一点,在每次训练出新的分类器之后都需要检查训练出的分类器的是否符合条件。图中基分类器的错误率 α t > 0.5 \alpha_{t}>0.5 αt>0.5 时就停止继续训练新分类器原因是,对于二分类问题,当分类器的错误率大于0.5也就意味着分类器的性能已经不如随机猜测了,此时分类器将被丢弃并停止训练。

实现

  接下来以基于决策树桩的Adaboost分类器为例实现马疝病数据集上的预测。决策树桩(decision stump是最简单的分类器,只通过一个特征来做决策,换句话说,决策树只有一次分类过程,包含两个用于决策的叶子节点。   马疝病数据集包含两类样本,为了后面实现方便,在加载数据集的时候将原本的类别标签0和1转化成-1和1,加载数据集代码如下:

def loadDataSet(path): dataSet = np.loadtxt(path) X = dataSet[:, :-1] y = 2 * dataSet[:, -1] - 1 # 注意将类标签换成-1, 1 return X, y

决策树桩分类器的代码如下:

def stumpClassify(X, dimen, threshVal, inqual): '''根据阈值和当前维度(dimen)的特征值返回样本分类结果''' returnArr = np.ones(X.shape[0]) if inqual == 'lt': returnArr[X[:, dimen] <= threshVal] = -1.0 else: returnArr[X[:, dimen] >= threshVal] = -1.0 return returnArr def buildStump(X, y, D): ''' 根据数据集构建一个决策树桩 :param X: 训练集的特征 :param y: 训练集的类别标签 :param D: 样本的权重向量 :return: 训练出的决策树桩,误差,分类结果 ''' m, n = X.shape numStep = 10 bestStump = {} # 训练出的决策树桩用字典存储 bestClassEst = np.zeros(m) # 样本的类别估计值 minError = np.inf for i in range(n): # 遍历特征 rangeMin = np.min(X[:, i]) # 特征取值的区间范围 rangeMax = np.max(X[:, i]) stepSize = (rangeMax - rangeMin) / numStep # 根据步数和范围求步长 for j in range(-1, numStep+1): for inequal in ['lt', 'gt']: threshVal = rangeMin + (j * stepSize) # 第j个区间的上边界,用来作为当前分类的阈值 predictedVals = stumpClassify(X, i, threshVal, inequal) error = np.ones(m) error[predictedVals == y] = 0 # 通过错误分类计算当前决策树桩的分类错误率 weightedError = np.dot(D, error) # 加权错误率 if weightedError < minError: # 记录误差最小的决策树桩 minError = weightedError bestClassEst = predictedVals bestStump['dim'] = i bestStump['thresh'] = threshVal bestStump['ineq'] = inequal return bestStump, minError, bestClassEst

由于数据集的特征是连续的,因此程序中通过区间化的方式对特征值做了离散化处理。通过遍历数据集每个特征属性的不同分割阈值,得到最好的决策树桩分类器,最后返回的参数中bestStump为存储决策树桩的字典,包括了用来分类的特征索引、分割阈值和分割方式(lt or gt)、决策树桩的分类误差、分类结果向量。

Adaboost分类器的构建过程如下,过程和前面图中的伪代码基本一致。

def adaBoostTrainDS(X, y, numIt=40): ''' :param X: 训练集的特征 :param y: 训练集的类别标签 :param numIt: 最大迭代次数,且每次迭代会生成一个弱分类器 :return: 弱分类器序列 ''' weakClassArr = [] m = X.shape[0] # 训练样本数目 D = np.ones(m) / m # 初始每个样本权重相同 aggClassEst = np.zeros(m) # 样本的类别估计值 for i in range(numIt): # 训练一个决策树桩分类器 bestStump, error, classEst = buildStump(X, y, D) # print("样本权重: ", D.T) alpha = 0.5 * np.log((1 - error) / max(error, 1e-16)) # 根据误差为当前分类器分配权重 bestStump['alpha'] = alpha weakClassArr.append(bestStump) # 当前的决策树桩加入弱分类器列表 # print("样本类别估计值:", classEst) D *= np.exp(-1 * alpha * y * classEst) # 更新每个样本的权重 D /= np.sum(D) aggClassEst += alpha * classEst # 当前集成分类器的分类结果 # print("分类结果:", aggClassEst) errorRate = np.dot(np.sign(aggClassEst) != y, np.ones(m)) / m print("累加错误率:%f" % errorRate) if abs(errorRate - 0.0) < 1e-10: break return weakClassArr

函数返回了所有的决策树桩分类器以及其对应的权重,也就是最终的分类器。接下来,对于需要预测的样本,将其传入分类器,即可根据各个决策树桩的分类结果的线性组合得到最终的分类结果。预测函数的代码如下:

def adaClassify(X, classifierArr): ''' 分类函数 :param X: 待分类样本 :param classifierArr: 弱分类器列表 :return: 分类结果 ''' m = X.shape[0] aggClassEst = np.zeros(m) for i in range(len(classifierArr)): classEst = stumpClassify(X, classifierArr[i]['dim'], classifierArr[i]['thresh'], classifierArr[i]['ineq']) aggClassEst += classifierArr[i]['alpha'] * classEst return np.sign(aggClassEst)

数据集及完整的实现代码可以从我的github下载。

总结

  从偏差和方差的角度理解,Adaboost算法主要侧重于降低偏差,其每个基分类器的泛化性能较弱,但通过集成最终可以构建出性能很好的分类器。   与boosting很相似的另一种集成方法是bagging算法,随机森林就是其中一个典型的例子。后面再慢慢总结。

参考资料

《机器学习》(周志华) 《机器学习实战》(Peter Harrington)

最新回复(0)