← 文章列表
LLM

機器學習的本質思考:Vibe Coding 時代工程師的關鍵決策指南

2026-03-26 · — views

在 AI 輔助開發的時代,Claude 和 ChatGPT 可以幫你寫 code,但「何時用哪個指標」、「為什麼模型會失效」、「如何避開陷阱」這些決策,才是工程師的核心價值。本文聚焦在機器學習中 7 個最容易出錯但影響最深遠的決策點,幫助你從「會用工具」進化到「懂得決策」。


前言:為什麼我們需要理解「為什麼」?

從 Coding 到 Vibing

2024 年以來,軟體開發的範式已經改變。過去我們需要熟記語法、API、設計模式;現在,Claude 和 ChatGPT 能在幾秒內生成符合最佳實踐的程式碼。

但這不代表工程師變得不重要——恰恰相反,決策能力變得更關鍵。

機器學習中的決策陷阱

在機器學習領域,這個差異更加明顯:

AI 可以幫你做的

  • 寫一個 Random Forest 模型的訓練程式碼
  • 實作 cross-validation
  • 畫出混淆矩陣

AI 無法替你決策的

  • 這個問題該用 Accuracy 還是 F1 Score?
  • 為什麼模型在測試集表現很好,部署後卻崩潰?
  • 該花時間在特徵工程還是調參?
  • 這個模型的弱點在哪裡?會在什麼情況下失效?

這篇文章在聊什麼

這不是教學文,而是我整理機器學習實務中那些「踩過坑才懂」的決策點。

程式碼的部分交給 Claude 就好,這裡想聊的是:

  1. 評估指標選擇 - 為什麼 Accuracy 會騙人?何時該用 PR-AUC?
  2. 資料探索策略 - 如何在訓練前發現致命問題?
  3. 過擬合診斷 - Overfitting 的本質與解法邏輯
  4. 優化思維差異 - Training 加速 vs Inference 加速的完全不同邏輯
  5. 商業價值對齊 - Confusion Matrix 背後的商業意義
  6. 特徵工程決策 - 何時用哪種方法?如何避免 Data Leakage?
  7. 模型選擇陷阱 - 每個模型的致命弱點與避坑指南

希望這些筆記能幫你少走一些彎路。


Chapter 0: 商業價值對齊 — 先想清楚要解決什麼問題

在深入技術細節前,我們必須先建立一個認知:技術再炫,解決不了真實問題就是零

為什麼從商業價值開始?

常見的技術導向陷阱

你可能聽過這些對話:

場景 1:模型指標的迷思

數據科學家:「我們的模型 Accuracy 達到 95%!」
產品經理:「太好了!那為什麼使用者留存率沒有提升?」
數據科學家:「...」

場景 2:技術炫技的代價

工程師:「我們用了最新的 Transformer 架構!」
老闆:「推論成本怎麼暴增 10 倍?這樣根本不划算。」
工程師:「但這是 SOTA...」

場景 3:特徵工程的空轉

ML 工程師:「我們做了很酷的特徵工程,用了 50 個新特徵!」
產品團隊:「A/B test 顯示使用者體驗反而變差了。」
ML 工程師:「不可能,模型效能明明提升了...」

正確的思維框架

在開始任何 ML 專案前,先回答三個問題:

  1. 商業目標是什麼?

    • 提升收入?降低成本?改善使用者體驗?
    • 具體的數字目標是什麼?
  2. ML 要解決什麼問題?

    • 注意:不是「ML 能做什麼」,而是「需要解決什麼」
    • 問題的核心痛點在哪裡?
  3. 成本效益如何?

    • 開發成本(人力、時間、基礎設施)
    • 維護成本(監控、重訓、更新)
    • 預期收益(用資料估算,而非拍腦袋)
    • ROI 是否值得投入?

從商業問題到 ML 問題:兩個完整案例

案例 1:電商推薦系統

讓我們看一個完整的思考流程:

Step 1: 商業目標

核心目標:提升 GMV (Gross Merchandise Value)

Step 2: 拆解可衡量指標

GMV = 流量 × 點擊率 × 轉換率 × 客單價

可優化的部分:
├─ 點擊率 (CTR)      - 推薦更吸引人的商品
├─ 轉換率 (CVR)      - 推薦更可能購買的商品
├─ 客單價 (AOV)      - 推薦更高價值的商品
└─ 回購率           - 提升長期價值

Step 3: 轉化為 ML 問題

問題定義:
├─ CTR 預測:給定使用者和商品,預測點擊機率
├─ CVR 預測:給定點擊,預測購買機率
└─ 商品排序:根據預測機率和商業規則排序

Step 4: 選擇 Proxy Metrics

Model Metrics(技術指標):
├─ AUC-ROC
├─ Precision@K
└─ NDCG (Normalized Discounted Cumulative Gain)

Business Metrics(商業指標):
├─ GMV
├─ 使用者留存率
└─ 平均購買頻率

Step 5: 關鍵決策點

這裡是最容易出錯的地方:Model Metric 與 Business Metric 不一定正相關

真實案例:

模型 A:
- AUC = 0.85
- 策略:推薦多樣性高,包含長尾商品
- A/B test 結果:GMV +20%

模型 B:
- AUC = 0.88
- 策略:只推薦熱門商品
- A/B test 結果:GMV +5%

正確決策:選模型 A

為什麼?

  • AUC 高不代表商業價值高
  • 推薦系統的目標不是「預測準確」,而是「創造價值」
  • 長尾商品可能毛利更高、庫存需要清理、或提升使用者黏性

核心洞察:模型指標只是代理(Proxy),商業指標才是目標(Goal)。

案例 2:信用評分系統

Step 1: 商業目標

降低壞帳率,同時維持放款量

注意這裡有個微妙的 Trade-off:

  • 如果只追求「降低壞帳」,把所有貸款都拒絕就好(但賺不到錢)
  • 如果只追求「提高放款量」,全部核准就好(但壞帳會爆炸)

Step 2: 商業決策框架

關鍵問題:FP 和 FN 的成本各是多少?

定義:
- FP (False Positive):拒絕好客戶(預測會違約,但其實不會)
- FN (False Negative):接受壞客戶(預測不會違約,但其實會)

成本計算:
- FP 成本 = 損失的利息收入 = 平均貸款額 × 利率 × 期限
  假設:$100,000 × 5% × 3年 = $15,000

- FN 成本 = 違約損失 = 平均貸款額 × (1 - 回收率)
  假設:$100,000 × (1 - 30%) = $70,000

比例:FN 成本是 FP 成本的 4.7 倍

Step 3: 指標選擇

錯誤思維:
「我們要降低壞帳,所以優化 Precision」❌

正確思維:
「FN 的成本是 FP 的 4.7 倍,所以我們應該:
1. 優先優化 Recall(不能漏掉好客戶)
2. 設定 Precision 的下限(控制壞帳在可接受範圍)
3. 根據成本比例調整決策閾值」✅

Step 4: 閾值調整

# 預設閾值 0.5 可能不是最優
# 應該根據成本比例找到最優閾值

# 成本函數
def business_cost(y_true, y_pred_proba, threshold, fp_cost=15000, fn_cost=70000):
    y_pred = (y_pred_proba >= threshold).astype(int)
    fp = np.sum((y_pred == 1) & (y_true == 0))
    fn = np.sum((y_pred == 0) & (y_true == 1))
    return fp * fp_cost + fn * fn_cost

# 找出最小化總成本的閾值
thresholds = np.linspace(0, 1, 100)
costs = [business_cost(y_test, y_pred_proba, t) for t in thresholds]
optimal_threshold = thresholds[np.argmin(costs)]

print(f"最優閾值: {optimal_threshold:.3f}")
# 可能輸出:0.23(遠低於預設的 0.5)

核心洞察:機器學習的決策閾值應該由商業成本決定,而非技術預設值。

何時該/不該用 ML?

並非所有問題都適合用機器學習解決。過度使用 ML 會造成不必要的複雜度和成本。

決策框架

1. 問題能用規則解決嗎?
   └─ 是 → 先用規則(簡單、可解釋、成本低)
   └─ 否 → 繼續

2. 有足夠的資料嗎?
   └─ 否(< 1000 筆有標註資料)→ 規則或簡單統計
   └─ 是 → 繼續

3. 需要動態調整嗎?
   └─ 否(業務邏輯固定且明確)→ 規則引擎可能更好
   └─ 是 → 繼續

4. ROI 合理嗎?
   └─ ML 總成本 < 預期收益的 1/3 → 值得用 ML
   └─ 否 → 重新評估或用更簡單的方法

反面案例:不該用 ML 的情境

