🗺️ AI 學習與考證地圖
中級科目三程式實戰 · Pipeline 與前處理

fit 學、predict 用:
X_test 的洞,填的是訓練學到的 -0.12

800 筆資料、5 個特徵、挖了 310 個洞。三站生產線——補洞、縮放、模型——pipe.fit(X_train, y_train) 一聲令下三站依序「學」:中位數、平均與標準差、迴歸係數各自存進口袋;pipe.predict(X_test) 三站只「用」不再學。實跑證據:predict 前後四組參數 np.array_equal 全 True;X_test 自己的中位數明明是 -0.21,洞卻填訓練學到的 -0.12。要是像選項 A 說的重新 fit——單筆預測會變成全零特徵、人人同分 0.4965

閱讀模式

00題目

閱讀下列 scikit-learn 程式,何者敘述正確?

pipe = Pipeline([                                    # 開始組一條三站生產線
    ('imputer', SimpleImputer(strategy='median')),  # 第一站:用中位數補洞
    ('scaler', StandardScaler()),                   # 第二站:標準化
    ('model', LogisticRegression(max_iter=1000))    # 第三站:模型
])                                                  # 生產線組裝完成(還沒學任何東西)
pipe.fit(X_train, y_train)                          # 三站依序在「訓練資料」上學
pred = pipe.predict(X_test)                         # 三站依序「用」學到的參數處理 X_test

先說:健檢報告的「參考區間」是誰定的?

你去健檢,報告上每項數值旁邊都印著參考區間——那個區間是拿大量人群事先統計好的,不是拿今天來的十個客人現場重算的。輪到你抽血,檢驗所做的事只有一件:拿你的數值,對照那份「早就定好」的區間。這就是 fit 與 predict 的分工——fit 是「建參考區間」(只在訓練資料上做一次),predict 是「拿新資料來對照」(永遠重用同一份區間)。

要是反過來,每天用當天客人重定義「正常值」會怎樣?今天只來你一個人——你的數值就是平均值,你永遠「完全正常」。這不是比喻誇飾:05 節實跑給你看,predict 若真的重新 fit,單筆預測的特徵會被自己的平均標準化成全零,每個人拿到一模一樣的機率 0.4965。

三個先立好的事實: Pipeline 是照順序串起來的生產線:前面的站要會 transform(填補、縮放),最後一站是模型。 fit——每站把學到的參數存進自己口袋(statistics_mean_coef_)。 predict——前面每站只 transform、最後模型只出答案,全程沒有任何一站重新學

先點開看:Pipeline 生產線fit:學predict:用

不熟 AI 專有名詞?你需要先認識下列名詞

點擊後出現漸進式說明:白話說明 → 說清楚一點 → 常見錯誤與考點。

生產線與三站
Pipeline 生產線SimpleImputer 補洞工StandardScaler 縮放工LogisticRegression
兩個動詞
fit:學predict:用transformfit_transform
各站學到的參數
statistics_ 訓練中位數mean_ 與 scale_coef_ 與 intercept_max_iter=1000
為什麼要這樣分工
訓練集與測試集的分工資料洩漏部署與上線「重新 fit」的世界

不熟 Python?你需要先認識下列名詞

點擊後出現漸進式說明

把工具拿進來
importfrom
生產線的寫法
[( , ), ( , )] 清單裝元組元組 tuple'imputer' 字串命名
呼叫與設定
( ) 呼叫括號strategy='median' 關鍵字引數= 指派

01逐行拆解:第 1 行到第 7 行

七行,一行一行走完。右上角的「看位置」可以把這一行放回完整程式裡看。

