決策樹分兩種分類和回歸,這篇博客我將對兩種方法進行實戰講解
一、分類決策樹
代碼的核心任務是預測 “電信客戶流失狀態”,這是一個典型的分類任務
數據集附在該博客上,可以直接下載
代碼整體結構整理
代碼主要分為以下幾個部分:
- 導入必要的庫
- 數據讀取與預處理
- 數據平衡處理
- 決策樹模型參數調優
- 最佳模型訓練與評估
- 閾值調整以優化召回率
詳細代碼講解
1. 導入必要的庫
from imblearn.over_sampling import RandomOverSampler
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import train_test_split
import pandas as pd
from sklearn import metrics
import matplotlib.pyplot as plt
from sklearn.tree import plot_tree
from sklearn.model_selection import cross_val_score
import numpy as np
這部分代碼導入了后續需要用到的各類庫:
- 數據處理:pandas、numpy
- 模型相關:DecisionTreeClassifier(決策樹分類器)
- 數據劃分與評估:train_test_split、cross_val_score、metrics
- 可視化:matplotlib.pyplot、plot_tree
- 不平衡數據處理:RandomOverSampler
2. 數據讀取與初步處理
# 讀取數據
data = pd.read_excel('電信客戶流失數據.xlsx')
x = data.iloc[:, :-1] # 提取特征變量(除最后一列外的所有列)
y = data['流失狀態'] # 提取目標變量(流失狀態列)# 將數據分為訓練集和測試集,測試集占20%
xtr, xte, ytr, yte = train_test_split(x, y, test_size=0.2, random_state=42)
- 讀取 Excel 格式的電信客戶流失數據
- 分割特征變量 (x) 和目標變量 (y),其中目標變量是 "流失狀態"
- 使用 train_test_split 將數據分為訓練集 (80%) 和測試集 (20%)
- random_state=42 確保結果可重現
3. 數據平衡處理
# 手動實現過采樣以平衡數據集
new_date = xtr.copy()
new_date['流失狀態'] = ytr # 將訓練集特征和標簽合并# 分離正例和負例(假設0表示未流失,1表示流失)
positive_eg = new_date[new_date['流失狀態'] == 0]
negative_eg = new_date[new_date['流失狀態'] == 1]# 對多數類進行下采樣,使其數量與少數類相同
positive_eg = positive_eg.sample(len(negative_eg))# 合并處理后的正負例,得到平衡的訓練集
date_c = pd.concat([positive_eg, negative_eg])
xtr = date_c.iloc[:, :-1] # 平衡后的特征
ytr = date_c['流失狀態'] # 平衡后的標簽
這部分通過下采樣方法處理數據不平衡問題:
- 將多數類 (未流失客戶) 的樣本數量減少到與少數類 (流失客戶) 相同
- 這樣處理是因為在客戶流失預測中,我們通常更關注少數類 (流失客戶) 的識別
- 平衡數據有助于模型更好地學習少數類的特征
4. 決策樹模型參數調優
# 網格搜索尋找最佳參數組合
scores = []
# 遍歷不同的參數組合
for d in range(3, 10): # 樹的最大深度for l in range(2, 8): # 葉節點的最小樣本數for s in range(2, 6): # 分裂所需的最小樣本數for n in range(2, 8):# 最大葉節點數# 創建決策樹模型model = DecisionTreeClassifier(max_depth=d,min_samples_split=s,min_samples_leaf=l,max_leaf_nodes=n,random_state=42)# 5折交叉驗證,評估指標為召回率score = cross_val_score(model, xtr, ytr, cv=5, scoring='recall')score_mean = sum(score) / len(score) # 計算平均召回率scores.append([d, l, s, n, score_mean])# 找到召回率最高的參數組合
best_params = max(scores, key=lambda x: x[4])
d, l, s, n, best_recall = best_params
print(f"最佳參數: 最大深度={d}, 最小葉節點樣本數={l}, 最小分裂樣本數={s}, 最大葉節點數={n}, 最佳召回率={best_recall}")
這部分通過網格搜索進行參數調優:
- 遍歷決策樹的四個關鍵參數的不同取值組合
- 使用 5 折交叉驗證評估每個組合的性能,重點關注召回率 (recall)
- 召回率在客戶流失預測中很重要,因為我們希望盡可能識別出所有可能流失的客戶
- 選擇召回率最高的參數組合作為最佳參數
5. 最佳模型訓練與評估
# 使用最佳參數創建并訓練模型
dtr_best = DecisionTreeClassifier(max_depth=d,min_samples_split=s,min_samples_leaf=l,max_leaf_nodes=n,random_state=42
)
dtr_best.fit(xtr, ytr)# 在訓練集和測試集上進行預測
test_predicted = dtr_best.predict(xte)
train_predicted = dtr_best.predict(xtr)# 輸出分類報告
print("訓練集分類報告:")
print(metrics.classification_report(ytr, train_predicted))
print("測試集分類報告:")
print(metrics.classification_report(yte, test_predicted))
這部分使用最佳參數訓練模型并評估:
- 用最佳參數組合構建決策樹模型并在訓練集上擬合
- 在訓練集和測試集上分別進行預測
- 輸出詳細的分類報告,包括精確率、召回率、F1 分數等指標
- 通過對比訓練集和測試集的表現,可以初步判斷模型是否過擬合
6. 閾值調整以優化召回率
# 嘗試不同的閾值以優化召回率
thresholds = [0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.4, 0.5]
recalls = []for i in thresholds:# 獲取測試集的預測概率y_predict_proba = dtr_best.predict_proba(xte)y_predict_proba = pd.DataFrame(y_predict_proba)# 根據當前閾值調整預測結果y_predict_proba[y_predict_proba[[1]] > i] = 1y_predict_proba[y_predict_proba[[1]] <= i] = 0# 計算并記錄召回率recall = metrics.recall_score(yte, y_predict_proba[1])recalls.append(recall)print(f"閾值: {i}, 召回率: {recall}")# 找到最佳閾值
best_threshold = thresholds[np.argmax(recalls)]
print(f'最佳閾值: {best_threshold}')
print(f'調整閾值后的最高recall為: {max(recalls)}')# 使用最佳閾值生成最終預測結果
y_predict_proba = dtr_best.predict_proba(xte)
y_predict_proba = pd.DataFrame(y_predict_proba)
y_predict_proba[y_predict_proba[[1]] > best_threshold] = 1
y_predict_proba[y_predict_proba[[1]] <= best_threshold] = 0# 輸出最佳閾值下的分類報告
print(f"\n最佳閾值 {best_threshold} 對應的分類報告:")
print(metrics.classification_report(yte, y_predict_proba[1]))
這部分通過調整分類閾值進一步優化模型:
- 決策樹默認使用 0.5 作為分類閾值
- 降低閾值會增加預測為流失 (1) 的樣本比例,通常會提高召回率
- 嘗試多個閾值,計算每個閾值對應的召回率
- 選擇召回率最高的閾值作為最佳閾值
- 輸出最佳閾值下的分類報告,此時模型在識別流失客戶方面表現最優
二、分類決策樹
下面會創建一個使用回歸決策樹解決問題的示例,我們將使用 scikit-learn 自帶的波士頓房價數據集(或其替代數據集)來預測房價。(這個代碼不用下載數據集,python的sklearn庫自帶這個數據集
1. 導入必要的庫
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.datasets import fetch_california_housing # 加州房價數據集
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeRegressor
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score
from sklearn.model_selection import cross_val_score
from sklearn.tree import plot_tree
功能說明:
這部分導入了后續分析所需的各類工具庫:
- 數值計算:
numpy
?用于數學運算,pandas
?用于數據處理與分析 - 可視化:
matplotlib.pyplot
?用于繪圖,plot_tree
?用于決策樹可視化 - 數據集:
fetch_california_housing
?獲取 sklearn 自帶的加州房價數據集 - 模型相關:
DecisionTreeRegressor
?是回歸決策樹模型類 - 數據劃分與評估:
train_test_split
?分割訓練集和測試集;cross_val_score
?用于交叉驗證;mean_squared_error
等是回歸任務的評估指標
回歸 vs 分類:此處使用?DecisionTreeRegressor
(回歸決策樹),而非分類任務的?DecisionTreeClassifier
,因為我們要預測的是連續數值(房價)。
2. 加載并探索數據集
# 1. 加載數據集
housing = fetch_california_housing()
X = housing.data # 特征數據
y = housing.target # 目標變量(房價,單位:10萬美元)# 將數據轉換為DataFrame以便查看
df = pd.DataFrame(X, columns=housing.feature_names)
df['MedHouseVal'] = y # 添加目標變量列(房價中位數)# 查看數據集基本信息
print("數據集形狀:", df.shape)
print("\n數據集前5行:")
print(df.head())
print("\n數據集描述統計:")
print(df.describe())
功能說明:
- 加載加州房價數據集:該數據集包含加州各地區的房價數據,替代了已移除的波士頓房價數據集
- 數據結構:
X
?是特征矩陣(8 個特征),y
?是目標變量(房價中位數,單位為 10 萬美元) - 特征說明:包括平均收入(MedInc)、房齡(HouseAge)、平均房間數(AveRooms)等 8 個特征
- 數據探索:通過打印數據集形狀、前 5 行和描述統計量,快速了解數據分布特征(均值、標準差、最值等)
關鍵輸出:
- 數據集形狀:查看樣本數量和特征數量
- 描述統計:了解各特征的數值范圍和分布,為后續建模提供參考
3. 劃分訓練集和測試集
# 2. 劃分訓練集和測試集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42
)
功能說明:
- 使用?
train_test_split
?將數據分為訓練集(80%)和測試集(20%)X_train
/y_train
:用于模型訓練的特征和目標變量X_test
/y_test
:用于評估模型泛化能力的特征和目標變量
test_size=0.2
?表示測試集占總數據的 20%random_state=42
?固定隨機種子,確保每次運行結果可重現
為什么要劃分數據集:
訓練集用于模型學習數據規律,測試集用于模擬真實場景,評估模型對新數據的預測能力,避免過擬合問題。
4. 模型參數調優(尋找最佳樹深度
# 3. 訓練回歸決策樹模型并進行參數調優
best_score = -np.inf
best_depth = 1# 嘗試不同的樹深度,尋找最佳參數
for depth in range(1, 21):# 創建回歸決策樹模型regressor = DecisionTreeRegressor(max_depth=depth,random_state=42)# 使用交叉驗證評估模型scores = cross_val_score(regressor, X_train, y_train, cv=5, scoring='neg_mean_squared_error')# 計算平均MSE(注意交叉驗證返回的是負的MSE)avg_mse = -np.mean(scores)# 計算R2分數r2_scores = cross_val_score(regressor, X_train, y_train, cv=5, scoring='r2')avg_r2 = np.mean(r2_scores)if avg_r2 > best_score:best_score = avg_r2best_depth = depthprint(f"樹深度: {depth}, 平均MSE: {avg_mse:.4f}, 平均R2: {avg_r2:.4f}")print(f"\n最佳樹深度: {best_depth}, 最佳R2分數: {best_score:.4f}")
功能說明:
這是模型調優的核心步驟,通過遍歷不同樹深度尋找最佳參數:
- 參數選擇:重點優化?
max_depth
(樹的最大深度),范圍從 1 到 20- 樹深度過小:模型簡單,可能欠擬合(無法捕捉數據規律)
- 樹深度過大:模型復雜,可能過擬合(過度擬合訓練數據噪聲)
- 交叉驗證:使用 5 折交叉驗證(
cv=5
)評估每個深度的模型性能- 將訓練集分為 5 份,輪流用 4 份訓練、1 份驗證,最終取平均值
- 避免單次劃分的隨機性影響,更穩定地評估模型性能
- 評估指標:
neg_mean_squared_error
:負均方誤差(sklearn 習慣返回負值,需取反)r2
:決定系數,衡量模型對目標變量變異的解釋能力(越接近 1 越好)
- 最佳參數選擇:保留 R2 分數最高的樹深度作為最佳參數
調優目的:找到泛化能力最強的模型參數,平衡欠擬合和過擬合。
5. 訓練最佳模型并評估
# 4. 使用最佳參數訓練模型
best_regressor = DecisionTreeRegressor(max_depth=best_depth,random_state=42
)
best_regressor.fit(X_train, y_train)# 5. 模型評估
# 在測試集上進行預測
y_pred = best_regressor.predict(X_test)# 計算評估指標
mse = mean_squared_error(y_test, y_pred)
rmse = np.sqrt(mse) # 均方根誤差
mae = mean_absolute_error(y_test, y_pred) # 平均絕對誤差
r2 = r2_score(y_test, y_pred) # R2分數print("\n測試集評估指標:")
print(f"均方誤差 (MSE): {mse:.4f}")
print(f"均方根誤差 (RMSE): {rmse:.4f}") # 單位與目標變量相同(10萬美元)
print(f"平均絕對誤差 (MAE): {mae:.4f}")
print(f"R2分數: {r2:.4f}") # 越接近1表示模型擬合越好
功能說明:
- 用最佳樹深度創建模型并在訓練集上擬合(
fit
方法) - 在測試集上生成預測結果(
predict
方法) - 計算核心評估指標:
- MSE(均方誤差):預測值與實際值差的平方的平均值,衡量誤差大小
- RMSE(均方根誤差):MSE 的平方根,單位與目標變量一致(此處為 10 萬美元),更易解釋
- MAE(平均絕對誤差):預測值與實際值差的絕對值的平均值,對異常值更穩健
- R2 分數:表示模型解釋的房價變異比例,R2=0.6 意味著模型解釋了 60% 的房價變異
評估意義:通過測試集指標判斷模型的實際預測能力,若測試集與訓練集指標差距過大,可能存在過擬合。
6. 決策樹可視化(可選
# 6. 可視化部分決策樹(如果樹不太大)
if best_depth <= 5: # 只可視化較淺的樹,太深的樹可視化效果不好plt.figure(figsize=(15, 10))plot_tree(best_regressor,feature_names=housing.feature_names,filled=True,rounded=True,precision=2)plt.title(f"回歸決策樹 (深度: {best_depth})")plt.show()
else:print(f"\n由于最佳樹深度({best_depth})較大,未進行可視化")
功能說明:
- 當樹深度較小時(≤5),使用?
plot_tree
?可視化決策樹結構 - 可視化參數:
feature_names
:顯示特征名稱,增強可讀性filled=True
:根據節點的目標值范圍填充顏色rounded=True
:圓角矩形顯示節點
- 深層樹不可視化的原因:深度過大的樹結構復雜,節點繁多,可視化后難以解讀
可視化價值:直觀展示決策樹的分裂規則,幫助理解模型如何通過特征判斷房價。
7. 特征重要性分析
# 7. 分析特征重要性
feature_importance = pd.DataFrame({'特征': housing.feature_names,'重要性': best_regressor.feature_importances_
})
feature_importance = feature_importance.sort_values('重要性', ascending=False)
print("\n特征重要性排序:")
print(feature_importance)
功能說明:
- 回歸決策樹通過?
feature_importances_
?屬性輸出各特征的重要性 - 重要性定義:特征在樹中所有分裂點減少的誤差總和(歸一化到 0-1 之間,總和為 1)
- 結果排序:按重要性降序排列,清晰展示哪些特征對房價預測影響最大
實際意義:在房價預測中,通常 "平均收入(MedInc)" 會是最重要的特征,這符合現實邏輯 —— 收入水平直接影響購房能力。