ML16-集成學(xué)習(xí)算法

集成學(xué)習(xí)算法應(yīng)用比較廣泛,只要包含Bagging,Boosting與Stacking三大類方法窖认,這是本文的主要內(nèi)容:
??1. 集成算法的主要思想;
??2. 集成算法的Bagging告希,Boosting與Stacking方法的數(shù)學(xué)模型與推導(dǎo)扑浸;
??3. 集成算法的實(shí)現(xiàn)與應(yīng)用;
??4. XGBoost算法介紹燕偶;


??到目前為止機(jī)器學(xué)習(xí)入門告一個(gè)結(jié)束喝噪;后面繼續(xù)寫框架擴(kuò)展部分并深入補(bǔ)充部分優(yōu)化算法;并使用C++實(shí)現(xiàn)一個(gè)系列指么,包含OpenCV與Tensorflow酝惧;


集成學(xué)習(xí)算法說明

集成學(xué)習(xí)介紹

  • 集成學(xué)習(xí)(ensemble learning)本身不是一個(gè)單獨(dú)的機(jī)器學(xué)習(xí)算法,而是通過構(gòu)建并結(jié)合多個(gè)機(jī)器學(xué)習(xí)器來完成學(xué)習(xí)任務(wù)伯诬。

  • 集成學(xué)習(xí)可以用于:

    • 分類問題集成
    • 回歸問題集成
    • 特征選取集成
    • 異常點(diǎn)檢測(cè)集成
  • 怎么通過多個(gè)機(jī)器學(xué)習(xí)的結(jié)果得到一個(gè)最終的學(xué)習(xí)結(jié)果晚唇,這是集成學(xué)習(xí)算法的關(guān)鍵,這需要組織不同機(jī)器學(xué)習(xí)的策略盗似。一般從兩個(gè)角度出發(fā):

    • 數(shù)據(jù)集
    • 算法
  • 集成學(xué)習(xí)思想示意圖
  • 名詞說明:

    • 個(gè)體學(xué)習(xí)器哩陕,也稱:基學(xué)習(xí)器
    • 在基學(xué)習(xí)器上形成的學(xué)習(xí)器稱為元學(xué)習(xí)器。(下面會(huì)有詳細(xì)的介紹)
  • 集成學(xué)習(xí)的核心:

    • 個(gè)體機(jī)器學(xué)習(xí)器
    • 結(jié)合策略

其他集成學(xué)習(xí)的說明

  • 集成學(xué)習(xí)算法的有效性:

    • 比如用線性函數(shù)去擬合曲線,效果當(dāng)然不會(huì)很好萌踱;
    • 如果把數(shù)據(jù)集拆分開葵礼,使用多個(gè)線性回歸集成,這些線性回歸組合而成的折線其實(shí)也算集成學(xué)習(xí)并鸵。
  • 通過集成學(xué)習(xí)鸳粉,可以提高算法的泛化能力,所謂泛化能力就是通用性园担、廣泛性届谈、穩(wěn)定性更強(qiáng)。

  • 集成學(xué)習(xí)集成不同算法的條件:

    • 需要選擇有差異性的機(jī)器學(xué)習(xí)算法弯汰; 比如沒有差異的分類器艰山,集成起來的分類效果也不會(huì)有什么變化。
    • 集成的學(xué)習(xí)算法的準(zhǔn)確率需要50%以上咏闪;比如分類器的準(zhǔn)確率小于0.5曙搬,隨著集成規(guī)模的增加,分類準(zhǔn)確率會(huì)不斷下降鸽嫂;如果準(zhǔn)確度大于0.5纵装,那么最終分類準(zhǔn)曲率可以趨近于1。

個(gè)體學(xué)習(xí)器構(gòu)建

個(gè)體學(xué)習(xí)器構(gòu)建方式

  1. 算法不同:比如對(duì)相同數(shù)據(jù)集据某,使用貝葉斯分類橡娄,SVM分類算法,訓(xùn)練出多個(gè)基學(xué)習(xí)器癣籽。
  2. 算法相同:比如對(duì)不同的數(shù)據(jù)集挽唉,使用決策樹訓(xùn)練出多個(gè)決策樹基學(xué)習(xí)器。
    • 訓(xùn)練集不同筷狼;
    • 參數(shù)不同

個(gè)體學(xué)習(xí)構(gòu)建算法

  • 常見的基學(xué)習(xí)器的構(gòu)建算法有:
    1. boosting:用于減少偏差的boosting
      • AdaBoost算法
      • GradientBoosting算法
      • HistGradientBoosting算法
      • XGBoost
    2. Bagging:用于減少方差
      • 隨機(jī)森林
    3. Stacking:用于提升預(yù)測(cè)結(jié)果

個(gè)體學(xué)習(xí)器結(jié)合策略

  • 把多個(gè)基學(xué)習(xí)器(弱學(xué)習(xí)器)的按照不同的結(jié)合策略瓶籽,形成強(qiáng)學(xué)習(xí)器,常見結(jié)合策略有兩種:
    • Averaging 平均法
      • 一般在回歸問題中采用
    • Voting 投票法
      • 一般在分類問題中采用

Averaging 平均法結(jié)合策略

  • 假設(shè)基學(xué)習(xí)器數(shù)量是n個(gè)埂材,每個(gè)基學(xué)習(xí)器的預(yù)測(cè)輸出是:

    • p_i,\qquad i \in [1,2,\dots,n]
  • 最終平均值輸出是:

    • p = \dfrac{1}{n} \sum \limits _{i=1} ^ n p_i
  • 如果對(duì)基學(xué)習(xí)器考慮加權(quán),每個(gè)基學(xué)習(xí)器的加權(quán)為w_i,\qquad i \in [1,2,\dots, n]塑顺,則加權(quán)輸出為:

    • p = \dfrac{1}{n} \sum \limits _{i=1} ^ n w_i p_i

Voting 投票法結(jié)合策略

  • 假設(shè)我們的預(yù)測(cè)類別是[c_1,c_2,...c_k],對(duì)于任意一個(gè)預(yù)測(cè)樣本x楞遏,我們的n個(gè)弱學(xué)習(xí)器的預(yù)測(cè)結(jié)果分別是:

    • (p_1(x), p_2(x), \dots, p_n(x))
  • 投票法是相對(duì)多數(shù)投票法茬暇,也就是少數(shù)服從多數(shù):

    • 也就是n個(gè)弱學(xué)習(xí)器的對(duì)樣本x的預(yù)測(cè)結(jié)果中首昔,數(shù)量最多的類別c_i為最終的分類類別寡喝。
    • 如果不止一個(gè)類別獲得最高票,則隨機(jī)選擇一個(gè)做最終類別勒奇。
  • 投票方式:
    • 硬投票(hard):基于分類標(biāo)簽投票
    • 軟投票(soft):基于分類標(biāo)簽概率投票

sklearn集成學(xué)習(xí)應(yīng)用體驗(yàn)

import sklearn.datasets as ds
from sklearn.model_selection import train_test_split
# HistGradientBoostingClassifier 分類器不存在
from sklearn.ensemble import AdaBoostClassifier, GradientBoostingClassifier
from sklearn.ensemble import BaggingClassifier, ExtraTreesClassifier,RandomForestClassifier, IsolationForest
from sklearn.ensemble import VotingClassifier

# 加載數(shù)據(jù)集
data,target = ds.load_iris(return_X_y=True)  

# 切分?jǐn)?shù)據(jù)集
data_train, data_test, target_train, target_test = train_test_split(
    data,    # 數(shù)據(jù)集
    target,    # 數(shù)據(jù)集標(biāo)簽
    test_size=0.2)  

Boosting 算法集成學(xué)習(xí)算法

AdaBoostClassifier


classifier = AdaBoostClassifier(
    base_estimator=None, 
    n_estimators=100, 
    learning_rate=0.01, 
    algorithm='SAMME.R',
    random_state=None)

classifier.fit(data_train, target_train)
pre = classifier.predict(data_test)
correct_num = (pre == target_test).sum()
print(F'測(cè)試樣本總數(shù): {len(target_test)}预鬓,分類正確數(shù):{correct_num}' )
測(cè)試樣本總數(shù): 30,分類正確數(shù):29

GradientBoostingClassifier