第 1 行開始組一條生產線
pipe = Pipeline([   # 造一條生產線,名字叫 pipe;[ 開始列站

Pipeline 收一個清單,清單裡每一項是一個 ('名字', 工具) 的元組——照寫的順序就是資料流過的順序。這行的 [ 只是打開清單,生產線還在組裝中;此刻什麼都還沒學,只是把三站的空白工具擺上輸送帶。

相關名詞:Pipeline 生產線清單裝元組= 指派

第 2 行第一站:中位數補洞工
('imputer', SimpleImputer(strategy='median')),   # 站名 imputer:把 NaN 換成中位數

SimpleImputer(strategy='median')補洞工:fit 時把每個特徵欄的中位數學起來(存進 statistics_),transform 時把該欄的 NaN 全部換成那個數。本站實跑:訓練集 600 筆有 237 個洞,學到的五個中位數是 [0.82, -0.06, -0.81, -0.12, -0.33]'imputer' 只是你幫這一站取的名字,之後可以用 pipe.named_steps['imputer'] 把它叫出來問話。

相關名詞:SimpleImputer 補洞工中位數 medianNaN 缺值'imputer' 字串命名

第 3 行第二站:標準化縮放工
('scaler', StandardScaler()),   # 站名 scaler:每欄減平均、除以標準差

StandardScaler縮放工:fit 時學每欄的平均(mean_)與標準差(scale_),transform 時做 (x − mean_) ÷ scale_,讓每個特徵都變成「平均 0、標準差 1」的尺度——系列第 7 頁(分群前的 StandardScaler)整頁都在講它為什麼重要。注意順序:它排在補洞工之後,所以它學統計量時看到的是「洞已補好的訓練資料」——NaN 是算不了平均的。

相關名詞:StandardScaler 縮放工mean_ 與 scale_元組 tuple

第 4 行第三站:模型本人
('model', LogisticRegression(max_iter=1000))   # 站名 model:邏輯迴歸,最多迭代 1000 輪

生產線的最後一站是模型:LogisticRegression 學一組係數 coef_ 與截距 intercept_,把特徵加權後過 sigmoid 變成機率。max_iter=1000 是給優化器的迭代上限——不是「訓練 1000 次」;本站實跑第 12 輪就收斂,1000 只是保險絲(系列第 1 頁就見過它:沒標準化時邏輯迴歸會收斂不了、噴 ConvergenceWarning)。

相關名詞:LogisticRegressionmax_iter=1000coef_ 與 intercept_關鍵字引數

第 5 行組裝完成——但還沒開工
])   # ] 關清單、) 關 Pipeline——三站就位,等指令

] 收掉清單、) 收掉 Pipeline(。到這裡 pipe 只是一條組好但空白的生產線:三站的口袋裡都還沒有參數——statistics_mean_coef_ 通通不存在,現在就叫它 predict 只會得到 NotFittedError。建立物件不等於學習——學習要等下一行。

相關名詞:named_steps( ) 呼叫括號

第 6 行fit:三站依序在訓練資料上「學」
pipe.fit(X_train, y_train)   # 補洞工學中位數 → 縮放工學平均/標準差 → 模型學係數

整題的上半場。Pipeline 的 fit 是一場接力賽 imputer 在 X_train 上 fit_transform——學中位數、順手把訓練資料的洞補掉; scaler 在「補好洞的訓練資料」上 fit_transform——學平均與標準差、順手標準化; model 在「補好又縮好的訓練資料」配 y_train 上 fit——學係數。三站學到的東西全部來自 X_train,y_train 只有最後一站用到。02 節把每一棒學到的實跑參數攤開。

相關名詞:fit:學fit_transform訓練集與測試集的分工

第 7 行predict:三站依序「用」,全程不再學
pred = pipe.predict(X_test)   # 用訓練學到的中位數補洞 → 用訓練的平均/標準差縮放 → 模型出答案

下半場只有一個動詞:。imputer 對 X_test 只做 transform——洞用訓練學到的中位數填;scaler 只做 transform——用訓練學到的平均與標準差縮放;model 只做 predict——用訓練學到的係數出答案。沒有任何一站重新 fit——這正是選項 A 與 D 的分水嶺,03 節用 np.array_equal 當場對質。

一句話記住本題:fit 學、predict 用——參數只在訓練資料上學一次,測試與上線永遠重用。X_test 在整條生產線裡只被「處理」,從沒被「學習」。

相關名詞:predict:用transformstatistics_ 訓練中位數

02fit 的接力賽:三站各學了什麼(實跑)

pipe.fit(X_train, y_train) 這一行執行完,三站的口袋分別多了什麼?本站實跑(800 筆 × 5 特徵、310 個 NaN,75/25 切分):

互動實驗室:fit/predict 接力賽切換兩個動詞,看三站各做什麼
本站實跑(fit 之後翻口袋):imputer 學到 statistics_[0.82, -0.06, -0.81, -0.12, -0.33](訓練 600 筆、237 個洞的五欄中位數);scaler 學到 mean_ = [0.7154, -0.0472, -0.6927, -0.0456, -0.4338]、scale_ = [1.4284, 0.9221, 1.4822, 1.3701, 1.9564];model 學到 coef_[0.5316, -0.141, -0.4632, 3.1811, -2.7509]intercept_ = -0.014——max_iter 給到 1000,第 12 輪就收斂。三站學到的每一個數字,原料全部是 X_train

相關名詞:statistics_mean_ 與 scale_coef_ 與 intercept_

03predict 的三站:只用不學——當場對質

選項 A 說 predict 會「重新以 X_test 的中位數與平均數 fit 前處理器」。最直接的對質方法:predict 前把參數抄一份,predict 完再比一次

stat_before = pipe.named_steps['imputer'].statistics_.copy()   # predict 前抄一份中位數
pred = pipe.predict(X_test)                                    # 跑預測
np.array_equal(stat_before, pipe.named_steps['imputer'].statistics_)   # True——一個位元都沒變
實跑判決:predict 前後比對四組參數——statistics_mean_scale_coef_——np.array_equal 全部 True。X_test 的 73 個洞被填的值是訓練中位數;預測 200 筆的準確率 0.965。A 在第一關就出局:前處理器的口袋動都沒動。

還有第二個證據,藏在「洞被填成什麼」裡。X_test 第 3 欄自己的中位數是 -0.21,跟訓練學到的 -0.12 不一樣——洞到底被填成誰?

互動實驗室:中位數對照鏡訓練學到的 vs X_test 自己的——洞聽誰的
訓練學到的中位數(statistics_)X_test 自己的中位數(沒人用它)
主角洞的證詞:X_test 第 4 筆的特徵 3 是 NaN。transform 之後它變成 -0.12——訓練學到的中位數,而不是 X_test 自己的 -0.21。差距最大的在特徵 0:訓練 0.82 vs 測試 0.515——如果 predict 真的重新 fit,五個填補值全都會變。洞只聽訓練的。另外實跑一筆加碼:pipe.predict_proba(X_test[0:1]) 跟整批預測裡的第 0 筆完全相等——因為參數固定,一筆資料不管單獨送還是跟誰一起送,答案都一樣——這件事在 A 的世界會塌掉,下一節開演。

相關名詞:中位數 medianNaN 缺值單筆預測

04如果 A 是真的:不報錯的錯,加一個會報錯的

把 A 的世界真的寫出來——predict 時用 X_test 重新 fit 前處理器——三種送件方式、三種災難(全部實跑):

互動實驗室:單筆預測模擬器同一筆資料,兩個世界、兩種送法
挑一筆 X_test 的資料
送件方式
正確世界:pipe.predict(只用)
A 的世界:predict 時重新 fit
三種災難清單(實跑): 單筆、沒有洞:這筆自己就是「全班」——中位數=自己、平均=自己 → 標準化後五個特徵全變 0 → 模型只剩截距 → 每個人的機率都是 σ(-0.014) = 0.4965:該是 0.9901 的第 16 筆與該是 0.0005 的第 1 筆,拿到一模一樣的分數。 單筆、有洞:那一欄整欄是 NaN,中位數算不出來,SimpleImputer 直接把欄丟掉——5 個特徵剩 4 個,模型當場 ValueError: X has 4 features, but LogisticRegression is expecting 5 features 整批送:統計量換成 X_test 的 → 200 筆裡 2 筆翻案(第 8 筆從 0.4495 被推過線變 0.5101、判成 1)——同一筆資料,答案隨同批的人改變。①③ 不報錯、默默出鬼——「不報錯的錯」家族又添一員(第 9、10、12 頁的老朋友)。

相關名詞:「重新 fit」的世界sigmoid 與 0.4965部署與上線

05六宮格分工表:三站 × 兩個動詞

整題濃縮成一張 3×2 的表——直欄是三站、橫列是兩個動詞。點任一格看它做的事與實跑數字:

互動實驗室:六宮格分工表fit 那排都在學、predict 那排都在用
fit(訓練,X_train)
predict(上場,X_test)
imputer
補洞工
fit_transform
學中位數+補訓練的洞
transform
只補洞(用學過的)
scaler
縮放工
fit_transform
學平均/標準差+縮放
transform
只縮放(用學過的)
model
模型
fit
學係數與截距
predict
只出答案
看出規律了嗎:fit 那一欄,前兩站都是 fit_transform學完馬上把訓練資料處理掉,交給下一站)、最後一站是 fit;predict 那一欄,前兩站都是 transform、最後一站是 predict——「fit」這個字在 predict 那一欄一次都沒出現。這張表就是答案 D 的全文,也是 A 的死刑判決書。

