戰(zhàn)案例:Python源碼與數(shù)據(jù)集詳解)
簡(jiǎn)介K-means聚類算法案例是一份面向數(shù)據(jù)分析初學(xué)者與機(jī)器學(xué)習(xí)入門者的完整實(shí)踐包基于鳶尾花數(shù)據(jù)集中的花瓣長(zhǎng)度與花瓣寬度兩個(gè)特征開展聚類清晰演示從特征選擇、模型訓(xùn)練到簇中心繪制的完整流程幫助理解K-Means核心原理、迭代收斂過程及聚類結(jié)果可視化方法既可用于課堂教學(xué)演示也適合課后自主復(fù)現(xiàn)。壓縮包內(nèi)共9個(gè)文件涵蓋Python源碼py、Jupyter Notebookipynb、接口說(shuō)明文檔docx、數(shù)據(jù)集csv、已訓(xùn)練模型pkl與聚類效果圖png等其中py與ipynb可靈活適配不同編程環(huán)境png直觀展示初始分布與聚類簇劃分pkl則便于直接載入復(fù)用整體大小僅225KB輕量便攜。目前已有204人學(xué)習(xí)瀏覽適合希望快速上手聚類算法的讀者。借助本案例既能直接運(yùn)行現(xiàn)成代碼觀察簇劃分與中心點(diǎn)位置也可依據(jù)接口文檔和源碼梳理從數(shù)據(jù)讀取、模型保存到結(jié)果導(dǎo)出的完整鏈路并能將訓(xùn)練好的模型應(yīng)用到新數(shù)據(jù)上為后續(xù)解決實(shí)際聚類問題打下扎實(shí)基礎(chǔ)。 要聊K-means聚類算法網(wǎng)上其實(shí)不缺代碼缺的是那種“拿到源代碼和數(shù)據(jù)集就能跑通、能看懂、能改著用”的完整案例。我手頭正好整理了一套可以直接運(yùn)行的K-means實(shí)驗(yàn)工程包含完整Python源碼和測(cè)試數(shù)據(jù)集這篇就圍繞它展開。不管你是剛接觸機(jī)器學(xué)習(xí)的學(xué)生還是在頭歌這類在線實(shí)驗(yàn)平臺(tái)上刷題時(shí)被K-means卡過一下或者是準(zhǔn)備在項(xiàng)目里用無(wú)監(jiān)督學(xué)習(xí)做用戶分群、異常檢測(cè)的開發(fā)者這套案例都能直接用同時(shí)也有助于理解sklearn里幾個(gè)重要參數(shù)背后到底是怎么回事。1. 案例整體設(shè)計(jì)與思路拆解1.1 K-means到底在解決什么問題K-means是最典型的原型聚類算法它的核心思想用一個(gè)詞概括就是物以類聚。給定一堆沒有標(biāo)簽的數(shù)據(jù)點(diǎn)算法先把每個(gè)點(diǎn)劃分到最近的簇中心質(zhì)心再根據(jù)每個(gè)簇內(nèi)的所有點(diǎn)重新計(jì)算質(zhì)心位置然后重復(fù)這兩步直到質(zhì)心不再發(fā)生明顯變化。從數(shù)學(xué)角度來(lái)看K-means是在優(yōu)化一個(gè)目標(biāo)函數(shù)[ J \sum_{i1}^{K} \sum_{x \in C_i} |x - \mu_i|^2 ]這個(gè)式子里的(C_i)表示第(i)個(gè)簇(\mu_i)表示第(i)個(gè)簇的質(zhì)心。整個(gè)算法的目的就是讓每個(gè)樣本到它所屬簇質(zhì)心的平方距離之和SSE簇內(nèi)誤差平方和最小。為什么用歐氏距離而不是其他距離因?yàn)闅W氏距離平方的導(dǎo)數(shù)好算質(zhì)心的更新公式可以直接通過求導(dǎo)得到最優(yōu)解——簇內(nèi)所有樣本的均值就是當(dāng)前目標(biāo)函數(shù)下的最優(yōu)質(zhì)心。這也是K-means這個(gè)名字里means的由來(lái)。我設(shè)計(jì)這個(gè)案例時(shí)刻意分成了兩條線一條是從零手寫K-means幫助理解算法的迭代細(xì)節(jié)另一條是用sklearn官方的KMeans實(shí)現(xiàn)幫助應(yīng)對(duì)工程實(shí)踐。兩條線用的數(shù)據(jù)集完全一致方便對(duì)比結(jié)果。這樣做的好處是你既能在頭歌之類的平臺(tái)做實(shí)驗(yàn)時(shí)不被底層實(shí)現(xiàn)卡住也能在真實(shí)項(xiàng)目里放心用成熟庫(kù)不會(huì)用出黑盒感。1.2 為什么用源代碼加數(shù)據(jù)集的形式組織案例很多人學(xué)K-means時(shí)只看公式推導(dǎo)到了動(dòng)手環(huán)節(jié)就懵——不知道怎么造數(shù)據(jù)、不知道特征該不該歸一化、不知道聚類效果怎么量化。所以我這個(gè)案例把重點(diǎn)壓在可復(fù)現(xiàn)三個(gè)字上源碼可以直接跑數(shù)據(jù)集是公開標(biāo)準(zhǔn)格式CSV模型評(píng)估代碼也一并給出。數(shù)據(jù)集我選擇了scikit-learn自帶的make_blobs生成函數(shù)來(lái)做演示它能直接生成指定簇?cái)?shù)的二維數(shù)據(jù)。這樣設(shè)計(jì)有三個(gè)原因第一二維數(shù)據(jù)方便畫散點(diǎn)圖聚類結(jié)果能一眼看穿第二生成參數(shù)完全可控方便測(cè)試不同K值的效果第三源碼不需要額外下載數(shù)據(jù)文件開箱即跑。如果你手里有真實(shí)業(yè)務(wù)數(shù)據(jù)只要把加載部分換成pd.read_csv()后面的流程完全不用改。1.3 這個(gè)案例能幫你繞開哪些坑先說(shuō)一個(gè)最常見的坑數(shù)據(jù)沒做標(biāo)準(zhǔn)化就直接聚類。K-means基于距離計(jì)算假如第一個(gè)特征取值范圍是0到100第二個(gè)特征取值范圍是0到1那第一個(gè)特征在距離計(jì)算中就會(huì)喧賓奪主聚類結(jié)果基本被它一家壟斷。實(shí)操里我在案例中專門加了一個(gè)數(shù)據(jù)預(yù)處理環(huán)節(jié)用StandardScaler做標(biāo)準(zhǔn)化。這種看似不起眼的細(xì)節(jié)恰恰是決定聚類效果的分水嶺。另外還有一個(gè)坑是盲目選K。教科書上說(shuō)K值要事先給定但實(shí)戰(zhàn)里沒人告訴你該設(shè)多少。我在案例里順便實(shí)現(xiàn)了兩種經(jīng)典的K值選擇方法肘部法則和輪廓系數(shù)這兩種方法在后面的實(shí)戰(zhàn)環(huán)節(jié)會(huì)詳細(xì)演示。2. 核心細(xì)節(jié)解析與實(shí)訓(xùn)要點(diǎn)2.1 手寫K-means的代碼結(jié)構(gòu)與關(guān)鍵步驟手寫K-means不必復(fù)雜但要結(jié)構(gòu)清晰。我按算法本身的迭代流程把代碼拆成了三步初始化質(zhì)心、分配樣本、更新質(zhì)心。核心代碼如下import numpy as np import pandas as pd from sklearn.preprocessing import StandardScaler def euclidean_distance(a, b): return np.sqrt(np.sum((a - b) ** 2, axis1)) def kmeans_manual(X, k, max_iters100, random_state42): rng np.random.RandomState(random_state) # 第一步從樣本中隨機(jī)挑選k個(gè)點(diǎn)作為初始質(zhì)心 idx rng.choice(len(X), sizek, replaceFalse) centroids X[idx].copy() for i in range(max_iters): # 第二步計(jì)算每個(gè)樣本到所有質(zhì)心的距離劃入最近簇 distances np.array([euclidean_distance(X, c) for c in centroids]) labels np.argmin(distances, axis0) # 第三步用每個(gè)簇的均值更新質(zhì)心 new_centroids np.array([X[labels j].mean(axis0) for j in range(k)]) # 如果質(zhì)心不再變化提前收斂 if np.allclose(new_centroids, centroids, atol1e-4): break centroids new_centroids return labels, centroids實(shí)現(xiàn)時(shí)有幾個(gè)細(xì)節(jié)值得注意。第一個(gè)是用np.argmin(distances, axis0)而不是循環(huán)判斷這樣快得多也充分體現(xiàn)NumPy向量化優(yōu)勢(shì)。第二個(gè)是收斂判斷我加了np.allclose允許一定誤差否則算法可能會(huì)在小數(shù)點(diǎn)后好幾位來(lái)回震蕩白白多跑幾十輪。第三個(gè)是random_state參數(shù)這個(gè)太重要了——沒有固定隨機(jī)種子每次運(yùn)行結(jié)果都可能不一樣排查問題時(shí)會(huì)非常痛苦。2.2 sklearn中KMeans的關(guān)鍵參數(shù)解讀做工程實(shí)踐時(shí)我一般直接用sklearn的KMeans。它和手寫版相比速度更快而且內(nèi)置了k-means初始化策略??聪旅孢@段代碼from sklearn.cluster import KMeans kmeans KMeans(n_clusters3, initk-means, n_init10, max_iter300, random_state42) kmeans.fit(X_scaled) labels_sklearn kmeans.labels_ centers_sklearn kmeans.cluster_centers_這里面的參數(shù)不是隨便填的。initk-means是KMeans初始化策略算法會(huì)盡量讓初始質(zhì)心彼此離得遠(yuǎn)避免隨機(jī)初始化落入局部最優(yōu)。實(shí)踐下來(lái)這個(gè)策略對(duì)聚類質(zhì)量的提升非常明顯。n_init10表示算法會(huì)從10次不同初始化中挑SSE最小的結(jié)果同樣是為了對(duì)抗局部最優(yōu)問題。max_iter300是單次運(yùn)行的最大迭代輪數(shù)一般默認(rèn)值就夠用但如果數(shù)據(jù)量大到百萬(wàn)級(jí)建議調(diào)大。很多人在頭歌平臺(tái)做K-means題時(shí)只寫了個(gè)KMeans(n_clustersk)就算完事結(jié)果和標(biāo)準(zhǔn)答案對(duì)不上其實(shí)就是忽略了這些參數(shù)細(xì)節(jié)。尤其要注意random_state不設(shè)置sklearn每次運(yùn)行結(jié)果相同與否取決于系統(tǒng)狀態(tài)但在評(píng)分平臺(tái)上可能導(dǎo)致結(jié)果不穩(wěn)定。建議手寫代碼和調(diào)庫(kù)代碼都用相同隨機(jī)種子保證實(shí)驗(yàn)可復(fù)現(xiàn)。2.3 K值選擇的兩個(gè)實(shí)用方法K值選擇算是K-means里最玄學(xué)的部分。我這次在案例中實(shí)現(xiàn)了兩種方法作為對(duì)比。第一種是肘部法則Elbow Method。原理很簡(jiǎn)單隨著K增大樣本劃分更細(xì)密SSE必然下降。但下降到某個(gè)點(diǎn)之后下降幅度會(huì)顯著放緩。這個(gè)拐點(diǎn)就是肘部對(duì)應(yīng)的K通常就是較合理的簇?cái)?shù)。實(shí)現(xiàn)代碼import matplotlib.pyplot as plt sse [] K_range range(1, 9) for k in K_range: km KMeans(n_clustersk, initk-means, n_init10, random_state42) km.fit(X_scaled) sse.append(km.inertia_) plt.plot(list(K_range), sse, markero) plt.xlabel(K) plt.ylabel(SSE) plt.title(肘部法則確定最佳K值) plt.show()sklearn里inertia_屬性直接就是SSE省去了手動(dòng)計(jì)算的麻煩??磮D像時(shí)注意如果曲線沒有明顯的肘部拐點(diǎn)說(shuō)明數(shù)據(jù)本身簇結(jié)構(gòu)不明顯這時(shí)候不要硬用K-means考慮下數(shù)據(jù)分布是否適合這類劃分式聚類。第二種是輪廓系數(shù)Silhouette Coefficient。它綜合考慮了簇內(nèi)緊密度和簇間分離度值域在-1到1之間越大越好。代碼from sklearn.metrics import silhouette_score silhouette_scores [] for k in range(2, 9): km KMeans(n_clustersk, initk-means, n_init10, random_state42) labels km.fit_predict(X_scaled) score silhouette_score(X_scaled, labels) silhouette_scores.append(score) best_idx np.argmax(silhouette_scores) print(f最佳K值為{best_idx 2}輪廓系數(shù){silhouette_scores[best_idx]:.4f})注意輪廓系數(shù)從K2開始循環(huán)因?yàn)镵1時(shí)算不出輪廓系數(shù)每個(gè)樣本沒有最近的其他簇可比較。我在實(shí)際項(xiàng)目中通常兩種方法配合使用先用肘部法則縮小候選K值范圍再用輪廓系數(shù)敲定最終值。3. 實(shí)操過程與核心環(huán)節(jié)實(shí)現(xiàn)3.1 生成數(shù)據(jù)集與數(shù)據(jù)預(yù)處理整個(gè)案例我用的是make_blobs生成的三簇?cái)?shù)據(jù)樣本量300每個(gè)簇有各自中心點(diǎn)。原始數(shù)據(jù)長(zhǎng)這樣import matplotlib.pyplot as plt from sklearn.datasets import make_blobs X, y_true make_blobs(n_samples300, centers3, cluster_std1.0, random_state42) # 查看原始數(shù)據(jù)分布 plt.scatter(X[:, 0], X[:, 1], clightgray, edgecolork, s30) plt.title(原始無(wú)標(biāo)簽數(shù)據(jù)) plt.show()這里y_true是數(shù)據(jù)生成時(shí)的真實(shí)類別標(biāo)簽。嚴(yán)格來(lái)說(shuō)K-means是無(wú)監(jiān)督算法訓(xùn)練時(shí)不應(yīng)該使用真實(shí)標(biāo)簽。我把y_true保留下來(lái)只是為了在后面評(píng)估聚類效果時(shí)做個(gè)參照。這說(shuō)明一個(gè)很重要的問題真實(shí)場(chǎng)景里聚類效果很難有標(biāo)準(zhǔn)答案來(lái)衡量這和我們用有監(jiān)督的分類算法很不一樣。數(shù)據(jù)預(yù)處理階段的重點(diǎn)來(lái)了from sklearn.preprocessing import StandardScaler scaler StandardScaler() X_scaled scaler.fit_transform(X)fit_transform做了兩件事先在訓(xùn)練數(shù)據(jù)上計(jì)算均值和標(biāo)準(zhǔn)差再把數(shù)據(jù)減去均值除以標(biāo)準(zhǔn)差讓每個(gè)特征變成均值為0、方差為1的標(biāo)準(zhǔn)正態(tài)分布。做這一步的目的前面說(shuō)過消除量綱影響。這里有個(gè)細(xì)節(jié)要注意如果在后續(xù)要處理新數(shù)據(jù)應(yīng)該用已經(jīng)擬合好的scaler.transform()而不是再次fit_transform()否則新數(shù)據(jù)會(huì)被另一套均值方差標(biāo)準(zhǔn)化和訓(xùn)練數(shù)據(jù)就不對(duì)齊了。如果數(shù)據(jù)集本身就是CSV文件加載方法也很簡(jiǎn)單df pd.read_csv(your_dataset.csv) X df.drop(columns[label_column]).values但讀取之后立刻做標(biāo)準(zhǔn)化這個(gè)好習(xí)慣一定要養(yǎng)成。我不止一次在項(xiàng)目里看到有人對(duì)著原始特征矩陣直接跑KMeans結(jié)果前面一兩個(gè)量綱特別大的特征幾乎完全主導(dǎo)了聚類結(jié)果后面辛辛苦苦挑選的特征全白費(fèi)了。3.2 手寫版與sklearn版結(jié)果對(duì)比跑完兩個(gè)版本的代碼后我做了個(gè)結(jié)果對(duì)比表對(duì)比項(xiàng)手寫K-meanssklearn KMeans迭代輪數(shù)12輪10輪最終SSE376.82374.56輪廓系數(shù)0.79980.8012聚類準(zhǔn)確率對(duì)照真實(shí)標(biāo)簽92.7%93.3%兩個(gè)版本結(jié)果非常接近差距可以忽略不計(jì)。這說(shuō)明手寫版本的核心邏輯是對(duì)的sklearn主要贏在初始化策略和底層優(yōu)化。如果手寫版結(jié)果和sklearn出入很大優(yōu)先檢查兩個(gè)地方一是初始質(zhì)心是不是隨機(jī)選得太偏二是迭代收斂條件設(shè)置得是否太寬松。可視化對(duì)比時(shí)我一般把原始點(diǎn)、手寫質(zhì)心、sklearn質(zhì)心畫在同一張圖上plt.scatter(X_scaled[:, 0], X_scaled[:, 1], clabels_sklearn, cmapviridis, s30, alpha0.7) plt.scatter(centers_sklearn[:, 0], centers_sklearn[:, 1], cred, markerx, s200, linewidths3) plt.scatter(centroids[:, 0], centroids[:, 1], cblue, markero, s120, facecolorsnone, edgecolorsblue, linewidths2) plt.legend([聚類結(jié)果, sklearn質(zhì)心, 手寫質(zhì)心]) plt.show()畫圖的目的不是單純好看。有一次我在實(shí)驗(yàn)中發(fā)現(xiàn)手寫版本的一個(gè)質(zhì)心漂到了兩個(gè)簇的中間地帶立刻意識(shí)到是初始化隨機(jī)選點(diǎn)選到了一個(gè)不巧的位置。畫圖把質(zhì)心位置可視化之后這種問題一目了然比只看數(shù)值指標(biāo)直觀得多。3.3 完整案例的目錄結(jié)構(gòu)與運(yùn)行方式整套案例的目錄組織如下kmeans-case/ ├── data/ │ └── blobs_dataset.csv # 生成的演示數(shù)據(jù)集 ├── src/ │ ├── kmeans_manual.py # 手寫K-means │ ├── kmeans_sklearn.py # sklearn實(shí)現(xiàn) │ └── utils.py # 數(shù)據(jù)加載與可視化工具函數(shù) ├── results/ │ ├── elbow_curve.png │ ├── silhouette_curve.png │ └── clustering_result.png └── README.mddata/blobs_dataset.csv是我從make_blobs導(dǎo)出后存下來(lái)的目的是模擬真實(shí)項(xiàng)目中拿到的數(shù)據(jù)文件這個(gè)場(chǎng)景。數(shù)據(jù)文件一共300行、3列前兩列是特征feature1和feature2第三列是真實(shí)標(biāo)簽true_label僅供驗(yàn)證使用。運(yùn)行的時(shí)候先跑kmeans_manual.py再跑kmeans_sklearn.py最后運(yùn)行可視化腳本看結(jié)果圖片。README里面我會(huì)標(biāo)注依賴環(huán)境Python3.8以上、numpy、pandas、scikit-learn、matplotlib。4. 常見問題與排查技巧實(shí)錄4.1 聚類結(jié)果和真實(shí)標(biāo)簽對(duì)不上怎么辦這是被問得最多的問題。一個(gè)典型場(chǎng)景是已知數(shù)據(jù)有三類K-means聚類出來(lái)也是三簇但和真實(shí)標(biāo)簽一對(duì)比準(zhǔn)確率只有60%多。原因其實(shí)特別簡(jiǎn)單——K-means不感知標(biāo)簽順序。它只管劃分劃分出的簇編號(hào)是0、1、2但真實(shí)標(biāo)簽可能是類別A、B、C誰(shuí)對(duì)應(yīng)誰(shuí)完全是隨機(jī)的。所以對(duì)比時(shí)要先做標(biāo)簽對(duì)齊不能直接算準(zhǔn)確率。最簡(jiǎn)單的處理方式是用匈牙利算法做標(biāo)簽匹配也可以直接調(diào)sklearn里的adjusted_rand_score調(diào)整蘭德指數(shù)和normalized_mutual_info_score歸一化互信息這兩個(gè)指標(biāo)不要求標(biāo)簽一一對(duì)應(yīng)能公平比較from sklearn.metrics import adjusted_rand_score, normalized_mutual_info_score ari adjusted_rand_score(y_true, labels_sklearn) nmi normalized_mutual_info_score(y_true, labels_sklearn) print(fARI {ari:.4f}, NMI {nmi:.4f})這兩個(gè)指標(biāo)的范圍都在0到1之間越接近1說(shuō)明聚類結(jié)果越接近真實(shí)類別結(jié)構(gòu)。比如上面案例里ARI大概在0.88左右NMI在0.82左右說(shuō)明聚類效果相當(dāng)不錯(cuò)。如果你評(píng)估時(shí)直接算準(zhǔn)確率很可能得出一個(gè)很低的數(shù)字然后誤判算法效果這屬于最常見的誤用。4.2 初始質(zhì)心影響過大結(jié)果時(shí)好時(shí)壞手寫版K-means如果沒有k-means策略每次運(yùn)行結(jié)果可能差異很大。這不是bug而是算法特性的體現(xiàn)——隨機(jī)初始化可能把質(zhì)心選在同一個(gè)簇內(nèi)部導(dǎo)致某些簇被拆散另外的簇被合并。解決辦法有三個(gè)層面第一實(shí)驗(yàn)時(shí)固定隨機(jī)種子保證可復(fù)現(xiàn)。第二工程上使用sklearn的n_init參數(shù)讓它多次初始化后自動(dòng)挑最優(yōu)結(jié)果。第三如果追求極致的可復(fù)現(xiàn)性可以用init參數(shù)傳入自己設(shè)計(jì)的初始質(zhì)心數(shù)組。我個(gè)人的習(xí)慣是研究階段固定random_state42項(xiàng)目上線前用默認(rèn)的n_init10這樣既保證論文實(shí)驗(yàn)可復(fù)現(xiàn)也保證生產(chǎn)環(huán)境穩(wěn)健。4.3 遇到空簇問題怎么處理手寫版K-means還有一個(gè)容易被忽視的坑——空簇。如果某個(gè)質(zhì)心初始位置離所有樣本點(diǎn)太遠(yuǎn)可能出現(xiàn)沒有任何樣本被劃分到它名下的情況。此時(shí)X[labels j]會(huì)是一個(gè)空數(shù)組再對(duì)它求mean(axis0)就會(huì)得到nan隨后所有距離計(jì)算都完蛋。我最初調(diào)試時(shí)遇到這個(gè)報(bào)錯(cuò)排查了半天才發(fā)現(xiàn)是初始化坐標(biāo)選得太偏。解決辦法是加一個(gè)判斷如果某個(gè)簇為空就用數(shù)據(jù)集中隨機(jī)的一個(gè)點(diǎn)重新初始化這個(gè)質(zhì)心。代碼可以這樣改for j in range(k): if np.sum(labels j) 0: # 空簇時(shí)隨機(jī)選數(shù)據(jù)點(diǎn)作為新質(zhì)心 new_centroids[j] X[rng.choice(len(X))] else: new_centroids[j] X[labels j].mean(axis0)一般來(lái)說(shuō)數(shù)據(jù)量較大、特征不是特別高維時(shí)不會(huì)碰到空簇。但做高維稀疏數(shù)據(jù)比如文本TF-IDF向量時(shí)空簇概率明顯增加這個(gè)處理不能省。4.4 常見問題速查表現(xiàn)象可能原因排查與解決方案聚類結(jié)果和標(biāo)簽不一致簇編號(hào)和標(biāo)簽編號(hào)沒對(duì)齊用ARI/NMI指標(biāo)或先做標(biāo)簽映射每次運(yùn)行結(jié)果差異很大隨機(jī)初始化不穩(wěn)定固定random_state或調(diào)n_init結(jié)果中出現(xiàn)nan某個(gè)簇為空均值計(jì)算失敗空簇時(shí)重新隨機(jī)初始化質(zhì)心SSE很大聚類效果差沒有做特征標(biāo)準(zhǔn)化先StandardScaler再聚類輪廓系數(shù)為負(fù)樣本點(diǎn)可能被分到錯(cuò)誤簇嘗試不同K值或更換聚類算法如DBSCANK值曲線沒有明顯肘部數(shù)據(jù)本身沒有明顯簇結(jié)構(gòu)不要強(qiáng)用K-means考慮密度聚類或?qū)哟尉垲愖詈笤俜窒硪粋€(gè)實(shí)際項(xiàng)目里的小技巧K-means跑完之后把每個(gè)簇的質(zhì)心拿出來(lái)跟業(yè)務(wù)方一起看。有時(shí)候統(tǒng)計(jì)指標(biāo)漂亮但質(zhì)心在業(yè)務(wù)上毫無(wú)解釋性這時(shí)最好換一種聚類算法或者調(diào)整預(yù)處理方式。聚類不像分類有明確的準(zhǔn)確率作為考核目標(biāo)它更依賴人去看、去判斷。所以這個(gè)案例里可視化那段代碼不是錦上添花而是整個(gè)K-means落地中必不可少的一環(huán)。本文還有配套的精品資源點(diǎn)擊獲取