連續型與類別型特徵混在一起,怎麼做特徵工程?
在一個同時包含連續型特徵與類別型特徵的資料集中,若希望透過適當的特徵工程流程來提升模型整體表現,下列哪一種作法最為合適?
有一個資料集,裡面有兩種特徵:「連續型特徵」(例如年齡、收入、購買金額,是數字)和「類別型特徵」(例如城市、性別、商品類別,是文字標籤)。
為了讓模型表現更好,要對這兩種特徵分別做適當的處理(特徵工程)。
問你:哪一種做法最為合適?
一句話總結
連續型和類別型特徵混合時,最好的做法是:連續特徵做標準化(Standardization)讓數值縮放一致,類別特徵用目標編碼(Target Encoding)轉成有意義的數值,再生成交互特徵捕捉兩種特徵的聯合效應,這樣每種特徵都被正確處理,模型整體表現最好。
先感受問題:電商推薦系統怎麼混用兩種特徵
假設「台灣購物平台購易網」要預測使用者購買某商品的機率。資料集裡有:
連續型特徵(數字):
過去 30 天消費金額:12,500 元
平均每次瀏覽時間:4.2 分鐘
會員天數:820 天
類別型特徵(文字標籤):
性別:女
最常購買類別:美妝
會員等級:黃金
機器學習模型只能吃數字,所以「台北」「女」「美妝」這些文字標籤要先轉成數字。但轉換方式不同,效果差很多。
同時,「年齡 35」跟「消費金額 12500」這兩個數字差了幾百倍,如果不縮放,模型會誤以為「消費金額」比「年齡」重要幾百倍,其實只是單位不同。
錯誤處理方式的五個坑
- 對類別用標籤編碼(Label Encoding)產生假順序:把「台北=1、台中=2、高雄=3」,模型會以為高雄比台北「大」,城市之間其實沒有大小關係,這個數字順序是憑空捏造的,會誤導模型。
- 連續特徵不縮放,大數值吃掉小數值的影響力:「消費金額」範圍 0~50,000 元,「年齡」範圍 18~80 歲。不縮放的話,梯度下降時消費金額的步伐大得多,年齡的信號幾乎被淹沒。
- 把連續特徵全部離散化,丟失精度:把年齡 35 變成「青壯年」、消費 12500 元變成「中高消費」,喪失了 34 歲和 36 歲的差異、11000 元和 14000 元的差異,模型因此損失精確性。
- 類別特徵直接 One-Hot Encoding,維度爆炸:如果城市有 300 個、商品類別有 1000 個,One-Hot 後特徵維度暴增到幾千維,訓練慢、容易過擬合。
- 忽略交互特徵,錯過組合信號:「台北的女性黃金會員」的購買行為可能和「高雄的男性普通會員」完全不同,但只看單個特徵看不出來,需要生成交互特徵(如城市 × 性別 × 會員等級)才能捕捉。
標準化 + 目標編碼 + 交互特徵,怎麼解
回到購易網。正確的特徵工程流程:
連續特徵:標準化(Standardization)
把年齡、消費金額、瀏覽時間都轉成「平均值 0、標準差 1」的分佈。年齡 35 歲在所有使用者裡是什麼位置?比平均大還是小、大多少個標準差?這樣模型就能公平比較不同量級的特徵。
類別特徵:目標編碼(Target Encoding)
把「台北」替換成「住台北的使用者,平均購買機率是多少」(例如 0.34)。這樣編碼保留了類別和目標變數的統計關係,不會有假順序問題,也不會維度爆炸。
生成交互特徵
把「城市 × 會員等級」「年齡 × 消費金額」這類組合特徵加進來,捕捉兩個維度的聯合效應。
年齡(標準化)× 消費金額(標準化)→ 新特徵
這就是選項 C 講的:對連續特徵做標準化,類別特徵採用目標編碼,並生成交互特徵提升模型表現。
技術版:標準化、目標編碼、交互特徵的程式碼實作
中級考試大概率會考程式碼跟公式,所以這部分你還是要學。但如果現在學起來很痛苦,可以先跳過,等讀完其他題目回頭再來。
import pandas as pd
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import KFold
import numpy as np
# 假設 df 是購易網資料集
# 連續特徵列表
continuous_cols = ["age", "spend_30d", "avg_browse_min", "member_days"]
# 類別特徵列表
categorical_cols = ["city", "gender", "category", "member_level"]
# Step 1: 標準化連續特徵
scaler = StandardScaler()
df[continuous_cols] = scaler.fit_transform(df[continuous_cols])
# Step 2: 目標編碼類別特徵(使用 K-Fold 避免 target leakage)
target = "purchased"
kf = KFold(n_splits=5, shuffle=True, random_state=42)
for col in categorical_cols:
df[f"{col}_te"] = 0.0
for train_idx, val_idx in kf.split(df):
mean_map = df.iloc[train_idx].groupby(col)[target].mean()
df.loc[df.index[val_idx], f"{col}_te"] = df.iloc[val_idx][col].map(mean_map)
# Step 3: 生成交互特徵
df["city_x_level_te"] = df["city_te"] * df["member_level_te"]
df["age_x_spend"] = df["age"] * df["spend_30d"]
- 標準化:把所有連續數字換算成「跟平均值差幾個標準差」,讓大小不同的特徵站在同一個基準上比
- 目標編碼:把「台北」換成「住台北的人平均購買率」,讓類別變成有意義的數字
- K-Fold 防漏:用交叉驗證方式做目標編碼,避免用自己的標籤算自己的分數(target leakage)
- 交互特徵:把兩個編碼後的特徵相乘,捕捉組合效應
| 故事 | 程式碼 |
|---|---|
| 把連續特徵縮放到均值 0 標準差 1 | StandardScaler().fit_transform(df[cols]) |
| 算每個城市的平均購買率 | df.groupby(col)[target].mean() |
| 把城市名換成那個均值 | df[col].map(mean_map) |
| 生成城市 × 會員等級交互特徵 | df["city_te"] * df["member_level_te"] |
- StandardScaler
- 把每個特徵轉成 z-score:(x - 均值) / 標準差。轉換後整體均值 = 0,標準差 = 1。
- Target Encoding(目標編碼)
- 把類別值替換成「該類別在訓練集裡對應目標變數的平均值」,讓類別特徵帶有統計意義。
- K-Fold(K 折交叉驗證)
- 把資料分成 K 份,每次用其他 K-1 份的均值來編碼當前這份,防止用自己的標籤算自己的分數(target leakage)。
- Interaction Feature(交互特徵)
- 兩個特徵相乘(或組合)產生新特徵,捕捉它們的聯合效應,線性模型尤其需要。
標準化公式:
z = (x - μ) / σ
# x:原始值,μ:該特徵的均值,σ:標準差
目標編碼公式(簡化版):
TE(category_k) = mean(target | category == k)
# 即「屬於這個類別的樣本,目標變數的平均值」
蓋住程式碼,說出 3 步驟的作用:
- 標準化:讓不同量級的連續特徵能公平競爭
- 目標編碼:把類別換成有統計意義的數字,不造假順序
- 交互特徵:捕捉兩個特徵的聯合效應
為什麼其他選項是錯的
A將類別型特徵使用標籤編碼(Label Encoding)轉換後,與連續特徵直接合併進行模型訓練
把城市、性別等類別特徵用標籤編碼(Label Encoding)轉成整數(台北=0、台中=1、高雄=2),然後直接跟連續特徵合在一起訓練。
標籤編碼會為無序類別創造「假順序」。城市之間沒有大小關係,但 Label Encoding 之後模型會誤以為「高雄(2)比台北(0)大」,這個假序數關係會誤導線性模型、邏輯迴歸等對數值大小敏感的演算法。決策樹類模型雖然不受影響,但本題問的是「最合適」的做法,標籤編碼不是處理無序類別的最佳方式。
知道「類別特徵要先編碼」但對 Label Encoding 的適用條件不熟的考生。Label Encoding 適合有序類別(小學 < 國中 < 高中 < 大學),不適合無序類別(城市、顏色、品牌)。
B將連續特徵進行離散化(Discretization)或分桶(Binning)轉為類別型特徵,統一以類別方式處理
把年齡、消費金額這類連續數字切成幾個區間(例如「18-30 歲」「31-45 歲」),變成類別,再統一用類別方式處理。
離散化會損失資訊。34 歲和 36 歲被分到同一個桶就沒有區別了,29 歲和 31 歲明明只差 2 歲卻落在不同桶。這種人為的邊界讓模型看不到年齡的連續趨勢。此外,切分點的選擇本身就很主觀,不同切法結果差異很大,反而增加了設計複雜度而不是簡化。
聽過「有時候離散化能提升模型表現」的考生,把這個技巧誤用在全部連續特徵上。離散化偶爾有用,但把所有連續特徵都強制轉成類別,丟失的資訊遠大於得到的好處。
D只保留連續特徵,忽略類別型變量以簡化模型
把城市、性別、商品類別、會員等級全部丟掉,只用年齡、消費金額等數字特徵訓練模型。
類別特徵往往包含極高的預測力。「這個人是黃金會員」「她最常買美妝」這些資訊對預測購買行為非常重要,比年齡或消費金額更能代表行為偏好。把它們全部丟掉等於主動降低模型準確率。「簡化」不等於「更好」,如果簡化的代價是丟掉有用資訊,那就得不償失。
被「類別特徵處理麻煩」嚇到,覺得乾脆不用的考生。或者聽過「Occam's Razor(奧卡姆剃刀)越簡單越好」就過度應用。記住:特徵工程的目標是保留有用資訊並排除雜訊,不是把所有麻煩的特徵都刪掉。
同個考點下次怎麼變形
目標編碼什麼情況下會出問題?
目標編碼看起來很完美,有沒有什麼邊界條件會讓它失效?
最大的風險是「目標洩漏」(Target Leakage):如果用全部資料(包含驗證集自己)計算目標均值,模型相當於「看到了答案」,訓練時準確率很高但測試時大崩。解法是用 K-Fold 交叉驗證方式計算,或加入平滑(Smoothing)混合全局均值,減少小樣本類別的過擬合。
有哪些情況下 Label Encoding 反而是正確選擇?
題目說 Label Encoding 有問題,但有沒有它適合用的場景?
有,而且很常見。Label Encoding 適合「有序類別」(Ordinal Features):教育程度(國中 < 高中 < 大學 < 研究所)、商品評分(差 < 普通 < 好 < 很好)、會員等級(一般 < 銀卡 < 金卡 < 白金)。這些類別本來就有大小順序,Label Encoding 保留了這個順序,是合理的。
除了目標編碼,還有哪些處理高基數類別特徵(幾百個城市)的方法?
城市有 300 個,One-Hot 太大,Label Encoding 有假序,目標編碼外還有別的選擇嗎?
幾個常用方法:1. 頻率編碼(Frequency Encoding):把城市名換成「這個城市在資料集裡出現幾次 / 百分比」;2. Embedding(嵌入層):讓模型自己學習每個類別的低維向量表示,深度學習常用;3. 二元編碼(Binary Encoding):類似 One-Hot 但用二進位壓縮維度;4. 把低頻類別合併成「其他」類,降低基數後再 One-Hot。
自然語言處理(NLP)裡的文字特徵,跟類別型特徵處理方式一樣嗎?
文字也是「類別」的一種(每個詞是一個類別),應該類似?
有些概念相通,但尺度完全不同。一般類別特徵可能有幾十到幾百個值,詞彙表可能有幾萬到幾十萬個詞,One-Hot 完全不可行。NLP 用詞嵌入(Word Embedding,如 Word2Vec、BERT)把每個詞轉成稠密向量,相當於更複雜版的 Embedding 編碼。另外 NLP 有詞序(word order)的概念,一般類別特徵沒有,處理方式因此更複雜。
怎麼評估特徵工程做完之後,有沒有真的改善模型?
做完標準化、目標編碼,怎麼知道有沒有用?
幾個評估方式:1. 交叉驗證 AUC 或 F1-score 的提升幅度,最直接;2. 特徵重要性(Feature Importance)看新加的交互特徵有沒有被模型採用;3. 對比實驗(Ablation Study):只做標準化、只做目標編碼、兩者都做、再加交互特徵,逐步看每個步驟的貢獻;4. Learning Curve:特徵工程改善後,模型的偏差和變異數應該都往好的方向移動。
想再往下看,這 5 個
- 獨熱編碼(One-hot Encoding)將類別特徵轉成二進位向量,適合低基數類別,與目標編碼是處理類別特徵的兩種主要對比方法。
- 特徵工程(Feature Engineering)本題的核心主題,對連續特徵和類別特徵採用不同的轉換策略,並生成交互特徵,是提升模型效能的關鍵步驟。
- 特徵縮放(Feature Scaling)包含標準化(Standardization)在內的連續特徵量級對齊技術,對線性模型和神經網路特別重要,是本題正解的第一步。
- 標籤編碼(Label Encoding)將類別特徵轉成整數,暗示類別間存在大小順序,本題選項 A 的陷阱:直接用於無序類別特徵會誤導模型。
- 資料前處理(Data Preprocessing)特徵工程的上游步驟,包含缺失值填補、異常值處理,確保連續和類別特徵的品質後才能做有效的編碼與縮放。