相關名詞:fit_transformtransformnamed_steps

06為什麼要這樣設計:洩漏防線與部署

「參數只在訓練學一次」不是 sklearn 的怪癖,是兩個很實際的理由:

理由一:防資料洩漏。如果前處理偷看了測試資料的統計量,測試成績就摻了「未來的情報」——系列第 1 頁的主戲:純亂數資料配上「先選特徵再切折」的洩漏流程能假造出 0.929 的 AUC,換成 Pipeline 立刻現形 0.657。Pipeline 把「學」全部鎖在 fit 裡,配交叉驗證時每一折都只用該折的訓練部分重新學前處理——本頁資料實跑 cross_val_score(pipe, X, y, cv=5) = [0.9875, 0.9688, 0.9625, 0.975, 0.9875],平均 0.9763 ± 0.010——五折裡沒有一滴驗證資料流進參數。

理由二:上線要能接單筆。模型部署後,客人是一筆一筆來的——一筆資料沒有「中位數」也沒有「標準差」可學(04 節的災難①②就是下場)。正確設計下參數凍結在訓練期,joblib 存一顆 pipe,前處理參數跟模型永遠綁在一起,單筆進來照樣填洞、縮放、出答案——實跑驗證:單獨送與跟整批送,機率一個位元都不差。

