同一題標準化,四種寫法。實跑結果:沒有漂移時四方同分 0.7767——分數完全看不出誰作弊;等測試集漂移 +2.5(上線常態),誠實的 B 掉到 0.5000(模型把 300 筆全判 1=丟銅板),兩把尺的 C 卻假報 0.7767——那成績上線兌現不了。A 更陰:合併刻尺讓模型跟著考卷變——訓練集一個字沒動,換一批測試集,coef_ 最大差 0.2247、f3 係數從 +0.07 翻成 -0.15。正解只有一段:尺在訓練集刻一次,兩邊用同一把。
下列哪一段標準化程式能避免把測試集統計量洩漏到訓練流程?
# 已有 X_train 與 X_test from sklearn.preprocessing import StandardScaler # 拿出標準化工具(每欄減平均、除以標準差) # A. scaler = StandardScaler().fit(pd.concat([X_train, X_test])) # 訓練+測試黏成一包,一起算 mean/std X_train_s = scaler.transform(X_train); X_test_s = scaler.transform(X_test) # 用這把「混合尺」量兩邊 # B. scaler = StandardScaler() # 先造一把空白的尺 X_train_s = scaler.fit_transform(X_train) # 在訓練集上刻尺+量 X_test_s = scaler.transform(X_test) # 測試集只量,用同一把尺 # C. X_train_s = StandardScaler().fit_transform(X_train) # 訓練集刻自己的尺、量自己 X_test_s = StandardScaler().fit_transform(X_test) # 測試集另刻一把尺、量自己 # D. scaler = StandardScaler().fit(X_test) # 用測試集刻尺 X_train_s = scaler.transform(X_train); X_test_s = scaler.transform(X_test) # 用這把「測試尺」量兩邊
合格的磅秤出廠前,用標準砝碼校準好刻度;到你家之後,它只做一件事——照著已刻好的刻度秤你。標準化的尺(每欄的平均與標準差)也一樣:在訓練集上刻一次(fit),之後訓練集、測試集、上線資料通通用同一把尺量(transform)。
四個選項就是四種校秤方式:B 工廠校準、全世界同一把尺(正解);A 把你家的東西也搬進工廠一起校——秤的刻度摻了「未來要秤的東西」;D 乾脆只用你家的東西校;C 最妙——每家自己校自己的秤,於是你家的「1 公斤」和工廠的「1 公斤」不是同一個重量,東西一過門,重量就變了。
mean_ 與 scale_,fit 給誰,尺就是誰刻的。② 題目問的是「洩漏到訓練流程」——只要測試統計量影響了 X_train_s(模型的教材),就是洩漏:A 與 D 都中。③ 兩邊必須同一把尺——尺不同,同一個原始值會落在不同座標:C 的病根。點擊後出現漸進式說明:白話說明 → 說清楚一點 → 常見錯誤與考點。
點擊後出現漸進式說明。
題幹只給了一行 import,勝負全在四個選項的寫法。先把正解 B 一行一行走完,其他三個選項在 03~05 節逐一開庭。
from sklearn.preprocessing import StandardScaler # 從前處理抽屜拿出「刻尺器」
StandardScaler 把每個特徵欄調成「平均 0、標準差 1」:z = (x − mean_) ÷ scale_。它是系列的老面孔——第 7 頁(分群前的 StandardScaler)講為什麼要縮放、第 14 頁講fit/predict 誰學誰用,本頁講尺該在哪裡刻。注意題目說「已有 X_train 與 X_test」——切分已經完成,現在的問題純粹是:刻尺的資料,該餵誰?
scaler = StandardScaler() # 尺造好了,還沒刻度——mean_ 與 scale_ 都不存在
先把尺存進變數 scaler——這個動作是 B 能贏的第一步:尺有了名字,才能「刻一次、用兩次」。對照選項 C:它從頭到尾沒把尺存起來,兩行各自 StandardScaler() 造了兩把匿名的尺,用完即丟——兩把尺,就是兩套座標。
X_train_s = scaler.fit_transform(X_train) # 刻尺(學 mean_/scale_)+馬上量訓練集
fit_transform = 先 fit(把訓練 700 筆的每欄平均與標準差刻上尺)再 transform(用剛刻好的尺量訓練集自己)。本站實跑刻出來的尺(f0 欄):mean_ = 0.5133、scale_ = 1.2672——注意,這兩個數只來自訓練集,X_test 連一根汗毛都沒參與。整場流程裡「fit」這個字只出現這一次、只餵訓練集——這正是題目要的「不洩漏」。
X_test_s = scaler.transform(X_test) # 不重刻——拿訓練刻好的尺量測試集
同一個 scaler、只呼叫 transform——測試集被訓練刻的尺量:(x − 0.5133) ÷ 1.2672(f0 欄)。於是兩件好事同時成立:① 測試統計量從未流進任何「學」的動作(不洩漏);② 訓練與測試活在同一套座標系——模型在哪個空間學,就在哪個空間考。上線時這把尺跟著模型一起凍結交付,一筆一筆來的新資料照量不誤——第 14 頁 predict 的機制在這裡完整重演。
四個選項其實只差一件事:fit 的括號裡放了誰。本站實跑(1000 筆 × 5 特徵、70/30 切分),把三把尺的刻度全部翻出來:
四段程式各自配一顆 LogisticRegression(max_iter=1000) 訓練、評分。兩個場景(全部實跑):
題目問的是洩漏到「訓練流程」。A 的 pd.concat 讓合併統計量進了 X_train_s——模型的教材被測試集污染。抓現行的方法:訓練集一個字不動,只換測試批次,看模型變不變。
np.array_equal 比對完全相同、對同一筆資料的機率永遠是 0.5849。「你的模型長什麼樣,居然由測試集決定」——這就是「洩漏到訓練流程」六個字的具體樣貌。D 連遮羞布都不要(整把尺都是測試集的),罪名同款、加重量刑。C 看起來最無辜:測試統計量確實沒碰到訓練側。但它犯了另一條罪——訓練與測試各用一把尺。開庭,兩件展品(全部實跑):
fit_transform → 平均=自己 → 五個特徵全變 0 → 人人 σ(0.1027) = 0.5256(這筆的正解其實是 0.7768)——第 14 頁「重新 fit 的世界」原案重演。C 的病名:評估時靠不住、部署時做不到。把四個選項的罪狀排成一張表——考場上掃一眼 fit( 的括號就能判案:
| 選項 | fit 看到誰 | 洩漏去向 | 兩邊同尺? | 上線單筆可用? |
|---|---|---|---|---|
| A 合併刻尺 | 訓練+測試 | 訓練流程+評估 | 同尺(混合尺) | 尺含歷史測試集,換批就變 |
| B 訓練刻尺 | 只有訓練集 | 無 | 同尺(訓練尺) | 可以——尺凍結隨模型交付 |
| C 各刻各的 | 各自(訓練側乾淨) | 評估(批內取暖) | 兩把尺、兩套座標 | 不行——單筆全零 |
| D 測試刻尺 | 只有測試集 | 訓練流程+評估(最赤裸) | 同尺(測試尺) | 不行——上線沒有「測試集」 |
防呆版:B 的兩行手寫,正是 Pipeline([('scaler', StandardScaler()), ('model', LogisticRegression())]) 內部自動做的事——本站實跑:Pipeline 的分數 0.7767、300 筆機率與 B 逐位元相同(np.array_equal True)。B 是 Pipeline 的手寫展開;Pipeline 是 B 的防呆包裝——機制拆解在第 14 頁(fit 學、predict 用)、配交叉驗證的完整版在第 1 頁:CV 裡每一折都要「重刻訓練尺」,把 pipe 整顆丟進 cross_val_score 就自動做對。
尺的刻度由訓練+測試 1000 筆一起算——測試統計量經由 X_train_s 流進模型教材,正是題目點名的「洩漏到訓練流程」。實跑抓現行:訓練集一字不動、只換一批(漂移)測試集,A 的 coef_ 最大差 0.2247、f3 係數從 +0.0742 翻成 -0.1505——模型跟著考卷變。順帶一提:它的分數平時跟 B 一模一樣(0.7767)——分數看不出這種弊,流程才看得出。
尺在訓練集刻一次、兩邊用同一把:fit 只餵訓練 700 筆(f0 的 mean_ 0.5133、scale_ 1.2672),測試集只被 transform。三重驗收全過:測試統計量從未進任何「學」的動作(不洩漏);訓練測試同座標系(漂移來了誠實現形:0.5000,全判 1——這才是上線的真話);尺凍結可交付,單筆照量。與 Pipeline 逐位元同分——它就是 Pipeline 的手寫展開。
訓練側確實乾淨,但訓練與測試各用一把尺:同一個 1.00,訓練尺說 z=0.384、漂移測試自尺說 z=-1.6397——座標系裂開。更毒的是它的分數最好看:漂移場景 B 誠實 0.5000、C 假報 0.7767——兩把尺把漂移悄悄抹掉,而上線單筆 fit_transform 全零、人人 0.5256,那成績根本兌現不了。分數越好看、流程越骯髒的活教材。
最赤裸的一款:刻度百分之百來自 300 筆測試集——訓練特徵、模型係數全部踩在測試統計量上,訓練流程與評估一起淪陷。上線更尷尬:正式環境沒有「測試集」這種東西可以刻尺,這段程式根本無法部署。考場判法:X_test 出現在 fit 的括號裡,直接出局——A 和 D 都是這一刀。
再看一次四段程式。這次你手上有判案口訣了:掃一眼 fit( 的括號——X_test 在裡面就出局;再數尺的把數——兩把也出局。
scaler = StandardScaler() # 正解 B:一把尺 X_train_s = scaler.fit_transform(X_train) # 刻尺只在訓練集——f0 尺:mean_ 0.5133 X_test_s = scaler.transform(X_test) # 同一把尺量測試——漂移來了誠實現形
八題,全部都是本題的延伸。答錯會直接告訴你錯在哪。
scaler.fit_transform(X_train) + scaler.transform(X_test)——尺在訓練集刻一次,兩邊用同一把。fit( 的括號裡出現 X_test → 出局(A 的 concat、D 的直接 fit);② 出現兩次 fit_transform → 兩把尺,出局(C)。「標準化與資料洩漏」是中級科目三機器學習技術與應用的高頻考點,跟切分順序、Pipeline、交叉驗證綁在一起出。最常見的六種問法:① 給四段 scaler 程式選不洩漏的(本題——掃 fit 括號+數尺的把數);② 問測試集該用 fit_transform 還是 transform(transform,永遠);③ 問「先標準化再切分」對不對(不對——等於 A 的合併刻尺);④ 問兩邊各自 fit_transform 會怎樣(兩把尺、座標裂開=C);⑤ 問交叉驗證時標準化放哪(放 Pipeline 裡,每折重刻訓練尺);⑥ 問為什麼洩漏的分數更好看(統計量偷含了評估資料的資訊——樂觀偏誤)。一句口訣:尺在訓練集刻一次——fit_transform 給訓練、transform 給測試。
因為測試集的角色是「模擬上線後沒看過的資料」。尺(mean_ 與 scale_)一旦摻了測試統計量,X_train_s 的每個數字就都帶著測試集的影子——模型的教材被污染,這正是題目說的「洩漏到訓練流程」。本站實跑抓現行:A 的合併刻尺之下,訓練集一字不動、只換一批測試集,模型 coef_ 最大差 0.2247、f3 係數從 +0.0742 翻成 -0.1505——你的模型長什麼樣,由測試集決定。乾淨的 B 在同一實驗裡 coef_ 完全不變(np.array_equal True)。摻「一點」也一樣是摻:權重小只是差距小,方向已經錯了。
C 犯的是另一條罪:兩把尺。訓練用訓練的 mean/std、測試用測試的——同一個原始值在兩個座標系有兩個位置(實跑:1.00 在訓練尺 z=0.384、在漂移後的測試自尺 z=-1.6397,高於平均變遠低於平均)。模型在訓練座標系學的邊界,拿到測試座標系用,全部對不上號。更陰險的是它的分數常常「更好看」:漂移場景實跑 B 誠實 0.5000、C 假報 0.7767——第二把尺把漂移悄悄抹掉,等於評估時偷偷幫測試集「校正」了,而這個校正上線根本做不到(單筆全零)。所以 C 是「評估靠不住、部署做不到」雙罪並罰——也是新手最常寫出來的版本。
同 A。先在全資料上 fit_transform 再 train_test_split,等於尺由「訓練+測試」一起刻——跟 pd.concat 合併刻尺一模一樣,只是動作順序把洩漏藏得更隱蔽。判斷法不變:問「刻尺那一刻,尺看到了誰」。正確順序永遠是:先切分 → 只在訓練集 fit → 兩邊 transform。切分相關的完整討論在第 10 頁(時序資料還要加「不可打散時間」);把這條紀律自動化的做法,就是把 scaler 放進 Pipeline(第 1、14 頁)。
兩件事分開講。本題場景一四方同分 0.7767——因為資料乾淨、分佈穩定,尺差一點點不影響分類邊界;這說明「分數正常」不等於「流程乾淨」,弊要用流程檢查,不能等分數報警。分數變好看的時機:評估資料的資訊透過統計量流進流程時——本頁最極端的是 C 在漂移場景假報 0.7767(真話是 0.5);第 1 頁更誇張:純亂數配洩漏流程假造 AUC 0.929(真值 0.5)。共同機制:模型或前處理「偷看」了要考它的資料,考出來的當然偏高——這就是樂觀偏誤,上線那天一次還清。
放進 Pipeline,跟模型一起被每一折重新 fit。CV 的每一折都是一次小型「訓練/驗證」切分——尺必須只用該折的訓練部分刻,驗證部分只 transform;在 CV 外面先把全資料標準化好,等於每一折都拿了「含驗證資料」的尺(A 的罪在 CV 裡重演 k 次)。寫法:cross_val_score(Pipeline([('scaler', StandardScaler()), ('model', …)]), X, y, cv=5)——sklearn 自動每折重刻。第 1 頁的洩漏對照(0.929 假 vs 0.657 真)就是這條紀律的破與立;第 14 頁實跑了 Pipeline 進 CV 的乾淨五折。
用訓練期刻好、隨模型一起凍結交付的那把尺——跟測試集的處理完全同款。上線資料常一筆一筆來:一筆資料算不出自己的平均與標準差(硬算=C 的單筆災難:全零特徵、人人 σ(0.1027)=0.5256)。所以正確部署是把 scaler 與模型打包(joblib 存整顆 Pipeline),新資料進來 transform → predict 一氣呵成。附帶的營運課題:尺凍結了,資料卻會漂(實跑:f0 漂 +2.5,成績從 0.7767 掉到 0.5)——所以上線要監控輸入分佈,漂太多就回訓練集重刻尺重訓,而不是讓推論端偷偷自我標準化。
三頁一條龍。第 7 頁(分群前的 StandardScaler):為什麼要縮放——量綱懸殊時距離被單一特徵綁架(proline 獨佔 99.77% 變異)。第 14 頁(Pipeline 的 fit 與 predict):縮放參數的一生——fit 學、predict 用,predict 前後 array_equal 全 True。本頁:尺該在哪裡刻——fit 只餵訓練集,兩邊同一把尺,四種寫法的對錯全實跑。再往上接第 1 頁:把這條紀律放進交叉驗證與 Pipeline 的完整工程版。四頁合起來,就是 StandardScaler 從「為什麼」到「怎麼寫」到「怎麼上線」的全部考點。