Python 演算法 Day 13 - Ensemble Learning

Chap.II Machine Learning 机器学习

https://ithelp.ithome.com.tw/upload/images/20210621/20138527JSvpTArTcw.png

https://yourfreetemplates.com/free-machine-learning-diagram/

Part 3. Learning Algorithm 演算法

上一章提到的基础学习(又称弱学习)於多数真实情况下的性能其实并不好。
低维资料时具有较高的标准差,高维资料时则是变异太大导致稳定性不够。
为了降低模型变异及提高准确度,可根据不同的数据,於各阶段使用不同的演算法来训练模型。
最终以不同权重的方式结合每个结果,得到一个更优性能的学习器。

3-2. Ensemble Learning 集成学习

核心理念:透过组合多个弱学习器,从而创建一个强学习器(或称「集成学习」)。
当数据非常复杂,或有多种潜在的假设时,集成学习非常实用。

A. Majority Voting 多数投票法

这是最简单的集成学习。
将资料丢进 n 个「不同的弱学习器」,求出测试资料的预测结果,将结果向量相加。
因为分类只有 0/1,新的结果向量里的值最大为 n,最小为0。
透过投票,至少有过半数以上的学习器预测 1,才判定结果是 1,其余则为 0。

投票方法又分两种:
硬投票:预测结果是所有投票结果最多出现的类。
软投票:预测结果是所有投票结果中「概率和」最大的类。
如:预测某样本的结果为 70%、51%、1%,硬投票会判定为正样本(2 正 1 负),但软投票则会判定为负样本(70%+51%+1% < 30%+49%+99%)。

优点:简单快速,不需调整过多参数。
缺点:学习器种类上限会限制其精确度。
https://ithelp.ithome.com.tw/upload/images/20220104/20138527QFfacPqZnX.png

以鸢尾花为例,且故意拿掉部分 y 与特徵(使模型预测较不准):

from sklearn import datasets
from sklearn.preprocessing import LabelEncoder
from sklearn.model_selection import train_test_split

ds = datasets.load_iris()
X, y = ds.data[50:, [1, 2]], ds.target[50:]
y = LabelEncoder().fit_transform(y)

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.5, random_state=1, stratify=y)

# 三个弱学习器:此处用逻辑斯回归、决策树和 KNN
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.tree import DecisionTreeClassifier
from sklearn.neighbors import KNeighborsClassifier 
from sklearn.model_selection import cross_val_score
from sklearn.ensemble import RandomForestClassifier

lr_PL = make_pipeline(
    StandardScaler(),
    lr = LogisticRegression(penalty='l2', C=0.001, random_state=1)
)
dt_PL = make_pipeline(
    DecisionTreeClassifier(max_depth=1, criterion='entropy', random_state=0)
)
knn_PL = make_pipeline(
    StandardScaler(),
    KNeighborsClassifier(n_neighbors=1, metric='minkowski')
)

all_clf = [lr_PL, dt_PL, knn_PL]

多数投票法:此处使用软投票

from sklearn.ensemble import VotingClassifier
ens = [('LR', lr_PL), ('DT', dt_PL), ('KNN', knn_PL)]
vc = VotingClassifier(ens, voting='soft')

clf_labels = ['Logistic regression', 'Decision tree', 'KNN', 'Voting Classifier']
all_clf = [lr_PL, dt_PL, knn_PL, vc]

# 使用 cross validation 避免过拟合
from sklearn.model_selection import cross_val_score
for clf, label in zip([lr_PL, dt, knn_PL], clf_labels):
    scores = cross_val_score(
        clf, X_train, y_train,
        cv=10,
        scoring='roc_auc'
    )
    print(f"ROC AUC: {scores.mean():0.2f} (+/- {scores.std():0.2f}) [{label}]")
    
>>  ROC AUC: 0.92 (+/- 0.15) [Logistic regression]
    ROC AUC: 0.87 (+/- 0.18) [Decision Tree]
    ROC AUC: 0.85 (+/- 0.13) [KNN]
    ROC AUC: 0.98 (+/- 0.05) [Voting Classifier]

把决策边界作图(代码在补充 1.):
https://ithelp.ithome.com.tw/upload/images/20220105/20138527qhd1Fen7to.png

看起来成效却实比单个学习器还好!

B. Bagging 装袋法

直接投票法的问题-学习器种类有限,仅凭数个演算法,可能无法达到专案需求的精度。
因此,人们发明 Bootstrap AGGregatING,也就是 Bagging 装袋法来解决。

