🗺️ AI 學習與考證地圖
中級科目三程式實戰 · 模型解釋

重要性最高的特徵,是「原因」嗎?
feature_importances_ 的正確打開方式

五行程式印出前五名特徵和它們的分數。這一題不考計算,考的是你知不知道這串數字「是什麼」、更重要的是「不是什麼」。四個選項就是四種讀法——一種剛剛好,三種讀過頭。讀過頭的那三種,恰好是實務上最常見的三個誤會。

閱讀模式

00題目

訓練 Random Forest 後取得特徵重要性。feature_importances_ 的解讀,何者最正確?

from sklearn.ensemble import RandomForestClassifier                      # 拿出隨機森林分類器
rf = RandomForestClassifier(n_estimators=300, random_state=42)           # 300 棵樹、固定亂數種子
rf.fit(X_train, y_train)                                                 # 用訓練資料把森林種出來
importance = dict(zip(feature_names, rf.feature_importances_))           # 把「特徵名字」和「重要性分數」拉鍊成一本字典
print(sorted(importance.items(), key=lambda x: x[1], reverse=True)[:5])  # 依分數由大到小排序,印出前 5 名

先說「特徵重要性」是什麼

一支球隊贏球之後,教練團會整理一份功勞簿:這一季的關鍵時刻,球是交到誰手上的、誰的上場改變了局面。feature_importances_ 就是隨機森林的功勞簿——森林訓練完之後,回頭統計「這 300 棵樹在切分資料時,倚重了哪些特徵、各佔多少」,每個特徵一個分數,全部加起來等於 1。

但功勞簿有它天生的邊界。它記的是「這支球隊、這一季」的倚重程度:換一批對手(資料)、換一套戰術(模型)、甚至只是換個賽程順序(隨機種子),數字就會變。它也從來沒有說「得分王是贏球的原因」——也許他只是站在真正原因旁邊的人。

一句話定位:feature_importances_ 回答的是「這座訓練好的森林,用了誰、用得多兇」——它是模型內的相對分數。至於「誰造成了結果」(因果)、「誰跟結果同進退」(相關係數),是另外兩個不同的問題,要用不同的工具回答。這正是本題四個選項在測試的分界線。

先點開看:feature_importances_ 是什麼?分數是怎麼算的?相關與因果差在哪?

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

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

這串數字本身
feature_importances_MDI 不純度下降不純度 Gini分裂與切點為什麼總和是 1
會影響它的東西
隨機森林 RandomForestn_estimatorsrandom_state訓練集與測試集相關特徵攤分高基數偏誤
解讀的邊界
相關與因果混淆因子介入(do 操作)皮爾森相關係數模型解釋 XAI
更可信的工具
permutation importanceSHAP
資料
Wine 資料集X 與 y

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

點擊後出現漸進式說明。第 4、5 行是這題的 Python 重頭戲——一長串連環呼叫。

把工具拿進來
importfrom模組 module
存東西、叫東西
= 指派變數與命名. 點運算子方法 method( ) 呼叫括號關鍵字引數
第 4 行的連環
zip() 拉鍊dict() 造字典
第 5 行的連環
.items()sorted() 排序lambda 小函式[:5] 切片print()

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

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

先認識「連環呼叫」:一行裡好幾層括號

第 4、5 行都是一層包一層的寫法。訣竅只有一個:由最裡面往外讀,把每一層的成品想像出來,再交給外面那層。

第 4 行: dict( zip(名字們, 分數們) )
          └ 先拉鍊配對 ──→ 再裝訂成字典

第 5 行: sorted( importance.items(), key=..., reverse=True )[:5]
          └ 先攤平成一疊(名字,分數) → 依分數大到小排序 → 只拿前 5 張

想深入的話點這裡:zip 是什麼?sorted 怎麼用?lambda 是什麼?[:5] 是什麼?

第 1 行拿出隨機森林分類器
from sklearn.ensemble import RandomForestClassifier   # 從「集成方法」抽屜拿出隨機森林

