值與文本混合特征工程實戰(zhàn):從清洗到模型融合全攻略)
做機器學習項目時我自己最怕遇到的一類數(shù)據(jù)就是“既有文本又有數(shù)值”的混合表格。比如用戶畫像里既有“年消費金額”這種數(shù)值列又有“商品評價內(nèi)容”這種自由文本做退款風險預測既有“訂單總價”、“歷史退款率”又有“客服工單描述”、“買家留言”。這類數(shù)據(jù)在真實業(yè)務(wù)里非常常見但大多數(shù)教程要么只講純數(shù)值表格怎么調(diào)LightGBM要么只講NLP文本怎么進BERT很少有人把“數(shù)值和文本怎么揉在一起做特征工程”講透。這篇內(nèi)容我打算用一整篇的篇幅把這套處理思路、工具鏈、實操路線和踩坑點都盤一遍。如果你想做大數(shù)據(jù)開發(fā)、數(shù)據(jù)挖掘或者準備大數(shù)據(jù)面試這個主題其實是高頻考點值得認真看。1. 混合特征難處理的根源在哪里先別急著調(diào)包。要處理混合特征首先要搞清楚一個問題為什么數(shù)值和文本拼在一起不像“把兩個DataFrame合并”那么簡單很多新人一上來就把文本用TF-IDF轉(zhuǎn)成稀疏矩陣再和數(shù)值列拼在一起丟進模型結(jié)果效果稀爛還找不到原因。其實問題不在模型而在特征本身的“底層邏輯”完全不一樣。1.1 數(shù)值與文本的底層數(shù)據(jù)性質(zhì)不同數(shù)值特征通常是稠密的、有序的、量綱統(tǒng)一的。比如訂單金額800元、歷史退款次數(shù)2次、用戶停留時長320秒這些數(shù)字本身就具備“可比較性”和“數(shù)學運算意義”。模型可以直接通過分裂點來捕捉“金額大于500時風險升高”這種規(guī)律連續(xù)特征還能做分箱、歸一化、多項式交叉都不會破壞它本身的語義。文本特征則完全不同。文本是離散的、稀疏的、無序的字符串單個詞匯必須經(jīng)過“向量化”才能被模型理解。把“退款好慢客服態(tài)度差”這句話轉(zhuǎn)成向量時不同分詞方式、不同詞典大小、不同權(quán)重算法產(chǎn)出的特征空間天差地別。哪怕同樣一句中文用jieba分詞和用字符級切分出來的結(jié)果可能完全不沾邊。而且文本的長度差異極大短的可能只有幾個字長的能有上千字這種“變長結(jié)構(gòu)”是純數(shù)值表格完全不具備的。也正是因為一個稠密連續(xù)、一個稀疏離散直接把兩者拼起來放進單一模型會讓模型在同一套分裂邏輯里同時處理兩種截然不同的分布形態(tài)。此時如果文本特征維度巨大、數(shù)值特征維度很小權(quán)重天然失衡模型很容易被文本側(cè)的高維稀疏信號帶偏而數(shù)值側(cè)真正有區(qū)分度的業(yè)務(wù)字段反而沒有被充分利用。1.2 真正的難點三類隱性矛盾在實際項目里混合特征真正的難點其實不只是“拼接”本身而是三類很容易被忽略的隱性矛盾。第一類是“尺度矛盾”。數(shù)值特征做標準化后基本都在0到1之間或者以均值為中心波動但TF-IDF的取值區(qū)間是0到某個正數(shù)而且大量是0少量是幾個點以上的浮點數(shù)兩者的分布形態(tài)差異極大。如果模型是神經(jīng)網(wǎng)絡(luò)或基于距離的算法如KMeans、KNN尺度不一致會造成嚴重的特征淹沒如果是樹模型雖然對尺度不敏感但大量稀疏維度會顯著拖慢訓練速度并降低分裂質(zhì)量。第二類是“語義鴻溝”。數(shù)值特征表達的語義是“多少、多快、多大”文本特征表達的語義是“發(fā)生了什么、用戶說了什么”。同樣是“退款”這個行為數(shù)值列可能只是退款1次、退款金額50元但文本評論里可能寫了“申請退款多次未果客服一直推諉”前者是行為事實后者是過程體驗。如果只靠數(shù)值側(cè)的“退款次數(shù)1”來預測風險根本捕捉不到用戶情緒和客服處理時效問題的深層信息。文本的價值恰恰在于它能補充數(shù)值記錄中沒有的過程細節(jié)。第三類是“樣本對齊問題”。一個用戶有10條文本評論、對應一條數(shù)值記錄怎么把10條文本聚合成一條文檔向量一個訂單里有5個商品標題、若干條客服備注怎么融合到訂單粒度去做二分類這種一對多、多對一、甚至多對多的粒度關(guān)系往往比“特征怎么設(shè)”更先決定成敗。2. 數(shù)值側(cè)預處理別讓基礎(chǔ)字段拖后腿數(shù)值特征雖然看起來簡單但在混合建模里反而是最容易“先入為主”出錯的地方。因為大家都覺得數(shù)值列不用處理直接讀進來就能用。實際上數(shù)值列的質(zhì)量直接影響文本側(cè)特征能不能發(fā)揮價值。畢竟模型最后是一個整體一側(cè)崩了另一側(cè)再強也會被拖累。2.1 先做業(yè)務(wù)口徑歸一再做統(tǒng)計變換很多初學者在處理數(shù)值特征時分不清“歸一化”和“統(tǒng)計變換”更不會去想“這個數(shù)值在本業(yè)務(wù)里到底代表什么”。第一步我建議先做業(yè)務(wù)口徑歸一。比如“消費金額”這個字段不同渠道傳入的口徑可能不一樣有的含運費有的不含有的是實付金額有的是商品原價“退款金額”有的是正數(shù)有的是負數(shù)。如果不把業(yè)務(wù)口徑拉齊后面的統(tǒng)計變換全是白費。做這一步時最好列一個字段口徑清單每列寫明單位、允許范圍、缺失值的業(yè)務(wù)含義是該用戶沒有記錄還是記錄丟失這個動作能在后面省下大量排查時間。第二步才是統(tǒng)計變換。我自己的習慣是先觀察數(shù)值分布形狀再決定用不用對數(shù)或Box-Cox變換。比如“用戶累計充值金額”這類偏態(tài)極大的長尾數(shù)據(jù)我一般會先做np.log1p變換因為直接喂原始金額模型的分裂點基本會被極少數(shù)大額用戶帶跑而大量普通用戶的金額差異在幾百元以內(nèi)反而區(qū)分不開。再比如“距上次登錄天數(shù)”這種本身就是“時間間隔”語義的字段我更傾向于直接保留原始天數(shù)同時構(gòu)造“間隔是否超過7天/30天”等業(yè)務(wù)閾值特征因為間隔本身的分界點在業(yè)務(wù)上是可解釋的。歸一化方面有一點要特別注意樹模型不做歸一化沒關(guān)系但一旦后續(xù)要把數(shù)值特征和文本向量同時送入神經(jīng)網(wǎng)絡(luò)或做距離計算必須統(tǒng)一做標準化或MinMax縮放。這正是混合建模里最容易埋雷的環(huán)節(jié)——文本側(cè)已經(jīng)做了TF-IDF歸一化數(shù)值側(cè)如果不做兩個空間就會歪掉。2.2 離散化與目標編碼的取舍數(shù)值特征在混合建模里另一個重要議題是離散化還是保持連續(xù)該不該用目標編碼從純樹模型角度來看連續(xù)數(shù)值會被自動尋找最優(yōu)切分點所以很多情況下我們不需要手動分箱。但手動分箱有一個不可替代的好處可以對缺失值單獨成箱、對異常值單獨成箱并且能讓模型捕捉“非線性區(qū)間效應”。比如“年齡”這個變量如果沒有業(yè)務(wù)經(jīng)驗直接喂連續(xù)值樹模型雖然能找切分點但切出來的區(qū)間可能不符合業(yè)務(wù)認知解釋性也不強。如果業(yè)務(wù)上已知“24歲以下和40歲以上退款風險高”可以先做出風險曲線再按曲線拐點分箱這樣每箱都有明確的業(yè)務(wù)標簽。目標編碼Target Encoding在混合特征場景里也非常好用因為表格數(shù)據(jù)往往有很少的類別列比如商品類目、店鋪ID、渠道來源等這些本質(zhì)上不是自由文本但可以和文本、數(shù)值構(gòu)成上下文。目標編碼就是用“該類別下的目標均值全局先驗平滑系數(shù)”替換類別本身能夠高效利用類別信息對樹模型尤其友好。不過要切記防止標簽泄漏目標編碼必須在訓練集內(nèi)用K-Fold方式計算均值再映射到驗證集和測試集不能在整份數(shù)據(jù)上直接groupby求均值。我這里給出一段典型的數(shù)值列清洗順序偽代碼能覆蓋大多數(shù)常規(guī)場景import pandas as pd import numpy as np def preprocess_numeric(df, num_cols): for col in num_cols: # 1. 缺失值填充業(yè)務(wù)無記錄填-999讓樹模型單獨成枝 df[col] df[col].fillna(-999) # 2. 異常值截斷超過99.9%分位的值縮到該分位 upper df[col].quantile(0.999) df[col] df[col].clip(upperupper) # 3. 長尾偏態(tài)分布做log變換 if df[col].skew() 2: df[col] np.log1p(df[col].clip(lower0)) # 4. 高頻類別如渠道等單獨做頻率編碼或目標編碼 return df這段代碼的關(guān)鍵在于第四步渠道、類目這類高基數(shù)類別列不要直接扔給模型做LabelEncoder因為LabelEncoder只是賦予任意整數(shù)編號樹模型會誤以為編號有大小關(guān)系。正確做法要么OneHot類別少時要么目標編碼或頻率編碼類別多時。3. 文本側(cè)表示與向量化方法選擇要跟場景走文本特征處理是混合建模里的重頭戲也往往是耗時最長的部分。文本側(cè)處理的目標不是“把文本轉(zhuǎn)成模型能讀的數(shù)字”就完了核心目標是讓文本特征成為數(shù)值特征的“語義補充”而不是“噪聲疊加”。我實際在項目中按數(shù)據(jù)規(guī)模和應用場景把文本處理分成三檔第一檔是統(tǒng)計型向量TF-IDF/BOW第二檔是詞向量聚合Word2Vec/Doc2Vec第三檔是預訓練語言模型Embedding。三檔方案的選型和調(diào)參思路下面分開說。3.1 TF-IDF是主力方案但關(guān)鍵在調(diào)參在很多真實業(yè)務(wù)場景中時間緊、標注少、文本短、還要可解釋性TF-IDF其實是最好的起點。它無需GPU、訓練極快、特征含義明確任何一個維度都能映射回原始詞項方便做錯誤分析。但TF-IDF“上限不高下限不低”很多人直接調(diào)用TfidfVectorizer默認參數(shù)效果平庸還不知道問題出在哪。我總結(jié)了一套相對穩(wěn)定的調(diào)法。首先一定要按業(yè)務(wù)先清洗文本。中文文本要去除特殊符號、HTML標簽、郵箱、網(wǎng)址、連續(xù)數(shù)字和無意義停用詞但不要過度清洗比如“退款”“客服”這種關(guān)鍵詞必須保留。切忌一開始就上完整停用詞表把所有高頻詞干掉因為很多文本在高頻詞里恰恰藏著業(yè)務(wù)信號。from sklearn.feature_extraction.text import TfidfVectorizer import jieba def cut_words(text): text re.sub(r[a-zA-Z0-9], , str(text)) words jieba.lcut(text) # 只保留長度2的詞去掉單字和空白 words [w.strip() for w in words if len(w.strip()) 2] return .join(words) tfidf TfidfVectorizer( token_patternr(?u)\b\w\b, max_features3000, min_df5, max_df0.6, sublinear_tfTrue, strip_accentsunicode, ngram_range(1, 2), ) X_tfidf tfidf.fit_transform(corpus)這里幾個參數(shù)的含義值得展開講一下。min_df5的意思是“一個詞至少在5條樣本中出現(xiàn)過否則丟棄”這能過濾掉只在一條樣本里出現(xiàn)的噪音詞。max_df0.6的意思是“一個詞如果在60%以上的樣本里都出現(xiàn)也丟棄”原因是這類詞基本是通用詞區(qū)分能力弱。max_features根據(jù)文本量級定我一般經(jīng)驗是數(shù)據(jù)量在幾萬條、文本短時設(shè)1000到2000足夠數(shù)據(jù)量在幾十萬條、文本較長時3000到5000比較穩(wěn)。max_features太大容易過擬合且拖慢訓練太小又可能丟棄有效詞。sublinear_tfTrue非常關(guān)鍵它用 1log(tf) 替代原始詞頻做子線性縮放能抑制那些在單條文檔中反復出現(xiàn)的詞的權(quán)重避免“某個詞出現(xiàn)10次和出現(xiàn)50次被模型視作10倍重要”的失真實際效果對短文本尤其明顯。ngram_range(1,2) 則是同時保留單個詞和相鄰雙詞。比如“客服態(tài)度差”如果不加bigram模型看到的是“客服”“態(tài)度”“差”三個孤立的詞“差”字很容易被其他場景的“差評”“差異”稀釋。加了bigram后“態(tài)度差”作為一個整體特征語義強度高出很多。不過bigram會讓維度飆升所以必須配合max_features做截斷。3.2 詞向量與預訓練模型的適用邊界那什么時候不用TF-IDF而要使用Word2Vec類方法或預訓練模型呢我的判斷標準很簡單如果文本是短文本、業(yè)務(wù)關(guān)鍵詞明確、需要快速迭代和可解釋性TF-IDF足夠如果文本是長文本、語義復雜、上下文依賴明顯或者數(shù)值側(cè)本身已經(jīng)包含很強結(jié)構(gòu)化信息而文本是唯一的信息增量來源那么詞向量或BERT類模型值得上。Word2Vec類方法最大的優(yōu)勢是把詞映射成稠密向量保留了部分語義關(guān)系比如“客服”和“售后”的向量距離會比較近。但用一個粗糙的Word2Vec直接去Mean Pooling整條文本效果往往不如TF-IDF好。原因是短文本里不同詞的重要性差異很大簡單平均會把“好的”“不錯”這類泛化詞和“漏水”“爆炸”這樣的強情感詞同等對待。我實測下來如果業(yè)務(wù)沒有那么復雜用TF-IDF做主特征再用Word2Vec做一個“語義相似度特征”作為輔助比如計算文本向量和“風險詞庫”向量的余弦相似度反而更容易帶來增量。至于BERT等預訓練模型適合文本較長、語義理解要求高、且有時間做微調(diào)的場景。但在大數(shù)據(jù)量、上千維稀疏特征混合建模的場景里我不建議直接把BERT輸出的768維向量和數(shù)值特征粗暴拼接后送進XGBoost因為BERT向量空間和TF-IDF空間的性質(zhì)差異太大拼接后解釋性極差也很難做特征篩選。我的折中做法是用BERT對每條文本抽出一個低維的Embedding比如通過句向量模型壓到128維或256維再和數(shù)值特征混合建?;蛘咧苯影袯ERT當作一個獨立模型輸出預測概率再用這個概率作為新特征加入主模型也就是做Stacking。這種“文本模型吐特征表格模型吃特征”的思路在很多競賽和業(yè)務(wù)項目里都更穩(wěn)定。4. 把兩類特征真正融到一起前面數(shù)值側(cè)和文本側(cè)各自預處理完畢后接下來的融合環(huán)節(jié)才是整篇內(nèi)容的核心。為什么很多項目做到這一步效果不升反降因為“融合”不只有一種方案而不同方案對數(shù)據(jù)和模型類型的適配性完全不同。我在項目里把融合方法分成四個層級從最基礎(chǔ)的拼接一路到深度模型結(jié)構(gòu)設(shè)計后面分別講清楚適用場景和細節(jié)。4.1 直接拼接是基線但要注意稀疏度第一種方案也是最基礎(chǔ)的方案——直接橫向拼接。用scipy.sparse.hstack把數(shù)值特征矩陣和TF-IDF稀疏矩陣拼在一起再丟給LightGBM或XGBoost。from scipy.sparse import hstack X_all hstack([X_numeric_scaled, X_tfidf]).tocsr()這是最省事的做法但有幾個問題必須處理。首先是稀疏度問題。TF-IDF矩陣通常稀疏度在90%以上而數(shù)值列拼進去后是稠密的。樹模型在訓練時遍歷特征維度如果稀疏特征占絕大多數(shù)分裂點搜索時大量時間花在“判斷是否為0”上訓練速度會明顯下降。解決方案是控制文本維度我一般把max_features控制在3000以內(nèi)并且確保稀疏矩陣以CSR格式存儲訓練時算法庫對CSR的遍歷優(yōu)化會好很多。其次是特征重要性的誤導。在拼接后的XGBoost特征重要性里TF-IDF的幾千個維度會占據(jù)多數(shù)表面看“文本特征貢獻最大”但實際上可能是文本特征維度多、單維重要性低總和虛高。反觀2到3個數(shù)值特征雖然每次分裂都能帶來很大的Information Gain卻因為維度少累積重要性反而偏低。因此評估模型時不要只看gain型重要性還要結(jié)合shap值或permutation importance。4.2 比拼接更穩(wěn)的做法分組建模與交叉特征直接拼接的替代方案是“分組建模”。將數(shù)值特征、文本特征分別訓練兩個模型比如一個LightGBM只吃數(shù)值列一個LogisticRegression或者LightGBM只吃TF-IDF再把兩個模型輸出的預測概率作為新特征放入第二層模型融合。這種做法在Kaggle和實際業(yè)務(wù)中都非常有效好處有三個第一避免高維文本淹沒低維數(shù)值第二兩個子模型可以用各自最擅長的預處理方式不必強行統(tǒng)一尺度第三第二層模型能學到“文本側(cè)概率高但數(shù)值側(cè)概率低”這種非線性關(guān)系。數(shù)值分組建模的另一個重要拓展是構(gòu)造文本與數(shù)值的交叉特征。我舉一個實際業(yè)務(wù)案例電商客服工單里用戶輸入文本中出現(xiàn)了“發(fā)票”二字同時訂單金額字段大于5000元那么大概率是B端企業(yè)客戶需要專票報銷這個組合信號比單獨看任意一側(cè)都強。具體做法是先用文本分類或關(guān)鍵詞規(guī)則給文本打一個“意圖標簽”比如“發(fā)票問題”、“物流問題”、“退款問題”然后做“意圖標簽×金額分箱”的交叉特征。如果想讓模型自己學交叉還可以直接把文本規(guī)則篩選出的樣本ID作為一個二值特征和數(shù)值列一起進樹模型讓樹模型自動去找交叉切分點。# 交叉特征示例意圖標簽從文本中提取再與數(shù)值分箱做交叉 df[intent] df[text].apply(lambda t: detect_intent(t)) # 關(guān)鍵詞/分類模型均可 df[amount_bucket] pd.cut(df[order_amount], bins[0, 200, 500, 1000, np.inf], labels[0-200, 200-500, 500-1000, 1000]) df[intent_amount_cross] df[intent].astype(str) _ df[amount_bucket].astype(str)這種交叉特征通常能帶來非常顯著的AUC提升遠高于直接堆更多TF-IDF維度。4.3 時間窗口類特征文本與數(shù)值混合的高頻場景金融、風控、電商場景里時間窗口類文本數(shù)值混合特征是另一個大頭。比如歷史上每個月的文本評價量、近7天客服投訴關(guān)鍵詞出現(xiàn)次數(shù)、近30天平均退款金額變化率等。這些特征本質(zhì)上已經(jīng)屬于“時序聚合”范疇處理起來有幾條特別容易踩坑的經(jīng)驗。時間窗口的計算必須在“樣本時間點之前”截止嚴禁使用未來數(shù)據(jù)這是時序建模的絕對鐵律。我用自己的親身教訓舉個例當時做用戶流失預警用某個用戶30天內(nèi)工單文本里的投訴關(guān)鍵詞數(shù)量做特征因為計算腳本在跑全量ETL時沒有按時間過濾導致驗證集里包含了未來信息離線AUC做到了0.87上線后線上AUC直接跌到0.71。后來排查半天才發(fā)現(xiàn)特征本身存在時間穿越。處理這類特征的正確做法是在訓練集的每個樣本上用“以該樣本的截止時間向前回溯窗口”不是先聚好一張全量大寬表再劃分訓練集和測試集。時間窗口通常取多個長度比如近7天、近14天、近30天因為業(yè)務(wù)上不同行為的影響時效不同。文本側(cè)可以聚合出“窗口內(nèi)負面詞數(shù)”、“窗口內(nèi)平均文本長度”、“窗口內(nèi)意圖類別熵”等高層變量再和數(shù)值側(cè)的“窗口內(nèi)消費總額”、“最近一次交互間隔”做時間衰減加權(quán)。因為文本大數(shù)據(jù)量聚合計算量大一般建議先在離線節(jié)流側(cè)把時間分桶、用戶分組、關(guān)鍵詞分類都做好索引。5. 實操記錄電商退款預測中的混合特征全流程理論講了不少下面用一個可落地的業(yè)務(wù)場景走一遍全流程。場景是電商平臺的退款風險預測任務(wù)預測目標是“用戶下單后7天內(nèi)是否申請退款二分類”。數(shù)據(jù)布局大概是每行一個用戶數(shù)值列有歷史訂單數(shù)、歷史退款數(shù)、近30天登錄次數(shù)、客單價等文本列是用戶最近的客服投訴描述長度從幾個字到幾百字不等而且不是每個人都有描述很多人這一欄是空的。這是一個相當?shù)湫偷摹拔谋九c數(shù)值混合特征”場景。5.1 數(shù)據(jù)觀察與指標設(shè)定我先做數(shù)據(jù)抽樣質(zhì)檢發(fā)現(xiàn)文本列空值率約為42%。這非常關(guān)鍵因為如果直接把這列文本處理成“空字符串”TF-IDF會把空字符串給一個全零向量但如果42%都是全零行會造成特征矩陣底部堆積大量零向量影響后續(xù)的稀疏計算和模型學習。于是我構(gòu)造了一個二值列“is_text_present”標記是否有文本記錄這個簡單特征本身往往也有很強的業(yè)務(wù)含義因為會留文字投訴的用戶大概率比沉默用戶的退款率更高。業(yè)務(wù)指標選什么也很重要。退款預測這種正負樣本不平衡任務(wù)正樣本大約只占15%AUC是主要參考指標但做特征工程和閾值決策時還要兼顧召回率和精確率。因為業(yè)務(wù)核心是想提前盯住高風險用戶寧可有部分誤報也不要漏掉真正會退款的人所以線下評估時我通常會同時觀察AUC、召回率Precision0.5以及KS值多個角度驗證特征有沒有帶來真正的業(yè)務(wù)增量。5.2 特征構(gòu)建完整流水線整條特征構(gòu)建流水線我是這樣設(shè)計的拆成四個模塊模塊一是“用戶靜態(tài)數(shù)值特征”。包括歷史訂單數(shù)、歷史退款數(shù)、退款率、平均訂單金額、近30天登錄天數(shù)等。退款率我做了平滑處理退款率歷史退款數(shù)1/歷史訂單數(shù)5避免新用戶訂單少時退款率為0%或100%的極端表現(xiàn)。平均訂單金額單獨保留同時增加一條“用戶最高訂單額是否大于500”的布爾特征。模塊二是“客服投訴文本特征”。先對文本做分詞清洗再計算每條描述的TF-IDF。max_features設(shè)為2000min_df設(shè)為5max_df設(shè)為0.6。同時算了幾個文本側(cè)統(tǒng)計特征文本長度、句子數(shù)、負面詞數(shù)量基于自建負面詞典、“退款”“賠償”等關(guān)鍵詞數(shù)量。因為TF-IDF維度只有2000不會造成過大的稀疏壓力這部分可以直接參與拼接。模塊三是“交叉與目標編碼特征”。把“是否投訴過退款”與“歷史退款率分箱”做交叉把“商品是否有售后問題關(guān)鍵詞”的布爾值作為單列加入。另外我針對“最近一次投訴文本”做了目標編碼基于K-Fold計算每個投訴關(guān)鍵詞組合下的平均退款率。模塊四是“模型輸入拼接”。用ColumnTransformer把數(shù)值列做StandardScaler、把TF-IDF保持稀疏再用scipy.sparse.hstack拼接成總特征矩陣最后喂給LightGBM。from sklearn.preprocessing import StandardScaler from sklearn.pipeline import Pipeline from sklearn.compose import ColumnTransformer from sklearn.feature_extraction.text import TfidfVectorizer import lightgbm as lgb from scipy.sparse import hstack num_cols [history_order, history_refund, avg_order_amount, login_days_30] text_col complaint_text preprocessor ColumnTransformer( transformers[ (num, StandardScaler(), num_cols), (txt, TfidfVectorizer(max_features2000, min_df5, max_df0.6, sublinear_tfTrue, ngram_range(1, 2)), text_col), ], remainderdrop ) X_trans preprocessor.fit_transform(df) y df[is_refund].values model lgb.LGBMClassifier( n_estimators500, learning_rate0.03, num_leaves31, max_depth6, subsample0.8, colsample_bytree0.8, reg_alpha0.1, reg_lambda0.1, ) model.fit(X_trans, y, eval_set[(X_trans, y)], eval_metricauc)5.3 實驗對比三組方案的差異為了驗證混合特征方案是否真的有效我同一份數(shù)據(jù)上跑了三組實驗做對照。第一組只使用數(shù)值特征不做任何文本處理。第二組只使用文本TF-IDF不看數(shù)值列。第三組按上面完整流水線拼接了數(shù)值與文本特征。線下AUC結(jié)果分別大約為0.712、0.745和0.823。文本特征單用確實比數(shù)值強畢竟投訴描述里包含大量直接業(yè)務(wù)信號但第三組的混合拼接提升更大比只拼文本特征提升了約7.8個AUC點。隨后我又做了消融實驗在第三組的基礎(chǔ)上去掉“歷史退款率與投訴詞交叉特征”AUC跌回0.801去掉文本的is_text_present布爾列AUC再跌0.005。這些細微變化說明混合特征的價值不是某一列突然帶來的而是“多組信號交叉覆蓋”的結(jié)果。沒有交叉特征的純拼接文本側(cè)和數(shù)值側(cè)只是并排放置缺少讓模型把兩邊信號“聯(lián)動”起來的橋梁效果提升就會受限。6. 實際項目中最常見的幾個坑與排查方法混合特征處理項目里特征本身往往不是最大的坑能成功跑通流程之后真正耗時的是“排查問題”。我把自己實踐過程中反復踩過的幾類問題和排查思路整理成清單這些內(nèi)容在常規(guī)文檔里都很難找到屬于做完幾個項目后才慢慢沉淀下來的經(jīng)驗。6.1 數(shù)據(jù)泄漏陷阱清洗和編碼放錯了時間點典型錯誤包括在全量數(shù)據(jù)上用TF-IDF的fit_transform后再切訓練集和驗證集或者用全量數(shù)據(jù)計算目標編碼均值。這種全局fit操作會把驗證集和測試集的信息帶進訓練過程導致離線指標虛高。正確做法是在Pipeline內(nèi)部完成fit或者手動先切分數(shù)據(jù)再用訓練集fit、驗證集只做transform。一個簡單的檢查方法是在特征工程完成后隨機抽取一條驗證集樣本看它的TF-IDF維度權(quán)重和全量fit出來的權(quán)重是否完全一致如果不一致說明transform鏈路有問題。6.2 詞表不一致線上線下的隱藏殺手另一個非常隱蔽的坑是詞表不一致。我遇到過這樣的情況離線訓練時用的是全量數(shù)據(jù)分詞得到的詞典包含文本里的縮寫、表情符號、新詞線上服務(wù)時由于前端清洗邏輯把某些字符轉(zhuǎn)掉了導致線上文本經(jīng)過詞表映射后大量詞匯變成了OOV特征分布和離線完全不一樣。最典型的案例是離線把“000”視為無意義噪聲去除線上卻保留了“000”并映射到某個維度甚至語義完全變了。解決辦法是提前固定清洗規(guī)則和詞表一般通過joblib保存TfidfVectorizer對象線上加載同一個對象做transform。如果有增量更新需求也要在離線流程中嚴格走詞表版本管理。6.3 多值文本融合不當如果一個用戶可以有多條評論不能直接把所有評論拼接成一個超長文本丟進TF-IDF因為長文本的詞頻統(tǒng)計會被長度主導而且不同數(shù)量評論的樣本間文本長度差距極大。我在項目里常用的做法是分別計算每條評論文本的TF-IDF然后做均值池化或者對文本進行截斷每個用戶只保留最近2條評論拼接。但截斷策略要結(jié)合業(yè)務(wù)如果評論包含售后過程和最終解決反饋較早那條可能包含問題產(chǎn)生原因最近一條包含最終結(jié)果兩條都很重要。更穩(wěn)健的折中方案是對用戶的所有評論文本做“先拼接但按條數(shù)歸一化”同時附加“評論條數(shù)”作為數(shù)值特征。6.4 超大數(shù)據(jù)量下的性能壓力最后補一個工程向的坑。幾十萬甚至千萬級數(shù)據(jù)量做TF-IDF的fit_transform過程默認會非常占用內(nèi)存因為sklearn會先構(gòu)建完整的詞匯表再轉(zhuǎn)換。如果有幾百萬條長文本即便max_features設(shè)2000分詞階段的內(nèi)存峰值也可能很高。實測中我會先用jieba分詞并落盤成parquet格式再抽一個小樣本來fit詞表最后用已經(jīng)fit好的詞表對整個大數(shù)據(jù)集做transform并把過程拆成多個批次。同時TF-IDF轉(zhuǎn)換的結(jié)果一定用scipy.sparse.csr_matrix保存不要轉(zhuǎn)成numpy數(shù)組否則幾百萬文本乘以2000維會直接把你機器的內(nèi)存打爆。7. 從快速實現(xiàn)到穩(wěn)定落地的一些補充經(jīng)驗聊了這么多具體方法和坑點最后補充一些我自己的個人經(jīng)驗尤其是當你把混合特征方案放到更大的工程體系里時需要注意的細節(jié)。7.1 關(guān)于列名管理在混合特征項目里特征列名一定不能靠記住“第幾列是哪個”來管理。數(shù)字特征經(jīng)過縮放后拼上txt前綴再追加到稀疏矩陣里時和原本的文本列名、原數(shù)字列名已經(jīng)形成了很長的列名串聯(lián)。如果你要用shap解釋模型、做錯誤分析沒有好用的列名映射會崩潰。我的習慣是保存一個“列名列表”文件包含模型輸入時的全部列名尤其對文本側(cè)每個TF-IDF維度標注出它對應的原始詞。這樣在分析某個特征造成誤判時能直接找到文本內(nèi)容和權(quán)重來源調(diào)試效率高很多。7.2 先設(shè)一個快速的弱基線不要一開始就把所有特征工程做完再訓練模型。我會先跑一個“只用數(shù)值列”的LightGBM基線然后很快跑一個“只用文本TF-IDF”的基線最后再做拼接和交叉。這樣如果完整方案的性能不如最弱基線能快速定位問題在“融合方式”還是“單側(cè)特征構(gòu)造”上。具體來說第一版跑通以后先看錯誤樣本里文本和數(shù)值各自的貢獻等到所有方案性能都提升到穩(wěn)定區(qū)段再回頭給文本加更多語義特征。7.3 文本側(cè)不是模型越多越好最后一句話文本特征處理方案永遠是“簡單方案先搞通復雜方案按需上”。在真實業(yè)務(wù)場景中時間成本、線上延遲、可解釋性往往比模型精度更值錢。如果一個TF-IDF加手工交叉特征已經(jīng)讓AUC從0.71升到0.82再加BERT反而可能只提升0.005卻讓線上推理時間翻了十倍那這筆賬要好好衡量。根據(jù)我個人的經(jīng)驗文本與數(shù)值混合特征項目的成功關(guān)鍵通常在“兩側(cè)特征如何交互補充”而不在于用了多厲害的文本模型。把數(shù)值列吃透、把文本意圖抽準、把時間窗口算對往往比盲目套用深度模型更有效。這篇文章里我的核心思路是從工程實踐出發(fā)盡量把文本與數(shù)值混合特征從清洗、編碼到模型融合的關(guān)鍵環(huán)節(jié)都過了一遍。不管你是剛開始接觸大數(shù)據(jù)特征工程還是在準備特征工程方向的大數(shù)據(jù)面試題我相信這些方法和案例都能給到一些可以落地的參考。以后你有機會拿到真實的混合特征數(shù)據(jù)可以先用我給的整套流程快速出第一版結(jié)果再基于具體業(yè)務(wù)調(diào)細節(jié)這個方向會比你想象中容易出成績。