选定一弱学习器。将原样本做「会放回抽样」,生成 n 个与原数据一样大的「自助样本」。
(因此,某些样本点可能在自助样本中出现多次,某些则被忽略)
学习器会将每个自助样本都训练,最终产出 n 个模型。藉投票决定将测试样本分派(预测)到哪个类别。

优点:从样本随机抽样,降低杂讯被重复训练到的机率,进而提升模型稳定性。
缺点:只是将每次的错误率稀释掉,实际模型依旧没有学的更好。
https://ithelp.ithome.com.tw/upload/images/20220104/20138527F2TCFeeB3G.png

以红酒分类为例:

from sklearn.datasets import load_wine
X, y = load_wine(return_X_y=True)

from sklearn.preprocessing import LabelEncoder
from sklearn.model_selection import train_test_split
le = LabelEncoder()
y = le.fit_transform(y)

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=1, stratify=y)

B1. Bagging:此处使用决策树做 Bagging(其实就是随机森林)

from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import BaggingClassifier

tree = DecisionTreeClassifier(criterion='entropy', random_state=1)
bag = BaggingClassifier(
    base_estimator=tree,
    n_estimators=500,
    max_samples=1.0,
    max_features=1.0,
    bootstrap=True,
    bootstrap_features=False,
    n_jobs=1,
    random_state=1
)

超参数:
1. nestimators: 创建 n 个子模型。默认 10。
2. maxsamples: 每个子模型使用 n 个样本数据训练。默认 1.0。
3. maxfeatures: 若是 int,则选取 n 个特徵;若是 float 则选取 n\X.shape[1] 个特徵。默认 1.0。
4. bootstrap: 在随机选取样本时是否将已选样本放回。默认 True。
5. bootstrapfeatures: 在随机选取特徵时是否将已选特徵放回。默认 False。
6. njobs: 用於训练和预测所需要 CPU 的数量(-1 表使用所有空闲 CPU)。

接着比较一下 一般决策树 vs. Bagging 决策树(就是随机森林)

# 1. 决策树
from sklearn.metrics import accuracy_score

tree.fit(X_train, y_train)
y_train_pred = tree.predict(X_train)
y_test_pred = tree.predict(X_test)

tree_train = accuracy_score(y_train, y_train_pred)
tree_test = accuracy_score(y_test, y_test_pred)
print(f'Decision tree train/test accuracies {tree_train:.3f} / {tree_test:.3f}')

# 2. Bagging
bag.fit(X_train, y_train)
y_train_pred = bag.predict(X_train)
y_test_pred = bag.predict(X_test)

bag_train = accuracy_score(y_train, y_train_pred) 
bag_test = accuracy_score(y_test, y_test_pred) 
print(f'Bagging train/test accuracies {bag_train:.3f} / {bag_test:.3f}')

>>  Decision tree train/test accuracies 1.000 / 0.833
    Bagging train/test accuracies 1.000 / 0.917

把决策边界作图(代码在补充 2.):
https://ithelp.ithome.com.tw/upload/images/20220105/20138527uIxtdnHOfk.png

B2. Random Forest 随机森林法

基於决策树演算法上,透过 Bagging 演算法,让多颗树生长且不进行剪枝,
并对这些树的结果进行组合(分类用简单多数投票法,回归则用平均法)。

优点:可以处理分类与回归资料。接受高维度资料。非平衡误差资料时能平衡误差。
缺点:需大量记忆体储存每颗树的资讯。无法针对单一颗树作解释。

from sklearn.datasets import load_boston
ds = datasets.load_breast_cancer()
X=pd.DataFrame(ds.data, columns=ds.feature_names)
y=pd.DataFrame(ds.target, columns=['Cancer'])

from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.3)

from sklearn.ensemble import RandomForestClassifier as RFC
rfc = RFC(n_estimators=100, criterion='gini', max_depth=3, random_state=1)

超参数:
1. nestimators: 要种几棵树。默认 100。
2. criterion: 不纯度方法,'gini' & 'entropy'。默认 gini。
3. maxdepth: 树的分裂层数。若 None,则扩展节点至所有叶 minsamplessplit 样本。默认 None。
4. minsamplessplit: 分裂一个内部节点所需的最小样本数。默认 2。

rfc.fit(X_train, y_train)
rfc.score(X_test, y_test)

>>  0.9415204678362573

C. Boosting 强化法

