加權條件互信息:特征選擇中的冗余消除與工程實踐)
簡介《動態(tài)加權條件互信息的特征選擇算法.docx》是一篇系統(tǒng)介紹面向高維數(shù)據(jù)特征選擇場景的過濾式新算法WMRI的技術文檔適合數(shù)據(jù)挖掘、機器學習領域的研究者與工程師參考。壓縮包內(nèi)共一個docx文檔大小約四百五十四KB內(nèi)容覆蓋算法研究背景、信息論框架、權重計算、偽代碼及實驗對比等模塊。文檔首先梳理基于互信息的過濾式特征選擇發(fā)展脈絡指出現(xiàn)有算法在冗余特征處理與分類信息權重預設上的不足隨后詳細說明WMRI如何利用條件互信息衡量特征與標簽的相關性及特征間冗余并通過均值和標準差動態(tài)調(diào)整保留類別信息與新分類信息的權重以避免人工設置參數(shù)帶來的偏差。最后在十個基準數(shù)據(jù)集上與DCSF、MRI、CFR、IG-RFE、JMIM等算法進行對比給出實驗分析與結論能幫助讀者理解算法機理并復現(xiàn)實驗。目前已有八十人學習/下載適合作為特征選擇、冗余特征消除及信息論方法調(diào)優(yōu)的參考筆記。1. 動態(tài)加權條件互信息一個被多數(shù)項目低估的冗余消除器做特征篩選時單變量互信息排序表是最常被拉出來的工具但也是翻車率最高的工具。某風控項目里我曾見過高基數(shù)特征把 MI 排序表刷到第一模型上線后離線指標反而比原始特征集更差。動態(tài)加權條件互信息DWCMI正是沖著這個缺口去的它在條件互信息的基礎上引入一個隨已選特征集合變化的權重把“候選特征對標簽的增量信息”和“候選特征與已選特征的冗余度”合并成一個可排序的得分。這篇文章把它的原理、可復現(xiàn)代碼、核心參數(shù)和五個高頻避坑點一次講透適合正在做特征工程或準備把特征選擇算法落到建模流程里的從業(yè)者。2. 從互信息到條件互信息為什么排序表連續(xù)翻車2.1 互信息、條件互信息與動態(tài)加權的位置關系互信息 I(X;Y) 衡量兩個隨機變量之間的相關性它的最大優(yōu)勢是不依賴線性假設能捕獲非線性依賴。但特征選擇場景里它有兩個天然缺陷第一它對取值數(shù)多的特征有系統(tǒng)性偏向第二它完全忽略特征之間的冗余。兩個強特征高度相關單變量 MI 會把它們都排到前面而模型真正需要的信息量只有一個特征那么多。條件互信息 I(X;Y|Z) 解決的是第二個問題在已知 Z 的前提下X 還能給 Y 帶來多少新信息。計算公式是 I(X;Y|Z) I((X,Z);Y) ? I(Z;Y)也就是“把 X 和 Z 合起來看能獲得多少信息減去 Z 單獨提供的信息”。動態(tài)加權則是給這個條件互信息加一個隨迭代輪次變化的系數(shù)讓算法在篩選前期大膽選高區(qū)分度特征后期對與已選集合高度冗余的候選特征自動降權。整體脈絡是互信息解決“相關不強”問題條件互信息解決“冗余”問題動態(tài)加權解決“迭代后期權重失穩(wěn)”問題。2.2 十行代碼復現(xiàn)條件互信息離散分箱是更穩(wěn)的估計器工程上算條件互信息有兩個流派一派用連續(xù) KNN 估計器一派把連續(xù)變量分箱后走離散統(tǒng)計。我一般走離散分箱路線原因是可復現(xiàn)性好、不依賴外部包、超參數(shù)直觀。核心做法是把連續(xù)特征先做秩變換再等頻分箱然后借助 sklearn 的 mutual_info_score 算離散互信息。下面這個函數(shù)可以直接抄進項目里import numpy as np import pandas as pd from sklearn.metrics import mutual_info_score def bin_series(x, n_bins10): # 先做秩變換消除長尾和離群值對等頻分箱的干擾 x_rank pd.Series(x).rank(methodaverage) # qcut 按分位數(shù)切分duplicatesdrop 避免重復邊界導致空箱 bins pd.qcut(x_rank, qn_bins, duplicatesdrop, labelsFalse) return bins.values def conditional_mutual_information(X, Z, y, n_bins10): X_bin bin_series(X, n_bins) Z_bin bin_series(Z, n_bins) # 把 X 和 Z 的聯(lián)合狀態(tài)拼成一個離散標簽等價于 I((X,Z);Y) joint_bin X_bin.astype(str) _ Z_bin.astype(str) mi_joint mutual_info_score(y, joint_bin) mi_z mutual_info_score(y, Z_bin) # 鏈式法則I(X;Y|Z) I((X,Z);Y) - I(Z;Y) return mi_joint - mi_z這段代碼里最關鍵的是 joint_bin 的構造。它把每個樣本的 X 箱號和 Z 箱號組合成一個字符串標簽比如“3_7”這樣 mutual_info_score 就能把二元聯(lián)合分布當作一個離散變量來計算。秩變換放在分箱之前是為了防止某個特征被極端值拉成一根長尾導致 qcut 產(chǎn)生大量空箱或單樣本箱。參數(shù) n_bins 在樣本量一萬左右時取 10 到 15樣本量越大可以適當增大。需要注意的是如果 Z 本身也是待選特征而不是已選特征這段代碼不能直接套用因為條件互信息要求 Z 是固定已知的集合。2.3 別直接用 sklearn 的 mutual_info_classif 排序三個隱蔽偏差很多人會把 sklearn 的 mutual_info_classif 當作萬能工具直接把整個特征矩陣丟進去拿返回的每個特征的 MI 值排序。這個用法有三個隱蔽偏差。第一個是估計口徑問題mutual_info_classif 內(nèi)部對每個特征單獨算 MI它返回的是逐特征評分不是聯(lián)合互信息的分解。有人把兩列高相關特征同時丟進去把兩個分數(shù)相加以為得到了聯(lián)合信息量實際上相加結果遠大于真正的聯(lián)合 MI冗余被重復計算。第二個是 n_neighbors 參數(shù)的敏感性連續(xù)特征下它依賴 kNN 估計默認 n_neighbors3 時樣本量小或噪聲大的數(shù)據(jù)評分波動很明顯同一條數(shù)據(jù)跑兩次排序可能就變了。第三個是高基數(shù)特征偏差類別或連續(xù)特征離散化后取值數(shù)越多互信息估計越傾向于給出高分這在數(shù)學上是偏差而非方差問題靠增加樣本量解決不了。因此我通常把 mutual_info_classif 只當作初篩工具粗選一個候選池再交給條件互信息和動態(tài)加權去精排。如果你確實想用連續(xù)估計器至少要把 n_neighbors 調(diào)到 5 以上并在任何正式實驗里固定 random_state否則結果不可復現(xiàn)。3. 動態(tài)權重設計三種權重來源與一個可直接落地的 Python 類3.1 動態(tài)權重的來源類別平衡、區(qū)分度先驗與冗余懲罰動態(tài)加權條件互信息里的“動態(tài)”兩個字主要體現(xiàn)在權重不是固定常數(shù)而是每選入一個特征后重新計算的。常見做法是把它拆成三部分。第一是類別平衡權重面向不平衡樣本如果正樣本只占 5%MI 估計會被多數(shù)類主導需要按類別給樣本加權或者對少數(shù)類做重采樣后再計算。第二是區(qū)分度先驗權重面向本身就沒區(qū)分度的特征即使它和已選特征完全不冗余一個與標簽幾乎無關的特征也不該得高分可以用單變量 MI 或 Cramérs V 做先驗經(jīng)過 sigmoid 映射后作為基礎權重。第三是冗余懲罰權重這是動態(tài)核心每輪計算候選特征與已選特征集合的最大相關性相關性越高權重越小。三者乘在一起構成最終權重作用于條件互信息得分。這樣設計的好處是讓算法在迭代前半段偏向“相關性強”的特征后半段偏向“互補性強”的特征而不是只盯著單變量排序表。3.2 DWCMI 最小實現(xiàn)分箱、CMI 與權重的完整代碼下面這個類是我在模擬項目X里反復調(diào)過的版本代碼不長但把分箱、條件互信息、冗余懲罰、迭代衰減都包進去了。你可以直接復制后按自己數(shù)據(jù)跑一遍import numpy as np import pandas as pd from sklearn.metrics import mutual_info_score class DWCMI: def __init__(self, n_bins10, lambda_red2.0, alpha_decay0.9, beta_mix0.3): self.n_bins n_bins self.lambda_red lambda_red # 冗余懲罰系數(shù)越大對冗余越苛刻 self.alpha_decay alpha_decay # 迭代衰減系數(shù)控制動態(tài)權重的衰減速度 self.beta_mix beta_mix # 單變量 MI 的混合比例防止 CMI 數(shù)值退化 self.selected [] # 已選特征的值用于計算冗余度 def _bin(self, x): x_rank pd.Series(x).rank(methodaverage) return pd.qcut(x_rank, qself.n_bins, duplicatesdrop, labelsFalse).values def _cmi(self, x_cand, y): cand_bin self._bin(x_cand) joint_bin cand_bin.astype(str) for sel in self.selected: joint_bin joint_bin _ self._bin(sel).astype(str) mi_joint mutual_info_score(y, joint_bin) if not self.selected: return mi_joint sel_bin self._bin(self.selected[0]) mi_sel mutual_info_score(y, sel_bin) return mi_joint - mi_sel def score(self, x_cand, y): cmi_val self._cmi(x_cand, y) mi_val mutual_info_score(y, self._bin(x_cand)) if not self.selected: w 1.0 else: # 冗余懲罰與已選特征最大絕對相關系數(shù)的權重衰減 rho_max max(abs(np.corrcoef(x_cand, sel)[0, 1]) for sel in self.selected) w np.exp(-self.lambda_red * rho_max) * (self.alpha_decay ** len(self.selected)) # 最終得分動態(tài)加權 CMI 為主單變量 MI 為輔 return w * cmi_val self.beta_mix * (1 - w) * mi_val邏輯說明分三層。_cmi 方法里 joint_bin 的構造是核心每輪迭代時把候選特征箱號和所有已選特征箱號拼接成聯(lián)合標簽算出聯(lián)合互信息后減去第一個已選特征的單獨互信息這是鏈式法則的工程化近似。當已選集合大于一個特征時嚴格來說應該使用 I(Z;Y) 的完整聯(lián)合值但實際應用里用“減第一個已選特征”的方式收斂更快且不易數(shù)值崩潰代價是冗余懲罰會被高估一點點。score 方法里的權重公式做了兩件事一是通過 exp(?lambda_red * rho_max) 讓與已選特征高度相關的候選特征立刻降權二是通過 alpha_decay 的迭代次冪讓后期特征整體權重遞減越晚進入的特征門檻越高。beta_mix 的作用是給純 CMI 情況兜底當條件互信息因為樣本稀疏而降到接近 0 時單變量 MI 還能保留一段基礎的區(qū)分度信號。跑起來時建議先用 2000 行數(shù)據(jù)調(diào)試確認分數(shù)沒有 NaN 再上全量。3.3 必調(diào)參數(shù)分箱數(shù)、冗余懲罰系數(shù)與混合系數(shù)DWCMI 的調(diào)參主要集中在三個參數(shù)上。分箱數(shù) n_bins 直接決定概率估計的顆粒度太小會丟失非線性細節(jié)太大則把聯(lián)合狀態(tài)表打稀。我一般用 min(20, max(5, int(sqrt(n_samples/10)))) 來定初值樣本量一萬時算出來是 10五萬時是 15。冗余懲罰系數(shù) lambda_red 控制算法對“候選特征與已選特征相關”的敏感度默認 2.0 時相關系數(shù) 0.5 的候選權重衰減到約 0.37如果你希望算法更激進地去冗余可以調(diào)到 3.0但小心把強特征也壓掉?;旌舷禂?shù) beta_mix 是最后要調(diào)的那個默認 0.3 意味著最終得分里最多有 30% 來自單變量 MI其余靠條件互信息主導。數(shù)據(jù)噪聲大時調(diào)到 0.5讓單變量 MI 托底數(shù)據(jù)質(zhì)量高時調(diào)到 0.2讓條件互信息說了算。這三組參數(shù)調(diào)完排序結果通常會比直接跑 MI 排序穩(wěn)定很多。參數(shù)默認值建議范圍調(diào)參影響n_bins10520過小丟非線性過大聯(lián)合分布稀疏lambda_red2.01.03.5越大越激進去冗余過大壓制強特征alpha_decay0.90.70.95越大后期特征越容易被選中beta_mix0.30.20.5越大越依賴單變量 MI條件信息比重下降4. 把 DWCMI 接進特征選擇主流程前向選擇、閾值與選型4.1 預處理與去重連續(xù)變量分箱與高相關列剔除DWCMI 對輸入數(shù)據(jù)的干凈程度要求很高預處理做不好后面所有排序都是玄學。第一步是剔除方差接近 0 的常量列和缺失率超過 30% 的列這些列在互信息估計里只會貢獻噪聲。第二步是處理高相關列先算特征兩兩的斯皮爾曼相關系數(shù)矩陣把相關系數(shù)絕對值大于 0.95 的特征合并成一組每組只保留與目標變量單變量 MI 最高的那個其余先踢出候選池。這一步不做的話DWCMI 的冗余懲罰會幫你降權但計算量白白增加一倍。第三步才是分箱每個特征獨立做秩變換再按 3.3 節(jié)的公式確定分箱數(shù)。注意分箱要在訓練集上擬合邊界驗證集和測試集直接應用同一套邊界否則會引入數(shù)據(jù)泄漏。我一般會把分箱邊界保存成一個 json 文件上線推理時直接加載。4.2 前向選擇主循環(huán)候選池、終止條件與結果輸出有了 DWCMI 類主循環(huán)就只是一個前向搜索每輪遍歷候選池用 score() 給每個候選打分取最高分特征加入已選集合然后進入下一輪。終止條件我會用兩個信號一是到達預設的 max_k二是當前最高分相對上一輪的增益低于閾值。如果連續(xù)三輪增益都小于 1%說明繼續(xù)選下去邊際收益已經(jīng)很低。下面是一個可以直接跑的主循環(huán)示例def select_features(X, y, max_k30, min_gain0.01): selector DWCMI(n_bins10, lambda_red2.0, alpha_decay0.9, beta_mix0.3) remaining {name: X[name].values for name in X.columns} selected_names [] selected_scores [] for t in range(max_k): if not remaining: break scored {name: selector.score(col, y) for name, col in remaining.items()} best_name max(scored, keyscored.get) best_score scored[best_name] if t 0 and best_score selected_scores[-1] * (1 min_gain): print(f停止于第 {t1} 輪增益不足) break selected_names.append(best_name) selected_scores.append(best_score) selector.selected.append(remaining[best_name]) del remaining[best_name] return selected_names, selected_scores # 示例調(diào)用X 是 DataFramey 是 Series # selected_names, scores select_features(X_train, y_train, max_k15)主循環(huán)里有幾個細節(jié)值得注意。min_gain 是針對“絕對得分”的比較由于得分本身會隨已選集合變大而自然下降min_gain 取 0.01 通常就夠了設太大可能第一輪之后就停設太小則容易選到噪聲特征。我遇到得分曲線先降后升的情況通常是分箱數(shù)偏大導致偶發(fā)高分這時優(yōu)先調(diào) n_bins 而不是調(diào)閾值。另外這道循環(huán)每輪都要對剩余所有特征算一次 score復雜度大約是 O(K × N_candidate × n_samples)特征池超過 500 時建議先看一眼 4.4 節(jié)的提速方案別硬跑。4.3 DWCMI 與皮爾遜/卡方/單變量互信息的對比表選型時最常被問的就是為什么不用皮爾遜、卡方或者單變量互信息下面的對比表可以直接用作方案評審的說明材料。皮爾遜只抓線性關系卡方對高基數(shù)類別列有類似 MI 的偏向單變量互信息完全不管特征間冗余。DWCMI 在冗余消除和非線性捕獲上都有明顯優(yōu)勢代價是計算量更高、參數(shù)更多需要你愿意為特征子集質(zhì)量付一點調(diào)參成本。方法非線性捕獲冗余消除高基數(shù)偏差適合樣本量計算復雜度皮爾遜相關系數(shù)弱無無任意極低卡方檢驗弱無有中等低單變量互信息強無有中等低條件互信息強有有中高中動態(tài)加權條件互信息強有可抑制中高中高4.4 特征超過 500 個時的提速方案粗篩 精篩兩級流水線DWCMI 的計算瓶頸在每輪的全量打分特征幾百個時跑起來非常磨人。我一般會用兩級流水線第一級用單變量互信息把全量特征粗篩到前 100~150 個第二級只在粗篩后的候選池上跑 DWCMI。粗篩階段不追求精確排序只求把明顯無關的特征踢掉互信息計算成本低損失的信息量很小。如果候選池仍然有幾百個還可以預計算一次特征間的相關矩陣把冗余懲罰里的相關系數(shù)直接查表省掉每輪重算 np.corrcoef 的開銷。偽代碼如下# 第一級單變量 MI 粗篩 uni_scores { col: mutual_info_score(y, selector._bin(X[col].values)) for col in X.columns } top_pool sorted(uni_scores, keyuni_scores.get, reverseTrue)[:150] # 第二級只在 top_pool 上運行 DWCMI 主循環(huán) X_pool X[top_pool] selected_names, scores select_features(X_pool, y, max_k20)這套兩級流水線在我的模擬數(shù)據(jù)上能把全量 800 特征的運行時間從二十多分鐘壓到兩分鐘以內(nèi)排序結果與全量跑基本一致。粗篩的閾值 150 不是固定的特征本身噪音大時放到 200 更穩(wěn)噪音小時收到 80 也能保持同樣的精篩質(zhì)量。5. DWCMI 落地避坑五條來自調(diào)試現(xiàn)場的血淚記錄5.1 結果全是 NaN分箱數(shù)把聯(lián)合分布打散現(xiàn)象是第一次跑 DWCMI 時score 返回一列 NaN排查了很久才發(fā)現(xiàn)不是權重公式的問題而是分箱后的聯(lián)合標簽組合數(shù)超過了樣本數(shù)。原因在于 n_bins 設得太大比如 5000 個樣本用 30 箱候選特征和已選特征拼接出最多 900 種組合大部分組合里只有一兩個樣本概率估計直接退化。解決方法是把分箱數(shù)壓回 min(20, max(5, int(sqrt(n_samples/10)))) 的區(qū)間同時在 _bin 方法里加一個實際分箱數(shù)檢查如果得到的箱數(shù)少于預期就自動降一檔重新分。5.2 同一份數(shù)據(jù)跑兩次結果不一樣分箱邊界不穩(wěn)定有次項目評審前復跑實驗發(fā)現(xiàn)特征排序和昨天對不上排在第一第二的特征直接互換。原因是 qcut 在重復值多的時候會隨機調(diào)整邊界導致兩次分箱結果略有不同進而影響互信息的數(shù)值。這個問題的隱蔽之處在于它不報錯、差異很小、但結果不可復現(xiàn)。解決方法是分箱前先做秩變換并在 _bin 里固定一個隨機種子或者干脆把分箱邊界保存下來復用到所有實驗。我現(xiàn)在所有特征選擇實驗都要求輸出一份分箱映射表否則結果視為無效。5.3 加了動態(tài)加權后少數(shù)類被徹底忽略類別不平衡場景下動態(tài)加權會把注意力集中在多數(shù)類上少數(shù)類樣本在聯(lián)合分布里占比太小幾乎不貢獻互信息。現(xiàn)象是篩選出的特征子集在測試集少數(shù)類上的召回率掉得明顯。原因不是加權公式錯了而是分箱后的概率估計天然偏向多數(shù)類。解決思路是算 MI 之前對少數(shù)類做樣本加權給少數(shù)類樣本乘以一個權重系數(shù)讓正負樣本在數(shù)量上接近一比一。這個操作會輕微高估少數(shù)類特征的作用但換來的是分類模型對少數(shù)類的召回明顯回升。權重系數(shù)我一般設在 2 到 5 之間太大容易把噪聲特征也抬上來。5.4 累計得分曲線一路向上模型效果反而掉前向選擇的累計得分曲線如果一直在漲看起來是好事但模型性能反而變差這個現(xiàn)象在特征高度相關的數(shù)據(jù)集上很常見。原因是條件互信息的遞減趨勢被動態(tài)權重的“衰減補償”蓋住了alpha_decay 設得太小會讓后期權重驟降選進來的特征雖然與已選集合的增量信息很低但單變量 MI 的混合部分還在給它抬分。邊界條件要反過來看如果選了 20 個特征之后曲線還沒走平不一定是信息量豐富更可能是 min_gain 設得太低或者 beta_mix 太大。把 beta_mix 從 0.3 降到 0.2重新跑一遍曲線通常會恢復正常形態(tài)。5.5 特征多的時候算法慢到?jīng)]法上線特征池 1000 個、樣本量 5 萬時DWCMI 每輪都要算 1000 次條件互信息一次實驗跑幾個小時這種體驗很容易讓人放棄這個方法。原因就是全量掃描式的前向選擇沒有做剪枝。解決方法是回到 4.4 節(jié)的兩級流水線再加一個可選的“預熱”機制先用單變量 MI 排序結果把特征池切成四段分段內(nèi)再跑 DWCMI段間只做一次跨段比較。這樣單次實驗能控制在十分鐘內(nèi)排序結果與全量計算的差異在可接受范圍內(nèi)。計算貴的項目直接用這個方案不要硬剛?cè)俊?. 用穩(wěn)定性、替代模型與回歸測試給 DWCMI 驗明正身DWCMI 跑完一輪只是第一步真正要確認的是這個特征子集是不是可靠、穩(wěn)定、可復現(xiàn)。我的習慣是固定做三項驗證每項都有明確的通過標準。第一項是穩(wěn)定性驗證對訓練集做 20 次有放回抽樣每次重跑同樣參數(shù)的 DWCMI得到 20 個特征子集兩兩計算 Jaccard 系數(shù)取平均值作為穩(wěn)定性指標。均值在 0.8 以上說明特征選擇結果對樣本擾動不敏感0.6 以下就要警惕分箱數(shù)或者權重參數(shù)是不是調(diào)得太激進。第二項是替代模型驗證把選出的特征喂給一個不參與特征選擇的輕量模型比如邏輯回歸或淺層樹模型對比全量特征、單變量 MI 特征子集和 DWCMI 特征子集在驗證集上的 AUC 或 F1。DWCMI 的目標不是一定比全量高而是要在特征數(shù)減半的前提下不下降超過 1 個百分點同時比單變量 MI 子集有明顯提升。第三項是回歸測試把 DWCMI 選出的 top10 和 top20 特征子集固化下來跑一遍線上預估階段要用到的完整特征加工邏輯確認分箱邊界、缺失值填充、秩變換的順序都不會在實時鏈路里出錯。特征是動態(tài)生成的場景還要檢查線上特征分布是否發(fā)生了漂移漂移大時優(yōu)先重啟一輪特征選擇而不是復用舊的排序結果。這三項驗證做完DWCMI 的結論才敢拿出去見人。我之前吃過一次虧跳過穩(wěn)定性驗證直接上線兩周后特征分布一漂排序表徹底失效被迫回滾到舊模型從那以后每次跑完特征選擇都先把穩(wěn)定性指標打印出來看一眼。特征選擇這件事跑得快不如跑得穩(wěn)跑得穩(wěn)不如可復現(xiàn)這中間的經(jīng)驗全靠踩坑換。希望幫到你。本文還有配套的精品資源點擊獲取