1.集成學習概述
1.1. 什么是集成學習
集成學習是一種通過組合多個模型來提高預測性能的機器學習方法。它類似于:
-
超級個體?vs?弱者聯盟
-
單個復雜模型(如9次多項式函數)可能能力過強但容易過擬合
-
組合多個簡單模型(如一堆1次函數)可以增強能力而不易過擬合
-
集成學習通過生成多個分類器/模型,將它們的預測結果組合起來,通常能獲得優于任何單一分類器的預測性能。
1.2. 機器學習的兩個核心任務
-
如何優化訓練數據?- 主要用于解決欠擬合問題——booting
-
如何提升泛化性能?- 主要用于解決過擬合問題——bagging
1.3. 集成學習的兩種主要方法
Boosting方法
-
逐步增強學習
-
通過序列化方式構建模型,每個新模型都更關注前序模型處理不好的樣本(錯誤數據增強)
-
典型算法:AdaBoost, Gradient Boosting, XGBoost
Bagging方法
-
采樣學習集成
-
通過并行方式構建多個模型,每個模型基于數據的隨機子集
-
典型算法:隨機森林
集成學習的關鍵優勢是:只要單分類器的表現不太差,集成后的結果通常優于單分類器。這種方法能有效平衡模型的偏差和方差,提高泛化能力
2.Bagging和隨機森林
2.1Bagging集成原理
先看一個圖
2.1.1. Bagging 基本概念
Bagging(Bootstrap Aggregating,自助聚合)是一種并行式集成學習方法,通過構建多個相互獨立的基學習器,并綜合它們的預測結果來提高模型的泛化能力。
核心思想:
-
Bootstrap(自助采樣):從訓練數據中有放回地隨機抽取多個子集,每個子集用于訓練一個基學習器。
-
Aggregating(聚合):所有基學習器的預測結果通過投票(分類)或平均(回歸)進行集成,得到最終預測。
📌?關鍵特點:
適用于高方差、低偏差的模型(如決策樹、神經網絡)。
能有效降低方差,減少過擬合風險。(可以理解因為你隨機選取數據)
2.1.2. Bagging 算法流程
-
自助采樣(Bootstrap Sampling)
-
從原始訓練集?DD?中有放回地隨機抽取?mm?個樣本,構成一個子集?DiDi?。
-
重復該過程?TT?次,得到?TT?個不同的訓練子集。
-
-
基學習器訓練
-
每個子集?DiDi??訓練一個基學習器(如決策樹)。
-
基學習器之間相互獨立(可并行訓練)。
-
-
集成預測
-
分類任務:采用投票法(多數表決)
-
回歸任務:采用平均法(取均值)
-
2.1.3. Bagging 的典型算法:隨機森林(Random Forest)
-
改進點:不僅對樣本進行自助采樣,還對特征進行隨機選擇(進一步降低相關性)。
-
優勢:
-
比普通 Bagging 更魯棒,抗過擬合能力更強。
-
能處理高維數據,適用于分類和回歸任務。
-
2.1.4. Bagging 的優缺點
? 優點
-
有效減少方差,防止過擬合。
-
適用于高噪聲數據,魯棒性強。
-
可并行訓練,計算效率高。
? 缺點
-
對低偏差、高方差的模型(如線性回歸)提升有限。
-
如果基學習器本身偏差較大,Bagging 可能無法顯著提升性能。
2.2隨機森林
2.2.1. 隨機森林的核心概念
隨機森林是一種基于 Bagging + 決策樹(基學習器是決策樹) 的集成學習方法,通過構建多棵決策樹并綜合它們的預測結果來提高模型的泛化能力。
2.2.2核心特點:
雙重隨機性:樣本隨機(自助采樣),特征隨機(隨機選擇部分特征)
投票機制:分類任務:眾數投票(多數表決),回歸任務:均值預測
2.2.3構造過程:
?2.2.4關鍵問題解答
Q1:為什么要隨機抽樣訓練集?
-
如果所有樹使用相同的訓練數據,會導致所有樹高度相似,失去集成的意義。
-
隨機抽樣?保證每棵樹學習到數據的不同方面,提高多樣性。
Q2:為什么要有放回地抽樣?
-
無放回抽樣會導致每棵樹的訓練數據完全不同,可能引入偏差。
-
有放回抽樣?使不同樹的數據分布相似但不相同,平衡偏差與方差。
2.3包外估計(之前講的自助法)
5. 實際應用示例
(1)隨機森林的 OOB 誤差計算
from sklearn.ensemble import RandomForestClassifier# 啟用 OOB 估計
model = RandomForestClassifier(n_estimators=100, oob_score=True)
model.fit(X_train, y_train)# 輸出 OOB 準確率
print("OOB Score:", model.oob_score_)
(2)特征重要性可視化
import matplotlib.pyplot as plt# 獲取特征重要性
importances = model.feature_importances_# 可視化
plt.barh(range(X.shape[1]), importances)
plt.yticks(range(X.shape[1]), X.columns)
plt.show()
6. 關鍵問題
包外數據能完全替代驗證集嗎?
可以:在隨機森林中,OOB 估計已被證明是無偏的。但:對于超參數調優,建議結合交叉驗證。
2.4隨機森林API和案例
還是和以前一樣,先實例化后使用
?案例:
import numpy as np
import pandas as pd
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import GridSearchCV, train_test_split
from sklearn.datasets import load_breast_cancer # 示例數據集# 1. 加載數據(這里使用sklearn自帶的乳腺癌數據集作為示例)
data = load_breast_cancer()
X = data.data
y = data.target# 2. 劃分訓練集和測試集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)# 3. 初始化隨機森林分類器(啟用OOB估計)
rf = RandomForestClassifier(oob_score=True, random_state=42)# 4. 定義超參數網格
param_grid = {"n_estimators": [120, 200, 300, 500, 800, 1200],"max_depth": [5, 8, 15, 25, 30],"max_features": ["sqrt", "log2"] # 添加特征選擇方式
}# 5. 使用GridSearchCV進行超參數調優
gc = GridSearchCV(estimator=rf,param_grid=param_grid,cv=5, # 使用5折交叉驗證n_jobs=-1, # 使用所有CPU核心verbose=2 # 顯示詳細日志
)# 6. 訓練模型
gc.fit(X_train, y_train)# 7. 輸出最佳參數和模型評估結果
print("\n=== 最佳參數組合 ===")
print(gc.best_params_)print("\n=== 模型評估 ===")
print(f"測試集準確率: {gc.score(X_test, y_test):.4f}")# 8. 獲取最佳模型并輸出OOB得分
best_rf = gc.best_estimator_
print(f"包外估計(OOB)得分: {best_rf.oob_score_:.4f}")
3.通過一個案例,來看一下我們拿到一個數據如何分析:
3.1題目介紹
?
?3.2題目分析:我說一下目前了解到的各個部分
3.1獲取數據?
數據描述,可視化
3.2數據基本處理:選取特征值(部分特征值無用,比如id),對于類別不平衡數據的處理(過采樣和欠采樣),缺失值和異常值的處理,分割數據,將標簽值轉化為數字(這個案例里會講)只有進行數據的可視化才能看到數據是否平衡,有無異常值等,所以可視化很重要。
3.3特征處理:特征預處理(歸一化,標準化),特征提取(字典特征提取,文本特征提取,圖像特征提取),將類別特征轉換為One-hot編碼。
3.4模型訓練:實際上涉及到參數調優,如果算力夠強使用交叉驗證和網格搜索即可,不行的話可以一個一個來,我們這個案例就是。
3.5模型評估
3.3代碼實現
獲取數據,以及數據描述:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.metrics import classification_report, confusion_matrix
from sklearn.utils import class_weight# 加載數據
data = pd.read_csv('./data/otto/train.csv')# 查看數據形狀
print(f"數據集形狀: {data.shape}") # (61878, 95)# 查看數據概覽
print(data.describe())# 可視化類別分布
plt.figure(figsize=(12, 6))
sns.countplot(x='target', data=data)
plt.title('類別分布情況')
plt.xticks(rotation=45)
plt.show()
可以看到標簽值是一個不平衡數據,所以需要進行處理。
數據預處理
?(1)確定特征值和標簽值
# 首先需要確定特征值\標簽值
y = data["target"]
x = data.drop(["id", "target"], axis=1)
(2)類別不平衡問題處理
# 欠采樣獲取數據
from imblearn.under_sampling import RandomUnderSampler
rus = RandomUnderSampler(random_state=0)
X_resampled, y_resampled = rus.fit_resample(x, y)# 圖形可視化,查看數據分布
import seaborn as sns
sns.countplot(y_resampled)
plt.show()
?
看到處理完畢,并且 類別平衡。
(3)標簽值的轉化
使用轉換器即可
from sklearn.preprocessing import LabelEncoder
le = LabelEncoder()
y_resampled = le.fit_transform(y_resampled)
?
(4)分割數據?
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)
?特征工程? ?這個數據全是0-1數據,是被脫敏處理過后的數據,無需再進行處理
模型訓練與評估(這里要使用要求的損失函數)
# 模型訓練
rf = RandomForestClassifier(oob_score=True)#使用包外估計
rf.fit(x_train, y_train)# 模型預測
y_pre = rf.predict(x_test)# 計算模型準確率
accuracy = rf.score(x_test, y_test)
print(f"模型在測試集上的準確率: {accuracy}")# 查看袋外分數
oob_score = rf.oob_score_
print(f"模型的包外估計分數: {oob_score}")
?按要求評估:
from sklearn.metrics import log_loss
log_loss(y_test, y_pre, eps=1e-15, normalize=True)
#normalize是將損失進行歸一化
但我們會發現這樣會報錯,因為 這個評估函數要求輸入是一堆矩陣,y_test是一個one-hot編碼矩陣,后面的也要求是相應的大小。于是我們更改方式:
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))
對于?
y_test.reshape(-1, 1)
?:y_test
?原本可能是一維數組,reshape(-1, 1)
?將其轉換為二維列向量形式,因為?OneHotEncoder
?要求輸入數據是二維數組。
這樣就可以了嗎,但是我們可以通過將預測值的那個矩陣替換為概率矩陣,就是每一行代表一個樣本,每一列是代表這個樣本屬于這個類別的概率:這樣會降低特別大
# 改變預測值的輸出模式,讓輸出結果為百分比概率
y_pre_proba = rf.predict_proba(x_test)# 再次查看袋外分數
oob_score = rf.oob_score_
print(f"模型的袋外分數: {oob_score}")# 第二次 logloss 模型評估
log_loss_value_2 = log_loss(y_test1, y_pre_proba, eps=1e-15, normalize=True)
print(f"第二次計算的 log_loss 值: {log_loss_value_2}")
?參數調優 最好使用網格搜索的方法,當算力小的時候可以如下調優
# 確定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)# 輸出accuracyaccuracy_t[j] = rf2.oob_score_# 輸出log_lossy_pre = rf2.predict_proba(x_test)error_t[j] = log_loss(y_test, y_pre, eps=1e-15, normalize=True)print(error_t)# 優化結果過程可視化
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()
for j, one_parameter in enumerate(tuned_parameters):
相當于給原本的要調優的值加了一個從0開始的索引,方便把每個值存到數組里
注意我們并不是選取數組里最小的作為最優調參,而是通過繪圖查看趨勢,通常選取趨勢平緩的轉折點
之后我們固定這個參數,繼續進行調優,直到全部調優完畢!