序言
K-means算法是非監督學習(unsupervised learning)中最簡單也是最常用的一種聚類算法,具有的特點是:
- 對初始化敏感。初始點選擇的不同,可能會產生不同的聚類結果
- 最終會收斂。不管初始點如何選擇,最終都會收斂。
本文章介紹K-means聚類算法的思想,同時給出在matlab環境中實現K-means算法的代碼。代碼使用向量化(vectorization1)來計算,可能不是很直觀但是效率比使用循環算法高。
K-means算法
本節首先直觀敘述要解決的問題,然後給出所要求解的數學模型,最後從EM2 算法的角度分析K-means算法的特點。
問題描述
首先我們有N個數據
找
爲了更好的理解,我將在下節給出一些數學符號來定義清楚問題。
問題定義
上小節我們知道要把數據分成
我們的目標就是要得到
最小。
問題求解
這一部分將介紹使用EM算法4來求解K-means問題。關於EM算法求解總體分爲兩種步驟
- E(expectation): 求期望最大。初始化時,隨機生成
K 箇中心點{μk}|Kk=1 。然後使用公式(1) 決定數據的類別。 - M(Maximization): 這裏的極大化取決於你的問題,我們這裏是要最優化目標函數。所以在這一步我們保持數據的類別不變,要使用公式
(2) 更新中心點,也就是要求出
μk=argminukJ(3)
這個等式。
這裏我們注意到,因爲保持了類別不變,也就是說目標函數只有μk 一個變量,等式(3) 變成了
μk=argminukJ(μk)
式子。所以我們對目標函數求極值,也就對μk 求導並令其爲零,得到
2∑n=1Nrnk(xn−μk)=0
這樣的式子。求解可以得到
μk=∑nrnkxn∑nrnk(4)
的表達式。 - 重複以上兩步,直到收斂。
至此,我們就完成了對K-means方法的求解。接下來,我們將通過實例以及代碼實現來理解K-means。
K-means實現
這一節主要通過實例和代碼,來充分理解K-means算法,完成聚類分析,並在最後分析收斂效果。
實例分析
我們的數據來源是Old Faithful Geyser,我們想將其分成
代碼分析
都代碼還是先整體再局部吧。我們先對代碼整體設計如下
function [costDis] = runKMeans(K,fileString)
X=load(fileString);
%determine and store data set information
N=size(X,1);
D=size(X,2);
%allocate space for the K mu vectors
Kmus=zeros(K,D); % not need to allocate it but it is still worthy
%initialize cluster centers by randomly picking points from the data
rndinds=randperm(N);
Kmus=X(rndinds(1:K),:);
%specify the maximum number of iterations to allow
maxiters=1000;
for iter=1:maxiters
%do this by first calculating a squared distance matrix where the n,k entry
%contains the squared distance from the nth data vector to the kth mu vector
%sqDmat will be an N-by-K matrix with the n,k entry as specfied above
sqDmat=calcSqDistances(X,Kmus);
%given the matrix of squared distances, determine the closest cluster
%center for each data vector
Rnk=determineRnk(sqDmat);
KmusOld=Kmus;
plotCurrent(X,Rnk,Kmus);
pause(1);
Kmus=recalcMus(X,Rnk);
%check to see if the cluster centers have converged. If so, break.
if sum(abs(KmusOld(:)-Kmus(:)))<1e-6
disp(iter);
break
end
end
costDis = sum(min(sqDmat,[],2));
end
首先讀入數據,X
爲Kmus
爲calcSqDistances()
計算數據與各中心點之間的距離,然後determineRnk()
根據距離決定數據屬於哪一類,然後recalcMus()
根據確定好的數據的類重新計算出新的中心點,最後重複循環直到收斂。
接下來是各個內部函數,首先是距離計算函數。我們要得到的矩陣第
(X(n:,)-Kmus(k:,))*(X(n:,)-Kmus(k:,))'
這樣就能夠計算出一個元素的值,這裏面還要用到一點矩陣運算的技巧,因爲
可以發現,其實對數據和中心點矩陣的每一行元素,只要計算自己與自己的距離,然後減去兩倍向量乘積的值就可以了。所以我們應該對每個矩陣先自己相乘得到自己的距離,比如對數據點這個距離就通過
Data_sq = diag(X*X'); % N by 1
來計算得出。
計算距離的代碼爲
function SQD = calcSqDistances(X,Kmus)
% compute the squared distance w.r.t. each center point for every data
% X; N by D; Kmus: K by D
% ||x-u||^2 = xx' - 2xu' + uu' N by K
N = size(X,1);
D = size(X,2);
K = size(Kmus,1);
Data_sq = diag(X*X'); % N by 1
Kmus_sq = diag(Kmus*Kmus'); % 1 by K
trans = 2*X*Kmus'; % N by K
SQD = repmat(Data_sq,1,K) - trans + repmat(Kmus_sq',N,1);
end
決定類的函數,其實通過公式
function RnkMat = determineRnk(sqDmat)
% calculate the label for each cluster
% 1 for belong, 0 for not belong
N = size(sqDmat,1);
K = size(sqDmat,2);
RnkMat = zeros(N,K);
[~,minIndex] = min(sqDmat,[],2);
positionVec = 1:N;
idxVec = N*(minIndex-1) + positionVec'; % or we can ues this
% idxVec = sub2ind([N,K],positionVec',minIndex); but it is slower than my
% code implementation
RnkMat(idxVec) = 1;
end
最後就是更新中心點的函數,也是根據EM算法中的公式
function Kmus = recalcMus(X,Rnk)
% get the Kmus from the mean value of the cluster
% mu_k = frac{\sum_n{r_{nk}X_n}{\sum_n{r_{nk}}}
% X: N by D
% Rnk: N by K
% Kmus: K by D
N = size(X,1);
K = size(Rnk,2);
D = size(X,2);
sumCluster = Rnk'*X; % K by D
numCluster = sum(Rnk)'; % K by 1
normMat = repmat(numCluster,1,D);
Kmus = sumCluster./normMat;
end
最後是一個小trick在主程序畫圖過程中的plotCurrent()
函數後面跟着一個停頓函數pause(1)
會在循環過程中產生動態效果,如下圖所示(忽略噁心的水印)
繪圖函數是這樣的
function plotCurrent(X,Rnk,Kmus)
[N,D]=size(X);
K=size(Kmus,1);
clf;
figure(1);
hold on;
InitColorMat= [1 0 0;
0 1 0;
0 0 1;
0 0 0;
1 1 0;
1 0 1;
0 1 1;
0.5 1 0.5];
KColorMat=InitColorMat(1:K,:);
colorVec=Rnk*KColorMat;
muColorVec=eye(K)*KColorMat;
scatter(X(:,1),X(:,2),[],colorVec)
scatter(Kmus(:,1),Kmus(:,2),200,muColorVec,'d','filled');
axis equal;
hold off;
end
結果分析
隨着K的變化,整體的距離變化爲如圖所示,動態變化上圖已經展示。
調試代碼爲
% KMeans_script
% for i = 1:100
filename = 'scaledfaithful.txt';
%%
K = 2;
k2_cost_all = 0;
max_num = 100;
for num_comput = 1:max_num
k2_cost = runKMeans(K,filename);
k2_cost_all = k2_cost_all + k2_cost;
end
k2_cost_avg = k2_cost_all/max_num;
%%
K = 3;
k3_cost_all = 0;
max_num = 100;
for num_comput = 1:max_num
k3_cost = runKMeans(K,filename);
k3_cost_all = k3_cost_all + k3_cost;
end
k3_cost_avg = k3_cost_all/max_num;
%%
K = 4;
k4_cost_all = 0;
max_num = 100;
for num_comput = 1:max_num
k4_cost = runKMeans(K,filename);
k4_cost_all = k4_cost_all + k4_cost;
end
k4_cost_avg = k4_cost_all/max_num;
%%
K = 5;
k5_cost_all = 0;
max_num = 100;
for num_comput = 1:max_num
k5_cost = runKMeans(K,filename);
k5_cost_all = k5_cost_all + k5_cost;
end
k5_cost_avg = k5_cost_all/max_num;
%%
K = 6;
k6_cost_all = 0;
max_num = 100;
for num_comput = 1:max_num
k6_cost = runKMeans(K,filename);
k6_cost_all = k6_cost_all + k6_cost;
end
k6_cost_avg = k6_cost_all/max_num;
%%
K = 7;
k7_cost_all = 0;
max_num = 100;
for num_comput = 1:max_num
k7_cost = runKMeans(K,filename);
k7_cost_all = k7_cost_all + k7_cost;
end
k7_cost_avg = k7_cost_all/max_num;
%%
cost_K = [k2_cost_avg, k3_cost_avg, k4_cost_avg, k5_cost_avg, k6_cost_avg, k7_cost_avg];
K = 2:7;
plot(K, cost_K, 'rx-','LineWidth', 3)
xlim([2 7])
ylim([20 100])
xlabel('K')
ylabel('cost function value')
雖然無法找到一個最優的K值,但相對來說,k=4或5的時候效果還是不錯的。當K=4的時候,收斂圖爲
可以發現,收斂的還是非常快的。
總結
本文介紹了聚類算法中常用的K-means算法。從EM算法求解K-means算法問題,並給出了matlab下實現K-means的算法程序。所有的程序和數據均可以從我的github上面下載。希望對大家有所幫助!
參考文獻
- Array programming in wikipedia
https://en.wikipedia.org/wiki/Array_programming ↩ - 最大期望算法
https://en.wikipedia.org/wiki/Expectation%E2%80%93maximization_algorithm ↩ - 模式識別與機器學習
http://users.isr.ist.utl.pt/~wurmd/Livros/school/Bishop%20-%20Pattern%20Recognition%20And%20Machine%20Learning%20-%20Springer%20%202006.pdf ↩ - 最大期望算法
https://en.wikipedia.org/wiki/Expectation%E2%80%93maximization_algorithm ↩