情境為什麼不該用 ML更好的解法真實例子
規則明確不需要「學習」正則表達式 + 查表郵遞區號驗證、Email 格式檢查
簡單邏輯if-else 就夠了業務規則引擎VIP 客戶判定(消費金額 > $10k)
資料太少無法訓練有效模型專家規則新產品推薦(< 100 筆歷史資料)
需要 100% 準確ML 有誤差確定性演算法金額計算、法律條文匹配
必須可解釋黑盒模型不可接受決策樹或規則醫療診斷、貸款拒絕原因
維護成本高需要持續重訓、監控靜態規則內部工具的異常偵測

正面案例:適合用 ML 的情境

反過來,以下情境 ML 能發揮價值:

  1. 模式複雜且動態變化

    • 例:詐欺偵測(詐騙手法不斷演變)
    • 規則系統無法跟上變化速度
  2. 有大量標註資料

    • 例:影像分類(ImageNet 百萬張圖)
    • 人工規則難以窮舉所有情況
  3. 需要個人化

    • 例:推薦系統(每個使用者偏好不同)
    • 通用規則無法滿足個別需求
  4. 人類難以明確定義規則

    • 例:自然語言理解
    • 語言的多義性、上下文依賴難以窮舉

成功案例:Spotify Discover Weekly

讓我們看一個「商業價值對齊」做得很好的例子。

背景

Spotify 的「Discover Weekly」是每週一推薦 30 首使用者可能喜歡的新歌。

商業對齊

核心目標:使用者留存率 + 聽歌時長

可衡量的 Metrics:
├─ 每週活躍用戶數(WAU)
├─ Discover Weekly 播放完成率
├─ 新歌探索比例
└─ 訂閱續約率

商業價值:
留存提升 → 訂閱收入增加 + 廣告收入增加

技術選擇

不是用最炫的技術,而是用「夠好且可靠」的技術:
├─ 協同過濾(Collaborative Filtering)
├─ 內容推薦(Audio Features)
└─ 混合模型

決策邏輯:
├─ Latency 不敏感(週更新,非即時)
├─ 成本可控(Batch prediction,不需要即時推論)
└─ 可解釋性高(使用者信任度高)

持續驗證

不只看 Model Metrics(AUC, Precision),更看 Business Metrics:
├─ A/B test 驗證推薦效果
├─ 使用者調查(質性分析)
└─ 長期追蹤留存率變化

為什麼成功?

  1. 商業目標明確:不是「推薦準確度」,而是「使用者留存」
  2. 技術務實:不追求 SOTA,而是「有效且可靠」
  3. 持續驗證:Model Metric 改善 ≠ Business Metric 改善,必須驗證

核心原則

在進入技術細節前,記住這三句話:

  1. 「模型指標只是代理,商業指標才是目標」

    • 不要為了 Accuracy 犧牲業務價值
    • A/B test 是唯一的真理
  2. 「最簡單能 work 的方案,就是最好的方案」

    • 不要為了技術而技術
    • 複雜度是負債,不是資產
  3. 「持續驗證,快速迭代」

    • Model Metric 改善 ≠ Business Metric 改善
    • 上線後持續監控,隨時準備調整

Chapter 1: 模型評估指標 — 選錯指標,全盤皆輸

商業目標對齊後,下一步是選擇正確的評估指標。這是機器學習中最容易出錯、但影響最深遠的決策之一。

為什麼 Accuracy 會騙人?

詐欺檢測的惡夢

想像你在開發一個信用卡詐欺檢測系統:

# 資料分布
total_transactions = 100,000
fraud_transactions = 100  # 只有 0.1% 是詐欺
normal_transactions = 99,900

# 「愚蠢」的模型:全部預測為「正常」
y_true = [0] * 99900 + [1] * 100
y_pred = [0] * 100000  # 全部預測為 0(正常)

from sklearn.metrics import accuracy_score
accuracy = accuracy_score(y_true, y_pred)
print(f"Accuracy: {accuracy:.2%}")
# 輸出:99.90%

結果:Accuracy 99.9%!看起來很厲害,對吧?

但實際上:這個模型完全沒用,它連一筆詐欺都抓不到!

問題出在哪?

Accuracy 的公式:

Accuracy = (TP + TN) / (TP + TN + FP + FN)

當資料極度不平衡時(99.9% 正常,0.1% 詐欺):

  • TN(正確預測正常)= 99,900 → 佔比極大
  • TP(正確預測詐欺)= 0 → 但對 Accuracy 影響很小

核心洞察:在不平衡資料中,Accuracy 被多數類「綁架」了。

混淆矩陣:理解模型行為的基石

所有分類指標都源自混淆矩陣(Confusion Matrix):

                 Predicted
              Positive  Negative
Actual  Pos     TP        FN
        Neg     FP        TN

定義

  • TP (True Positive):正確預測為正類
  • FP (False Positive):錯誤預測為正類(Type I Error,誤報)
  • FN (False Negative):錯誤預測為負類(Type II Error,漏報)
  • TN (True Negative):正確預測為負類

實際案例:詐欺檢測

                 Predicted
              Fraud   Normal
Actual  Fraud  [50]   [50]  ← 抓到 50 個真詐欺,漏掉 50 個
        Normal [100]  [99800] ← 誤判 100 個正常交易

從這個矩陣,我們能看出:

  • Recall = 50%:真詐欺案件,只抓到一半
  • Precision = 33%:模型說是詐欺的,只有 1/3 是真的
  • Accuracy = 99.85%:看起來很高,但其實沒什麼意義

核心指標的數學直覺

Precision(精確率)

公式

Precision = TP / (TP + FP)

直覺:「模型說『是』的時候,有多少是真的?」

實務意義

  • 高 Precision → 模型說「是」時很可靠
  • 低 Precision → 模型亂槍打鳥,很多誤報

何時優化 Precision?

False Positive 代價很高 時:

場景FP 的代價為什麼要優化 Precision
垃圾郵件分類正常郵件被誤判 → 使用者錯過重要信件寧可漏掉一些垃圾郵件
醫療診斷(某些情境)誤診導致不必要的治療(副作用、花費)減少過度治療
推薦系統推薦不相關商品 → 使用者體驗變差只推薦有把握的

案例:郵件分類

# 場景:1000 封郵件,100 封垃圾郵件
# 模型 A:高 Recall,低 Precision
true_spam = 100
predicted_spam = 200
tp = 95  # 抓到 95 封垃圾郵件
fp = 105  # 但誤判了 105 封正常郵件

precision_A = 95 / 200 = 0.475  # 只有 47.5% 是真垃圾郵件
recall_A = 95 / 100 = 0.95

# 使用者抱怨:「為什麼我的重要郵件被丟到垃圾桶!」

決策:這種情境應該提高 Precision,寧可漏掉一些垃圾郵件,也不要誤殺正常郵件。

Recall(召回率 / Sensitivity / True Positive Rate)

公式

Recall = TP / (TP + FN)

直覺:「所有真正的正類樣本中,模型找到了多少?」

實務意義

  • 高 Recall → 模型很少「漏掉」真正的陽性案例
  • 低 Recall → 模型會遺漏很多重要案例

何時優化 Recall?

False Negative 代價很高 時:

場景FN 的代價為什麼要優化 Recall
癌症篩檢漏掉真正的患者 → 延誤治療,危及生命不能漏掉任何一個
詐欺檢測漏掉真詐欺 → 金錢損失寧可多審查一些
火災警報漏掉真火災 → 生命財產損失寧可多警報
搜尋引擎漏掉相關結果 → 使用者找不到資訊召回所有相關結果

案例:癌症篩檢

# 場景:1000 人篩檢,10 人有癌症
# 模型 B:高 Precision,低 Recall
true_cancer = 10
predicted_cancer = 5
tp = 5  # 抓到 5 個真患者
fn = 5  # 但漏掉了 5 個!

recall_B = 5 / 10 = 0.5  # 只找到一半的患者
precision_B = 5 / 5 = 1.0  # 但預測是癌症的 100% 正確

# 醫生的憤怒:「有 5 個患者被漏掉,他們可能會錯過治療黃金期!」

決策:這種情境應該提高 Recall,寧可多一些誤報(再進一步檢查),也不能漏掉真正的患者。

F1 Score:平衡 Precision 與 Recall

公式

F1 = 2 × (Precision × Recall) / (Precision + Recall)

這是 Precision 和 Recall 的調和平均數(Harmonic Mean)。

為什麼用調和平均而非算術平均?

讓我們看一個例子:

# 情況 1:極端不平衡
precision_1 = 1.0
recall_1 = 0.1

arithmetic_mean_1 = (1.0 + 0.1) / 2 = 0.55
harmonic_mean_1 = 2 * (1.0 * 0.1) / (1.0 + 0.1) = 0.18

# 情況 2:相對平衡
precision_2 = 0.8
recall_2 = 0.7

arithmetic_mean_2 = (0.8 + 0.7) / 2 = 0.75
harmonic_mean_2 = 2 * (0.8 * 0.7) / (0.8 + 0.7) = 0.747

