1.標準Kmeans
經典的標準kmeans算法無需多言,每個無監督學習的開場白一般都是標準kmeans算法。具體的原理不再多言,可以參考之前的文章:
https://blog.csdn.net/bitcarmanlee/article/details/52092288
標準的kmeans的優缺點,上面的文章也有詳細介紹,再詳細說一說kmeans++對於初始中心點的優化
kmeans++中心點初始化步驟
下面舉個例子來說明怎麼優化初始點。
數據集中共有8個樣本,分佈以及對應序號如圖所示。
假設經過圖2的步驟一後6號點被選擇爲第一個初始聚類中心,那在進行步驟二時每個樣本的D(x)和被選擇爲第二個聚類中心的概率如下表所示:
其中的P(x)就是每個樣本被選爲下一個聚類中心的概率。最後一行的Sum是概率P(x)的累加和,用於輪盤法選擇出第二個聚類中心。方法是隨機產生出一個0~1之間的隨機數,判斷它屬於哪個區間,那麼該區間對應的序號就是被選擇出來的第二個聚類中心了。例如1號點的區間爲[0,0.2),2號點的區間爲[0.2, 0.525)。
從上表可以直觀的看到第二個初始聚類中心是1號,2號,3號,4號中的一個的概率爲0.9。而這4個點正好是離第一個初始聚類中心6號點較遠的四個點。這也驗證了K-means的改進思想:即離當前已有聚類中心較遠的點有更大的概率被選爲下一個聚類中心。可以看到,該例的K值取2是比較合適的。當K值大於2時,每個樣本會有多個距離,需要取最小的那個距離作爲D(x)。
(kmeans++優化中心點的例子來自參考文獻1)
2.kmeans python實現
用python實現一個kmeans的例子。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#Author: WangLei
#date: 2020/3/19
import numpy as np
class KMeansClassifier():
def __init__(self, k=3, init_cent='random', max_iter=500):
self._k = k
self._init_cent = init_cent
self._max_iter = max_iter
self._clusterAssment = None
self._labels = None
self._sse = None
def _cal_edist(self, arrA, arrB):
return np.math.sqrt(sum(np.power(arrA - arrB, 2)))
def _cal_mdist(self, arrA, arrB):
return sum(np.abs(arrA, arrB))
def _rand_cent(self, data_X, k):
n = data_X.shape[1] # 特徵維度
centroids = np.empty((k, n)) #使用numpy生成一個k*n的矩陣,用於存儲質心
for j in range(n):
minJ = min(data_X[:, j])
rangeJ = float(max(data_X[:, j]) - minJ)
centroids[:, j] = (minJ + rangeJ * np.random.rand(k, 1)).flatten()
return centroids
def fit(self, data_X):
if not isinstance(data_X, np.ndarray) or isinstance(data_X, np.matrixlib.defmatrix.matrix):
try:
data_X = np.asarray(data_X)
except:
raise TypeError("numpy.ndarray resuired for data_X")
m = data_X.shape[0] #獲取樣本的個數
self._clusterAssment = np.zeros((m, 2))
if self._init_cent == 'random':
self._centroids = self._rand_cent(data_X, self._k)
clusterChanged = True
for _ in range(self._max_iter):
clusterChanged = False
for i in range(m): #將每個樣本點分配到離它最近的質心所屬的族
minDist = np.inf
minIndex = -1
for j in range(self._k):
arrA = self._centroids[j,:]
arrB = data_X[i,:]
distJI = self._cal_edist(arrA, arrB)
if distJI < minDist:
minDist = distJI
minIndex = j
if self._clusterAssment[i, 0] != minIndex or self._clusterAssment[i, 1] > minDist ** 2:
clusterChanged = True
self._clusterAssment[i, :] = minIndex, minDist ** 2
if not clusterChanged:#若所有樣本點所屬的族都不改變,則已收斂,結束迭代
break
for i in range(self._k): # 更新質心,將每個族中的點的均值作爲質心
index_all = self._clusterAssment[:, 0] # 取出樣本所屬簇的索引值
value = np.nonzero(index_all == i) # 取出所有屬於第i個簇的索引值
ptsInClust = data_X[value[0]] # 取出屬於第i個簇的所有樣本點
self._centroids[i, :] = np.mean(ptsInClust, axis=0) # 計算均值
self._labels = self._clusterAssment[:, 0]
self._sse = sum(self._clusterAssment[:, 1])
def predict(self, X): # 根據聚類結果,預測新輸入數據所屬的族
# 類型檢查
if not isinstance(X, np.ndarray):
try:
X = np.asarray(X)
except:
raise TypeError("numpy.ndarray required for X")
m = X.shape[0] # m代表樣本數量
preds = np.empty((m,))
for i in range(m): # 將每個樣本點分配到離它最近的質心所屬的族
minDist = np.inf
for j in range(self._k):
distJI = self._calEDist(self._centroids[j, :], X[i, :])
if distJI < minDist:
minDist = distJI
preds[i] = j
return preds
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#Author: WangLei
#date: 2020/3/19
from cluster.kmeans import KMeansClassifier
import numpy as np
import pandas as pd
def loadDataSet(infile):
df = pd.read_csv(infile, sep='\t', header=None, dtype=str, na_filter=False)
return np.array(df).astype(np.float)
def main():
data_X = loadDataSet("data/testSet.txt")
print(data_X.shape)
k = 3
clf = KMeansClassifier(k)
clf.fit(data_X)
cents = clf._centroids
labels = clf._labels
sse = clf._sse
print(cents)
print(labels)
print(len(labels))
print(sse)
main()
數據如下
1.658985 4.285136
-3.453687 3.424321
4.838138 -1.151539
-5.379713 -3.362104
0.972564 2.924086
-3.567919 1.531611
0.450614 -3.302219
-3.487105 -1.724432
2.668759 1.594842
-3.156485 3.191137
3.165506 -3.999838
-2.786837 -3.099354
4.208187 2.984927
-2.123337 2.943366
0.704199 -0.479481
-0.392370 -3.963704
2.831667 1.574018
-0.790153 3.343144
2.943496 -3.357075
-3.195883 -2.283926
2.336445 2.875106
-1.786345 2.554248
2.190101 -1.906020
-3.403367 -2.778288
1.778124 3.880832
-1.688346 2.230267
2.592976 -2.054368
-4.007257 -3.207066
2.257734 3.387564
-2.679011 0.785119
0.939512 -4.023563
-3.674424 -2.261084
2.046259 2.735279
-3.189470 1.780269
4.372646 -0.822248
-2.579316 -3.497576
1.889034 5.190400
-0.798747 2.185588
2.836520 -2.658556
-3.837877 -3.253815
2.096701 3.886007
-2.709034 2.923887
3.367037 -3.184789
-2.121479 -4.232586
2.329546 3.179764
-3.284816 3.273099
3.091414 -3.815232
-3.762093 -2.432191
3.542056 2.778832
-1.736822 4.241041
2.127073 -2.983680
-4.323818 -3.938116
3.792121 5.135768
-4.786473 3.358547
2.624081 -3.260715
-4.009299 -2.978115
2.493525 1.963710
-2.513661 2.642162
1.864375 -3.176309
-3.171184 -3.572452
2.894220 2.489128
-2.562539 2.884438
3.491078 -3.947487
-2.565729 -2.012114
3.332948 3.983102
-1.616805 3.573188
2.280615 -2.559444
-2.651229 -3.103198
2.321395 3.154987
-1.685703 2.939697
3.031012 -3.620252
-4.599622 -2.185829
4.196223 1.126677
-2.133863 3.093686
4.668892 -2.562705
-2.793241 -2.149706
2.884105 3.043438
-2.967647 2.848696
4.479332 -1.764772
-4.905566 -2.911070
最後代碼運行的結果爲
(80, 2)
[[ 2.99405094 -0.1605263 ]
[-1.6334182 3.03655888]
[-3.01169468 -3.01238673]]
[1. 1. 0. 2. 1. 1. 2. 2. 0. 1. 0. 2. 0. 1. 0. 2. 0. 1. 0. 2. 0. 1. 0. 2.
1. 1. 0. 2. 0. 1. 2. 2. 0. 1. 0. 2. 1. 1. 0. 2. 1. 1. 0. 2. 0. 1. 0. 2.
0. 1. 0. 2. 0. 1. 0. 2. 0. 1. 0. 2. 0. 1. 0. 2. 0. 1. 0. 2. 0. 1. 0. 2.
0. 1. 0. 2. 0. 1. 0. 2.]
80
136.84604817873276
3.kmeans算法複雜度分析
假設有k個聚類中心,總共有n個點,迭代t輪,那麼每一輪迭代都需要算n個點到k箇中心點的距離,算法的總複雜度爲
4.GMM(Gaussian Mixed Model)
GMM混合高斯分佈是多個高斯分佈函數的線性組合,假設有隨機變量,GMM模型可以用如下公式表示
其中,
是模型中的第k個分量(component)。是混合係數(mixture coefficient),而且有
如果樣本是一維數據(Univariate),高斯分佈的概率密度函數PDF(Probability Density Function)爲:
當爲多維數據(Multivariate)時,其PDF如下:
其中,爲期望,爲協方差矩陣,是一個對稱矩陣。D爲數據的維度。
協方差:
如果用表示隨機變量的第i個樣本,n表示樣本總數,則第m維與第k維的協方差爲
其中,對角線上的元素尾各隨機變量的方差,而非對角線上的元素爲兩兩隨機變量之間的協方差。由協方差矩陣的定義易知,協方差矩陣爲方針,大小爲d*d,且爲對稱矩陣(symmetric matrix)。
5.GMM聚類
GMM聚類的核心思想是假設所有的數據點來自多個參數不同的高斯分佈,來自同一分佈的數據點被劃分爲同一類。算法結果返回的是數據點屬於不同類別的概率。
提GMM,必然繞不過EM算法。反過來,提EM算法,一般也會提GMM,這兩跟孿生兄弟一樣經常成對出現,下面我們試着用簡單的方式來描述一下EM算法。
假設我們得到一組樣本,而且服從高斯分佈,但是高斯分佈的參數未知,就是第四小節裏提到的。我們的目標是找一個合適的高斯分佈,確定分佈參數,使得這個高斯分佈能產生這組樣本的可能性儘可能大。
學過概率統計的同學應該有感覺,產生這組樣本的可能性儘可能大,這個時候就輪到極大似然估計(Maximum Likehood Estimate, MLE)大顯身手的時候了。
假設樣本集,而是高斯分佈的概率分佈函數。如果假設樣本抽樣獨立,樣本集的聯合概率就是似然函數:
接下來的求解過程就是求極值了,學過高數的同學都知道,對上式求導,並令導數爲0,就可以求得高斯分佈的參數。
所以最大化似然函數的意義在於:通過使得樣本集的聯合概率最大來對參數進行估計,從而選擇最佳的分佈模型。
回到GMM。上面是隻有一個高斯分佈的情況,在GMM中,有多個高斯分佈,我們並不知道樣本來自哪個高斯分佈。換句話說,
中,的值我們並不知道,我們不光需要求解不同高斯分佈的參數,還需要求解不同高斯分佈的概率。
我們引入一個隱變量,它是一個K維二值隨機變量,在它的K維取值中只有某個特定的元素的取值爲1,其它元素的取值爲0。實際上,隱變量描述的就是:每一次採樣,選擇第k個高斯模型的概率,故有:
當給定了的一個特定的值之後(也就是知道了這個樣本從哪一個高斯模型進行採樣),可以得到樣本x的條件分佈是一個高斯分佈,滿足:
而實際上,每個樣本到底是從這K個高斯模型中哪個模型進行採樣的,是都有可能的。故樣本y的概率爲:
樣本集X(n個樣本點)的聯合概率爲:
對數似然函數表示爲:
接下來再求導,令導數爲0,可以得到模型參數。
到此爲止,貌似問題已經解決了。但是在對數似然函數裏面,裏面還有求和。實際上沒有辦法通過求導的方法來求這個對數似然函數的最大值。
具體的EM算法步驟參見參考文獻2,公式比較多,就不復制粘貼了。
6.GMM代碼示例
import numpy as np
import itertools
from scipy import linalg
import matplotlib.pyplot as plt
import matplotlib as mpl
from sklearn import mixture
# Number of samples per component
n_samples = 500
# Generate random sample, two components
np.random.seed(0)
C = np.array([[0., -0.1], [1.7, .4]])
X = np.r_[np.dot(np.random.randn(n_samples, 2), C),
.7 * np.random.randn(n_samples, 2) + np.array([-6, 3])]
lowest_bic = np.infty
bic = []
n_components_range = range(1, 7)
cv_types = ['spherical', 'tied', 'diag', 'full']
for cv_type in cv_types:
for n_components in n_components_range:
# Fit a Gaussian mixture with EM
gmm = mixture.GaussianMixture(n_components=n_components,
covariance_type=cv_type)
gmm.fit(X)
bic.append(gmm.bic(X))
if bic[-1] < lowest_bic:
lowest_bic = bic[-1]
best_gmm = gmm
bic = np.array(bic)
color_iter = itertools.cycle(['navy', 'turquoise', 'cornflowerblue',
'darkorange'])
clf = best_gmm
bars = []
# Plot the BIC scores
plt.figure(figsize=(8, 6))
spl = plt.subplot(2, 1, 1)
for i, (cv_type, color) in enumerate(zip(cv_types, color_iter)):
xpos = np.array(n_components_range) + .2 * (i - 2)
bars.append(plt.bar(xpos, bic[i * len(n_components_range):
(i + 1) * len(n_components_range)],
width=.2, color=color))
plt.xticks(n_components_range)
plt.ylim([bic.min() * 1.01 - .01 * bic.max(), bic.max()])
plt.title('BIC score per model')
xpos = np.mod(bic.argmin(), len(n_components_range)) + .65 +\
.2 * np.floor(bic.argmin() / len(n_components_range))
plt.text(xpos, bic.min() * 0.97 + .03 * bic.max(), '*', fontsize=14)
spl.set_xlabel('Number of components')
spl.legend([b[0] for b in bars], cv_types)
# Plot the winner
splot = plt.subplot(2, 1, 2)
Y_ = clf.predict(X)
for i, (mean, cov, color) in enumerate(zip(clf.means_, clf.covariances_,
color_iter)):
v, w = linalg.eigh(cov)
if not np.any(Y_ == i):
continue
plt.scatter(X[Y_ == i, 0], X[Y_ == i, 1], .8, color=color)
# Plot an ellipse to show the Gaussian component
angle = np.arctan2(w[0][1], w[0][0])
angle = 180. * angle / np.pi # convert to degrees
v = 2. * np.sqrt(2.) * np.sqrt(v)
ell = mpl.patches.Ellipse(mean, v[0], v[1], 180. + angle, color=color)
ell.set_clip_box(splot.bbox)
ell.set_alpha(.5)
splot.add_artist(ell)
plt.xticks(())
plt.yticks(())
plt.title('Selected GMM: full model, 2 components')
plt.subplots_adjust(hspace=.35, bottom=.02)
plt.show()
關鍵步驟有註釋,參考註釋就好。
最後出來的效果
7.高斯分佈於混合高斯分佈流程小結
對於一個高斯分佈,參數估計的流程一般如下:
1.首先得到一些符合高斯分佈的採樣數據。
2.假設有一個高斯分佈最有可能得到這些採樣數據,接下來求解該高斯分佈的參數。
3.求採樣數據產生的聯合概率P
4.使用極大似然估計最大化P獲得高斯分佈的參數。
如果是一個混合高斯分佈,一般的求解流程如下
1.首先得到一些符合混合高斯分佈的採樣數據。
2.假設有一組高斯分佈最有可能得到這些採樣數據,接下來求解該高斯分佈的參數。
3.1 各個採樣數據最有可能是從哪一個高斯分佈產生的,相當於對採樣數據做分類劃分。
3.2 各個類對劃分到的樣本,求樣本產生的概率
3.3 求採樣數據產生的概率(Q函數)
3.4 最大化Q函數來優化混合高斯分佈參數。
8 Kmeans與GMM的區別與聯繫
相同點
兩者都是迭代算法,並且迭代策略大致相同:算法開始對需要計算的參數賦初值,然後交替執行兩個步驟。第一個步驟是對數據的估計,k-means是估計每個點所屬簇,GMM是計算隱含變量的期望。第二個步驟是用第一步的估計值重新計算參數值,更新目標參數。其中k-means是計算簇心位置;GMM是計算各個高斯分佈的中心位置和協方差矩陣。
不同點
1.k-means是計算簇心位置,GMM是計算各個高斯分佈的參數。
2.k-means是計算當前簇中所有元素的位置的均值,GMM是通過計算似然函數的最大值實現分佈參數的求解的。
參考文獻
1.https://www.cnblogs.com/yixuan-xu/p/6272208.html
2.https://blog.csdn.net/lin_limin/article/details/81048411