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。
閱讀下列 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。
fit = 學——每站把學到的參數存進自己口袋(statistics_、mean_、coef_)。③ predict = 用——前面每站只 transform、最後模型只出答案,全程沒有任何一站重新學。點擊後出現漸進式說明:白話說明 → 說清楚一點 → 常見錯誤與考點。
點擊後出現漸進式說明。
七行,一行一行走完。右上角的「看位置」可以把這一行放回完整程式裡看。
pipe = Pipeline([ # 造一條生產線,名字叫 pipe;[ 開始列站
Pipeline 收一個清單,清單裡每一項是一個 ('名字', 工具) 的元組——照寫的順序就是資料流過的順序。這行的 [ 只是打開清單,生產線還在組裝中;此刻什麼都還沒學,只是把三站的空白工具擺上輸送帶。
('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'] 把它叫出來問話。
('scaler', StandardScaler()), # 站名 scaler:每欄減平均、除以標準差
StandardScaler 是縮放工:fit 時學每欄的平均(mean_)與標準差(scale_),transform 時做 (x − mean_) ÷ scale_,讓每個特徵都變成「平均 0、標準差 1」的尺度——系列第 7 頁(分群前的 StandardScaler)整頁都在講它為什麼重要。注意順序:它排在補洞工之後,所以它學統計量時看到的是「洞已補好的訓練資料」——NaN 是算不了平均的。
('model', LogisticRegression(max_iter=1000)) # 站名 model:邏輯迴歸,最多迭代 1000 輪
生產線的最後一站是模型:LogisticRegression 學一組係數 coef_ 與截距 intercept_,把特徵加權後過 sigmoid 變成機率。max_iter=1000 是給優化器的迭代上限——不是「訓練 1000 次」;本站實跑第 12 輪就收斂,1000 只是保險絲(系列第 1 頁就見過它:沒標準化時邏輯迴歸會收斂不了、噴 ConvergenceWarning)。
]) # ] 關清單、) 關 Pipeline——三站就位,等指令] 收掉清單、) 收掉 Pipeline(。到這裡 pipe 只是一條組好但空白的生產線:三站的口袋裡都還沒有參數——statistics_、mean_、coef_ 通通不存在,現在就叫它 predict 只會得到 NotFittedError。建立物件不等於學習——學習要等下一行。
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 節把每一棒學到的實跑參數攤開。
pred = pipe.predict(X_test) # 用訓練學到的中位數補洞 → 用訓練的平均/標準差縮放 → 模型出答案
下半場只有一個動詞:用。imputer 對 X_test 只做 transform——洞用訓練學到的中位數填;scaler 只做 transform——用訓練學到的平均與標準差縮放;model 只做 predict——用訓練學到的係數出答案。沒有任何一站重新 fit——這正是選項 A 與 D 的分水嶺,03 節用 np.array_equal 當場對質。
pipe.fit(X_train, y_train) 這一行執行完,三站的口袋分別多了什麼?本站實跑(800 筆 × 5 特徵、310 個 NaN,75/25 切分):
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。選項 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——一個位元都沒變
statistics_、mean_、scale_、coef_——np.array_equal 全部 True。X_test 的 73 個洞被填的值是訓練中位數;預測 200 筆的準確率 0.965。A 在第一關就出局:前處理器的口袋動都沒動。還有第二個證據,藏在「洞被填成什麼」裡。X_test 第 3 欄自己的中位數是 -0.21,跟訓練學到的 -0.12 不一樣——洞到底被填成誰?
pipe.predict_proba(X_test[0:1]) 跟整批預測裡的第 0 筆完全相等——因為參數固定,一筆資料不管單獨送還是跟誰一起送,答案都一樣——這件事在 A 的世界會塌掉,下一節開演。把 A 的世界真的寫出來——predict 時用 X_test 重新 fit 前處理器——三種送件方式、三種災難(全部實跑):
整題濃縮成一張 3×2 的表——直欄是三站、橫列是兩個動詞。點任一格看它做的事與實跑數字:
fit_transform(學完馬上把訓練資料處理掉,交給下一站)、最後一站是 fit;predict 那一欄,前兩站都是 transform、最後一站是 predict——「fit」這個字在 predict 那一欄一次都沒出現。這張表就是答案 D 的全文,也是 A 的死刑判決書。「參數只在訓練學一次」不是 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,前處理參數跟模型永遠綁在一起,單筆進來照樣填洞、縮放、出答案——實跑驗證:單獨送與跟整批送,機率一個位元都不差。
predict 前後把 statistics_、mean_、scale_ 用 np.array_equal 比對——全 True,一個位元都沒變;X_test 的洞填的是訓練中位數 -0.12,不是它自己的 -0.21。而且要是 A 是真的,世界會壞得很具體:單筆預測全零特徵、人人同分 0.4965;含 NaN 的單筆直接 ValueError;同一筆資料的答案隨同批者改變(實跑 200 筆翻案 2 筆)。
題目自己就串了 SimpleImputer 與 StandardScaler 兩個前處理器,跑起來一點錯都沒有——pipe.named_steps 實跑印出 ['imputer', 'scaler', 'model']。Pipeline 的真正規則剛好相反:中間站都必須是「會 transform 的轉換器」、只有最後一站是估計器(模型)——它就是為了把前處理跟模型綁在一起而生的。
兩個資料集、兩個動詞,四個字全反了:模型是在 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 行的人。
每個字都對得上實跑:「依序」= 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
八題,全部都是本題的延伸。答錯會直接告訴你錯在哪。
[('名字', 工具), ...] 排隊)、第 6 行 fit(三站依序學)、第 7 行 predict(三站依序用)。fit_transform → scaler fit_transform → model fit——每站把學到的參數存口袋、把處理完的訓練資料交給下一站。statistics_ = [0.82, -0.06, -0.81, -0.12, -0.33](訓練中位數);mean_/scale_(訓練的平均與標準差);coef_/intercept_(max_iter=1000 只用 12 輪收斂)。transform → transform → predict——「fit」一次都沒出現;X_test 只被處理、從沒被學習。statistics_/mean_/scale_/coef_ 以 np.array_equal 比對全 True;X_test 的洞填訓練中位數 -0.12,不是它自己的 -0.21。「Pipeline 與 fit/predict 分工」是中級科目三機器學習技術與應用的高頻考點,常跟資料洩漏、交叉驗證合併出題。最常見的六種問法:① 給一段 Pipeline 程式問 fit/predict 的行為(本題這種——認出「學」與「用」);② 問 predict 會不會重新 fit 前處理器(不會——參數凍結,實跑 array_equal 全 True);③ 問測試資料的缺值/縮放用誰的統計量(訓練的——洞填 -0.12 不是 -0.21);④ 問 Pipeline 步驟的規則(中間要會 transform、最後一站是估計器——不是「只能串模型」);⑤ 問為什麼 Pipeline 要配交叉驗證(每折重新學前處理、防洩漏);⑥ 問 fit_transform 與 transform 誰用在哪(訓練 fit_transform、測試只 transform)。一句口訣:fit 學、predict 用;訓練學一次,上線永遠重用。
fit(X_train, y_train) 是接力賽:imputer 在 X_train 上 fit_transform(學五欄中位數、補掉訓練的 237 個洞)→ scaler 在補好的資料上 fit_transform(學 mean_ 與 scale_、標準化)→ model 在處理完的資料配 y_train 上 fit(學 coef_ 與 intercept_,實跑 12 輪收斂)。predict(X_test) 是同一條路線的「只用版」:imputer 只 transform(洞用訓練中位數填)→ scaler 只 transform(用訓練的平均與標準差)→ model 只 predict(用學好的係數出答案)。兩條路線的差別就一個字:fit 那趟每站都在「學」,predict 那趟每站都只「用」。
三個理由。①公平:測試是在模擬「上線後遇到沒看過的資料」——上線時你只有訓練期學到的參數,測試若偷用自己的統計量,成績就摻了未來情報(資料洩漏)。②一致:模型學係數時看到的世界是「用訓練統計量處理過的特徵空間」,測試資料必須用同一套處理才活在同一個空間——換了統計量,同一筆資料的座標就飄了(實跑:整批重算讓 200 筆翻案 2 筆)。③可行:上線常常一筆一筆來,單筆根本沒有「中位數」可算——只有重用訓練參數,單筆預測才可能成立。本站實跑主角洞:X_test[4] 的特徵 3 是 NaN,填的是訓練學到的 -0.12,即使 X_test 自己該欄中位數是 -0.21。
本站把「A 的世界」實跑了三種送件方式。單筆、無缺值:這筆自己就是全班——中位數=自己、平均=自己、標準差 0(sklearn 補成 1)→ 標準化後五個特徵全變 0 → 模型只剩截距 → 每筆的機率都是 σ(-0.014) = 0.4965:該 0.9901 的與該 0.0005 的拿同一個分數,模型等於報廢,而且不報錯。單筆、有缺值:那欄整欄 NaN、中位數不存在,SimpleImputer 把欄丟掉 → 5 特徵剩 4 → ValueError: X has 4 features, but LogisticRegression is expecting 5 features。整批 200 筆:統計量換成 X_test 的 → 2 筆預測翻案(第 8 筆 0.4495 → 0.5101 過線變 1)——同一筆資料的答案隨同批者改變,這在正確世界絕不發生(實跑:單獨送=整批送)。
剛好相反——Pipeline 就是為了把前處理跟模型綁在一起而生的。規則:中間的每一站都要是「會 fit 與 transform 的轉換器」(填補、縮放、編碼、降維、特徵選擇都行),最後一站是估計器(分類、迴歸、分群都可以,甚至也可以是轉換器)。本題的 ['imputer', 'scaler', 'model'] 就是標準範例。順序自己排:習慣上先補洞再縮放(NaN 算不了平均),需要 PCA 就塞在縮放之後、模型之前——第 1 頁的 Pipeline 就是 scaler → pca → lr 三站。
fit_transform(X) = fit(X) 加 transform(X) 一次做完:先學參數、再用剛學的參數處理同一份資料——只該用在訓練資料上。transform(X):不學,直接用口袋裡既有的參數處理——測試資料與上線資料永遠只用它。手寫前處理時的標準樣板:X_train_s = scaler.fit_transform(X_train)、X_test_s = scaler.transform(X_test)——寫成兩個 fit_transform 就是自己動手製造了選項 A 的世界。Pipeline 的好處是這個選擇自動幫你做對:fit 走 fit_transform、predict 走 transform,想寫錯都難。
把 pipe 整顆丟給 cross_val_score(pipe, X, y, cv=5),每一折都會在該折的訓練部分重新 fit 整條生產線(中位數、平均、係數全部重學),再對該折驗證部分只 transform + predict——本頁資料實跑五折 [0.9875, 0.9688, 0.9625, 0.975, 0.9875]、平均 0.9763±0.010,沒有一滴驗證資料流進參數。GridSearchCV 同理,還能用 model__C、imputer__strategy 這種「站名__參數」語法連前處理的參數一起搜(第 4 頁的主戲)。反面教材在第 1 頁:先在全資料上選特徵再切折,純亂數也能假造 AUC 0.929——Pipeline 進 CV 立刻打回 0.657 原形。
一顆檔案帶著全套參數走:joblib.dump(pipe, 'model.joblib') 存的不只是模型係數,連 imputer 的中位數、scaler 的平均與標準差全部打包——上線端 load 回來直接 pipe.predict(新資料),不會發生「模型帶走了、前處理參數忘在筆電裡」的經典事故。單筆可預測:參數凍結,所以一筆進來照樣填洞、縮放、出答案(實跑:單獨送與整批送的機率一個位元不差)。訓練/上線一致性:同一條生產線、同一套參數,訓練期怎麼處理、上線就怎麼處理——業界說的 training-serving skew,Pipeline 是最便宜的解法。