from A import B =「去 A 那個抽屜,只把 B 拿出來」。sklearn.ensemble集成方法的抽屜——把很多個小模型合起來投票的那一類演算法。隨機森林=一大群故意長得不一樣的決策樹,各自判斷、最後多數決。

名字拆開讀:Random(每棵樹拿到的資料與特徵都刻意隨機打亂)+ Forest(樹很多)+ Classifier(做分類題)。

之所以是隨機森林出這題,是因為樹家族天生就有「重要性」可以回報——每棵樹的每次分岔都記錄了「用哪個特徵、讓資料變純多少」,把這些帳目加總,就是待會的 feature_importances_

相關名詞:fromimport隨機森林分裂與切點

第 2 行組一座 300 棵樹的森林,固定亂數種子
rf = RandomForestClassifier(n_estimators=300, random_state=42)   # 300 棵樹、亂數固定住

呼叫 RandomForestClassifier(...) 做出一台還沒訓練的森林,貼上名字 rf。兩個關鍵字引數n_estimators=300 是樹的棵數;random_state=42 把森林裡所有抽籤(抽資料、抽特徵)固定住,讓結果可重現。

random_state 跟本題的關係比你想的大。重要性分數是 300 棵「隨機長出來的樹」的平均帳目——換一個種子,分數就會小幅變動、名次甚至互換(本站實跑:seed 42 的第 2 名 flavanoids,在 seed 0 掉到第 3 名)。這是「A 選項說『相對重要性』」的第一個伏筆:這串數字連對自己的模型都只是個會浮動的估計。

跟上一頁的網格搜尋不同,這題沒有搜參數——直接指定一組超參數就開種。300 棵是常見的「夠穩」的量級。

相關名詞:n_estimatorsrandom_state= 指派

第 3 行訓練:功勞簿就是在這一步記下來的
rf.fit(X_train, y_train)   # 用訓練資料把 300 棵樹種出來

rf.fit(X_train, y_train)訓練集把 300 棵樹全部種出來。種樹的過程中,每棵樹的每次分岔都會記帳:「這一刀用了哪個特徵、讓資料變純了多少」——feature_importances_ 就是這本帳的總結,所以它天生只反映「訓練資料 × 這個模型」的視角。

題目裡的 X_trainy_trainfeature_names 沒有定義?對,考題常省略資料準備的前置(載入資料、切訓練/測試集)。本站實跑用 Wine 資料集(178 瓶酒 × 13 個化學特徵、三個品種)補上這段——完整版按右上角「看位置」或右下角「完整程式碼」。

尾巴帶底線的 feature_importances_ 是 sklearn 的慣例:fit 之後才存在的屬性。還沒 fit 就去拿,會噴 NotFittedError。

相關名詞:訓練集與測試集方法. 點運算子Wine 資料集

第 4 行把名字和分數拉鍊起來,裝訂成字典
importance = dict(zip(feature_names, rf.feature_importances_))   # 名字配分數,一對一裝進字典

由內往外讀,兩層:

 zip(feature_names, rf.feature_importances_)
   像拉鍊:左排是 13 個名字、右排是 13 個分數,拉起來變成
   ("alcohol", 0.1435), ("malic_acid", 0.0344), ... 一對一對

 dict(①)
   把這疊配對裝訂成字典:{"alcohol": 0.1435, "malic_acid": 0.0344, ...}
   之後就能用名字查分數:importance["alcohol"] → 0.1435

rf.feature_importances_ 本身只是一排沒有名字的數字(順序跟資料的欄位順序一樣)。這一行做的事就是幫每個分數掛上名牌——人才讀得懂。

拉鍊有個小脾氣:兩排要一樣長。如果 feature_names 有 13 個、分數也有 13 個,就完美配對;萬一名字只有 12 個,zip 會默默拉到短的那排結束就停,不報錯——這是實務上很陰的 bug 來源。

相關名詞:zip() 拉鍊dict() 造字典feature_importances_

第 5 行依分數排序,印出前五名
print(sorted(importance.items(), key=lambda x: x[1], reverse=True)[:5])   # 排序取前 5 印出來