觀察

  • 當兩個數值差距大時,調和平均數會接近較小的那個(0.18 vs 0.55)
  • 當兩個數值接近時,兩種平均數差不多(0.747 vs 0.75)

核心洞察:調和平均數會懲罰極端不平衡

直覺類比

  • 算術平均:學生一科 100 分,另一科 10 分 → 平均 55 分(及格)
  • 調和平均:學生一科 100 分,另一科 10 分 → F1 = 18 分(不及格)

你不能靠一科滿分掩蓋另一科不及格。

何時使用 F1 Score?

  • 當 Precision 和 Recall 都重要,且無法明確判斷誰更重要時
  • 當類別不平衡,但 FP 和 FN 的代價相近時
  • 作為一個「初步評估」的綜合指標

Imbalanced Data 的致命陷阱

ROC-AUC 的虛高問題

ROC Curve 的定義:

  • X 軸:False Positive Rate (FPR) = FP / (FP + TN)
  • Y 軸:True Positive Rate (TPR / Recall) = TP / (TP + FN)
  • AUC:曲線下面積,範圍 [0, 1],隨機猜測 = 0.5

看起來很合理,有什麼問題?

讓我們看一個極端不平衡的例子:

# 極度不平衡:99% negative, 1% positive
from sklearn.metrics import roc_auc_score, average_precision_score
import numpy as np

y_true = np.array([0] * 990 + [1] * 10)
y_scores = np.concatenate([
    np.random.uniform(0.1, 0.4, 990),  # negative 的分數較低
    np.random.uniform(0.3, 0.7, 10)    # positive 的分數中等(弱模型)
])

roc_auc = roc_auc_score(y_true, y_scores)
pr_auc = average_precision_score(y_true, y_scores)

print(f"ROC-AUC: {roc_auc:.3f}")  # 可能 0.65-0.75
print(f"PR-AUC: {pr_auc:.3f}")    # 可能 0.15-0.25

為什麼 ROC-AUC 虛高?

關鍵在 FPR 的分母:

FPR = FP / (FP + TN)

當負類樣本極多時(TN 很大):

  • 即使 FP 很大,FPR 也會被稀釋
  • ROC 曲線看起來還不錯

但 Precision 直接反映 FP 的影響:

Precision = TP / (TP + FP)

真實案例對比

模型預測結果:
TP = 8(抓到 8 個正類)
FP = 100(誤判 100 個負類)
FN = 2(漏掉 2 個正類)
TN = 890(正確預測 890 個負類)

ROC 指標:
TPR (Recall) = 8 / 10 = 0.8
FPR = 100 / 990 = 0.10  ← 看起來還好(只有 10%)
→ ROC-AUC 可能有 0.7-0.8

Precision-Recall 指標:
Precision = 8 / 108 = 0.074  ← 只有 7.4% 是真的!
Recall = 0.8
→ PR-AUC 可能只有 0.2-0.3

核心洞察:ROC-AUC 在不平衡資料中會被 TN(大量負類)「撐住」,無法反映真實表現。

決策規則

資料平衡度?
├─ 40%-60%(平衡)→ ROC-AUC
├─ 10%-30%(中度不平衡)→ 兩者都看
└─ < 5%(極度不平衡)→ 必須用 PR-AUC

實務建議

  • 永遠同時看 ROC-AUC 和 PR-AUC
  • 不平衡資料以 PR-AUC 為主
  • 用 Precision-Recall Curve 檢查不同閾值下的表現

決策框架:如何選擇評估指標?

總結前面的討論,這是一個完整的決策樹:

資料類別平衡嗎?
├─ 是(40%-60%)
│  ├─ FP 和 FN 代價相近?
│  │  ├─ 是 → Accuracy, F1
│  │  └─ 否 → 根據代價選 Precision 或 Recall
│  └─ 需要調整閾值?→ ROC-AUC

└─ 否(不平衡)
   ├─ 不平衡程度?
   │  ├─ 輕度(10-30%)→ F1, Weighted F1
   │  └─ 極度(< 5%)→ PR-AUC, F1

   └─ FP vs FN 代價?
      ├─ FP 代價高 → Precision
      ├─ FN 代價高 → Recall
      └─ 都重要 → F1, PR-AUC

實戰案例:詐欺檢測系統

讓我們完整走一遍決策流程:

Step 1: 資料探索

print(y.value_counts(normalize=True))
# 0 (正常):99.5%
# 1 (詐欺):0.5%

→ 極度不平衡

Step 2: 商業成本分析

FP 成本(誤判正常為詐欺):
- 人工審查成本:$10
- 使用者體驗損失:約 $20
- 小計:$30

FN 成本(漏掉真詐欺):
- 平均詐欺金額:$500
- 追回率:30%
- 實際損失:$350

FN / FP 成本比 = 350 / 30 ≈ 11.7

Step 3: 指標選擇

主要指標:PR-AUC(不平衡資料)
輔助指標:
├─ Precision(控制誤報率)
├─ Recall(不能漏太多)
└─ F2 Score(加重 Recall,因為 FN 成本更高)

Step 4: 閾值調整

# 根據成本比例找最優閾值
def business_cost(y_true, y_pred_proba, threshold):
    y_pred = (y_pred_proba >= threshold).astype(int)
    cm = confusion_matrix(y_true, y_pred)
    tn, fp, fn, tp = cm.ravel()

    fp_cost = 30
    fn_cost = 350

    total_cost = fp * fp_cost + fn * fn_cost
    return total_cost

# 測試不同閾值
thresholds = np.linspace(0.01, 0.99, 100)
costs = [business_cost(y_test, y_pred_proba, t) for t in thresholds]

optimal_idx = np.argmin(costs)
optimal_threshold = thresholds[optimal_idx]

print(f"最優閾值: {optimal_threshold:.3f}")
print(f"最小總成本: ${costs[optimal_idx]:,.0f}")

Step 5: 監控與迭代

上線後持續監控:
├─ 每日詐欺偵測率
├─ 誤報率
├─ 實際金錢損失
└─ 人工審查工作量

觸發重訓條件:
├─ PR-AUC 下降 > 5%
├─ 詐欺損失增加 > 20%
└─ 資料分布明顯飄移

章節總結

核心原則

  1. Accuracy 會騙人

    • 不平衡資料中,Accuracy 毫無意義
    • 永遠先檢查資料分布
  2. 理解 Precision vs Recall

    • Precision:「說是的時候,有多少是真的?」
    • Recall:「真的陽性,找到了多少?」
    • 根據 FP 和 FN 的代價決定優化哪個
  3. F1 Score 的真正意義

    • 調和平均會懲罰極端不平衡
    • 不能靠一個指標高來掩蓋另一個低
  4. ROC-AUC vs PR-AUC

    • 不平衡資料:ROC-AUC 會虛高,必須用 PR-AUC
    • 平衡資料:兩者都可以
  5. 商業成本才是王道

    • 模型指標只是代理
    • 決策閾值應由商業成本決定

決策檢查表

  • [ ] 檢查資料平衡度
  • [ ] 計算 FP 和 FN 的商業成本
  • [ ] 選擇對應的主要指標
  • [ ] 設定輔助指標(多角度評估)
  • [ ] 根據成本調整閾值
  • [ ] 上線後持續監控

Chapter 2: EDA — 訓練模型前該做的功課

很多人一拿到資料就急著訓練模型,結果踩了一堆坑。這章聊聊為什麼要先「看一下資料」。

為什麼需要 EDA?

核心問題:每個演算法都有「隱藏的假設」,不做 EDA 就像矇著眼睛開車。

舉幾個真實慘案:

慘案 1:沒發現資料不平衡

# 你以為的分布:正常
print(y.value_counts())
# 0: 5000
# 1: 5000

# 實際的分布:GG
print(y.value_counts())
# 0: 9950
# 1: 50  ← 只有 0.5%

# 結果:
# - 模型偏向預測 0(多數類)
# - Accuracy 看起來 99%,但完全沒用
# - 浪費三天調參,都是白工

慘案 2:沒發現異常值

# 收入特徵
df['income'].describe()
# mean: 50,000
# std: 20,000
# max: 50,000,000  ← WTF?

# 結果:
# - Linear model 被異常值主導
# - 預測結果完全偏掉
# - 重訓三次才發現是資料問題

慘案 3:沒發現缺失值模式

# 某個重要特徵
missing_rate = df['credit_history'].isnull().mean()
print(f"缺失率: {missing_rate:.1%}")
# 缺失率: 60%

# 而且缺失不是隨機的:
# - 新客戶沒有信用歷史(系統性缺失)
# - 直接填 0 或 mean 都是錯的
# - 應該把「是否有信用歷史」當成一個特徵

