分市場(chǎng)銷量預(yù)測(cè):分層建模與特征工程實(shí)戰(zhàn))
1. 賽題背景與整體思路拆解1.1 這個(gè)賽題到底在預(yù)測(cè)什么乘用車細(xì)分市場(chǎng)銷量預(yù)測(cè)說白了就是給定歷史若干個(gè)月的車型銷量數(shù)據(jù)去預(yù)測(cè)未來若干個(gè)月、不同車型在不同細(xì)分市場(chǎng)下的銷量。2019年那場(chǎng)競賽給出的數(shù)據(jù)維度相當(dāng)豐富車型配置、品牌、車身類型、排量、變速箱、上市時(shí)間、指導(dǎo)價(jià)區(qū)間以及按月份排列的銷量序列。目標(biāo)是把未來一段時(shí)間內(nèi)每個(gè)車型-細(xì)分市場(chǎng)組合的銷量預(yù)測(cè)準(zhǔn)。這類問題的難點(diǎn)不在于模型有多深而在于數(shù)據(jù)本身極其稀疏且波動(dòng)劇烈。一款車可能上市三個(gè)月月銷過萬然后突然掉到幾百臺(tái)也可能長期穩(wěn)定在某個(gè)區(qū)間。再加上細(xì)分市場(chǎng)劃分比如按車身形式、按價(jià)格帶、按動(dòng)力類型每個(gè)細(xì)分市場(chǎng)里的樣本量差異巨大有的細(xì)分市場(chǎng)只有幾十條記錄有的上千條。這種長尾分布直接決定了不能用一個(gè)統(tǒng)一模型硬套所有細(xì)分市場(chǎng)。適合參考這篇內(nèi)容的人我大致分三類一是打過結(jié)構(gòu)化數(shù)據(jù)預(yù)測(cè)類競賽、想看看別人怎么處理稀疏時(shí)序的二是做汽車行業(yè)數(shù)據(jù)分析、需要落地銷量預(yù)測(cè)的從業(yè)者三是剛?cè)腴T機(jī)器學(xué)習(xí)、想找一個(gè)完整項(xiàng)目練手的同學(xué)。不管哪一類核心收獲應(yīng)該是一樣的——如何把臟、亂、稀疏的業(yè)務(wù)數(shù)據(jù)轉(zhuǎn)化成模型能吃的形態(tài)并且在驗(yàn)證環(huán)節(jié)不騙自己。1.2 為什么最終方案是分而治之而不是端到端我們隊(duì)伍在賽后復(fù)盤時(shí)最大的共識(shí)就是這個(gè)賽題如果強(qiáng)行做一個(gè)端到端的深度模型大概率會(huì)翻車。原因有三個(gè)。第一數(shù)據(jù)量不夠。雖然看起來有幾萬條記錄但分?jǐn)偟矫總€(gè)細(xì)分市場(chǎng)、每個(gè)車型、每個(gè)時(shí)間點(diǎn)上可用樣本非常有限。深度模型在這種稀疏場(chǎng)景下極易過擬合驗(yàn)證集稍微一變線上成績就崩。第二業(yè)務(wù)邏輯強(qiáng)。汽車銷量有明顯的季節(jié)性金九銀十、春節(jié)前后低谷、政策周期性排放標(biāo)準(zhǔn)切換、購置稅優(yōu)惠、產(chǎn)品生命周期改款、停產(chǎn)。這些因素用樹模型加人工特征工程比讓神經(jīng)網(wǎng)絡(luò)自己學(xué)要靠譜得多。第三評(píng)估指標(biāo)敏感。競賽用的評(píng)估指標(biāo)對(duì)高銷量車型的絕對(duì)誤差懲罰很重這意味著大銷量車型的預(yù)測(cè)精度直接決定排名而小銷量車型預(yù)測(cè)得再準(zhǔn)也拉不開差距。所以策略上必須對(duì)大銷量車型單獨(dú)加碼。基于這三點(diǎn)我們最終走的是**分層建模 多模型融合 后處理校準(zhǔn)**的路線。分層是指按細(xì)分市場(chǎng)或銷量量級(jí)分組分別訓(xùn)練模型多模型融合是指LightGBM、XGBoost、CatBoost以及一個(gè)簡單的時(shí)序基線做加權(quán)后處理校準(zhǔn)則是針對(duì)預(yù)測(cè)結(jié)果的分布做修正避免出現(xiàn)負(fù)銷量或異常尖峰。這里給一個(gè)實(shí)操心得不要一上來就追求模型復(fù)雜度。我們前兩周試過LSTM和Seq2Seq驗(yàn)證集RMSE比LightGBM高了將近30%而且訓(xùn)練時(shí)間翻了好幾倍。后來果斷放棄深度路線把精力全砸在特征工程和分組策略上成績才穩(wěn)步上升。1.3 整體流程的五個(gè)階段把整個(gè)項(xiàng)目拆開大概是這么五步數(shù)據(jù)清洗與對(duì)齊把車型配置表、銷量表、上市時(shí)間表按車型ID和月份對(duì)齊處理缺失值和異常值。探索性分析看銷量分布、季節(jié)性、車型生命周期曲線確定分組依據(jù)。特征工程構(gòu)造滯后特征、滑動(dòng)窗口統(tǒng)計(jì)、同比環(huán)比、生命周期階段標(biāo)記、細(xì)分市場(chǎng)聚合特征等。模型訓(xùn)練與驗(yàn)證按時(shí)間切分驗(yàn)證集分組訓(xùn)練多個(gè)模型記錄每組最優(yōu)模型。融合與后處理加權(quán)融合、截?cái)嘭?fù)值、對(duì)高銷量車型做殘差修正。這五步里第三步和第四步占了我們80%的時(shí)間。下面我逐個(gè)展開把踩過的坑和最終有效的做法都講清楚。2. 數(shù)據(jù)清洗與探索性分析的關(guān)鍵細(xì)節(jié)2.1 數(shù)據(jù)對(duì)齊時(shí)最容易忽略的三個(gè)坑第一個(gè)坑是車型ID不一致。配置表和銷量表里的車型標(biāo)識(shí)可能因?yàn)楦目?、年式變化而出現(xiàn)一對(duì)多或多對(duì)一的情況。我們的處理方式是以銷量表的車型ID為主鍵配置表通過品牌車系年式做模糊匹配匹配不上的用同車系最近年式的配置填充。這一步聽起來簡單但實(shí)際做的時(shí)候會(huì)發(fā)現(xiàn)大概有5%到8%的記錄匹配不上如果直接丟掉會(huì)損失不少高銷量車型的樣本。第二個(gè)坑是月份缺失。有些車型在某些月份沒有銷量記錄這到底是銷量為零還是數(shù)據(jù)缺失我們的判斷邏輯是如果該車型在缺失月份的前后都有銷量且上市時(shí)間早于缺失月份、停產(chǎn)時(shí)間晚于缺失月份那就填0否則標(biāo)記為缺失不參與訓(xùn)練。這個(gè)邏輯必須寫清楚否則模型會(huì)把缺失當(dāng)成零來學(xué)導(dǎo)致預(yù)測(cè)偏低。第三個(gè)坑是異常值。汽車銷量偶爾會(huì)出現(xiàn)極端值比如某個(gè)月突然幾萬臺(tái)下個(gè)月又回到幾百臺(tái)。這可能是批發(fā)數(shù)據(jù)集中錄入也可能是特殊促銷。我們的做法是對(duì)每個(gè)車型的銷量序列做3倍標(biāo)準(zhǔn)差檢測(cè)超過閾值的點(diǎn)用前后三個(gè)月的移動(dòng)中位數(shù)替換。注意不要直接刪除異常點(diǎn)因?yàn)閯h除會(huì)破壞時(shí)間連續(xù)性影響滯后特征的構(gòu)造。2.2 探索性分析三個(gè)必須看的圖做時(shí)序預(yù)測(cè)有三張圖一定要畫。第一張是整體銷量按月匯總的折線圖。這張圖能告訴你季節(jié)性有多強(qiáng)。我們當(dāng)時(shí)畫出來發(fā)現(xiàn)每年2月都是低谷9月到11月是高峰這和行業(yè)常識(shí)完全吻合。有了這個(gè)認(rèn)知后面構(gòu)造月份特征時(shí)就有了依據(jù)而不是盲目讓模型去學(xué)。第二張是車型生命周期曲線。隨機(jī)抽20到30個(gè)車型把它們的銷量按上市后的月份數(shù)畫出來。你會(huì)發(fā)現(xiàn)大部分車型符合上市爬坡-穩(wěn)定-衰退的S型曲線但也有一些車型一上市就是巔峰然后迅速下滑。這個(gè)觀察直接啟發(fā)了我們構(gòu)造上市月數(shù)和上市月數(shù)的平方兩個(gè)特征用來捕捉生命周期的非線性。第三張是細(xì)分市場(chǎng)銷量分布箱線圖。這張圖決定了你怎么分組。我們當(dāng)時(shí)按車身類型和價(jià)格帶交叉分了大概12個(gè)細(xì)分市場(chǎng)發(fā)現(xiàn)其中3個(gè)細(xì)分市場(chǎng)的銷量中位數(shù)是另外9個(gè)的5倍以上。這就意味著如果統(tǒng)一建模大銷量細(xì)分市場(chǎng)會(huì)主導(dǎo)損失函數(shù)小銷量細(xì)分市場(chǎng)被忽略。所以必須分組或者至少給樣本加權(quán)。2.3 分組策略的確定過程分組不是拍腦袋我們?cè)嚵巳N方案分組方案依據(jù)優(yōu)點(diǎn)缺點(diǎn)最終是否采用按細(xì)分市場(chǎng)車身類型價(jià)格帶業(yè)務(wù)含義清晰部分組樣本過少作為主分組按銷量量級(jí)歷史月均銷量分高/中/低樣本均衡業(yè)務(wù)解釋弱作為輔助加權(quán)按品牌國別自主/合資/進(jìn)口宏觀因素一致組內(nèi)差異仍大放棄最終采用的是主分組按細(xì)分市場(chǎng)同時(shí)在每個(gè)組內(nèi)按銷量量級(jí)給樣本加權(quán)。具體來說月均銷量超過5000臺(tái)的樣本權(quán)重設(shè)為31000到5000的設(shè)為2低于1000的設(shè)為1。這樣既保留了業(yè)務(wù)解釋性又緩解了樣本不均衡。注意分組數(shù)量不要太多。我們一開始分了20多個(gè)組結(jié)果有些組只有幾十條訓(xùn)練樣本模型根本學(xué)不動(dòng)。后來合并到12個(gè)組每組至少300條以上樣本效果明顯穩(wěn)定。3. 特征工程從原始字段到模型可用的輸入3.1 滯后特征與滑動(dòng)窗口時(shí)序預(yù)測(cè)的命根子任何時(shí)序預(yù)測(cè)滯后特征都是最重要的。我們構(gòu)造了以下幾類滯后1到12個(gè)月的銷量直接反映近期趨勢(shì)?;瑒?dòng)窗口統(tǒng)計(jì)過去3個(gè)月、6個(gè)月、12個(gè)月的均值、中位數(shù)、標(biāo)準(zhǔn)差、最大值、最小值。同比特征去年同月的銷量以及今年累計(jì)銷量與去年累計(jì)銷量的比值。環(huán)比特征本月銷量與上月銷量的差值、比值。這里有個(gè)細(xì)節(jié)滯后特征在訓(xùn)練集和測(cè)試集上的構(gòu)造方式必須一致。比如預(yù)測(cè)未來3個(gè)月那么測(cè)試集里每個(gè)樣本的滯后特征只能用預(yù)測(cè)起點(diǎn)之前的數(shù)據(jù)。我們當(dāng)時(shí)寫了一個(gè)滑動(dòng)窗口生成函數(shù)確保不會(huì)出現(xiàn)用未來數(shù)據(jù)預(yù)測(cè)未來的泄漏?;瑒?dòng)窗口的窗口長度選擇也有講究。窗口太短噪聲大窗口太長反應(yīng)遲鈍。我們?cè)嚵?、6、12三種最終發(fā)現(xiàn)6個(gè)月窗口在驗(yàn)證集上表現(xiàn)最穩(wěn)。這可能是因?yàn)槠囦N量的中期趨勢(shì)比短期波動(dòng)更有預(yù)測(cè)價(jià)值。3.2 生命周期特征讓模型知道車處于什么階段前面探索性分析發(fā)現(xiàn)生命周期很重要所以專門構(gòu)造了一組特征上市月數(shù)當(dāng)前月份減去上市月份。上市月數(shù)的平方捕捉爬坡和衰退的非線性。是否改款如果配置表里有年式變化標(biāo)記為1。是否停產(chǎn)如果后續(xù)月份沒有銷量記錄標(biāo)記為1。這組特征在樹模型里的重要性排名一直很靠前。特別是上市月數(shù)在很多細(xì)分市場(chǎng)組里都是前五重要的特征。3.3 細(xì)分市場(chǎng)聚合特征引入橫向?qū)Ρ瘸藛诬囆蛄刑卣魑覀冞€構(gòu)造了同細(xì)分市場(chǎng)同月的聚合特征該細(xì)分市場(chǎng)當(dāng)月總銷量。該車型銷量占細(xì)分市場(chǎng)總銷量的比例。該細(xì)分市場(chǎng)當(dāng)月車型數(shù)量。該車型銷量在細(xì)分市場(chǎng)內(nèi)的排名。這些特征的作用是給模型提供橫向參照。比如一款車自身銷量在下降但如果整個(gè)細(xì)分市場(chǎng)都在下降那它的相對(duì)表現(xiàn)其實(shí)沒變。模型有了這些特征就能區(qū)分個(gè)體問題和市場(chǎng)問題。3.4 類別特征的處理目標(biāo)編碼 vs 獨(dú)熱編碼品牌、車身類型、變速箱這些類別特征我們?cè)嚵藘煞N處理方式獨(dú)熱編碼適合類別數(shù)少的特征比如變速箱手動(dòng)/自動(dòng)/雙離合等不到10類。目標(biāo)編碼適合類別數(shù)多的特征比如品牌幾十個(gè)、車系上百個(gè)。目標(biāo)編碼有個(gè)經(jīng)典陷阱必須用交叉驗(yàn)證的方式計(jì)算否則會(huì)泄漏標(biāo)簽。我們的做法是把訓(xùn)練集分成5折每折用其他4折的銷量均值來編碼當(dāng)前折的品牌測(cè)試集用全量訓(xùn)練集的均值編碼。這樣既利用了品牌信息又避免了過擬合。實(shí)操心得目標(biāo)編碼后一定要加一個(gè)平滑項(xiàng)。比如品牌A只有2條記錄均值是10000直接編碼會(huì)讓模型對(duì)這個(gè)品牌過擬合。我們的平滑公式是編碼值 (該品牌銷量和 全局均值 × 平滑系數(shù)) / (該品牌樣本數(shù) 平滑系數(shù))平滑系數(shù)取10到20之間。實(shí)測(cè)下來平滑后驗(yàn)證集誤差下降了約5%。4. 模型訓(xùn)練、驗(yàn)證與融合的完整實(shí)操4.1 時(shí)間序列驗(yàn)證集的切分方式這是整個(gè)項(xiàng)目里最容易犯錯(cuò)的地方。很多人做時(shí)序預(yù)測(cè)直接用隨機(jī)切分結(jié)果驗(yàn)證集里混入了未來信息線下成績虛高線上直接崩。正確的做法是按時(shí)間切分。比如用前18個(gè)月做訓(xùn)練第19到21個(gè)月做驗(yàn)證第22到24個(gè)月做測(cè)試。如果要做多折驗(yàn)證就滾動(dòng)切分第一折用1到15月訓(xùn)練、16到18月驗(yàn)證第二折用1到18月訓(xùn)練、19到21月驗(yàn)證以此類推。我們當(dāng)時(shí)用了3折滾動(dòng)驗(yàn)證每折的驗(yàn)證集大小和測(cè)試集一致都是3個(gè)月。這樣得到的驗(yàn)證成績和線上成績相關(guān)性很高基本能反映真實(shí)水平。4.2 三個(gè)模型的參數(shù)配置與調(diào)優(yōu)過程我們最終融合了三個(gè)模型LightGBM、XGBoost、CatBoost。參數(shù)不是隨便設(shè)的而是用貝葉斯優(yōu)化跑了大概200輪。LightGBM的核心參數(shù)lgb_params { objective: regression, metric: rmse, learning_rate: 0.03, num_leaves: 63, max_depth: 8, min_data_in_leaf: 20, feature_fraction: 0.8, bagging_fraction: 0.8, bagging_freq: 5, lambda_l2: 1.0, n_estimators: 2000, early_stopping_rounds: 100 }XGBoost的核心參數(shù)xgb_params { objective: reg:squarederror, eta: 0.03, max_depth: 7, min_child_weight: 5, subsample: 0.8, colsample_bytree: 0.8, alpha: 0.1, lambda: 1.0, n_estimators: 2000, early_stopping_rounds: 100 }CatBoost的核心參數(shù)cat_params { loss_function: RMSE, learning_rate: 0.05, depth: 7, l2_leaf_reg: 3.0, iterations: 2000, early_stopping_rounds: 100, verbose: False }調(diào)參時(shí)有個(gè)經(jīng)驗(yàn)學(xué)習(xí)率不要設(shè)太大。0.03到0.05之間比較穩(wěn)雖然訓(xùn)練慢一點(diǎn)但驗(yàn)證集波動(dòng)小。另外min_data_in_leaf和min_child_weight這兩個(gè)參數(shù)對(duì)稀疏數(shù)據(jù)特別重要設(shè)得太小會(huì)過擬合設(shè)得太大又欠擬合。我們最終取20和5是在驗(yàn)證集上試了十幾組才定下來的。4.3 融合策略加權(quán)平均 vs Stacking我們?cè)嚵藘煞N融合方式加權(quán)平均三個(gè)模型的預(yù)測(cè)值按權(quán)重相加權(quán)重用驗(yàn)證集上的RMSE反比確定。Stacking把三個(gè)模型的預(yù)測(cè)值作為新特征再訓(xùn)練一個(gè)線性回歸或小樹模型。結(jié)果發(fā)現(xiàn)加權(quán)平均在驗(yàn)證集上更穩(wěn)Stacking容易過擬合尤其是當(dāng)某個(gè)模型在某個(gè)細(xì)分市場(chǎng)上表現(xiàn)特別差時(shí)Stacking會(huì)放大這個(gè)錯(cuò)誤。最終我們用的是加權(quán)平均權(quán)重大概是LightGBM 0.4、XGBoost 0.35、CatBoost 0.25。注意融合前一定要檢查各模型預(yù)測(cè)值的量綱是否一致。我們有一次發(fā)現(xiàn)CatBoost因?yàn)橛昧瞬煌哪繕?biāo)函數(shù)預(yù)測(cè)值整體偏大融合后反而拉低了成績。后來統(tǒng)一了目標(biāo)函數(shù)才解決。4.4 后處理截?cái)?、平滑與高銷量修正模型輸出后我們做了三步后處理截?cái)嘭?fù)值預(yù)測(cè)銷量不能為負(fù)所有負(fù)值直接置0。平滑處理對(duì)每個(gè)車型的預(yù)測(cè)序列做3個(gè)月移動(dòng)平均避免出現(xiàn)鋸齒狀波動(dòng)。高銷量修正對(duì)歷史月均銷量超過5000臺(tái)的車型用驗(yàn)證集上的殘差均值做加性修正。第三步是提分的關(guān)鍵。因?yàn)樵u(píng)估指標(biāo)對(duì)大銷量車型的誤差懲罰重我們專門針對(duì)這部分車型做了偏差校正。具體做法是在驗(yàn)證集上計(jì)算每個(gè)高銷量車型的預(yù)測(cè)殘差真實(shí)值減預(yù)測(cè)值取均值然后加到測(cè)試集預(yù)測(cè)上。這個(gè)操作讓我們的線上成績提升了大概2到3個(gè)名次。5. 常見問題與排查技巧實(shí)錄5.1 驗(yàn)證集成績很好但線上崩了怎么查這是時(shí)序競賽最經(jīng)典的問題。排查順序如下排查項(xiàng)可能原因解決方法特征泄漏用了未來數(shù)據(jù)構(gòu)造特征檢查所有滯后和滑動(dòng)窗口的生成邏輯驗(yàn)證集切分隨機(jī)切分而非時(shí)間切分改為滾動(dòng)時(shí)間切分分布偏移訓(xùn)練集和測(cè)試集時(shí)間跨度大加入時(shí)間衰減權(quán)重過擬合模型太復(fù)雜或特征太多減少特征、增加正則化我們當(dāng)時(shí)遇到過一次線上崩盤最后查出來是目標(biāo)編碼時(shí)用了全量數(shù)據(jù)導(dǎo)致驗(yàn)證集泄漏。改成交叉驗(yàn)證編碼后線上線下相關(guān)性立刻恢復(fù)正常。5.2 小銷量車型預(yù)測(cè)全是零怎么辦小銷量車型樣本少模型容易直接預(yù)測(cè)零。我們的處理方式是對(duì)月均銷量低于100臺(tái)的車型單獨(dú)訓(xùn)練一個(gè)分類模型先判斷是否有銷量再回歸具體數(shù)值。在損失函數(shù)里給小銷量樣本更高的權(quán)重但不要太高否則會(huì)犧牲大銷量車型的精度。后處理時(shí)如果模型預(yù)測(cè)為零但歷史上有過銷量用歷史中位數(shù)替換。5.3 訓(xùn)練時(shí)間太長怎么加速LightGBM和XGBoost在幾萬條數(shù)據(jù)上其實(shí)很快但如果特征維度上千、樹的數(shù)量上千訓(xùn)練時(shí)間會(huì)明顯增加。加速手段用feature_fraction和colsample_bytree降低每棵樹的特征采樣比例。提前停止early stopping一定要開通常500到800棵樹就夠了。把數(shù)據(jù)轉(zhuǎn)成LightGBM的Dataset格式能省不少內(nèi)存和時(shí)間。如果機(jī)器有多核設(shè)置num_threads為物理核心數(shù)不要設(shè)成邏輯核心數(shù)否則會(huì)因超線程反而變慢。5.4 獨(dú)家避坑清單最后整理幾條我們踩過的坑都是文檔里不會(huì)寫的不要用未來數(shù)據(jù)做任何統(tǒng)計(jì)包括均值、中位數(shù)、編碼全部要用滾動(dòng)窗口或交叉驗(yàn)證。驗(yàn)證集一定要和測(cè)試集同分布如果測(cè)試集是未來3個(gè)月驗(yàn)證集也必須是連續(xù)的3個(gè)月不能隨機(jī)抽。融合權(quán)重不要用測(cè)試集調(diào)只能用驗(yàn)證集否則就是變相泄漏。后處理要謹(jǐn)慎平滑和修正都可能引入偏差每次改動(dòng)都要在驗(yàn)證集上確認(rèn)。保存每次實(shí)驗(yàn)的配置和結(jié)果我們最后復(fù)盤時(shí)發(fā)現(xiàn)最好的方案往往是幾次小改動(dòng)的組合如果沒有記錄根本復(fù)現(xiàn)不出來。這個(gè)賽題做完我最大的體會(huì)是結(jié)構(gòu)化數(shù)據(jù)預(yù)測(cè)特征工程和驗(yàn)證策略的重要性遠(yuǎn)大于模型本身。把數(shù)據(jù)對(duì)齊做干凈、把驗(yàn)證集切對(duì)、把滯后特征構(gòu)造好哪怕只用LightGBM單模型也能進(jìn)前10%。反過來如果這些基礎(chǔ)沒做好堆再多深度模型也是白搭。后續(xù)如果還想繼續(xù)優(yōu)化可以考慮引入外部數(shù)據(jù)比如宏觀經(jīng)濟(jì)指標(biāo)、油價(jià)或者對(duì)每個(gè)細(xì)分市場(chǎng)單獨(dú)做超參搜索但邊際收益可能就沒那么大了。