整題最長的一行,還是由內往外拆四步:

 importance.items()          把字典攤回一疊 (名字, 分數) 的配對
 sorted(①, key=lambda x: x[1], reverse=True)
      key=lambda x: x[1]        「排序時,看每張配對的第 [1] 格(分數)」
      reverse=True              由大到小(預設是由小到大)
 ②[:5]                      切片:只留前 5 張
 print(③)                   印出來
本站實跑(Wine + sklearn 1.8)這一行印出:
[('color_intensity', np.float64(0.1589...)), ('flavanoids', np.float64(0.1538...)), ('proline', np.float64(0.1525...)), ('alcohol', np.float64(0.1435...)), ('hue', np.float64(0.0962...))]
前五名:色澤強度 0.159、類黃酮 0.154、脯胺酸 0.152、酒精 0.143、色調 0.096。那個看起來很吵的 np.float64(...) 是 NumPy 2 的顯示格式——數值本身就是普通的小數。

lambda x: x[1]現寫現用的一句話小函式:「給我一個 x,我回你 x 的第 1 格」。因為 Python 從 0 開始數,第 [0] 格是名字、第 [1] 格是分數——所以這句的意思就是「按分數排」。

相關名詞:.items()sorted()lambda[:5] 切片print()

02這串分數是怎麼算出來的:MDI

feature_importances_ 的正式身分是 MDI(Mean Decrease in Impurity,不純度下降平均),也叫 Gini importance。原理跟著種樹的過程走:

每棵樹的每一次分岔,都在做同一件事:
  挑一個特徵、挑一個切點,把資料切成「更純」的兩堆
  (純 = 堆裡幾乎都是同一類;用 Gini 不純度來量)

某特徵的重要性 = 它主刀的每一次分岔「讓不純度下降多少」
                (乘上該節點分到的樣本比例)通通加起來,
                再對 300 棵樹取平均,最後全體正規化成總和 1

\[ \text{MDI}(f) \;=\; \frac{1}{T}\sum_{t=1}^{T}\;\sum_{\substack{\text{樹 } t \text{ 中}\\ \text{用 } f \text{ 分岔的節點}}} \frac{n_{node}}{n} \,\Delta\text{impurity} \]

三個馬上能用的性質: 每個分數都 ≥ 0(不純度只會被「下降」記帳,沒有負的貢獻); 全部加起來恰好等於 1(正規化過,所以它天生是「比例」); 它是訓練過程的帳目——只看訓練資料,跟測試集表現無關。

本站實跑(Wine 178 瓶 × 13 特徵,train_test_split 0.3 分層、RF 300 棵):rf.feature_importances_ 的 shape 是 (13,)、每個值都 ≥ 0、總和 = 1.0000000000。前五名:color_intensity 0.1589、flavanoids 0.1538、proline 0.1525、alcohol 0.1435、hue 0.0962;最低的是 ash 0.0121。這座森林在測試集 54 瓶全對(accuracy 1.0——Wine 對隨機森林來說太簡單,這件事待會有戲份)。
互動實驗室:重要性排序條換個種子、丟個亂數欄位進去看看
玩兩件事:①「只換種子」——同資料同設定,分數照樣浮動(flavanoids 從第 2 掉到第 3、color_intensity 從 0.159 漲到 0.186)。②「加亂數欄位」——連純亂數都能分到 0.0057,因為 300 棵樹裡總有幾刀碰巧用它切出一點點「假下降」;而且連續亂數(切點多)拿到的分數是 0/1 亂數的 5 倍——這就是高基數偏誤的縮影。

03選項 A:它是「這座森林」的相對分數——為什麼這樣讀剛剛好

A 說了兩件事,兩件都對:「此已擬合模型中的相對重要性」「不能單憑此數值推論因果」。前半句的三個「相對」值得拆開:

相對於什麼意思本站實跑的證據
相對於彼此總和正規化成 1,單一數值只有跟其他特徵比才有意義——0.1589 是「份額」,不是絕對強度13 個分數總和 1.0000000000
相對於這個模型它是 300 棵「隨機長出來的樹」的帳目平均,連換個種子都會動seed 42→0:color_intensity 0.159→0.186、第 2/3 名互換
相對於這份資料換一份資料(或多塞幾個欄位),份額就重新分配加兩個亂數欄位後,13 個真特徵的分數全體被稀釋
「重要」也不等於「不可或缺」(實跑加碼):把第一名 color_intensity 整欄拿掉重新訓練,測試集 accuracy 只從 1.000 掉到 0.944——因為跟它相關的特徵馬上補位(拿掉後 alcohol 的重要性從 0.144 跳到 0.207)。功勞簿排第一,不代表少了他球隊就崩盤。

至於後半句「不能推論因果」——這正是 B 選項踩爆的地雷,下一節用一個你一定聽過的例子實跑給你看。

相關名詞:為什麼總和是 1random_state相關特徵攤分

04選項 B:重要性最高 ≠ 原因,更不會是「唯一原因」

經典故事:統計上,冰淇淋銷量溺水人數高度同進退。冰淇淋是溺水的原因嗎?當然不是——背後有一個沒被寫進表格的混淆因子:夏天。天氣熱,冰淇淋賣得多、玩水的人也多。

我們把這個世界「造」出來實跑一次——因為是模擬,我們知道真相:溺水率 y 完全由「夏天炎熱程度 Z」決定,冰淇淋銷量也只是 Z 的結果,而 Z 沒有被收進特徵表(現實中混淆因子常常就是沒被記錄的那個)。

互動實驗室:冰淇淋因果模擬先旁觀,再介入,看分數騙不騙人
模擬世界的真相(模型看不到這張圖):

        夏天炎熱 Z ────(沒被收進特徵表!)
        ↙          ↘
冰淇淋銷量 X        溺水率 y        X 和 y 之間【沒有】因果箭頭
冰淇淋的重要性
0.609
四個特徵裡的壓倒性第一
模型測試 accuracy
0.849
預測得還不錯
真實世界的 y
不變
y 只由 Z 生成
模型的功勞簿說:冰淇淋重要性 0.609,遙遙領先。它「重要」,因為它是 Z 的影子——拿它來預測完全合理。按右邊的「介入」看看把它當原因去操作會發生什麼事。
介入實跑結果:把測試集的冰淇淋欄整欄打亂(模擬「下令改變冰淇淋銷量」的 do 操作)——真實的 y 一個都沒變(它只由 Z 生成),但模型的預測翻掉了 28.2%、accuracy 從 0.849 掉到 0.682。
翻譯成人話:重要性高,代表「拿它來猜很好用」;不代表「動它能改變結果」。禁售冰淇淋救不了任何一個溺水的人。B 選項的「必然是唯一原因」錯了三層:不必然、不見得是原因、更沒有「唯一」——因果要靠實驗設計(隨機對照試驗)或因果推論方法回答,不是靠這串數字。

相關名詞:混淆因子相關與因果介入(do 操作)

05選項 C:0 分不是永久判決

C 說「數值為 0 的特徵,在任何資料集與任何模型中都永遠無效」——「任何」和「永遠」這種宇宙級的量詞,就是它的死因。重要性 0 只說明一件事:這座森林(在這份訓練資料上)沒有從它身上記到任何帳。換個情境,同一個特徵可以立刻滿血復活。兩個實跑證據:

證據一:常數欄位——這批資料剛好沒變化而已

本站實跑:把訓練集裡的 flavanoids(類黃酮)整欄改成同一個值(想像這批酒剛好來自同一產區、檢測值全同),重新訓練——它的重要性變成恰好 0.000000,模型照樣 100% 準(其他特徵補位)。
但同一個特徵在原本的資料裡:重要性 0.1538(第 2 名)、跟品種的相關係數高達 |r| = 0.847(全場最高)「這批資料裡它是 0」跟「它永遠無效」是兩句完全不同的話。

證據二:分身攤分——分數被本尊拿走了

更常見的情境:兩個高度相關的特徵會互相攤分數。樹分岔時兩個都能用、隨機挑到誰誰記帳——所以其中一個看起來「不重要」,可能只是功勞被分身拿走。