EDA 的三大目標

  1. 發現資料問題:缺失值、異常值、重複資料、資料錯誤
  2. 理解資料特性:分布、相關性、類別平衡
  3. 驗證假設:模型適用性、特徵有效性

關鍵檢查項目

檢查 1:類別平衡(最容易忽略但影響最大)

為什麼重要?

  • 影響指標選擇(Accuracy 失效)
  • 影響模型訓練(Decision Tree 會偏向多數類)
  • 影響閾值設定

怎麼檢查?

# 方法 1:統計
print(y.value_counts())
print(y.value_counts(normalize=True))

# 方法 2:視覺化
import matplotlib.pyplot as plt
y.value_counts().plot(kind='bar')
plt.title('類別分布')
plt.show()

決策樹

類別比例?
├─ 40%-60% → 平衡,正常訓練就好
├─ 20%-40% → 輕度不平衡
│  └─ 考慮:class_weight='balanced'
├─ 5%-20% → 中度不平衡
│  └─ 必須:SMOTE 或調整閾值
└─ < 5% → 極度不平衡
   └─ 必須:改用 PR-AUC + F1 + 調整閾值

真實案例

我曾經在一個詐欺檢測專案上,一開始沒注意到資料是 99.5% 正常、0.5% 詐欺。結果:

  • 模型 Accuracy 99.7%(看起來超棒)
  • Precision 只有 10%(根本不能用)
  • 浪費了一整週調參

後來發現問題後:

  • 加上 class_weight='balanced'
  • 改用 PR-AUC 當主要指標
  • 調整閾值到 0.2(而非預設 0.5)
  • Precision 提升到 60%,實際可用

檢查 2:特徵分布

為什麼重要?

  • 長尾分布 → Linear models 表現差
  • 異常值 → 主導梯度方向
  • 不同尺度 → 某些演算法會失效(KNN, SVM)

怎麼檢查?

# 統計摘要
print(df.describe())

# 視覺化分布
df.hist(bins=50, figsize=(20,15))
plt.tight_layout()
plt.show()

# 檢查偏態
from scipy.stats import skew
for col in df.select_dtypes(include=['float64', 'int64']).columns:
    skewness = skew(df[col].dropna())
    if abs(skewness) > 1:
        print(f"{col}: skewness = {skewness:.2f} (考慮 log transform)")

決策樹

分布類型?
├─ 長尾/右偏態(收入、房價)
│  └─ 用 log transformation 或 np.log1p()
├─ 有大量異常值
│  ├─ 可以移除?→ 移除 (如資料錯誤)
│  └─ 不能移除?→ RobustScaler 或 clip
├─ 接近正態分布
│  └─ StandardScaler 就夠了
└─ 尺度差異很大(身高 vs 體重)
   └─ StandardScaler 或 MinMaxScaler

實戰案例:房價預測

# 原始分布:右偏態
plt.hist(df['price'], bins=50)
# 大部分在 30-50 萬,但有幾個 500 萬的豪宅

# 問題:
# - Linear regression 被豪宅拉偏
# - R² 只有 0.6

# 解法:log transformation
df['price_log'] = np.log1p(df['price'])
plt.hist(df['price_log'], bins=50)
# 現在接近正態分布

# 結果:
# - R² 提升到 0.85
# - 預測更穩定

檢查 3:缺失值模式

為什麼重要?

  • 隨機缺失 vs 系統性缺失(處理方式完全不同)
  • 缺失率太高 → 可能要捨棄該特徵
  • 缺失本身可能就是一個特徵

怎麼檢查?

# 缺失率統計
missing = df.isnull().sum() / len(df)
missing = missing[missing > 0].sort_values(ascending=False)
print(missing)

# 視覺化缺失模式
import missingno as msno
msno.matrix(df)
plt.show()

# 檢查缺失是否相關
msno.heatmap(df)
plt.show()

決策樹

缺失率?
├─ < 5%
│  └─ 填補:mean/median/mode
├─ 5%-30%
│  ├─ 隨機缺失?
│  │  └─ 填補 + 可選:加上 is_missing 欄位
│  └─ 系統性缺失?
│     └─ 必須加上 is_missing 作為特徵
└─ > 30%
   ├─ 重要特徵?→ 想辦法處理(模型預測缺失值)
   └─ 不重要?→ 直接捨棄

真實案例:信用評分

# 信用歷史長度
credit_history_missing = df['credit_history'].isnull().mean()
# 60% 缺失

# 分析後發現:
# - 缺失 = 新客戶,沒有信用記錄
# - 這本身就是重要資訊!

# 錯誤做法:
df['credit_history'].fillna(0)  # ❌ 把新客戶當成信用差

# 正確做法:
df['has_credit_history'] = (~df['credit_history'].isnull()).astype(int)
df['credit_history'] = df['credit_history'].fillna(0)
# 兩個特徵都保留,模型可以學到「沒有信用記錄」的影響

檢查 4:相關性與多重共線性

為什麼重要?

  • 高度相關的特徵造成冗餘(浪費運算)
  • 多重共線性讓 Linear model 係數不穩定

怎麼檢查?

# 相關矩陣
import seaborn as sns
corr_matrix = df.corr()

plt.figure(figsize=(12, 10))
sns.heatmap(corr_matrix, annot=True, cmap='coolwarm', center=0)
plt.show()

# 找高度相關的特徵對
high_corr_pairs = []
for i in range(len(corr_matrix.columns)):
    for j in range(i+1, len(corr_matrix.columns)):
        if abs(corr_matrix.iloc[i, j]) > 0.9:
            high_corr_pairs.append((
                corr_matrix.columns[i],
                corr_matrix.columns[j],
                corr_matrix.iloc[i, j]
            ))

for feat1, feat2, corr in high_corr_pairs:
    print(f"{feat1} - {feat2}: {corr:.3f}")

決策

相關性 > 0.95?
├─ 是 → 移除其中一個(通常保留更容易解釋的)
└─ 否 → 保留

VIF > 10?(多重共線性)
├─ 是 → 有問題,考慮:
│  ├─ 移除某些相關特徵
│  ├─ PCA 降維
│  └─ 改用 Tree-based models(不受影響)
└─ 否 → OK

實戰案例

# 發現問題:
# total_rooms 和 total_bedrooms 相關性 0.93

# 分析:
# - 房間多 → 臥室也多(廢話)
# - 兩個特徵資訊重複

# 決策:
# 方法 1:移除一個
df = df.drop('total_bedrooms', axis=1)

# 方法 2:創造新特徵
df['avg_rooms_per_bedroom'] = df['total_rooms'] / df['total_bedrooms']
df = df.drop(['total_rooms', 'total_bedrooms'], axis=1)
# 新特徵更有意義:每個臥室平均有幾個房間

Confusion Matrix 的商業解讀

這部分在 Chapter 1 講過了,但再強調一次:不要只看數字,要算成本

              Predicted
           Fraud   Normal
Actual Fraud  [50]  [10]  ← 漏掉 10 個,損失 $100k
       Normal [20]  [920] ← 誤判 20 個,人工審查成本 $2k

重點

  1. 計算 FP 和 FN 的實際成本
  2. 找出最小化總成本的閾值
  3. 不要用預設的 0.5

EDA 的實戰流程

我自己的 checklist:

# 1. 基本資訊
print(df.info())
print(df.describe())

# 2. 目標變數分布
print(y.value_counts(normalize=True))
y.value_counts().plot(kind='bar')

# 3. 缺失值
missing = df.isnull().sum() / len(df)
print(missing[missing > 0])

# 4. 數值特徵分布
df.hist(bins=50, figsize=(20,15))

# 5. 類別特徵分布
for col in df.select_dtypes(include=['object']).columns:
    print(f"\n{col}:")
    print(df[col].value_counts()[:10])

# 6. 相關性
sns.heatmap(df.corr(), annot=True)

# 7. 異常值檢查
from scipy import stats
z_scores = stats.zscore(df.select_dtypes(include=['float64', 'int64']))
outliers = (abs(z_scores) > 3).sum(axis=0)
print(f"異常值數量:\n{outliers[outliers > 0]}")

小結

核心觀念

  1. 不做 EDA 就訓練 = 浪費時間

    • 資料問題比模型選擇更重要
    • 10 分鐘的 EDA 能省下 10 小時的調參
  2. 類別不平衡是最容易忽略的問題

    • 永遠先檢查 y.value_counts()
    • 不平衡時,Accuracy 毫無意義
  3. 視覺化是最便宜的 debug 工具

    • 一張圖勝過 100 行統計數字
    • df.hist()sns.heatmap() 是必備
  4. 缺失值要看模式,不是亂填

    • 系統性缺失本身就是資訊
    • 不要無腦填 mean/median

Chapter 3: Overfitting vs Underfitting — 模型的兩種死法

