據(jù)聚類分析實(shí)戰(zhàn):從K-Means到DBSCAN的算法選型與工程落地)
“大數(shù)據(jù)領(lǐng)域數(shù)據(jù)挖掘的聚類分析方法應(yīng)用”——這句話我這些年被問到的頻率基本上能排進(jìn)數(shù)據(jù)挖掘方向的前三名。盡管聚類本身是個(gè)老概念但放到大數(shù)據(jù)的環(huán)境里它的復(fù)雜性、工程難度和業(yè)務(wù)價(jià)值跟教科書里講的是兩碼事。很多朋友一上來就摳K-Means的公式糾結(jié)質(zhì)心怎么更新可一到真業(yè)務(wù)里立刻卡殼幾百萬行數(shù)據(jù)怎么跑怎么選K聚類結(jié)果怎么解釋為什么有時(shí)候效果特別差這篇文章不是講數(shù)學(xué)推導(dǎo)的而是從這些年我實(shí)際做過的大數(shù)據(jù)項(xiàng)目里把有關(guān)聚類的完整思路、算法選型、工程落地和踩坑過程一次性攤開講透。內(nèi)容適合正在做用戶分群、異常檢測(cè)、畫像分析、數(shù)據(jù)治理方向的數(shù)據(jù)分析和算法初階到中階讀者也適合剛接觸數(shù)據(jù)挖掘、想弄清楚“聚類到底能解決什么問題”的人。1. 為什么在大數(shù)據(jù)項(xiàng)目里你早晚需要一輪聚類分析1.1 聚類的真實(shí)定位它和分類有什么本質(zhì)差別很多人把聚類和分類混為一談這是第一個(gè)認(rèn)知誤區(qū)。分類是有標(biāo)簽的學(xué)習(xí)你需要提前準(zhǔn)備好“好人”“壞人”“高價(jià)值”“普通用戶”這些標(biāo)記然后用這些標(biāo)記去訓(xùn)練模型再用模型去預(yù)測(cè)新樣本。聚類不是這樣聚類手里的數(shù)據(jù)通常沒有任何標(biāo)簽它要回答的問題是這群樣本到底可以天然地分成幾個(gè)“山頭”。我習(xí)慣把聚類理解成一種“找結(jié)構(gòu)”的工具。比如你拿到一份幾十萬條的用戶行為數(shù)據(jù)每條數(shù)據(jù)都記錄了訪問次數(shù)、停留時(shí)長(zhǎng)、消費(fèi)金額你根本不知道用戶到底分幾類也不想憑空定義“高價(jià)值用戶”的標(biāo)準(zhǔn)。這時(shí)候聚類就能幫你從數(shù)據(jù)自身的分布出發(fā)把相似的人聚到一起得出幾個(gè)自然的群體。聚類給你的是“發(fā)現(xiàn)”而不是“驗(yàn)證”。放到大數(shù)據(jù)場(chǎng)景里這個(gè)“發(fā)現(xiàn)”特性變得極其重要。數(shù)據(jù)量大到你根本看不過來無法靠人工規(guī)則一批一批篩選而聚類可以自動(dòng)完成粗分類把你的后續(xù)精細(xì)化運(yùn)營(yíng)、風(fēng)險(xiǎn)排查、內(nèi)容推薦全部引導(dǎo)到正確的起點(diǎn)上。我在實(shí)際項(xiàng)目里常跟業(yè)務(wù)方講一句話“聚類做的是把一灘渾水變成幾杯清水抗渾水的過程可能痛苦但結(jié)果幾乎一定會(huì)影響后面所有環(huán)節(jié)的判斷。”1.2 大數(shù)據(jù)場(chǎng)景給聚類提了哪些新難題傳統(tǒng)教材里演示聚類一般就是用幾百個(gè)樣本畫個(gè)散點(diǎn)圖然后運(yùn)行一遍算法看結(jié)果。實(shí)際大數(shù)據(jù)業(yè)務(wù)完全不是這個(gè)畫風(fēng)。你面對(duì)的往往是幾千萬條數(shù)據(jù)、幾百個(gè)字段、海量缺失值和高基數(shù)類別特征這些問題疊加起來會(huì)讓教材里干干凈凈的算法“原地爆炸”。我總結(jié)過大數(shù)據(jù)環(huán)境下聚類分析最常見的四類挑戰(zhàn)計(jì)算規(guī)模數(shù)據(jù)量一旦到了千萬級(jí)普通的單機(jī)構(gòu)建相似度矩陣或反復(fù)計(jì)算距離內(nèi)存和算力都吃不消。比如層次聚類需要維護(hù)一個(gè)樣本與樣本的距離矩陣這在大數(shù)據(jù)場(chǎng)景里幾乎無法落地。維度災(zāi)難字段特別多時(shí)所有樣本在高維空間里都會(huì)變得“彼此差不多”距離趨于集中聚類很難跳出有明顯物理意義的簇。噪聲和缺失業(yè)務(wù)數(shù)據(jù)不是實(shí)驗(yàn)室數(shù)據(jù)臟數(shù)據(jù)、異常值、缺失值是常態(tài)。K-Means這類基于均值的算法對(duì)噪聲非常敏感一個(gè)離群點(diǎn)就可能把整個(gè)質(zhì)心帶偏。結(jié)果解釋與落地算法給你吐出一個(gè)“標(biāo)簽列”但如果業(yè)務(wù)方看不懂這個(gè)標(biāo)簽代表什么聚類就只是個(gè)技術(shù)玩具。所以做大數(shù)據(jù)聚類不能只會(huì)調(diào)包。你得先有對(duì)業(yè)務(wù)的判斷力再會(huì)做特征工程還需要有能力在分布式環(huán)境里把算法跑起來最后能把數(shù)字結(jié)果翻譯成業(yè)務(wù)動(dòng)作。四個(gè)能力缺一個(gè)項(xiàng)目最后基本都是“做完了但沒意義”。2. 幾種主流聚類算法到底怎么選從K-Means到DBSCAN2.1 K-Means繞不開的基礎(chǔ)款但別盲目迷信K-Means大概是聚類算法里知名度最高、代碼最容易寫的一個(gè)。它的基本思想很簡(jiǎn)單給定一個(gè)K值先隨機(jī)初始化K個(gè)質(zhì)心然后把每個(gè)樣本劃給距離最近的質(zhì)心接著重新計(jì)算每個(gè)簇的質(zhì)心再迭代一次直到質(zhì)心不再變化。整個(gè)過程像反復(fù)整理幾個(gè)倉(cāng)庫(kù)的貨物位置多試幾次貨自然就分類放好了。我在很多項(xiàng)目里仍然首選K-Means因?yàn)樗诖髷?shù)據(jù)場(chǎng)景下具有良好的可伸縮性訓(xùn)練速度快解釋起來也容易。但它的缺點(diǎn)同樣明顯第一需要預(yù)先指定K值第二只能用凸形簇如果數(shù)據(jù)分布呈彎月形或者環(huán)狀K-Means基本無能為力第三它對(duì)初始質(zhì)心很敏感選不好會(huì)陷入局部最優(yōu)第四它是基于歐氏距離的最小化損失對(duì)異常值非常敏感。針對(duì)K-Means自己項(xiàng)目里幾個(gè)經(jīng)典規(guī)避方法用K-Means初始化質(zhì)心降低隨機(jī)初始化帶來的偏差。能跑多個(gè)隨機(jī)種子的話就別省這個(gè)資源交叉對(duì)比結(jié)果選一個(gè)穩(wěn)定解。如果數(shù)據(jù)里有明顯離群點(diǎn)要么先做離群處理要么直接換抗噪能力更強(qiáng)的算法。2.2 DBSCAN不被“均值”淹沒的密度視角如果說K-Means是“距離視角”的聚類DBSCAN就是“密度視角”的聚類。它不看均值和質(zhì)心而是看每個(gè)樣本周圍是否有足夠數(shù)量的鄰居。一個(gè)樣本半徑Eps范圍內(nèi)如果至少有MinPts個(gè)鄰居它就被視作核心點(diǎn)互相能到達(dá)的核心點(diǎn)會(huì)連成一片形成一個(gè)任意形狀的簇而那些離所有核心點(diǎn)都遠(yuǎn)、又?jǐn)D不進(jìn)去的樣本會(huì)被直接判定為噪聲。這幾點(diǎn)讓DBSCAN在實(shí)際業(yè)務(wù)里非常討喜它不需要預(yù)先指定K值能處理不規(guī)則的簇形狀還能順手把噪聲點(diǎn)給識(shí)別出來。我做過一個(gè)不少交易反作弊相關(guān)的挖掘項(xiàng)目里面很多時(shí)間序列特征天然就長(zhǎng)成奇怪的形狀用K-Means根本聚不出有意義的群體DBSCAN反而把正常用戶、機(jī)器行為、異常交易自動(dòng)分開效果很直觀。DBSCAN也不是沒有毛病。它最大的問題在于參數(shù)敏感Eps和MinPts這兩個(gè)指標(biāo)稍一變聚類結(jié)果可能有天壤之別。數(shù)據(jù)量很大時(shí)基于密度的計(jì)算對(duì)索引結(jié)構(gòu)要求也高但沒有優(yōu)化好的實(shí)現(xiàn)也很容易變成“跑得慢”。如果業(yè)務(wù)數(shù)據(jù)是幾千個(gè)高維特征又沒做過有效降維DBSCAN在參數(shù)選擇上會(huì)把你折磨到懷疑人生。2.3 層次聚類和高斯混合什么時(shí)候能派上用場(chǎng)層次聚類在教材里被講得很多但實(shí)際操作中我?guī)缀醪粫?huì)在大數(shù)據(jù)量級(jí)上直接用它。因?yàn)樗S護(hù)樣本間距離矩陣復(fù)雜度至少是O(n2)n到幾萬就相當(dāng)吃力。不過當(dāng)你做的小批量樣本分析需要展示從下往上的合并過程或者想畫一張樹狀圖來輔助判斷K值時(shí)層次聚類仍然是很好的可視化選手。我在做特征篩選時(shí)偶爾會(huì)用它對(duì)變量而不是對(duì)樣本聚類那幾千個(gè)變量還是撐得住的。高斯混合模型GMM跟K-Means比核心區(qū)別在于它是一種軟聚類。K-Means給出的結(jié)論是非此即彼你屬于A簇就不能屬于B簇GMM會(huì)告訴你“你屬于A簇的概率是0.7屬于B簇的概率是0.3”。這種概率輸出在業(yè)務(wù)上非常有價(jià)值比如做客戶分群時(shí)有些人的行為天然就是“一半像白領(lǐng)一半像自由職業(yè)者”硬性切分反而失真。GMM的計(jì)算量也比K-Means大而且對(duì)數(shù)據(jù)分布有較強(qiáng)假設(shè)如果數(shù)據(jù)不是真正的高斯分布效果可能不如樸素方法。我的經(jīng)驗(yàn)是常規(guī)場(chǎng)景先用K-Means打底業(yè)務(wù)需要概率輸出時(shí)再升級(jí)到GMM不要一上來就把復(fù)雜度拉滿。2.4 一張表說清大數(shù)據(jù)場(chǎng)景下的算法選型為了讓你在實(shí)際項(xiàng)目里快速鎖定方向我整理了一個(gè)算法選型對(duì)照表。這個(gè)表不是說哪個(gè)算法“最好”而是基于我遇到的典型大數(shù)據(jù)場(chǎng)景把各自的匹配度列出來。算法是否需要預(yù)設(shè)K值能否處理任意形狀抗噪聲能力可擴(kuò)展性大數(shù)據(jù)下典型用途K-Means需要較差主要凸形簇較弱很好適合分布式大規(guī)模樣本粗分、用戶分群DBSCAN不需要能較強(qiáng)依賴索引較大規(guī)模時(shí)偏慢異常檢測(cè)、不規(guī)則簇識(shí)別層次聚類不需要中等中等差樣本量受限小樣本探索、可視化輔助選K高斯混合GMM需要一般一般中等需要概率歸屬的軟分群選型時(shí)我的判斷順序很簡(jiǎn)單先確認(rèn)數(shù)據(jù)規(guī)模能不能容忍O(n2)類算法再看數(shù)據(jù)形狀是否規(guī)則然后考慮業(yè)務(wù)到底是要硬標(biāo)簽還是軟概率最后才考慮哪種算法解釋起來更方便。很多時(shí)候不是算法越復(fù)雜越好而是越能穩(wěn)定產(chǎn)出業(yè)務(wù)可理解的標(biāo)簽越好。3. 大數(shù)據(jù)聚類落地的工程準(zhǔn)備從預(yù)處理到分布式計(jì)算3.1 預(yù)處理決定八成結(jié)果清洗、標(biāo)準(zhǔn)化、降維我見過太多人費(fèi)勁把算法調(diào)到各種參數(shù)結(jié)果聚類效果還是稀爛回頭一看數(shù)據(jù)連最基礎(chǔ)的標(biāo)準(zhǔn)化都沒做。聚類算法幾乎都依賴距離度量如果兩個(gè)字段的單位不同比如“訪問次數(shù)”的量級(jí)是幾十“消費(fèi)金額”的量級(jí)是幾千那最終算距離的時(shí)候消費(fèi)金額幾乎完全主導(dǎo)了相似度判斷訪問次數(shù)直接被淹沒了。這就好比你把“身高米”和“體重克”直接相加根本沒意義。做聚類之前我必做三件事缺失值處理簡(jiǎn)單的字段用均值或中位數(shù)填充業(yè)務(wù)含義明確的字段按經(jīng)驗(yàn)值填充缺失率過高又無法解釋的字段直接剔除。異常值處理先通過箱線圖、百分位數(shù)或DBSCAN輔助手段找出離群點(diǎn)結(jié)合業(yè)務(wù)判斷是否刪除或單獨(dú)打標(biāo)簽別讓極少數(shù)異常值污染質(zhì)心。標(biāo)準(zhǔn)化處理最常用的是Z-score標(biāo)準(zhǔn)化讓每個(gè)特征變成均值0、方差1的量這樣不同量綱才能放在同一個(gè)距離體系里比較。維度太高又是一個(gè)容易翻車的地方。大數(shù)據(jù)場(chǎng)景里動(dòng)輒一二百個(gè)原始特征很正常許多特征之間還存在相關(guān)關(guān)系直接全部丟進(jìn)聚類算法高維詛咒會(huì)讓所有樣本之間的距離都趨于接近聚類形同虛設(shè)。我通常先用主成分分析PCA做一個(gè)預(yù)降維目標(biāo)不是盲目追求降到2維或者3維而是保留能解釋80%到90%方差的維度。降維還能順手去噪讓后續(xù)聚類更穩(wěn)定。3.2 用Spark MLlib跑分布式聚類一個(gè)可直接抄的流程數(shù)據(jù)量到了單機(jī)跑不動(dòng)的時(shí)候我一般會(huì)直接走Spark MLlib。Spark的分布式計(jì)算框架配合它的MLlib機(jī)器學(xué)習(xí)庫(kù)能讓你用相對(duì)少的工作量把K-Means從小數(shù)據(jù)集直接擴(kuò)展到幾百萬甚至上千萬的樣本。關(guān)鍵思路是把數(shù)據(jù)切成RDD或DataFrame分區(qū)讓不同節(jié)點(diǎn)并行計(jì)算局部距離和局部和最后再匯總更新質(zhì)心。下面的代碼流程可以作為基礎(chǔ)模板我已經(jīng)在多個(gè)項(xiàng)目里用過稍作修改就能套用。這里以Scala版本為例因?yàn)槲覀€(gè)人覺得Spark的Scala API在寫機(jī)器學(xué)習(xí)流水線時(shí)比Python版本更順手但兩家核心邏輯是一樣的。import org.apache.spark.sql.SparkSession import org.apache.spark.ml.feature.{VectorAssembler, StandardScaler, PCA} import org.apache.spark.ml.clustering.KMeans import org.apache.spark.ml.evaluation.ClusteringEvaluator val spark SparkSession.builder() .appName(KMeansUserClustering) .enableHiveSupport() .getOrCreate() // 1. 讀取清洗后的業(yè)務(wù)表里面已經(jīng)是特征列 val rawDF spark.table(ods.user_feature) // 2. 把多個(gè)特征列組裝成一個(gè)向量列 val featureCols Array(visit_cnt, stay_seconds, order_amt, order_interval_days, coupon_times) val assembler new VectorAssembler() .setInputCols(featureCols) .setOutputCol(features_raw) val featureDF assembler.transform(rawDF) // 3. 標(biāo)準(zhǔn)化確保量綱統(tǒng)一 val scaler new StandardScaler() .setInputCol(features_raw) .setOutputCol(features_scaled) .setWithMean(true) .setWithStd(true) val scaledDF scaler.fit(featureDF).transform(featureDF) // 4. 先PCA降維保留主要信息同時(shí)削弱噪聲影響 val pca new PCA() .setInputCol(features_scaled) .setOutputCol(features_pca) .setK(6) val pcaDF pca.fit(scaledDF).transform(scaledDF) // 5. 運(yùn)行K-Means固定隨機(jī)種子保證可復(fù)現(xiàn) val kmeans new KMeans() .setK(4) .setFeaturesCol(features_pca) .setPredictionCol(cluster) .setSeed(42L) .setMaxIter(30) val model kmeans.fit(pcaDF) val clusteredDF model.transform(pcaDF) // 6. 評(píng)估輪廓系數(shù)驗(yàn)證聚類分離效果 val evaluator new ClusteringEvaluator() .setFeaturesCol(features_pca) .setPredictionCol(cluster) .setMetricName(silhouette) val silhouetteScore evaluator.evaluate(clusteredDF) println(saverage silhouette score $silhouetteScore)說幾個(gè)代碼之外的工程心得。第一分布式環(huán)境下一定要固定隨機(jī)種子否則每次跑的結(jié)果可能不同業(yè)務(wù)或下游鏈路都會(huì)受影響。第二如果數(shù)據(jù)量特別大PCA計(jì)算協(xié)方差矩陣在分布式下依然有壓力建議先用Spark的抽樣做一次近似PCA或者用截?cái)郤VD方案我一般會(huì)根據(jù)集群資源判斷。第三數(shù)據(jù)分區(qū)不要冷熱不均常見做法是按業(yè)務(wù)ID哈希打散避免熱點(diǎn)分區(qū)拖垮整體訓(xùn)練。3.3 評(píng)估方法與可解釋性別把輪廓系數(shù)當(dāng)唯一標(biāo)準(zhǔn)聚類是“無監(jiān)督”算法所以沒有準(zhǔn)確率。怎么判斷這次聚類是不是好工程上我通常會(huì)同時(shí)看幾個(gè)指標(biāo)而不是迷信某一個(gè)。SSE簇內(nèi)平方和評(píng)價(jià)簇內(nèi)的緊湊程度SSE越小說明樣本離自己的質(zhì)心越近。輪廓系數(shù)綜合反映簇內(nèi)緊密和簇間分離程度取值范圍在-1到1之間越高說明聚類越清晰。業(yè)務(wù)解讀率這是我最看重的指標(biāo)。每個(gè)簇是否可以用一句話說清楚比如“高消費(fèi)低頻用戶”“低消費(fèi)高頻用戶”。如果聚類結(jié)果連業(yè)務(wù)方都沒法解釋數(shù)字上再漂亮也別用。我還習(xí)慣把一個(gè)聚類結(jié)果灌到可視化工具里做降維展示。比如用t-SNE或UMAP把高維特征投影到二維平面把聚類標(biāo)簽映射成顏色能直觀看到簇與簇的重疊程度。很多算法指標(biāo)看起來還行可視化和業(yè)務(wù)側(cè)一檢查就會(huì)發(fā)現(xiàn)邊界重疊得一塌糊涂。這種步驟該花的時(shí)間不能省后期返工成本更高。4. 一個(gè)完整實(shí)戰(zhàn)復(fù)盤某零售平臺(tái)用戶分群全流程4.1 業(yè)務(wù)目標(biāo)與初始數(shù)據(jù)我這套方法曾經(jīng)用在一個(gè)零售平臺(tái)的用戶運(yùn)營(yíng)項(xiàng)目上當(dāng)時(shí)業(yè)務(wù)方的需求很樸素目前所有用戶一鍋粥運(yùn)營(yíng)活動(dòng)不知道發(fā)給誰(shuí)想根據(jù)行為把用戶分成幾個(gè)有區(qū)分的群體再做針對(duì)性觸達(dá)。我拿到的原始數(shù)據(jù)大概兩百多萬行來自近兩個(gè)月的埋點(diǎn)日志和訂單流水。字段主要包括當(dāng)日訪問商品頁(yè)次數(shù)、平均單次瀏覽時(shí)長(zhǎng)、最近一次下單至今的間隔天數(shù)、累計(jì)訂單金額、使用優(yōu)惠券次數(shù)、常用支付方式類別。字段不算多但每個(gè)字段的質(zhì)量參差不齊訪問次數(shù)有大量空值訂單金額有很多明顯偏大的異常值還有一些只在活動(dòng)期產(chǎn)生行為的用戶光憑直覺判斷很容易被帶偏。4.2 特征工程實(shí)操過程我沒有直接把原始字段丟進(jìn)算法。第一步做了標(biāo)簽匯總把每個(gè)用戶在兩個(gè)月的記錄里聚合成分特征比如每天平均訪問次數(shù)、訪購(gòu)轉(zhuǎn)化率、平均客單價(jià)、近30天活躍天數(shù)。聚合完以后業(yè)務(wù)邏輯就自然嵌到特征里了“活躍度”和“消費(fèi)能力”成為兩個(gè)隱性的主導(dǎo)維度。第二步清理異常值。訂單金額超過99%分位數(shù)的用戶先用業(yè)務(wù)規(guī)則判斷是不是B2B采購(gòu)或者退款異常如果不是就直接把字段截?cái)嗟竭@個(gè)分位數(shù)避免極端值主導(dǎo)距離。第三步標(biāo)準(zhǔn)化加降維。五六個(gè)特征在標(biāo)準(zhǔn)化之后我做了一次PCA前三個(gè)主成分已經(jīng)解釋了大概85%的方差后面的成分對(duì)聚類基本沒有增量貢獻(xiàn)所以保留三到四個(gè)維度既降低計(jì)算量又順手去掉了一部分相關(guān)冗余。這步做完聚類效果肉眼可見地穩(wěn)定下來而不是像沒降維之前那樣換個(gè)隨機(jī)種子結(jié)果就面目全非。4.3 建模、選K與參數(shù)調(diào)整選K這一步我相信很多人都在猶豫。我的標(biāo)準(zhǔn)流程是“先肘部法粗篩、再輪廓系數(shù)細(xì)選、最后結(jié)合業(yè)務(wù)判斷”。針對(duì)處理后的特征我讓Spark跑了K從2到8的一組K-Means算出SSE和輪廓系數(shù)。K值SSE平均輪廓系數(shù)簇間距離感受221863000.28分得太粗315542000.33有點(diǎn)意思但還是太粗411207000.41簇區(qū)分明顯輪廓最高58745000.36輪廓下降業(yè)務(wù)難解釋66932000.31出現(xiàn)碎片簇可以看到K3到K4時(shí)SSE下降很陡到K4之后下降變緩輪廓系數(shù)也在K4達(dá)到最高所以最終選了K4。選K的時(shí)候我會(huì)額外做一次隨機(jī)種子穩(wěn)定性測(cè)試同一個(gè)K跑五個(gè)隨機(jī)種子看每個(gè)簇的樣本數(shù)分布是否穩(wěn)定。如果只是K碰巧分得開但每個(gè)種子結(jié)果差很多那就說明這個(gè)K不是真正自然的簇個(gè)數(shù)。4.4 簇群解讀與可落地的運(yùn)營(yíng)動(dòng)作最終模型把兩百多萬用戶分成了四個(gè)簇每個(gè)簇的特征中心很有規(guī)律第一簇我們內(nèi)部叫“高價(jià)值活躍用戶”。訪問頻次高、停留時(shí)長(zhǎng)久、訂單金額高、優(yōu)惠券使用次數(shù)少典型的價(jià)格不敏感型人群。運(yùn)營(yíng)動(dòng)作是提供新品內(nèi)測(cè)權(quán)限、升級(jí)會(huì)員權(quán)益不要用大額折扣去刺激反而容易拉低利潤(rùn)。第二簇叫“價(jià)格敏感型用戶”。訂單金額適中但優(yōu)惠券使用率特別高常常在活動(dòng)期突然活躍。運(yùn)營(yíng)動(dòng)作是定向發(fā)滿減券、促銷預(yù)告用低價(jià)和贈(zèng)品綁定復(fù)購(gòu)。第三簇是“猶豫型用戶”。訪問次數(shù)不少但下單間隔特別長(zhǎng)訂單金額偏低。這類用戶處在決策門檻上運(yùn)營(yíng)動(dòng)作是推限時(shí)搶購(gòu)、庫(kù)存緊張?zhí)嵝焉踔链钆淙斯る娫捇卦L。第四簇是“沉默用戶”。各項(xiàng)行為指標(biāo)都低近一個(gè)月幾乎沒有訪問。這部分的運(yùn)營(yíng)策略是低成本召回比如短信觸達(dá)或跨平臺(tái)消息推送如果連續(xù)幾個(gè)周期都無聲甚至可以考慮清理出主運(yùn)營(yíng)池節(jié)約成本。這個(gè)分群方案上線以后運(yùn)營(yíng)活動(dòng)點(diǎn)擊率從原來的6%左右提升到了超過16%下單轉(zhuǎn)化率也跟著翻了接近一倍。業(yè)務(wù)方第一次對(duì)“聚類分析”產(chǎn)生了真實(shí)的信任感因?yàn)樗鼪]有靠人工去拍腦袋定用戶等級(jí)而是直接把用戶的自然分布擺在了他們眼前。5. 那些沒人提醒你的坑聚類項(xiàng)目實(shí)操問題排查速查表5.1 不對(duì)特征做標(biāo)準(zhǔn)化聚類等于給身高體重直接加權(quán)我在第一部分就提到過標(biāo)準(zhǔn)化但這里值得單獨(dú)拿出來強(qiáng)調(diào)因?yàn)樗且粋€(gè)極其隱蔽又高頻的問題。有一類項(xiàng)目特征是“年齡”和“年收入”年齡在20到60之間收入在5萬到500萬之間直接跑距離收入基本決定了整個(gè)聚類結(jié)果年齡起不了任何作用。業(yè)務(wù)方拿到結(jié)果后就蒙了“怎么年齡完全沒體現(xiàn)”這不是算法問題是數(shù)據(jù)尺度的鍋。解決方式很簡(jiǎn)單全部特征Z-score標(biāo)準(zhǔn)化或統(tǒng)一歸一到0到1區(qū)間。不要嫌這一步簡(jiǎn)單很多“算法效果不好”的最終原因排查到最后都是這個(gè)。5.2 只看“肘部法”選K可能被偽裝成最優(yōu)的錯(cuò)誤答案肘部法是通過看SSE隨K下降的拐點(diǎn)來選K但實(shí)際數(shù)據(jù)往往沒有特別明顯的一個(gè)“肘”整條曲線平滑下滑你從中看到的一切“肘”都可能只是巧合。我試過很多次自認(rèn)為找到了一個(gè)明確的肘部結(jié)果換一種標(biāo)準(zhǔn)化方式整個(gè)肘部位置都變了說明那個(gè)“肘”并沒有真實(shí)業(yè)務(wù)含義。我的建議是肘部法只用來初篩真正選K要跟輪廓系數(shù)、簇內(nèi)樣本量穩(wěn)定性、業(yè)務(wù)可解釋性放在一起綜合評(píng)判。而且選K之前一定要先確定好特征和預(yù)處理方案否則后面所有環(huán)節(jié)都是“在沙子上蓋樓”。5.3 高維空間里距離會(huì)失靈高維數(shù)據(jù)會(huì)發(fā)生一個(gè)反直覺的現(xiàn)象所有樣本對(duì)之間的距離會(huì)越來越接近無論聚集還是分散大家看起來都像“均勻分布在一個(gè)球面上”。這時(shí)候不管你用歐氏距離還是余弦距離直接硬跑聚類很容易得到一堆沒有意義的簇。應(yīng)對(duì)方法就三條路一是用PCA或相關(guān)性的方法降維把有效信號(hào)壓縮到少數(shù)維度二是換用更適合高維的相似度算法三是用更能抗高維的工程方案如先用粗粒度預(yù)聚類生成候選再做精細(xì)聚類。我常用PCA打頭陣它簡(jiǎn)單穩(wěn)定大多數(shù)業(yè)務(wù)場(chǎng)景下夠用。5.4 分布式環(huán)境下的隨機(jī)性不固定種子結(jié)果飄忽在Spark這類分布式環(huán)境下初始質(zhì)心的選擇是有隨機(jī)成分的。如果你沒有固定seed每次訓(xùn)練得到的簇中心可能都不完全一樣。雖然得出來的往往是局部最優(yōu)但如果這個(gè)結(jié)果要進(jìn)入下游調(diào)度或被寫入報(bào)表那么每次結(jié)果的波動(dòng)都會(huì)引發(fā)一連串返工問題。解決起來就是一行代碼的事給K-Means設(shè)置固定seed在模型參數(shù)里把隨機(jī)種子寫死。如果有企業(yè)合規(guī)要求需要更嚴(yán)謹(jǐn)?shù)碾S機(jī)初始化驗(yàn)證就多跑幾個(gè)不同的固定種子對(duì)結(jié)果做交叉對(duì)比選最穩(wěn)定的一組而不是讓模型每天都“看心情”。5.5 數(shù)據(jù)漂移與周期性重訓(xùn)聚類結(jié)果會(huì)過期的聚類不是一個(gè)一次性的快照。用戶行為會(huì)隨著季節(jié)、活動(dòng)、產(chǎn)品迭代而變化你今天很干凈的集群三個(gè)月后可能已經(jīng)跟真實(shí)分布脫節(jié)。拿電商場(chǎng)景來說大促期間消費(fèi)者的行為模式和平日完全不同如果長(zhǎng)期沿用舊的聚類模型觸達(dá)策略就會(huì)失準(zhǔn)。我在項(xiàng)目后期一般會(huì)搭一個(gè)簡(jiǎn)單的時(shí)間窗口重訓(xùn)機(jī)制每周或者每月重新跑一次聚類并把新舊聚類結(jié)果做交叉表觀察每個(gè)簇的樣本遷移率。遷移率過大時(shí)就要回頭檢查是業(yè)務(wù)變了還是特征工程出了問題。這個(gè)步驟雖然不會(huì)直接帶來模型指標(biāo)變化但能避免模型像“過期地圖”一樣把用戶引向錯(cuò)誤方向。最后再說兩句我實(shí)際的感受。聚類這個(gè)方向聽起來入門門檻很低但能做好的關(guān)鍵從來不在算法本身而在數(shù)據(jù)的理解、特征的處理和業(yè)務(wù)結(jié)果解釋這三件事上。如果你剛開始做聚類我的建議是不要追求復(fù)雜模型先把一個(gè)K-Means在真實(shí)業(yè)務(wù)數(shù)據(jù)上玩到滾瓜爛熟熟悉整個(gè)從預(yù)處理到評(píng)估再到解釋的鏈路然后再去切換DBSCAN或GMM。鏈路熟了以后你會(huì)發(fā)現(xiàn)換了算法只是換一個(gè)參數(shù)對(duì)象和輸出格式的事真正難的是讓每一個(gè)聚類簇都能在業(yè)務(wù)里站得住腳讓業(yè)務(wù)方發(fā)自內(nèi)心覺得自己被算法幫到了而不再只是看一張“看起來很科學(xué)”的分群圖形。希望這篇分享能幫你少走一些我走過的彎路。