互動實驗室:分身實驗複製最強特徵,看分數怎麼被攤開
分身跟本尊的相關程度:
random_state:
把兩個證據合起來讀 C 選項:0 分(或接近 0 分)的成因至少有三種——這批資料裡它沒變化、功勞被相關特徵拿走、或這個模型剛好用不上它(線性模型愛用的特徵,樹不一定愛)。重要性是「這座森林 × 這份資料」的區域性結論,不是跨資料集、跨模型的永久判決。

相關名詞:相關特徵攤分feature_importances_

06選項 D:它也不是「相關係數」

D 把 feature_importances_ 說成「每個特徵與目標之間的機率相關係數」。先說個小事實:「機率相關係數」根本不是統計學的正式名詞——選項自己發明術語,通常就是陷阱的味道。就算好心把它讀成「皮爾森相關係數」,也完全對不上,三個硬差異:

feature_importances_(MDI)相關係數 r
值域與正負0 到 1,沒有正負方向−1 到 +1,正負代表同向/反向
定義的對象模型內的分裂貢獻帳目(一整座森林的性質)兩個變數之間的線性同動程度(跟模型無關)
總和被正規化成加總 = 1(互相排擠的份額)各算各的,沒有總和限制
本站實跑對照(Wine,13 個特徵各算 MDI 與 r):兩張排行榜長得不一樣——
MDI 冠軍 color_intensity(0.1589)的 r 只有 +0.266,連 |r| 前四名都排不進;|r| 冠軍 flavanoids(−0.847,注意是負的)MDI 排第 2;|r| 亞軍 od280/od315(−0.788)的 MDI 只有 0.0911、排第 6。
一個沒有方向、總和為 1 的「模型帳目」,跟一個有正負、逐對計算的「統計量」,根本是兩種語言。(附註:這裡的 r 是把品種編號 0/1/2 當數字算的示意——多類別的類別變數連「算 r」這件事本身都很勉強,又是一層 D 站不住的理由。)

相關名詞:皮爾森相關係數MDI

07那想要更可信的重要性,該看什麼?

MDI 快、免費(訓練完就有),但有兩個已知偏誤:偏愛切點多的高基數特徵只看訓練資料。sklearn 官方文件自己就提醒:對高基數特徵,MDI 可能誤導。常用的補強是 permutation importance

permutation importance 的做法(在測試集上):
  1. 先量模型的正常分數
  2. 把「某一欄」整欄打亂(其他欄不動)→ 再量一次分數
  3. 分數掉多少,就是這一欄的重要性(重複 30 次取平均)

  問的問題從「訓練時誰被用到」變成「現在弄壞誰,模型會真的變差」
互動實驗室:MDI vs Permutation同一個模型,兩本帳對照
亂數欄位在兩本帳上的待遇:MDI 給了 random_uniform 0.0057(被 178 個不同取值的切點騙到幾刀);permutation 給 0.0000(打亂它,分數一分都不掉)——壞不了模型的東西,就不重要。也注意 od280:MDI 0.1094、permutation 卻是 0.0062——不是誰錯了,是兩本帳問的問題不同(od280 在訓練時常被用到,但測試時就算打亂它,相關特徵也能補位)。
實務工作流(考題也愛考這個層次): MDI 當免費快篩,看個大概; 要寫進報告的重要性,用 permutation importance(在測試集或驗證集上)複核,相關特徵記得先分群; 要解釋「單筆預測」,用 SHAP 要宣稱因果——請做實驗(A/B 測試、隨機對照)或用因果推論方法,任何重要性分數都到不了那裡

四個選項一句話收工

A此模型中的相對重要性,且不能單憑它推論因果正確

「相對」=總和為 1 的份額、隨模型/資料/種子而動(實跑:seed 一換名次就換);「不能推因果」=冰淇淋重要性 0.609,但介入它對真實 y 影響為零。兩個限定詞都下得剛剛好。

B最高分=目標的唯一原因因果跳躍

錯三層:預測有用不必然是原因(可能只是混淆因子的影子);就算真有因果也不會只有一個原因;而「原因」需要實驗或因果推論才能宣稱。實跑的冰淇淋就是活教材。

C0 分=在任何資料、任何模型永遠無效過度概括