classifier = GradientBoostingClassifier(
    loss='deviance',   # 損失度量方式:默認(rèn)偏差
    learning_rate=0.1,    # 學(xué)習(xí)率
    n_estimators=100,    # 基學(xué)習(xí)器
    subsample=1.0, 
    criterion='friedman_mse',   
    min_samples_split=2,   # 決策樹的特性
    min_samples_leaf=1, 
    min_weight_fraction_leaf=0.0, 
    max_depth=3, 
    min_impurity_decrease=0.0, 
    min_impurity_split=None, 
    init=None, 
    random_state=None, 
    max_features=None, 
    verbose=0, 
    max_leaf_nodes=None, 
    warm_start=False, 
    # presort='auto', 
    # validation_fraction=0.1, 
    # n_iter_no_change=None, 
    # tol=0.0001    # 結(jié)束誤差
)
classifier.fit(data_train, target_train)
pre = classifier.predict(data_test)
correct_num = (pre == target_test).sum()
print(F'測(cè)試樣本總數(shù): {len(target_test)},分類正確數(shù):{correct_num}' )
測(cè)試樣本總數(shù): 30格二,分類正確數(shù):29

Bagging 與隨機(jī)森林集成學(xué)習(xí)算法

BaggingClassifier


classifier = BaggingClassifier(
    base_estimator=None, 
    n_estimators=50, 
    max_samples=1.0, 
    max_features=1.0, 
    bootstrap=True, 
    bootstrap_features=False, 
    oob_score=False, 
    warm_start=False, 
    n_jobs=-1,    # 使用None 報(bào)異常
    random_state=None, 
    verbose=0)
classifier.fit(data_train, target_train)
pre = classifier.predict(data_test)
correct_num = (pre == target_test).sum()
print(F'測(cè)試樣本總數(shù): {len(target_test)}劈彪,分類正確數(shù):{correct_num}' )
測(cè)試樣本總數(shù): 30,分類正確數(shù):29

RandomForestClassifier

classifier =RandomForestClassifier(
    n_estimators= 100,   # 森林?jǐn)?shù)量 
    criterion='gini', 
    max_depth=None, 
    min_samples_split=2, 
    min_samples_leaf=1, 
    min_weight_fraction_leaf=0.0, 
    max_features='auto', 
    max_leaf_nodes=None, 
    min_impurity_decrease=0.0, 
    min_impurity_split=None, 
    bootstrap=True, 
    oob_score=False, 
    n_jobs= -1,   # 指定數(shù)量顶猜,與默認(rèn)值的區(qū)別 
    random_state=None, 
    verbose=0, 
    warm_start=False, 
    class_weight=None)

classifier.fit(data_train, target_train)
pre = classifier.predict(data_test)
correct_num = (pre == target_test).sum()
print(F'測(cè)試樣本總數(shù): {len(target_test)}沧奴,分類正確數(shù):{correct_num}' )
測(cè)試樣本總數(shù): 30,分類正確數(shù):29

ExtraTreesClassifier 極限樹

classifier = ExtraTreesClassifier(
    n_estimators=100, 
    criterion='gini', 
    max_depth=None, 
    min_samples_split=2, 
    min_samples_leaf=1, 
    min_weight_fraction_leaf=0.0, 
    max_features='auto', 
    max_leaf_nodes=None, 
    min_impurity_decrease=0.0, 
    min_impurity_split=None, 
    bootstrap=False, 
    oob_score=False, 
    n_jobs=-1, 
    random_state=None, 
    verbose=0, 
    warm_start=False, 
    class_weight=None)
classifier.fit(data_train, target_train)
pre = classifier.predict(data_test)
correct_num = (pre == target_test).sum()
print(F'測(cè)試樣本總數(shù): {len(target_test)}长窄,分類正確數(shù):{correct_num}' )
測(cè)試樣本總數(shù): 30滔吠,分類正確數(shù):29

IsolationForest 離群點(diǎn)檢測(cè)

outlier=  IsolationForest(
     n_estimators=100, 
     max_samples='auto', 
     contamination=0.1, 
     max_features=1.0, 
     bootstrap=False, 
     # n_jobs=-1, 
     # behaviour='old', 
     # random_state=None, 
     # verbose=0, 
     # warm_start=False
)

outlier.fit(data_train)
y_pred_train = outlier.predict(data_train)
y_pred_test = outlier.predict(data_test)    # 返回 1(正常inlier ),-1(異常outlier)
y_pred_test
array([-1, -1, -1,  1,  1,  1,  1,  1,  1,  1,  1,  1, -1,  1,  1,  1,  1,
       -1,  1,  1,  1,  1, -1,  1, -1,  1,  1,  1,  1,  1])

其他集成學(xué)習(xí)算法

VotingClassifier投票分類器

# import warnings
# warnings.filterwarnings(module='sklearn*', action='ignore', category=DeprecationWarning)

from sklearn.linear_model import LogisticRegression
from sklearn.svm import SVC
from sklearn.ensemble import RandomForestClassifier
import sklearn.datasets as ds
from sklearn.model_selection import train_test_split
from sklearn.ensemble import VotingClassifier


# 加載數(shù)據(jù)集
data,target = ds.load_iris(return_X_y=True)  
# data = data[0:100]
# target = target[0:100]
# 切分?jǐn)?shù)據(jù)集
data_train, data_test, target_train, target_test = train_test_split(
    data,    # 數(shù)據(jù)集
    target,    # 數(shù)據(jù)集標(biāo)簽
    test_size=0.2)  
c1 =RandomForestClassifier(n_estimators= 50,  criterion='gini')
c2 = LogisticRegression()
c3 = SVC(C=1.0, kernel='rbf')

classifier = VotingClassifier(
    estimators=[        # 參數(shù)是元組列表
        ('c1', c1),
        ('c2', c2),
        ('c3', c3)], 
    voting='hard', 
    weights=None, 
    n_jobs=-1, 
    flatten_transform=True)

classifier.fit(data_train, target_train)
pre = classifier.predict(data_test)
correct_num = (pre == target_test).sum()
print(F'測(cè)試樣本總數(shù): {len(target_test)}挠日,分類正確數(shù):{correct_num}' )

測(cè)試樣本總數(shù): 30疮绷,分類正確數(shù):30

sklearn與bumpy版本不兼容的問題

  • 因?yàn)閟klearn默認(rèn)調(diào)用當(dāng)前python環(huán)境的numpy模塊,如果sklearn與numpy版本不匹配嚣潜,會(huì)導(dǎo)致出現(xiàn)一些警告。

  • 一般版本不兼容,都是因?yàn)閚umpy版本更新速度超過sklearn的更新速度導(dǎo)致慌洪,一般官網(wǎng)提示都是最低版本株憾,沒有對(duì)最高版本進(jìn)行說明,所以容易出現(xiàn)問題犯犁。下面是官網(wǎng)對(duì)版本的要求說明:

    • 我當(dāng)前sklearn版本:0.19.2属愤,匹配的版本是1.13.3(當(dāng)前最新版本是:1.16.3)
    • 版本匹配
  • 更新sklearn最新版本:

    • pip install -U scikit-learn
  • 提示

    • 上面代碼可能報(bào)警告如下:

      • numpy的不兼容問題
    • 上面的警告問題是numpy自身的問題,改問題已經(jīng)被修正酸役,但沒有體現(xiàn)在最新的版本中住诸。

  • sklearn最新版本下載安裝:

  • 關(guān)閉警告

import warnings
warnings.filterwarnings(module='sklearn*', action='ignore', category=DeprecationWarning)

  • 降低numpy的版本也可以解決這個(gè)問題,可能不同的sklearn版本匹配不同的numpy版本涣澡。
    • pip install numpy==1.13.3

Bagging集成

Bagging算法思想

  • Bagging是Bootstrap Aggregating(自助結(jié)合)的簡寫

  • Bagging是通過組合隨機(jī)生成的訓(xùn)練集而改進(jìn)分類的集成算法贱呐。

    1. Bagging把訓(xùn)練數(shù)據(jù)集采用放回抽樣,產(chǎn)生n個(gè)訓(xùn)練集(是原始數(shù)據(jù)集的子集)入桂,使用自定的分類器算法對(duì)n個(gè)子集進(jìn)行訓(xùn)練而得到n個(gè)分類器(基分類器)奄薇;
    2. 對(duì)待分類樣本進(jìn)行分類是,調(diào)用n個(gè)基學(xué)習(xí)器抗愁,得到n個(gè)分類結(jié)果馁蒂,然后采用投票結(jié)合策略,得到的分類結(jié)果作為最終的分類結(jié)果蜘腌。
  • Baggind算法的特點(diǎn):

    • 數(shù)據(jù)集不同(對(duì)已知數(shù)據(jù)集隨機(jī)抽樣產(chǎn)生多個(gè)不同的數(shù)據(jù)集)
    • 算法相同
  • Bagging算法的關(guān)鍵點(diǎn)

    • Bagging算法的關(guān)鍵在抽樣的方式沫屡,一般采用的抽樣方法是:自助采樣法(Bootstrap Sampling)