這是機器學習最核心的概念,但很多人只知道名詞,不懂本質。

本質理解

Underfitting (High Bias)

本質:模型太簡單,連訓練資料的模式都學不好。

比喻

  • 用直線擬合曲線
  • 用小學數學解微積分題
  • 用「身高」預測「收入」(太簡化了)

症狀

Train Error: 高
Test Error: 也高
兩者差距: 小

結論:模型根本學不到東西

實際例子

# 真實關係:二次曲線
X = np.linspace(0, 10, 100).reshape(-1, 1)
y = 2 * X**2 + 3 * X + 5 + noise

# 用線性模型擬合
from sklearn.linear_model import LinearRegression
model = LinearRegression()
model.fit(X_train, y_train)

print(f"Train R²: {model.score(X_train, y_train):.3f}")  # 0.65
print(f"Test R²: {model.score(X_test, y_test):.3f}")    # 0.63

# 兩個都很低 → Underfitting

Overfitting (High Variance)

本質:模型太複雜,把噪音當成模式學起來了。

比喻

  • 背題目的答案,不懂原理
  • 記住每個訓練樣本,沒有泛化能力
  • 考古題 100 分,新題目 0 分

症狀

Train Error: 很低(甚至 0)
Test Error: 很高
兩者差距: 巨大

結論:模型只會背答案,不會舉一反三

實際例子

# 用高次多項式擬合
from sklearn.preprocessing import PolynomialFeatures
from sklearn.pipeline import make_pipeline

# 15 次多項式(過度複雜)
model = make_pipeline(PolynomialFeatures(15), LinearRegression())
model.fit(X_train, y_train)

print(f"Train R²: {model.score(X_train, y_train):.3f}")  # 0.99
print(f"Test R²: {model.score(X_test, y_test):.3f}")    # 0.45

# Train 很高,Test 很低 → Overfitting

診斷:Learning Curve

最直觀的診斷工具:畫出 Train Error vs Test Error

from sklearn.model_selection import learning_curve

train_sizes, train_scores, test_scores = learning_curve(
    model, X, y,
    train_sizes=np.linspace(0.1, 1.0, 10),
    cv=5
)

plt.plot(train_sizes, train_scores.mean(axis=1), label='Train')
plt.plot(train_sizes, test_scores.mean(axis=1), label='Test')
plt.xlabel('Training Set Size')
plt.ylabel('Score')
plt.legend()
plt.show()

三種典型曲線

情況 1: Underfitting
Train ————————— (低且平)
Test  ————————— (低且平)
→ 兩條線都低且接近
→ 增加資料也沒用,需要更複雜的模型

情況 2: Overfitting
Train ————————— (高)
Test  \_____/    (低,有大 gap)
→ Train 高,Test 低,gap 很大
→ 需要 Regularization 或更多資料

情況 3: Good Fit
Train ————————— (高)
Test  ————————— (高,gap 小)
→ 兩條線都高且接近
→ 可以嘗試更複雜的模型看能否再提升

解法與原理

解 Overfitting

原理:限制模型複雜度,或增加資料多樣性

方法為什麼有效?何時使用實際效果
L1/L2 Regularization懲罰大權重,逼迫模型簡化Linear models, Neural Networks通常降低 5-15% overfit
Dropout隨機關閉神經元,防止共適應Neural Networks深度模型必備,降低 10-20% overfit
Early Stopping在驗證集錯誤上升前停止所有迭代式模型簡單有效,省時間
Data Augmentation增加資料多樣性影像、文字、音訊效果最好,但費時
Ensemble多個模型平均,降低變異Tree models (Random Forest)Bagging 能降低 20-30% variance
減少特徵降低模型複雜度Feature selection特徵太多時有效
更多資料讓模型看到更多樣本任何情況最根本的解法,但通常不容易取得

實戰案例:Regularization

from sklearn.linear_model import Ridge, Lasso

# 沒有 regularization:overfitting
model = LinearRegression()
model.fit(X_train, y_train)
print(f"Train: {model.score(X_train, y_train):.3f}")  # 0.95
print(f"Test: {model.score(X_test, y_test):.3f}")    # 0.68

# L2 Regularization (Ridge)
model = Ridge(alpha=1.0)  # alpha 越大,懲罰越重
model.fit(X_train, y_train)
print(f"Train: {model.score(X_train, y_train):.3f}")  # 0.88
print(f"Test: {model.score(X_test, y_test):.3f}")    # 0.82

# Train 降了一點,但 Test 提升了 → 成功!

核心邏輯

  • Regularization 會讓 Train Error 稍微上升(模型被「限制」了)
  • 但 Test Error 會明顯下降(泛化能力變好)
  • 這個 trade-off 是值得的

解 Underfitting

原理:增加模型複雜度,或改善特徵

方法為什麼有效?何時使用
更複雜的模型增加學習能力Linear → Polynomial, Tree → Deep Tree
更多特徵提供更多資訊給模型Feature Engineering
減少 Regularization放鬆限制alpha 太大時
訓練更久給模型更多學習機會Neural Networks (增加 epochs)
Polynomial Features捕捉非線性關係Linear models 遇到非線性資料

實戰案例

# Underfitting:線性模型 + 二次關係
model = LinearRegression()
print(f"Test R²: {model.score(X_test, y_test):.3f}")  # 0.65

# 解法 1:加入多項式特徵
from sklearn.preprocessing import PolynomialFeatures
poly = PolynomialFeatures(degree=2)
X_train_poly = poly.fit_transform(X_train)
X_test_poly = poly.transform(X_test)

model = LinearRegression()
model.fit(X_train_poly, y_train)
print(f"Test R²: {model.score(X_test_poly, y_test):.3f}")  # 0.89

# 解法 2:換更複雜的模型
from sklearn.ensemble import RandomForestRegressor
model = RandomForestRegressor(n_estimators=100)
model.fit(X_train, y_train)
print(f"Test R²: {model.score(X_test, y_test):.3f}")  # 0.91

實戰決策流程

# Step 1: 訓練模型
model.fit(X_train, y_train)
train_score = model.score(X_train, y_train)
test_score = model.score(X_test, y_test)

print(f"Train: {train_score:.3f}")
print(f"Test: {test_score:.3f}")
print(f"Gap: {train_score - test_score:.3f}")

# Step 2: 診斷
if train_score < 0.7 and test_score < 0.7:
    print("→ Underfitting")
    print("   建議:")
    print("   1. 增加模型複雜度")
    print("   2. Feature Engineering")
    print("   3. 減少 Regularization")

elif train_score > 0.9 and (train_score - test_score) > 0.15:
    print("→ Overfitting")
    print("   建議:")
    print("   1. 加 Regularization")
    print("   2. Early Stopping")
    print("   3. 更多資料")
    print("   4. Feature Selection")

else:
    print("→ Good Fit")
    print("   可以嘗試更複雜模型看能否再提升")

小結

記住這些

  1. Underfitting = 模型太笨

    • 症狀:Train 和 Test 都差
    • 解法:增加複雜度
  2. Overfitting = 模型背題目

    • 症狀:Train 好,Test 差
    • 解法:限制複雜度或增加資料
  3. Learning Curve 是最好的診斷工具

    • 一張圖就能看出問題
    • 不要只看單一數字
  4. Regularization 是對抗 Overfit 的第一選擇

    • 簡單、有效、通用
    • 幾乎所有模型都能用

Chapter 4: Training 加速 vs Inference 加速 — 完全不同的優化邏輯

很多人搞混這兩個,結果優化方向錯誤。先理解差異,才知道該優化什麼。

核心差異

維度TrainingInference
目標盡快完成訓練,快速迭代實驗即時回應使用者
頻率每週/每月一次每秒數千次
資源可用 GPU/TPU,成本可控通常用 CPU,成本敏感
Latency 要求分鐘到小時級別 OK毫秒到秒級別
優化重點總時間(wall-clock time)單次推論時間

直覺理解

  • Training:馬拉松,重點是「跑完」
  • Inference:短跑,重點是「每次都快」

Training 加速

為什麼要加速 Training?

真實情境

實驗 1:Baseline model
→ 訓練時間:2 小時
→ 結果:Accuracy 85%

想嘗試的實驗:
- 不同的特徵組合(10 種)
- 不同的超參數(20 種)
- 不同的模型架構(5 種)

總共需要測試:10 × 20 × 5 = 1000 次
總時間:2 小時 × 1000 = 2000 小時 = 83 天

如果能加速到 10 分鐘:
10 分鐘 × 1000 = 167 小時 = 7 天

核心問題:Training 慢 → 實驗少 → 模型差

加速策略

策略 1: Mixed Precision Training (FP16)

原理:用 16-bit 浮點數代替 32-bit

# PyTorch 範例
from torch.cuda.amp import autocast, GradScaler

