owned this note
owned this note
Published
Linked with GitHub
# 8_決策樹
## Source
<!-- - 大數據分析與資料探勘 W11 -->
- 應⽤機器學習於Python C2-4
## 課堂投影片與練習
- [ ] **今日學習目標**
* 理解決策樹的基本概念與工作原理。
* 認識決策樹的優點與限制。
* 掌握決策樹在實際問題中的應用。
* 透過Python實作,建立並評估決策樹模型。
---
- [ ] **決策樹概念:為什麼需要它?**
* **情境:** 假設你是一位披薩公司(如:必勝客、達美樂)的資料科學家,成功建立了一個分類模型,能預測披薩是美味還是難吃的披薩。
* 你的模型決策邊界是數學公式:`-100 + 6*溫度 + 3*濕度 = 0`。
* 當你向主管和製作披薩的人員講解此模型時,他們可能會覺得「你到底在說什麼啊?」甚至覺得「你根本不懂怎麼烤披薩的基本知識」。因為只給數學公式,大家常常無法理解模型是如何做出判斷的。
* **問題:** 如何才能讓你的模型更容易被理解,並贏得業務團隊的信任呢?
* **答案:** 模型的**「可解釋性 (Interpretability)」**在真實環境中非常重要。而**決策樹就是一個解釋性能力很強的模型**。
---
- [ ] **決策樹概念:什麼是決策樹?**
* **定義:** 決策樹它是一個**樹型結構的分類演算法**。它也具有**監督式的特徵萃取與描述**功能,能將輸入變數根據目標設定選擇分枝方式,並以樹枝狀的層級架構呈現,萃取分類規則。
* **核心思想:** 決策樹的分類主要是從最上方的**樹根**開始,將特徵變數切分到不同的子樹或節點,透過不斷的切割,最終成為具有決策節點的**葉節點**。
* **範例解釋:** 與其說「-100 + 6\*溫度 + 3\*濕度 > 0 預測是一個美味的披薩」,不如說「在烘烤披薩過程中,只要**溫度維持在120~125度,濕度維持在5%~7%**,就會是一個很美味的披薩」。這樣的話,老闆跟製作Pizza人員就能立即理解。
---
- [ ] **決策樹概念:決策樹的組成**
* **樹根 (Root Node):**
* 位於決策樹的最上方,是資料切分的起點。
* 例如:在鐵達尼號生存率的分析中,根節點可能是「性別 (Sex)」。
* **決策節點 / 內部節點 (Internal Node / Decision Node):**
* 位於樹的中間,用於根據特徵變數進行切分,引導資料流向不同的子節點。
* **葉節點 (Leaf Node):**
* 位於樹的最底層,沒有子節點,通常代表最終的決策結果或分類結果。
* 例如:鐵達尼號範例中,葉節點會顯示出「存活率最高的是誰」或「存活率較低的人來自哪裡」等決策結果。
---
- [ ] **決策樹概念:如何進行切分?**
* **目標:** 在決策樹中,我們希望**每一個節點的「資訊增益 (Information Gain)」都是最大的**。資訊增益就是**原始資料的總訊息減去分枝後的總訊息**。
* **常用衡量方法:**
* **熵 (Entropy):**
* 來自熱力學的量,用來計算一個系統中失序的現象。
* 套用到資訊理論上,它代表了**接收每條信息中包含的資訊平均量**。熵值越高,表示資料的離散程度或亂度越高,**不確定性越大**。
* **Gini 係數 (Gini Index):**
* 衡量資料集合對於所有類別的**不純度 (impurity)**。
* 我們希望透過分枝,能最大幅度地**減少不純度**。
* **Scikit-Learn 的預設演算法:** Python 中常用的機器學習套件 Scikit-Learn 所使用的決策樹演算法是 **CART (Classification and Regression Tree)**,它主要以 **Gini 係數**作為決定分枝變數的準則,建立**二分式**的決策樹。
---
- [ ] **課堂練習:決策樹概念理解**
1. **任務:** 請用自己的話解釋「決策樹」是什麼,以及它與我們之前學過的「羅吉斯迴歸」模型相比,最大的優勢是什麼?
2. **任務:** 決策樹的「葉節點」代表什麼?請舉一個除了鐵達尼號生存率之外的例子來解釋。
3. **思考:** 為什麼決策樹在進行節點切分時,會希望「資訊增益」最大化?這和我們想要「好好分類」有什麼關係?
---
- [ ] **決策樹的優點**
* **高可解釋性 (Interpretability):**
* 模型結果直觀,易於理解和解釋。
* 可以直接提出「如果...則...」的規則,便於與非技術人員溝通和分享模型洞察。
* **容易提取規則:**
* 從決策樹的結構中,可以輕鬆地歸納出分類或預測的規則。
* **適用於多類別變數的大型資料集:**
* 能處理包含許多類別型特徵的大型資料集。
* **無需特徵縮放:**
* 決策樹對數值型特徵的尺度不敏感,通常不需要像其他模型一樣進行標準化或正規化。 (非來源資訊,可提醒學生)
---
- [ ] **決策樹的缺點**
* **抗噪性較低:**
* 容易受到資料中**雜訊 (Noise)** 的影響。
* **容易過度學習 (Overfitting) 或低度學習 (Underfitting):**
* **樹越大,過度學習的機率會較高**。這表示模型在訓練資料上表現很好,但在新的、未見過的資料上表現很差。
* **樹越小,低度學習的機率也會較高**。這表示模型無法充分捕捉資料中的模式,導致在訓練資料和新資料上表現都不好。
* **結果穩定性較差:**
* 決策樹的結果常常會因為特徵數量的微小不同而產生不一樣的變化。這使得模型在面對略微變化的資料時,其決策邊界可能會劇烈變化。
* **對不平衡資料敏感:** (非來源資訊,可提醒學生)
* 如果某些類別的樣本數量遠多於其他類別,決策樹可能會偏向多數類別,導致對少數類別的預測效果不佳。
---
- [ ] **決策樹的優化:修剪 (Pruning)**
* 為了解決過度學習和低度學習的問題,在決策樹中有一種叫做**「修剪 (Pruning)」**的技術。
* **預先修剪 (Pre-pruning):**
* 這是我們**比較常使用**的一種方法。
* 它在決策樹**生長過程中**就設定停止門檻值。當分割的評估值未達到此門檻時,就會停止生長。例如:設定資訊增益比必須大於0.1,或資料比數要超過5才繼續分枝。
* **優點:** 執行效率較高,且能提早停止學習,常常就可以幫助我們**解決過度學習的問題**。
* **缺點:** 可能導致**過度修剪 (over-pruning)**,且門檻值設定不易。
* **事後修剪 (Post-pruning):**
* 讓決策樹可以隨意發展,**等樹都長完了**,我們才用專業的知識去判斷是否將多餘的分支剪掉。
* **優點:** 可避免過度修剪,以及加強對雜訊的忍受程度。
* **缺點:** 效率較低。
* **常見方法:** 最小成本複雜度修剪 (Minimal Cost-Complexity Pruning)。
---
- [ ] **課堂練習:優缺點與修剪**
1. **情境題:** 你的公司想要用機器學習模型來預測客戶流失,並且希望業務團隊能夠理解這個模型,以便他們採取精準的挽留措施。請問你會推薦使用決策樹嗎?為什麼?
2. **思考題:** 什麼是「過度學習 (Overfitting)」?如果你的決策樹模型發生了過度學習,你會考慮採取哪種「修剪」策略來改善?為什麼?
3. **討論:** 決策樹的結果穩定性較差,這可能帶來什麼潛在問題?
---
- [ ] **決策樹的應用場景**
* **分類問題 (Classification):**
* **鐵達尼號生存率分析:** 根據乘客的性別、艙等、年齡等特徵預測其是否存活。
* **鳶尾花分類:** 根據花萼和花瓣的長度、寬度將鳶尾花分為不同品種。
* **員工績效評估:** 根據年資、教育程度、經驗等預測員工績效等級。
* **迴歸問題 (Regression):**
* **波士頓房價預測:** 根據房屋特徵預測房價。
* **薪資預測:** 根據員工特徵預測月收入。
* **其他常見應用:**
* 客戶行為分析 (例如:判斷客戶是否會購買某產品)
* 醫療診斷輔助 (例如:根據症狀判斷疾病)
* 風險評估 (例如:判斷貸款申請人的信用風險)
---
- [ ] **實作範例:鐵達尼號生存預測 (分類問題)**
* **資料集介紹:** 鐵達尼號生存範例是一個**很經典的分類教學問題**,非常適合用決策樹的數學模型來解釋分類如何操作。
* **變數包含:** 存活與否 (Survived)、船票等級 (Pclass)、性別 (Sex)、年齡 (Age)、配偶/手足人數 (SibSp)、父母/小孩人數 (Parch)、票價 (Fare)、船艙號 (Cabin) 以及登船港口 (Embarked) 等。
* **流程概覽:** 我們將依循資料分析的標準流程:**資料探索 -> 資料前處理 -> 模型建立 -> 模型評估 -> 結果匯出**。
* **程式碼環境:** 我們將使用 **Python 語言搭配 Scikit-learn 函式庫**來進行實作。
---
- [ ] **實作範例:資料探索 - 載入與概覽**
* 通常在資料分析比賽中,主辦單位會提供資料下載連結或直接存取URL,這樣就不需要把非常大的資料集下載到本機端。
* 我們將利用 `pandas` 函式庫來載入資料,並使用 `info()` 和 `head()` 兩個常用函數來快速查看資料的結構和前幾筆內容,以初步了解資料的狀況。
```python=
# 匯入必要的函式庫
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
df_train = pd.read_csv('train.csv')
print("--- 資料概覽 (df_train.info()) ---")
df_train.info()
print("\n--- 資料前五筆 (df_train.head()) ---")
df_train.head()
```
---
**實作範例:資料探索 - 視覺化分析**
* 透過視覺化圖表,我們可以更直觀地理解資料分佈和變數間的關係。這是資料分析中非常重要的一步!
* **範例:**
* **不同登船口的分佈:** 我們發現大多數人都是從 'S' 這個登船口登船的。
* **不同性別的存活人數分析:** 可以觀察到女性的存活率通常比男性高,這與當時「老弱婦孺優先上救生船」的常識相符。
* **不同艙等的存活率:** 頭等艙 (Pclass=1) 的存活率通常是最高的,因為他們有最高的優先順序,且更接近救生船。
* **家庭人數與存活率:** 資料探索顯示,隨着家庭人數的增加,存活率會降低,尤其有5到8個成員的大家庭,存活率甚至可能是0。
```python=
#不同登船口的分佈
sns.countplot(x='Embarked', data=df_train)
plt.title('不同登船口的分佈')
plt.show()
#不同性別的存活人數分析
sns.countplot(x='Sex', hue='Survived', data=df_train)
plt.title('不同性別的存活人數分析')
plt.show()
#不同艙等的存活率
sns.countplot(x='Pclass', hue='Survived', data=df_train)
plt.title('不同艙等的存活率')
plt.show()
#家庭人數與存活率
sns.countplot(x='SibSp', hue='Survived', data=df_train)
plt.title('家庭人數與存活率')
plt.show()
```
---
- [ ] **實作範例:資料前處理 - 處理缺失值**
* 在現實世界的資料中,經常會遇到**缺失值 (NA/NaN)** 的情況,需要進行處理,否則模型可能無法正確運行。
* **年齡 (Age):**
* 這個欄位有較多缺失值。
* 由於年齡分佈很廣 (從兒童到80歲的長者),如果使用平均數來填充,容易引入偏差。
* 因此,我們決定使用**中位數 (Median)** 來取代缺失值,因為中位數對極端值不那麼敏感。
* **登船港口 (Embarked):**
* 這個欄位也有一些缺失值。
* 在資料探索時我們發現,從 'S' 這個登船口登船的人數最多。
* 因此,我們假設缺失的登船口最可能是 'S',並使用**眾數 (Mode)** 'S' 來填充。
* **船艙號 (Cabin):**
* 這個欄位的缺失值非常多,甚至超過了資料的一半。
* 根據常識,船艙號可能與艙等 (`Pclass`) 和票價 (`Fare`) 有關聯,但在我們的分析中,其重要性可能較低。
* 因此,為了簡化模型,我們決定直接**刪除 (Drop)** 此欄位。
```python=
# 處理缺失值
# 1. 年齡 (Age):用中位數取代
df_train['Age'].fillna(df_train['Age'].median(), inplace=True)
# 2. 登船港口 (Embarked):用眾數 'S' 取代
# 先找到眾數
most_frequent_embarked = df_train['Embarked'].mode()
df_train['Embarked'].fillna(most_frequent_embarked, inplace=True)
# 3. 船艙號 (Cabin):直接刪除
df_train.drop('Cabin', axis=1, inplace=True)
# 檢查是否還有缺失值
print("處理缺失值後,各欄位的缺失值數量:")
print(df_train.isnull().sum())
```
---
- [ ] **實作範例:資料前處理 - 特徵工程與編碼**
* **建立新特徵 (Feature Engineering):**
* 有時候原始資料的特徵不足以表達其潛在關係,我們需要**創造新的特徵**。
* **`TravelAlong` (是否有家人同行):** 我們觀察到 `SibSp` (手足/配偶人數) 和 `Parch` (父母/小孩人數) 這兩個欄位都代表了家庭成員的資訊。在逃生時,家人是兄弟姐妹或父母都一樣重要。因此,我們將這兩個變數合併為一個新的變數 `TravelAlong`。如果 `SibSp` 或 `Parch` 任何一個不為0,則 `TravelAlong` 為1 (表示有家人同行),否則為0 (表示獨自一人)。
* 建立新特徵後,原始的 `SibSp` 和 `Parch` 欄位就可以刪除,因為它們的資訊已被 `TravelAlong` 取代。
* **刪除不必要的特徵:**
* 像 `PassengerId` (乘客ID)、`Name` (姓名)、`Ticket` (船票號碼) 這些欄位屬於**個人資料**,在做資料分析時通常**不重要**,因為它們與生存與否的模式關係不大,因此我們會將它們刪除。
* **類別變數編碼 (Encoding):**
* 機器學習模型通常只能處理**數值型資料**。因此,我們需要將像「性別 (Sex)」和「登船港口 (Embarked)」這樣的**類別變數 (Categorical Variables)** 轉換為數值。
* 我們可以使用 Scikit-learn 中的 **`LabelEncoder`** 來完成這件事。它會將不同的類別映射為不同的整數數字。例如,性別可能轉換為0和1 (男/女),登船港口可能轉換為0, 1, 2。
```python=
from sklearn.preprocessing import LabelEncoder
# 1. 建立新特徵 'TravelAlong'
# 如果 SibSp 或 Parch 任何一個大於 0,則 TravelAlong 為 1,否則為 0
df_train['TravelAlong'] = ((df_train['SibSp'] > 0) | (df_train['Parch'] > 0)).astype(int)
# 2. 刪除不必要的特徵 (包括已被 TravelAlong 取代的 SibSp 和 Parch)
df_train.drop(['PassengerId', 'Name', 'Ticket', 'SibSp', 'Parch'], axis=1, inplace=True)
# 3. 類別變數編碼
# 性別 (Sex)
le_sex = LabelEncoder()
df_train['Sex'] = le_sex.fit_transform(df_train['Sex']) # male/female -> 0/1
# 登船港口 (Embarked)
le_embarked = LabelEncoder()
df_train['Embarked'] = le_embarked.fit_transform(df_train['Embarked']) # C/Q/S -> 0/1/2
print("\n--- 前處理後資料前五筆 (df_train.head()) ---")
print(df_train.head())
print("\n--- 前處理後資料型態 (df_train.info()) ---")
df_train.info()
```
---
- [ ] **實作範例:模型建立與訓練**
* **區分特徵 (X) 和目標變數 (Y):**
* `X` 代表我們用來預測的**輸入特徵** (例如:艙等、性別、年齡、票價等)。
* `Y` 代表我們想要預測的**目標變數** (在本例中是「存活」與否)。
* **劃分訓練集與測試集:**
* 在建立模型之前,我們會將資料劃分為**訓練集 (Training Set)** 和**測試集 (Testing Set)**。
* 訓練集用於**模型學習**資料中的模式,而測試集則用於**評估模型在未知資料上的表現**,模擬真實世界的預測情況。
* 通常,訓練集佔總資料的70%,測試集佔30%。
* **載入決策樹分類器:**
* 我們從 `sklearn.tree` 中匯入 `DecisionTreeClassifier`。
* **設定模型參數:**
* `max_depth` (樹的最大深度) 是決策樹非常重要的參數,它**可以限制樹的生長,防止過度學習**。
* 我們不知道樹的最佳深度應該是多深,所以會透過迴圈來設定從2到10的不同深度,看看哪一個深度能讓模型有最好的分類表現。
```python=
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score
# 定義特徵 (X) 和目標變數 (Y)
# 移除 'Survived' 欄位作為特徵,保留其他欄位
X = df_train.drop('Survived', axis=1)
# 將 'Survived' 欄位作為目標變數
Y = df_train['Survived']
# 劃分訓練集和測試集
# test_size=0.3 表示測試集佔總資料的30%
# random_state=42 用於確保每次運行結果一致 (可重現性)
X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size=0.3, random_state=42)
print(f"訓練集特徵 (X_train) 樣本數: {len(X_train)}")
print(f"訓練集目標 (Y_train) 樣本數: {len(Y_train)}")
print(f"測試集特徵 (X_test) 樣本數: {len(X_test)}")
print(f"測試集目標 (Y_test) 樣本數: {len(Y_test)}")
```
---
- [ ] **實作範例:尋找最佳樹深度**
* 為了找到最適合模型的樹深度,我們可以建立一個迴圈,**嘗試不同的 `max_depth` 設定 (從2到10)**,並評估每個模型的表現。
* **評估指標:**
* **Accuracy (準確率):** 最直觀的指標,表示模型正確預測的比例。
* **Precision (精確率)、Recall (召回率)、F1-Score (F1分數):** 這些是更全面的分類模型評估指標。例如,**精確率**關注在所有被模型預測為正類別的樣本中,有多少是真正屬於正類別的;**召回率**則關注所有真正屬於正類別的樣本中,有多少被模型成功找出。
```python=
# 嘗試不同深度來尋找最佳模型
best_accuracy = 0
best_depth = 0
model_performance = [] # 用來儲存不同深度模型的表現
# 迴圈從深度 2 到 10 (range(2, 11) 表示從 2 開始,到 10 結束)
for depth in range(2, 11):
# 建立決策樹分類器模型,設定當前迴圈的 max_depth
dt_classifier = DecisionTreeClassifier(max_depth=depth, random_state=42)
# 使用訓練資料來擬合 (Fit) 模型
dt_classifier.fit(X_train, Y_train)
# 使用測試資料來進行預測 (Predict)
Y_pred = dt_classifier.predict(X_test)
# 計算模型的評估指標
accuracy = accuracy_score(Y_test, Y_pred)
# precision_score 和 recall_score 在某些情況下如果只有一類被預測到,可能會產生警告
# zero_division=0 參數可以避免這個警告,並將該指標值設為 0
precision = precision_score(Y_test, Y_pred, zero_division=0)
recall = recall_score(Y_test, Y_pred, zero_division=0)
f1 = f1_score(Y_test, Y_pred, zero_division=0)
# 將結果儲存到列表中
model_performance.append({
'Depth': depth,
'Accuracy': accuracy,
'Precision': precision,
'Recall': recall,
'F1-Score': f1
})
# 如果當前模型的準確率比之前的最佳準確率更高,則更新最佳深度和準確率
if accuracy > best_accuracy:
best_accuracy = accuracy
best_depth = depth
print("\n--- 不同樹深度下的模型表現 ---")
for perf in model_performance:
# 格式化輸出,保留四位小數
print(f"深度: {perf['Depth']}, 準確率: {perf['Accuracy']:.4f}, "
f"精確率: {perf['Precision']:.4f}, 召回率: {perf['Recall']:.4f}, "
f"F1分數: {perf['F1-Score']:.4f}")
print(f"\n根據準確率,最佳樹深度為: {best_depth} (準確率: {best_accuracy:.4f})")
# 根據來源的範例,最佳深度為6,準確率為0.813433
```
---
- [ ] **課堂練習:模型訓練與評估**
1. **任務:** 在Python程式碼中,`test_size=0.3` 代表什麼意思?為什麼我們需要將資料劃分為訓練集和測試集?
2. **任務:** 試著將 `random_state` 的值改為不同的數字 (例如:1、10、100),重新執行程式碼,觀察最佳深度和準確率是否有變化。這說明了什麼?
3. **討論:** 如果我們在預測疾病時,希望盡量找出所有真正患病的病人 (即使會有一些誤報),那麼在評估模型時,你會更關注「精確率 (Precision)」、「召回率 (Recall)」還是「準確率 (Accuracy)」?為什麼?
---
- [ ] **實作範例:模型視覺化**
* 訓練好的決策樹模型可以被視覺化,這對於理解模型的決策邏輯非常有幫助。
* **`export_graphviz`:** Scikit-learn 提供這個函數,可以將決策樹輸出為 **`.dot` 格式的檔案**。這個 `.dot` 檔案可以被 **Graphviz 軟體**讀取並轉換成圖片檔 (例如:`.png` 或 `.jpg`)。
* **如何解讀決策樹圖:**
* 圖中的每個方框代表一個**節點**。
* 每個節點內會顯示用於切分的**特徵條件** (例如:`Sex <= 0.5`)。
* 根據條件判斷結果,資料會流向左邊 (通常是 `True`) 或右邊 (通常是 `False`) 的子節點。
* 最終到達的**葉節點**會顯示該路徑下的**最終分類結果** (例如:`class = Survived`)。
**安裝必要套件 (Graphviz + pydotplus)**
```cmd=
# 安裝套件(Graphviz + pydotplus)
!apt-get install -y graphviz
!pip install graphviz pydotplus
```
**模型視覺化**
```python=
# 匯入套件
from sklearn.tree import export_graphviz
import pydotplus
from IPython.display import Image
import graphviz
# 重新訓練最佳模型(保險起見)
final_dt_classifier = DecisionTreeClassifier(max_depth=best_depth, random_state=42)
final_dt_classifier.fit(X_train, Y_train)
# 特徵與類別名稱
feature_names = X.columns.tolist()
class_names = ['Not Survived', 'Survived']
# ➤ 1. 儲存為 .dot 檔案(可供下載或外部轉檔用)
export_graphviz(
final_dt_classifier,
out_file='titanic_decision_tree.dot',
feature_names=feature_names,
class_names=class_names,
filled=True,
rounded=True,
special_characters=True
)
print(".dot 檔案已儲存為 'titanic_decision_tree.dot'")
# ➤ 2. 轉成圖形,inline 顯示在 Colab/Jupyter 中
dot_data = export_graphviz(
final_dt_classifier,
out_file=None,
feature_names=feature_names,
class_names=class_names,
filled=True,
rounded=True,
special_characters=True
)
graph = pydotplus.graph_from_dot_data(dot_data)
Image(graph.create_png())
```
---
**實作範例 : 模型預測**
* 假設我們要預測一個新的乘客是否存活,這裡我們需要輸入所有特徵的值,並且確保順序和前處理方式一致
* 假設新乘客資料: Pclass=3, Sex=male(1), Age=30, Fare=15, Embarked=S(2), TravelAlong=0(No)
* **注意:這裡的數值必須是經過 LabelEncoder 編碼後的結果**
```python=
new_passenger_features = pd.DataFrame([
[3, 1, 30, 15.0, 2, 0]],
columns=['Pclass', 'Sex', 'Age', 'Fare', 'Embarked', 'TravelAlong'])
# 使用訓練好的模型進行預測
predicted_survival = final_dt_classifier.predict(new_passenger_features)
# 使用 predict_proba 估計屬於各類別的機率
predicted_proba = final_dt_classifier.predict_proba(new_passenger_features)
print(f"\n預測新乘客的存活結果: {predicted_survival} (0: 死亡, 1: 存活)")
print(f"預測為死亡的機率: {predicted_proba[0][0]:.4f}")
print(f"預測為存活的機率: {predicted_proba[0][1]:.4f}")
```
---
- [ ] **課堂練習:決策樹視覺化與解讀**
1. **任務:** 如果你成功將決策樹視覺化成圖片,請試著追蹤一條從根節點到任意一個葉節點的路徑。這條路徑代表了什麼樣的決策規則?
2. **任務:** 解釋 `predict_proba()` 函式在決策樹中的作用是什麼?它提供了什麼額外的信息?
3. **思考:** 除了 `max_depth` 之外,`DecisionTreeClassifier` 還有哪些參數可以控制樹的形狀?(例如:`min_samples_leaf`、`max_leaf_nodes`)。這些參數對決策樹的生長和過度學習問題有何影響?
---
- [ ] **實作範例:迴歸問題 - 波士頓房價預測**
* 決策樹不僅可以用於**分類問題**,也可以用於**迴歸問題**,這時我們使用**決策樹迴歸器 (DecisionTreeRegressor)**。
* **模型:** 從 `sklearn.tree` 匯入 `DecisionTreeRegressor`。
* **評估指標:**
* 迴歸模型的績效通常以**最小極值**為主。
* 常見的迴歸評估指標包括:
* **R² (決定係數):** 衡量模型解釋變異的比例,值越接近1越好。
* **MAE (平均絕對誤差):** 預測值與真實值之間絕對差值的平均值,越小越好。
* **MSE (均方誤差):** 預測值與真實值之間平方差值的平均值,對大誤差懲罰較重,越小越好。
* **RMSE (均方根誤差):** MSE 的平方根,與原始數據單位相同,越小越好。
* **表現:** 決策樹做迴歸分析**未必會比線性模型差,有時候還會有比較好的表現**。
```python=
from sklearn.tree import DecisionTreeRegressor
from sklearn.metrics import r2_score, mean_absolute_error, mean_squared_error
from sklearn.model_selection import train_test_split
import pandas as pd
import numpy as np
# 載入波士頓房價資料集 (在較新版本的 sklearn 中已被移除,這裡使用 try-except 或模擬數據)
try:
from sklearn.datasets import load_boston
boston = load_boston()
X_boston = pd.DataFrame(boston.data, columns=boston.feature_names)
Y_boston = pd.Series(boston.target, name='MEDV')
print("已成功載入波士頓房價資料集。")
except ImportError:
print("警告: load_boston 在當前 sklearn 版本中可能已棄用。將使用模擬數據集進行演示。")
# 建立一個簡易的模擬數據集用於演示
np.random.seed(42) # 設置隨機種子以確保結果可重現
X_boston = pd.DataFrame(np.random.rand(200, 5), columns=[f'Feature_{i}' for i in range(5)])
Y_boston = pd.Series(20 + 5 * X_boston['Feature_0'] + 3 * X_boston['Feature_1'] - 2 * X_boston['Feature_2'] + np.random.randn(200) * 5)
print("已使用模擬數據集。")
# 劃分訓練集和測試集
X_train_boston, X_test_boston, Y_train_boston, Y_test_boston = \
train_test_split(X_boston, Y_boston, test_size=0.3, random_state=42)
# 建立決策樹迴歸模型
# max_depth 參數同樣重要,可以防止過度學習
dt_regressor = DecisionTreeRegressor(max_depth=5, random_state=42)
dt_regressor.fit(X_train_boston, Y_train_boston)
# 進行預測
Y_pred_boston = dt_regressor.predict(X_test_boston)
# 評估模型表現
r2 = r2_score(Y_test_boston, Y_pred_boston)
mae = mean_absolute_error(Y_test_boston, Y_pred_boston)
mse = mean_squared_error(Y_test_boston, Y_pred_boston)
rmse = np.sqrt(mse) # RMSE 是 MSE 的平方根
print("\n--- 決策樹迴歸模型表現 ---")
print(f"R² (決定係數): {r2:.4f}")
print(f"MAE (平均絕對誤差): {mae:.4f}")
print(f"MSE (均方誤差): {mse:.4f}")
print(f"RMSE (均方根誤差): {rmse:.4f}")
```
---
- [ ] **總結與展望**
* **決策樹:** 是一種**直觀、可解釋性強**的機器學習模型,適用於**分類和迴歸**問題。
* **優點:** 易於理解其決策邏輯、結果可轉換為規則、無需複雜的資料預處理 (如特徵縮放)。
* **缺點:** 對資料中的雜訊較敏感、容易發生過度學習或低度學習。
* **優化:** 可以透過**「修剪 (Pruning)」**技術來控制樹的複雜度,改善模型的泛化能力。
* **進階概念 (簡要提及,非本課程重點):**
* **整合學習 (Ensemble Learning):** 透過結合**多個決策樹 (或多個模型)**,可以顯著提高模型的預測準確率和穩定性。這就像「三個臭皮匠,勝過一個諸葛亮」。
* **Bagging (拔靴整合):** 一種整合學習方法,透過**重複取樣**訓練多個模型,然後將它們的預測結果**聚合** (例如:分類問題用「投票」,迴歸問題用「平均」)。
* **隨機森林 (Random Forest):** 是一種非常流行的整合學習方法,它是 **Bagging 概念的一種應用**。它訓練多個決策樹,並在每個節點隨機選擇特徵進行分枝,進一步提高了模型的穩定性和準確率。這是一種非常強大且廣泛使用的模型!