matlab利用內置K-means聚類並保存結果
在matlab中,利用內置的程序進行k-means聚類是非常容易的,以下以經典的MNIST數據集爲例。
data = load('datasets/MNIST.mat')
[Idx,Ctrs,SumD,D] = kmeans(data.X,10,'Replicates',4);
save('datasets/MNIST_rs.mat','Idx','Ctrs','SumD','D')
上述代碼實現了讀取數據集,kmeans聚類(數據集中data.X代表訓練集,聚10類),保存聚類結果以便於運算的功能。聚類的輸出結果如下:
Idx: N*1的向量,存儲的是每個點的聚類標號
C: K*P的矩陣,存儲的是K個聚類質心位置
sumD: 1*K的和向量,存儲的是類間所有點與該類質心點距離之和
D: N*K的矩陣,存儲的是每個點與所有質心的距離
Munkres函數
首先給出在matlab論壇中找到的重排函數munkres.m:
function [assignment,cost] = munkres(costMat)
% MUNKRES Munkres Assign Algorithm
%
% [ASSIGN,COST] = munkres(COSTMAT) returns the optimal assignment in ASSIGN
% with the minimum COST based on the assignment problem represented by the
% COSTMAT, where the (i,j)th element represents the cost to assign the jth
% job to the ith worker.
%
% This is vectorized implementation of the algorithm. It is the fastest
% among all Matlab implementations of the algorithm.
% Examples
% Example 1: a 5 x 5 example
%{
[assignment,cost] = munkres(magic(5));
[assignedrows,dum]=find(assignment);
disp(assignedrows'); % 3 2 1 5 4
disp(cost); %15
%}
% Example 2: 400 x 400 random data
%{
n=400;
A=rand(n);
tic
[a,b]=munkres(A);
toc % about 6 seconds
%}
% Reference:
% "Munkres' Assignment Algorithm, Modified for Rectangular Matrices",
% http://csclab.murraystate.edu/bob.pilgrim/445/munkres.html
% version 1.0 by Yi Cao at Cranfield University on 17th June 2008
assignment = false(size(costMat));
cost = 0;
costMat(costMat~=costMat)=Inf;
validMat = costMat<Inf;
validCol = any(validMat);
validRow = any(validMat,2);
nRows = sum(validRow);
nCols = sum(validCol);
n = max(nRows,nCols);
if ~n
return
end
dMat = zeros(n);
dMat(1:nRows,1:nCols) = costMat(validRow,validCol);
%*************************************************
% Munkres' Assignment Algorithm starts here
%*************************************************
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% STEP 1: Subtract the row minimum from each row.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
dMat = bsxfun(@minus, dMat, min(dMat,[],2));
%**************************************************************************
% STEP 2: Find a zero of dMat. If there are no starred zeros in its
% column or row start the zero. Repeat for each zero
%**************************************************************************
zP = ~dMat;
starZ = false(n);
while any(zP(:))
[r,c]=find(zP,1);
starZ(r,c)=true;
zP(r,:)=false;
zP(:,c)=false;
end
while 1
%**************************************************************************
% STEP 3: Cover each column with a starred zero. If all the columns are
% covered then the matching is maximum
%**************************************************************************
primeZ = false(n);
coverColumn = any(starZ);
if ~any(~coverColumn)
break
end
coverRow = false(n,1);
while 1
%**************************************************************************
% STEP 4: Find a noncovered zero and prime it. If there is no starred
% zero in the row containing this primed zero, Go to Step 5.
% Otherwise, cover this row and uncover the column containing
% the starred zero. Continue in this manner until there are no
% uncovered zeros left. Save the smallest uncovered value and
% Go to Step 6.
%**************************************************************************
zP(:) = false;
zP(~coverRow,~coverColumn) = ~dMat(~coverRow,~coverColumn);
Step = 6;
while any(any(zP(~coverRow,~coverColumn)))
[uZr,uZc] = find(zP,1);
primeZ(uZr,uZc) = true;
stz = starZ(uZr,:);
if ~any(stz)
Step = 5;
break;
end
coverRow(uZr) = true;
coverColumn(stz) = false;
zP(uZr,:) = false;
zP(~coverRow,stz) = ~dMat(~coverRow,stz);
end
if Step == 6
% *************************************************************************
% STEP 6: Add the minimum uncovered value to every element of each covered
% row, and subtract it from every element of each uncovered column.
% Return to Step 4 without altering any stars, primes, or covered lines.
%**************************************************************************
M=dMat(~coverRow,~coverColumn);
minval=min(min(M));
if minval==inf
return
end
dMat(coverRow,coverColumn)=dMat(coverRow,coverColumn)+minval;
dMat(~coverRow,~coverColumn)=M-minval;
else
break
end
end
%**************************************************************************
% STEP 5:
% Construct a series of alternating primed and starred zeros as
% follows:
% Let Z0 represent the uncovered primed zero found in Step 4.
% Let Z1 denote the starred zero in the column of Z0 (if any).
% Let Z2 denote the primed zero in the row of Z1 (there will always
% be one). Continue until the series terminates at a primed zero
% that has no starred zero in its column. Unstar each starred
% zero of the series, star each primed zero of the series, erase
% all primes and uncover every line in the matrix. Return to Step 3.
%**************************************************************************
rowZ1 = starZ(:,uZc);
starZ(uZr,uZc)=true;
while any(rowZ1)
starZ(rowZ1,uZc)=false;
uZc = primeZ(rowZ1,:);
uZr = rowZ1;
rowZ1 = starZ(:,uZc);
starZ(uZr,uZc)=true;
end
end
% Cost of assignment
assignment(validRow,validCol) = starZ(1:nRows,1:nCols);
cost = sum(costMat(assignment));
該函數爲了解決工人成本分配問題
令C爲一個nxn矩陣,表示n個工人中每個工人執行n個工作的成本。分配問題是將工作分配給工人,以使總成本最小化。由於每個工人只能執行一項工作,並且每個作業只能分配給一個工人,因此分配構成矩陣C的獨立集。
上面顯示了一個任意分配,其中爲工作人員a分配了作業q,爲工作人員b分配了作業s,依此類推。這項作業的總費用爲23。
解決分配問題的蠻力算法包括生成矩陣C的所有獨立集,計算每個分配的總成本以及搜索所有分配以找到最小和獨立集。此方法的複雜性由nxn矩陣中可能存在的獨立分配的數量決定。該方法至少具有指數級的運行時複雜度。
選擇每個分配後,將不再考慮行和列。提出了關於是否有更好的算法的問題。實際上,儘管有一些參考文獻仍將其描述爲指數複雜性問題,但實際上存在一種用於解決James Munkre在1950年代末提出的分配問題的多項式運行時複雜性算法。
以下6步算法是原始Munkres的分配算法(有時稱爲匈牙利算法)的修改形式。該算法描述瞭如何通過對零加星標和標記以及覆蓋和揭示行和列來手動操作二維矩陣。這是因爲,在出版時(1957年),很少有人可以使用計算機,並且算法是手動執行的。
步驟0: 創建一個稱爲成本矩陣的nxm矩陣,其中每個元素代表將n個工人之一分配給m個工作之一的成本。旋轉矩陣,以便至少有與行一樣多的列,並令k = min(n,m)。
步驟1: 對於矩陣的每一行,找到最小的元素,並將其從其行中的每個元素中減去。轉到步驟2。
步驟2: 在結果矩陣中找到零(Z)。如果其行或列中沒有加星號的零,請加星號Z。對矩陣中的每個元素重複此操作。轉到步驟3。
步驟3: 覆蓋每列包含加星號的零。如果覆蓋了K列,則加星號的零表示完整的唯一賦值集。在這種情況下,請轉到完成,否則,請轉到步驟4。
第4步: 找到一個未覆蓋的零並將其填充。如果在包含該加註底零的行中沒有加星號的零,請轉到步驟5。否則,請覆蓋該行並找出包含加註星號的零的列。以這種方式繼續,直到沒有剩餘的零爲止。保存最小的發現值,然後轉到步驟6。
步驟5: 按以下步驟構造一系列交替的填色和加星號的零。令Z 0 表示在步驟4中發現的未覆蓋的準備好的零。讓Z 1表示Z 0列中的加星標的零(如果有)。令Z 2表示Z 1行中的質數爲零(始終爲1)。繼續直到該序列終止於在其列中沒有加星號爲0的預塗零位處終止。取消對系列中每個加註星號的零加註星標,對系列中每個加註零的星號加註星標,擦除所有質數並發現矩陣中的每一行。返回步驟3。
步驟6: 將在步驟4中找到的值添加到每個覆蓋行的每個元素,並從每個未覆蓋列的每個元素中減去它。返回第4步,而不更改任何星形,素數或覆蓋線。
完成: 分配對由成本矩陣中加星號的零的位置指示。如果C(i,j)爲星號爲零,則將與行i關聯的元素分配給與列j關聯的元素。
更加詳細的英文文檔說明請參照這裏
利用munkres進行聚類結果重排
load('datasets/MNIST_rs.mat')
load('datasets/MNIST.mat')
% tmp矩陣的兩行是(聚類結果,真值)
tmp = horzcat(Idx, Y);
% compare是按照聚類結果進行sort的矩陣
compare = sortrows(tmp,1);
% rs用來存儲最後的分類結果以及數量
rs = zeros(10,1);
% 取出聚類結果的所有分類
for i=1:10
for j=1:10
rs(i,j) = -length(find(compare(find(compare(:,1)==i),2)==j));
end
end
%利用James.Munkres算法完成重排操作
[assignment,cost] = munkres(rs)
% 在compare矩陣的第三行上,給出矩陣標籤
for i=1:10
compare(find(compare(:,1)==i),3) = find(assignment(i,:)==1);
end
% 計算acc和nmi,acc只需要分類結果,nmi需要結果包含所有不同的聚類簇
acc = -cost/3000
v = nmi(compare(:,3), compare(:,2))
通過讀取之前的聚類結果,並把聚類結果按照聚類情況保存爲一個10*10的矩陣,這裏需要注意由於munkres是找最小成本,所以將每一類結果的數量保存爲負數。
上圖中橫行表示對於真實結果爲i的數字,經過無監督聚類後分成不同類的數量。
經過匈牙利算法重排後的assignment,裏面標記爲1的地方就表示真實值和標記類的對應關係。
聚類結果:
acc =
0.5280
nmi =
0.4690