如何選K?)
簡介K-means聚類算法可視化Python代碼面向數(shù)據(jù)科學初學者和需要搭建聚類分析原型的開發(fā)者實現(xiàn)經(jīng)典K-means并集成肘部法則與輪廓系數(shù)分析前者通過簇內(nèi)誤差平方和曲線輔助選K后者量化簇內(nèi)緊密性與簇間分離度幫助用戶科學確定最佳簇數(shù)。壓縮包共27個文件包含7個Python腳本、18張預(yù)生成的PNG結(jié)果圖以及依賴列表與說明文檔整體約10.04MB腳本覆蓋主程序與功能模塊圖片對應(yīng)不同K值和聚類步驟的可視化結(jié)果文檔說明環(huán)境依賴與運行方式。目前已有165人瀏覽學習適合配合教程邊看邊跑、對照圖片理解算法原理。通過運行主程序即可復(fù)現(xiàn)完整流程模塊化結(jié)構(gòu)便于替換數(shù)據(jù)集或調(diào)整參數(shù)既能用于課堂演示也能作為實際項目中的聚類分析起點。1. K-means聚類可視化這份代碼能告訴你K到底選幾做客戶分群的時候我曾在 K3 和 K4 之間猶豫了很久最后拍腦袋選了4結(jié)果業(yè)務(wù)方一句第三類和第五類本質(zhì)是一群人讓整個分析返工。選 K 這件事很多人靠直覺但其實有成熟的量化手段——肘部法則看畸變程度輪廓系數(shù)看簇內(nèi)緊密度和簇間分離度。這次拆的這份 K-means 聚類可視化 Python 代碼把這兩套指標做成了完整可視化流程跑一次 main.py 就能看到聚類效果、肘部曲線和輪廓系數(shù)分布。適合正在學數(shù)據(jù)分析與可視化、或者要在實際項目里給老板一個K為什么選這個數(shù)交代的從業(yè)者。它不解決算法創(chuàng)新問題解決的是怎么選K、怎么證明選對了、怎么把結(jié)果講清楚。2. 跑通項目先看結(jié)構(gòu)main.py入口、src模塊與requirements依賴拿到壓縮包先別急著跑花兩分鐘把目錄結(jié)構(gòu)看清楚。拆開 kmeans_clustering 目錄后核心文件分四類main.py 是總?cè)肟趕rc 里放的是算法和可視化封裝examples 是演示腳本results 是跑完后的輸出目錄。這份資源的目錄設(shè)計比較規(guī)矩基本是入口 源碼 示例 產(chǎn)物四段式適合直接拿來改成自己的實驗框架。2.1 先裝依賴再跑main.py環(huán)境配置與運行邏輯第一步永遠是裝依賴requirements.txt 里鎖定了五個庫numpy、matplotlib、seaborn、scikit-learn、pandas。前四個是 Python 數(shù)據(jù)分析與可視化標配pandas 主要用于讀表和結(jié)果整理。我用 venv 建獨立環(huán)境python -m venv kmeans_env source kmeans_env/bin/activate # Windows 下執(zhí)行 kmeans_env\Scripts\activate pip install -r requirements.txt依賴裝完后直接跑主程序python main.pymain.py 的邏輯通常是生成或加載數(shù)據(jù)集 → 調(diào)用 src 里的聚類函數(shù) → 依次畫出原始數(shù)據(jù)分布、聚類結(jié)果、肘部法則曲線、輪廓系數(shù)圖。它用的是 sklearn 的 KMeans不是自己從零實現(xiàn)迭代更新好處是結(jié)果可靠、參數(shù)語義清晰壞處是如果你想看每一輪迭代中心點怎么移動這份代碼不能直接滿足得自己改。需要注意一個細節(jié)如果 requirements.txt 里鎖的是 scikit-learn 1.2 以上版本n_init 參數(shù)的默認值從 10 變成了 auto。第一次跑如果發(fā)現(xiàn)聚類結(jié)果和網(wǎng)上教程對不上先查 sklearn 版本這是最常見的翻車點。2.2 src目錄下的模塊劃分可視化封裝與數(shù)據(jù)預(yù)處理src 目錄是這個項目的精華所在通常按功能拆成幾個模塊類似模塊文件職責關(guān)鍵函數(shù)preprocessing.py數(shù)據(jù)標準化、生成樣本數(shù)據(jù)generate_blob_data, standardizekmeans_utils.py封裝 sklearn KMeans返回模型和標簽fit_kmeansmetrics.py計算 SSE、輪廓系數(shù)等指標compute_sse, compute_silhouettevisualize.py所有繪圖邏輯plot_clusters, plot_elbow, plot_silhouette這種拆法的好處是你在實際項目里不需要全部代碼只需要調(diào) visualize 里的繪圖函數(shù)就行。比如你已經(jīng)有了一份用戶特征表想快速看聚類效果只需要把數(shù)據(jù)傳進 fit_kmeans 和 plot_clusters不用管 preprocessing 里的生成樣本邏輯。我一般會把預(yù)處理單獨拎出來說一句這份代碼里數(shù)據(jù)標準化用的是 StandardScaler不是 MinMaxScaler。這兩個選擇直接影響聚類結(jié)果。K-means 基于歐氏距離如果某個特征量綱特別大比如消費金額單位是元、而訪問次數(shù)是個位數(shù)距離計算基本被金額主導(dǎo)聚類出來的簇本質(zhì)上是金額分段其他特征都成了擺設(shè)。StandardScaler 把每個特征變成均值為0、方差為1雖然不能完全消除離群點影響但對 K-means 來說是默認首選。3. K-means核心參數(shù)與可視化實現(xiàn)k-means初始化、n_init和random_state的暗坑K-means 的數(shù)學原理不復(fù)雜隨機選 K 個中心點把每個樣本歸到最近中心重新計算中心重復(fù)直到中心不再移動。但就是這個隨機二字讓同樣的數(shù)據(jù)在不同人手里跑出完全不同結(jié)果。這章把它拆開講透。3.1 初始化方式對結(jié)果的影響為什么默認要用k-means如果不做任何設(shè)置sklearn 的 KMeans 默認 initk-means。k-means 的核心思想是第一個中心點隨機選之后每個中心點選的時候離已有中心點越遠的樣本被選中的概率越大。這樣初始中心點分散在整個數(shù)據(jù)空間迭代更容易收斂到全局最優(yōu)附近。反過來如果 initrandom每次跑可能都落在不同局部最優(yōu)。尤其當數(shù)據(jù)有明顯重疊、簇的形狀不規(guī)則時random 初始化經(jīng)常產(chǎn)出畸形簇——一個簇吃掉兩個真實群體另一個簇只剩邊緣幾個點。這段代碼里既然封裝了 fit_kmeans一般會在內(nèi)部寫好 initk-means但你接手項目后最好確認一層避免別人改過參數(shù)。3.2 n_init、tol和random_state可視化結(jié)果可復(fù)現(xiàn)的底線這三個參數(shù)是 K-means 結(jié)果能否復(fù)現(xiàn)、是否穩(wěn)定的關(guān)鍵。下面這段代碼演示了實際項目里怎么用項目里 src/kmeans_utils.py 的封裝思路差不多from sklearn.cluster import KMeans import numpy as np def fit_kmeans(X, k, seed42): 封裝 KMeans固定隨機種子保證結(jié)果可復(fù)現(xiàn) X: 形狀為 (n_samples, n_features) 的特征矩陣應(yīng)先做標準化 k: 聚類數(shù) seed: 隨機種子默認 42 model KMeans( n_clustersk, initk-means, # 用改進的初始化方法避免隨機初始化落入局部最優(yōu) n_init10, # 跑 10 輪每輪不同初始中心保留最優(yōu)結(jié)果 max_iter300, # 單輪最多迭代 300 次防止不收斂死循環(huán) tol1e-4, # 中心點位移小于該閾值視為收斂 random_stateseed # 固定種子讓每次跑出的聚類結(jié)果完全一致 ) labels model.fit_predict(X) return model, labels解釋一下關(guān)鍵參數(shù)n_init10 表示算法會從 10 組不同初始中心開始各跑一遍完整迭代最后取 SSE簇內(nèi)平方和最小的那組結(jié)果。sklearn 1.2 版本之后如果 n_init 不顯式指定默認 auto 在 8 個簇以內(nèi)時相當于 n_init10但顯式寫出來更保險。tol1e-4 是收斂判定閾值實際項目中如果你的數(shù)據(jù)特別大、想要更快跑完可以把 tol 放寬到 1e-3代價是中心點可能沒完全收斂就停。random_state42 固定后你每次跑結(jié)果都一樣這在給業(yè)務(wù)方展示時非常重要——同一個項目今天跑出一個分群結(jié)果、明天跑出一個相反結(jié)果你還怎么解釋4. 肘部法則與輪廓系數(shù)兩種選K方法怎么配合、閾值怎么定K 到底選幾個這是 K-means 實踐里最核心的問題。這份代碼同時提供了肘部法則和輪廓系數(shù)兩種分析路徑它們的數(shù)學邏輯不同適用范圍也不同配合使用才能避免誤判。4.1 肘部法則的落點SSE曲線的彎折處不等于最佳K肘部法則的邏輯是隨著 K 增大樣本到所屬簇中心的距離總和SSE一定單調(diào)下降因為簇多了每個簇內(nèi)部當然更緊湊。但下降速度會越來越慢因為簇從分割真實群體變成把大簇強行切開邊際收益遞減。那個下降速度突變的點就是肘部。在實際畫圖時SSE 曲線往往不是教科書那樣的完美 L 形而是平滑下降、沒有明顯拐點。這時候用看圖的直覺去選 K 就是玄學。更可靠的做法是算 SSE 的一階差分找差分變化率最大的位置import numpy as np from sklearn.cluster import KMeans import matplotlib.pyplot as plt def plot_elbow_with_diff(X, k_rangerange(2, 11)): 繪制肘部法則曲線同時標注SSE變化率最大的K值 X: 標準化后的特征矩陣 k_range: K 的取值區(qū)間 inertias [] for k in k_range: model KMeans(n_clustersk, initk-means, n_init10, random_state7) model.fit(X) inertias.append(model.inertia_) # inertia_ 就是 SSE # 計算一階差分即 K 增加一檔時 SSE 的下降量 diffs np.diff(inertias) # 差分變化率最大的位置對應(yīng)曲線最陡的轉(zhuǎn)折點 elbow_k list(k_range)[np.argmin(diffs) 1] plt.figure(figsize(10, 5)) plt.subplot(1, 2, 1) plt.plot(list(k_range), inertias, bo-, linewidth2) plt.xlabel(K) plt.ylabel(SSE) plt.title(Elbow Method) plt.subplot(1, 2, 2) plt.plot(list(k_range)[1:], diffs, rs-, linewidth2) plt.axvline(elbow_k, colorgray, linestyle--) plt.xlabel(K) plt.ylabel(SSE decrease rate) plt.title(Elbow Differential) plt.show()這里有兩個關(guān)鍵點。第一model.inertia_ 是 sklearn KMeans 在 fit 之后自帶的屬性直接取就行不用手動算。第二差分變化率最大并不意味著必須選這個 K它只告訴你哪個 K 附近增加簇的收益衰減最明顯。如果業(yè)務(wù)上 K3 和 K4 的差分變化率接近你可以分別跑一下聚類看哪個結(jié)果更容易解釋而不是死磕數(shù)學指標。4.2 輪廓系數(shù)的判讀區(qū)間特征與業(yè)務(wù)解釋的平衡輪廓系數(shù)是另一種思路它不關(guān)心 K 增加帶來的 SSE 變化而是直接評估每個樣本與其所在簇的緊密度、以及與其最近鄰簇的分離度。每個樣本的輪廓系數(shù)在 -1 到 1 之間接近 1 表示這個樣本離自己簇中心近、離隔壁簇遠分類明確接近 0 表示在兩個簇的邊界上接近 -1 表示它可能被分錯了簇。使用 sklearn 的 silhouette_score 計算整體輪廓系數(shù)用 silhouette_samples 拿到每個樣本的值來畫分布圖from sklearn.metrics import silhouette_score, silhouette_samples def evaluate_silhouette(X, labels, k): 輸出整體輪廓系數(shù)并返回每個樣本的輪廓系數(shù) X: 標準化特征矩陣 labels: 聚類標簽 k: 聚類數(shù)用于確認標簽范圍 # 整體輪廓系數(shù)所有樣本輪廓系數(shù)的均值衡量聚類整體質(zhì)量 overall_score silhouette_score(X, labels) # 每個樣本的輪廓系數(shù)用于后續(xù)繪制輪廓分布圖 sample_scores silhouette_samples(X, labels) return overall_score, sample_scores輪廓系數(shù)的判讀有一條經(jīng)驗線整體系數(shù)大于 0.5 說明簇結(jié)構(gòu)明顯0.25 到 0.5 之間說明有重疊但可接受小于 0.25 說明聚類基本沒有意義。但這條線不是鐵律業(yè)務(wù)含義永遠優(yōu)先。我實際遇到的場景是K3 時輪廓系數(shù) 0.52K4 時 0.46按數(shù)值選 K3 沒問題但業(yè)務(wù)方說我要把新客和老帶新拆開運營這本質(zhì)上是 K4 的切法。處理方式是把兩份聚類結(jié)果都輸出輔以各簇的特征統(tǒng)計表讓業(yè)務(wù)方看數(shù)據(jù)說話而不是拿一個 0.52 的分數(shù)壓人。輪廓系數(shù)告訴你的不是唯一正確答案而是哪些 K 在結(jié)構(gòu)上說得通、哪些 K 純粹是把數(shù)據(jù)切碎了。5. 避坑排查聚類翻車的四類共性問題跑這份代碼或者拿它改自己的項目最容易踩的坑不在算法本身而在數(shù)據(jù)處理和參數(shù)理解上。這里寫四條高頻問題都是我反復(fù)遇到過的。5.1 聚類結(jié)果被單一特征主導(dǎo)現(xiàn)象跑完聚類后畫各簇的特征分布發(fā)現(xiàn)數(shù)據(jù)其實只按某一維切開了其他特征在各簇間沒什么差別。比如做用戶分群簇和消費金額完全對應(yīng)而訪問深度、停留時長在簇間幾乎一致。原因沒有做特征標準化。K-means 用歐氏距離度量樣本相似度量綱大的特征天然權(quán)重更高。金額字段動輒幾萬而訪問次數(shù)是幾十距離計算被金額壓制。解決對所有特征做 StandardScaler讓每個特征均值為 0、方差為 1。注意要在切分訓(xùn)練集之前做否則會引入數(shù)據(jù)泄漏。改完代碼后重新跑肘部法則和輪廓系數(shù)K 的最優(yōu)值很可能都變了這是正常現(xiàn)象。5.2 同一份數(shù)據(jù)每次跑出來聚類結(jié)果不一樣現(xiàn)象main.py 連續(xù)跑兩次兩次的聚類標簽不完全一樣簇中心也有偏移。如果換了臺機器跑結(jié)果差異更大。原因KMeans 不管是用 k-means 還是 random 初始化都含有隨機過程如果不固定 random_state每次運行產(chǎn)生的初始中心不同迭代收斂到的最優(yōu)解也不同。解決給 KMeans 構(gòu)造函數(shù)顯式傳入 random_state42。注意如果你用的是 pip 安裝的最新版 scikit-learnn_init 的默認值是 auto它會自動選擇一個基于數(shù)據(jù)規(guī)模的計算策略這個策略本身也要隨機種子。固定 random_state 后n_init 的次數(shù)會自動設(shè)為 10基本不會再出現(xiàn)結(jié)果漂移問題。5.3 肘部法則和輪廓系數(shù)給出的最優(yōu)K不一致現(xiàn)象肘部曲線的拐點顯示 K3 合適輪廓系數(shù)在 K5 時最高。兩個指標打架不知道聽誰的。原因這兩個指標優(yōu)化目標不同。肘部法則看的是緊密度的邊際收益輪廓系數(shù)看的是某個樣本離自己簇和隔壁簇的距離差。當數(shù)據(jù)存在嵌套簇或者各簇密度不均時一個整體大的簇被拆成兩個小簇后樣本的輪廓系數(shù)反而上升因為簇變小了、樣本離簇中心更近了但肘部法則的邊際收益已經(jīng)衰減。解決以業(yè)務(wù)目標為最終裁決。商家要做的是分群運營那 K 的選擇取決于運營資源能不能覆蓋 K 個群。一般來說先用肘部法則圈出 K 的大致范圍比如 3 到 5再在范圍內(nèi)比較各 K 的輪廓系數(shù)同時看各簇的樣本量是否均衡——如果你選了 K5但第三簇只有 3% 的樣本這個在實際業(yè)務(wù)里很難單獨運營不如降回 K4。5.4 聚類圖看著清晰但換一批數(shù)據(jù)就崩現(xiàn)象在原始數(shù)據(jù)集上聚類效果很好各簇顏色明顯分離。把同樣的參數(shù)套到新數(shù)據(jù)上聚類結(jié)果一團糟甚至出現(xiàn)空簇。原因通常是三個問題同時發(fā)生。一是新數(shù)據(jù)沒有經(jīng)過同樣的標準化而模型是在標準化后的數(shù)據(jù)上訓(xùn)練的二是 K 的選擇依賴原始數(shù)據(jù)的分布新數(shù)據(jù)分布如果偏移K 就得重新選三是 n_init 太小新數(shù)據(jù)上多次初始化后不同運行的結(jié)果差異大最終選到的可能是局部最優(yōu)。解決把數(shù)據(jù)預(yù)處理和聚類參數(shù)選擇寫成一條 pipeline新數(shù)據(jù)進來后走同一套流程標準化 → 肘部法則重算 → 輪廓系數(shù)驗證 → 訓(xùn)練。不要復(fù)用上一次的 K 值和標準化參數(shù)。真正穩(wěn)定的做法是確認標準化參數(shù)是重新用新數(shù)據(jù)擬合的還是沿用舊數(shù)據(jù)的統(tǒng)計量這取決于業(yè)務(wù)場景——如果數(shù)據(jù)是滾動產(chǎn)生的一般用全量歷史數(shù)據(jù)擬合標準化器再對新數(shù)據(jù)做 transform這樣新老數(shù)據(jù)的量綱口徑一致。6. 進階技巧3D聚類可視化與結(jié)果驗證的完整打法二維散點圖展示聚類結(jié)果有個硬傷當特征超過兩個時你看到的只是任意兩維的投影簇在二維圖上重疊不代表在真實特征空間里重疊。這里給一個項目里留好的進階驗證方法PCA 降維到三維后做 3D 散點圖把高維空間的簇間分離情況投射到三維里看很多二維圖上不明顯的結(jié)構(gòu)轉(zhuǎn)一個角度就能看清楚。from sklearn.decomposition import PCA import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import Axes3D def plot_clusters_3d(X, labels, k, title3D Cluster Visualization): 將高維特征降至三維并繪制聚類散點圖 X: 標準化后的特征矩陣 labels: 聚類標簽 k: 聚類數(shù) # PCA 降維把 n 維特征線性變換到 3 維保留最大方差方向 pca PCA(n_components3) X_3d pca.fit_transform(X) fig plt.figure(figsize(10, 8)) ax fig.add_subplot(111, projection3d) # 為每個簇分配一個顏色循環(huán)繪制 for cluster_id in range(k): mask (labels cluster_id) ax.scatter( X_3d[mask, 0], X_3d[mask, 1], X_3d[mask, 2], s20, alpha0.6, labelfCluster {cluster_id} ) ax.set_xlabel(PC1) ax.set_ylabel(PC2) ax.set_zlabel(PC3) ax.set_title(title) ax.legend() plt.show()3D 圖對透視要求高不是所有場景都適合展示。我在這份代碼里看到的一個比較好用的技巧是先畫出降維后各主成分的方差解釋比例看到前三個主成分累計蓋過 70%再決定要不要用 3D 圖如果只蓋了 40%3D 圖描繪的只是局部信息不要拿它作為結(jié)論依據(jù)。驗證聚類結(jié)果時我還習慣做一件事按簇輸出特征統(tǒng)計表——每簇的樣本量、各特征均值、分位數(shù)。數(shù)值上看起來各簇在關(guān)鍵特征上有明確差異這個聚類才算在業(yè)務(wù)上落地。光看不帶特征解釋的散點圖老板永遠會問然后呢。那份 K-means 可視化代碼包主程序跑一遍就能出四張圖你拿自己的數(shù)據(jù)替換掉樣本生成部分調(diào)一調(diào)參數(shù)基本就能用到項目里去。從那以后我每次做分群分析都會強制走一遍標準化 → 肘部法則 → 輪廓系數(shù) → 特征差異表這個流程哪怕最后 K 還是拍腦袋定的至少每個候選 K 值都有數(shù)據(jù)支撐不會心里沒底。希望幫到你。本文還有配套的精品資源點擊獲取