集成學習 Adaboost

提升方法

現在我們擁有的數據集和算法已經給出,如果用bagging方法從數據集中進行採樣訓練,則我們得到的模型仍然無法擺脫數據集分佈固有的限制,即泛化誤差的上界。如果要擺脫它,我們需要人爲的改變數據分佈;一種方法是爲每個樣本點加權,如果一個樣本點在之前的學習中被錯分,則下次學習中我們會更加照顧這些錯分的樣本。這樣生成的學習器將更有可能把這些錯誤糾正過來。
如何確定這個權重和每個學習器的投票重要度呢?這就是adaboost要解決的問題。

二分類Adaboost推導

算法的基礎是基學習器,以及一個二分類數據集。我們要通過提升得到的集成模型是一個多基學習器線性加權投票委員會。f(x)=sign[iMαiGi(x)]f(x) = sign[\sum_i^M \alpha_i G_i(x)]
Adaboost裏使用指數的損失函數套在上面的G(x)上,寫爲L(f(x),y)=exp(yf(x))L(f(x),y)=exp(-yf(x)),我們希望在生成第m個新的基學習器時,把上面的指數損失最小化。
argminα,GL(α,G)i=1Nexp(yi(fm1(xi)+αG(xi))) argmin_{\alpha,G}\quad L(\alpha,G)\sum_{i=1}^{N} exp(-y_i (f_{m-1}(x_i)+\alpha G(x_i)))
從式子中提取出wmi=exp(yi(fm1(xi)))w_{mi} = exp(-y_i(f_{m-1}(x_i))),它不參與優化,則上式變爲argminα,Gi=1Nwmiexp(yiαG(xi))argmin_{\alpha,G}\quad \sum_{i=1}^{N} w_{mi} exp(-y_i\alpha G(x_i))。因爲G是分類器,輸出是1或-1,上式又可以進一步簡化。
i=1Nwmiexp(yiαG(xi))=yi=G(xi)wmiexp(α)+yiG(xi)wmiexp(α)\sum_{i=1}^{N} w_{mi} exp(-y_i\alpha G(x_i)) = \sum_{y_i=G(x_i)}w_{mi} exp(-\alpha)+\sum_{y_i \neq G(x_i)}w_{mi} exp(\alpha)
對這個東西求個常微分等於0就得到我們需要的,現在生成的第m個學習器的權重。
dLdα=I(yG)exp(α)I(y=G)exp(α)=0 \frac{dL} {d\alpha}=I(y\neq G)exp(\alpha)-I(y = G)exp(-\alpha) = 0
α=12log1emem \alpha = \frac{1}{2}log\frac{1-e_m}{e_m}
其中em是模型的錯誤率。到這裏我們解出了單步的alpha取值,它能讓我們加入這個新的基學習器G後,loss最小化。
爲了讓模型學到更多,我們調整樣本的權重分佈。讓本次被錯分的樣本乘以係數$exp(2\alpha) $來修正新的模型,這個係數也是推導得到的。我們把loss中G(x)的一項做泰勒展開,再做一個期望最大化就能得到。不過因爲這步操作的意義比較明顯,我們就不敲公式了。

算法

1、初始化樣本權值分佈,一般初始化爲均勻分佈。
2、迭代M次:
\qquad每次在當前的權重確定的數據分佈D上生成一個基學習器。
\qquad計算誤差率e,賦值係數α=12log1emem\alpha = \frac{1}{2}log\frac{1-e_m}{e_m},爲單個基學習器的權重。
\qquad調整樣本分佈,wiwiexp((1 if y=G(x) else 1)α)w_i \leftarrow w_i exp(-(1\ if\ y=G(x)\ else\ -1)\alpha)
\qquad歸一化樣本分佈
3、輸出最終模型
f(x)=sign[iMαiGi(x)] f(x) = sign[\sum_i^M \alpha_i G_i(x)]

實戰

Adaboost最著名的子產物是非常強大的梯度上升樹GBDT和Xgboost。梯度上升樹使用殘差逼近的思想,提升地訓練多個迴歸樹CART組成強學習器。Xgboost在今天的深度學習時代依然扮演者重要角色。
我們實作就不做Xgboost了,我們做一個西瓜書上的例子,使用決策樹樁作爲基學習器,訓練非線性決策平面的強學習器。

import numpy as np
import matplotlib.pyplot as plt
import matplotlib
import pandas as pd
import random
from sklearn.metrics import accuracy_score
from sklearn.metrics import roc_auc_score
from sklearn.model_selection import train_test_split
from sklearn import datasets
dataset = pd.read_csv("watermelon.csv", encoding = "gb18030")

y=dataset['好瓜']
X=dataset.drop('好瓜',axis=1)
X=X.drop('編號',axis=1)
# 轉numpy
X ,y = X.values,y.values
n,m = X.shape
from sklearn.preprocessing import LabelEncoder
labelencoder=LabelEncoder()
y=labelencoder.fit_transform(y)
X = X[:,[6,7]]
y = 2*y-1