model = MyModel().cuda()
optimizer = torch.optim.Adam(model.parameters())
scaler = GradScaler()

for data, target in train_loader:
    optimizer.zero_grad()

    # 自動混合精度
    with autocast():
        output = model(data)
        loss = criterion(output, target)

    # 縮放loss,避免underflow
    scaler.scale(loss).backward()
    scaler.step(optimizer)
    scaler.update()

效果

  • 記憶體減半(可用更大的 batch)
  • 速度提升 2-3x
  • 幾乎無精度損失

何時用

  • GPU 訓練(需要 Tensor Cores 支援)
  • 深度學習模型
  • 記憶體不夠大 batch 時

策略 2: Gradient Accumulation

原理:累積多個小 batch 的梯度,模擬大 batch

# 真實 batch size = accumulation_steps × mini_batch_size
accumulation_steps = 4

optimizer.zero_grad()
for i, (data, target) in enumerate(train_loader):
    output = model(data)
    loss = criterion(output, target)

    # 梯度累積
    loss = loss / accumulation_steps
    loss.backward()

    # 每 N 步才更新一次
    if (i + 1) % accumulation_steps == 0:
        optimizer.step()
        optimizer.zero_grad()

為什麼有效?

  • 大 batch 通常訓練更快(GPU 利用率高)
  • 但記憶體不夠 → 用累積模擬

何時用

  • GPU 記憶體不足
  • 想用大 batch 但記憶體有限

策略 3: 資料載入優化

問題:GPU 在等資料,很浪費

# 慢的寫法
train_loader = DataLoader(dataset, batch_size=32, shuffle=True)

# 快的寫法
train_loader = DataLoader(
    dataset,
    batch_size=32,
    shuffle=True,
    num_workers=4,        # 多線程載入
    pin_memory=True,      # 加速 CPU → GPU 傳輸
    prefetch_factor=2     # 預先載入
)

效果

  • 減少 GPU 等待時間
  • 整體加速 20-50%

策略 4: 更好的優化器

# 標準 Adam
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)

# AdamW(更快收斂,Transformer 必備)
optimizer = torch.optim.AdamW(
    model.parameters(),
    lr=0.001,
    weight_decay=0.01
)

# LAMB(大 batch 訓練專用)
# 用在 BERT 等大模型

為什麼有效?

  • 更好的優化器 → 更快收斂 → 更少 epochs
  • AdamW:修正 weight decay,收斂更穩定
  • LAMB:專為大 batch 設計

Inference 加速

為什麼要加速 Inference?

真實場景

推薦系統:
- 每秒 10,000 次推論
- 每次推論需要 100ms
- 需要 10,000 × 0.1 = 1000 秒 ≈ 17 分鐘處理一秒的請求
→ GG,系統癱瘓

如果加速到 10ms:
- 10,000 × 0.01 = 100 秒
- 可以用 10 台機器平行處理
→ OK,可行

成本計算

假設:
- 每天 1 億次推論
- CPU instance: $0.05/hour
- 每次推論 100ms → 需要 2778 小時 → $138.9/天
- 每次推論 10ms → 需要 278 小時 → $13.9/天

加速 10 倍 = 省 $125/天 = $45,625/年

核心問題:Inference 慢 → 成本高 or 使用者體驗差

加速策略

策略 1: Quantization (量化)

原理:FP32 → INT8,模型大小減 4 倍

import torch

# 原始模型 (FP32)
model = MyModel()
model.eval()

# Dynamic Quantization(最簡單)
quantized_model = torch.quantization.quantize_dynamic(
    model,
    {torch.nn.Linear},  # 量化哪些層
    dtype=torch.qint8
)

# 比較大小
torch.save(model.state_dict(), 'model.pth')
torch.save(quantized_model.state_dict(), 'quantized_model.pth')

# 原始:100 MB
# 量化後:25 MB

效果

  • 模型大小減少 4 倍
  • 推論速度提升 2-4 倍
  • 精度損失 < 1%(通常可接受)

何時用

  • 部署到邊緣設備(手機、IoT)
  • CPU 推論
  • 記憶體/頻寬受限

策略 2: Pruning (剪枝)

原理:移除不重要的權重

import torch.nn.utils.prune as prune

# 對 Linear layer 剪枝
prune.l1_unstructured(
    module=model.fc1,
    name='weight',
    amount=0.3  # 移除 30% 的權重
)

# 檢查稀疏度
print(f"Sparsity: {100. * float(torch.sum(model.fc1.weight == 0)) / float(model.fc1.weight.nelement()):.1f}%")

效果

  • 移除 30-50% 權重
  • 速度提升 1.5-2x
  • 精度損失 1-3%

策略 3: Knowledge Distillation (知識蒸餾)

原理:大模型「教」小模型

# Teacher model (大而準)
teacher = LargeModel()  # 1GB, Accuracy 95%
teacher.eval()

# Student model (小而快)
student = SmallModel()  # 100MB

# 蒸餾訓練
for data, target in train_loader:
    # Teacher 的預測(軟標籤)
    with torch.no_grad():
        teacher_output = teacher(data)

    # Student 學習
    student_output = student(data)

    # Loss = 學 Teacher + 學真實標籤
    loss = (
        distillation_loss(student_output, teacher_output) +
        ce_loss(student_output, target)
    )

    loss.backward()
    optimizer.step()

效果

  • 模型大小減少 5-10 倍
  • 速度提升 5-10 倍
  • 保留 95% 的準確度

經典案例:DistilBERT

  • 原始 BERT:340M 參數
  • DistilBERT:66M 參數(減少 40%)
  • 速度:快 60%
  • 準確度:保留 97% 性能

策略 4: Batch Prediction(批次推論)

原理:非即時場景用批次處理

# 即時推論(慢)
for user in users:
    recommendation = model.predict(user)
    send_to_user(recommendation)

# 批次推論(快)
# 每小時批次處理一次
all_users = get_all_users()
recommendations = model.predict_batch(all_users)  # 一次推論
cache_results(recommendations)

# 使用者查詢時直接從 cache 拿

效果

  • GPU 利用率高(大 batch)
  • 成本降低 70-90%
  • Latency:從「毫秒」變成「分鐘」(但很多場景可接受)

適用場景

  • 推薦系統(每小時更新)
  • 報表生成(每天一次)
  • 離線分析

策略 5: Caching(快取)

原理:重複查詢直接回傳

from functools import lru_cache
import hashlib

class ModelWithCache:
    def __init__(self, model):
        self.model = model
        self.cache = {}

    def predict(self, features):
        # 用 hash 當 key
        key = hashlib.md5(str(features).encode()).hexdigest()

        if key not in self.cache:
            self.cache[key] = self.model.predict(features)

        return self.cache[key]

何時有效?

  • 重複查詢多(如熱門商品推薦)
  • 特徵空間有限(如類別變數為主)

決策樹

需要即時回應?
├─ 是
│  ├─ Latency < 10ms?
│  │  ├─ 是 → Quantization + Pruning + Cache
│  │  └─ 否(< 100ms)→ Quantization + Cache
│  └─ 資源有限?(邊緣設備)
│     └─ 是 → Knowledge Distillation

└─ 否(可以非即時)
   └─ Batch Prediction(成本最低)

小結

記住這些

  1. Training vs Inference 的優化邏輯完全不同

    • Training:總時間,用得起 GPU
    • Inference:單次時間,成本敏感
  2. Training 加速

    • FP16:簡單有效,必用
    • 資料載入優化:低垂的果實
    • 更好的優化器:AdamW
  3. Inference 加速

    • Quantization:4 倍壓縮,2-4 倍加速
    • Knowledge Distillation:10 倍加速,保留 95% 效能
    • Batch Prediction:非即時場景的最佳選擇
  4. 成本影響巨大

    • Inference 成本是長期的
    • 加速 10 倍 = 省 90% 成本
    • 值得投資時間優化

Chapter 5: Feature Engineering — 何時用哪種方法?

這章不講「怎麼做」(Claude 會),而是講「何時用」與「為什麼」。

核心問題:Feature Engineering 要解決什麼?

本質:把原始資料轉換成模型能理解的語言。

三大目標

  1. 降低模型學習難度
# 原始:身高 170cm, 體重 70kg
# 模型需要自己學會:BMI = 體重 / (身高²)
# 很難學,因為要學「除法」和「平方」

# Feature Engineering:
df['BMI'] = df['weight'] / (df['height'] / 100) ** 2
# 模型只需要學:BMI 高 → 某種結果
# 簡單多了
  1. 捕捉領域知識
# 電商:購買日期 2024-01-15
# 模型很難從日期學到「週末消費多」

# Feature Engineering:
df['is_weekend'] = df['date'].dt.dayofweek.isin([5, 6])
# 直接告訴模型「這是週末」
  1. 處理特殊資料類型
