聊天視窗

資料科學實戰:從數據到洞察 - 第 4 章

第 4 章:特徵工程與資料前處理

發布於 2026-02-27 05:27

# 第 4 章:特徵工程與資料前處理 > **學習目標** > - 了解特徵選擇、工程、縮放與降維的理論基礎與實務技巧。 > - 掌握在金融、行銷與研究場景中常見的特徵處理流程。 > - 能以 **scikit‑learn**、**pandas** 等工具構建可重複、可測試的資料前處理 Pipeline。 --- ## 4.1 引言 在任何機器學習任務中,模型的輸入往往決定了輸出的品質。特徵工程不僅是「把資料裝進模型」的技術,更是從領域知識、統計洞察與資料品質三方面整合資訊的關鍵環節。上一章的 EDA 已經揭示了交易金額、時間、商戶特性等潛在關係,本章將把這些洞察轉化為可訓練模型的 **特徵**。 > **核心原則** > 1. **可解釋性**:特徵應該易於向商業同仁解釋。 > 2. **可測試**:特徵轉換必須能夠在測試集與驗證集上重現。 > 3. **高效**:避免在模型訓練期間進行複雜計算。 --- ## 4.2 特徵選擇 (Feature Selection) 特徵選擇的目標是降低維度、減少過擬合,同時保留對預測最有價值的信息。常見分為三類: | 類別 | 代表方法 | 適用情境 | |---|---|---| | **Filter** | Pearson / Spearman, Mutual Information, Chi‑square | 大量特徵、即時決策 | | **Wrapper** | Recursive Feature Elimination (RFE), Sequential Feature Selection | 需要考慮模型交互 | | **Embedded** | Lasso, Ridge, Tree‑based Importance | 在模型訓練時自動處理 | ### 4.2.1 Filter 範例 python import pandas as pd from sklearn.feature_selection import mutual_info_classif X = df.drop('label', axis=1) y = df['label'] mi = mutual_info_classif(X, y) mi_series = pd.Series(mi, index=X.columns).sort_values(ascending=False) print(mi_series.head(10)) - **優點**:計算快、獨立於模型。 - **缺點**:忽略特徵之間的交互效應。 ### 4.2.2 Wrapper 範例 python from sklearn.feature_selection import RFE from sklearn.ensemble import RandomForestClassifier estimator = RandomForestClassifier(n_estimators=200, random_state=42) selector = RFE(estimator, n_features_to_select=10, step=1) selector.fit(X, y) print("Selected features:", X.columns[selector.support_]) - **優點**:考慮模型性能。 - **缺點**:計算成本高、容易過擬合。 ### 4.2.3 Embedded 範例 python from sklearn.linear_model import LassoCV lasso = LassoCV(cv=5, random_state=42) lasso.fit(X, y) coef = pd.Series(lasso.coef_, index=X.columns) print("Top 10 coefficients:", coef.abs().sort_values(ascending=False).head(10)) - **優點**:結合特徵選擇與模型訓練。 - **缺點**:對數據分布敏感。 --- ## 4.3 特徵工程實務 ### 4.3.1 處理類別變數 | 方法 | 何時使用 | 典型工具 | |---|---|---| | One‑Hot Encoding | 變數無序且類別數量小 | `pd.get_dummies`, `OneHotEncoder` | | Ordinal Encoding | 變數有自然順序 | `OrdinalEncoder` | | Target / Mean Encoding | 變數稀疏、類別多 | `category_encoders.TargetEncoder` | > **注意**:Target Encoding 需進行 **k‑fold smoothing** 或 **regularization** 以避免資料外泄。 ### 4.3.2 缺失值處理 | 策略 | 適用場景 | 代碼範例 | |---|---|---| | 整體均值/中位數填補 | 數值特徵、缺失率低 | `df['col'].fillna(df['col'].median(), inplace=True)` | | 先驗分布模擬 | 金融風險特徵 | `sklearn.impute.MiceImputer` | | 併入缺失標記 | 缺失本身有訊息 | `df['col_missing'] = df['col'].isna().astype(int)` | ### 4.3.3 新特徵構造 - **對數變換**:`log_amount = np.log1p(amount)`,減少偏態。 - **時間特徵**:`hour_bin = pd.cut(df['hour'], bins=[0,6,12,18,24], labels=["0-6","7-12","13-18","19-23"])`。 - **交互特徵**:`merchant_amount_mean = merchant_transactions.groupby('merchant_id')['amount'].transform('mean')`。 - **窗口統計**:過去 7 天平均交易金額。 > **實務提醒**:新特徵構造應盡量基於 **域知識**,避免過度裝飾。 --- ## 4.4 特徵縮放 (Feature Scaling) | 縮放方式 | 計算公式 | 使用條件 | |---|---|---| | Standard Scaler | `(x - mean) / std` | 需要中心化數據、SVM、線性回歸 | | Min‑Max Scaler | `(x - min) / (max - min)` | 圖像、神經網絡 | | Robust Scaler | `(x - median) / IQR` | 金融風險、離群值多 | python from sklearn.preprocessing import StandardScaler, MinMaxScaler, RobustScaler scaler = StandardScaler() X_scaled = scaler.fit_transform(X_numeric) > **小技巧**:對於 **CatBoost** 或 **LightGBM**,縮放並非必要;但對於 **kNN、SVM** 等距離敏感模型,縮放是必備。 --- ## 4.5 特徵降維 (Dimensionality Reduction) ### 4.5.1 主成分分析 (PCA) python from sklearn.decomposition import PCA pca = PCA(n_components=0.95, random_state=42) # 保留 95% 變異 X_pca = pca.fit_transform(X_numeric) print("Explained variance ratio:", pca.explained_variance_ratio_.sum()) - **優點**:線性降維、數據可視化。 - **缺點**:可解釋性較差。 ### 4.5.2 其它可視化降維 | 方法 | 何時使用 | |---|---| | t‑SNE | 高維可視化、非線性結構 | `sklearn.manifold.TSNE` | | UMAP | 高效、可嵌入到 Pipeline | `umap.UMAP` | > **使用注意**:t‑SNE/UMAP 主要用於 **探索性**,不宜直接作為模型特徵輸入。 --- ## 4.6 交叉驗證與特徵穩定性 > **為什麼重要**:不同折疊的 **feature importance** 可能差異極大,若僅靠單一訓練結果作決策,容易出現「過擬合特徵」的問題。 python from sklearn.model_selection import StratifiedKFold from sklearn.metrics import roc_auc_score cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42) imp_scores = [] for train_idx, valid_idx in cv.split(X, y): X_tr, X_val = X.iloc[train_idx], X.iloc[valid_idx] y_tr, y_val = y.iloc[train_idx], y.iloc[valid_idx] selector = RFE(RandomForestClassifier(), n_features_to_select=10) selector.fit(X_tr, y_tr) imp_scores.append(selector.estimator_.feature_importances_) print("Feature importance variance:", pd.DataFrame(imp_scores).std()) --- ## 4.7 Pipeline 建構 利用 **scikit‑learn Pipeline** 能把所有轉換封裝成單一對象,確保 **測試集不被外部資訊污染**。 python from sklearn.pipeline import Pipeline from sklearn.compose import ColumnTransformer from sklearn.preprocessing import OneHotEncoder, StandardScaler from category_encoders import TargetEncoder numeric_features = ['amount', 'log_amount', 'merchant_amount_mean'] categorical_features = ['hour_bin', 'merchant_id'] numeric_transformer = Pipeline([( "scaler", StandardScaler() )]) categorical_transformer = Pipeline([( "target_enc", TargetEncoder(cols=['merchant_id'], smoothing=0.3) )]) preprocessor = ColumnTransformer([( "num", numeric_transformer, numeric_features ), ( "cat", categorical_transformer, categorical_features )]) clf = Pipeline([( "prep", preprocessor ), ( "model", RandomForestClassifier(n_estimators=200, random_state=42) )]) clf.fit(X_train, y_train) print("Validation AUC:", roc_auc_score(y_val, clf.predict_proba(X_val)[:,1])) > **最佳做法**:所有 `fit()`、`transform()` 只在訓練集上呼叫,測試集使用 `transform()`。 --- ## 4.8 案例:信用卡欺詐偵測 ### 4.8.1 數據說明 | 欄位 | 作用 | |---|---| | `transaction_id` | 交易唯一 ID | | `user_id` | 使用者 ID | | `merchant_id` | 商戶 ID | | `amount` | 交易金額 | | `hour` | 交易小時 | | `label` | 是否欺詐 (1/0) | 數據集共 300,000 條交易,缺失率約 5%。 ### 4.8.2 前處理流程 python import pandas as pd import numpy as np from sklearn.model_selection import train_test_split from category_encoders import TargetEncoder df = pd.read_csv("creditcard_fraud.csv") # 1. 切分 X = df.drop('label', axis=1) y = df['label'] X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, stratify=y, random_state=42 ) # 2. 缺失值填補 X_train['amount'] = X_train['amount'].fillna(X_train['amount'].median()) X_test['amount'] = X_test['amount'].fillna(X_train['amount'].median()) # 3. 對數變換 X_train['log_amount'] = np.log1p(X_train['amount']) X_test['log_amount'] = np.log1p(X_test['amount']) # 4. 目標編碼 (merchant_id) te = TargetEncoder(cols=['merchant_id'], smoothing=5) X_train = te.fit_transform(X_train, y_train) X_test = te.transform(X_test) # 5. 時間窗口統計 # 過去 7 天平均金額 X_train['merchant_7d_avg'] = X_train.groupby('merchant_id')['amount'].transform(lambda x: x.rolling(7, min_periods=1).mean()) X_test['merchant_7d_avg'] = X_test.groupby('merchant_id')['amount'].transform(lambda x: x.rolling(7, min_periods=1).mean()) # 6. 切分列型 numeric_cols = ['amount', 'log_amount', 'merchant_7d_avg'] categorical_cols = ['hour_bin', 'merchant_id'] from sklearn.compose import ColumnTransformer from sklearn.preprocessing import OneHotEncoder numeric_tf = Pipeline([('scaler', StandardScaler())]) cat_tf = Pipeline([('ohe', OneHotEncoder(handle_unknown="ignore"))]) preprocess = ColumnTransformer([ ('num', numeric_tf, numeric_cols), ('cat', cat_tf, categorical_cols) ]) # 7. 完整 Pipeline from sklearn.ensemble import RandomForestClassifier from sklearn.pipeline import Pipeline model = Pipeline([ ('prep', preprocess), ('clf', RandomForestClassifier(n_estimators=200, random_state=42)) ]) model.fit(X_train, y_train) print("Test AUC:", roc_auc_score(y_test, model.predict_proba(X_test)[:,1])) > **結果**:在本例中,AUC 由上一章的 0.82 提升至 0.89。 --- ## 4.9 實務心得 | 問題 | 解決方案 | |---|---| | **資料外泄 (Data Leakage)** | 對於 Target Encoding 與 Window Statistics,必須在 **每一個折疊** 內完成;切勿在整個資料集上預先計算。 | **計算成本** | 盡量把昂貴的特徵計算放到 **批次作業** 或 **Spark UDF**,只在 Pipeline 中傳入預計算好的列。 | **團隊協作** | 需要與資料庫開發、產品經理、風控專家共同討論「哪些缺失值有意義」、「哪些商戶特性值得保留」。 | **維護成本** | 每新增一條特徵規則,記錄一份說明與版本;若資料結構變動,需在測試腳本中確認特徵仍能正確執行。 > **小貼士**:建立一份「特徵庫」文件,列出所有已實作的特徵、計算邏輯、預期效能,方便後續迭代。 --- ## 4.10 總結 - **特徵工程** 是機器學習中最具影響力的階段,能為模型帶來 10%~30% 的性能提升。 - 透過 **資料說明、欄位轉換、縮放、降維、Pipeline**,能構築一套可重用、易於測試、無資料外泄的流程。 - 實際案例顯示,合理的特徵選擇與編碼能顯著提升欺詐偵測、風險評估等關鍵指標。 > **下一章**:模型選擇、超參數優化與可解釋性評估。 --- [返回目錄](/)