找回密碼
 註冊
搜索
查看: 1856|回復: 0

[教學] KMeans:能從資料中找出 K 個分類的非監督式機器學習演算法

[複製鏈接]
發表於 2021-6-1 21:33:04 | 顯示全部樓層 |閱讀模式
 
Push to Facebook
使用 scikit-learn 與 Python

例子:


  1. import matplotlib.pyplot as plt
  2. from sklearn.datasets import make_blobs
  3. from sklearn.cluster import KMeans
  4. # 隨機產生 10 組 2 features 的資料 500 筆 (dy 即原始目標值 label 0~9)
  5. dx, dy = make_blobs(n_samples=500, n_features=2, centers=10, random_state=42)
  6. # 用 KMeans 在資料中找出 5 個分組
  7. kmeans = KMeans(n_clusters=5)
  8. kmeans.fit(dx)
  9. # 預測新的目標值 label
  10. new_dy = kmeans.predict(dx)
  11. plt.rcParams['font.size'] = 14
  12. plt.figure(figsize=(16, 8))
  13. # 以不同顏色畫出原始的 10 群資料
  14. plt.subplot(121)
  15. plt.title('Original data (10 groups)')
  16. plt.scatter(dx.T[0], dx.T[1], c=dy, cmap=plt.cm.Set1)
  17. # 根據重新分成的 5 組來畫出資料
  18. plt.subplot(122)
  19. plt.title('KMeans=5 groups')
  20. plt.scatter(dx.T[0], dx.T[1], c=new_dy, cmap=plt.cm.Set1)
  21. # 顯示圖表
  22. plt.tight_layout()
  23. plt.show()
複製代碼

KMeans 非監督式 機器學習 演算法 Python

KMeans 非監督式 機器學習 演算法 Python

藉由顏色的識別,很容易看出資料從 10 組變成 5 組了。也就是說,你可以用 new_dy 取代原本的目標值 dy,等於是簡化了標籤數量。

上面之所以只有 2 個 features(即 2 個自變數),是因為這樣才能畫成二維圖表。增加到 3 個可畫成三維,來挑戰一下:


  1. import matplotlib.pyplot as plt
  2. from mpl_toolkits.mplot3d import Axes3D # matplotlib 3.2.0 後可省略
  3. from sklearn.datasets import make_blobs
  4. from sklearn.cluster import KMeans
  5. dx, dy = make_blobs(n_samples=500, n_features=3, centers=10, random_state=42)
  6. kmeans = KMeans(n_clusters=5)
  7. kmeans.fit(dx)
  8. new_dy = kmeans.predict(dx)
  9. plt.rcParams['font.size'] = 14
  10. fig = plt.figure(figsize=(16, 8))
  11. ax = fig.add_subplot(121, projection='3d')
  12. plt.title('Original data (10 groups)')
  13. ax.scatter(dx.T[0], dx.T[1], dx.T[2], c=dy, cmap=plt.cm.Set1)
  14. ax = fig.add_subplot(122, projection='3d')
  15. plt.title('KMeans=5 groups')
  16. ax.scatter(dx.T[0], dx.T[1], dx.T[2], c=new_dy, cmap=plt.cm.Set1)
  17. plt.show()
複製代碼

KMeans 非監督式 機器學習 演算法 Python

KMeans 非監督式 機器學習 演算法 Python


但 K 值要指定為多少才是合適的呢?說什麼非監督式學習,結果還不是要自己一個個試啊?特別是 make_blobs() 每次產生的資料還不一樣呢,一重新執行資料就跑掉了(除非你加上 random_state 參數來固定它)。

