【機器學習】Kmeans聚類
寫在篇前
Kmeans演算法是一種經典的聚類演算法,屬於無監督學習的範疇。所謂聚類,即指對於給定的一個樣本集,按照樣本之間的距離大小,將樣本集劃分為K個簇,且讓簇內的點儘量緊密的連在一起,而讓簇間的距離儘量的大。
優點:
- 原理簡單
- 速度快
- 對大資料集有比較好的伸縮性
缺點:
- 需要指定聚類數量K
- 對異常值敏感
- 對初始值敏感
原理概述
演算法流程
以下描述基於 Lloyd’s 演算法
- 設定一個k值,即設定需要聚類多少個簇
- 隨機選擇k個質心(centroids)
- 計算各個樣本點到質心的距離,劃分簇
- 重複第2、3步驟,直至迭代次數達到設定的最大值或者質心不再移動
評價準則
The k-means algorithm divides a set of N samples X into K disjoint clusters C, each described by the mean of the samples in the cluster. The means are commonly called the cluster “centroids”; note that they are not, in general, points from X, although they live in the same space. The K-means algorithm aims to choose centroids that minimise the inertia
值得注意的是:
-
Inertia假設聚類是凸的和各向同性的(convex and isotropic),但它對細長簇或具有不規則形狀資料聚類不佳;
-
Inertia不是標準化的度量標準:我們只知道較低的值更好,零是最佳的;
演算法改進
Kmeans ++
k個初始化的質心的位置選擇對最後的聚類結果和執行時間都有很大的影響,因此需要選擇合適的k個質心。K-Means++演算法就是對K-Means隨機初始化質心的方法的優化:
-
從輸入的資料點集合中隨機選擇一個點作為第一個聚類中心\mu_1
-
對於資料集中的每一個點x_i,計算它與已選擇的聚類中心中最近聚類中心的距離
-
選擇一個新的資料點作為新的聚類中心,選擇的原則是:D(x)較大的點,被選取作為聚類中心的概率較大
-
重複b和c直到選擇出k個聚類質心
-
利用這k個質心來作為初始化質心去執行標準的K-Means演算法
elkan
在傳統的K-Means演算法中,我們在每輪迭代時,要計算所有的樣本點到所有的質心的距離,這樣會比較的耗時。elkan K-Means演算法就是從這塊入手加以改進。它的目標是減少不必要的距離的計算。elkan K-Means利用了兩邊之和大於等於第三邊,以及兩邊之差小於第三邊的三角形性質,來減少距離的計算。利用上邊的兩個規律,elkan K-Means比起傳統的K-Means迭代速度有很大的提高。但是如果我們的樣本的特徵是稀疏的,有缺失值的話,這個方法就不使用了,此時某些距離無法計算,則不能使用該演算法。
mini-batch
MiniBatch-KMeans是KMeans演算法的一種變體,它使用mini-batch來減少計算時間,同時仍試圖優化相同的目標函式。mini-batch是輸入資料集的子集,在每次訓練迭代中隨機取樣。它大大減少收斂到區域性最優值所需的計算量,並達到大致相同的效果。
演算法實現
在本篇主要藉助sklearn包提供的介面來實現kmeans演算法,具體的實現當然我們可以直接看原始碼啦~首先看一下建構函式:
def __init__(self,
n_clusters=8, # 即理論部分的k值
init='k-means++', # 質心初始化方法
n_init=10, # 質心初始化次數,最後結果取結果最好的一個
max_iter=300, # 最大迭代次數
tol=1e-4, # 容忍度,即kmeans執行準則收斂的條件
precompute_distances='auto', # 是否需要提前計算距離,並將其放入記憶體
verbose=0, # 冗長模式,深入看原始碼你會發現和作業系統底層有關,個人認為與演算法本身無關
random_state=None, # 實際上是種子,改引數會傳入check_random_state()函式
copy_x=True, # 當平行計算時,必須為True,為資料建立copy
n_jobs=1, # 平行計算程序數,-1時表示佔滿cpu
algorithm='auto' # ‘auto’, ‘full’, ‘elkan’, 其中 'full’表示用EM方式實現,即傳統的kmeans演算法計算距離
)
上面這些引數可以一一對應到上面講的理論部分,為了進一步瞭解我們繼續深入,看看一個Kmeans物件有哪些屬性和方法,同樣直接看程式碼:
#! /usr/bin/python
# _*_ coding: utf-8 _*_
__author__ = 'Jeffery'
import numpy as np
from sklearn.cluster import KMeans
data = np.random.rand(100, 3) # 生成一個隨機資料,shape(100, 3)
estimator = KMeans(n_clusters=3,
init='k-means++',
n_init=10,
max_iter=300,
tol=1e-4,
precompute_distances='auto',
verbose=0,
random_state=None,
copy_x=False,
n_jobs=1,
algorithm='auto') # 對於非稀疏資料,會選擇elkan演算法
estimator.fit(data) # 聚類
# --------------------屬性-------------------------
# 獲取estimator建構函式中設定的屬性
print('n_clusters:', estimator.n_clusters)
print('init:', estimator.init)
print('max_iter:', estimator.max_iter)
print('tol:', estimator.tol)
print('precompute_distances:', estimator.precompute_distances)
print('verbose:', estimator.verbose)
print('random_state:', estimator.random_state)
print('copy_x:', estimator.copy_x)
print('n_jobs:', estimator.n_jobs)
print('algorithm:', estimator.algorithm)
print('n_init:', estimator.n_init)
# 其他重要屬性
print('n_iter_:', estimator.n_iter_) # 實際迭代次數
print('cluster_centers_:', estimator.cluster_centers_) # 獲取聚類中心點結果
print('inertia_:', estimator.inertia_) # 獲取聚類準則的總和,越小越好
print('labels_:', estimator.labels_) # 獲取聚類標籤結果
# --------------------函式-------------------------
print('get_params:', estimator.get_params(deep=True)) # 與set_params()相對
# 這裡的介紹先不講 fit()、transform()、fit_transform()、fit_predict()、predict()、score()等函式
# 這些函式放在案例裡面
案例
這個示例來源於官網,這個示例旨在說明k-means的一些不合適的場景:在前三個圖中,輸入資料不符合k-means所產生的一些隱含假設,結果產生了不希望的聚類結果;在最後一個圖中,k-means返回直觀、合理的簇,儘管大小不均勻。這個示例很簡單,但是一定要充分看到kmeans的應用關鍵,比如k選取的重要性、聚類各向異性資料的侷限性
#! /usr/bin/python
# _*_ coding: utf-8 _*_
__author__ = 'Jeffery'
__date__ = '2018/11/11 13:55'
import numpy as np
import matplotlib.pyplot as plt
from sklearn.cluster import KMeans
from sklearn.datasets import make_blobs
plt.figure(figsize=(12, 12))
n_samples = 1500
random_state = 170
X, y = make_blobs(n_samples=n_samples, random_state=random_state)
# Incorrect number of clusters
y_pred = KMeans(n_clusters=2, random_state=random_state).fit_predict(X)
plt.subplot(221)
plt.scatter(X[:, 0], X[:, 1], c=y_pred)
plt.title("Incorrect Number of Blobs")
# Anisotropicly distributed data
transformation = [[0.60834549, -0.63667341], [-0.40887718, 0.85253229]]
X_aniso = np.dot(X, transformation)
y_pred = KMeans(n_clusters=3, random_state=random_state).fit_predict(X_aniso)
plt.subplot(222)
plt.scatter(X_aniso[:, 0], X_aniso[:, 1], c=y_pred)
plt.title("Anisotropicly Distributed Blobs")
# Different variance
X_varied, y_varied = make_blobs(n_samples=n_samples,
cluster_std=[1.0, 2.5, 0.5],
random_state=random_state)
y_pred = KMeans(n_clusters=3, random_state=random_state).fit_predict(X_varied)
plt.subplot(223)
plt.scatter(X_varied[:, 0], X_varied[:, 1], c=y_pred)
plt.title("Unequal Variance")
# Unevenly sized blobs
X_filtered = np.vstack((X[y == 0][:500], X[y == 1][:100], X[y == 2][:10]))
y_pred = KMeans(n_clusters=3,
random_state=random_state).fit_predict(X_filtered)
plt.subplot(224)
plt.scatter(X_filtered[:, 0], X_filtered[:, 1], c=y_pred)
plt.title("Unevenly Sized Blobs")
plt.show()
寫在篇後
如果有足夠的時間,K-means將最終收斂,但這可能是區域性最優值,這很大程度上取決於質心的初始化;另外,對於大資料集,可以先做PCA等降維處理(PCA請參考降維技術-PCA),然後再進行聚類,以減少計算資源的消耗以及可能的提升聚類效果。