利用Python實現(xiàn)K-Means聚類的方法實例(案例:用戶分類)
K-Means聚類算法介紹
K-Means又稱為K均值聚類算法,屬于聚類算法中的一種,而聚類算法在機器學習算法中屬于無監(jiān)督學習,在業(yè)務中常常會結合實際需求與業(yè)務邏輯理解來完成建模;
無監(jiān)督學習:訓練時只需要特征矩陣X,不需要標簽;
K-Means聚類算法基礎原理
K-Means聚類算法是聚類算法家族中的典型代表,同時也是最簡單的算法,接下來為大家簡單地介紹聚類算法基本原理:
將一組存在N個樣本的特征矩陣X劃分為K個無交集的簇,每一個簇中含有多個數(shù)據(jù),每一個數(shù)據(jù)代表著一個樣本,在同一個簇中的數(shù)據(jù)即被算法認為是同一類;
- N:假設為樣本數(shù)量;
- K:假設為聚類簇的數(shù)量;
- 簇:類似于集合,也可以通俗地理解成一個小組,不同小組等于不同分類;
而一個簇中的所有數(shù)據(jù)的均值,被稱為這個簇的質心,質心的維度與特征矩陣X的維度相同,如特征矩陣X是三維數(shù)據(jù)集,質心也就是一個三維的坐標,如此類推至更高維度;
K-Means聚類算法實現(xiàn)流程
步驟一:隨機在N個樣本中抽取K個作為初始的質心;
步驟二:開始遍歷除開質心外的所有樣本點,將其分配至距離它們最近的質心,每一個質心以及被分配至其下的樣本點視為一個簇(或者說一個分類),這樣便完成了一次聚類;
步驟三:對于每一個簇,重新計算簇內所有樣本點的平均值,取結果為新的質心;
步驟四:比對舊的質心與新的質心是否再發(fā)生變化,若發(fā)生變化,按照新的質心從步驟二開始重復,若沒發(fā)生變化,聚類完成;
關鍵要點:不斷地為樣本點尋找質心,然后更新質心,直至質心不再變化;
開始做一個簡單的聚類
環(huán)境說明:本文實際案例中使用Jupyter環(huán)境下運行(安裝與使用可自行百度);
數(shù)據(jù)導入
做數(shù)據(jù)分析前,首先第一步是導入數(shù)據(jù),可以利用pandas內的read_csv函數(shù)來導入數(shù)據(jù);
首先,導入所需要用到的類,并使用read_csv函數(shù)導入案例數(shù)據(jù):
import numpy as np import pandas as pd data = pd.read_csv(r'D:\Machine_learning\KMeans\client_data.csv') # 使用pandas中的read_csv函數(shù)導入數(shù)據(jù)集后,默認格式為DataFrame # 直接查看當前數(shù)據(jù)集長什么樣子 data.head()
數(shù)據(jù)打開后會發(fā)現(xiàn)大概長這樣:
交易額 成交單量 最近交易時間
0 76584.92 294 64
1 94581.00 232 1
2 51037.60 133 1
3 43836.00 98 1
4 88032.00 95 2
# 若表頭項為中文時,可能出現(xiàn)亂碼情況,請自行百度解決,或直接修改為英文;
數(shù)據(jù)探索
先探索數(shù)據(jù)類型:
# 探索數(shù)據(jù)類型 data.info() # 輸出結果: <class 'pandas.core.frame.DataFrame'> RangeIndex: 8011 entries, 0 to 8010 Data columns (total 3 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 交易額 8011 non-null float64 1 成交單量 8011 non-null int64 2 最近交易時間 8011 non-null int64 dtypes: float64(1), int64(2) memory usage: 187.9 KB
- 共8011個數(shù)據(jù)樣本,3個維度列(2個整數(shù)、1個浮點數(shù)),且無缺失數(shù)據(jù);
- 數(shù)據(jù)背景:從三個維度獲取所有用戶交易180天內交易數(shù)據(jù)(數(shù)據(jù)獲取、清洗規(guī)則在此不作詳細說明);
- 第一列:索引(read_csv函數(shù)導入數(shù)據(jù)時會自動生成索引,若數(shù)據(jù)集本身自帶索引,可設置參數(shù)index_=0,代表數(shù)據(jù)集中第一列為索引);
- 第二列:180天內交易額,浮點數(shù);
- 第三列:180天內成交單量,整數(shù);
- 第四列:最近成交訂單的日期與當前日期差,整數(shù)(180內無數(shù)據(jù)按照180運算);
由于sklearn中K-Means聚類算法僅支持二維數(shù)組運算,所以要先將數(shù)據(jù)集轉化為二維數(shù)組:
data = np.array(data,type(float)) # 查看數(shù)據(jù)集 data —————————————————————————————————————————————————— # 輸出結果: array([[76584.92, 294.0, 64.0], [94581.0, 232.0, 1.0], [51037.6, 133.0, 1.0], ..., [0.0, 0.0, 180.0], [0.0, 0.0, 180.0], [0.0, 0.0, 180.0]], dtype=object)
查看數(shù)組結構:
# 查看數(shù)組結構 data.shape ? —————————————————————————————————————————————————— # 輸出結果: (8011, 3)
開始聚類
數(shù)據(jù)集導入完成后,現(xiàn)在調用sklearn完成簡單的聚類:
from sklearn.cluster import KMeans X = data # 實例化K-Means算法模型,先使用5個簇嘗試聚類 cluster = KMeans(n_clusters=5, random_state=0) # 使用數(shù)據(jù)集X進行訓練 cluster = cluster.fit(X) # 調用屬性labels_,查看聚類結果 cluster.labels_ —————————————————————————————————————————————————— # 輸出結果: array([4, 4, 1, ..., 0, 0, 0])
- 參數(shù)n_clusters:
設定聚類的目標簇數(shù)量,本次聚類先用5個簇嘗試; - 參數(shù)random_state:
設定隨機數(shù)種子,若不設定則每次聚類時都會使用不同的隨機質心; - 接口fit():
使用數(shù)據(jù)集對模型進行訓練; - 屬性labels_:
查看訓練后,每一樣本的預測分類結果;
查看輸出結果
查看輸出結果的數(shù)組結構:
# 查看預測結果的數(shù)據(jù)結構 cluster.labels_.shape —————————————————————————————————————————————————— # 輸出結果: (8011,)
分類結果的數(shù)組結構為(8011,),剛好對應著8011個樣本的預測分類結果;
再次確認目標分類結果只有5類,可以使用numpy中的unique()函數(shù)實現(xiàn):
# 查看數(shù)組中存在的類別(對一維數(shù)組去重) np.unique(cluster.labels_) ———————————————————————————————————————— # 輸出結果: array([0, 1, 2, 3, 4])
輸出結果0~4中分別代表著5個不同的分類;
查看預測結果中每一分類的數(shù)量:
# 查看每一分類結果的數(shù)量 pd.value_counts(cluster.labels_) —————————————————————————————————————————— 0 7068 2 688 4 198 1 38 3 19 dtype: int64
分類為0的數(shù)據(jù)占比較大(約88%),這部分數(shù)據(jù)數(shù)據(jù)實際行業(yè)應用中的長尾數(shù)據(jù),這類用戶對平臺幾乎沒有任何價值貢獻;
聚類質心
聚類質心代表每一個分類簇的中心,某種意義上講,質心坐標可以代表著這一個簇的普遍特征,質心可以通過調用屬性cluster_centers_來查看:
# 查看質心 cluster.cluster_centers_ —————————————————————————————————————————————————————————— # 輸出結果: array([[3.40713759e+02, 7.43350311e-01, 1.48025750e+02], [4.30125087e+04, 4.70000000e+01, 2.03947368e+01], [6.06497324e+03, 9.37354651e+00, 3.55159884e+01], [7.57037853e+04, 7.84736842e+01, 1.52631579e+01], [1.80933537e+04, 2.34040404e+01, 1.49444444e+01]])
輸出結果中分別對應著0~4五種分類的普遍數(shù)據(jù)特征;
K-Means聚類算法的評估指標
當我們完成聚類建模后,怎么知道聚類的效果好不好,這時我們便需要「評估指標」來評價模型的優(yōu)劣,并根據(jù)此來調整參數(shù);
對于聚類算法的評估指標,從大方向上區(qū)分為兩種:真實標簽已知與真實標簽未知;
真實標簽已知
即我們對于每一個樣本的標簽Y都是已知的,但是這種情況在實際的業(yè)務中幾乎是不存在的,若標簽已知,使用分類算法(如隨機森林、SVM等)在各個方面來說都會更加合適;
- 調整蘭德系數(shù):
在sklearn中的類為sklearn.metrics.adjusted_rand_score(y_true, y_pred)
y_true:代表測試集中一個樣本的真實標簽;
y_pred:使用測試集中樣本調用預測接口的預測結果(上文中使用的cluster.labels_);
調整蘭德系數(shù)的取值在[-1,1]:數(shù)值越接近1越好,大于0時聚類效果較為優(yōu)秀,小于0時代表簇內差異巨大甚至相互獨立,模型幾乎不可用;
由于案例數(shù)據(jù)集中真實標簽是未知的,故不在此展示;
真實標簽未知
即我們對每一個樣本的標簽Y都是未知的,我們事先不知道每一個樣本是屬于什么分類,這種情況才是符合我們實際業(yè)務中真實使用聚類算法的場景;
- 輪廓系數(shù)系數(shù):
在sklearn中的類為:
返回輪廓系數(shù)的均值:sklearn.metrics.silhouette_score(X, y_pred);
返回數(shù)據(jù)集中每個樣本自身的輪廓系數(shù):sklearn.metrics.silhouette_sample(X, y_pred);
輪廓系數(shù)的取值在(-1,1):
對于某一樣本點來說,當值越接近1時就代表自身與所在的簇中其他樣本越相似,并且與其他簇中的樣本不相似,而當值越接近-1時則代表與上述內容相反;綜述,輪廓系數(shù)越接近1越好,負數(shù)則表示聚類效果非常差;
那接下來看看輪廓系數(shù)在剛才的聚類中效果如何:
# 導入輪廓系數(shù)所需要的庫 from sklearn.metrics import silhouette_score from sklearn.metrics import silhouette_samples # 查看輪廓系數(shù)均值 silhouette_score(X,cluster.labels_) —————————————————————————————————————————————————— # 輸出結果 0.8398497410297728 —————————————————————————————————————————————————— # 查看每一樣本輪廓系數(shù) silhouette_samples(X,cluster.labels_) —————————————————————————————————————————————————— # 輸出結果 array([0.94301872, 0.94301872, 0.94301872, ..., 0.64706719, 0.60820687, 0.58272791]) —————————————————————————————————————————————————— # 查看樣本輪廓系數(shù)結果的數(shù)組結構 silhouette_samples(X,cluster.labels_).shape —————————————————————————————————————————————————— # 輸出結果 (8011,)
本次聚類的輪廓系數(shù)為0.84,表示聚類效果良好;
樣本輪廓系數(shù)的數(shù)據(jù)結構可以看出:數(shù)組中每一個輸出結果對應著每一個樣本的輪廓系數(shù),共8011個;
- 卡林斯基-哈拉巴斯指數(shù):
sklearn中的類:sklearn.metrics.calinski_haabasz_score (X, y_pred);
卡林斯基-哈拉巴斯指數(shù)的數(shù)值無上限,且對于模型效果來說越高越好,而由于無上限的特性,導致只能用作對比,而無法快速知曉模型效果是否好;
可以看看輪廓系數(shù)在剛才的聚類中效果如何:
# 調用所需要的類 from sklearn.metrics import calinski_harabasz_score calinski_harabasz_score(X,cluster.labels_) ——————————————————————————————————————————————————————— # 輸出結果 31777.971149699857
輸出的結果為31778,那究竟效果如何?因為沒有對照組,所以無法得知,如果有興趣的小伙伴可以在調整參數(shù)的時候使用對照組試試效果;
實用案例:基于輪廓系數(shù)來選擇最佳的n_clusters
需要繪制輪廓系數(shù)分布圖,先導入所需用到的庫:
import matplotlib.pyplot as plt import matplotlib.cm as cm
繪制輪廓系數(shù)分布圖
使用for循環(huán)分別對2~8個簇的情況畫出輪廓系數(shù)分布圖:
for n_clusters in [2,3,4,5,6,7,8]: n_clusters = n_clusters # 設置畫布 fig, ax1 = plt.subplots(1) # 設置畫布尺寸 fig.set_size_inches(18, 7) # 設置畫布X軸 ax1.set_xlim([-0.1, 1]) # 設置畫布Y軸:X.shape[0]代表著柱狀的寬度,(n_clusters + 1) * 10代表著柱與柱之間的間隔 ax1.set_ylim([0, X.shape[0] + (n_clusters + 1) * 10]) # 模型實例化 clusterer = KMeans(n_clusters=n_clusters, random_state=100) # 開始訓練模型 clusterer = clusterer.fit(X) # 提取訓練結果中的預測標簽 cluster_labels = clusterer.labels_ # 提取訓練結果中的輪廓系數(shù)均值 silhouette_avg = silhouette_score(X, cluster_labels) # 打印出當前的簇數(shù)與輪廓系數(shù)均值 print("簇數(shù)為", n_clusters, ",輪廓系數(shù)均值為", silhouette_avg) # 提取每一個樣本的輪廓系數(shù) sample_silhouette_values = silhouette_samples(X, cluster_labels) # 設置Y軸的起始坐標 y_lower = 10 # 添加一個循環(huán),把每一個樣本的輪廓系數(shù)畫在圖中 for i in range(n_clusters): # 提取第i個簇下的所有樣本輪廓系數(shù) ith_cluster_silhouette_values = sample_silhouette_values[cluster_labels == i] # 對樣本的輪廓系數(shù)進行排序(降序) ith_cluster_silhouette_values.sort() # 設置當前簇的柱狀寬度(使用樣本數(shù)量)以便于設置下一個簇的起始坐標 size_cluster_i = ith_cluster_silhouette_values.shape[0] # 設置Y軸第i個簇的起始坐標 y_upper = y_lower + size_cluster_i # 設置顏色 color = cm.nipy_spectral(float(i)/n_clusters) # 畫圖 ax1.fill_betweenx(np.arange(y_lower, y_upper) ,ith_cluster_silhouette_values ,facecolor=color ,alpha=0.7 ) ax1.text(-0.05 , y_lower + 0.5 * size_cluster_i , str(i)) y_lower = y_upper + 10 # 設置圖的標題 ax1.set_title("The silhouette plot for the various clusters.") ax1.set_xlabel("The silhouette coefficient values") ax1.set_ylabel("Cluster label") # 添加輪廓系數(shù)均值線,使用虛線 ax1.axvline(x=silhouette_avg, color="red", linestyle="--") ax1.set_yticks([]) ax1.set_xticks([-0.1, 0, 0.2, 0.4, 0.6, 0.8, 1]) plt.show()
結果對比
輸出結果:
簇數(shù)為 2 ,輪廓系數(shù)均值為 0.9348704011138467:
簇數(shù)為 3 ,輪廓系數(shù)均值為 0.8889120986545176:
簇數(shù)為 4 ,輪廓系數(shù)均值為 0.8432045328349393:
簇數(shù)為 5 ,輪廓系數(shù)均值為 0.8397653971050274:
簇數(shù)為 6 ,輪廓系數(shù)均值為 0.8217141668609508:
簇數(shù)為 7 ,輪廓系數(shù)均值為 0.7995236853252528:
簇數(shù)為 8 ,輪廓系數(shù)均值為 0.7995236853252528:
從本次的輸出結果中可知,當簇數(shù)量為2時,會存在最大的輪廓系數(shù)均值,是否簇數(shù)量為2就是最佳的參數(shù)呢?
答案必須是否定的,我們可以通過輪廓系數(shù)分部圖看到,基本上每一個圖內都會有一片面積很大的塊,這就是長尾數(shù)據(jù)帶來的,因為他們基本都集中在一個點上,所以導致整體輪廓系數(shù)均值“被平均”得很大,這樣的狀況也是很多實際業(yè)務數(shù)據(jù)中常常會碰到的;
優(yōu)化方案選擇
既然由于長尾數(shù)據(jù)對輪廓系數(shù)帶來較大偏差,那咱們的思路可以把長尾數(shù)據(jù)剔除掉,僅計算非長尾數(shù)據(jù)(數(shù)據(jù)分析需要在不同的具體場景下有不同的思路,以下僅是一種思路舉例);
當簇數(shù)量為3時:
# 實例化,訓練模型 n_clusters = 3 clusterer = KMeans(n_clusters=n_clusters, random_state=100) clusterer = clusterer.fit(X) # 查看訓練結果 pd.value_counts(clusterer.labels_) ———————————————————————————————————————————————————————————— # 輸出結果 0 7599 1 362 2 50 dtype: int64
長尾數(shù)據(jù)所在的簇為0,計算非長尾數(shù)據(jù)的輪廓系數(shù)均值:
cluster_labels = clusterer.labels_ print(np.average(silhouette_samples(X, cluster_labels)[cluster_labels != 0])) —————————————————————————————————————————————————————————————————————————————— # 輸出結果 0.4909204497858037
當簇數(shù)量為4時:
# 實例化,訓練,并查看結果分布 n_clusters = 4 clusterer = KMeans(n_clusters=n_clusters, random_state=100) clusterer = clusterer.fit(X) pd.value_counts(clusterer.labels_) ———————————————————————————————————————————————————————————— # 輸出結果 0 7125 3 663 2 179 1 44 dtype: int64 ———————————————————————————————————————————————————————————— # 計算非長尾數(shù)據(jù)的輪廓系數(shù)均值 cluster_labels = clusterer.labels_ print(np.average(silhouette_samples(X, cluster_labels)[cluster_labels != 0])) ———————————————————————————————————————————————————————————— # 輸出結果 0.4766824917258095
當簇數(shù)量為5時:
# 實例化,訓練,并查看結果分布 n_clusters = 5 clusterer = KMeans(n_clusters=n_clusters, random_state=100) clusterer = clusterer.fit(X) pd.value_counts(clusterer.labels_) ———————————————————————————————————————————————————————————— # 輸出結果 2 7065 0 691 3 198 1 38 4 19 dtype: int64 ———————————————————————————————————————————————————————————— # 計算非長尾數(shù)據(jù)的輪廓系數(shù)均值 cluster_labels = clusterer.labels_ print(np.average(silhouette_samples(X, cluster_labels)[cluster_labels != 2])) ———————————————————————————————————————————————————————————— # 輸出結果 0.49228555254491085
當簇數(shù)量為6時:
# 實例化,訓練,并查看結果分布 n_clusters = 6 clusterer = KMeans(n_clusters=n_clusters, random_state=100) clusterer = clusterer.fit(X) pd.value_counts(clusterer.labels_) ———————————————————————————————————————————————————————————— # 輸出結果 0 6806 5 799 3 252 2 99 1 36 4 19 dtype: int64 ———————————————————————————————————————————————————————————— # 計算非長尾數(shù)據(jù)的輪廓系數(shù)均值 cluster_labels = clusterer.labels_ print(np.average(silhouette_samples(X, cluster_labels)[cluster_labels != 0])) ———————————————————————————————————————————————————————————— # 輸出結果 0.5043196493336838
當簇數(shù)量為7時:
# 實例化,訓練,并查看結果分布 n_clusters = 7 clusterer = KMeans(n_clusters=n_clusters, random_state=100) clusterer = clusterer.fit(X) pd.value_counts(clusterer.labels_) ———————————————————————————————————————————————————————————— # 輸出結果 0 6374 5 931 6 387 2 188 1 76 4 36 3 19 dtype: int64 ———————————————————————————————————————————————————————————— # 計算非長尾數(shù)據(jù)的輪廓系數(shù)均值 cluster_labels = clusterer.labels_ print(np.average(silhouette_samples(X, cluster_labels)[cluster_labels != 0])) ———————————————————————————————————————————————————————————— # 輸出結果 0.501667625921486
當簇數(shù)量為8時:
# 實例化,訓練,并查看結果分布 n_clusters = 8 clusterer = KMeans(n_clusters=n_clusters, random_state=100) clusterer = clusterer.fit(X) pd.value_counts(clusterer.labels_) ———————————————————————————————————————————————————————————— # 輸出結果 0 6411 5 927 4 372 2 172 6 74 1 32 7 13 3 10 dtype: int64 ———————————————————————————————————————————————————————————— # 計算非長尾數(shù)據(jù)的輪廓系數(shù)均值 cluster_labels = clusterer.labels_ print(np.average(silhouette_samples(X, cluster_labels)[cluster_labels != 0])) ———————————————————————————————————————————————————————————— # 輸出結果 0.4974116370311323
對比上述結果,當n_clusters=6時,輪廓系數(shù)均值存在最大值0.5043;
這時查看質心的坐標:
# 設置參數(shù)n_clusters=6再次訓練模型 n_clusters = 6 clusterer = KMeans(n_clusters=n_clusters, random_state=100) clusterer = clusterer.fit(X) # 使用屬性cluster_centers_查看質心坐標 clusterer.cluster_centers_ —————————————————————————————————————————————————————————————— # 輸出結果 array([[2.50559675e+02, 5.95944755e-01, 1.51620923e+02], [4.36372269e+04, 4.83333333e+01, 2.06111111e+01], [2.23257222e+04, 2.73232323e+01, 1.72020202e+01], [1.15493973e+04, 1.65515873e+01, 1.95833333e+01], [7.57037853e+04, 7.84736842e+01, 1.52631579e+01], [4.25642288e+03, 6.82227785e+00, 4.39336671e+01]]) —————————————————————————————————————————————————————————————— # 查看聚類結果分布 pd.value_counts(clusterer.labels_) —————————————————————————————————————————————————————————————— # 輸出結果 0 6806 5 799 3 252 2 99 1 36 4 19 dtype: int64 —————————————————————————————————————————————————————————————— # 聚類結果分布以百分比形式顯示 pd.value_counts(clusterer.labels_,normalize=True) —————————————————————————————————————————————————————————————— # 輸出結果 0 0.849582 5 0.099738 3 0.031457 2 0.012358 1 0.004494 4 0.002372 dtype: float64
從結果可得(數(shù)據(jù)結果為科學計數(shù)法),6個類別客戶的畫像特征分別對應著:
- 分類0——6806位——占比85%:
交易額:251元,平均單量:0.6單,最近交易時間:152天前; - 分類1——36位——占比0.4%:
交易額:43637元,平均單量:48單,最近交易時間:21天前; - 分類2——99位——占比1.2%:
交易額:22325元,平均單量:27單,最近交易時間:17天前; - 分類3——252位——占比3.1%:
交易額:11549元,平均單量:17單,最近交易時間:20天前; - 分類4——19位——占比0.2%:
交易額:75703元,平均單量:78單,最近交易時間:15天前; - 分類5——799位——占比10%:
交易額:4256元,平均單量:7單,最近交易時間:44天前;
到此這篇關于利用Python實現(xiàn)K-Means聚類的文章就介紹到這了,更多相關Python實現(xiàn)K-Means聚類內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Pytorch通過保存為ONNX模型轉TensorRT5的實現(xiàn)
這篇文章主要介紹了Pytorch通過保存為ONNX模型轉TensorRT5的實現(xiàn),具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-05-05python分布式庫celery處理大規(guī)模的任務并行化
Python中的分布式任務隊列時,Celery是一個備受推崇的工具,它是一個功能強大的分布式系統(tǒng),可用于處理大規(guī)模的任務并行化,本文將介紹Celery的基本概念、用法和示例代碼,幫助讀者更好地了解和使用這個庫2024-01-01vscode寫python時的代碼錯誤提醒和自動格式化的方法
這篇文章主要介紹了vscode寫python時的代碼錯誤提醒和自動格式化的方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-05-05如何用scheduler實現(xiàn)learning-rate學習率動態(tài)變化
這篇文章主要介紹了如何用scheduler實現(xiàn)learning-rate學習率動態(tài)變化問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-09-09