Bootstrap采樣

  • Bootstrap方法最初由美國斯坦福大學(xué)統(tǒng)計(jì)學(xué)教授Efron在1977年提出。作為一種嶄新的增廣樣本統(tǒng)計(jì)方法撮珠,Bootstrap方法為解決小規(guī)模子樣試驗(yàn)評(píng)估問題提供了很好的思路沮脖。

  • Bootstrap采樣的思想:

    • 假設(shè)原始樣本數(shù)據(jù)集X,樣本容量為n
    • Bootstrap采樣就是在原始數(shù)據(jù)的范圍內(nèi)作有放回的再抽樣, 樣本容量仍為n勺届,原始數(shù)據(jù)中每個(gè)樣本每次被抽到的概率相等, 為\dfrac{1}{n} (這個(gè)在python編程中可以通過先獲取1 \to n之間的均勻隨機(jī)數(shù)作為下標(biāo)得到), 所得樣本稱為Bootstrap樣本驶俊。
  • Bootstrap采樣法解決了小樣本時(shí)主成分分析的置信區(qū)間的計(jì)算方法:

    • 比如鳶尾花的四個(gè)特征(X_1,X_2,X_3,X_4),我們?cè)谧鯬CA主成分分析的時(shí)候免姿,通過協(xié)方差矩陣得到特征值與特征向量饼酿,當(dāng)樣本數(shù)據(jù)集產(chǎn)生改變(數(shù)量與順序)的時(shí)候,這個(gè)四個(gè)特征值也會(huì)產(chǎn)生改變胚膊,從而得到不同的結(jié)果嗜湃,這個(gè)結(jié)果可能導(dǎo)致很大的學(xué)習(xí)誤差。
    • 怎么評(píng)估這個(gè)特征值隨著樣本變化澜掩,有多大的可信度购披,Bootstrap方法就解決了這個(gè)問題:
      • 采用n次boostrap抽樣,可以得到n個(gè)樣本集(與原樣本集容量一樣:樣本肯定有重復(fù))肩榕,從而得到n個(gè)特征值(\lambda _1^i, \lambda _2^i, \lambda _3^i, \lambda _4^i, )
      • 采用特征值在某個(gè)分位區(qū)間出現(xiàn)的頻次(記得matplotlib中密度圖與直方圖不刚陡?就是那個(gè)意思),可以得到特征值的置信區(qū)間的計(jì)算方式:值落在在某個(gè)范圍內(nèi)的概率株汉。
      • 最終可以證明Bootstrap采樣的有效性在于:只要采樣的次數(shù)足夠多筐乳,鳶尾花的主成分分析中最大特征值95%的概率落在一個(gè)比較窄的范圍內(nèi),這種主成分就是在樣本放生變化的時(shí)候就是可信的乔妈,特征的穩(wěn)定性在未知的測(cè)試樣本中也是可信的蝙云。
      • Bootstrap采樣次數(shù)越多,其最后分類的結(jié)果置信度越高路召,方差越小勃刨。
  • Bagging算法的每個(gè)基學(xué)習(xí)器之間沒有聯(lián)系,都是采用獨(dú)立抽樣的樣本集訓(xùn)練出來的股淡,我們稱這種方式是并行方式身隐。

  • 隨機(jī)森林是bagging的一個(gè)特例優(yōu)化版本:

    • 算法都采用決策樹。
    • 在樣本隨機(jī)采樣基礎(chǔ)上唯灵,又加上了特征的隨機(jī)選擇贾铝。

Bagging基礎(chǔ)算法的實(shí)現(xiàn)

# 略

Boosting集成

Boosting算法思想

  • Boosting(增長)算法是通過對(duì)數(shù)據(jù)集中樣本采用不同的加權(quán),形成不同數(shù)據(jù)集的方式改進(jìn)分類效果:

    1. 首先從訓(xùn)練集用初始權(quán)重訓(xùn)練出一個(gè)基學(xué)習(xí)器1埠帕;
    2. 根據(jù)基學(xué)習(xí)器1的分類結(jié)果來更新訓(xùn)練樣本的權(quán)重垢揩,分類錯(cuò)誤的樣本認(rèn)為是分類困難樣本,權(quán)重增加敛瓷,反之權(quán)重降低叁巨;改變權(quán)重后得到新的樣本;
    3. 對(duì)新的樣本集琐驴,再訓(xùn)練一個(gè)新的基學(xué)習(xí)器2俘种;
    4. 重復(fù)步驟2,得到n個(gè)基學(xué)習(xí)器绝淡;
    5. 采用結(jié)合策略宙刘,得到最終的分類結(jié)果。
  • Baggind算法的特點(diǎn):

    • 數(shù)據(jù)集不同(對(duì)已知數(shù)據(jù)集采用不同加權(quán)產(chǎn)生多個(gè)不同的數(shù)據(jù)集)
    • 算法相同(可以采用不同的算法)
  • Boosting算法的核心是權(quán)重的更新方式牢酵,因?yàn)闄?quán)重的更新方式悬包,產(chǎn)生不同的Boosting算法的

    • Ada Boosting算法
    • Gradient Boosting算法

AdaBoosting算法

AdaBoosting算法模型

分類與預(yù)測(cè)模型

  • 假設(shè),訓(xùn)練出m個(gè)基學(xué)習(xí)器G_i(x),\quad i \in [1,2,\dots,m]馍乙,則最終強(qiáng)學(xué)習(xí)器輸出模型為:

    • f(x) = \sum \limits _{i=1}^m \alpha_i G_i(x)
  • AdaBoosting算法就是要找出一組\alpha_i, G_i(x)\quad i \in [1,2,\dots,m]使得G(x)輸出的損失最小布近。

損失函數(shù)模型

  • AdaBoosting模型的損失最小的損失函數(shù)定義如下:

    • L(y, f(x)) = e^{-yf(x)}
  • 如果假設(shè)對(duì)所有樣本:X = [ (x_1, y_1), (x_2, y_2), \dots , (x_n, y_n) ],損失函數(shù)最小模型為:

    • L(\alpha, f) = \sum \limits _{i=1}^{n} L(y_i, f(x_i)) = \sum \limits _{i=1}^{n} L(y_i, \sum \limits _{k=1}^m \alpha_k G_k(x_i))
      • 其中: \alpha = [\alpha_1, \alpha_2, \dots, \alpha_m]
  • 最終完整表示損失函數(shù)為

    • L(\alpha, G) = \sum \limits _{i=1}^{n} e^{-y_i \sum \limits _{k=1}^m \alpha_k G_k(x_i)}

損失函數(shù)模型簡化

  • 上面是復(fù)雜的全局優(yōu)化問題丝格,通常我們使用其簡化版撑瞧,即假設(shè)在第k次迭代中,前k-1次的系數(shù)\alpha與基學(xué)習(xí)器G(x)是已知與固定的显蝌,則第k個(gè)基學(xué)習(xí)器是基于前面k-1個(gè)\alpha與基學(xué)習(xí)器G(x)的预伺。

    • f_{k}(x) = f_{k-1}(x) + \alpha_{k}G_{k}(x)
  • 損失最小模型,轉(zhuǎn)換為如下問題求解:

    • 在已知(\alpha_1, \alpha_2, \dots, \alpha_{k-1})與基學(xué)習(xí)器(G_1(x), G_2(x),\dots)下曼尊,求(\alpha_k酬诀,G_k(x))使得下面公式最小:
      • L(\alpha_{k},G_{k}(x)) = \sum\limits_{i=1}^{N}e^{-y_{i}f_{k}(x_{i})} = \sum\limits_{i=1}^{N}e^{-y_{i}(f_{k-1}(x_{i}) + \alpha_k G_k(x_{i}))}

備注:選擇指數(shù)作為損失函數(shù)定義的理由

  • 選擇指數(shù)損失函數(shù)e^{-yf(x)}的理由是:可以通過數(shù)學(xué)推導(dǎo)骆撇,使得最后結(jié)果得到貝葉斯最優(yōu)錯(cuò)誤率:

    • 即對(duì)于每個(gè)樣本x都選擇后驗(yàn)概率最大的類別瞒御。
    • 若指數(shù)損失最小化,則分類錯(cuò)誤率也將最小化神郊。
  • 指數(shù)損失函數(shù)是分類任務(wù)0-1損失函數(shù)的一致性替代函數(shù)肴裙。

    • 由于這個(gè)替代函數(shù)是單調(diào)連續(xù)可微函數(shù),因此用它代替0-1損失函數(shù)作為優(yōu)化目標(biāo)涌乳。
  • 下面算法過程都是由上面模型推導(dǎo)出來的践宴,具體的推導(dǎo)過程,這里省略爷怀。

