r(jià)預(yù)測(cè):數(shù)據(jù)預(yù)處理才是決勝關(guān)鍵)
做Kaggle房?jī)r(jià)預(yù)測(cè)比賽我第一次提交的成績(jī)慘不忍睹排名直接掉到中下游。當(dāng)時(shí)我以為問題出在模型選型上換了隨機(jī)森林又換XGBoost分?jǐn)?shù)就是紋絲不動(dòng)。后來我才意識(shí)到真正拖后腿的壓根不是模型而是數(shù)據(jù)本身——該處理的缺失值沒處理偏態(tài)分布沒校正類別特征一股腦當(dāng)啞變量丟進(jìn)去測(cè)試集和訓(xùn)練集的特征口徑還對(duì)不上。從那次之后我每次碰這類型的比賽都先把八成精力砸在數(shù)據(jù)預(yù)處理上效果立竿見影。這篇博文就完整復(fù)盤我在這個(gè)經(jīng)典房?jī)r(jià)預(yù)測(cè)比賽里的數(shù)據(jù)預(yù)處理全過程從缺失值取舍、偏態(tài)分布校正、類別編碼、新特征構(gòu)建到訓(xùn)練集測(cè)試集同步清洗的管道搭建都會(huì)講清楚為什么要這么做、踩過哪些坑、怎么做最穩(wěn)。適合剛?cè)腴T機(jī)器學(xué)習(xí)、想在Kaggle上拿第一個(gè)獎(jiǎng)牌或至少把分?jǐn)?shù)刷進(jìn)前20%的讀者也適合那些已經(jīng)能跑通baseline但覺得分?jǐn)?shù)卡住上不去的同學(xué)。1. 預(yù)處理前先搞懂比賽在打什么1.1 比賽任務(wù)與數(shù)據(jù)集長什么樣這個(gè)比賽的任務(wù)本身很簡(jiǎn)單給定一批房屋的幾十個(gè)特征比如占地面積、建造年份、地下室面積、廚房質(zhì)量、外部材料等等預(yù)測(cè)房屋的最終售價(jià)。訓(xùn)練集有1500條左右?guī)?biāo)簽樣本測(cè)試集有差不多同樣數(shù)量的無標(biāo)簽樣本需要提交預(yù)測(cè)出的房?jī)r(jià)。標(biāo)簽列是SalePrice也就是成交價(jià)格這列只在訓(xùn)練集里存在。數(shù)據(jù)集的構(gòu)成我習(xí)慣一上來就看三件事行數(shù)列數(shù)、特征類型分布、缺失值情況。用pandas讀進(jìn)來之后我通常先跑一遍train.info()和train.describe()快速掃一眼哪些字段是object類型、哪些是數(shù)值型、有沒有明顯的缺失。很多新手拿到數(shù)據(jù)直接就開始填缺失值其實(shí)是不對(duì)的數(shù)據(jù)長什么樣、特征代表什么意思都沒搞清楚填缺失值就是瞎填。我當(dāng)時(shí)做了一件事打印出所有特征的名稱列表逐個(gè)去理解這個(gè)字段是什么意思。這不光是比賽的習(xí)慣也是處理真實(shí)業(yè)務(wù)數(shù)據(jù)的基本功。比如LotArea是地塊面積GrLivArea是地面上的居住面積TotalBsmtSF是地下室總面積YearBuilt是建造年份OverallQual是整體材料和質(zhì)量評(píng)分。對(duì)這些字段要有概念后面做特征工程才能想出合理的組合特征。1.2 為什么數(shù)據(jù)預(yù)處理是決定排名的勝負(fù)手Kaggle比賽里很多新手會(huì)有一種錯(cuò)覺模型越復(fù)雜分?jǐn)?shù)越高。實(shí)際上在這類結(jié)構(gòu)化表格數(shù)據(jù)比賽里模型能力的差距遠(yuǎn)小于數(shù)據(jù)質(zhì)量的差距。你用一個(gè)調(diào)參得當(dāng)?shù)木€性回歸只要特征工程和預(yù)處理做到位完全可能打敗一個(gè)直接扔原始數(shù)據(jù)的高級(jí)集成模型。原因很簡(jiǎn)單。樹模型比如XGBoost、LightGBM確實(shí)能容忍一些缺失值和異常值但它們對(duì)特征表達(dá)非常敏感。偏態(tài)分布的特征如果直接參與分裂樹模型往往會(huì)在高值區(qū)間反復(fù)切分來擬合尾部浪費(fèi)了大量分裂深度。而線性模型更直接偏態(tài)特征和離群點(diǎn)會(huì)讓損失函數(shù)被極端樣本牽著鼻子走。更關(guān)鍵的是測(cè)試集和訓(xùn)練集之間的特征一致性一旦預(yù)處理方式不對(duì)稱線上成績(jī)和線下驗(yàn)證成績(jī)就會(huì)產(chǎn)生巨大偏差讓人完全失去調(diào)優(yōu)方向。所以我把預(yù)處理當(dāng)成一個(gè)獨(dú)立的工程階段來看待它至少包含四條主線缺失值處理、偏態(tài)與數(shù)值畸變校正、類別特征編碼、新特征構(gòu)造。再加上最后一層對(duì)訓(xùn)練集測(cè)試集口徑統(tǒng)一的管控。這篇文章剩余部分就按這條線逐個(gè)展開。2. 第一眼就要發(fā)現(xiàn)的問題偏態(tài)分布與數(shù)值特征畸變2.1 房?jī)r(jià)和面積不是正態(tài)的先做log變換拿到含標(biāo)簽的訓(xùn)練集后我第一件要做的事不是填缺失值而是看SalePrice的分布。這個(gè)習(xí)慣是從一次慘痛教訓(xùn)里學(xué)來的我在第一個(gè)版本里完全沒關(guān)心標(biāo)簽分布直接用原始房?jī)r(jià)做回歸結(jié)果模型對(duì)高價(jià)房完全預(yù)測(cè)不準(zhǔn)。后來把標(biāo)簽取了對(duì)數(shù)再用RMSE肉眼可見地往下掉。為什么會(huì)這樣房?jī)r(jià)數(shù)據(jù)幾乎總是右偏的大部分房子價(jià)格集中在中等偏低區(qū)間少數(shù)豪宅在右端拖出一條長尾。直接回歸這條長尾模型梯度會(huì)被那些高價(jià)樣本主導(dǎo)反復(fù)調(diào)整參數(shù)去迎合少數(shù)豪宅普通房子的擬合精度就被犧牲了。對(duì)標(biāo)簽做log1p變換后長尾被壓縮分布更接近對(duì)稱的鐘形回歸問題在數(shù)值上更好優(yōu)化誤差度量也穩(wěn)定得多。代碼上只需要兩行import numpy as np train[SalePrice] np.log1p(train[SalePrice])注意這里用的是log1p而不是log因?yàn)榉績(jī)r(jià)本身沒有0這種極端情況但log1p在輸入為0時(shí)不會(huì)報(bào)錯(cuò)更安全后面某些組合特征比如總面積也可能出現(xiàn)0值統(tǒng)一用log1p能省掉不少麻煩。同理很多數(shù)值特征也是右偏的比如GrLivArea、LotArea、TotalBsmtSF。在建模之前我會(huì)對(duì)這些特征也做log變換讓它們的量綱和分布形態(tài)更適合線性模型。需要提醒的是不要機(jī)械地對(duì)所有數(shù)值特征做變換先畫直方圖或者用.skew()算一下偏度大于0.75的才考慮log變換。2.2 數(shù)值特征要不要標(biāo)準(zhǔn)化、什么時(shí)候不做偏態(tài)校正之后另一個(gè)常見問題是標(biāo)準(zhǔn)化。StandardScaler會(huì)先把均值歸零方差歸一這個(gè)操作對(duì)線性回歸、嶺回歸、神經(jīng)網(wǎng)絡(luò)這類依賴特征量綱的模型幾乎是必需的。但對(duì)樹模型來說標(biāo)準(zhǔn)化并不影響分裂點(diǎn)的選擇因?yàn)闃淠P椭豢磁判蜿P(guān)系不信均值方差所以很多參賽者拿到樹模型直接不標(biāo)準(zhǔn)化完全沒問題。在房?jī)r(jià)比賽里我的習(xí)慣是分兩條線走如果最終上線的是LightGBM或XGBoost這類樹模型不需要StandardScaler但要保證缺失值處理干凈如果我想同時(shí)跑嶺回歸或者彈性網(wǎng)絡(luò)做模型融合那數(shù)值特征一定得縮放。特征融合是我后來分?jǐn)?shù)大幅提升的關(guān)鍵讓樹模型和線性模型各自看到不同的特征尺度再到上層做加權(quán)平均比單一模型穩(wěn)得多。具體操作上標(biāo)準(zhǔn)化必須遵循“擬合訓(xùn)練集變換測(cè)試集”的原則from sklearn.preprocessing import StandardScaler scaler StandardScaler() X_train_num_scaled scaler.fit_transform(X_train_num) X_test_num_scaled scaler.transform(X_test_num)如果對(duì)測(cè)試集也調(diào)用fit_transform等于讓測(cè)試集參與計(jì)算均值和方差這在嚴(yán)格意義上是信息泄漏。雖然在一個(gè)比賽里對(duì)最終排名影響未必那么致命但它會(huì)讓你的驗(yàn)證方法失去校準(zhǔn)意義這是大忌。3. 缺失值處理取舍比填補(bǔ)更重要3.1 先給缺失值分類該刪的刪該補(bǔ)的補(bǔ)缺失值處理的第一步不是補(bǔ)而是分類。我一般把缺失值分成三種情況。第一種是“缺失即無”。比如PoolQC泳池質(zhì)量、MiscFeature其他設(shè)施特征大量房子根本沒有泳池沒有這些設(shè)施字段自然為空這種空值不代表數(shù)據(jù)缺失而是代表“無”這個(gè)狀態(tài)。第二種是“缺失需合理推算”。比如LotFrontage臨街面寬度有些房子沒記錄這個(gè)值但同街道或同街區(qū)的其他房子有可以用中位數(shù)或按街區(qū)聚合來推算。第三種是“缺失無意義且占比過高”。比如某個(gè)特征有超過70%的空值而且和房?jī)r(jià)關(guān)聯(lián)性本身就很弱這種我傾向于直接刪掉減少噪聲。我當(dāng)時(shí)的做法是先跑一行代碼看缺失率排名train_missing train.isnull().mean().sort_values(ascendingFalse) train_missing[train_missing 0]結(jié)果一目了然排在前面的是PoolQC、MiscFeature、Alley、Fence、FireplaceQu這類典型“缺失即無”特征。這些我不會(huì)直接刪因?yàn)椤坝袥]有泳池”“有沒有圍欄”本身就是影響房?jī)r(jià)的因素我會(huì)填一個(gè)None的標(biāo)簽讓它作為一個(gè)有意義的類別特征參與建模。但如果缺失率超過70-80%且含義模糊比如某些年份記錄不全的字段我會(huì)考慮舍棄。3.2 數(shù)值列缺失的填補(bǔ)策略中位數(shù)、按組聚合、邊界值數(shù)值特征的缺失值填補(bǔ)我踩過一個(gè)特別典型的坑用均值填LotFrontage。LotFrontage里面有一些特別寬的臨街地塊均值被這些異常值拉高了導(dǎo)致所有缺失樣本都被填成一個(gè)偏大的值。后來改成按Neighborhood分組取中位數(shù)分?jǐn)?shù)明顯提升。為什么中位數(shù)比均值穩(wěn)中位數(shù)只關(guān)心排序位置不受極端大值影響。當(dāng)特征分布是右偏的均值會(huì)被長尾拉走中位數(shù)更能代表典型水平。GarageYrBlt車庫建造年份是另一個(gè)典型例子。一個(gè)房子如果沒有車庫這個(gè)字段肯定是空如果直接填充年份中位數(shù)就等于給無車庫的房子編了一個(gè)車庫建造年份模型學(xué)到的信息就是錯(cuò)的。正確的做法是先看GarageArea或GarageCars如果是0就說明沒有車庫那GarageYrBlt應(yīng)該填0而不是中位數(shù)df.loc[df[GarageArea] 0, GarageYrBlt] 0 df[GarageYrBlt] df[GarageYrBlt].fillna(0)這里把“無車庫”映射成0既保留了缺失的含義又用數(shù)值0表達(dá)了“沒有”和實(shí)際有車庫的年份數(shù)值天然區(qū)分開。類似的策略用在有明確邊界含義的字段上比盲目用均值中位數(shù)靠譜得多。核心思路就是先想清楚這個(gè)字段空著到底是什么意思再?zèng)Q定填什么。3.3 分類特征缺失填“None”比填眾數(shù)靠譜分類特征缺失是我最早犯的錯(cuò)。比如FireplaceQu壁爐質(zhì)量我一開始直接用眾數(shù)填結(jié)果就是大多數(shù)樣本都變成了同一個(gè)質(zhì)量的壁爐模型完全分不清哪些房子根本沒有壁爐。后來才明白FireplaceQu為空實(shí)際上表示“無壁爐”無壁爐和有差壁爐、有好壁爐是完全不同的三個(gè)狀態(tài)。處理方式很簡(jiǎn)單所有“缺失即無”的分類特征統(tǒng)一填一個(gè)標(biāo)記值None或Not Applicablefor col in [PoolQC, MiscFeature, Alley, Fence, FireplaceQu]: df[col] df[col].fillna(None)這樣做的意義在編碼階段會(huì)體現(xiàn)出來。如果后續(xù)做One-Hot編碼None會(huì)單獨(dú)成為一個(gè)啞變量模型可以學(xué)習(xí)到“沒有這個(gè)設(shè)施”對(duì)房?jī)r(jià)的影響。如果你用眾數(shù)去填這個(gè)信息就永遠(yuǎn)丟失了填充出來的值還會(huì)誤導(dǎo)模型。另外還有一類分類特征缺失是真正的記錄缺失跟語義無關(guān)比如MSZoning偶爾缺幾條。這種占比很小我才會(huì)考慮用眾數(shù)填充因?yàn)閷?duì)整體分布影響微乎其微。所以分類特征的缺失處理也分情況核心判斷標(biāo)準(zhǔn)還是“缺失是否代表狀態(tài)”。4. 特征工程構(gòu)建新特征與類別編碼4.1 組合特征一列頂十列我把缺失值處理完了之后開始做特征工程。組合特征這件事看著簡(jiǎn)單但實(shí)際上需要你對(duì)業(yè)務(wù)有理解。房?jī)r(jià)預(yù)測(cè)這個(gè)比賽里有一些很有名的組合特征比如總面積、總衛(wèi)生間數(shù)、整體質(zhì)量分這些特征單獨(dú)看可能和房?jī)r(jià)有相關(guān)性但它們組合起來的意義更大。我最常用的幾個(gè)組合特征df[TotalSF] df[TotalBsmtSF] df[1stFlrSF] df[2ndFlrSF] df[TotalBath] df[FullBath] 0.5 * df[HalfBath] df[BsmtFullBath] 0.5 * df[BsmtHalfBath] df[Age] df[YrSold] - df[YearBuilt] df[OverallGrade] df[OverallQual] * df[OverallCond]為什么這些有效拿TotalSF來說原始數(shù)據(jù)把總面積拆成了地下室、一樓、二樓三列線性模型要自己學(xué)習(xí)三個(gè)系數(shù)來組合出總面積如果三列之間還有缺失值和噪聲模型學(xué)起來很容易走偏。我直接給它一個(gè)總面積的精確值相當(dāng)于把業(yè)務(wù)常識(shí)灌進(jìn)了模型。OverallGrade同理OverallQual是材料質(zhì)量OverallCond是房屋狀況質(zhì)量高但狀況差的房子不一定值錢兩者相乘才是一個(gè)更有區(qū)分度的綜合指標(biāo)。做組合特征的時(shí)候有一個(gè)細(xì)節(jié)對(duì)偏態(tài)明顯的組合特征也要記得做log變換比如TotalSF大概率是右偏的用np.log1p處理一下對(duì)線性模型很有幫助。樹模型對(duì)這種變換不敏感但不影響你統(tǒng)一處理反正結(jié)果不會(huì)更差。4.2 類別特征的編碼有序和無序要分開處理類別編碼是很多新手處理得最粗糙的環(huán)節(jié)也是我最想強(qiáng)調(diào)的地方。數(shù)據(jù)里有大量object類型的特征比如ExterQual外部材料質(zhì)量、KitchenQual廚房質(zhì)量、BsmtQual地下室質(zhì)量這些特征的取值是Ex、Gd、TA、Fa、Po分別代表優(yōu)秀、良好、典型、差、極差它們之間是有明確順序的。對(duì)于這些有序類別直接用pd.get_dummies會(huì)把這五個(gè)等級(jí)拆成五個(gè)啞變量模型為了表達(dá)“Ex大于Gd”這個(gè)順序必須同時(shí)組合兩個(gè)啞變量學(xué)起來既費(fèi)參數(shù)又容易過擬合。正確做法是手工映射成等差數(shù)值qual_map {Po: 0, Fa: 1, TA: 2, Gd: 3, Ex: 4} for col in [ExterQual, KitchenQual, BsmtQual, HeatingQC]: df[col] df[col].map(qual_map)映射成0到4的數(shù)值之后模型可以直接學(xué)會(huì)這個(gè)單調(diào)關(guān)系效果遠(yuǎn)好于啞變量。這里要特別提醒做映射的字典必須包含所有可能出現(xiàn)但當(dāng)前數(shù)據(jù)里沒出現(xiàn)的等級(jí)否則測(cè)試集一旦出現(xiàn)訓(xùn)練集沒見過的取值代碼直接報(bào)錯(cuò)或者生成NaN這是后期很煩人的問題。對(duì)于真正沒有順序的類別特征比如MSZoning區(qū)域劃分、Neighborhood地理位置、SaleType銷售類型默認(rèn)用One-Hot編碼。我用pd.get_dummies的時(shí)候會(huì)把drop_firstTrue打開去除一個(gè)參考類別避免完美多重共線性。這個(gè)操作對(duì)線性模型尤其重要不然正則化系數(shù)會(huì)被共線特征拉偏。不過在pd.get_dummies之后記得檢查一下列數(shù)如果某個(gè)類別特征的唯一值特別多比如街道名有上百種那就要考慮是不是要合并低頻類別否則維度會(huì)爆炸。5. 訓(xùn)練集測(cè)試集同步清洗少一步就白干5.1 用一個(gè)函數(shù)同時(shí)處理訓(xùn)練集和測(cè)試集數(shù)據(jù)預(yù)處理最容易翻車的點(diǎn)不是方法選錯(cuò)而是訓(xùn)練集和測(cè)試集處理不一致。剛開始我自己寫代碼的時(shí)候是先用一串代碼處理train再復(fù)制一份改成處理test看起來省事實(shí)際上隱患巨大。后來我把所有預(yù)處理代碼全部收斂到一個(gè)函數(shù)里面輸入一個(gè)原始DataFrame輸出一個(gè)處理好的DataFrame訓(xùn)練集和測(cè)試集分別調(diào)用一次。這個(gè)函數(shù)包含的順序一定是固定的先處理缺失值再處理偏態(tài)再映射有序類別再生成組合特征最后做One-Hot編碼。順序?yàn)槭裁粗匾驗(yàn)镺ne-Hot編碼會(huì)把類別特征打散成很多列如果你在One-Hot之后才做有序類別映射那些已經(jīng)展開的啞變量就無法合并回去了。缺失值的填補(bǔ)也是一樣必須在One-Hot之前完成因?yàn)閱∽兞恐划a(chǎn)生0和1你沒法對(duì)啞變量做中位數(shù)填補(bǔ)。寫成函數(shù)還有另一個(gè)好處基線模型的完整流程可以被串起來。我在process_data函數(shù)之后直接接train_model這樣每次調(diào)整一個(gè)預(yù)處理細(xì)節(jié)只要重新跑一遍函數(shù)就能看到對(duì)分?jǐn)?shù)的真實(shí)影響。5.2 全量對(duì)齊確保訓(xùn)練集和測(cè)試集列名完全一致One-Hot編碼有一個(gè)非常隱蔽的坑如果訓(xùn)練集和測(cè)試集在某個(gè)類別特征上的取值集合不完全相同就會(huì)導(dǎo)致兩邊編碼后列數(shù)不一致。比如MSZoning在訓(xùn)練集里有5個(gè)取值在測(cè)試集里有一個(gè)額外的類別那么pd.get_dummies會(huì)在測(cè)試集上多生成一列之后模型訓(xùn)練和預(yù)測(cè)就對(duì)應(yīng)不上。穩(wěn)住的做法是在兩個(gè)數(shù)據(jù)集都處理完之后做一次列對(duì)齊。我會(huì)先把訓(xùn)練集和測(cè)試集concat到一起統(tǒng)一做One-Hot編碼再拆開n_train train.shape[0] df_all pd.concat([train, test], axis0) df_all pd.get_dummies(df_all, drop_firstTrue) train_transformed df_all.iloc[:n_train, :] test_transformed df_all.iloc[n_train:, :]這樣兩邊編碼出來的列一定是一致的因?yàn)槭且黄鹁幋a的天然對(duì)齊。這一招我后來在幾乎所有表格類比賽里都用省掉了無數(shù)調(diào)試時(shí)間。要注意的是concat之前測(cè)試集里要人工加上SalePrice占位列還是不加其實(shí)無所謂只要后續(xù)拆開后單獨(dú)處理標(biāo)簽就行。5.3 驗(yàn)證預(yù)處理效果的三個(gè)信號(hào)預(yù)處理做到什么程度算好我一般盯三個(gè)信號(hào)。第一個(gè)是交叉驗(yàn)證的RMSE分?jǐn)?shù)每次改動(dòng)預(yù)處理后重新跑一遍五折交叉驗(yàn)證分?jǐn)?shù)穩(wěn)定下降說明改動(dòng)有效。第二個(gè)是線上提交的Public Score但Public Score容易被少數(shù)異常樣本帶偏我更看重等比賽結(jié)束后Private Score的穩(wěn)定性。第三個(gè)是特征重要性分布訓(xùn)練完樹模型后輸出feature_importances_如果某個(gè)我自己構(gòu)造的特征重要性排名很靠前說明這個(gè)特征工程是有用的如果所有新特征的占比都很低可能是處理方式出了問題。這三個(gè)信號(hào)會(huì)聯(lián)動(dòng)。曾經(jīng)有一次我在本地CV分?jǐn)?shù)上看到了微弱提升但feature_importances_里新特征幾乎為0后來發(fā)現(xiàn)是因?yàn)樾绿卣骱团f特征有高度相關(guān)性樹模型隨機(jī)選了舊特征做分裂重要性被分散了。這時(shí)可以考慮去掉冗余的舊特征只看新特征所在的那組子集重新評(píng)估。6. 踩坑實(shí)錄常見問題與排查技巧6.1 測(cè)試集出現(xiàn)訓(xùn)練集沒見過的類別怎么兜底這個(gè)問題我在早期幾乎每一場(chǎng)比賽都會(huì)遇到。訓(xùn)練集和測(cè)試集都是從一個(gè)完整數(shù)據(jù)集里隨機(jī)切出來的按理說類別分布應(yīng)該接近但在小樣本下某些稀有類別可能只在測(cè)試集出現(xiàn)。比如SaleType里的某個(gè)冷門交易類型訓(xùn)練集里從沒出現(xiàn)過pd.get_dummies之后測(cè)試集就會(huì)多出一列。我在函數(shù)里加了一行保險(xiǎn)test_transformed test_transformed.reindex(columnstrain_transformed.columns, fill_value0)這行代碼的含義是以訓(xùn)練集的列名為準(zhǔn)測(cè)試集有這些列就保留沒有的列填0。這樣即使測(cè)試集出現(xiàn)了新類別它對(duì)應(yīng)的啞變量也會(huì)被強(qiáng)制歸零模型不會(huì)因?yàn)榱袛?shù)不一致而報(bào)錯(cuò)。當(dāng)然這種方法會(huì)丟失新類別信息但新類別本身在訓(xùn)練階段就沒出現(xiàn)過模型本來就學(xué)不到它的規(guī)律歸零是最合理的兜底。6.2 一上來就把缺失行刪掉排名反而掉了我見過不少選手的習(xí)慣是dropna()一條龍把所有含缺失值的行全刪了。這個(gè)做法在訓(xùn)練集里可能會(huì)刪掉30%的樣本然后他們發(fā)現(xiàn)驗(yàn)證分?jǐn)?shù)還不錯(cuò)因?yàn)槭O碌臉颖救琴|(zhì)量好的記錄。但提交之后線上分?jǐn)?shù)崩了原因很簡(jiǎn)單測(cè)試集里那些被刪掉類型的房子你完全沒有訓(xùn)練樣本可以參照測(cè)試集里的缺失值照樣存在可你沒法把測(cè)試集的行也刪了。正確的理念是能補(bǔ)則不刪缺失率高且語義確實(shí)不明的特征再刪。前文說的“缺失即無”類特征全部要保留并補(bǔ)充成None狀態(tài)。數(shù)據(jù)行盡量全保留特征列按具體含義決定去留。這個(gè)判斷比任何算法都重要。6.3 在CV折內(nèi)做填補(bǔ)別把測(cè)試數(shù)據(jù)漏進(jìn)訓(xùn)練里另一個(gè)隱蔽的數(shù)據(jù)泄漏出現(xiàn)在交叉驗(yàn)證和缺失值處理的配合上。假設(shè)我先對(duì)整個(gè)訓(xùn)練集做了中位數(shù)填補(bǔ)然后做五折交叉驗(yàn)證每一折的驗(yàn)證集里被填補(bǔ)的樣本其實(shí)是由訓(xùn)練部分的統(tǒng)計(jì)量生成的這看起來沒毛病因?yàn)槲覀冇玫氖峭粋€(gè)數(shù)據(jù)集的信息。但如果我在每一折內(nèi)部重新計(jì)算中位數(shù)去填補(bǔ)訓(xùn)練折再用這個(gè)中位數(shù)去填驗(yàn)證折會(huì)更嚴(yán)謹(jǐn)。實(shí)際上很多參賽者在表格比賽里直接全局填補(bǔ)再做CV也能得到穩(wěn)定分?jǐn)?shù)因?yàn)閱蝹€(gè)特征缺失比例不高時(shí)對(duì)全局統(tǒng)計(jì)量的影響很小。但我想要一個(gè)更嚴(yán)謹(jǐn)?shù)脑u(píng)估體系時(shí)會(huì)用SimpleImputer放進(jìn)Pipeline里這樣缺失值填補(bǔ)的統(tǒng)計(jì)量就是每折訓(xùn)練部分自動(dòng)計(jì)算出來并應(yīng)用到驗(yàn)證部分的泄漏風(fēng)險(xiǎn)被降到最低。代碼長一點(diǎn)但心理踏實(shí)。6.4 一張表看清常見問題和處理方向現(xiàn)象原因處理方向訓(xùn)練CV分?jǐn)?shù)好但線上分?jǐn)?shù)差預(yù)處理或特征選擇存在泄漏檢查是否有全局fit的scaler和填補(bǔ)器One-Hot后列數(shù)不一致類別取值訓(xùn)練集測(cè)試集不同合并編碼或reindex對(duì)齊新特征重要性為0特征與舊特征高度共線考慮刪舊特征或重新設(shè)計(jì)組合均價(jià)模型對(duì)高價(jià)房預(yù)測(cè)不準(zhǔn)標(biāo)簽和偏態(tài)特征未做變換對(duì)SalePrice和目標(biāo)特征做log1p缺失值刪除過多導(dǎo)致樣本不足沒有區(qū)分缺失即無的類型保留缺失標(biāo)記填充而非刪行7. 一些我在實(shí)操中摸索出來的加分小習(xí)慣講完主流程我再補(bǔ)幾個(gè)額外的小習(xí)慣可能不是預(yù)處理的核心但確實(shí)幫我在分?jǐn)?shù)上摳出了不少提升空間。合并低頻類別。Categories里如果某個(gè)值只出現(xiàn)一兩次One-Hot之后就是一兩列稀疏特征不但沒有泛化能力還容易過擬合。我會(huì)把這些低頻值統(tǒng)一合并成Other。判斷標(biāo)準(zhǔn)以訓(xùn)練集為主如果一個(gè)類別在訓(xùn)練集里出現(xiàn)次數(shù)低于總樣本數(shù)的1%就合并掉。偏度校正后記得檢查是否有無窮值。log變換后如果原數(shù)據(jù)里有0log0會(huì)得到負(fù)無窮fillna都救不回來。這也是我堅(jiān)持用np.log1p而不是np.log的原因log1p對(duì)0值安全。如果你見到某個(gè)特征變換后出現(xiàn)一堆奇怪的大負(fù)數(shù)優(yōu)先檢查是不是這個(gè)原因。把所有預(yù)處理函數(shù)和參數(shù)寫成配置字典。比如每個(gè)有序類別對(duì)應(yīng)的映射字典、每個(gè)數(shù)值特征的填補(bǔ)策略、要?jiǎng)h除的高缺失列名單都集中放在一個(gè)config里。這個(gè)做法看似多余但當(dāng)你需要做模型融合或者朋友想看你的預(yù)處理邏輯時(shí)你會(huì)感謝當(dāng)時(shí)的自己。我在第二次跑這個(gè)比賽時(shí)就是靠這份配置快速復(fù)現(xiàn)了所有版本的結(jié)果。別忘了在預(yù)處理完成后做一次可見性檢查。打印處理后的數(shù)據(jù)維度、列數(shù)量、有沒有NaN殘留print(train_transformed.shape) print(test_transformed.shape) print(train_transformed.isnull().sum().sum()) print(test_transformed.isnull().sum().sum())NaN殘留如果是0說明預(yù)處理沒有遺漏如果還有殘留先停下去補(bǔ)別急著訓(xùn)練模型。這一步花不了三十秒但能攔住一晚上的debug。我個(gè)人在實(shí)際操作中最深刻的體會(huì)是預(yù)處理不是在比賽開頭做一次就結(jié)束的它是一個(gè)和建模循環(huán)迭代的過程。你上一輪模型跑出來發(fā)現(xiàn)某個(gè)特征重要性很高下一輪就會(huì)去為它設(shè)計(jì)更細(xì)致的處理你發(fā)現(xiàn)某個(gè)缺失字段被簡(jiǎn)單填充后模型依然很敏感就會(huì)回來研究更合理的分組填補(bǔ)。不要把數(shù)據(jù)預(yù)處理當(dāng)成一個(gè)一次性關(guān)卡它是整個(gè)比賽過程中陪你到最后的核心環(huán)節(jié)。這個(gè)比賽的房?jī)r(jià)數(shù)據(jù)我前后跑了五個(gè)版本每一版本的預(yù)處理都在變化分?jǐn)?shù)也隨之逐步提升這才是這類比賽真正的魅力所在。