# 問題:23點 vs 0點 數值差 23,但其實只差 1 小時
# Feature Engineering:
df['hour_sin'] = np.sin(2 * np.pi * df['hour'] / 24)
df['hour_cos'] = np.cos(2 * np.pi * df['hour'] / 24)
# 現在 23點 和 0點 在特徵空間中很接近了

決策框架:類別特徵處理

類別特徵是最常遇到的,但處理方式很多種,該怎麼選?

類別數量?
├─ < 10 個(如性別、星期幾)
│  └─ One-Hot Encoding

├─ 10-100 個(如城市、部門)
│  ├─ Tree-based models?
│  │  └─ 是 → Label Encoding 就夠了
│  └─ Linear models?
│     └─ 是 → One-Hot Encoding

└─ > 100 個(如郵遞區號、User ID)
   ├─ 有目標變數?
   │  └─ 是 → Target Encoding(小心 Data Leakage!)
   └─ 否 → Frequency Encoding 或 Hashing

One-Hot Encoding

何時用

  • 類別數 < 10
  • 類別之間沒有順序關係
  • 用 Linear models 或 Neural Networks

為什麼有效

  • 不引入錯誤的順序關係
  • 模型可以獨立學習每個類別的影響

陷阱

# 類別太多會爆炸
df['city'].nunique()  # 500 個城市
pd.get_dummies(df['city'])  # 產生 500 個特徵 → GG

Label Encoding

何時用

  • Tree-based models (Random Forest, XGBoost)
  • 類別有順序關係(如教育程度:高中 < 大學 < 碩士)

為什麼對 Tree 有效

  • Tree 可以學到「>」「<」的關係
  • 不會被順序誤導

陷阱

# 在 Linear model 用 Label Encoding 會出事
# 例:顏色 [紅, 綠, 藍] → [0, 1, 2]
# 模型會以為「藍色 = 2 × 紅色」→ 錯誤!

Target Encoding

何時用

  • 高基數類別(> 100 個)
  • 有目標變數(supervised learning)
  • 用 Tree-based models(對 leakage 較不敏感)

原理

# 用「該類別的平均目標值」替代類別
city_mean = train_df.groupby('city')['price'].mean()
train_df['city_encoded'] = train_df['city'].map(city_mean)

# 台北的平均房價 80 萬 → 台北 encode 成 80
# 高雄的平均房價 40 萬 → 高雄 encode 成 40

為什麼強大

  • 直接捕捉「類別 → 目標」的關係
  • 降維(500 個城市 → 1 個數值特徵)

致命陷阱:Data Leakage

# ❌ 錯誤:用全部資料計算 mean
city_mean = df.groupby('city')['price'].mean()
df['city_encoded'] = df['city'].map(city_mean)
train, test = train_test_split(df)

# 問題:test set 的資訊洩漏到 encoding 裡了!

# ✅ 正確:只用 training set 計算
train, test = train_test_split(df)
city_mean = train.groupby('city')['price'].mean()
train['city_encoded'] = train['city'].map(city_mean)
test['city_encoded'] = test['city'].map(city_mean)

# 更好:用 Cross-Validation Target Encoding
from category_encoders import TargetEncoder
te = TargetEncoder()
te.fit(X_train, y_train)
X_train_encoded = te.transform(X_train)
X_test_encoded = te.transform(X_test)

決策框架:數值特徵處理

特徵分布?
├─ 長尾/右偏態(收入、房價、訂單金額)
│  └─ Log Transformation: np.log1p(x)

├─ 有大量異常值
│  ├─ 可以移除?(如明顯的資料錯誤)
│  │  └─ 是 → 移除
│  └─ 不能移除?
│     └─ RobustScaler 或 clip

├─ 接近正態分布
│  └─ StandardScaler

└─ 尺度差異很大(身高 cm vs 體重 kg)
   ├─ Neural Network?→ StandardScaler
   ├─ Tree models?→ 不需要 scaling
   └─ 不確定?→ StandardScaler(最安全)

Log Transformation

何時用

  • 分布右偏態(長尾)
  • 資料範圍跨度大(1-1000000)

為什麼有效

# 原始:[10, 100, 1000, 10000, 100000]
# 跨度:10 - 100000(很大)
# 對 Linear model 很難學

# Log 後:[2.3, 4.6, 6.9, 9.2, 11.5]
# 跨度:2.3 - 11.5(小多了)
# 接近線性,模型好學

實戰案例

# 房價預測
plt.hist(df['price'])  # 右偏態,大部分 30-50 萬,少數 500 萬

# 不 transform:R² = 0.65
model = LinearRegression()
model.fit(X_train, y_train)

# Log transform:R² = 0.85
df['price_log'] = np.log1p(df['price'])
model.fit(X_train, df_train['price_log'])

StandardScaler vs MinMaxScaler

StandardScaler (Z-score)

# 轉換後:mean = 0, std = 1
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

何時用

  • 大多數情況(預設選這個)
  • SVM, KNN, Neural Networks
  • 對異常值較不敏感

MinMaxScaler

# 轉換後:min = 0, max = 1
from sklearn.preprocessing import MinMaxScaler
scaler = MinMaxScaler()
X_scaled = scaler.fit_transform(X)

何時用

  • Neural Networks 的輸入層(特別是圖像)
  • 知道明確的最大最小值範圍
  • 需要固定範圍 [0, 1]

差異

StandardScaler:
- 值可能超出 [-3, 3](異常值)
- 對異常值較穩健

MinMaxScaler:
- 值一定在 [0, 1]
- 對異常值敏感(一個極端值會壓縮其他值)

最致命的陷阱:Data Leakage

Data Leakage = 測試集資訊洩漏到訓練過程

這是最容易犯、但影響最嚴重的錯誤。

案例 1:在 split 前做 Scaling

# ❌ 錯誤
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)  # 用全部資料 fit
X_train, X_test = train_test_split(X_scaled)

# 問題:test set 的 mean 和 std 影響了 scaling
# 結果:test set 表現虛高,部署後崩潰

# ✅ 正確
X_train, X_test = train_test_split(X)
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)  # 只用 train fit
X_test_scaled = scaler.transform(X_test)        # test 用 train 的參數

案例 2:Feature Selection 用全部資料

# ❌ 錯誤
from sklearn.feature_selection import SelectKBest
selector = SelectKBest(k=10)
X_selected = selector.fit_transform(X, y)  # 全部資料
X_train, X_test = train_test_split(X_selected)

# 問題:feature selection 看到 test set 了

# ✅ 正確
X_train, X_test, y_train, y_test = train_test_split(X, y)
selector = SelectKBest(k=10)
X_train_selected = selector.fit_transform(X_train, y_train)
X_test_selected = selector.transform(X_test)

案例 3:時間序列用未來資訊

# ❌ 錯誤:移動平均包含未來資料
df['sales_ma7'] = df['sales'].rolling(7).mean()

# 問題:第 7 天的 MA 包含第 8-14 天的資料(未來資訊)

# ✅ 正確:shift 確保只用過去資料
df['sales_ma7'] = df['sales'].shift(1).rolling(7).mean()

小結

記住這些

  1. 類別特徵處理決策

    • < 10 個:One-Hot
    • 10-100 個:Tree 用 Label,Linear 用 One-Hot
    • 100 個:Target Encoding(小心 leakage)

  2. 數值特徵處理決策

    • 長尾:Log transformation
    • 異常值:RobustScaler
    • 預設:StandardScaler
  3. Data Leakage 是最大的坑

    • 永遠先 split,再做任何處理
    • Test set 絕對不能影響 training
    • 時間序列不能用未來資訊
  4. Feature Engineering > 模型選擇

    • 好特徵 + 簡單模型 > 壞特徵 + 複雜模型
    • 領域知識 > 自動化工具

Chapter 6: 常見模型的致命弱點 — 避坑指南

每個模型都有「隱藏的假設」,違反假設就會失效。這章整理各模型的致命弱點。

Linear Models (Linear Regression, Logistic Regression)

假設:特徵與目標是線性關係

致命弱點

1. 無法捕捉非線性

# 真實關係:y = x²
X = np.linspace(0, 10, 100).reshape(-1, 1)
y = X**2

model = LinearRegression()
model.fit(X, y)
# R² 很低,因為是線性模型硬擬合二次曲線

解法

  • 加入多項式特徵:PolynomialFeatures(degree=2)
  • 換模型:Tree-based models

2. 對異常值敏感

# 一個極端值就能拉偏整條線
X = [1, 2, 3, 4, 5]
y = [2, 4, 6, 8, 100]  ← 最後一個是異常值

model = LinearRegression()
# 整條線被拉高,前面 4 個點的預測都變差

解法

  • 移除異常值
  • 用 RobustScaler
  • 換模型:Huber Regression(對異常值穩健)

3. 多重共線性