Boosting 是指能够将弱学习器转化爲强学习器的一系列算法。
概念类似 Bagging,不同的是,它会将前一次学习器「分类错误的资料」的权重提高,以训练下一次。
学习器会学习到上一次「错误分类资料」的特性,进而提升分类结果。

优点:一般来说可得到比 Bagging 更好的结果。
缺点:若原生资料集杂讯太多,易使模型放大对杂讯的判断,导致结果失准。
https://ithelp.ithome.com.tw/upload/images/20220105/20138527cTwBAdoiqV.png

C1. AdaBoost

以红酒分类为例,:

from sklearn.datasets import load_wine
X, y = load_wine(return_X_y=True)

from sklearn.preprocessing import LabelEncoder
from sklearn.model_selection import train_test_split
le = LabelEncoder()
y = le.fit_transform(y)

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=1, stratify=y)

Boosting:此处使用决策树做 Boosting

from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import AdaBoostClassifier

tree = DecisionTreeClassifier(criterion='entropy', max_depth=1, random_state=1)
ada = AdaBoostClassifier(
    base_estimator=tree,
    n_estimators=500, 
    learning_rate=0.01,
    random_state=1
)

超参数:
1. nestimators: 创建 n 个学习器。默认 50。
2. learningrate: 学习速率。默认 1.0。

接着比较一下 一般决策树 vs. Boosting 决策树

from sklearn.metrics import accuracy_score
tree.fit(X_train, y_train)

y_train_pred = tree.predict(X_train)
y_test_pred = tree.predict(X_test)

tree_train = accuracy_score(y_train, y_train_pred)
tree_test = accuracy_score(y_test, y_test_pred)
print(f'Decision tree train/test accuracies {tree_train:.3f}/{tree_test:.3f}')

ada.fit(X_train, y_train)
y_train_pred = ada.predict(X_train)
y_test_pred = ada.predict(X_test)

ada_train = accuracy_score(y_train, y_train_pred) 
ada_test = accuracy_score(y_test, y_test_pred) 
print(f'AdaBoost train/test accuracies {ada_train:.3f}/{ada_test:.3f}')

>>  Decision tree train/test accuracies 0.916/0.875
    AdaBoost train/test accuracies 0.968/0.917

把决策边界作图(代码在补充 3.):
https://ithelp.ithome.com.tw/upload/images/20220105/20138527rmUXLqa66T.png

C2. Gradient Boosting 梯度强化法

其原理与 Ada 类似,差别在前者利用「分类错误资料」权重定位模型的不足,後者则是透过「梯度」。
原理可参考文章 1文章 2 & 文章 3

最有名的莫过於是 XGBoost。
XGBoost 全名 eXtreme Gradient Boosting,是 Kaggle 竞赛中常见的演算法。
它保有 Gradient Boosting 的做法,使後生成树能修正前棵树的错误。
此外,如随机森林,生成每棵树时会随机抽取特徵,使每棵树的生成不会拿全部的特徵参与决策。
故可实现平行处理。并不是说可以平行处理多颗树,而是指它可平行处理特徵选取的计算。
可说是同时结合 Bagging 和 Boosting 的优点。

为抑制模型复杂化,XGboost 在目标函数添加了 Regularization。
模型在训练时为了拟合训练资料,会产生很多高次项的函数,但反而容易被杂讯干扰导致过度拟合。
因此 L1/L2 使损失函数更平滑,抗杂讯干扰能力更大。

最後 XGboost 用到了一 & 二阶导数来生成下一棵树。
其中 Gradient 就是所谓的一阶导数,而 Hessian 即为二阶导数。

优点:准确率高。支援平行处理。抗过拟合与抗杂讯能力强。

# 同样用上面的酒类分类
from sklearn.tree import DecisionTreeClassifier
from xgboost import XGBClassifier

tree = DecisionTreeClassifier(criterion='entropy', max_depth=1, random_state=1)
xgb = XGBClassifier(
    n_estimators=500, 
    learning_rate=0.01,
    random_state=1
)

超参数:
1. nestimators: 创建 n 个学习器。
2. maxdepth: 树的最大深度,(通常设计 3~10)。默认 6。
3. booster: gbtree 树模型(默认) / gbliner 线性模型。
4. learningrate: 学习速率,(通常设计 0.01~0.2)。默认 0.3。
*完整可看: https://www.twblogs.net/a/5db37e49bd9eee310ee694ee*

Stacking 黏合法

与前面 Baggin / Boosting 不同,Stacking 会有两种模型:

  1. Base-Models: 用於训练数据并预测资料。
  2. Meta-Model : 用於结合 Base-Models 预测的资料,得出最终预测模型。

