這一題不用跑程式,也不用懂紅酒。你只需要抓對一件事:GridSearchCV 把 param_grid 攤開成幾種組合、每種組合又各被訓練幾次——以及 refit=False 那一行,有沒有偷偷多送你一次 fit。乘法抓對,答案就出來了;乘錯一步,就會掉進另外三個選項。
Wine 分類任務以 RandomForestClassifier 進行網格搜尋,程式如下;本題不計搜尋完成後另行重訓,因此設定 refit=False。交叉驗證期間總共會執行多少次模型 fit?
from sklearn.datasets import load_wine # 從 sklearn 的資料集抽屜,拿出「載入紅酒資料」的工具 from sklearn.ensemble import RandomForestClassifier # 拿出隨機森林分類器 from sklearn.model_selection import GridSearchCV # 拿出網格搜尋工具(本題主角) X_wine, y_wine = load_wine(return_X_y=True) # 載入資料:X 是特徵、y 是答案 param_grid = { # 開一本「候選值菜單」(字典) "n_estimators": [100, 200, 400], # 樹的數量:3 個候選值 "max_depth": [None, 5, 10, 20], # 樹的深度上限:4 個候選值 "max_features": ["sqrt", "log2"] # 分岔時看幾個特徵:2 個候選值 } # 菜單寫完,大括號收起來 search = GridSearchCV( # 組一台搜尋機,取名 search RandomForestClassifier(random_state=42), # 要被反覆訓練的模型 param_grid=param_grid, # 把菜單交給它 cv=5, # 每種組合都做 5 折交叉驗證 refit=False # 搜完不自動重訓最後一次 ) # 設定收尾——還沒開始跑 search.fit(X_wine, y_wine) # 按下開關:fit 從這裡開始算
你幫全公司訂手搖飲,想找出最好喝的一杯:茶底有幾種、甜度有幾種、冰塊有幾種。最老實的辦法就是把每一種搭配都實際做一杯出來喝喝看,一種都不跳過——這就是網格搜尋(Grid Search)。「網格」的意思是:把每個選項的候選值排成一格一格的表,然後逐格全部試過。
機器學習裡,「茶底、甜度、冰塊」對應的是模型的超參數——像隨機森林要種幾棵樹(n_estimators)、每棵樹最多長幾層(max_depth)。這些數字模型自己學不出來,只能由你先決定;而「哪一組最好」很難用猜的,所以 sklearn 提供 GridSearchCV:你開出菜單(param_grid),它幫你逐一「試喝」。
點擊後出現漸進式說明:白話說明 → 說清楚一點 → 常見錯誤與考點。
點擊後出現漸進式說明。
十六行,一行一行走完。右上角的「看位置」可以把這一行放回完整程式裡看。
這題的程式一半都在跟括號打交道,先把三種括號的分工弄清楚,後面會順很多。
{ "n_estimators": [100, 200, 400] } 大括號 { } = 字典:「名字 → 內容」的對照表
[100, 200, 400] 中括號 [ ] = 串列:一排照順序放的東西
GridSearchCV(...) 小括號 ( ) = 呼叫:叫這個工具開始做事
from sklearn.datasets import load_wine # 只拿 load_wine 這一支工具出來
from A import B 的意思是「去 A 那個抽屜裡,只把 B 拿出來」。sklearn.datasets 是 scikit-learn 附的內建資料集抽屜,裡面放著練習用的小資料:紅酒(load_wine)、鳶尾花(load_iris)、乳癌(load_breast_cancer)、手寫數字(load_digits)⋯⋯
為什麼教學都愛用內建資料集?因為不用下載、不用清理、一行就能開始。它們都很小(本題的紅酒只有 178 筆),跑起來快,適合拿來練「流程」而不是練「資料工程」。
注意這一行只是把工具拿出來放桌上,還沒有真的載入資料——真正動手是第 4 行呼叫它的時候。
from sklearn.ensemble import RandomForestClassifier # 拿出「隨機森林」這個模型
sklearn.ensemble 是「集成方法」的抽屜——把很多個小模型合起來投票的那一類演算法都住在這裡。隨機森林就是最有名的一個:種一大群決策樹,每棵樹各自判斷,最後多數決。
名字直接拆開讀就懂:Random(隨機)=每棵樹拿到的資料和特徵都刻意隨機打亂,讓樹長得不一樣;Forest(森林)=樹很多;Classifier(分類器)=做的是分類題。字尾如果是 Regressor,就是做預測數值的迴歸題。
之所以要介紹它,是因為待會 param_grid 裡的三個參數,全部都是這座森林的「造林規格」:種幾棵樹、每棵長多高、分岔時看幾個特徵。
from sklearn.model_selection import GridSearchCV # 拿出網格搜尋工具
sklearn.model_selection 是「模型挑選」的抽屜:切訓練測試集(train_test_split)、交叉驗證(cross_val_score、StratifiedKFold)、搜參數(GridSearchCV、RandomizedSearchCV)都在這裡。
把 GridSearchCV 這個名字拆成三段,本題的解法其實已經寫在裡面:
Grid 網格 → 把 param_grid 攤開成一格一格的候選組合 Search 搜尋 → 每一格都老實試過,一格都不跳 CV 交叉驗證 → 每一格都用 k 折來評分(本題 k = 5)
也就是說,看到 GridSearchCV 這個名字,就要反射性想到「格子數 × 折數」這個乘法——這正是本題唯一要考的東西。
X_wine, y_wine = load_wine(return_X_y=True) # 178 瓶酒的特徵與答案,分兩包接住
這一行真的去載入資料了。return_X_y=True 的意思是「直接回傳(X, y)兩包,不要包成一個大物件」;等號左邊用逗號寫了兩個名字,Python 就會照順序一次接住兩包——第一包給 X_wine、第二包給 y_wine。
X 是題目、y 是答案:X_wine 是一張 178 列 × 13 欄的大表格,每一列是一瓶酒、每一欄是一個化學檢測值;y_wine 是 178 個答案(0、1、2),代表這瓶酒來自三個品種中的哪一個。
X_wine.shape = (178, 13)、三個類別各有 59 / 71 / 48 瓶。13 個特徵是酒精濃度、蘋果酸、灰分、鎂、總酚、類黃酮、色澤強度、脯胺酸⋯⋯等化學檢測值。13 這個數字待會有戲份——第 8 行的 "sqrt" 和 "log2" 要用它來換算。param_grid = { # 大括號=字典:開一本菜單
"n_estimators": [100, 200, 400], # 森林要種幾棵樹:3 個候選
"max_depth": [None, 5, 10, 20], # 每棵樹最多長幾層:4 個候選
"max_features": ["sqrt", "log2"] # 分岔時抽幾個特徵來比:2 個候選
} # 菜單寫完這五行做出一個字典,取名 param_grid。字典的每一筆都是「參數名(字串)→ 候選值(串列)」:
第 6 行 "n_estimators":森林要種幾棵樹。候選 [100, 200, 400],3 個值。
第 7 行 "max_depth":每棵樹最多長幾層。候選 [None, 5, 10, 20],4 個值——None 也算一個正式的候選值,意思是「不設上限,讓樹自然長到底」。
第 8 行 "max_features":每次分岔時,隨機抽幾個特徵出來比較。候選 ["sqrt", "log2"],2 個值,分別代表「取特徵數的平方根」與「取特徵數的以 2 為底對數」。
"n_estimator" 少個 s)不會默默被忽略,fit 時會直接噴 ValueError: Invalid parameter——這其實是好事,錯誤越早爆越好。None 是 Python 的正式值,不是「沒有填」。它在候選名單裡佔一個名額,算組合數時要數進去——很多人第 7 行只數到 3 個,就是把 None 漏掉了。讀法示範:param_grid["max_depth"] 唸作「param_grid 這本菜單裡,max_depth 那一頁」,翻開會看到 [None, 5, 10, 20] 四個候選。
search = GridSearchCV( # 開始組搜尋機(括號先不關) RandomForestClassifier(random_state=42), # 第一個材料:要被反覆訓練的模型
第 10 行開始呼叫 GridSearchCV(...) 並把成品存進變數 search。括號裡的第一個材料(第 11 行)是要被反覆訓練的模型本體——一台設定好 random_state=42 的隨機森林。
注意這台森林「沒有」寫 n_estimators、max_depth、max_features。這不是漏寫——這三個位子就是故意空下來的,待會搜尋時,GridSearchCV 會把菜單裡的候選值一組一組填進去。而且它每次都會先 clone 出一台全新的、乾淨的模型再填再訓練,不會沿用上一輪的訓練結果。
random_state=42 是「固定隨機種子」:隨機森林裡有很多抽籤動作(抽資料、抽特徵),固定種子後每次重跑抽出來的都一樣,結果才能重現。42 沒有魔力,只是個大家愛用的哏。
第 10 行行尾的括號沒有關起來——Python 規定括號沒關完就可以一直換行寫下去,所以第 10 到 15 行其實是「同一句話」,只是排版排得比較好讀。
param_grid=param_grid, # 左邊是參數名、右邊是我們第 5 行做的字典
這一行長得有點繞口:param_grid=param_grid。其實是兩個不同的東西剛好同名——等號左邊是 GridSearchCV 的關鍵字引數名稱(它規定這個欄位就叫 param_grid);等號右邊是我們自己在第 5 行取名的那本字典。
像寄包裹:包裹單上有一格印好的欄位叫「品名」,你在格子裡填上自己包裹的名字。欄位名和包裹名剛好一樣,只是巧合(也是慣例——大家都把那本字典取名叫 param_grid)。把第 5 行的字典改名叫 menu,這一行寫成 param_grid=menu 也完全合法。
cv=5, # 每種組合都做 5 折交叉驗證
cv=5 指定每一種參數組合都要做 5 折交叉驗證:把 178 瓶酒切成 5 份,輪流留 1 份當考題、其餘 4 份拿去訓練——每一輪都是一次全新的 fit,然後在留下的那份上打分數。5 輪的平均分,就是這個組合的成績。
對分類問題傳整數給 cv,sklearn 會自動改用分層版 StratifiedKFold:每一折裡三個品種的比例都跟整體幾乎一樣,不會出現「某一折剛好都是同一種酒」的不公平考題。
refit=False # 搜完不自動用最佳參數重訓一次 ) # 括號關起來,search 組裝完成
refit 的預設值是 True:搜尋結束後,GridSearchCV 會自動拿「成績最好的那組參數」,用全部資料再訓練最後一次,把成品掛在 best_estimator_ 上讓你直接拿去預測。那一次不屬於交叉驗證,是搜尋結束後的加碼。
本題寫成 False,就是「搜完就停,只留成績單」。題目那句「本題不計搜尋完成後另行重訓」講的正是這件事——出題者把 refit 關掉,是為了讓「總 fit 次數」只剩交叉驗證這一段,答案乾乾淨淨。
第 15 行的 ) 把第 10 行開的括號關起來——這句長達六行的「組裝指令」到此結束。到目前為止還是一次 fit 都沒有發生,我們只是把機器組好了。
search.fit(X_wine, y_wine) # 本題問的「fit 次數」就是從這裡開始數
前面 15 行全部只是「設定」。這一行才是按下開關:GridSearchCV 接過全部資料,開始它的固定流程——把每一種參數組合,在每一折上各訓練一次、評分一次,全部做完才把成績整理進 cv_results_。
有趣的是這裡呼叫的 .fit() 跟一般模型的 .fit() 長得一模一樣——sklearn 刻意讓「搜尋器」用起來像「一個模型」。差別在於:一般模型的 fit 訓練一次,search 的 fit 裡面裝著一大串 fit。
verbose=1 打開重跑一次,GridSearchCV 自己印出了這一行——Fitting 5 folds for each of 24 candidates, totalling 120 fitsGridSearchCV 拿到菜單後做的第一件事,是把它攤開成所有可能的搭配:從每個參數的候選裡各挑一個值,湊成一組完整的設定,這叫一個「候選組合(candidate)」。數學上這是三個串列的笛卡兒積,講白話就是連乘:
n_estimators 有 3 種選法(100 / 200 / 400) max_depth 有 4 種選法(None / 5 / 10 / 20) max_features 有 2 種選法(sqrt / log2) 組合數 = 3 × 4 × 2 = 24 種 為什麼是「乘」不是「加」?——因為每一種樹數量,都要配上每一種深度, 再配上每一種特徵抽法。就像 3 種茶底 × 4 種甜度 × 2 種冰塊 = 24 種飲料。
len(search.cv_results_["params"]) 會告訴你候選組合數——本站實跑回傳 24。成績單 cv_results_ 就是一張 24 列的表,一列一個組合。注意 24 還不是答案——它只是第一個乘法,每個組合接下來還要各考 5 次試。24 種組合排好隊之後,GridSearchCV 對每一種都執行同一套儀式——5 折交叉驗證:
一個組合的 5 折之旅(以 178 瓶酒、cv=5 為例): 第 1 折: 用第 2~5 份共 142 瓶 fit 一次 → 拿第 1 份 36 瓶考試打分 第 2 折: 用其餘 142 瓶 fit 一次 → 拿第 2 份 36 瓶考試打分 第 3 折: 用其餘 142 瓶 fit 一次 → 拿第 3 份 36 瓶考試打分 第 4 折: 用其餘 143 瓶 fit 一次 → 拿第 4 份 35 瓶考試打分 第 5 折: 用其餘 143 瓶 fit 一次 → 拿第 5 份 35 瓶考試打分 → 一個組合吃掉 5 次 fit;5 個分數平均,就是它的總成績
重點有三個。第一,每一折都是從零開始的全新訓練——sklearn 每次都先 clone 一台乾淨的模型,絕不沿用上一折學到的東西,所以 5 折就是紮紮實實的 5 次 fit。第二,每次 fit 用的是 142~143 瓶(五分之四),不是全部 178 瓶。第三,24 種組合各自獨立地做完這套,於是:
totalling 120 fits 一致。單執行緒跑了 47.5 秒;順帶一提,這 120 次 fit 裡森林總共種出了 28,000 棵決策樹(每個 n_estimators 值各出現在 8 個組合 × 5 折裡:5 × 8 × (100+200+400))。.fit() 被呼叫幾次,不是森林裡有幾棵樹。交叉驗證做完,GridSearchCV 手上只有一張成績單——它知道哪一組參數平均分最高,但還沒有任何一台「用最佳參數訓練好、可以直接拿去用」的模型。因為剛剛那 120 台模型,每一台都只看過五分之四的資料,而且考完就丟了。
這就是 refit 存在的理由:
| refit=True(預設) | refit=False(本題) | |
|---|---|---|
| 搜尋結束後 | 自動用最佳參數+全部 178 瓶再訓練一次 | 就地解散,只留成績單 |
| 總 fit 次數 | 120 + 1 = 121 | 120 |
| 多的那一次算交叉驗證嗎 | 不算——它用全部資料、也不打分數 | — |
沒有那最後一次重訓,就沒有「訓練好的最佳模型」。本站把每個屬性實際摸過一遍:
| 搜尋完之後想拿⋯⋯ | refit=False(本題) | refit=True |
|---|---|---|
| cv_results_ 完整成績單 | 拿得到實跑確認 | 拿得到 |
| best_params_ 最佳參數 | 拿得到(單一 scoring 時)實跑確認 | 拿得到 |
| best_score_ 最佳平均分 | 拿得到(單一 scoring 時)實跑確認 | 拿得到 |
| best_estimator_ 訓練好的最佳模型 | AttributeError實跑確認 | 拿得到 |
| search.predict() / search.score() | AttributeError實跑確認 | 可以直接用 |
實跑的錯誤訊息長這樣(值得看一眼,考題很愛考): search.predict(X_wine) → AttributeError: This 'GridSearchCV' has no attribute 'predict' search.score(X_wine, y_wine) → AttributeError: This GridSearchCV instance was initialized with `refit=False`. score is available only after refitting ...
best_params_ 和 best_score_ 仍然拿得到(因為它們只是「讀成績單」,不需要訓練好的模型)——但這只限 scoring 只有一個指標的情況。如果 scoring 傳了多個指標(例如同時看 accuracy 和 f1),sklearn 不知道該用哪個指標定義「最佳」,此時 refit 必須明確指定指標名稱,否則連 best_params_ 都沒有。把前面兩節收成一條公式,以後看到同型題直接套:
\[ \text{總 fit 次數} \;=\; \underbrace{\Big(\prod_{i} n_i\Big)}_{\text{各參數候選數連乘}} \times \; k \;+\; \underbrace{\big[\,\text{refit=True 則加 } 1\,\big]}_{\text{用全部資料重訓}} \]
其中 \(n_i\) 是第 \(i\) 個參數的候選值個數、\(k\) 是交叉驗證折數。本題:\(3 \times 4 \times 2 \times 5 + 0 = 120\)。
| 做法 | fit 次數 | 特性 |
|---|---|---|
| GridSearchCV | 組合數 × k(+refit) | 地毯式全搜,保證試過菜單上每一格;網格一大就爆炸 |
| RandomizedSearchCV | n_iter × k(+refit)——跟網格大小無關 | 從菜單裡隨機抽 n_iter 組來試;預算固定、可搜連續分布,大網格的首選 |
| HalvingGridSearchCV | 介於中間(逐輪淘汰) | 先用少量資料讓全部組合初賽,贏家才能用更多資料複賽;sklearn 的逐次減半搜尋 |
| n_jobs=-1 | 次數完全不變 | 只是「同時開好幾個爐子」——平行化改變的是等待時間,不是 fit 次數 |
RandomizedSearchCV(..., n_iter=10, cv=5, refit=False),verbose 印出 Fitting 5 folds for each of 10 candidates, totalling 50 fits,計數器同樣停在 50——它只抽了 24 種組合裡的 10 種。另外,本題的網格開 n_jobs=-1 全核心平行後,還是 120 次 fit,但時間從 47.5 秒縮到 27.2 秒。算完次數,本站把這場搜尋真的跑完,結果成績單藏了一個超展開——先看兩個換算:
Wine 有 13 個特徵:
max_features="sqrt" → 取 √13 ≈ 3.61 → 無條件捨去 → 每次分岔抽 3 個特徵
max_features="log2" → 取 log₂13 ≈ 3.70 → 無條件捨去 → 每次分岔抽 3 個特徵
↑
兩個候選值,算出來是同一個數字
也就是說,在 13 個特徵的資料上,"sqrt" 和 "log2" 是同一件事的兩種寫法。加上 random_state=42 固定了抽籤順序,每一對「只差 max_features」的組合訓練出來的森林一模一樣——本站實跑驗證:兩邊在驗證折上的預測一瓶都不差。24 個組合,實際上只有 12 種不同的模型。
這個彩蛋有三個實戰教訓:
{'max_depth': None, 'max_features': 'sqrt', 'n_estimators': 100})。看到 best_params_ 別急著寫報告說「最佳深度是 None」——先翻 rank_test_score 看看有幾個並列。四個數字,各自對應一條「乘法走歪」的路。把歪路認出來,下次看到變形題也不會踩。
24 是候選組合數(3 × 4 × 2),也是成績單 cv_results_ 的列數——但它不是 fit 次數。每一個組合都還要在 5 折上各訓練一次,選 A 等於以為「每種組合只試了一口」。verbose 的原話已經把兩個數字分開講了:24 candidates、120 fits。
60 = 3 × 4 × 5——把 max_features 那個「× 2」弄丟了。最常見的失手方式有兩種:只顧著看數字候選、忽略 ["sqrt", "log2"] 這種字串候選也要算;或是把第 7 行的 None 當成「沒有值」漏數(那樣會得到 3 × 3 × 2 × 5 = 90,連選項都對不上)。param_grid 有幾個鍵,就要乘幾個數字,一個都不能少。
兩個乘法都做齊:組合數 3 × 4 × 2 = 24,再乘折數 24 × 5 = 120;refit=False,所以沒有搜尋後的加碼重訓,就停在 120。本站實跑雙重驗證:verbose 印出 totalling 120 fits,計數器攔截到的 fit 也是 120 次,每次用 142~143 瓶訓練。
240 = 120 × 2,通常來自兩種誤會。①「每折要訓練一次+驗證一次,所以乘 2」——不對,驗證用的是同一台剛訓練好的模型拿去 predict/score,評分不會再 fit 一次。②「refit 會把整套再跑一遍」——也不對,refit=True 只是在最後多 1 次(用全部資料訓練最佳組合),是 121,不是 240。
看完之後再看一次同一段程式,三個數字應該會自己跳出來。
Wine 分類任務以 RandomForestClassifier 進行網格搜尋,程式如下;本題不計搜尋完成後另行重訓,因此設定 refit=False。交叉驗證期間總共會執行多少次模型 fit?
param_grid = { # 候選值菜單
"n_estimators": [100, 200, 400], # 3 個候選
"max_depth": [None, 5, 10, 20], # 4 個候選(None 也算一個)
"max_features": ["sqrt", "log2"] # 2 個候選
} # 菜單寫完
search = GridSearchCV( # 組搜尋機
RandomForestClassifier(random_state=42), # 被反覆訓練的模型
param_grid=param_grid, # 交出菜單
cv=5, # 每個組合都做 5 折
refit=False # 搜完不重訓
) # 組裝完成
search.fit(X_wine, y_wine) # 開始跑八題,全部都是本題的變形。答錯會直接告訴你錯在哪。
{ } 是字典(名字 → 內容的菜單)、中括號 [ ] 是串列(一排候選值)、小括號 ( ) 是呼叫。param_grid 就是「字典裡包著串列」。search.fit() 才按下開關。None 是正式候選值要數進去;字串候選("sqrt"、"log2")也要數。best_estimator_、不能 search.predict()(實跑:AttributeError);但單一 scoring 時 best_params_、best_score_、cv_results_ 都還在。多指標 scoring 時 refit 必須指名指標,否則連 best_params_ 都沒有。n_jobs=-1 平行化不改變次數,只縮短時間(47.5 秒 → 27.2 秒)。.fit() 的呼叫次數。"sqrt" 和 "log2" 都換算成 3,24 個組合其實只有 12 種模型;GridSearchCV 不會去重,120 次照跑。設計網格前先確認候選值真的會改變模型。Fitting 5 folds for each of 24 candidates, totalling 120 fits。超參數搜尋是中級科目三「機器學習技術與應用」的常客,而「算總 fit 次數」是其中最像數學題、也最好拿分的題型。最常見的五種問法:① 給 param_grid 和 cv 問總 fit 次數(本題這種,答案=候選數連乘 × 折數,再看 refit 要不要 +1);② 問 refit=False 之後哪個屬性拿不到/哪個操作會報錯(best_estimator_ 與 predict);③ 換成 RandomizedSearchCV 問次數(n_iter × 折數,跟網格大小無關);④ 問 cv=5 對分類問題預設用哪種切法(StratifiedKFold 分層);⑤ 問 n_jobs 或 verbose 的作用(平行化不改次數;verbose 會把 candidates 和 fits 印出來)。把「組合數相乘、折數再乘上去、refit 才加一」這三步背熟,這一型幾乎都能秒殺。
因為交叉驗證是對每一個組合分別做一輪完整的 k 折,不是全部組合共用一輪。想像 24 位考生每人都要考 5 科:總考科數是 24 × 5 = 120,不是 24 + 5 = 29。程式的角度看更清楚:GridSearchCV 內部是一個雙層迴圈——外層走過每個候選組合、內層走過每一折,每進一次內層就 clone 一台新模型 fit 一次。所以只要記得「雙層迴圈」,乘法就不會寫成加法。
因為它的目的和用料都不一樣。交叉驗證的 120 次 fit 是「評分用」:每次只用五分之四的資料訓練,剩下五分之一拿來打分數,考完就丟。refit 的那一次是「生產用」:搜尋結束後,用成績最好的參數+全部 178 筆資料訓練一台正式模型,掛在 best_estimator_ 上讓你之後predict 用——它不打分數、不留成績,自然不屬於交叉驗證。本題題幹特別寫「不計搜尋完成後另行重訓,因此設定 refit=False」,就是在把這一次明確排除,讓答案乾淨地停在 120。
用 k 折的「k 分之 k−1」。178 瓶酒切 5 折,各折考題 36/36/36/35/35 瓶,所以每次 fit 的訓練資料是 142 或 143 瓶(178 減掉當折考題數)。只有 refit=True 的那第 121 次會用滿 178 瓶。考題偶爾會反過來問「每次訓練用幾筆」,記住口訣:交叉驗證的每一次 fit 都看不到那一折的考題。另外分類問題傳整數給 cv 預設走 StratifiedKFold(不洗牌的分層切法),每折的類別比例會貼近整體的 59:71:48。
會更嚴格:連 best_params_ 和 best_score_ 都拿不到。單一指標時「最佳」的定義毫無歧義,所以 refit=False 仍能從成績單直接讀出最佳列;多指標時(例如 scoring=["accuracy","f1_macro"])sklearn 不知道你要以哪個指標為準,規定 refit 必須寫成指標名稱(如 refit="f1_macro")才會定義 best_params_ 並用該指標重訓。這是考題愛出的進階陷阱:「refit=False 時 best_params_ 拿得到嗎?」——單一指標拿得到,多指標拿不到。
次數= n_iter × 折數(refit 同樣另計)。它每輪從 param_distributions 隨機抽一組參數,抽 n_iter 輪為止,所以次數由你的預算決定,跟網格大小完全無關——本站實跑 n_iter=10、cv=5 就是 50 次 fit。它不是次級品:當參數多、範圍大時,隨機抽樣反而更有機會踩到好區域(而且它能搜「連續分布」,例如 learning_rate 從 0.001 到 0.3 之間任何值,這是網格做不到的)。實務上常見的組合拳是:RandomizedSearch 大範圍圈地 → 看結果 → 開一個小 GridSearch 精修。
本站實跑印出 Fitting 5 folds for each of 24 candidates, totalling 120 fits,三個數字剛好對應公式的三個角色:5 是 cv 折數、24 是候選組合數(各參數候選數的連乘)、120 是兩者相乘的總 fit 次數。考題如果給你這行輸出反推 param_grid,就用 24 去因數分解。注意這行不包含 refit 的那一次——refit=True 時它照樣印 totalling 120 fits,第 121 次是印完之後才默默做的。
一次 RandomForest 的 fit 會種出 n_estimators 棵樹,所以「樹的總數」=把每次 fit 的 n_estimators 加總。本題每個 n_estimators 候選值(100、200、400)各出現在 4 × 2 = 8 個組合裡,每個組合 fit 5 次,所以總樹數 = 5 × 8 × (100 + 200 + 400) = 28,000 棵。但題目問的是「執行多少次模型 fit」,數的是 .fit() 呼叫了幾次(120),不是樹的棵數——兩個數字都可能出現在考題裡,看清楚題目要哪一個。