# 兩個特徵高度相關(如總房間數 vs 臥室數)
# 係數變得不穩定,難以解釋

解法

  • 移除相關特徵
  • Ridge Regression (L2 regularization)

何時避開 Linear Models

  • 特徵與目標明顯非線性(先畫 scatter plot 檢查)
  • 有大量異常值
  • 需要捕捉複雜的交互作用

Decision Trees

假設:資料可被逐步切割

致命弱點

1. 極易 Overfit

# 預設設定會長到每個葉子只有 1 個樣本
tree = DecisionTreeClassifier()  # 沒限制深度
tree.fit(X_train, y_train)

print(f"Train: {tree.score(X_train, y_train):.3f}")  # 1.000
print(f"Test: {tree.score(X_test, y_test):.3f}")    # 0.650

# 完全記住訓練集,泛化能力差

解法

  • 限制深度:max_depth=5
  • 限制葉子樣本數:min_samples_leaf=20
  • 換 Random Forest(ensemble 降低 variance)

2. 對資料變動敏感

# 只改變一個樣本,整棵樹結構可能完全不同
# 導致預測不穩定

3. 無法 Extrapolate

# 訓練資料:房價 10-100 萬
# 測試資料:房價 150 萬
# Decision Tree 只會預測「訓練集看過的最大值」
# 無法預測超出訓練範圍的值

何時避開 Decision Trees

  • 資料量小(< 1000 筆)→ 容易 overfit
  • 需要預測訓練範圍外的值
  • 需要穩定的預測(小變動不能影響太大)

Random Forest / XGBoost

假設:Ensemble 可以降低 variance

致命弱點

1. 訓練慢

# 100 棵樹,每棵深度 10
# 訓練時間可能是單棵樹的 100 倍

2. 模型大,部署成本高

# 100 棵完整的樹存下來
# 模型檔案可能幾百 MB
# 推論時需要走訪所有樹

3. 對高維稀疏資料效果差

# 文字資料(TF-IDF):10000 維,但 95% 是 0
# Tree 在稀疏資料中很難找到好的分割點
# Neural Networks 或 Linear Models 可能更好

4. 無法 Extrapolate

# 跟 Decision Tree 一樣的問題
# 訓練集房價 10-100 萬
# 預測 150 萬的房子會失敗

何時避開 Random Forest

  • 需要毫秒級推論(每次預測都要走訪 100 棵樹)
  • 記憶體有限(模型太大)
  • 高維稀疏資料(文字、one-hot 後有幾千個特徵)

SVM

假設:資料可線性分割(或用 kernel trick)

致命弱點

1. 訓練時間 O(n²) 或更差

# 大資料集(> 10 萬筆)訓練不動
# n = 100,000 → n² = 10,000,000,000

2. 對特徵尺度極度敏感

# 沒做 scaling,SVM 基本上廢了
# 必須 StandardScaler 或 MinMaxScaler

3. 超參數調整困難

# kernel, C, gamma 互相影響
# 需要大量實驗找到好的組合

4. 機率輸出未校準

# svm.predict_proba() 的機率不準
# 需要額外做 Calibration

何時避開 SVM

  • 資料量大(> 10 萬筆)
  • 沒時間調參
  • 需要可靠的機率估計

Neural Networks

假設:萬能逼近器(理論上能學任何函數)

致命弱點

1. 需要大量資料

# < 1000 筆:效果通常不如 Tree models
# 1000-10000 筆:可能持平
# > 10000 筆:才開始有優勢

2. 超參數地獄

# 需要調整:
# - 層數
# - 每層神經元數
# - Activation function
# - Learning rate
# - Batch size
# - Optimizer
# - Regularization (Dropout, L2)
# - ...

# 組合爆炸,調參很花時間

3. 訓練慢,需要 GPU

# 沒 GPU,訓練時間可能是天級別
# 有 GPU,成本增加

4. 黑盒,難解釋

# 金融、醫療領域需要可解釋性
# Neural Networks 很難說明「為什麼」

何時避開 Neural Networks

  • 資料量小(< 1 萬筆)
  • 需要模型可解釋性
  • 沒有 GPU 資源
  • 表格資料(Tree models 通常更好)

模型選擇決策樹

資料量?
├─ < 1000 筆
│  ├─ 需要可解釋?→ Linear Models
│  └─ 不需要?→ Random Forest (小的)

├─ 1k-100k
│  ├─ 表格資料?
│  │  └─ Random Forest / XGBoost
│  └─ 影像/文字/序列?
│     └─ Neural Networks

└─ > 100k
   ├─ 表格資料?→ XGBoost / LightGBM
   └─ 影像/文字/序列?→ Neural Networks

需要可解釋性?
├─ 是 → Linear Models / Decision Tree
└─ 否 → 任何模型

訓練時間有限?
├─ 是 → LightGBM / Linear Models
└─ 否 → 任何模型

推論速度 < 10ms?
├─ 是 → Linear Models / Small Trees
└─ 否 → 任何模型

小結

記住這些

  1. Linear Models

    • 快速、可解釋
    • 但無法捕捉非線性、對異常值敏感
  2. Decision Trees

    • 極易 overfit
    • 無法 extrapolate(預測範圍外的值)
  3. Random Forest / XGBoost

    • 表格資料首選
    • 但大、慢、無法 extrapolate
  4. SVM

    • 大資料集訓練不動
    • 對 scaling 敏感
  5. Neural Networks

    • 需要大量資料
    • 超參數多,難調
  6. 沒有銀彈

    • 根據資料量、時間、可解釋性需求選擇
    • 從簡單模型開始,逐步升級

全文總結:7 個關鍵決策點

終於寫完了!讓我們回顧這篇超長筆記的核心。

1. 商業價值對齊

核心:先想清楚要解決什麼問題

  • 模型指標(Accuracy, AUC)只是代理
  • 商業指標(收入、成本、使用者留存)才是目標
  • A/B test 是唯一的真理
  • 最簡單能 work 的方案就是最好的方案

記住:不要為了技術而技術。

2. 評估指標選擇

核心:Accuracy 會騙人,要根據成本選指標

  • 類別不平衡 → 不能用 Accuracy
  • FP 代價高 → 優化 Precision(垃圾郵件)
  • FN 代價高 → 優化 Recall(癌症篩檢)
  • 極度不平衡 → 必須用 PR-AUC

記住:先檢查資料平衡度,再選指標。

3. EDA(資料探索)

核心:訓練前先看資料,10 分鐘省下 10 小時

  • 檢查類別平衡(最容易忽略)
  • 檢查特徵分布(長尾?異常值?)
  • 檢查缺失值模式(隨機 vs 系統性)
  • 檢查相關性(移除冗餘特徵)

記住:視覺化是最便宜的 debug 工具。

4. Overfitting vs Underfitting

核心:用 Learning Curve 診斷

  • Underfitting:Train 和 Test 都差 → 增加複雜度
  • Overfitting:Train 好,Test 差 → Regularization
  • Regularization 是對抗 Overfit 的第一選擇

記住:不要只看單一數字,要看 Train vs Test。

5. Training vs Inference 加速

核心:兩者優化邏輯完全不同

Training 加速

  • FP16 混合精度(記憶體減半,速度提升 2-3x)
  • 資料載入優化(num_workers, pin_memory)

Inference 加速

  • Quantization(模型大小減 4 倍)
  • Knowledge Distillation(10 倍加速,保留 95% 效能)
  • Batch Prediction(非即時場景省 70-90% 成本)

記住:Inference 成本是長期的,值得投資時間優化。

6. Feature Engineering

核心:何時用哪種方法

類別特徵

  • < 10 個 → One-Hot
  • 10-100 個 → Tree 用 Label,Linear 用 One-Hot
  • 100 個 → Target Encoding(小心 leakage)

數值特徵

  • 長尾 → Log transformation
  • 異常值 → RobustScaler
  • 預設 → StandardScaler

最大的坑:Data Leakage

  • 永遠先 split,再做任何處理

記住:好特徵 > 複雜模型。

7. 模型選擇

核心:沒有銀彈,根據場景選擇

快速決策

  • < 1000 筆 → Linear Models / Small Tree
  • 表格資料 → XGBoost / LightGBM
  • 影像/文字 → Neural Networks
  • 需要可解釋 → Linear / Decision Tree
  • 需要快速推論 → Linear Models

記住:從簡單模型開始,逐步升級。


最後的話

這篇筆記整理了我這幾年踩過的坑。機器學習不是背公式、套模型,而是做決策:

  • 何時用哪個指標?
  • 為什麼模型會失效?
  • 如何避開陷阱?

Vibe Coding 時代,Claude 幫你寫 code,但決策還是得靠你自己。

希望這些筆記能幫你少走一些彎路。如果有幫助,歡迎分享給其他人。

如果有任何問題或想討論的,歡迎留言交流!