同样用上面的酒类分类:

from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.tree import DecisionTreeClassifier
from sklearn.neighbors import KNeighborsClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import StackingClassifier

tree = DecisionTreeClassifier(criterion='entropy', max_depth=1, random_state=1)

knn_PL = make_pipeline(
    StandardScaler(),
    KNeighborsClassifier(n_neighbors=1, metric='minkowski')
)

esn = [('Decision Tree', tree), ('KNN', knn_PL)]

stk = StackingClassifier(
    estimators=esn,
    final_estimator=LogisticRegression()
)

*超参数:

  1. estimators: 基础学习器们(用於训练数据)
  2. finalestimator: 最终学习器(用於整合基础学习器)*
from sklearn.metrics import accuracy_score
from sklearn.model_selection import cross_val_score

labels = ['Random Forest', 'SVC', 'Stacking']
all_clf = [rf, svc_PL, stk]

for clf, label in zip(all_clf, labels):
    scores = cross_val_score(
        clf, X_train, y_train,
        cv=10,
        scoring='roc_auc'
    )
    print(f"ROC AUC: {scores.mean():0.2f} (+/- {scores.std():0.2f}) [{label}]")

>>  ROC AUC: 0.88 (+/- 0.09) [Decision Tree]
    ROC AUC: 0.90 (+/- 0.11) [KNN]
    ROC AUC: 0.98 (+/- 0.05) [Stacking]

把决策边界作图(代码在补充 4.):
https://ithelp.ithome.com.tw/upload/images/20220107/20138527HN5AxzJzik.png

结论:

  1. Bagging:降低模型 Variance(多数决 → 降低预测不准度)
  2. Boosting:降低模型 Bias(矫正错误分类的偏差)
  3. Stacking:强化 predictive force(y 作为 Meta-Model 的 x)
    .
    .
    .
    .
    .
    *补充 1. 多数投票法 范例的决策边界图
sc = StandardScaler()
X_train_std = sc.fit_transform(X_train)

import numpy as np
from itertools import product

x_min, x_max = X_train_std[:, 0].min() - 1, X_train_std[:, 0].max() + 1
y_min, y_max = X_train_std[:, 1].min() - 1, X_train_std[:, 1].max() + 1

xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.1),
                     np.arange(y_min, y_max, 0.1)
                    )

f, axarr = plt.subplots(nrows=2, ncols=2, 
                        sharex='col', 
                        sharey='row', 
                        figsize=(7, 5))

for idx, clf, tt in zip(product([0, 1], [0, 1]), all_clf, labels):
    clf.fit(X_train_std, y_train)
    
    Z = clf.predict(np.c_[xx.ravel(), yy.ravel()])
    Z = Z.reshape(xx.shape)

    axarr[idx[0], idx[1]].contourf(xx, yy, Z, alpha=0.3)
    
    axarr[idx[0], idx[1]].scatter(X_train_std[y_train==0, 0], 
                                  X_train_std[y_train==0, 1], 
                                  c='blue', 
                                  marker='^',
                                  s=50)
    
    axarr[idx[0], idx[1]].scatter(X_train_std[y_train==1, 0], 
                                  X_train_std[y_train==1, 1], 
                                  c='green', 
                                  marker='o',
                                  s=50)
    
    axarr[idx[0], idx[1]].set_title(tt)

plt.text(-3.5, -5., 
         s='Sepal width [standardized]', 
         ha='center', va='center', fontsize=12)
plt.text(-12.5, 4.5, 
         s='Petal length [standardized]', 
         ha='center', va='center', 
         fontsize=12, rotation=90)
plt.show()

*补充 2. 袋装法 范例的决策边界图

import numpy as np
import matplotlib.pyplot as plt

x_min = X_train[:, 0].min() - 1
x_max = X_train[:, 0].max() + 1
y_min = X_train[:, 1].min() - 1
y_max = X_train[:, 1].max() + 1

xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.1),
                     np.arange(y_min, y_max, 0.1))

f, axarr = plt.subplots(nrows=1, ncols=2, 
                        sharex='col', 
                        sharey='row', 
                        figsize=(8, 3))