串起系列:第 1 頁(Pipeline × 交叉驗證)講「為什麼要包成 Pipeline」、第 4 頁(GridSearchCV)講「包起來之後怎麼調參數」、第 7 頁(分群前的 StandardScaler)講「縮放為什麼重要」——本頁補上最底層的機制:fit 學、predict 用,參數的一生只學一次

相關名詞:資料洩漏cross_val_score × Pipeline訓練集與測試集的分工

07四個選項收工

Apredict(X_test) 會重新以 X_test 的中位數與平均數 fit 前處理器實跑當場抓包

predict 前後把 statistics_mean_scale_np.array_equal 比對——全 True,一個位元都沒變;X_test 的洞填的是訓練中位數 -0.12,不是它自己的 -0.21。而且要是 A 是真的,世界會壞得很具體:單筆預測全零特徵、人人同分 0.4965;含 NaN 的單筆直接 ValueError;同一筆資料的答案隨同批者改變(實跑 200 筆翻案 2 筆)。

BPipeline 只能串接模型,不能包含缺值填補或標準化本題程式就是反例

題目自己就串了 SimpleImputerStandardScaler 兩個前處理器,跑起來一點錯都沒有——pipe.named_steps 實跑印出 ['imputer', 'scaler', 'model']。Pipeline 的真正規則剛好相反:中間站都必須是「會 transform 的轉換器」、只有最後一站是估計器(模型)——它就是為了把前處理跟模型綁在一起而生的。

CLogisticRegression 會先在 X_test 上訓練,再預測 X_train集合與動詞全說反