模型與訓練
我們使用X的後兩個維度連續特徵訓練決策樹樁,因此我們需要先設計能在連續特徵上運行的學習器。因爲決策樹樁對樣本進行簡單的二分類,而且本例子裏樣本平衡,所以直接用加權的準確率分數來評估如何劃分即可。

from math import log
from collections import Counter

class stump:
    def __init__(self):
        '''
        只進行單次簡單劃分,劃分維度是clue
        劃分中值是mid,小於mid的類別爲type
        '''
        self.clue = None
        self.mid = None
        self.type = None
        
    def fit(self, X, y, weights):
        '''
        遍歷所有維度的所有中值點即可
        計算信息熵時要考慮權重的影響
        '''
        n,m = X.shape
        best_score = -float("inf")
        
        for d in range(m):
            scale = X[:,d].copy()
            scale.sort()
            for i in range(n-1):
                mid = 0.5*(scale[i]+scale[i+1])
                
                y_les = y[np.where(X[:,d]<=mid)]
                y_lag = y[np.where(X[:,d]>mid)]
                type_ = np.sign(y_les.sum()+random.random())
                y_pred = type_*((X[:,d]<=mid).astype(np.int)*2-1)
                score = weights[y==y_pred].sum()-weights[y!=y_pred].sum()
                
                if score>best_score:
                    best_score = score
                    self.clue = d
                    self.mid = mid
                    self.type = type_
        
    def predict(self,X):
        return self.type*((X[:,self.clue]<=self.mid).astype(np.int)*2-1)
model = stump()
weights = np.ones(len(X))/len(X)
model.fit(X,y,weights)
accuracy_score(model.predict(X),y)
0.8235294117647058
class MixtureStump:
    def __init__(self):
        self.models = []
        self.alphas = []
        
    def fit(self,X,y,M=10):
        n = len(X)
        weights = np.ones(n)
        weights /= weights.sum() # 初始化樣本權重
        self.models.clear()
        self.alphas.clear()

        for m in range(M):
            model = stump()
            model.fit(X,y,weights)
            self.models.append(model)
            y_pred = model.predict(X)
            e = 1-accuracy_score(y_pred,y)
            if e>0.5:
                break
            alpha = np.log((1-e)/e)
            self.alphas.append(alpha)
            C = np.ones(n)
            C[y_pred==y] *= np.exp(-alpha)
            C[y_pred!=y] *= np.exp(alpha)
            weights *= C
            
    def predict(self,X):
        y_pred = sum(model.predict(X) for model in self.models)
        return np.sign(y_pred+random.random()*0.001)
def decision_plain(model, low1, upp1, low2, upp2):
    x_axis = np.linspace(low1,upp1,100)
    y_axis = np.linspace(low2,upp2,100)
    xx, yy = np.meshgrid(x_axis, y_axis)
    Z = np.zeros((100,100))
    for i in range(100):
        for j in range(100):
            Z[i][j] = model.predict(np.array([[xx[i][j],yy[i][j]]]))
    plt.figure(figsize=(8,6), dpi=80)
    colors = ['aquamarine','palegoldenrod']
    plt.contourf(xx, yy, Z, cmap=matplotlib.colors.ListedColormap(colors))
for num in (1,3,7,10):

    mix_model = MixtureStump()
    mix_model.fit(X,y,num)
    
    decision_plain(mix_model,0,1,0,0.6)
    plt.title("Adaboost %d decision stumps"%num)
    plt.scatter(X[y==1,0],X[y==1,1],c= 'black',marker='+')
    plt.scatter(X[y!=1,0],X[y!=1,1],c= 'black',marker='_')

在這裏插入圖片描述

在這裏插入圖片描述

在這裏插入圖片描述

在這裏插入圖片描述

混合專家

從上面的例子我們可以看見adaboost是怎樣通過加權來提升模型性能,但是adaboost仍然有一些侷限性。從上面的圖中我們發現,一些原本可以正確分類的樣本,在adaboost的發展過程中逐漸變得沒法正確劃分。這其實是有些"因小失大"的嫌疑,adaboost有時會把某些樣本看得太重要而沒法兼顧大局。一種很好的想法是使用"局部性"來限制模型,每個模型有自己掌管的數據區域,這個想法是通過混合帶有門函數,而不是隻帶常數權重的基學習器實現的。
p(tx)=k=1Kπk(x)pk(tx) p(t|x) = \sum_{k=1}^K \pi_k(x)p_k(t|x)
其中pi是門函數,每個分量密度,也就是一個基學習器判別器。這樣就可以對輸入空間的不同區域進行建模,門函數可以是硬性的,也可以是柔和的。門函數的權重和區域由參數控制,而且需要滿足概率的歸一化條件,所以參數一般用EM算法調節(你可能會發現,GMM也是一種無監督的混合專家模型)。混合專家模型在某些特殊的問題上,會有着比adaboost還要更好的表現。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章