AdaBoosting算法實(shí)現(xiàn)過程

  • AdaBoosting除了權(quán)重計(jì)算不同阻肩,最后的結(jié)合策略采用的是線性結(jié)合策略(加權(quán)表決),形成最終的強(qiáng)學(xué)習(xí)器运授。

  • 假設(shè)訓(xùn)練集是:

    • X = [ (x_1, y_1), (x_2, y_2), \dots , (x_n, y_n) ]烤惊,
      • 其中n為樣本容量(樣本總數(shù))
      • x_i \in R, \quad i \in [1,2,\dots, n]表示第i個(gè)樣本;
      • y_i \in [-1, 1], \quad i \in [1,2,\dots, n]標(biāo)識(shí)第i個(gè)樣本的分類標(biāo)簽(假設(shè)是二分類吁朦,并且標(biāo)簽標(biāo)準(zhǔn)化為-1與1)柒室;
  1. 初始化樣本的權(quán)重為:

    • (w_{k1}, w_{k2}, \dots, w_{kn}), \quad \text{其中:}w_{ki} = \dfrac{1}{n}, \quad i \in [1,2,\dots,n]
      • n為樣本容量。
      • \text{其中:} k \text{表示第}k \text{個(gè)基學(xué)習(xí)器}逗宜,目前是第1個(gè)雄右。
  2. 使用權(quán)重得到新的數(shù)據(jù)集:

    • X_{new} = [ (w_{k1} x_1, y_1), (w_{k2} x_2, y_2), \dots , (w_{kn} x_n, y_n) ]空骚,
  3. 使用新的數(shù)據(jù)集,訓(xùn)練出基學(xué)習(xí)器:

    • G_k(x) : x \to [-1, 1]
      • \text{其中:} k \text{表示第}k \text{個(gè)基學(xué)習(xí)器}擂仍,目前是第1個(gè)囤屹。
  4. 使用基學(xué)習(xí)器對(duì)樣本進(jìn)行分類,并計(jì)算出樣本分類的錯(cuò)誤率:

    • e_k = \sum \limits _{i=1} ^{n} w_{ki} I(G_k(x_i) \neq y_i),\quad I \text{函數(shù)輸入是True就輸出1逢渔,F(xiàn)alse就輸出0}
      • \text{其中:} k \text{表示第}k \text{個(gè)基學(xué)習(xí)器}肋坚,目前是第1個(gè)。
      • 如果w_{ki} = \dfrac{1}{n}肃廓,則錯(cuò)誤率就是e_k = \dfrac{1}{n} \sum \limits _{i=1} ^{n} I(G_k(x_i) \neq y_i)
  1. 計(jì)算每個(gè)分類器在最終結(jié)合的線性系數(shù):

    • \alpha_k = \dfrac{1}{2} ln \dfrac{1 - e_k}{ e_k}

      • 其中ln表示自然對(duì)數(shù)智厌。
    • 【注意】上面函數(shù)有一個(gè)良好的性質(zhì):

      • e_k越小,\alpha_k越大盲赊,這表示誤差率越小的基學(xué)習(xí)器在最后強(qiáng)學(xué)習(xí)器中的權(quán)重就越大铣鹏。
  2. 計(jì)算規(guī)范化因子

    • z_k = \sum \limits _{i=1} ^ n w_{ki} e^{-\alpha _k y_i G_k(x_i)}
  3. 新的權(quán)值計(jì)算

    • w_{k+1 i} = \dfrac{w_{ki}}{z_k} e^{-\alpha _k y_i G_k(x_i)}

    • 【注意】上面公式具有一個(gè)特點(diǎn):

      • e_k約大,w_{ki}就越大哀蘑,這樣下一個(gè)基學(xué)習(xí)器產(chǎn)生的時(shí)候吝沫,對(duì)應(yīng)樣本就強(qiáng)化(越難分類的樣本,越強(qiáng)化)递礼。
  4. 基學(xué)習(xí)器的結(jié)合策略

    • G(x) = \sum _{i=1} ^ m \alpha_i G_i(x)
      • 其中m表示最終基學(xué)習(xí)器總數(shù)惨险。
  5. 為了便于分類,使用符號(hào)函數(shù)處理下:

    • 因?yàn)樯厦婕僭O(shè)我們的類別輸出是[-1, 1]脊髓,我們的強(qiáng)學(xué)習(xí)器的輸出也表示成同樣的格式辫愉,這個(gè)使用符號(hào)函數(shù)實(shí)現(xiàn):
      • G(x) = sign(\sum _{i=1} ^ m \alpha_i G_i(x))

AdaBoosting的其他改進(jìn)

離散與連續(xù)

  • AdaBoost算法被稱為"Discrete AdaBoost",因?yàn)槠涓鱾€(gè)基學(xué)習(xí)器Gm(x)的輸出為{-1,1}
  • 連續(xù)AdaBoost算法采用的是概率輸出将硝,sklearn中的連續(xù)與離散指定:SAMME.R恭朗,連續(xù)的情況,可以提高訓(xùn)練速度依疼。

學(xué)習(xí)率與過擬合

  • 防止學(xué)的太快產(chǎn)生過擬合痰腮,對(duì)每個(gè)基學(xué)習(xí)器乘以一個(gè)系數(shù)\eta (又稱學(xué)習(xí)率),

    • scikit-learn中由learning rate參數(shù)指定律罢。
  • 一般較小的\eta產(chǎn)生更多的基學(xué)習(xí)器膀值,可以通過設(shè)置基學(xué)習(xí)器數(shù)量來提早終止學(xué)習(xí)。

樣本刪除

  • Weight trimming:
    • 在AdaBoost的每一輪基學(xué)習(xí)器訓(xùn)練過程中误辑,只有小部分樣本的權(quán)重較大沧踏,因而能產(chǎn)生較大的影響,而其他大部分權(quán)重小的樣本則對(duì)訓(xùn)練影響甚微巾钉。
    • Weight trimming的思想是每一輪迭代中刪除那些低權(quán)重的樣本翘狱,只用高權(quán)重樣本進(jìn)行訓(xùn)練。具體是設(shè)定一個(gè)閾值 (比如90%或99%)砰苍,再將所有樣本按權(quán)重排序潦匈,計(jì)算權(quán)重的累積和阱高,累積和大于閾值的權(quán)重 (樣本) 被舍棄,不會(huì)用于訓(xùn)練茬缩。注意每一輪訓(xùn)練完成后所有樣本的權(quán)重依然會(huì)被重新計(jì)算赤惊,這意味著之前被舍棄的樣本在之后的迭代中如果權(quán)重增加,可能會(huì)重新用于訓(xùn)練寒屯。

AdaBoosting算法實(shí)現(xiàn)

import numpy as np
from sklearn import datasets
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import train_test_split
from sklearn.base import clone
from sklearn.ensemble import AdaBoostRegressor
from sklearn.metrics import zero_one_loss
import time


def clock(func):

    def wrapper(*args, **kwargs):
        start_time = time.time()
        func(*args, **kwargs)
        print("training time: ", time.time() - start_time)

    return wrapper