兩個資料集、兩個動詞,四個字全反了:模型是在 fit(X_train, y_train)用訓練資料學係數(實跑 coef_ = [0.5316, -0.141, -0.4632, 3.1811, -2.7509]、12 輪收斂),predict(X_test)對測試資料出答案(accuracy 0.965)。X_test 連 y 都沒被提供,根本沒有東西可以「訓練」——這個選項是用來獎勵有讀懂第 6、7 行的人。

Dfit 時依序在訓練資料估計填補器、縮放器與模型參數;predict 時重用這些已學得參數處理 X_test正確

每個字都對得上實跑:「依序」= imputer → scaler → model 的接力(前兩站 fit_transform、最後 fit);「在訓練資料估計」= statistics_、mean_/scale_、coef_ 全部來自 X_train;「重用已學得參數」= predict 前後四組參數 np.array_equal 全 True、X_test 的 73 個洞全用訓練中位數填。fit 學、predict 用——一句話就是這一題

回到題目:現在再作答一次

再看一次同一段程式。這次你知道每一站在 fit 學什麼、在 predict 用什麼了。

閱讀下列 scikit-learn 程式,何者敘述正確?

pipe.fit(X_train, y_train)    # 學:中位數 [0.82 ...] → 平均/標準差 → 係數(12 輪收斂)
pred = pipe.predict(X_test)   # 用:填洞 -0.12(訓練的)→ 縮放(訓練的)→ 出答案 0.965

08自我檢測

八題,全部都是本題的延伸。答錯會直接告訴你錯在哪。

09重點整理

  1. 七行程式的分工:第 1–5 行組生產線(補洞工、縮放工、模型三站,用 [('名字', 工具), ...] 排隊)、第 6 行 fit(三站依序學)、第 7 行 predict(三站依序用)。
  2. 正確答案 D:fit 時依序在訓練資料估計填補器、縮放器與模型參數;predict 時重用這些已學得參數處理 X_test——fit 學、predict 用。
  3. fit 的接力賽:imputer fit_transform → scaler fit_transform → model fit——每站把學到的參數存口袋、把處理完的訓練資料交給下一站。
  4. 三站的口袋(實跑)statistics_ = [0.82, -0.06, -0.81, -0.12, -0.33](訓練中位數);mean_/scale_(訓練的平均與標準差);coef_/intercept_(max_iter=1000 只用 12 輪收斂)。
  5. predict 的三站transformtransformpredict——「fit」一次都沒出現;X_test 只被處理、從沒被學習。
  6. A 的死刑證據(實跑):predict 前後 statistics_mean_scale_coef_ 以 np.array_equal 比對全 True;X_test 的洞填訓練中位數 -0.12,不是它自己的 -0.21。
  7. A 的世界會怎樣(實跑):單筆無洞 → 標準化成全零特徵 → 人人同分 σ(-0.014)=0.4965;單筆有洞 → 整欄 NaN 被丟 → ValueError(4 特徵 vs 5);整批 → 200 筆翻案 2 筆——同一筆的答案隨同批者改變。
  8. B 的死因:本題程式就串了兩個前處理器。真正規則:中間站要會 transform、最後一站是估計器——named_steps 實跑 ['imputer', 'scaler', 'model']。
  9. C 的死因:集合與動詞全反——模型在 X_train 上學(coef_ 來自訓練)、對 X_test 出答案(accuracy 0.965);X_test 連 y 都沒有,沒東西可訓練。
  10. 為什麼要鎖住「學」:防洩漏——Pipeline 配 CV 每折只用該折訓練部分學前處理(實跑五折 0.9763±0.010);第 1 頁的洩漏對照(0.929 假 vs 0.657 真)就是不鎖的下場。
  11. 上線的理由:客人一筆一筆來——單筆沒有中位數與標準差可學;參數凍結在訓練期,joblib 存一顆 pipe,單筆照樣填洞、縮放、出答案(實跑:單獨送=跟整批送,一個位元不差)。
  12. 考場口訣fit 學、predict 用;訓練學一次,測試與上線永遠重用——訓練用 fit_transform、測試只 transform。
完整程式碼