for idx, clf, tt in zip([0, 1],
                        [tree, bag],
                        ['Decision tree', 'Bagging']):
    clf.fit(X_train, y_train)

    Z = clf.predict(np.c_[xx.ravel(), yy.ravel()])
    Z = Z.reshape(xx.shape)

    axarr[idx].contourf(xx, yy, Z, alpha=0.3)
    axarr[idx].scatter(X_train[y_train == 0, 0],
                       X_train[y_train == 0, 1],
                       c='blue', marker='^')

    axarr[idx].scatter(X_train[y_train == 1, 0],
                       X_train[y_train == 1, 1],
                       c='green', marker='o')

    axarr[idx].set_title(tt)

axarr[0].set_ylabel('Alcohol', fontsize=12)
plt.tight_layout()
plt.show()

*补充 3. 强化法 范例的决策边界图

import numpy as np
import matplotlib.pyplot as plt

x_min, x_max = X_train[:, 0].min() - 1, X_train[:, 0].max() + 1
y_min, y_max = X_train[:, 1].min() - 1, X_train[:, 1].max() + 1

xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.1),
                     np.arange(y_min, y_max, 0.1))

f, axarr = plt.subplots(nrows=1, ncols=2, 
                        sharex='col', 
                        sharey='row', 
                        figsize=(8, 3))


for idx, clf, tt in zip([0, 1],
                        [tree, ada],
                        ['Decision tree', 'AdaBoost']):
    clf.fit(X_train, y_train)

    Z = clf.predict(np.c_[xx.ravel(), yy.ravel()])
    Z = Z.reshape(xx.shape)

    axarr[idx].contourf(xx, yy, Z, alpha=0.3)
    axarr[idx].scatter(X_train[y_train == 0, 0],
                       X_train[y_train == 0, 1],
                       c='blue', marker='^')

    axarr[idx].scatter(X_train[y_train == 1, 0],
                       X_train[y_train == 1, 1],
                       c='green', marker='o')

    axarr[idx].set_title(tt)

axarr[0].set_ylabel('Alcohol', fontsize=12)
plt.tight_layout()
plt.show()

*补充 4. 黏合法 范例的决策边界图

import numpy as np
import matplotlib.pyplot as plt

x_min, x_max = X_train[:, 0].min() - 1, X_train[:, 0].max() + 1
y_min, y_max = X_train[:, 1].min() - 1, X_train[:, 1].max() + 1

xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.1),
                     np.arange(y_min, y_max, 0.1))

f, axarr = plt.subplots(nrows=1, ncols=2, 
                        sharex='col', 
                        sharey='row', 
                        figsize=(8, 3))


for idx, clf, tt in zip([0, 1, 2],
                        [tree, stk],
                        ['Decision tree', 'Stacking']):
    clf.fit(X_train, y_train)

    Z = clf.predict(np.c_[xx.ravel(), yy.ravel()])
    Z = Z.reshape(xx.shape)

    axarr[idx].contourf(xx, yy, Z, alpha=0.3)
    axarr[idx].scatter(X_train[y_train == 0, 0],
                       X_train[y_train == 0, 1],
                       c='blue', marker='^')

    axarr[idx].scatter(X_train[y_train == 1, 0],
                       X_train[y_train == 1, 1],
                       c='green', marker='o')

    axarr[idx].set_title(tt)

axarr[0].set_ylabel('Alcohol', fontsize=12)

plt.tight_layout()
plt.show()

<<:  javascript流程控制-回圈

>>:  使用 802.1X 实施网路存取控制中,让请求者(supplicant)向身份验证者(authenticator)进行身份验证且具有最少的系统管理负担(overhead)的是PEAP协议

第26车厢-眼睛眨啊眨~登入密码的显示/隐藏应用篇

本篇介绍现行登入密码栏位,旁边都有一个小眼睛,是如何点一下就秀出密码的呢? ▼ 完成图如下 首先先...

Day 11 - OOP 初探 (1) - Closures 与继承链

前言 在学习 FP 的过程中,会看到 FP 常常被拿来跟 OOP 做比较,那 OOP 究竟是什麽呢?...

Day2.程序运行的基本概念(预处理、编译、组译、链结)

平常我们很少关注编译和链结的过程,因为开发环境都集成开发的环境,比如Visual Studio、Ec...

[JS] You Don't Know JavaScript [this & Object Prototypes] - Object [下]

前言 在Object [上]中我们介绍了物件的宣告、型态、拷贝等等特性,接下来我们继续介绍物件中都有...

[DAY19]Ingress-k8s的海姆达尔

还有印象雷神索尔里面,管理着彩虹桥的海姆达尔吗~ 只有人从彩虹桥传送进来时,第一个面对的就是他。 在...