class AdaBoost(object):
    def __init__(self, M, clf, learning_rate=1.0, method="discrete", tol=None, weight_trimming=None):
        self.M = M
        self.clf = clf
        self.learning_rate = learning_rate
        self.method = method
        self.tol = tol
        self.weight_trimming = weight_trimming

    @clock
    def fit(self, X, y):
        if self.tol is not None:
            X, X_val, y, y_val = train_test_split(X, y, random_state=2)

        w = np.array([1 / len(X)] * len(X))
        self.clf_total = []
        self.alpha_total = []
        former_loss = 1
        count = 0
        tol_init = self.tol

        for m in range(self.M):
            classifier = clone(self.clf)
            if self.method == "discrete":
                if m >= 1 and self.weight_trimming is not None:
                    sort_w = np.sort(w)[::-1]
                    cum_sum = np.cumsum(sort_w)
                    percent_w = sort_w[np.where(cum_sum >= self.weight_trimming)][0]   # 0.999
                    w_fit, X_fit, y_fit = w[w >= percent_w], X[w >= percent_w], y[w >= percent_w]
                    y_pred = classifier.fit(X_fit, y_fit, sample_weight=w_fit).predict(X)
                #    if m % 100 == 0:
                #        print("round {}: {}".format(m, len(w_fit)))
                else:
                    y_pred = classifier.fit(X, y, sample_weight=w).predict(X)
                loss = np.zeros(len(X))
                loss[y_pred != y] = 1
                err = np.sum(w * loss)
                alpha = 0.5 * np.log((1 - err) / err) * self.learning_rate
                w = (w * np.exp(-y * alpha * y_pred)) / np.sum(w * np.exp(-y * alpha * y_pred))

                self.alpha_total.append(alpha)
                self.clf_total.append(classifier)

            elif self.method == "real":
                if m >= 1 and self.weight_trimming is not None:
                    sort_w = np.sort(w)[::-1]
                    cum_sum = np.cumsum(sort_w)
                    percent_w = sort_w[np.where(cum_sum >= self.weight_trimming)][0]
                    w_fit, X_fit, y_fit = w[w >= percent_w], X[w >= percent_w], y[w >= percent_w]
                    y_pred = classifier.fit(X_fit, y_fit, sample_weight=w_fit).predict_proba(X)[:, 1]
                else:
                    y_pred = classifier.fit(X, y, sample_weight=w).predict_proba(X)[:, 1]  
                y_pred = np.clip(y_pred, 1e-15, 1 - 1e-15)
                clf = 0.5 * np.log(y_pred / (1 - y_pred)) * self.learning_rate
                w = (w * np.exp(-y * clf)) / np.sum(w * np.exp(-y * clf))

                self.clf_total.append(classifier)

            '''early stopping'''
            if m % 10 == 0 and m > 300 and self.tol is not None:
                if self.method == "discrete":
                    p = np.array([self.alpha_total[m] * self.clf_total[m].predict(X_val) for m in range(m)])
                elif self.method == "real":
                    p = []
                    for m in range(m):
                        ppp = self.clf_total[m].predict_proba(X_val)[:, 1]
                        ppp = np.clip(ppp, 1e-15, 1 - 1e-15)
                        p.append(self.learning_rate * 0.5 * np.log(ppp / (1 - ppp)))
                    p = np.array(p)

                stage_pred = np.sign(p.sum(axis=0))
                # print("round {}".format(m), zero_one_loss(stage_pred,y_val))
                later_loss = zero_one_loss(stage_pred, y_val)
                if later_loss > (former_loss + self.tol):
                    count += 1
                    self.tol = self.tol / 2  
                #    print(self.tol)
                else:
                    count = 0
                    self.tol = tol_init
                if count == 2:
                    self.M = m - 20
                    print("early stopping in round {}, best round is {}, M = {}".format(m, m - 20, self.M))
                    break
                former_loss = later_loss

        return self

    def predict(self, X):
        if self.method == "discrete":
            pred = np.array([self.alpha_total[m] * self.clf_total[m].predict(X) for m in range(self.M)])

        elif self.method == "real":
            pred = []
            for m in range(self.M):
                p = self.clf_total[m].predict_proba(X)[:, 1]
                p = np.clip(p, 1e-15, 1 - 1e-15)
                pred.append(0.5 * np.log(p / (1 - p)))

        # pred = np.array([0.5 * np.log(self.clf_total[m].predict_proba(X)[:,1] / (1-self.clf_total[m].predict_proba(X)[:,1])) for m in range(self.M)])

        return np.sign(np.sum(pred, axis=0))

    def stage_predict(self, X):
        pred = None
        if self.method == "discrete":
            for alpha, clf in zip(self.alpha_total, self.clf_total):
                current_pred = alpha * clf.predict(X)

                if pred is None:
                    pred = current_pred
                else:
                    pred += current_pred

                yield np.sign(pred)

        elif self.method == "real":
            for clf in self.clf_total:
                p = clf.predict_proba(X)[:, 1]
                p = np.clip(p, 1e-15, 1 - 1e-15)
                current_pred = 0.5 * np.log(p / (1 - p))

                if pred is None:
                    pred = current_pred
                else:
                    pred += current_pred

                yield np.sign(pred)


if __name__ == "__main__":
    X, y = datasets.make_hastie_10_2(n_samples=2000, random_state=1)
    X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=1)

    model = AdaBoost(M=450, clf=DecisionTreeClassifier(max_depth=1, random_state=1), learning_rate=1.0, method="real",
                     tol=0.01, weight_trimming=0.999)
    model.fit(X_train, y_train)
    pred = model.predict(X_test)

    acc = np.zeros(pred.shape)
    acc[np.where(pred == y_test)] = 1
    accuracy = np.sum(acc) / len(pred)
    print('score: ', accuracy)  # 0.9656667

    base_model = DecisionTreeClassifier(max_depth=1,random_state=1)
    model = AdaBoostRegressor(n_estimators=450, learning_rate=1.0, base_estimator=base_model)
    model.fit(X_train, y_train)
    pred = model.predict(X_test)
    acc = np.zeros(pred.shape)
    acc[np.where(pred == y_test)] = 1
    accuracy = np.sum(acc) / len(pred)
    print('score_sklearn: ', accuracy)


training time:  1.3744990825653076
score:  0.918
score_sklearn:  0.734

GradientBoosting算法

  • AdaBoost使用的是指數(shù)損失,這個(gè)損失函數(shù)的缺點(diǎn)是對(duì)于異常點(diǎn)非常敏感厂置。因而通常在噪音比較多的數(shù)據(jù)集上表現(xiàn)不佳菩掏。

  • GradientBoosting算法的特點(diǎn)就是在損失函數(shù)上面做了改進(jìn):

    • GradientBoosting可以使用任何損失函數(shù) ,從而可以挑選適合數(shù)據(jù)集的損失函數(shù)來度量最終的分類與預(yù)測(cè)誤差昵济。

GradientBoosting算法模型

  • GradientBoosting算法模型與AdaBoosting的算法模型是一樣的智绸。但有個(gè)很大的區(qū)別是GradientBoosting最終要得到最強(qiáng)學(xué)習(xí)器的方式不一樣:

    • GradientBoosting算法把最終的最強(qiáng)學(xué)習(xí)器看成是一個(gè)最優(yōu)的學(xué)習(xí)器,滿足識(shí)別誤差最小访忿。
    • GradientBoosting算法的過程是函數(shù)梯度下降的算法瞧栗,每次下降的梯度就是一個(gè)學(xué)習(xí)器,為了控制下降速度海铆,提供一個(gè)學(xué)習(xí)率迹恐。
  • 算法的示意圖如下:

    • 梯度提升算法示意圖

分類與預(yù)測(cè)模型

  • 上述示意圖最后的強(qiáng)學(xué)習(xí)器可以表示為:G(x) = \sum \limits _{k=1}^m \eta_k G_k(x)
    • 除第一個(gè)基學(xué)習(xí)器,其他基學(xué)習(xí)器都是使用上一次的殘差作為標(biāo)簽作為訓(xùn)練得到卧斟。

損失函數(shù)模型

  • 由于GradientBoosting算法可以使用任何損失函數(shù)殴边,所以使用通用模式表示:
    • L(f) = \sum\limits_{i=1}^n L(y_i,f(x_i))
      • 其中n表示訓(xùn)練樣本的容量。
      • 其中f(x)表示最終的學(xué)習(xí)器

GradientBoosting算法思想

  • GradientBoosting算法算法的基本思想是梯度下降珍语,基于權(quán)重變量w的梯度下降是:

    • 爭對(duì)損失函數(shù)L(W)是:W_{new} = W_{old} + \eta \nabla
  • GradientBoosting算法中下降的是函數(shù):

    • 爭對(duì)損失函數(shù)L(f)是:f_{new} = f_{old} + \eta \nabla
  • 如果考慮第k次基學(xué)習(xí)器的訓(xùn)練锤岸,公式表示為:

    • f_k = f_{k-1} + \eta \nabla
  • 如果上面\nabla是第k次使用樣本X與上次殘差作為標(biāo)簽訓(xùn)練出來的基學(xué)習(xí)器G_k(x),則第k次最終學(xué)習(xí)器是:

    • f_k(x) = f_{k_1}(x) + \eta G_k(x)
  • 如果集成的基學(xué)習(xí)器是m個(gè),則最終的最強(qiáng)學(xué)習(xí)器是:

    • f_m(x) = f_{m-1}(x) + \eta G_{m-1}(x)
  • 總結(jié):

    • 使用機(jī)器學(xué)習(xí)去擬合每次的識(shí)別誤差板乙,這就是GradientBoosting算法的特點(diǎn)是偷。

GradientBoosting算法過程

  • 假設(shè)訓(xùn)練集是:
    • X = [ (x_1, y_1), (x_2, y_2), \dots , (x_n, y_n) ]
      • 其中n為樣本容量(樣本總數(shù))
      • x_i \in R, \quad i \in [1,2,\dots, n]表示第i個(gè)樣本募逞;
      • y_i \in [-1, 1], \quad i \in [1,2,\dots, n]標(biāo)識(shí)第i個(gè)樣本的分類標(biāo)簽(假設(shè)是二分類晓猛,并且標(biāo)簽標(biāo)準(zhǔn)化為-1與1);
  1. 第一個(gè)基學(xué)習(xí)器

    • 使用指定的算法凡辱,與已知樣本訓(xùn)練出一個(gè)基學(xué)習(xí)器:G_0(x) = f_0(x)
  2. 開始m次基學(xué)習(xí)器的訓(xùn)練戒职,使用k \in [1,2,\dots, m]表示第k

  3. 計(jì)算第k次的殘差:

    • \tilde{y}_i = -\dfrac{\partial L(y_i,f_{m-1}(x_i))}{\partial f_{m-1}(x_i)}, \qquad i = 1,2, \cdots, n

    • 注意:

      • 如果損失函數(shù)是均方差函數(shù)L(f) =(y_i - f(x_i)) ^ 2,殘差是:\tilde{y}_i = y_i - f(x_i)
  4. 使用數(shù)據(jù)集X = [ (x_1, \tilde{y}_1), (x_2, \tilde{y}_2), \dots , (x_n, \tilde{y}_n) ]訓(xùn)練得到基學(xué)習(xí)器G_k(x)

    • 這樣得到的基學(xué)習(xí)器是使得\tilde{y}_i最小透乾。
  5. 直到m步洪燥,完成集成學(xué)習(xí)過程磕秤,得到最強(qiáng)學(xué)習(xí)器:

    • G(x)= f_m(x) = f_{m-1}(x) + \eta G_{m-1}(x)
    • G(x)= G_0(x) + \eta G_1(x)+ \eta G_2(x) + \cdots + \eta G_{m-1}(x)

