K-means 量「誰離誰近」、DBSCAN 量「誰在誰的鄰域裡」——兩個都是靠距離吃飯的演算法。而距離這種東西,只要有一欄的數字特別大,它就只聽那一欄的話。本頁用 Wine 資料實跑給你看:未縮放時分群「其實只用一欄」,而且連同類的兩瓶酒都會被量成天涯海角。
同一批數值特徵的量尺差異很大,研究者將縮放與 K-means 放在同一 Pipeline;若末端改為以距離判定鄰域的 DBSCAN,也有相同的尺度考量。此處在分群前使用 StandardScaler 的主要理由為何?
from sklearn.cluster import KMeans # 拿出 K-means 分群工具 from sklearn.pipeline import make_pipeline # 拿出「串管線」的快速寫法 from sklearn.preprocessing import StandardScaler # 拿出標準化工具 cluster_pipe = make_pipeline( # 組管線:先縮放、再分群 StandardScaler(), # 第一站:把每欄調成同一量尺 KMeans(n_clusters=3, random_state=42, n_init=10) # 第二站:分 3 群(起點試 10 次) ) # 管線組裝完成 labels = cluster_pipe.fit_predict(X) # 一口氣:學縮放 → 轉換 → 分群,回傳群號
想像你要判斷「哪些人條件相近」,手上兩欄資料:身高(公尺,1.5~1.9)和年薪(元,300,000~1,600,000)。把兩欄直接代進距離公式,身高差 0.3 跟年薪差 300,000 加在一起——身高連小數點都排不上,「像不像」完全由薪水說了算。這不是薪水比較重要,只是它的數字比較大。
本題的 Wine 資料一模一樣:13 個化學檢測值裡,proline(脯胺酸)的量尺是 278~1680,其他欄多半在 0.1~5 之間。量尺差了兩三個數量級,距離公式裡誰說話,根本不用比。
點擊後出現漸進式說明:白話說明 → 說清楚一點 → 常見錯誤與考點。
點擊後出現漸進式說明。
八行,一行一行走完。右上角的「看位置」可以把這一行放回完整程式裡看。
from sklearn.cluster import KMeans # 從「分群」抽屜拿出 K-means
sklearn.cluster 是分群演算法的抽屜:KMeans、DBSCAN、AgglomerativeClustering(階層分群)都住這裡。K-means 的玩法:先猜 k 個「群中心」,把每筆資料分給距離最近的中心,再把中心移到自己群員的平均位置,反覆到穩定。
注意「距離最近」四個字——它的每一步都在算距離。這就是整題的伏筆:距離一被某欄綁架,K-means 就跟著被綁架。
from sklearn.pipeline import make_pipeline # 把好幾站串成一條生產線的工具
make_pipeline 是 Pipeline 的快速版:把幾個步驟依序丟進去,它自動串成一條「生產線」並自動取好每一站的名字(用類別名的小寫:本題兩站叫 standardscaler、kmeans——實跑確認)。
跟正式版 Pipeline([("scaler", ...), ("km", ...)]) 的差別只在「要不要自己命名」。功能一模一樣:對管線做的每個動作,會依序流過每一站。
from sklearn.preprocessing import StandardScaler # 每欄減均值、除標準差的縮放器
sklearn.preprocessing 是前處理抽屜。StandardScaler 對每一欄各自做兩件事:減掉這欄的平均、除以這欄的標準差——輸出就是 z 分數:「這個值比平均高/低幾個標準差」。
cluster_pipe = make_pipeline( # 組生產線,取名 cluster_pipe(括號先不關)
呼叫 make_pipeline(...),把成品貼上 cluster_pipe 的名牌。括號沒關——第 4 到 7 行是同一句話。放進去的順序就是生產線的順序:資料會先流過第 5 行那站,再流到第 6 行那站。
StandardScaler(), # 第一站:先把 13 欄調成同一量尺
生產線的第一站。它排在 KMeans 前面,所以 KMeans 看到的永遠是「已經站上同一量尺」的資料——距離公式裡 13 個特徵平權發言。
為什麼要放進管線、而不是自己先轉換好再丟給 KMeans?因為管線會把「怎麼轉換」記住(每欄的平均與標準差都存在這一站裡)——之後拿新資料來 predict,會自動用同一套轉換,不會忘記、也不會拿新資料重算。這正是上一系列 Pipeline 頁講的紀律,分群版。
KMeans(n_clusters=3, random_state=42, n_init=10) # 第二站:分 3 群、起點試 10 次 ) # 括號關上,管線組裝完成
三個設定:n_clusters=3 要分成 3 群(k 就是這個 3);random_state=42 固定亂數;n_init=10 是「隨機起點試 10 次、留下最好的一次」——K-means 的結果會受初始中心影響,多試幾個起點、取群內距離平方和(inertia)最小的那次,是標準保險。
為什麼 n_init 要明寫?因為 sklearn 新版把預設值改成 "auto",明寫 10 可以讓行為固定、老程式不噴警告——考題照抄實務寫法。
labels = cluster_pipe.fit_predict(X) # 學縮放 → 轉換 → 分群,回傳每筆的群號
fit_predict 是「訓練+回答」二合一:管線先讓 Scaler 學會每欄的平均與標準差並轉換資料,再讓 KMeans 在轉換後的資料上分群,最後回傳每一筆資料的群號(0、1、2 的一排數字)。
注意分群沒有 y——fit_predict(X) 裡只有 X。沒有人給答案,演算法只憑「誰跟誰近」自己把資料分堆,這就是非監督學習。
題目說「數值特徵的量尺差異很大」。Wine 有多大?把 13 欄的標準差攤開:proline 的標準差 314,第二名 magnesium 只有 14,其他 11 欄全部不到 3.5。換算成「各欄佔總變異的比例」更嚇人:
K-means 與 DBSCAN 用的都是歐氏距離:
\[ d(a,b) \;=\; \sqrt{\sum_{j=1}^{13} (a_j - b_j)^2 } \]
公式對 13 個特徵一視同仁——但誰的差值平方大,誰就主導總和。把實際的兩瓶酒拆開看:
把兩個版本都跑出來,跟真實品種(釀酒葡萄的三個品種)對答案,用 ARI 打分:
題目特別補一句「末端改為以距離判定鄰域的 DBSCAN,也有相同的尺度考量」。DBSCAN 的核心參數 eps 是「鄰域半徑」——半徑是一個帶單位的長度:在原始尺度上,這個單位實質上就是 proline 的單位,其他特徵的差異在半徑裡根本量不出來。實跑掃一輪:
把 Scaler 跟分群器串進同一條管線,買到三件事:① 順序不會忘——每次都是「先縮放、再分群」;② 轉換參數被記住——每欄的平均與標準差存在 scaler 站裡,之後新資料 predict 自動用同一套轉換;③ 若要搭配交叉驗證或網格搜尋(例如掃 n_clusters),縮放會只在訓練折上學——跟本系列第一頁(Pipeline 防洩漏)同一條紀律。
make_pipeline 自動把兩站命名為 standardscaler、kmeans(類別名小寫);KMeans 的 n_init=10 在這份資料上 10 次起點都收斂到同一組中心(inertia 1277.9)——它是保險絲,資料難的時候才看得出差別。分群是非監督學習——從頭到尾沒有「正確標籤」這種東西,fit_predict 回傳的 0/1/2 只是任意群號(實跑:品種 0 的酒拿到的群號是 2)。縮放器更不可能生出標籤,它只是改量尺。
實跑全套佐證:proline 獨佔 99.77% 變異、兩瓶酒的距離 99% 由它決定、同品種的距離比跨品種還遠、未縮放 K-means 與「只用 proline 一欄」完全等價(ARI 1.000)、標準化後 ARI 0.371 → 0.897;DBSCAN 的 eps 在原始尺度下失去意義。距離導向演算法 + 量尺懸殊 = 必須先縮放。
兩個演算法的原理根本不同(切球形 vs 依密度長形狀),縮放只是讓它們「都拿到公平的距離」,不會讓它們答案一致——實跑同一份標準化資料:K-means ARI 0.897、DBSCAN 最好 0.384。看到「保證」「任何資料」這種絕對句,先懷疑。
三重錯誤:StandardScaler 輸出是連續的 z 分數(實跑 proline 欄變 −1.49~+2.97,不是 0/1);「壓到 [0,1] 區間」是 MinMaxScaler、「只剩 0 與 1」是 Binarizer/OneHot——別家的事;而且標準化不消除離群值(實跑:把一瓶 proline 乘 10,標準化後 z = 12.9,還是巨大離群)。
再看一次同一段程式。這次你知道 StandardScaler 那一站在守什麼了。
同一批數值特徵的量尺差異很大,研究者將縮放與 K-means 放在同一 Pipeline;若末端改為以距離判定鄰域的 DBSCAN,也有相同的尺度考量。此處在分群前使用 StandardScaler 的主要理由為何?
cluster_pipe = make_pipeline( # 組管線 StandardScaler(), # 先把 13 欄調成同一量尺 KMeans(n_clusters=3, random_state=42, n_init=10) # 再用距離分 3 群 ) # 組裝完成 labels = cluster_pipe.fit_predict(X) # 回傳群號(不是標籤)
八題,全部都是本題的延伸。答錯會直接告訴你錯在哪。
make_pipeline 自動幫兩站取名 standardscaler、kmeans。fit_predict(X) 裡只有 X 沒有 y——分群是非監督學習。StandardScaler 對每欄各自「減平均、除標準差」,輸出 z 分數:每欄平均 0、標準差 1,相對大小原封不動。「縮放與距離導向演算法」是中級科目二資料準備 × 科目三演算法的交叉考點,出題率極高。最常見的五種問法:① 給一條 Scaler+分群(或 KNN/SVM)的 Pipeline 問縮放理由(本題這種,答案永遠往「距離公平」靠);② 問哪些演算法需要縮放(靠距離/梯度的:K-means、DBSCAN、KNN、SVM、PCA、神經網路、線性模型含正則化)、哪些不太需要(樹家族——按單欄門檻切分,量尺不影響);③ StandardScaler/MinMaxScaler/Binarizer 三兄弟的辨析(D 選項那種張冠李戴);④ 問群號是否等於類別標籤(非監督沒有標籤);⑤ 縮放該在切分前還是後做(訓練集上 fit、進 Pipeline——防洩漏)。一句口訣:量距離的演算法都怕單位,先讓每一欄站上同一個量尺。
需要縮放的是「把不同特徵加在同一條公式裡」的演算法:算距離的(K-means、DBSCAN、KNN、階層分群)、找最大變異方向的(PCA)、用梯度下降或正則化懲罰的(邏輯迴歸、SVM、神經網路、Lasso/Ridge)。樹家族(決策樹、隨機森林、GBDT)不需要——它們每次只問「某一欄是否大於某門檻」,永遠單欄比較、永遠不把兩欄相加,量尺再懸殊也不影響切點的選擇(單調轉換不改變樹的行為)。這是考題最愛的對比題:同一份量尺懸殊的資料,隨機森林照跑、K-means 就會被 proline 綁架。
四個都在 sklearn.preprocessing,功能完全不同。StandardScaler:減均值除標準差 → z 分數,範圍不固定(本站實跑 proline 欄 −1.49~+2.97)。MinMaxScaler:壓到 [0,1] 區間(最小值變 0、最大值變 1)——D 選項把它跟 Standard 混了一半。RobustScaler:用中位數與 IQR 取代均值與標準差,對離群值比較不敏感——資料髒的時候的替代品。Binarizer:依門檻把值變成 0 或 1——「只有 0 與 1」是它(或 OneHotEncoder 對類別欄)的事。考題辨析:看到「0 到 1 之間」選 MinMax、「平均 0 標準差 1」選 Standard、「只有 0 和 1」選 Binarizer。
不行,而且方向就錯了。標準化是線性轉換(每欄減一個數、除一個數),所有點的相對位置原封不動——離群值只是從「大得離譜的原始值」變成「大得離譜的 z 值」。本站實跑:把一瓶酒的 proline 乘 10,標準化後它的 z 分數是 12.9(正常資料 99.7% 落在 ±3 內),照樣一眼離群。更麻煩的是離群值會污染標準化本身:它把該欄的均值與標準差拉歪,讓其他正常點的 z 值變形——所以資料很髒時改用 RobustScaler,或先處理離群值再縮放。「消除離群值」是另一套流程(偵測+處理),不是縮放器的工作。
群號是任意編號,直接拿去跟 y 算 accuracy 是常見錯誤(編號對不上就顯得很爛)。正確做法有兩類:① 用「對編號不敏感」的指標——ARI(調整蘭德指數,本站用的;1=完美、0=隨機猜、可為負)、NMI(正規化互資訊);② 先用匈牙利演算法或眾數映射把群號對應到最可能的類別,再算 accuracy。另外注意:真實應用中通常根本沒有 y(有 y 幹嘛分群),這時改用內部指標評估——silhouette 係數、inertia 手肘法(站內都有專頁)。
K-means:要事先給 k、假設群大致是「圓球狀、大小相近」、每一筆都會被分到某群(沒有噪點概念)、對離群值敏感。DBSCAN:不用給 k(給 eps 與 min_samples),能抓不規則形狀(月牙、環狀)、天生會把稀疏點標成噪點(-1)——很適合異常偵測;代價是 eps 難調、對「密度不均」的資料吃力。本站實跑就是誠實案例:Wine 是橢圓雲+密度不均,DBSCAN 縮放後最好也只有 ARI 0.384,K-means 卻有 0.897——先看資料形狀選演算法,縮放則是兩者共同的前置紀律。順帶一提站內另有 HDBSCAN 頁(自動適應密度的進化版)。
三個理由。① 轉換參數要被記住:scaler 站存著每欄的均值與標準差,之後新資料 predict 會自動套同一組參數——自己在外面轉換,很容易新舊資料用了不同的均值。② 防洩漏:若搭配交叉驗證(例如掃 n_clusters 或給下游監督模型用),Pipeline 保證每折都只用訓練折學縮放參數——先對全部資料 fit_transform 再切分,就是系列第一頁講的經典洩漏。③ 部署簡潔:一個物件帶著完整流程,存檔、載入、上線都是一條龍。這題把 Scaler 和 KMeans 綁在一起,就是這條紀律的分群版。
K-means 對「初始質心的位置」敏感:起點不好可能收斂到局部最佳(某兩群被黏在一起、某群被切成兩半)。n_init=10 的意思是「隨機起點整套跑 10 次,留下 inertia(群內距離平方和)最小的那次」——買保險。本站實跑的 Wine 標準化後結構清楚,n_init=1 跟 10 收斂到同一組中心(inertia 都是 1277.9),但難資料上差異可以很大。另外 sklearn 1.4 起預設值改為 "auto"(k-means++ 初始化時 1 次),明寫 n_init=10 讓行為跨版本一致——這也是考題照抄實務寫法的原因。初始化本身預設用 k-means++(把起點彼此撒開),已經比純隨機穩很多。