)
簡介本資源源自京東JData算法大賽高潛用戶購買意向預測賽題是一套完整可直接復跑的機器學習項目。內容覆蓋數據預處理、正負樣本組合、特征生成與篩選、模型訓練與驗證等核心環(huán)節(jié)代碼含Python腳本及編譯緩存文件配套項目說明文檔與運行教程另有歷史代碼備份包。資源共24個文件以py源碼、pyc緩存、markdown說明及txt使用指南為主壓縮包僅約92KB結構清晰適合人工智能、數據科學等相關專業(yè)的畢設、課設、競賽復現(xiàn)與入門學習。目前已有176人學習下載項目中涉及的特征工程與梯度提升樹建模思路對理解電商用戶購買意向預測的完整流程具有直接參考價值下載后即可對照說明快速運行和二次開發(fā)。1. 高潛用戶購買意向預測為什么這個賽題值得一行行拆著學在電商營銷和數據挖掘的場景里“京東JData算法大賽-高潛用戶購買意向預測”是一道特別典型的成交建模題給你一批用戶過去一段時間的瀏覽、加購、關注、下單行為讓你判斷某個用戶對指定商品接下來的購買可能性。它和點擊率預估最大的區(qū)別是樣本稀疏、正負樣本懸殊但業(yè)務價值又比點擊率直接得多做起來很考驗特征功底。這篇文章把完整路線拆開講從字段理解、評估指標到特征工程、模型訓練和調參再到最容易翻車的地方。源碼包里的項目說明和資料我會按做這類賽題最常見的實現(xiàn)方式把邏輯補齊你拿到手后可以直接對著改不需要重新推一遍流程。適合兩類人一類是想拿真實比賽練手、把“用戶購買意向預測”做成完整項目經歷的數據新人另一類是在做電商轉化預測、總被離線在線指標不一致折磨的從業(yè)者。不預設你懂很多但從基線到調優(yōu)每一步都保證能照著復現(xiàn)。2. 預測目標與數據邊界字段結構、評估指標和第一個規(guī)則基線做這類賽題最忌諱一上來就訓練模型。先把“預測誰、拿什么預測、怎么算分”三件事釘死后面的特征和模型才談得上意義。JData 這類比賽的數據通常圍繞三個維度組織用戶信息、商品信息、用戶行為日志。用戶信息里常見的是年齡、性別、城市級別這類靜態(tài)屬性商品信息里常見的是商品編號、類別、品牌、價格行為日志則是重頭戲記錄每個用戶對每個商品在什么時間做了什么操作行為類型基本覆蓋瀏覽、加購、關注、下單幾種。時間跨度上行為日志一般覆蓋三十天左右預測目標落在日志末尾之后比如預測未來三到五天內用戶是否會產生購買。讀數據時有一個關鍵點不能馬虎訓練集和測試集的時間邊界。賽題經常在這里埋坑比如訓練集行為截止日期和測試集不一致或者測試集里混入訓練集時間之后才出現(xiàn)的用戶。拿到資料后第一步不是跑模型而是把每張表的日期范圍打出來標清楚邊界再繼續(xù)。2.1 字段梳理與數據質檢先解決臟數據再談特征我不建議直接讀完整數據。更穩(wěn)妥的做法是寫一個輕量概覽腳本確認每張表的主鍵、時間范圍和缺失情況同時把訓練集和測試集的結構對齊。這套動作看著基礎但能省掉后面大量排查時間屬于典型的“磨刀不誤砍柴工”。import pandas as pd # 讀入三張原始表路徑按你解壓后的實際目錄修改 users pd.read_csv(data/user_info.csv) items pd.read_csv(data/item_info.csv) actions pd.read_csv(data/user_action.csv, parse_dates[action_time]) # 查看表結構和缺失情況 for df, name in [(users, users), (items, items), (actions, actions)]: print(f {name} ) print(shape:, df.shape) print(缺失情況:) print(df.isnull().sum()) print(字段類型:) print(df.dtypes) # 行為日志的時間范圍這個必須確認 print(行為日志時間范圍, actions[action_time].min(), -, actions[action_time].max()) # 行為類型取值分布 print(行為類型分布) print(actions[action_type].value_counts())這段代碼沒有任何建模邏輯但它是整個項目的地基。字段類型和時間范圍常常比想象中更坑時間格式不統(tǒng)一、行為類型用數字編碼、缺失值分布在訓練集和測試集之間不一致這些都是常見狀況。確認清楚之后再做特征拼接才不會在后續(xù)步驟里報錯或產生莫名其妙的空值。參數說明parse_dates直接把時間列轉成 datetime 類型后續(xù)做時間窗口計算才方便value_counts用于確認行為類型有幾種、占比如何。如果發(fā)現(xiàn)訓練集有五種行為類型而測試集只有四種后續(xù)做特征時就要按并集處理不是按交集。2.2 評估指標AUC 還是 F1決定了你怎么調閾值這類預測賽題常用兩類指標一類是 AUC 這類排序指標只看模型給正樣本打的分是否普遍高于負樣本另一類是 F1 這類閾值指標要在某個分數之上才算正樣本。兩者的差別非常大AUC 高不代表 F1 高因為 F1 還受正負樣本比例和閾值選擇的影響。在高潛購買意向預測中正樣本通常只占整體樣本的百分之幾直接算 F1 會很難看必須配合閾值搜索。我一般會先以 AUC 為主優(yōu)化模型的排序能力最后單獨做一輪閾值搜索把 F1 或業(yè)務上更關心的精確率拉起來。這里要提醒一句如果你拿到的資料里官方指標是 F1那么訓練目標就不要設成分類正確率而是先把模型分數輸出再在驗證集上搜最優(yōu)閾值這兩條路徑最終分數差距可能超過十個點。2.3 第一個基線用“加購未購”做規(guī)則排序很多新手上來就上 LightGBM但第一個基線用規(guī)則更好原因有兩個一是能快速驗證數據是否存在泄露或時間錯位二是規(guī)則分數可以當作后續(xù)模型的強特征。我通常會先構造一個最簡單的預測邏輯如果用戶在窗口里對某商品加購過但沒有下單就給這個組合一個很高的分數。import numpy as np # actions: user_id, item_id, action_type, action_time # 先把行為表轉成用戶-商品對的行為集合 act actions.groupby([user_id, item_id])[action_type].apply(set).reset_index() # 規(guī)則打分加購未購是高意向只瀏覽是低意向 def rule_score(s): if buy in s: # 已購買過存量用戶短期再購概率低 return 0.3 if cart in s: # 加購未購意向最強 return 0.9 if fav in s: # 關注過次強 return 0.6 if view in s: # 只瀏覽過弱意向 return 0.4 return 0.0 act[rule_score] act[action_type].apply(rule_score) # 與測試集候選對做左連接得到每個候選的基線分數 test pd.read_csv(data/test_candidates.csv) test test.merge(act[[user_id, item_id, rule_score]], on[user_id, item_id], howleft) test[rule_score] test[rule_score].fillna(0.0) # 按分數排序輸出 test.sort_values(rule_score, ascendingFalse).to_csv(baseline_rule.csv, indexFalse)邏輯說明這個基線把行為類型按購買意圖強弱排序加購未購分最高關注次之只看過的最弱。它不含任何機器學習但能很快暴露兩個問題一是如果加購未購在測試集里大量存在說明意向信號充足模型應該重點利用二是如果連這種規(guī)則基線的線下 AUC 都很低說明候選集構成可能和訓練集差異很大需要先排查數據配比。參數說明rule_score的取值不是硬性的把加購未購調到 0.95、關注調到 0.7不影響排序指標但會影響閾值搜索。規(guī)則分數的主要作用是給后面的模型做參照驗證我們構造的特征是否超過了這個“常識線”。3. 特征工程把三十天行為日志壓縮成三類特征寬表特征工程是這個賽題真正的重頭戲。行為日志是典型的稀疏長表特征是“壓”出來的不是在原始表上直接學習。我習慣從三個方向分別壓用戶側、商品側、用戶-商品交叉?zhèn)茸詈笃闯梢粡垖挶?。主線思路是所有能在業(yè)務上解釋購買動機的行為都應該被數字特征顯式表達而不是讓模型自己從原始日志里找規(guī)律。窗口長度是第一個要決定的事。窗口太短統(tǒng)計不穩(wěn)定窗口太長容易混入過期信息。三十天數據我一般同時保留 7 天、15 天和全量 30 天三個窗口這樣模型既能捕捉近期意圖的突變又能保留長期偏好。3.1 時間窗口統(tǒng)計特征不同窗口長度為什么都要留下面這個函數按窗口天數做聚合對瀏覽、加購、關注、購買分別計數同時把行為天數、最近一次行為間隔等時間結構也記錄下來。生成三份不同窗口的特征再橫向拼接讓模型自己判斷哪個時間尺度更有效。def window_features(actions, window_days, date_ref): 對截止日期 date_ref 往前 window_days 天內的行為做統(tǒng)計 start date_ref - pd.Timedelta(dayswindow_days) mask (actions[action_time] start) (actions[action_time] date_ref) sub actions[mask] feat sub.groupby([user_id, item_id]).agg( view_cnt(action_type, lambda x: (x view).sum()), cart_cnt(action_type, lambda x: (x cart).sum()), fav_cnt(action_type, lambda x: (x fav).sum()), buy_cnt(action_type, lambda x: (x buy).sum()), action_days(action_time, lambda x: x.dt.date.nunique()), last_action_gap(action_time, lambda x: (date_ref - x.max()).days) ).reset_index() feat[action_total] feat[[view_cnt, cart_cnt, fav_cnt, buy_cnt]].sum(axis1) feat[interact_diversity] (feat[[view_cnt, cart_cnt, fav_cnt, buy_cnt]] 0).sum(axis1) feat.columns [user_id, item_id] [fw{window_days}_ c for c in feat.columns[2:]] return feat # 以資料中給定的預測截止日為例換成你的實際數據時間 date_ref pd.Timestamp(2023-05-15) feat_7 window_features(actions, 7, date_ref) feat_15 window_features(actions, 15, date_ref) feat_30 window_features(actions, 30, date_ref) # 合并成寬表 wide feat_7.merge(feat_15, on[user_id, item_id], howouter) wide wide.merge(feat_30, on[user_id, item_id], howouter) print(wide.shape)邏輯說明這個函數把行為日志按用戶-商品對壓縮成統(tǒng)計量每個窗口都生成瀏覽數、加購數、關注數、購買數、行為天數、最后一次行為距截止日的間隔、行為總量和交互多樣性。其中l(wèi)ast_action_gap特別有用用戶三天前還在反復看某商品說明注意力還在最后一次行為是二十天前意向大概率已經涼了。interact_diversity用來區(qū)分“被動刷到一次”和“多行為類型反復觸發(fā)”后者的購買意愿明顯更強。參數說明窗口天數必須和賽題時間邊界對齊。30 天數據里只做 7 天窗口特征會偏稀疏但短期意向信號更強烈。三個窗口都保留而不是只用一個是為了讓模型自己決定在什么時間尺度上做判斷后續(xù)用 feature importance 排查也無從下手。3.2 用戶-商品交叉特征加購未購和瀏覽轉加購率交叉特征把用戶、商品兩側的信息放在同一個對里計算目的是捕捉關系型信號。最有代表性的兩個特征是“加購未購”和“瀏覽轉加購率”。加購未購的含義很直白用戶把商品放進購物車但始終沒有下單放到真實業(yè)務里就是強烈購買意圖但還在猶豫模型拿到這個信號后可以直接把分數拉高。瀏覽轉加購率則區(qū)分深度瀏覽和淺層瀏覽看五次才加購和看一次就加購意圖強度完全不同。# 接上一節(jié) wide 表 wide[cart_not_buy] (wide[w30_cart_cnt] 0) (wide[w30_buy_cnt] 0) wide[view_to_cart_rate] wide[w30_cart_cnt] / (wide[w30_view_cnt] 1) # 距離上次加購的時間間隔加購動作離預測截止日越近意向越新鮮 last_cart_time actions[actions[action_type] cart].groupby([user_id, item_id])[action_time].max() last_cart_gap (date_ref - last_cart_time).dt.days.rename(last_cart_gap) wide wide.merge(last_cart_gap, on[user_id, item_id], howleft) wide[last_cart_gap] wide[last_cart_gap].fillna(999) # 粗略檢查新特征的分布 print(wide[[cart_not_buy, view_to_cart_rate, last_cart_gap]].describe())邏輯說明cart_not_buy直接表達“加購未購”這個業(yè)務強信號在成交預測里屬于少數高區(qū)分度特征。view_to_cart_rate分母加 1 是為了防止除零同時也讓只有瀏覽沒有加購的樣本得到一個接近 0 的小值而不是 NaN。last_cart_gap用 999 填充空值是為了讓模型能區(qū)分“從未加購”和“加購了很久都沒買”兩類樣本避免缺失值在樹模型中被合并到錯誤的分支。參數說明999 這個填充值必須遠大于正常間隔正常加購間隔最多 30 天999 能把缺失值和最大間隔徹底拉開。如果你用 LightGBM缺失值本身可以保留為 NaN但填一個大值在特征重要性和排查階段更直觀不容易讓人誤以為這個字段沒參與分裂。3.3 商品維度特征熱度、價格帶與品牌集中度只從用戶側做特征模型會缺少對商品本身的理解。同一個用戶對不同商品的行為差異很大但加入商品屬性后模型就能把這種差異解釋清楚。商品側常用的特征有三類熱度、價格帶、品牌集中度。熱度可以用商品在全部用戶中的瀏覽次數排名來表示價格帶把商品價格分桶不同價格帶對應完全不同的決策周期品牌集中度衡量銷售是否高度集中于爆款集中度高時非爆款單品被直接購買的概率預期要保守。# 商品側特征 item_feat actions.groupby(item_id).agg( item_view_cnt(action_type, lambda x: (x view).sum()), item_cart_cnt(action_type, lambda x: (x cart).sum()), item_buy_cnt(action_type, lambda x: (x buy).sum()), item_user_cnt(user_id, nunique) ).reset_index() # 熱度分桶用 rank 而不是直接歸一化減少長尾影響 item_feat[item_hot_rank] item_feat[item_view_cnt].rank(pctTrue) # 和商品信息表合并構造價格帶 item_feat item_feat.merge(items[[item_id, price, brand_id, cate_id]], onitem_id, howleft) item_feat[price_level] pd.cut(item_feat[price], bins[0, 100, 500, 1000, np.inf], labels[0, 1, 2, 3]) # 品牌購買集中度品牌下 top1 商品購買量占品牌總購買量比例 brand_buy actions[actions[action_type] buy].merge(items[[item_id, brand_id]], onitem_id, howleft) brand_total brand_buy.groupby(brand_id)[item_id].count().rename(brand_buy_total) brand_top1_share brand_buy.groupby([brand_id, item_id])[action_type].count().rename(item_buy_in_brand) brand_top1_share brand_top1_share.groupby(brand_id).max() / brand_total # 簡化版 item_feat item_feat.merge(brand_total, onbrand_id, howleft) item_feat item_feat.merge(brand_top1_share, onbrand_id, howleft) print(item_feat.head())邏輯說明熱度用 rank 而不是原始瀏覽數是因為熱門商品的瀏覽量可能是冷門商品的上千倍直接歸一化會讓大部分商品被壓到接近 0。價格帶把連續(xù)價格離散化讓樹模型能清晰區(qū)分不同決策周期的品類。品牌集中度的計算里先統(tǒng)計每個品牌下每個商品的購買次數再用 top1 商品購買量除以品牌總購買量集中度高時非爆款商品的購買意向預測要更保守。參數說明價格分桶邊界不是固定的可以按數據分布調整。如果發(fā)現(xiàn)大部分商品價格集中在 0-100就要把第一檔再切細成 0-50、50-100。pd.cut的 labels 參數也不一定非用 0-3樹模型可以吃任意有順序關系的數值關鍵是分桶邊界要貼合商品真實價格分布。4. 建模與調參把 LightGBM 穩(wěn)定地用在高潛購買預測上特征寬表構造完成之后建模本身反而直接。我的習慣是先跑一個默認參數的 LightGBM看驗證集 AUC 和特征重要性再按順序調參。不要一開始就追求 SOTA先把流程完整跑通才有資格談優(yōu)化。4.1 模型選型表格數據上為什么優(yōu)先用 GBDT特征工程完成后樣本通常是幾千到幾十萬行的寬表特征是數值型和少量類別型的混合。這種結構下 GBDT 類模型幾乎總是比深度模型更容易拿到更好的 AUC。原因是表格數據里的特征交互可以靠樹的天然分裂機制表達不需要像神經網絡那樣手動設計 embedding 結構而且 GBDT 對特征尺度不敏感數值型特征直接丟進去就好。在幾個 GBDT 實現(xiàn)里我通常先用 LightGBM而不是默認 XGBoost。LightGBM 訓練速度快在這個量級能快速迭代特征它對類別特征有原生支持省掉大量手動編碼工作直方圖算法對內存也更友好。這不代表 XGBoost 不好只是起步效率上 LightGBM 更適合這種要頻繁試特征的賽題。4.2 樣本不均衡別急著對負樣本做極端下采樣購買意向預測里正負樣本比例可能達到 1:20 甚至更懸殊。遇到這種情況很多新手第一反應是把負樣本砍到和正樣本一樣多這是過度反應。GBDT 對不均衡的容忍度比想象中高直接使用全部樣本、只調整評估方式和閾值往往就能取得不錯的效果。如果正樣本實在太少我才會考慮兩種做法一是負樣本按比例下采樣比如保留負樣本的 20%-50%同時讓驗證集保持原始比例二是在 LightGBM 里設置scale_pos_weight本質上給正樣本的梯度乘以權重。兩者相比我更愿意先調scale_pos_weight而不是采掉大量負樣本畢竟保留數據就是保留信息。4.3 三個關鍵參數n_estimators、learning_rate、max_depth下面是最小可用的訓練與驗證流程。先固定 learning_rate 從 0.05 開始用 early stopping 決定樹的數量之后再去動樹的復雜度。import lightgbm as lgb from sklearn.model_selection import train_test_split from sklearn.metrics import roc_auc_score # X 是前面拼好的寬表特征y 是標簽1 表示成交 X wide.drop(columns[user_id, item_id, label]) y wide[label] # 按用戶切分避免同一用戶的數據同時出現(xiàn)在訓練和驗證里 X_tr, X_va, y_tr, y_va train_test_split(X, y, test_size0.2, random_state42) params { objective: binary, metric: auc, learning_rate: 0.05, max_depth: 6, num_leaves: 31, min_child_samples: 20, scale_pos_weight: (len(y_tr) - y_tr.sum()) / y_tr.sum(), verbosity: -1, seed: 42 } model lgb.train( params, lgb.Dataset(X_tr, y_tr), num_boost_round1000, valid_sets[lgb.Dataset(X_va, y_va)], callbacks[lgb.early_stopping(100), lgb.log_evaluation(50)] ) pred model.predict(X_va) print(驗證集 AUC:, roc_auc_score(y_va, pred))邏輯說明按用戶而不是按行切分驗證集是一個關鍵決策。購買行為在同一用戶內部高度相關如果同一用戶的一部分行為出現(xiàn)在訓練集、另一部分出現(xiàn)在驗證集指標會被嚴重高估。scale_pos_weight按訓練集實際正負比例計算比寫死一個數更穩(wěn)。early_stopping在驗證集 AUC 連續(xù) 100 輪不提升時停止既防止過擬合也不需要手動糾結樹的數量。參數說明常用體積明細如下表所示先固根基再動結構。參數作用建議起點learning_rate控制每棵樹貢獻的步長越小越穩(wěn)越慢0.05max_depth限制樹高防止單棵過深6num_leaves葉子數上限復雜度控制核心31min_child_samples葉子節(jié)點最小樣本數抑制噪聲分裂20scale_pos_weight正樣本權重緩解不平衡訓練集負/正比例調參順序建議先固定 learning_rate把num_leaves從 31 調到 63、127 看驗證集變化再回來把max_depth調成 8 或 -1最后觀察特征重要性砍掉重要性接近 0 的列重新訓練。這套順序能讓調整可解釋而不是同時動五六個參數最后連自己都不知道哪一項起了作用。5. 高潛購買意向預測的 5 個常見坑與排查思路這類賽題做幾輪之后真正決定水平的是避坑能力。以下五個坑按出現(xiàn)頻率排每條都按現(xiàn)象、原因、解決三段式寫清楚。5.1 坑驗證集 AUC 極高提交后分數暴跌現(xiàn)象本地驗證集 AUC 0.85 以上看起來很漂亮一換成測試集就掉到 0.7 附近。原因最常見的是數據泄露。行為日志里混進了預測日期之后的數據或者某些靜態(tài)字段本身就攜帶未來信息。另一種常見泄露是特征拼接時不小心把用戶-商品對在測試期的行為也算了進去。解決做一個固定動作把訓練集和測試集的日期邊界重新打印一遍再檢查所有特征的取值分布。具體做法是在建模前把一個時間截止日釘死所有特征只用截止日之前的數據如果某個特征使用全量數據聚合就要追問它有沒有引用未來的行為。5.2 坑同一用戶既在訓練集又在驗證集指標虛高現(xiàn)象線下 AUC 穩(wěn)定線上結果不如預期回頭檢查發(fā)現(xiàn)切分是按行隨機切而不是按用戶切。原因同一個用戶的多條行為天然相似隨機按行切分等于讓模型在訓練時見過驗證集用戶的習慣評估結果自然虛高。解決使用train_test_split時先按 user_id 分組或者直接用 GroupKFold 做按用戶的交叉驗證。電商場景里我還會額外加按時間切分用前 25 天行為構造特征預測后 5 天而不是在整個時間區(qū)間里隨機抽樣。5.3 坑特征分布訓練和測試不一致現(xiàn)象特征重要性里某個特征排第一但它只在訓練集里表現(xiàn)好在測試集里幾乎全為空或分布完全改變。原因常見于價格、庫存、促銷活動相關字段訓練期和測試期對應的商品池或時間點不同導致該特征在兩側覆蓋度不一致。解決特征構造完成后做一個分布對比腳本逐列輸出訓練和測試的缺失率、均值、分位數。如果某個特征在測試集缺失率超過 30%要么重新構造只用覆蓋率高的字段要么把缺失率本身做成特征讓模型學會在信息缺失時如何決策。5.4 坑AUC 尚可但 F1 上不去現(xiàn)象AUC 0.72 看著尚可但按閾值搜出來 F1 只有 0.15業(yè)務完全沒法用。原因正樣本比例太低AUC 主要靠大量負樣本的低分拉高模型對正樣本的召回嚴重不足。解決先不要調模型結構而是做兩件事。一是檢查正樣本里“加購未購”的覆蓋率如果正樣本連加購都沒有過說明特征方向要轉向商品側或用戶側更宏觀的行為二是單獨搜閾值而不是默認用 0.5。閾值搜索要在驗證集上做且驗證集的正負比例要和測試集一致否則搜出來的閾值不可遷移。5.5 坑同一個行為類型在不同時間窗里語義不同現(xiàn)象把 30 天內的全部行為匯總得出“瀏覽次數越多越可能買”的結論但實際情況是最后一周的瀏覽和第一周的瀏覽對購買的影響完全相反。原因用戶第一次瀏覽可能來自首頁推薦屬于被動曝光臨近購買前的再次瀏覽是主動搜索意圖強度差異很大。把不同時間窗的瀏覽壓成一個總和等于抹掉了行為節(jié)奏。解決不要只做總量特征。把行為拆成最近 3 天、最近 7 天、最近 15 天多個窗口分別統(tǒng)計讓模型判斷哪個窗口更重要。也可以再加一個“第一次行為距預測截止日間隔”字段用來刻畫行為周期的起點。6. 驗證與進階滾動時間窗、分桶檢查和提交前復盤模型和特征穩(wěn)定后最后一步不是直接提交而是做幾件驗證可靠性的工作滾動時間窗驗證、分桶排序檢查、特征重要性復盤。這三件事做完整個項目的完整度就有了后面迭代和寫說明資料都有明確依據。6.1 滾動時間窗驗證而不是隨機 K 折隨機 K 折沒有模擬真實的時間先后關系。真實場景里永遠是用歷史預測未來不可能讓模型看未來的數據。我會用滾動時間窗比如 30 天數據按前 25 天做特征、后 5 天做標簽再往前滾一段重新構造訓練集每段獨立訓練評估最后取均值。from sklearn.model_selection import TimeSeriesSplit # 用行為日志中每個用戶最后一次行為日期作為排序鍵 X[last_action_day] X[user_id].map( actions.groupby(user_id)[action_time].max().dt.date ) tscv TimeSeriesSplit(n_splits5) for train_idx, val_idx in tscv.split(X): X_tr, X_va X.iloc[train_idx], X.iloc[val_idx] # 驗證集時間必須晚于訓練集否則檢查排序邏輯 assert X_va[last_action_day].min() X_tr[last_action_day].max()邏輯說明TimeSeriesSplit按數據順序遞增地劃分不會把后面的數據放進訓練集。用最后一次行為日期作為排序鍵可以保證驗證集在時間上嚴格晚于訓練集。這個結構比隨機 K 折更有說服力因為測試集本來就在訓練集之后。參數說明n_splits5表示做 5 輪切分第一輪訓練集只有 20% 的數據越往后訓練數據越多。樣本量不大時可以跑滿 5 折幾十萬行數據時建議調成 3降低重復訓練的時間開銷。6.2 分桶看預測分數和實際轉化率AUC 只告訴你整體水平不告訴你模型在哪個業(yè)務切片上好用。我習慣做一張分組表現(xiàn)表按用戶所在城市級別、商品價格帶、行為活躍度分桶分別統(tǒng)計每個桶的樣本數、實際購買率和預測分數分位數。X[pred_score] model.predict(X[X.columns.difference([pred_score])]) X[pred_bucket] pd.qcut(X[pred_score], 5, labels[1, 2, 3, 4, 5]) result X.groupby(pred_bucket).agg( sample_cnt(pred_score, count), real_buy_rate(label, mean), pred_avg(pred_score, mean) ).reset_index() print(result)邏輯說明這一步看的是分數單調性——預測分最高的桶實際購買率應該明顯更高。如果第 4 桶的購買率比第 5 桶還高說明這個區(qū)間排序混亂需要回頭補特征。如果某個高價商品桶樣本很少但購買率極高說明部署時要額外加規(guī)則保護。參數說明pd.qcut按樣本量等分為 5 桶每個桶樣本數相同購買率對比更穩(wěn)定。改成 10 桶也可以但樣本少時高桶的統(tǒng)計波動會很大。5 桶起步是我反復試過更穩(wěn)的方案。6.3 提交前的一輪特征復盤建模到最后我會把重要性前 20 的特征逐條過一遍這個特征在業(yè)務上是否解釋得通它和購買意向的邏輯關系是什么。如果某個重要性極高的特征在業(yè)務上完全講不通就要警惕是不是數據泄露。這一輪復盤也是給項目說明準備素材把特征按用戶意圖、商品屬性、交叉行為三類整理每類挑兩個最重要的解釋業(yè)務含義再附上驗證集指標讀者就能理解你的決策過程而不是看到一個黑匣子分數。我習慣至少準備兩份結果一份按模型分數排序一份把加購未購等業(yè)務強信號的樣本權重上調后排序對比選擇更穩(wěn)的那份提交。這也是我做過幾輪購買意向預測之后沉淀下來的習慣不要只信一個模型也不要只信一個指標。希望這次的高潛用戶購買意向預測拆解能幫到你——從數據邊界到特征、從模型調參到坑位排查拿這套流程去對照你自己的數據集能少走很多彎路。本文還有配套的精品資源點擊獲取