GradientBoosting算法實(shí)現(xiàn)

  • 備注:
    • 下面算法直接抄襲引用網(wǎng)絡(luò)資源:
      • https://github.com/massquantity/ML-algorithms-implemetation/blob/master/Gradient%20Boosting/GradientBoosting_article.py
import numpy as np
from sklearn.base import clone
from sklearn.tree import DecisionTreeRegressor
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error
from sklearn.ensemble import GradientBoostingRegressor, GradientBoostingClassifier
from sklearn.metrics import zero_one_loss
from sklearn import datasets
from scipy import stats

def SquaredLoss_NegGradient(y_pred, y):
    return y - y_pred

def Huberloss_NegGradient(y_pred, y, alpha):
    diff = y - y_pred
    delta = stats.scoreatpercentile(np.abs(diff), alpha * 100)
    g = np.where(np.abs(diff) > delta, delta * np.sign(diff), diff)
    return g

def logistic(p):
    return 1 / (1 + np.exp(-2 * p))

def LogisticLoss_NegGradient(y_pred, y):
    g = 2 * y / (1 + np.exp(1 + 2 * y * y_pred))  # logistic_loss = log(1+exp(-2*y*y_pred))
    return g

def modified_huber(p):
    return (np.clip(p, -1, 1) + 1) / 2

def Modified_Huber_NegGradient(y_pred, y):
    margin = y * y_pred
    g = np.where(margin >= 1, 0, np.where(margin >= -1, y * 2 * (1-margin), 4 * y))
    # modified_huber_loss = np.where(margin >= -1, max(0, (1-margin)^2), -4 * margin)
    return g


class GradientBoosting(object):
    def __init__(self, M, base_learner, learning_rate=1.0, method="regression", tol=None, subsample=None,
                 loss="square", alpha=0.9):
        self.M = M
        self.base_learner = base_learner
        self.learning_rate = learning_rate
        self.method = method
        self.tol = tol
        self.subsample = subsample
        self.loss = loss
        self.alpha = alpha

    def fit(self, X, y):
        # tol為early_stopping的閾值,如果使用early_stopping捧韵,則從訓(xùn)練集中分出驗(yàn)證集
        if self.tol is not None:
            X, X_val, y, y_val = train_test_split(X, y, random_state=2)
            former_loss = float("inf")
            count = 0
            tol_init = self.tol

        init_learner = self.base_learner
        y_pred = init_learner.fit(X, y).predict(X)   # 初始值
        self.base_learner_total = [init_learner]
        for m in range(self.M):

            if self.subsample is not None:  # subsample
                sample = [np.random.choice(len(X), int(self.subsample * len(X)), replace=False)]
                X_s, y_s, y_pred_s = X[sample], y[sample], y_pred[sample]  
            else:
                X_s, y_s, y_pred_s = X, y, y_pred

            # 計(jì)算負(fù)梯度
            if self.method == "regression":
                if self.loss == "square":
                    response = SquaredLoss_NegGradient(y_pred_s, y_s)
                elif self.loss == "huber":
                    response = Huberloss_NegGradient(y_pred_s, y_s, self.alpha)
            elif self.method == "classification":
                if self.loss == "logistic":
                    response = LogisticLoss_NegGradient(y_pred_s, y_s)
                elif self.loss == "modified_huber":
                    response = Modified_Huber_NegGradient(y_pred_s, y_s)

            base_learner = clone(self.base_learner)
            y_pred += base_learner.fit(X_s, response).predict(X) * self.learning_rate
            self.base_learner_total.append(base_learner)

            '''early stopping'''
            if m % 10 == 0 and m > 300 and self.tol is not None:
                p = np.array([self.base_learner_total[m].predict(X_val) for m in range(1, m+1)])
                p = np.vstack((self.base_learner_total[0].predict(X_val), p))
                stage_pred = np.sum(p, axis=0)
                if self.method == "regression":
                    later_loss = np.sqrt(mean_squared_error(stage_pred, y_val))
                if self.method == "classification":
                    stage_pred = np.where(logistic(stage_pred) >= 0.5, 1, -1)  
                    later_loss = zero_one_loss(stage_pred, y_val)

                if later_loss > (former_loss + self.tol):
                    count += 1
                    self.tol = self.tol / 2  
                    print(self.tol)          
                else:
                    count = 0
                    self.tol = tol_init
                    
                if count == 2:
                    self.M = m - 20
                    print("early stopping in round {}, best round is {}, M = {}".format(m, m - 20, self.M))
                   # print("loss: ", later_loss)
                    break
                former_loss = later_loss

        return self

    def predict(self, X):
        pred = np.array([self.base_learner_total[m].predict(X) * self.learning_rate for m in range(1, self.M + 1)])
        pred = np.vstack((self.base_learner_total[0].predict(X), pred))    # 初始值 + 各基學(xué)習(xí)器
        if self.method == "regression":
            pred_final = np.sum(pred, axis=0)
        elif self.method == "classification":
            if self.loss == "modified_huber":
                p = np.sum(pred, axis=0)
                pred_final = np.where(modified_huber(p) >= 0.5, 1, -1)
            elif self.loss == "logistic":
                p = np.sum(pred, axis=0)
                pred_final = np.where(logistic(p) >= 0.5, 1, -1)
        return pred_final

    def stage_predict(self, X):
        pred = None
        for base_learner in self.base_learner_total:
            current_pred = base_learner.predict(X)

            if pred is None:
                pred = current_pred
            else:
                pred += current_pred * self.learning_rate

            if self.method == "regression":
                yield pred
            elif self.method == "classification":
                if self.loss == "logistic":
                    yield np.where(logistic(pred) >= 0.5, 1, -1)
                elif self.loss == "modified_huber":
                    yield np.where(modified_huber(pred) >= 0.5, 1, -1)


class GBRegression(GradientBoosting):
    def __init__(self, M, base_learner, learning_rate, method="regression", loss="square",tol=None, subsample=None, alpha=0.9):
        super(GBRegression, self).__init__(M=M, base_learner=base_learner, learning_rate=learning_rate, method=method, 
                                            loss=loss, tol=tol, subsample=subsample, alpha=alpha)

class GBClassification(GradientBoosting):
    def __init__(self, M, base_learner, learning_rate, method="classification", loss="logistic", tol=None, subsample=None):
        super(GBClassification, self).__init__(M=M, base_learner=base_learner, learning_rate=learning_rate, method=method,
                                                loss=loss, tol=tol, subsample=subsample)


if __name__ == "__main__":

    X, y = datasets.make_regression(n_samples=20000, n_features=10, n_informative=4, noise=1.1, random_state=1)
    X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=42)
    model = GBRegression(M=1000, base_learner=DecisionTreeRegressor(max_depth=2, random_state=1), learning_rate=0.1,
                         loss="huber")
    model.fit(X_train, y_train)
    pred = model.predict(X_test)
    rmse = np.sqrt(mean_squared_error(y_test, pred))
    print('RMSE: ', rmse)

    X, y = datasets.make_classification(n_samples=20000, n_features=10, n_informative=4, flip_y=0.1, 
                                    n_clusters_per_class=1, n_classes=2, random_state=1)
    y[y==0] = -1
    X_train, X_test, y_train, y_test = train_test_split(X, y)
    model = GBClassification(M=1000, base_learner=DecisionTreeRegressor(max_depth=1, random_state=1), learning_rate=1.0,
                             method="classification", loss="logistic")
    model.fit(X_train, y_train)
    pred = model.predict(X_test)
    acc = np.zeros(pred.shape)
    acc[np.where(pred == y_test)] = 1
    accuracy = np.sum(acc) / len(pred)
    print('accuracy logistic score: ', accuracy)

    model = GBClassification(M=1000, base_learner=DecisionTreeRegressor(max_depth=1, random_state=1), learning_rate=1.0,
                             method="classification", loss="modified_huber")
    model.fit(X_train, y_train)
    pred = model.predict(X_test)
    acc = np.zeros(pred.shape)
    acc[np.where(pred == y_test)] = 1
    accuracy = np.sum(acc) / len(pred)
    print('accuracy modified_huber score: ', accuracy)