「任何」「永遠」是宇宙級量詞。實跑:flavanoids 弄成常數後重要性恰好 0,但它在原資料是第 2 名、|r| 全場最高;分身實驗裡分數還會被相關特徵拿走。0 分只是這座森林的區域性帳目。

D等同特徵與目標的「機率相關係數」張冠李戴

「機率相關係數」不是正式名詞;就算當成皮爾森 r 也對不上——MDI 無正負、總和為 1、是模型內帳目;r 有正負、逐對計算、與模型無關。實跑兩張排行榜根本長不一樣。

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

再看一次同一段程式。這次你知道那串數字的身分了:一本「這座森林的功勞簿」。

訓練 Random Forest 後取得特徵重要性。feature_importances_ 的解讀,何者最正確?

from sklearn.ensemble import RandomForestClassifier                      # 拿出隨機森林
rf = RandomForestClassifier(n_estimators=300, random_state=42)           # 300 棵樹、固定種子
rf.fit(X_train, y_train)                                                 # 訓練=記功勞簿
importance = dict(zip(feature_names, rf.feature_importances_))           # 名字配分數
print(sorted(importance.items(), key=lambda x: x[1], reverse=True)[:5])  # 印前 5 名

08自我檢測

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

09重點整理

  1. 第 4 行由內往外讀:zip 把「名字們」和「分數們」拉鍊成一對一對,dict 再裝訂成「用名字查分數」的字典。zip 遇到兩排不一樣長會默默拉到短的那排就停,不報錯。
  2. 第 5 行四連發:.items() 攤平成 (名字, 分數) 配對 → sorted(..., key=lambda x: x[1], reverse=True) 依第 [1] 格(分數)由大到小排 → [:5] 切前五 → print
  3. feature_importances_ 的正式身分是 MDI(不純度下降平均):統計每個特徵主刀的分岔讓 Gini 不純度下降多少,對 300 棵樹取平均後正規化。每個值 ≥ 0、總和恰為 1、只看訓練資料(實跑總和 1.0000000000)。
  4. 它尾巴的底線=fit 之後才存在的屬性;分類與迴歸的樹家族(DecisionTree、RandomForest、GradientBoosting)都有這個屬性。
  5. 它是相對分數:換 random_state 數值就浮動、名次會互換(實跑 seed 42→0:color_intensity 0.159→0.186、第 2/3 名互換);多塞欄位份額就重分。
  6. 重要 ≠ 不可或缺:拿掉第一名 color_intensity 重訓,accuracy 只從 1.000 掉到 0.944,相關特徵馬上補位(alcohol 0.144→0.207)。
  7. 重要 ≠ 原因(A 對、B 錯的核心):混淆因子模擬——冰淇淋重要性 0.609,但把它整欄打亂,真實 y 一個都沒變(y 只由沒被收錄的「夏天」生成)。預測上有用,操作上無效。因果要靠實驗設計或因果推論。
  8. 0 分不是永久判決(C 錯):flavanoids 弄成常數後 MDI 恰好 0,但它在原資料是第 2 名、|r|=0.847 全場最高;高度相關的分身還會互相攤分數(完全複製 → 0.159 攤成 0.138+0.136)。
  9. 它不是相關係數(D 錯):MDI 無正負、總和 1、是模型內帳目;r 有正負、逐對計算、與模型無關。實跑:MDI 冠軍的 r 只有 +0.266,|r| 冠軍是負相關的 flavanoids(−0.847)。「機率相關係數」更不是正式名詞。
  10. MDI 的兩個已知偏誤:偏愛高基數特徵(連純亂數都拿到 0.0057,連續亂數是 0/1 亂數的 5 倍)、只看訓練集。sklearn 官方文件明示對高基數特徵可能誤導。
  11. 要更可信就用 permutation importance(測試集上打亂單欄看分數掉多少):亂數欄位在它這本帳上拿到乾淨的 0.0000。要解釋單筆預測用 SHAP。
  12. 正確答案 A「此已擬合模型中的相對重要性」+「不能單憑此數值推論因果」——兩個限定詞,正好是這串數字的能與不能。
完整程式碼