隨機森林的 “Bootstrap 采樣” 與 “特征隨機選擇”:如何避免過擬合?(附分類 / 回歸任務實戰)
第一部分:揭開隨機森林的神秘面紗
1.1 告別“過擬合”,擁抱更強大的模型
在機器學習的旅程中,我們常常會遇到一個“敵人”——過擬合(Overfitting)。想象一個學生,他只會死記硬背老師劃定的考試范圍和標準答案。在模擬考試(訓練數據)中,他總能考滿分,因為題目一模一樣。可一旦到了正式考場(測試數據),題目稍微變換一下形式,他就束手無策,成績一落千丈。
這種“在訓練數據上表現完美,但在新數據上表現糟糕”的現象,就是“過擬合”。我們精心構建的模型,就像那個只會死記硬背的學生,失去了泛化到新環境的能力。
決策樹(Decision Tree)模型,作為一種簡單直觀的算法,就很容易陷入過擬合的陷阱。它會不斷地劃分數據,直到把訓練集中的每個樣本都“安排”得明明白白,但這往往導致它學習到了過多的噪音和細節。
那么,如何讓我們的模型變得更“聰明”,而不是一個只會“死記硬背”的書呆子呢?答案是:集成學習(Ensemble Learning)。古話說得好:“三個臭皮匠,頂個諸葛亮”。如果我們能匯集多個模型的智慧,是不是就能得到一個更全面、更穩健的決策?
今天,我們將要學習的主角——隨機森林(Random Forest),正是集成學習中的佼佼者。它通過構建一片由決策樹組成的“森林”,讓每棵樹獨立學習、獨立思考,最后通過集體投票的方式,做出遠比單棵樹更準確、更可靠的判斷。
在本篇博客中,我將牽著你的手,一步步帶你:
- 理解隨機森林為何能有效避免過擬合。
- 掌握其背后的兩大核心技術:Bootstrap 采樣與特征隨機選擇。
- 從零開始,搭建環境,并親手完成一個分類任務和一個回歸任務的實戰。
1.2 什么是隨機森林?—— 一片由決策樹構成的“智慧森林”
隨機森林,顧名思義,它不是單一的模型,而是一個包含了多棵決策樹的“森林”。當有新的數據需要預測時,森林中的每一棵決策樹都會獨立地給出一個自己的判斷。最后,模型會綜合所有樹的“意見”,通過“投票”(分類任務)或“取平均值”(回歸任務)的方式,得出最終的結論。
這就像一個專家委員會在進行會診。每一位專家(決策樹)都有自己的知識背景和判斷依據。面對同一個病例(輸入數據),他們分別給出診斷。最后,委員會采納最多專家支持的診斷結果(分類),或者綜合所有專家的數值評估得出一個平均指標(回歸)。這種集體決策的方式,遠比依賴單一專家的判斷要可靠得多。
我們可以用下面的圖來直觀地理解這個結構:
你可能會問,如果森林里的每一棵樹都長得一模一樣,那和只有一棵樹又有什么區別呢?問得好!這正是隨機森林“隨機”二字的精髓所在。為了讓每一棵樹都具備自己獨特的“視角”,隨機森林在兩個關鍵環節引入了隨機性:
- 訓練樣本的隨機性:每棵樹的訓練數據,都是從原始數據集中隨機抽樣得來的。
- 特征選擇的隨機性:每棵樹在構建時,其內部的每個節點都只能從一部分隨機選擇的特征中進行分裂。
正是這兩個“隨機”的引入,才保證了森林中樹木的多樣性,從而鑄就了模型的強大。接下來,讓我們深入探索這兩大“法寶”。
第二部分:隨機森林避免過擬合的兩大“法寶”
2.1 法寶一:Bootstrap 采樣 —— “我的訓練數據,我做主”
是什么?
Bootstrap 采樣,又稱“自助法采樣”或“有放回抽樣”。它的過程非常簡單:
假設我們有一個包含 N
個樣本的原始數據集。現在,我們想為森林中的第一棵樹準備訓練數據。我們從原始數據集中隨機抽取一個樣本,記錄下來,然后把它放回去。接著,我們再重復這個過程,總共重復 N
次。
這樣,我們就得到了一個同樣包含 N
個樣本的新數據集。由于是“有放回”抽樣,這個新的數據集中,有些原始樣本可能一次都沒被抽到,而有些則可能被抽到了一次、兩次甚至更多次。
隨機森林會為每一棵將要構建的決策樹,都重復進行一次這樣的 Bootstrap 采樣,為它們各自生成一套“專屬”的訓練數據。
為什么?
這樣做最大的好處,就是保證了每棵樹學習的“起點”不同。
如果所有的樹都使用完全相同的訓練數據,它們很可能會學習到相同的模式,犯相同的錯誤,最終長成一模一樣的“克隆樹”,這就失去了集成的意義。
通過 Bootstrap 采樣,每棵樹接觸到的數據都有細微的差別。有的樹可能對某類樣本“見多識廣”,有的則可能從未見過某些樣本。這種差異性,使得每棵樹都成了某一方面的“偏科生”,但當我們將這些“偏科生”的意見匯集起來時,就能得到一個非常全面的“全才”模型。
可視化
下面的流程圖清晰地展示了 Bootstrap 采樣的過程:
graph TDsubgraph 原始數據集 (N個樣本)D1[樣本1]D2[樣本2]D3[樣本3]D4[...]D5[樣本N]endsubgraph 為決策樹1采樣 (得到N個樣本)direction LRS1_1(樣本3)S1_2(樣本1)S1_3(樣本3)S1_4(...)S1_5(樣本N)endsubgraph 為決策樹2采樣 (得到N個樣本)direction LRS2_1(樣本N)S2_2(樣本2)S2_3(樣本1)S2_4(...)S2_5(樣本2)endsubgraph 為決策樹N采樣 (得到N個樣本)direction LRSN_1(...)SN_2(...)endD1 & D2 & D3 & D4 & D5 -- "有放回抽樣" --> S1_1 & S1_2 & S1_3 & S1_4 & S1_5;D1 & D2 & D3 & D4 & D5 -- "有放回抽樣" --> S2_1 & S2_2 & S2_3 & S2_4 & S2_5;D1 & D2 & D3 & D4 & D5 -- "有放回抽樣" --> SN_1 & SN_2;
2.2 法寶二:特征隨機選擇 —— “別讓‘學霸’特征帶偏節奏”
是什么?
如果說 Bootstrap 采樣保證了“輸入”的多樣性,那么特征隨機選擇則保證了“學習過程”的多樣性。
決策樹的構建過程,是在每個節點上,選擇一個“最優”的特征來進行數據劃分。但在隨機森林中,這個規則被修改了。
當某棵決策樹需要在某個節點進行分裂時,它不會考察當前所有的特征。而是,先從所有 M
個特征中,隨機挑選出 m
個特征(通常 m
遠小于 M
),然后再從這 m
個特征中,挑選出最優的一個用于分裂。
這個過程,在樹的每一個分裂節點上,都會重復進行。
為什么?
想象一下,在你的數據集中,有一個“學霸”特征。這個特征的預測能力非常強,如果讓決策樹自由選擇,它很可能在每一個節點都優先使用這個特征,導致森林中大部分樹的結構都變得非常相似,因為它們都嚴重依賴這同一個“學霸”。
這就削弱了我們想要的多樣性。
通過隨機選擇特征,我們“強迫”決策樹在分裂時,給那些不那么強勢的“普通”特征一些機會。也許在某個特定的數據子集中,一個“普通”特征反而能發揮出意想不到的好效果。
這極大地增強了森林中樹木之間的差異性,降低了它們之間的相關性。當所有樹都從不同角度、依據不同特征來進行判斷時,整個森林的魯棒性(Robustness)和泛化能力就得到了質的飛躍。
可視化
下圖展示了在一個決策節點上,特征是如何被隨機選擇的:
graph TDA[所有特征: M個<br/>(Feature 1, Feature 2, ... Feature M)] --> B{在節點分裂時};B -- "隨機選擇m個特征" --> C[候選特征子集<br/>(Feature 3, Feature 8, ...)];C -- "從中選擇最優特征" --> D[使用Feature 8進行分裂];
2.3 總結:兩大“法寶”如何協同工作
現在,我們將這兩個強大的工具結合起來,看看一個完整的隨機森林是如何被構建并避免過擬合的:
- 樣本隨機:通過 Bootstrap 采樣,為每棵樹生成一份獨特的數據“教材”,保證了樹與樹之間的“宏觀”差異性。
- 特征隨機:在每棵樹的生長過程中,通過限制節點分裂時的候選特征,保證了樹內部結構的“微觀”差異性。
這兩個機制共同作用,確保了森林中的每一棵樹都是獨一無二的。它們從不同的數據、不同的角度學習,最終匯集成的集體智慧,自然就擁有了強大的泛化能力,能夠很好地抵抗過擬合。
第三部分:萬事俱備,只欠“實戰”
理論講完了,是時候動手實踐了!一個好的程序員,不僅要懂理論,更要能動手。接下來,我們一起搭建一個簡單的機器學習環境。
3.1 環境準備:搭建你的機器學習實驗室
第一步:擁有 Python
首先,你需要一個 Python 環境。現代的操作系統通常都內置了 Python。你可以在終端(Terminal 或命令提示符)中輸入以下命令來檢查:
python --version
# 或者
python3 --version
如果能看到版本號(如 Python 3.9.7
),說明你已經安裝好了。如果沒有,請前往 Python 官方網站 下載并安裝最新版本。
第二步:創建虛擬環境(強烈推薦)
為什么需要虛擬環境? 想象一下,你同時在做兩個項目,項目 A 需要 1.0 版本的工具庫,而項目 B 需要 2.0 版本。如果你把它們都裝在系統里,就會產生沖突。虛擬環境就像是為每個項目創建了一個獨立的、干凈的“實驗室”,項目 A 的工具放在 A 實驗室,項目 B 的工具放在 B 實驗室,它們互不干擾。
讓我們為本次實戰創建一個名為 rf_env
的虛擬環境:
# 1. 創建虛擬環境
python3 -m venv rf_env# 2. 激活虛擬環境
# 在 macOS / Linux 上:
source rf_env/bin/activate
# 在 Windows 上:
.\\rf_env\\Scripts\\activate
激活成功后,你會看到終端提示符前面多了 (rf_env)
的字樣,這表示你已經進入了這個獨立的“實驗室”。
第三步:安裝必備工具庫
在這個虛擬環境中,我們需要安裝幾個核心的 Python 庫:
scikit-learn
:這是我們今天的主角,一個強大的機器學習庫,隨機森林模型就在其中。pandas
:用于數據處理和分析,可以方便地讀取和操作數據。numpy
:Python 科學計算的基礎庫,提供了高效的數組操作。
使用 pip
(Python 的包管理器)來安裝它們:
pip install scikit-learn pandas numpy
等待安裝完成,我們的實驗環境就準備就緒了!
第四部分:實戰演練:從零開始構建你的隨機森林模型
現在,讓我們用剛剛學到的知識,來解決兩個真實的機器學習問題。
4.1 任務一:隨機森林分類實戰(以“鳶尾花”分類為例)
鳶尾花數據集是一個非常經典的分類任務數據集,它包含了三種不同鳶尾花(Setosa, Versicolour, Virginica)的四個特征(花萼長度、花萼寬度、花瓣長度、花瓣寬度)。我們的任務是構建一個模型,根據這些特征來判斷鳶尾花屬于哪個種類。
第一步:創建代碼文件
在你喜歡的位置,創建一個名為 classification_test.py
的 Python 文件,然后我們開始編寫代碼。
第二步:編寫代碼
# classification_test.py# 1. 導入我們需要的工具庫
import pandas as pd
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score# 2. 加載數據并了解它
# scikit-learn 內置了鳶尾花數據集,我們可以直接加載
iris = load_iris()
# 為了方便觀察,我們把它轉換成 pandas 的 DataFrame 格式
# 特征數據
X = pd.DataFrame(iris.data, columns=iris.feature_names)
# 目標類別 (我們要預測的值)
y = pd.Series(iris.target)# 打印前5行數據看看
print("--- 特征數據 (前5行) ---")
print(X.head())
print("\\n--- 目標數據 (前5行) ---")
print(y.head())# 3. 劃分數據集
# 將數據劃分為 訓練集 和 測試集,這是評估模型性能的標準流程
# test_size=0.3 表示我們將30%的數據用作測試,剩下的70%用作訓練
# random_state 是一個隨機種子,保證每次劃分的結果都一樣,便于復現
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
print(f"\\n訓練集大小: {X_train.shape[0]} a, 測試集大小: {X_test.shape[0]}")# 4. “反面教材”:訓練一個單獨的決策樹
print("\\n--- 訓練單個決策樹 ---")
# 創建一個決策樹分類器實例
single_tree = DecisionTreeClassifier(random_state=42)
# 使用訓練數據來訓練模型
single_tree.fit(X_train, y_train)# 使用訓練好的模型在 測試集 和 訓練集 上進行預測
pred_tree_test = single_tree.predict(X_test)
pred_tree_train = single_tree.predict(X_train)# 評估模型性能
acc_tree_test = accuracy_score(y_test, pred_tree_test)
acc_tree_train = accuracy_score(y_train, pred_tree_train)
print(f"單個決策樹 - 訓練集準確率: {acc_tree_train:.4f}")
print(f"單個決策樹 - 測試集準確率: {acc_tree_test:.4f}")
# 你可能會發現,訓練集準確率是1.0,這正是過擬合的跡象!# 5. “正面典型”:訓練一個隨機森林
print("\\n--- 訓練隨機森林 ---")
# 創建一個隨機森林分類器實例
# n_estimators=100 表示這個森林里有100棵決策樹
# random_state 同樣是為了結果可復現
random_forest = RandomForestClassifier(n_estimators=100, random_state=42)
# 使用訓練數據來訓練模型
random_forest.fit(X_train, y_train)# 使用訓練好的模型在 測試集 和 訓練集 上進行預測
pred_rf_test = random_forest.predict(X_test)
pred_rf_train = random_forest.predict(X_train)# 評估模型性能
acc_rf_test = accuracy_score(y_test, pred_rf_test)
acc_rf_train = accuracy_score(y_train, pred_rf_train)
print(f"隨機森林 - 訓練集準確率: {acc_rf_train:.4f}")
print(f"隨機森林 - 測試集準確率: {acc_rf_test:.4f}")
第三步:運行與結果分析
在你的終端中(確保你仍處于 rf_env
虛擬環境中),運行這個文件:
python classification_test.py
你會看到類似下面的輸出:
--- 特征數據 (前5行) ---sepal length (cm) sepal width (cm) petal length (cm) petal width (cm)
0 5.1 3.5 1.4 0.2
1 4.9 3.0 1.4 0.2
2 4.7 3.2 1.3 0.2
3 4.6 3.1 1.5 0.2
4 5.0 3.6 1.4 0.2--- 目標數據 (前5行) ---
0 0
1 0
2 0
3 0
4 0
dtype: int64訓練集大小: 105, 測試集大小: 45--- 訓練單個決策樹 ---
單個決策樹 - 訓練集準確率: 1.0000
單個決策樹 - 測試集準確率: 1.0000--- 訓練隨機森林 ---
隨機森林 - 訓練集準確率: 1.0000
隨機森林 - 測試集準確率: 1.0000
結果分析
在這個特定的例子中,因為鳶尾花數據集非常“干凈”且易于區分,你會發現單個決策樹和隨機森林在測試集上的表現都達到了 100% 的準確率。但請注意關鍵的一點:單個決策樹在訓練集上的準確率是 1.0000
,這說明它完美地“記住”了所有訓練數據,這是一個典型的過擬合信號。雖然在這個簡單任務上它的測試表現也很好,但在更復雜、噪音更多的數據集上,這種過擬合會嚴重影響其泛化能力。
而隨機森林,同樣在訓練集上表現完美,但它的完美是建立在“集體智慧”上的,其模型本質上比單個決策樹要穩健得多。讓我們在下一個回歸任務中,更清晰地看到這種差異。
4.2 任務二:隨機森林回歸實戰(以“加州房價”預測為例)
現在,我們來挑戰一個更復雜的任務:根據加州各個街區的人口、收入等特征,來預測房價的中位數。這是一個回歸問題。
第一步:創建代碼文件 regression_test.py
第二步:編寫代碼
# regression_test.py# 1. 導入工具庫
import pandas as pd
from sklearn.datasets import fetch_california_housing
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeRegressor
from sklearn.ensemble import RandomForestRegressor
from sklearn.metrics import mean_squared_error
import numpy as np# 2. 加載數據
housing = fetch_california_housing()
X = pd.DataFrame(housing.data, columns=housing.feature_names)
y = pd.Series(housing.target)# 3. 劃分數據集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)# 4. 訓練單個決策樹回歸模型
print("--- 訓練單個決策樹回歸模型 ---")
tree_reg = DecisionTreeRegressor(random_state=42)
tree_reg.fit(X_train, y_train)# 在測試集上進行預測
pred_tree = tree_reg.predict(X_test)
# 使用“均方根誤差 (RMSE)”來評估模型
# RMSE 越小,說明模型的預測值與真實值的差異越小,模型越好
tree_rmse = np.sqrt(mean_squared_error(y_test, pred_tree))
print(f"單個決策樹 - 測試集 RMSE: {tree_rmse:.4f}")# 5. 訓練隨機森林回歸模型
print("\\n--- 訓練隨機森林回歸模型 ---")
forest_reg = RandomForestRegressor(n_estimators=100, random_state=42)
forest_reg.fit(X_train, y_train)# 在測試集上進行預測
pred_forest = forest_reg.predict(X_test)
# 同樣使用 RMSE 進行評估
forest_rmse = np.sqrt(mean_squared_error(y_test, pred_forest))
print(f"隨機森林 - 測試集 RMSE: {forest_rmse:.4f}")# 6. 對比結果
print(f"\\n隨機森林相比單個決策樹,誤差降低了: {((tree_rmse - forest_rmse) / tree_rmse) * 100:.2f}%")
第三步:運行與結果分析
在終端中運行:
python regression_test.py
你將看到如下輸出:
--- 訓練單個決策樹回歸模型 ---
單個決策樹 - 測試集 RMSE: 0.7107--- 訓練隨機森林回歸模型 ---
隨機森林 - 測試集 RMSE: 0.5034隨機森林相比單個決策樹,誤差降低了: 29.17%
結果分析
這次的結果對比就非常明顯了!
- 單個決策樹模型的均方根誤差(RMSE)是
0.7107
。 - 隨機森林模型的均方根誤差(RMSE)是
0.5034
。
隨機森林的預測誤差降低了近 30%!這清晰地證明,在處理更復雜的回歸問題時,由多棵樹組成的森林通過集成學習,顯著地減少了由單個決策樹過擬合帶來的誤差,得到了一個預測能力更強、更可靠的模型。