RMSE:  8.45962295476
accuracy logistic score:  0.9374
accuracy modified_huber score:  0.9356

關(guān)于GradientBoosting算法的說明

  • Gradient Boosting算法理論上可以選擇多種不同的學(xué)習(xí)算法作為基學(xué)習(xí)器市咆,但實(shí)際使用地最多的無疑是決策樹,這并非偶然再来。

    • 決策樹有很多優(yōu)良的特性:
      • 能靈活處理各種類型的數(shù)據(jù)蒙兰,包括連續(xù)值和離散值;
      • 對(duì)缺失值不敏感芒篷;
      • 不需要做特征標(biāo)準(zhǔn)化/歸一化搜变;
      • 可解釋性好;

    -決策樹致命缺點(diǎn)是不穩(wěn)定针炉,導(dǎo)致容易過擬合挠他,因而很多時(shí)候準(zhǔn)確率不如其他算法。

    • 決策樹是非參數(shù)模型篡帕,這并不意味著其沒有參數(shù)殖侵,而是在訓(xùn)練之前參數(shù)數(shù)量是不確定的,因此完全生長的決策樹有著較大的自由度镰烧,能最大化地?cái)M合訓(xùn)練數(shù)據(jù)拢军。然而單顆決策樹是不穩(wěn)定的,樣本數(shù)相同的訓(xùn)練集產(chǎn)生微小變動(dòng)就能導(dǎo)致最終模型的較大差異怔鳖,即模型的方差大朴沿,泛化性能不好。

    • 集成學(xué)習(xí)的另一代表Bagging可以解決這個(gè)問題败砂。而Bagging的拓展算法 —— 隨機(jī)森林赌渣,通過在樹內(nèi)部結(jié)點(diǎn)的分裂過程中,隨機(jī)選取固定數(shù)量的特征納入分裂的候選項(xiàng)昌犹,這樣就進(jìn)一步降低了單模型之間的相關(guān)性坚芜,總體模型的方差也比Bagging更低。

    • 決策樹和Gradient Boosting結(jié)合誕生了GBDT斜姥,GBDT繼承了決策樹的諸多優(yōu)點(diǎn)鸿竖,同時(shí)也改進(jìn)了其缺點(diǎn)。

      • 由于GBDT采用的樹都是復(fù)雜度低的樹铸敏,所以方差很小缚忧,通過梯度提升的方法集成多個(gè)決策樹,最終能夠很好的解決過擬合的問題杈笔。
      • 然而Boosting共有的缺點(diǎn)為訓(xùn)練是按順序的闪水,難以并行,這樣在大規(guī)模數(shù)據(jù)上可能導(dǎo)致速度過慢蒙具;
      • 近年來XGBoost和LightGBM的出現(xiàn)都極大緩解了這個(gè)問題球榆。

Stacking集成

Stacking算法思路

  • Stacking(有時(shí)候也稱之為stacked generalization)是指訓(xùn)練一個(gè)模型用于組合(combine)其他各個(gè)模型朽肥。

    • 首先訓(xùn)練多個(gè)不同的模型;
    • 然后用訓(xùn)練好的多個(gè)模型的輸出作為為輸入來訓(xùn)練一個(gè)模型持钉,以得到一個(gè)最終的輸出衡招。
  • 我們稱組合的訓(xùn)練模型為元(Meta)學(xué)習(xí)器;

  • 注意:

    • 通常使用單層logistic回歸作為組合模型每强。

Stacking算法過程

  • 假設(shè)訓(xùn)練集是:
    • X = [ (x_1, y_1), (x_2, y_2), \dots , (x_n, y_n) ]始腾,
      • 其中n為樣本容量(樣本總數(shù))
      • x_i \in R, \quad i \in [1,2,\dots, n]表示第i個(gè)樣本;
  1. 使用不同算法空执,訓(xùn)練多個(gè)基學(xué)習(xí)器

    • (G_1(x), G_2(x),\cdots, G_m(x))
      • 其中m表示基學(xué)習(xí)器的個(gè)數(shù)浪箭。
  2. 使用基學(xué)習(xí)器得到新的訓(xùn)練集

    • \bar{X} = \{ (G_1(x_i) , G_2(x_i), \cdots, G_m(x_i) , y_i) : xi \in \{x_1, x_2, \cdots, x_n\} \quad y_i \in {y_1,y_2, \cdots, y_n} \}
  3. 使用\bar{X}作為訓(xùn)練集訓(xùn)練一個(gè)元學(xué)習(xí)器

    • G(x)
  4. G(x)就是最后的強(qiáng)學(xué)習(xí)器

Stacking算法的其他方式-Blend結(jié)合

  • Blend結(jié)合策略:

    • 把訓(xùn)練集分成A,B兩個(gè)人部分:
    • 使用A訓(xùn)練對(duì)個(gè)基學(xué)習(xí)器脆烟;
    • 使用多個(gè)基學(xué)習(xí)器多數(shù)據(jù)集B做預(yù)測(cè)山林;
    • 使用預(yù)測(cè)值作為元學(xué)習(xí)器的訓(xùn)練集房待;
    • 最終訓(xùn)練出元學(xué)習(xí)器邢羔。
  • 上述算法還有很多變形處理,比如使用交叉驗(yàn)證的方式:

    • 使用K折驗(yàn)證桑孩,把數(shù)據(jù)集分成K個(gè)訓(xùn)練方案拜鹤;

    • 使用訓(xùn)練集訓(xùn)練,使用測(cè)試集得到測(cè)試輸出流椒,并使用測(cè)試集輸出作為元學(xué)習(xí)器的訓(xùn)練樣本敏簿,元學(xué)習(xí)器的輸入維數(shù)為基學(xué)習(xí)器個(gè)數(shù),K折中每折預(yù)測(cè)值一共K宣虾,取K個(gè)的平均值作為輸出惯裕。

    • 使用K折驗(yàn)證輸出(K)構(gòu)成的數(shù)據(jù)集訓(xùn)練的元學(xué)習(xí)器就是最終最強(qiáng)學(xué)習(xí)器。

Stacking算法的實(shí)現(xiàn)

import numpy as np
from sklearn.model_selection import KFold


def get_stacking(clf, x_train, y_train, x_test, n_folds=10):
    """
    這個(gè)函數(shù)是stacking的核心绣硝,使用交叉驗(yàn)證的方法得到次級(jí)訓(xùn)練集
    x_train, y_train, x_test 的值應(yīng)該為numpy里面的數(shù)組類型 numpy.ndarray .
    如果輸入為pandas的DataFrame類型則會(huì)把報(bào)錯(cuò)"""
    train_num, test_num = x_train.shape[0], x_test.shape[0]
    second_level_train_set = np.zeros((train_num,))
    second_level_test_set = np.zeros((test_num,))
    test_nfolds_sets = np.zeros((test_num, n_folds))
    kf = KFold(n_splits=n_folds)

    for i,(train_index, test_index) in enumerate(kf.split(x_train)):

        x_tra, y_tra = x_train[train_index], y_train[train_index]
        x_tst, y_tst =  x_train[test_index], y_train[test_index]
        clf.fit(x_tra, y_tra)

        second_level_train_set[test_index] = clf.predict(x_tst)

        test_nfolds_sets[:,i] = clf.predict(x_test)

    second_level_test_set[:] = test_nfolds_sets.mean(axis=1)

    return second_level_train_set, second_level_test_set

 

#我們這里使用5個(gè)分類算法蜻势,為了體現(xiàn)stacking的思想,就不加參數(shù)了
from sklearn.ensemble import (RandomForestClassifier, AdaBoostClassifier,
                              GradientBoostingClassifier, ExtraTreesClassifier)
from sklearn.svm import SVC


rf_model = RandomForestClassifier()
adb_model = AdaBoostClassifier()
gdbc_model = GradientBoostingClassifier()
et_model = ExtraTreesClassifier()
svc_model = SVC()


#在這里我們使用train_test_split來人為的制造一些數(shù)據(jù)
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split


iris = load_iris()
train_x, test_x, train_y, test_y = train_test_split(iris.data, iris.target, test_size=0.2)
train_sets = []
test_sets = []

for clf in [rf_model, adb_model, gdbc_model, et_model, svc_model]:
    train_set, test_set = get_stacking(clf, train_x, train_y, test_x)
    train_sets.append(train_set)
    test_sets.append(test_set)

meta_train = np.concatenate([result_set.reshape(-1,1) for result_set in train_sets], axis=1)
meta_test = np.concatenate([y_test_set.reshape(-1,1) for y_test_set in test_sets], axis=1)