決定最佳 K 值的方式有幾種,scikit-learn 能做的有手肘法(看誤差平方和)和看側影係數。手肘法讓人有點無所適從,用側影係數比較簡單,找最大的值就行了。但這邊還是把兩者一併畫出來:


  1. import matplotlib.pyplot as plt
  2. from sklearn.datasets import make_blobs
  3. from sklearn.metrics import silhouette_score
  4. from sklearn.cluster import KMeans
  5. colors = ['red', 'orange', 'yellow', 'green', 'cyan',
  6.           'blue', 'purple', 'brown', 'grey', 'black']
  7. # 產生的資料組數 (10)
  8. clusters = 10
  9. # K 值的範圍 (2~10)
  10. k_range = range(2, clusters + 1)
  11. dx, dy = make_blobs(n_samples=500, n_features=2, centers=clusters, random_state=42)
  12. distortions = []
  13. scores = []
  14. # 記錄每種 K 值建出的 KMeans 模型的成效
  15. for i in k_range:
  16.     kmeans = KMeans(n_clusters=i).fit(dx)
  17.     distortions.append(kmeans.inertia_) # 誤差平方和 (SSE)
  18.     scores.append(silhouette_score(dx, kmeans.predict(dx))) # 側影係數
  19. # 找出最大的側影係數來決定 K 值
  20. selected_K = scores.index(max(scores)) + 2
  21. # 重新建立 KMeans 模型並預測目標值
  22. kmeans = KMeans(n_clusters=selected_K).fit(dx)
  23. new_dy = kmeans.predict(dx)
  24. # 新分組的資料中心點
  25. centers = kmeans.cluster_centers_
  26. plt.rcParams['font.size'] = 12
  27. plt.figure(figsize=(12, 12))
  28. # 原始資料分組
  29. plt.subplot(221)
  30. plt.title(f'Original data ({clusters} groups)')
  31. plt.scatter(dx.T[0], dx.T[1], c=dy, cmap=plt.cm.Set1)
  32. # 新資料分組
  33. plt.subplot(222)
  34. plt.title(f'KMeans={selected_K} groups')
  35. plt.scatter(dx.T[0], dx.T[1], c=new_dy, cmap=plt.cm.Set3)
  36. plt.scatter(centers.T[0], centers.T[1], marker='^', color='orange')
  37. for i in range(centers.shape[0]): # 標上各分組中心點
  38.     plt.text(centers.T[0][i], centers.T[1][i], str(i + 1),
  39.              fontdict={'color': 'red', 'weight': 'bold', 'size': 24})
  40. # 繪製誤差平方和圖 (手肘法)
  41. plt.subplot(223)
  42. plt.title('SSE (elbow method)')
  43. plt.plot(k_range, distortions)
  44. plt.plot(selected_K, distortions[selected_K - 2], 'go') # 最佳解
  45. # 繪製係數圖
  46. plt.subplot(224)
  47. plt.title('Silhouette score')
  48. plt.plot(k_range, scores)
  49. plt.plot(selected_K, scores[selected_K - 2], 'go') # 最佳解
  50. plt.tight_layout()
  51. plt.show()
複製代碼

KMeans 非監督式 機器學習 演算法 Python

KMeans 非監督式 機器學習 演算法 Python


從上排的圖,可以看到資料自動從 10 組分成最佳的 7 組,比之前的 5 組合適多了,並標示出 KMeans 找到的各個中心點(來自 cluster_centers_)。下排的圖一邊是手肘法,另一邊是側影係數,兩者都在最佳 K 值處標上一個點。
當然,這邊我們在 make_blobs() 加入 random_state 參數,好確保每次產生的結果一致。你可以試著把這參數拿掉再重新執行程式,實際的 K 值和分組也會有所改變。這樣一來實際上會分成幾組,就看各組隨機資料彼此重疊的程度有多大。


那麼,KMeans 到底能怎麼套用在真實資料呢?考慮到 raw data 難尋,我們來嘗試對已經用到爛的波士頓房價資料集(原本是用來跑迴歸模型)做分析看看:


  1. import matplotlib.pyplot as plt
  2. import seaborn as sns
  3. import pandas as pd
  4. from sklearn import datasets
  5. from sklearn.metrics import silhouette_score
  6. from sklearn.cluster import KMeans
  7. data = datasets.load_boston()
  8. # 把波士頓房價的 data 和 target 合併成一個 pandas DataFrame
  9. dx = pd.DataFrame(data['data'], columns=data['feature_names'])
  10. dy = pd.DataFrame(data['target'], columns=['MEDV'])
  11. df = pd.concat((dy, dx), axis=1)
  12. # 最大 K 值
  13. K_max = 20
  14. # 計算側影係數
  15. scores = []
  16. for i in range(2, K_max + 1):
  17.     scores.append(
  18.         silhouette_score(
  19.             df, KMeans(n_clusters=i).fit_predict(df)))
  20. # 得出最佳 K 值
  21. selected_K = scores.index(max(scores)) + 2
  22. print('K =', selected_K, '\n')
  23. # 對房價分組
  24. kmeans = KMeans(n_clusters=selected_K)
  25. labels = kmeans.fit_predict(df)
  26. # 將分組資料 (分類標籤) 併入原資料
  27. lb = pd.DataFrame(labels, columns=['labels'])
  28. df = pd.concat((lb, df), axis=1)
  29. # 印出標籤和房價、以及其統計結果
  30. print(df[['labels', 'MEDV']], '\n')
  31. print('原始資料\n', df['MEDV'].describe(), '\n')
  32. # 抽出不同組的房價並印出統計結果
  33. df_group = []
  34. for i in range(selected_K):
  35.     df_new = df[df['labels']==i]['MEDV']
  36.     print(f'分類 {i + 1}\n', df_new.describe(), '\n')
  37.     df_group.append(df_new)
  38. # 用 seaborn 畫出所有組別房價的箱型圖
  39. sns.boxplot(data=df_group)
  40. plt.show()
複製代碼


輸出結果:

K = 3
labels  MEDV
0         1  24.0
1         1  21.6
2         1  34.7
3         1  33.4
4         1  36.2
..      ...   ...
501       1  22.4
502       1  20.6
503       1  23.9
504       1  22.0
505       1  11.9
[506 rows x 2 columns]
原始資料
count    506.000000
mean      22.532806
std        9.197104
min        5.000000
25%       17.025000
50%       21.200000
75%       25.000000
max       50.000000
Name: MEDV, dtype: float64
分類 1
count    366.000000
mean      24.931694
std        8.334857
min       11.800000
25%       19.500000
50%       22.800000
75%       28.575000
max       50.000000
Name: MEDV, dtype: float64
分類 2
count    102.000000
mean      17.429412
std        9.184539
min        5.000000
25%       12.150000
50%       15.300000
75%       20.600000
max       50.000000
Name: MEDV, dtype: float64
分類 3
count    38.000000
mean     13.126316
std       4.395310
min       7.000000
25%       9.750000
50%      13.250000
75%      14.975000
max      27.500000
Name: MEDV, dtype: float64

KMeans 非監督式 機器學習 演算法 Python

KMeans 非監督式 機器學習 演算法 Python

這裡 KMeans 把波士頓資料集分成了三組(就算把 K 上限設到 500 也一樣得到 K = 3),等於是加上了新的標籤。上面我們將這三組的房價拆開來看,可以發現每組的平均跟標準差都不同。當然,你程式跑出來這三組的統計數據可能會稍微有點差距就是。
這也許意味著,該資料集內的不動產依其居住條件(?)可大致劃分成三類。也許進一步分析資料集內的其他變數,會有更有趣的發現吧。
另一個常介紹的 KMeans 應用,便是圖片壓縮。更精確地說,是利用 KMeans 來減少顏色數量、進而達到減少圖片儲存空間的效果。

Photo by Simone Hutsch on Unsplash
下面的程式會從電腦載入一張照片(小編本人拍的照片,台北信義區,不算大的底片掃描檔,1272 x 1908),然後將它壓縮成只有 K 種顏色的圖片。


  1. import numpy as np
  2. import matplotlib.pyplot as plt # 需安裝 pillow 才能讀 JPEG
  3. from matplotlib import image
  4. from sklearn.cluster import MiniBatchKMeans
  5. # K 值 (要保留的顏色數量)
  6. K = 64
  7. # 讀取圖片
  8. image = image.imread(r'C:\Users\使用者名稱\Downloads\photo.jpg') / 255
  9. w, h, d = tuple(image.shape)
  10. image_data = np.reshape(image, (w * h, d))
  11. # 將顏色分類為 K 種
  12. kmeans = MiniBatchKMeans(n_clusters=K, batch_size=5000)
  13. labels = kmeans.fit_predict(image_data)
  14. centers = kmeans.cluster_centers_
  15. # 根據分類將顏色寫入新的影像陣列
  16. image_compressed = np.zeros(image.shape)
  17. label_idx = 0
  18. for i in range(w):
  19.     for j in range(h):
  20.         image_compressed[i][j] = centers[labels[label_idx]]
  21.         label_idx += 1
  22. # 如果想儲存壓縮後的圖片, 將下面這句註解拿掉
  23. #plt.imsave(r'C:\Users\使用者名稱\Downloads\compressed.jpg', image_compressed)
  24. # 顯示原圖跟壓縮圖的對照
  25. plt.figure(figsize=(12, 9))
  26. plt.subplot(121)
  27. plt.title('Original photo')
  28. plt.imshow(image)
  29. plt.subplot(122)
  30. plt.title(f'Compressed to KMeans={K} colors')
  31. plt.imshow(image_compressed)
  32. plt.tight_layout()
  33. plt.show()
複製代碼


在此,KMeans 從所有色彩(每個像素就有 256 x 256 x 256 種可能性)找出 K 種顏色,這時 cluster_centers_ 記錄的就是這 K 種顏色的紅綠藍組合。於是,只要根據新的標籤把分組過的顏色寫進新影像陣列,就能得到「壓縮過」的圖片了。
當然由於把整個照片讀進程式,電腦記憶體會爆掉,所以這邊改用 MiniBatchKMeans 來對像素「抽樣」。MiniBatchKMeans 跑得比 KMeans 快,但尋找中心點的效果也就會差一點。
下面來試試不同的 K 值,比較一下壓縮效果:

KMeans 非監督式 機器學習 演算法 Python

KMeans 非監督式 機器學習 演算法 Python

KMeans 非監督式 機器學習 演算法 Python

KMeans 非監督式 機器學習 演算法 Python

KMeans 非監督式 機器學習 演算法 Python

KMeans 非監督式 機器學習 演算法 Python


文章出處


 
您需要登錄後才可以回帖 登錄 | 註冊

本版積分規則

Archiver|手機版|小黑屋|TShopping

GMT+8, 2025-4-30 15:12 , Processed in 0.025889 second(s), 24 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

快速回復 返回頂部 返回列表