日萌社
人工智能AI:Keras PyTorch MXNet TensorFlow PaddlePaddle 深度學習實戰(不定時更新)
5.1 集成學習算法簡介
1 什麼是集成學習
集成學習通過建立幾個模型來解決單一預測問題。它的工作原理是生成多個分類器/模型,各自獨立地學習和作出預測。這些預測最後結合成組合預測,因此優於任何一個單分類的做出預測。
2 複習:機器學習的兩個核心任務
- 任務一:如何優化訓練數據 —> 主要用於解決欠擬合問題
- 任務二:如何提升泛化性能 —> 主要用於解決過擬合問題
3 集成學習中boosting和Bagging
只要單分類器的表現不太差,集成學習的結果總是要好於單分類器的
4 小結
- 什麼是集成學習【瞭解】
- 通過建立幾個模型來解決單一預測問題
- 機器學習兩個核心任務【知道】
- 1.解決欠擬合問題
- 弱弱組合變強
- boosting
- 2.解決過擬合問題
- 互相遏制變壯
- Bagging
- 1.解決欠擬合問題
5.2 Bagging和隨機森林
1 Bagging集成原理
目標:把下面的圈和方塊進行分類
實現過程:
1) 採樣不同數據集
2)訓練分類器
3)平權投票,獲取最終結果
4)主要實現過程小結
2 隨機森林構造過程
在機器學習中,隨機森林是一個包含多個決策樹的分類器,並且其輸出的類別是由個別樹輸出的類別的衆數而定。
隨機森林 = Bagging + 決策樹
例如, 如果你訓練了5個樹, 其中有4個樹的結果是True, 1個樹的結果是False, 那麼最終投票結果就是True
隨機森林夠造過程中的關鍵步驟(M表示特徵數目):
1)一次隨機選出一個樣本,有放回的抽樣,重複N次(有可能出現重複的樣本)
2) 隨機去選出m個特徵, m <<M,建立決策樹
- 思考
- 1.爲什麼要隨機抽樣訓練集?
- 如果不進行隨機抽樣,每棵樹的訓練集都一樣,那麼最終訓練出的樹分類結果也是完全一樣的
- 2.爲什麼要有放回地抽樣?
- 如果不是有放回的抽樣,那麼每棵樹的訓練樣本都是不同的,都是沒有交集的,這樣每棵樹都是“有偏的”,都是絕對“片面的”(當然這樣說可能不對),也就是說每棵樹訓練出來都是有很大的差異的;而隨機森林最後分類取決於多棵樹(弱分類器)的投票表決。
- 1.爲什麼要隨機抽樣訓練集?
3 包外估計 (Out-of-Bag Estimate)
在隨機森林構造過程中,如果進行有放回的抽樣,我們會發現,總是有一部分樣本我們選不到。
- 這部分數據,佔整體數據的比重有多大呢?
- 這部分數據有什麼用呢?
3.1 包外估計的定義
如何理解無偏估計?無偏估計有什麼用?
1.如何理解無偏估計
無偏估計:就是我認爲所有樣本出現的概率一樣。
假如有N種樣本我們認爲所有樣本出現概率都是1/N。然後根據這個來計算數學期望。此時的數學期望就是我們平常講的平均值。
數學期望本質就是平均值
2.無偏估計爲何叫做“無偏”?它要“估計”什麼?
首先回答第一個問題:它要“估計”什麼?
- 它要估計的是整體的數學期望(平均值)。
3.爲何要用無偏估計?
因爲現實生活中我不知道某個樣本出現的概率啊,就像骰子,我不知道他是不是加過水銀。
所以我們暫時按照每種情況出現概率一樣來算。
3.2 包外估計的用途
- 當基學習器是決策樹時,可使用包外樣本來輔助剪枝 ,或用於估計決策樹中各結點的後驗概率以輔助對零訓練樣本結點的處理;
- 當基學習器是神經網絡時,可使用包外樣本來輔助早期停止以減小過擬合 。
3 隨機森林api介紹
-
sklearn.ensemble.RandomForestClassifier(n_estimators=10, criterion=’gini’, max_depth=None, bootstrap=True, random_state=None, min_samples_split=2)
-
n_estimators:integer,optional(default = 10)森林裏的樹木數量120,200,300,500,800,1200
- 在利用最大投票數或平均值來預測之前,你想要建立子樹的數量。
-
Criterion:string,可選(default =“gini”)
- 分割特徵的測量方法
-
max_depth:integer或None,可選(默認=無)
- 樹的最大深度 5,8,15,25,30
-
max_features="auto”,每個決策樹的最大特徵數量
- If "auto", then
max_features=sqrt(n_features)
. - If "sqrt", then
max_features=sqrt(n_features)
(same as "auto"). - If "log2", then
max_features=log2(n_features)
. - If None, then
max_features=n_features
.
- If "auto", then
-
bootstrap:boolean,optional(default = True)
- 是否在構建樹時使用放回抽樣
-
min_samples_split 內部節點再劃分所需最小樣本數
- 這個值限制了子樹繼續劃分的條件,如果某節點的樣本數少於min_samples_split,則不會繼續再嘗試選擇最優特徵來進行劃分,默認是2。
- 如果樣本量不大,不需要管這個值。如果樣本量數量級非常大,則推薦增大這個值。
-
min_samples_leaf 葉子節點的最小樣本數
-
這個值限制了葉子節點最少的樣本數,如果某葉子節點數目小於樣本數,則會和兄弟節點一起被剪枝, 默認是1。
-
葉是決策樹的末端節點。 較小的葉子使模型更容易捕捉訓練數據中的噪聲。
-
一般來說,我更偏向於將最小葉子節點數目設置爲大於50。
-
-
min_impurity_split: 節點劃分最小不純度
-
這個值限制了決策樹的增長,如果某節點的不純度(基於基尼係數,均方差)小於這個閾值,則該節點不再生成子節點。即爲葉子節點 。
-
一般不推薦改動默認值1e-7。
-
-
-
上面決策樹參數中最重要的包括
- 最大特徵數max_features,
- 最大深度max_depth,
- 內部節點再劃分所需最小樣本數min_samples_split
- 葉子節點最少樣本數min_samples_leaf。
4 隨機森林預測案例
- 實例化隨機森林
# 隨機森林去進行預測
rf = RandomForestClassifier()
- 定義超參數的選擇列表
param = {"n_estimators": [120,200,300,500,800,1200], "max_depth": [5, 8, 15, 25, 30]}
- 使用GridSearchCV進行網格搜索
# 超參數調優
gc = GridSearchCV(rf, param_grid=param, cv=2)
gc.fit(x_train, y_train)
print("隨機森林預測的準確率爲:", gc.score(x_test, y_test))
注意
- 隨機森林的建立過程
- 樹的深度、樹的個數等需要進行超參數調優
5 bagging集成優點
Bagging + 決策樹/線性迴歸/邏輯迴歸/深度學習… = bagging集成學習方法
經過上面方式組成的集成學習方法:
- 均可在原有算法上提高約2%左右的泛化正確率
- 簡單, 方便, 通用
6 小結
- bagging集成過程【知道】
- 1.採樣 — 從所有樣本里面,採樣一部分
- 2.學習 — 訓練弱學習器
- 3.集成 — 使用平權投票
- 隨機森林介紹【知道】
- 隨機森林定義
- 隨機森林 = Bagging + 決策樹
- 流程:
- 1.隨機選取m條數據
- 2.隨機選取k個特徵
- 3.訓練決策樹
- 4.重複1-3
- 5.對上面的若決策樹進行平權投票
- 注意:
- 1.隨機選取樣本,且是有放回的抽取
- 2.選取特徵的時候嗎,選擇m<<M
- M是所有的特徵數
- 包外估計
- 如果進行有放回的對數據集抽樣,會發現,總是有一部分樣本選不到;
- api
- sklearn.ensemble.RandomForestClassifier()
- 隨機森林定義
- Bagging + 決策樹/線性迴歸/邏輯迴歸/深度學習… = bagging集成學習方法【瞭解】
- bagging的優點【瞭解】
- 1.均可在原有算法上提高約2%左右的泛化正確率
- 2.簡單, 方便, 通用
5.3 otto案例介紹 -- Otto Group Product Classification Challenge
1.背景介紹
奧托集團是世界上最大的電子商務公司之一,在20多個國家設有子公司。該公司每天都在世界各地銷售數百萬種產品,所以對其產品根據性能合理的分類非常重要。
不過,在實際工作中,工作人員發現,許多相同的產品得到了不同的分類。本案例要求,你對奧拓集團的產品進行正確的分分類。儘可能的提供分類的準確性。
鏈接:https://www.kaggle.com/c/otto-group-product-classification-challenge/overview
2.數據集介紹
- 本案例中,數據集包含大約200,000種產品的93個特徵。
- 其目的是建立一個能夠區分otto公司主要產品類別的預測模型。
- 所有產品共被分成九個類別(例如時裝,電子產品等)。
- id - 產品id
- feat_1, feat_2, ..., feat_93 - 產品的各個特徵
- target - 產品被劃分的類別
3.評分標準
本案例中,最後結果使用多分類對數損失進行評估。
具體公式:
4.實現過程
4.1 流程分析
- 獲取數據
- 數據基本處理
- 數據量比較大,嘗試是否可以進行數據分割
- 轉換目標值表示方式
- 模型訓練
- 模型基本訓練
4.2 代碼實現
In [1]:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
數據獲取
In [2]:
data = pd.read_csv("./data/otto/train.csv")
In [3]:
data.head()
Out[3]:
id | feat_1 | feat_2 | feat_3 | feat_4 | feat_5 | feat_6 | feat_7 | feat_8 | feat_9 | ... | feat_85 | feat_86 | feat_87 | feat_88 | feat_89 | feat_90 | feat_91 | feat_92 | feat_93 | target | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | Class_1 |
1 | 2 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | Class_1 |
2 | 3 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | Class_1 |
3 | 4 | 1 | 0 | 0 | 1 | 6 | 1 | 5 | 0 | 0 | ... | 0 | 1 | 2 | 0 | 0 | 0 | 0 | 0 | 0 | Class_1 |
4 | 5 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 1 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | Class_1 |
5 rows × 95 columns
In [4]:
data.shape
Out[4]:
(61878, 95)
In [5]:
data.describe()
Out[5]:
id | feat_1 | feat_2 | feat_3 | feat_4 | feat_5 | feat_6 | feat_7 | feat_8 | feat_9 | ... | feat_84 | feat_85 | feat_86 | feat_87 | feat_88 | feat_89 | feat_90 | feat_91 | feat_92 | feat_93 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
count | 61878.000000 | 61878.00000 | 61878.000000 | 61878.000000 | 61878.000000 | 61878.000000 | 61878.000000 | 61878.000000 | 61878.000000 | 61878.000000 | ... | 61878.000000 | 61878.000000 | 61878.000000 | 61878.000000 | 61878.000000 | 61878.000000 | 61878.000000 | 61878.000000 | 61878.000000 | 61878.000000 |
mean | 30939.500000 | 0.38668 | 0.263066 | 0.901467 | 0.779081 | 0.071043 | 0.025696 | 0.193704 | 0.662433 | 1.011296 | ... | 0.070752 | 0.532306 | 1.128576 | 0.393549 | 0.874915 | 0.457772 | 0.812421 | 0.264941 | 0.380119 | 0.126135 |
std | 17862.784315 | 1.52533 | 1.252073 | 2.934818 | 2.788005 | 0.438902 | 0.215333 | 1.030102 | 2.255770 | 3.474822 | ... | 1.151460 | 1.900438 | 2.681554 | 1.575455 | 2.115466 | 1.527385 | 4.597804 | 2.045646 | 0.982385 | 1.201720 |
min | 1.000000 | 0.00000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | ... | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 |
25% | 15470.250000 | 0.00000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | ... | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 |
50% | 30939.500000 | 0.00000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | ... | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 |
75% | 46408.750000 | 0.00000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 1.000000 | 0.000000 | ... | 0.000000 | 0.000000 | 1.000000 | 0.000000 | 1.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 |
max | 61878.000000 | 61.00000 | 51.000000 | 64.000000 | 70.000000 | 19.000000 | 10.000000 | 38.000000 | 76.000000 | 43.000000 | ... | 76.000000 | 55.000000 | 65.000000 | 67.000000 | 30.000000 | 61.000000 | 130.000000 | 52.000000 | 19.000000 | 87.000000 |
8 rows × 94 columns
In [7]:
# 圖形可視化,查看數據分佈
import seaborn as sns
sns.countplot(data.target)
plt.show()
由上圖可以看出,該數據類別不均衡,所以需要後期處理
數據基本處理
數據已經經過脫敏,不再需要特殊處理
截取部分數據
In [8]:
new1_data = data[:10000]
new1_data.shape
Out[8]:
(10000, 95)
In [9]:
# 圖形可視化,查看數據分佈
import seaborn as sns
sns.countplot(new1_data.target)
plt.show()
使用上面方式獲取數據不可行,然後使用隨機欠採樣獲取響應的數據
In [10]:
# 隨機欠採樣獲取數據
# 首先需要確定特徵值\標籤值
y = data["target"]
x = data.drop(["id", "target"], axis=1)
In [11]:
x.head()
Out[11]:
feat_1 | feat_2 | feat_3 | feat_4 | feat_5 | feat_6 | feat_7 | feat_8 | feat_9 | feat_10 | ... | feat_84 | feat_85 | feat_86 | feat_87 | feat_88 | feat_89 | feat_90 | feat_91 | feat_92 | feat_93 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
2 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
3 | 1 | 0 | 0 | 1 | 6 | 1 | 5 | 0 | 0 | 1 | ... | 22 | 0 | 1 | 2 | 0 | 0 | 0 | 0 | 0 | 0 |
4 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 1 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 |
5 rows × 93 columns
In [12]:
y.head()
Out[12]:
0 Class_1 1 Class_1 2 Class_1 3 Class_1 4 Class_1 Name: target, dtype: object
In [14]:
# 欠採樣獲取數據
from imblearn.under_sampling import RandomUnderSampler
rus = RandomUnderSampler(random_state=0)
X_resampled, y_resampled = rus.fit_resample(x, y)
In [15]:
x.shape, y.shape
Out[15]:
((61878, 93), (61878,))
In [16]:
X_resampled.shape, y_resampled.shape
Out[16]:
((17361, 93), (17361,))
In [17]:
# 圖形可視化,查看數據分佈
import seaborn as sns
sns.countplot(y_resampled)
plt.show()
把標籤值轉換爲數字
In [18]:
y_resampled.head()
Out[18]:
0 Class_1 1 Class_1 2 Class_1 3 Class_1 4 Class_1 Name: target, dtype: object
In [19]:
from sklearn.preprocessing import LabelEncoder
le = LabelEncoder()
y_resampled = le.fit_transform(y_resampled)
In [20]:
y_resampled
Out[20]:
array([0, 0, 0, ..., 8, 8, 8])
分割數據
In [21]:
from sklearn.model_selection import train_test_split
x_train, x_test, y_train, y_test = train_test_split(X_resampled, y_resampled, test_size=0.2)
In [23]:
x_train.shape, y_train.shape
Out[23]:
((13888, 93), (13888,))
In [24]:
x_test.shape, y_test.shape
Out[24]:
((3473, 93), (3473,))
模型訓練
基本模型訓練
In [26]:
from sklearn.ensemble import RandomForestClassifier
rf = RandomForestClassifier(oob_score=True)
rf.fit(x_train, y_train)
Out[26]:
RandomForestClassifier(bootstrap=True, ccp_alpha=0.0, class_weight=None, criterion='gini', max_depth=None, max_features='auto', max_leaf_nodes=None, max_samples=None, min_impurity_decrease=0.0, min_impurity_split=None, min_samples_leaf=1, min_samples_split=2, min_weight_fraction_leaf=0.0, n_estimators=100, n_jobs=None, oob_score=True, random_state=None, verbose=0, warm_start=False)
In [28]:
y_pre = rf.predict(x_test)
y_pre
Out[28]:
array([7, 7, 0, ..., 1, 1, 0])
In [29]:
rf.score(x_test, y_test)
Out[29]:
0.7782896631154621
In [30]:
rf.oob_score_
Out[30]:
0.7659850230414746
In [31]:
# 圖形可視化,查看數據分佈
import seaborn as sns
sns.countplot(y_pre)
plt.show()
In [33]:
# logloss模型評估
from sklearn.metrics import log_loss
#log_loss(y_test, y_pre, eps=1e-15, normalize=True)
In [35]:
y_test, y_pre
Out[35]:
(array([7, 7, 8, ..., 1, 1, 0]), array([7, 7, 0, ..., 1, 1, 0]))
上面報錯原因:logloss使用過程中,必須要求將輸出用one-hot表示,
需要將這個多類別問題的輸出結果通過OneHotEncoder修改爲如下:
In [37]:
from sklearn.preprocessing import OneHotEncoder
one_hot = OneHotEncoder(sparse=False)
y_test1 = one_hot.fit_transform(y_test.reshape(-1, 1))
y_pre1 = one_hot.fit_transform(y_pre.reshape(-1, 1))
In [38]:
y_test1
Out[38]:
array([[0., 0., 0., ..., 0., 1., 0.], [0., 0., 0., ..., 0., 1., 0.], [0., 0., 0., ..., 0., 0., 1.], ..., [0., 1., 0., ..., 0., 0., 0.], [0., 1., 0., ..., 0., 0., 0.], [1., 0., 0., ..., 0., 0., 0.]])
In [39]:
y_pre1
Out[39]:
array([[0., 0., 0., ..., 0., 1., 0.], [0., 0., 0., ..., 0., 1., 0.], [1., 0., 0., ..., 0., 0., 0.], ..., [0., 1., 0., ..., 0., 0., 0.], [0., 1., 0., ..., 0., 0., 0.], [1., 0., 0., ..., 0., 0., 0.]])
In [40]:
# logloss模型評估
log_loss(y_test1, y_pre1, eps=1e-15, normalize=True)
Out[40]:
7.65760375009538
In [41]:
# 改變預測值的輸出模式,讓輸出結果爲百分佔比,降低logloss值
y_pre_proba = rf.predict_proba(x_test)
In [42]:
y_pre_proba
Out[42]:
array([[0.04, 0. , 0. , ..., 0.01, 0.92, 0.01], [0.03, 0.02, 0.03, ..., 0.2 , 0.48, 0.08], [0.41, 0.06, 0.06, ..., 0.05, 0.04, 0.31], ..., [0. , 0.49, 0.28, ..., 0.02, 0. , 0.01], [0.01, 0.54, 0.09, ..., 0. , 0. , 0.02], [0.31, 0.03, 0.02, ..., 0.11, 0.13, 0.13]])
In [43]:
rf.oob_score_
Out[43]:
0.7659850230414746
In [44]:
# logloss模型評估
log_loss(y_test1, y_pre_proba, eps=1e-15, normalize=True)
Out[44]:
0.747188586960504
模型調優
n_estimators, max_feature, max_depth, min_samples_leaf
確定最優的n_estimators
In [45]:
# 確定n_estimators的取值範圍
tuned_parameters = range(10, 200, 10)
# 創建添加accuracy的一個numpy
accuracy_t = np.zeros(len(tuned_parameters))
# 創建添加error的一個numpy
error_t = np.zeros(len(tuned_parameters))
# 調優過程實現
for j, one_parameter in enumerate(tuned_parameters):
rf2 = RandomForestClassifier(n_estimators=one_parameter,
max_depth=10,
max_features=10,
min_samples_leaf=10,
oob_score=True,
random_state=0,
n_jobs=-1)
rf2.fit(x_train, y_train)
# 輸出accuracy
accuracy_t[j] = rf2.oob_score_
# 輸出log_loss
y_pre = rf2.predict_proba(x_test)
error_t[j] = log_loss(y_test, y_pre, eps=1e-15, normalize=True)
print(error_t)
[1.1363637 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. ]
[1.1363637 1.12502792 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. ] [1.1363637 1.12502792 1.12888587 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. ] [1.1363637 1.12502792 1.12888587 1.12663772 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. ] [1.1363637 1.12502792 1.12888587 1.12663772 1.12185466 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. ] [1.1363637 1.12502792 1.12888587 1.12663772 1.12185466 1.11919361 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. ] [1.1363637 1.12502792 1.12888587 1.12663772 1.12185466 1.11919361 1.11933586 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. ] [1.1363637 1.12502792 1.12888587 1.12663772 1.12185466 1.11919361 1.11933586 1.1209923 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. ] [1.1363637 1.12502792 1.12888587 1.12663772 1.12185466 1.11919361 1.11933586 1.1209923 1.12231238 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. ] [1.1363637 1.12502792 1.12888587 1.12663772 1.12185466 1.11919361 1.11933586 1.1209923 1.12231238 1.11887248 0. 0. 0. 0. 0. 0. 0. 0. 0. ] [1.1363637 1.12502792 1.12888587 1.12663772 1.12185466 1.11919361 1.11933586 1.1209923 1.12231238 1.11887248 1.11682286 0. 0. 0. 0. 0. 0. 0. 0. ] [1.1363637 1.12502792 1.12888587 1.12663772 1.12185466 1.11919361 1.11933586 1.1209923 1.12231238 1.11887248 1.11682286 1.11668191 0. 0. 0. 0. 0. 0. 0. ] [1.1363637 1.12502792 1.12888587 1.12663772 1.12185466 1.11919361 1.11933586 1.1209923 1.12231238 1.11887248 1.11682286 1.11668191 1.11607429 0. 0. 0. 0. 0. 0. ] [1.1363637 1.12502792 1.12888587 1.12663772 1.12185466 1.11919361 1.11933586 1.1209923 1.12231238 1.11887248 1.11682286 1.11668191 1.11607429 1.11658966 0. 0. 0. 0. 0. ] [1.1363637 1.12502792 1.12888587 1.12663772 1.12185466 1.11919361 1.11933586 1.1209923 1.12231238 1.11887248 1.11682286 1.11668191 1.11607429 1.11658966 1.11551087 0. 0. 0. 0. ] [1.1363637 1.12502792 1.12888587 1.12663772 1.12185466 1.11919361 1.11933586 1.1209923 1.12231238 1.11887248 1.11682286 1.11668191 1.11607429 1.11658966 1.11551087 1.11454353 0. 0. 0. ] [1.1363637 1.12502792 1.12888587 1.12663772 1.12185466 1.11919361 1.11933586 1.1209923 1.12231238 1.11887248 1.11682286 1.11668191 1.11607429 1.11658966 1.11551087 1.11454353 1.11539224 0. 0. ] [1.1363637 1.12502792 1.12888587 1.12663772 1.12185466 1.11919361 1.11933586 1.1209923 1.12231238 1.11887248 1.11682286 1.11668191 1.11607429 1.11658966 1.11551087 1.11454353 1.11539224 1.11527522 0. ] [1.1363637 1.12502792 1.12888587 1.12663772 1.12185466 1.11919361 1.11933586 1.1209923 1.12231238 1.11887248 1.11682286 1.11668191 1.11607429 1.11658966 1.11551087 1.11454353 1.11539224 1.11527522 1.11545188]
In [50]:
# 優化結果過程可視化
fig,axes = plt.subplots(nrows=1, ncols=2, figsize=(20, 4), dpi=100)
axes[0].plot(tuned_parameters, error_t)
axes[1].plot(tuned_parameters, accuracy_t)
axes[0].set_xlabel("n_estimators")
axes[0].set_ylabel("error_t")
axes[1].set_xlabel("n_estimators")
axes[1].set_ylabel("accuracy_t")
axes[0].grid(True)
axes[1].grid(True)
plt.show()
經過圖像展示,最後確定n_estimators=175的時候,表現效果不錯
確定最優的max_features
In [51]:
# 確定n_estimators的取值範圍
tuned_parameters = range(5, 40, 5)
# 創建添加accuracy的一個numpy
accuracy_t = np.zeros(len(tuned_parameters))
# 創建添加error的一個numpy
error_t = np.zeros(len(tuned_parameters))
# 調優過程實現
for j, one_parameter in enumerate(tuned_parameters):
rf2 = RandomForestClassifier(n_estimators=175,
max_depth=10,
max_features=one_parameter,
min_samples_leaf=10,
oob_score=True,
random_state=0,
n_jobs=-1)
rf2.fit(x_train, y_train)
# 輸出accuracy
accuracy_t[j] = rf2.oob_score_
# 輸出log_loss
y_pre = rf2.predict_proba(x_test)
error_t[j] = log_loss(y_test, y_pre, eps=1e-15, normalize=True)
print(error_t)
[1.21048299 0. 0. 0. 0. 0. 0. ] [1.21048299 1.11570883 0. 0. 0. 0. 0. ] [1.21048299 1.11570883 1.08477789 0. 0. 0. 0. ] [1.21048299 1.11570883 1.08477789 1.06988755 0. 0. 0. ] [1.21048299 1.11570883 1.08477789 1.06988755 1.06604809 0. 0. ] [1.21048299 1.11570883 1.08477789 1.06988755 1.06604809 1.05863125 0. ] [1.21048299 1.11570883 1.08477789 1.06988755 1.06604809 1.05863125 1.06050897]
In [52]:
# 優化結果過程可視化
fig,axes = plt.subplots(nrows=1, ncols=2, figsize=(20, 4), dpi=100)
axes[0].plot(tuned_parameters, error_t)
axes[1].plot(tuned_parameters, accuracy_t)
axes[0].set_xlabel("max_features")
axes[0].set_ylabel("error_t")
axes[1].set_xlabel("max_features")
axes[1].set_ylabel("accuracy_t")
axes[0].grid(True)
axes[1].grid(True)
plt.show()
經過圖像展示,最後確定max_features=15的時候,表現效果不錯
確定最優的max_depth
In [53]:
# 確定n_estimators的取值範圍
tuned_parameters = range(10, 100, 10)
# 創建添加accuracy的一個numpy
accuracy_t = np.zeros(len(tuned_parameters))
# 創建添加error的一個numpy
error_t = np.zeros(len(tuned_parameters))
# 調優過程實現
for j, one_parameter in enumerate(tuned_parameters):
rf2 = RandomForestClassifier(n_estimators=175,
max_depth=one_parameter,
max_features=15,
min_samples_leaf=10,
oob_score=True,
random_state=0,
n_jobs=-1)
rf2.fit(x_train, y_train)
# 輸出accuracy
accuracy_t[j] = rf2.oob_score_
# 輸出log_loss
y_pre = rf2.predict_proba(x_test)
error_t[j] = log_loss(y_test, y_pre, eps=1e-15, normalize=True)
print(error_t)
[1.08477789 0. 0. 0. 0. 0. 0. 0. 0. ] [1.08477789 0.85395849 0. 0. 0. 0. 0. 0. 0. ] [1.08477789 0.85395849 0.82965202 0. 0. 0. 0. 0. 0. ] [1.08477789 0.85395849 0.82965202 0.83011988 0. 0. 0. 0. 0. ] [1.08477789 0.85395849 0.82965202 0.83011988 0.8302702 0. 0. 0. 0. ] [1.08477789 0.85395849 0.82965202 0.83011988 0.8302702 0.8302702 0. 0. 0. ] [1.08477789 0.85395849 0.82965202 0.83011988 0.8302702 0.8302702 0.8302702 0. 0. ] [1.08477789 0.85395849 0.82965202 0.83011988 0.8302702 0.8302702 0.8302702 0.8302702 0. ] [1.08477789 0.85395849 0.82965202 0.83011988 0.8302702 0.8302702 0.8302702 0.8302702 0.8302702 ]
In [54]:
# 優化結果過程可視化
fig,axes = plt.subplots(nrows=1, ncols=2, figsize=(20, 4), dpi=100)
axes[0].plot(tuned_parameters, error_t)
axes[1].plot(tuned_parameters, accuracy_t)
axes[0].set_xlabel("max_depth")
axes[0].set_ylabel("error_t")
axes[1].set_xlabel("max_depth")
axes[1].set_ylabel("accuracy_t")
axes[0].grid(True)
axes[1].grid(True)
plt.show()
經過圖像展示,最後確定max_depth=30的時候,表現效果不錯
確定最優的min_sample_leaf
In [56]:
# 確定n_estimators的取值範圍
tuned_parameters = range(1, 10, 2)
# 創建添加accuracy的一個numpy
accuracy_t = np.zeros(len(tuned_parameters))
# 創建添加error的一個numpy
error_t = np.zeros(len(tuned_parameters))
# 調優過程實現
for j, one_parameter in enumerate(tuned_parameters):
rf2 = RandomForestClassifier(n_estimators=175,
max_depth=30,
max_features=15,
min_samples_leaf=one_parameter,
oob_score=True,
random_state=0,
n_jobs=-1)
rf2.fit(x_train, y_train)
# 輸出accuracy
accuracy_t[j] = rf2.oob_score_
# 輸出log_loss
y_pre = rf2.predict_proba(x_test)
error_t[j] = log_loss(y_test, y_pre, eps=1e-15, normalize=True)
print(error_t)
[0.71382973 0. 0. 0. 0. ] [0.71382973 0.74611594 0. 0. 0. ] [0.71382973 0.74611594 0.77748299 0. 0. ] [0.71382973 0.74611594 0.77748299 0.80311457 0. ] [0.71382973 0.74611594 0.77748299 0.80311457 0.82513751]
In [57]:
# 優化結果過程可視化
fig,axes = plt.subplots(nrows=1, ncols=2, figsize=(20, 4), dpi=100)
axes[0].plot(tuned_parameters, error_t)
axes[1].plot(tuned_parameters, accuracy_t)
axes[0].set_xlabel("min_sample_leaf")
axes[0].set_ylabel("error_t")
axes[1].set_xlabel("min_sample_leaf")
axes[1].set_ylabel("accuracy_t")
axes[0].grid(True)
axes[1].grid(True)
plt.show()
經過圖像展示,最後確定min_sample_leaf=1的時候,表現效果不錯
確定最優模型
n_estimators=175,
max_depth=30,
max_features=15,
min_samples_leaf=1,
In [59]:
rf3 = RandomForestClassifier(n_estimators=175, max_depth=30, max_features=15, min_samples_leaf=1,
oob_score=True, random_state=40, n_jobs=-1)
rf3.fit(x_train, y_train)
rf3.fit(x_train, y_train)
Out[59]:
RandomForestClassifier(bootstrap=True, ccp_alpha=0.0, class_weight=None, criterion='gini', max_depth=30, max_features=15, max_leaf_nodes=None, max_samples=None, min_impurity_decrease=0.0, min_impurity_split=None, min_samples_leaf=1, min_samples_split=2, min_weight_fraction_leaf=0.0, n_estimators=175, n_jobs=-1, oob_score=True, random_state=40, verbose=0, warm_start=False)
In [60]:
rf3.score(x_test, y_test)
Out[60]:
0.7837604376619637
In [61]:
rf3.oob_score_
Out[61]:
0.7746255760368663
In [62]:
y_pre_proba1 = rf3.predict_proba(x_test)
log_loss(y_test, y_pre_proba1)
Out[62]:
0.7113183040048942
生成提交數據¶
In [63]:
test_data = pd.read_csv("./data/otto/test.csv")
In [64]:
test_data.head()
Out[64]:
id | feat_1 | feat_2 | feat_3 | feat_4 | feat_5 | feat_6 | feat_7 | feat_8 | feat_9 | ... | feat_84 | feat_85 | feat_86 | feat_87 | feat_88 | feat_89 | feat_90 | feat_91 | feat_92 | feat_93 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 11 | 1 | 20 | 0 | 0 | 0 | 0 | 0 |
1 | 2 | 2 | 2 | 14 | 16 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 4 | 0 | 0 | 2 | 0 |
2 | 3 | 0 | 1 | 12 | 1 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 2 | 0 | 0 | 0 | 0 | 1 |
3 | 4 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 3 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
4 | 5 | 1 | 0 | 0 | 1 | 0 | 0 | 1 | 2 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 9 | 0 | 0 |
5 rows × 94 columns
In [65]:
test_data_drop_id = test_data.drop(["id"], axis=1)
test_data_drop_id.head()
Out[65]:
feat_1 | feat_2 | feat_3 | feat_4 | feat_5 | feat_6 | feat_7 | feat_8 | feat_9 | feat_10 | ... | feat_84 | feat_85 | feat_86 | feat_87 | feat_88 | feat_89 | feat_90 | feat_91 | feat_92 | feat_93 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 3 | ... | 0 | 0 | 11 | 1 | 20 | 0 | 0 | 0 | 0 | 0 |
1 | 2 | 2 | 14 | 16 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 4 | 0 | 0 | 2 | 0 |
2 | 0 | 1 | 12 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 2 | 0 | 0 | 0 | 0 | 1 |
3 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 3 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
4 | 1 | 0 | 0 | 1 | 0 | 0 | 1 | 2 | 0 | 3 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 9 | 0 | 0 |
5 rows × 93 columns
In [66]:
y_pre_test = rf3.predict_proba(test_data_drop_id)
In [67]:
y_pre_test
Out[67]:
array([[3.42857143e-02, 4.00000000e-02, 6.00000000e-02, ..., 4.01785714e-02, 5.71428571e-03, 2.28571429e-02], [1.14285714e-01, 4.00000000e-02, 3.42857143e-02, ..., 2.85714286e-02, 2.17142857e-01, 2.85714286e-02], [5.71428571e-03, 0.00000000e+00, 0.00000000e+00, ..., 0.00000000e+00, 5.71428571e-03, 0.00000000e+00], ..., [1.72644377e-02, 2.48465399e-01, 4.16172412e-01, ..., 3.42857143e-02, 1.21580547e-04, 5.71428571e-03], [1.72287863e-02, 3.06668815e-01, 1.07797351e-01, ..., 3.93841747e-04, 0.00000000e+00, 5.71428571e-03], [1.92414742e-02, 2.14828481e-01, 3.61958916e-01, ..., 1.60309793e-01, 0.00000000e+00, 1.63868444e-03]])
In [68]:
result_data = pd.DataFrame(y_pre_test, columns=["Class_"+str(i) for i in range(1, 10)])
In [69]:
result_data.head()
Out[69]:
Class_1 | Class_2 | Class_3 | Class_4 | Class_5 | Class_6 | Class_7 | Class_8 | Class_9 | |
---|---|---|---|---|---|---|---|---|---|
0 | 0.034286 | 0.040000 | 0.060000 | 0.774107 | 0.000000 | 0.022857 | 0.040179 | 0.005714 | 0.022857 |
1 | 0.114286 | 0.040000 | 0.034286 | 0.034286 | 0.017143 | 0.485714 | 0.028571 | 0.217143 | 0.028571 |
2 | 0.005714 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.988571 | 0.000000 | 0.005714 | 0.000000 |
3 | 0.027714 | 0.215714 | 0.300000 | 0.277143 | 0.000000 | 0.000000 | 0.017143 | 0.006286 | 0.156000 |
4 | 0.237060 | 0.000080 | 0.005834 | 0.000000 | 0.005954 | 0.023057 | 0.018190 | 0.199658 | 0.510166 |
In [70]:
result_data.insert(loc=0, column="id", value=test_data.id)
In [71]:
result_data.head()
Out[71]:
id | Class_1 | Class_2 | Class_3 | Class_4 | Class_5 | Class_6 | Class_7 | Class_8 | Class_9 | |
---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | 0.034286 | 0.040000 | 0.060000 | 0.774107 | 0.000000 | 0.022857 | 0.040179 | 0.005714 | 0.022857 |
1 | 2 | 0.114286 | 0.040000 | 0.034286 | 0.034286 | 0.017143 | 0.485714 | 0.028571 | 0.217143 | 0.028571 |
2 | 3 | 0.005714 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.988571 | 0.000000 | 0.005714 | 0.000000 |
3 | 4 | 0.027714 | 0.215714 | 0.300000 | 0.277143 | 0.000000 | 0.000000 | 0.017143 | 0.006286 | 0.156000 |
4 | 5 | 0.237060 | 0.000080 | 0.005834 | 0.000000 | 0.005954 | 0.023057 | 0.018190 | 0.199658 | 0.510166 |
In [72]:
result_data.to_csv("./data/otto/submission.csv", index=False)
5.4 Boosting
1 什麼是boosting
隨着學習的積累從弱到強
簡而言之:每新加入一個弱學習器,整體能力就會得到提升
代表算法:Adaboost,GBDT,XGBoost,LightGBM
2 實現過程:
3 bagging集成與boosting集成的區別:
- 區別一:數據方面
- Bagging:對數據進行採樣訓練;
- Boosting:根據前一輪學習結果調整數據的重要性。
- 區別二:投票方面
- Bagging:所有學習器平權投票;
- Boosting:對學習器進行加權投票。
- 區別三:學習順序
- Bagging的學習是並行的,每個學習器沒有依賴關係;
- Boosting學習是串行,學習有先後順序。
- 區別四:主要作用
- Bagging主要用於提高泛化性能(解決過擬合,也可以說降低方差)
- Boosting主要用於提高訓練精度 (解決欠擬合,也可以說降低偏差)
4 AdaBoost介紹
4.1 構造過程細節
4.2 關鍵點剖析
如何確認投票權重?
如何調整數據分佈?
4.3 案例:
給定下面這張訓練數據表所示的數據,假設弱分類器由xv產生,其閾值v使該分類器在訓練數據集上的分類誤差率最低,試用Adaboost算法學習一個強分類器。
4.4 api介紹
- from sklearn.ensemble import AdaBoostClassifier
5 小結
-
什麼是Boosting 【知道】
- 隨着學習的積累從弱到強
- 代表算法:Adaboost,GBDT,XGBoost,LightGBM
-
bagging和boosting的區別【知道】
-
區別一:數據方面
-
Bagging:對數據進行採樣訓練;
-
Boosting:根據前一輪學習結果調整數據的重要性。
-
-
區別二:投票方面
-
Bagging:所有學習器平權投票;
-
Boosting:對學習器進行加權投票。
-
-
區別三:學習順序
-
Bagging的學習是並行的,每個學習器沒有依賴關係;
-
Boosting學習是串行,學習有先後順序。
-
-
區別四:主要作用
-
Bagging主要用於提高泛化性能(解決過擬合,也可以說降低方差)
-
Boosting主要用於提高訓練精度 (解決欠擬合,也可以說降低偏差)
-
-
-
AdaBoost構造過程【知道】
- 步驟一:初始化訓練數據權重相等,訓練第一個學習器;
- 步驟二:AdaBoost反覆學習基本分類器;
- 步驟三:對m個學習器進行加權投票
5.5 GBDT介紹
GBDT 的全稱是 Gradient Boosting Decision Tree,梯度提升樹,在傳統機器學習算法中,GBDT算的上TOP3的算法。想要理解GBDT的真正意義,那就必須理解GBDT中的Gradient Boosting 和Decision Tree分別是什麼?
1 Decision Tree:CART迴歸樹
首先,GBDT使用的決策樹是CART迴歸樹,無論是處理迴歸問題還是二分類以及多分類,GBDT使用的決策樹通通都是都是CART迴歸樹。
- 爲什麼不用CART分類樹呢?
- 因爲GBDT每次迭代要擬合的是梯度值,是連續值所以要用迴歸樹。
對於迴歸樹算法來說最重要的是尋找最佳的劃分點,那麼迴歸樹中的可劃分點包含了所有特徵的所有可取的值。
在分類樹中最佳劃分點的判別標準是熵或者基尼係數,都是用純度來衡量的,但是在迴歸樹中的樣本標籤是連續數值,所以再使用熵之類的指標不再合適,取而代之的是平方誤差,它能很好的評判擬合程度。
1.1 迴歸樹生成算法(複習)
2 Gradient Boosting: 擬合負梯度
梯度提升樹(Grandient Boosting)是提升樹(Boosting Tree)的一種改進算法,所以在講梯度提升樹之前先來說一下提升樹。
先來個通俗理解:假如有個人30歲,我們首先用20歲去擬合,發現損失有10歲,這時我們用6歲去擬合剩下的損失,發現差距還有4歲,第三輪我們用3歲擬合剩下的差距,差距就只有一歲了。如果我們的迭代輪數還沒有完,可以繼續迭代下面,每一輪迭代,擬合的歲數誤差都會減小。最後將每次擬合的歲數加起來便是模型輸出的結果。
提升樹算法:
當損失函數是平方損失和指數損失函數時,梯度提升樹每一步優化是很簡單的,但是對於一般損失函數而言,往往每一步優化起來不那麼容易。
針對這一問題,Friedman提出了梯度提升樹算法,這是利用最速下降的近似方法,其關鍵是利用損失函數的負梯度作爲提升樹算法中的殘差的近似值。
那麼負梯度長什麼樣呢?
此時我們發現GBDT的負梯度就是殘差,所以說對於迴歸問題,我們要擬合的就是殘差。
那麼對於分類問題呢?
- 二分類和多分類的損失函數都是logloss。
本文以迴歸問題爲例進行講解。
3 GBDT算法原理
上面兩節分別將Decision Tree和Gradient Boosting介紹完了,下面將這兩部分組合在一起就是我們的GBDT了。
4 實例介紹
4.1 數據介紹
根據如下數據,預測最後一個樣本的身高。
編號 | 年齡(歲) | 體重(kg) | 身高(m)(標籤值) |
---|---|---|---|
0 | 5 | 20 | 1.1 |
1 | 7 | 30 | 1.3 |
2 | 21 | 70 | 1.7 |
3 | 30 | 60 | 1.8 |
4(要預測的) | 25 | 65 | ? |
4.2 模型訓練
4.2.1 設置參數:
- 學習率:learning_rate=0.1
- 迭代次數:n_trees=5
- 樹的深度:max_depth=3
4.2.2 開始訓練
1.年齡x=5 7爲左節點SEl,年齡x=21 30爲右節點SEr。
2.年齡x 左節點SEl:
負梯度(殘差)作爲標籤值y,然後計算左節點的標籤值y的平均值c1=(-0.375+(-0.175))/2=-0.275
求平方差(標籤值y-c1)^2:SEl=[-0.375-(-0.275)]^2 + [-0.175-(-0.275)]^2 = 0.02
3.年齡x 右節點SEr:
負梯度(殘差)作爲標籤值y,然後計算右節點的標籤值y的平均值c2=(0.225+0.325)/2=0.275
求平方差(標籤值y-c2)^2:SEr=[0.225-0.275]^2 + [0.325-0.275]^2 = 0.005