#使用決策樹作為我們的次級(jí)分類器
from sklearn.tree import DecisionTreeClassifier
dt_model = DecisionTreeClassifier()
dt_model.fit(meta_train, train_y)
df_predict = dt_model.predict(meta_test)
print((df_predict==test_y).sum())
29

XGBoost算法

XGBoost算法介紹

  • XGBoost全名叫(eXtreme Gradient Boosting)極限梯度提升

    • 經(jīng)常被用在一些比賽中鹉胖,其效果顯著握玛。
    • 它是大規(guī)模并行boosted tree的工具,它是目前最快最好的開源boosted tree工具包甫菠。
    • XGBoost 所應(yīng)用的算法就是 GBDT(gradient boosting decision tree)的改進(jìn)挠铲,改進(jìn)Boosting算法串行的局限性;
    • 既可以用于分類也可以用于回歸問題中寂诱。
  • XGBoost是以分類回歸樹(CART樹)進(jìn)行組合

XGBoost算法模型

預(yù)測(cè)與分類模型

  • 與其他Boosting算法模型一樣

  • f(x) = \sum \limits _{k=1}^m G_k(x)

    • m是基學(xué)習(xí)器的個(gè)數(shù)拂苹;
    • G_k表示第k個(gè)基學(xué)習(xí)器;

損失函數(shù)模型

  • L(f)= \sum \limits_{i=1} ^ n L(y_, f(x_i)) + \sum \limits _{k=1} ^ m \Omega (f_k)

    • 其中\Omega(f_k)表示樹的復(fù)雜度的函數(shù)痰洒,越小復(fù)雜度越低醋寝,泛化能力越強(qiáng)搞挣;
    • 實(shí)際是GradientBoosting梯度下降算法的改進(jìn),后面增加了一個(gè)樹的復(fù)雜度正則項(xiàng)(懲罰項(xiàng))音羞。
  • 通常正則項(xiàng)(懲罰項(xiàng))可以定義如下:

    • \Omega (f_k) = \gamma T + \dfrac{1}{2} \lambda ||w||_2
      • 其中w是所有葉子節(jié)點(diǎn)輸出構(gòu)成的向量囱桨;
      • 其中T表示葉子節(jié)點(diǎn)的個(gè)數(shù);
      • 其中\gamma表示葉子切分的難度嗅绰;
      • 其中\lambda是正則化系數(shù)舍肠;
  • 如果使用w_j標(biāo)識(shí)每個(gè)節(jié)點(diǎn)的輸出值,則損失函數(shù)可以表示如下:

    • \Omega (f_k) = \gamma T + \dfrac{1}{2} \lambda \sum _{t=1} ^ T {w_t} ^2
  • 由損失函數(shù)推導(dǎo)的算法以及算法過程窘面,這類不深入推導(dǎo)與解釋翠语,我們直接使用第三方的實(shí)現(xiàn)模塊;

XGBoost算法

官方網(wǎng)站介紹

  • 官方地址:

    • https://xgboost.readthedocs.io/en/latest/
  • 項(xiàng)目代碼地址:

    • https://github.com/dmlc/xgboost

安裝

  • 所有安裝的幫助在官網(wǎng)都有說明:

    • 安裝說明
  • 安裝指令

    • pip3 install xgboost
  • 安裝過程:

    • 安裝過程截圖
  • 安裝后的版本:

    • 安裝后的版本顯示截圖
  • 安裝注意:
    • 官方安裝提示需要編譯安裝xgboost共享庫與動(dòng)態(tài)庫财边;
    • 動(dòng)態(tài)庫安裝提示

XGBoost的模塊結(jié)構(gòu)

  • 可以使用多種方式的幫助獲得模塊結(jié)構(gòu)
    • 交互式幫助

XGBoost使用例子

from numpy import loadtxt
from xgboost import XGBClassifier
from sklearn import datasets as ds
from sklearn.model_selection import train_test_split

data,target = ds.load_iris(return_X_y=True)

data_train, data_test, target_train, target_test = train_test_split(data, target, test_size=0.2)

classifier = XGBClassifier()
classifier.fit(data_train, target_train)
pre = classifier.predict(data_test)

(pre==target_test).sum()
28
  • 注意:
    • 其中XGBClassifier類的構(gòu)造器參數(shù)比較多肌括,關(guān)注其中與算法有關(guān)的參數(shù):
% matplotlib inline
from xgboost import plot_importance
plot_importance(classifier)
<matplotlib.axes._subplots.AxesSubplot at 0x113c134e0>
特征選擇可視化
% matplotlib inline
from xgboost import plot_tree,to_graphviz
g = to_graphviz(classifier,num_trees=10)
#存儲(chǔ)決策樹到圖像  
import codecs  
f=codecs.open('xgb_tree.svg', mode='wb')
f.write(g.pipe('svg'));  
f.close()  
# plot_tree 缺少png圖像轉(zhuǎn)換環(huán)境
g
導(dǎo)出XGBoost樹的結(jié)果

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市酣难,隨后出現(xiàn)的幾起案子谍夭,更是在濱河造成了極大的恐慌,老刑警劉巖憨募,帶你破解...
    沈念sama閱讀 211,290評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件紧索,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡菜谣,警方通過查閱死者的電腦和手機(jī)珠漂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來尾膊,“玉大人媳危,你說我怎么就攤上這事「粤玻” “怎么了待笑?”我有些...
    開封第一講書人閱讀 156,872評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長莺债。 經(jīng)常有香客問我滋觉,道長,這世上最難降的妖魔是什么齐邦? 我笑而不...
    開封第一講書人閱讀 56,415評(píng)論 1 283
  • 正文 為了忘掉前任椎侠,我火速辦了婚禮,結(jié)果婚禮上措拇,老公的妹妹穿的比我還像新娘我纪。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評(píng)論 6 385
  • 文/花漫 我一把揭開白布浅悉。 她就那樣靜靜地躺著趟据,像睡著了一般。 火紅的嫁衣襯著肌膚如雪术健。 梳的紋絲不亂的頭發(fā)上汹碱,一...
    開封第一講書人閱讀 49,784評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音荞估,去河邊找鬼咳促。 笑死,一個(gè)胖子當(dāng)著我的面吹牛勘伺,可吹牛的內(nèi)容都是我干的跪腹。 我是一名探鬼主播,決...
    沈念sama閱讀 38,927評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼飞醉,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼冲茸!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起缅帘,我...
    開封第一講書人閱讀 37,691評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤轴术,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后股毫,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體膳音,經(jīng)...
    沈念sama閱讀 44,137評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡召衔,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評(píng)論 2 326
  • 正文 我和宋清朗相戀三年铃诬,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片苍凛。...
    茶點(diǎn)故事閱讀 38,622評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡趣席,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出醇蝴,到底是詐尸還是另有隱情宣肚,我是刑警寧澤,帶...
    沈念sama閱讀 34,289評(píng)論 4 329
  • 正文 年R本政府宣布悠栓,位于F島的核電站霉涨,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏惭适。R本人自食惡果不足惜笙瑟,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望癞志。 院中可真熱鬧往枷,春花似錦、人聲如沸赚爵。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽俯艰。三九已至脊奋,卻和暖如春蚁滋,著一層夾襖步出監(jiān)牢的瞬間璃赡,已是汗流浹背嚼蚀。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來泰國打工导而, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留酌摇,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,316評(píng)論 2 360
  • 正文 我出身青樓嗡载,卻偏偏與公主長得像窑多,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子洼滚,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容

  • 前面的文章已經(jīng)介紹了五種不同的分類器埂息,它們各有優(yōu)缺點(diǎn)。我們可以很自然地將不同的分類器組合起來遥巴,而這種組合結(jié)果則被成...
    nobodyyang閱讀 1,639評(píng)論 0 1
  • About 個(gè)人同時(shí)在簡書和自制個(gè)人博客兩個(gè)地方同時(shí)更新文章千康,有興趣的話可以來我的博客玩呀,一般而言排版會(huì)好不少铲掐。...
    DeamoV閱讀 2,953評(píng)論 0 1
  • 1. 章節(jié)主要內(nèi)容 集成學(xué)習(xí)(ensemble learning)是通過構(gòu)建并結(jié)合多個(gè)分類器來完成學(xué)習(xí)任務(wù)拾弃。 如何...
    閃電隨筆閱讀 2,142評(píng)論 0 8
  • 今天是什么日子 起床:8點(diǎn) 就寢:未知 天氣:晴 心情:未知 紀(jì)念日:無 叫我起床的不是鬧鐘是夢(mèng)想 年度目標(biāo)及關(guān)鍵...
    依夢(mèng)澈何閱讀 197評(píng)論 0 4
  • 你說 郎才女貌 天作之合 你說 有一個(gè)故事 既沒有開頭 又何必結(jié)尾
    木子李小姐閱讀 260評(píng)論 0 1