??? K-Means算法的輸入N,K和一個size為N的向量組vector.輸出K個兩兩互不相交的向量組.其本質是將給定的向量組劃分成K個類別,使得同類別的向量相似度比較大,而不同類別的向量之間的相似度較小.
??? 比如以下這個圖,人肉眼能看出有四個點團,但計算機不知道,為了讓計算機明白這一點,可以將點的坐標提取到向量組中,而向量之間的相似度定義為點之間的距離的相反數或者倒數.從而將這些點分開.
??? 實現過程:
??? (1)從n個數據對象任意選擇k個對象作為初始聚類中心;
??? (2)根據每個聚類對象的均值(中心對象),計算每個對象與這些中心對象的距離,并根據最小距離重新對相應對象進行劃分;
??? (3)重新計算每個(有變化)聚類的均值(中心對象);
??? (4)計算標準測度函數,當滿足一定條件,如函數收斂時,則算法終止,如果條件不滿足則回到步驟(2).
??? 實際應用中的問題:
??? 事實上,我是一個做ACM的選手,所以我比較感興趣的是K-Means能否求得一個最優解.對于這樣一個問題:從N個點取出K個作為核心,定義兩個向量之間的相似度函數f(vector1,vector2),使得所有點與其所對應的核心的相似度之和最大.然而事實讓我大失所望,K-Means算法對種子點的選取十分敏感,不同的種子會導致不同的解.
#include<math.h>
#include
<stdio.h>
#include
<
string
.h>
#define
Convergence (fabs(last-cur)<1e-8)
#define
dist(a,b) (sqrt((x[a]-px[b])*(x[a]-px[b])+(y[a]-py[b])*(y[a]-py[b])))
int
x[
50000
],y[
50000
],qx[
50000
],qy[
50000
],px[
100
],py[
100
],assign[
50000
];
int
main()
{
freopen(
"
data.txt
"
,
"
r
"
,stdin);
FILE
*fp=fopen(
"
output.txt
"
,
"
w
"
);
int
N,K,i,j,k;
double
ave=
0
,MIN=
1e15;
scanf(
"
%d%d
"
,&N,&
K);
for
(i=
1
;i<=N;i++) scanf(
"
%d%d
"
,&x[i],&
y[i]);
for
(
int
asd=
0
;asd<N;asd++
)
{
printf(
"
Executing case #%d\n
"
,asd);
if
(asd) printf(
"
Current Average:%.6lf\n
"
,ave/
asd);
printf(
"
Current Minimize:%.6lf\n
"
,MIN);
printf(
"
----------------------------------------\n
"
);
fprintf(fp,
"
Executing case #%d\n
"
,asd);
if
(asd) fprintf(fp,
"
Current Average:%.6lf\n
"
,ave/
asd);
fprintf(fp,
"
Current Minimize:%.6lf\n
"
,MIN);
fprintf(fp,
"
----------------------------------------\n
"
);
for
(i=
1
;i<=K;i++
)
{
px[i]
=x[(i+asd)%N+
1
];
py[i]
=y[(i+asd)%N+
1
];
}
double
last=1e15,cur=
0
;
while
(!
Convergence)
{
printf(
"
%.6lf\n
"
,last);
last
=
cur;
for
(i=
1
;i<=N;i++
)
{
double
Min=
1e15;
int
v;
for
(j=
1
;j<=K;j++
)
{
double
d=
dist(i,j);
if
(d<
Min)
{
Min
=
d;
v
=
j;
}
}
assign[i]
=
v;
}
for
(i=
1
;i<=K;i++
)
{
int
cnt=
0
;
for
(j=
1
;j<=N;j++
)
if
(assign[j]==
i)
{
qx[
++cnt]=
x[j];
qy[ cnt ]
=
y[j];
}
double
Min=
1e15;
int
v;
for
(j=
1
;j<=cnt;j++
)
{
double
tmp=
0
;
for
(k=
1
;k<=cnt;k++
)
tmp
+=(sqrt((qx[j]-qx[k])*(qx[j]-qx[k])+(qy[j]-qy[k])*(qy[j]-
qy[k])));
if
(tmp<
Min)
{
Min
=
tmp;
v
=
j;
}
}
px[i]
=
qx[v];
py[i]
=
qy[v];
}
cur
=
0
;
for
(i=
1
;i<=N;i++) cur+=
dist(i,assign[i]);
}
ave
+=
cur;
MIN
=MIN<cur ?
MIN:cur;
}
printf(
"
Total average:%.6lf\n
"
,ave/
N);
printf(
"
Total MIN:%.6lf\n
"
,MIN);
fprintf(fp,
"
Total average:%.6lf\n
"
,ave/
N);
fprintf(fp,
"
Total MIN:%.6lf\n
"
,MIN);
return
0
;
}
??? 運行結果如圖所示:
??? 另一個問題是算法的收斂速度,重新算了一下,結果如下圖所示:
??? 這個結果讓我大吃一驚啊,每次迭代之后更新量都很小,而且最終的值(9259914.963696)跟第一個有意義的值(10352922.175732)相差并不是很多.后來我仔細想了一下,應該是跟輸入數據有關,我的數據完全是在一定范圍內隨機生成的,分布比較均勻,所以即使隨便選也可以得到相當不錯的效果,這是我生成數據的程序:
program
makedata;
var
i,N,K:longint;
begin
assign(output,
'
data.txt
'
);
rewrite(output);
randomize;
N:
=random(
10000
);
K:
=random(
10000
);
writeln(N,
'
'
,K);
for
i:=
1
to
N
do
writeln(random(
10000
),
'
'
,random(
10000
));
close(output);
end
.
??? 于是我重新寫了makedada程序,想法是先隨機生成K個核心,再在其周圍生成其他的點:
#include<stdio.h>
#include
<time.h>
#include
<math.h>
#include
<stdlib.h>
int
main()
{
srand(unsigned(time(
0
)));
freopen(
"
data.txt
"
,
"
w
"
,stdout);
printf(
"
15000 15\n
"
);
for
(
int
i=
1
;i<=
15
;i++
)
{
int
X=rand()%
1000000
,Y=rand()%
1000000
;
for
(
int
j=
1
;j<=
1000
;j++
)
{
int
dx=rand()%
10000
,dy=rand()%
10000
;
if
(rand()&
1
) dx*=-
1
;
if
(rand()&
1
) dy*=-
1
;
printf(
"
%d %d\n
"
,X+dx,Y+
dy);
}
}
return
0
;
}
??? 再重新運行一下,得到如下結果:
??? 可以看出,收斂的速度還是可以的,而且最終結果幾乎只有最初解得一半.
??? 初除此之外,還有一個重要問題,核心數K是作為輸入給定的,而在實際應用中是無法預知的.對此可以用
ISODATA算法
作為補充.
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061
微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元

