1.基本Kmeans算法[1]
- 選擇K個點作爲初始質心
- repeat
- 將每個點指派到最近的質心,形成K個簇
- 重新計算每個簇的質心
- until 簇不發生變化或達到最大迭代次數
選擇K個點作爲初始質心
repeat
將每個點指派到最近的質心,形成K個簇
重新計算每個簇的質心
until 簇不發生變化或達到最大迭代次數
時間複雜度:O(tKmn),其中,t爲迭代次數,K爲簇的數目,m爲記錄數,n爲維數
空間複雜度:O((m+K)n),其中,K爲簇的數目,m爲記錄數,n爲維數
2.注意問題
(1)K如何確定
kmenas算法首先選擇K個初始質心,其中K是用戶指定的參數,即所期望的簇的個數。這樣做的前提是我們已經知道數據集中包含多少個簇,但很多情況下,我們並不知道數據的分佈情況,實際上聚類就是我們發現數據分佈的一種手段,這就陷入了雞和蛋的矛盾。如何有效的確定K值,這裏大致提供幾種方法,若各位有更好的方法,歡迎探討。
1.與層次聚類結合[2]
經常會產生較好的聚類結果的一個有趣策略是,首先採用層次凝聚算法決定結果粗的數目,並找到一個初始聚類,然後用迭代重定位來改進該聚類。
2.穩定性方法[3]
穩定性方法對一個數據集進行2次重採樣產生2個數據子集,再用相同的聚類算法對2個數據子集進行聚類,產生2個具有k個聚類的聚類結果,計算2個聚類結果的相似度的分佈情況。2個聚類結果具有高的相似度說明k個聚類反映了穩定的聚類結構,其相似度可以用來估計聚類個數。採用次方法試探多個k,找到合適的k值。
3.系統演化方法[3]
系統演化方法將一個數據集視爲僞熱力學系統,當數據集被劃分爲K個聚類時稱系統處於狀態K。系統由初始狀態K=1出發,經過分裂過程和合並過程,系統將演化到它的穩定平衡狀態Ki,其所對應的聚類結構決定了最優類數Ki。系統演化方法能提供關於所有聚類之間的相對邊界距離或可分程度,它適用於明顯分離的聚類結構和輕微重疊的聚類結構。
4.使用canopy算法進行初始劃分[4]
基於Canopy Method的聚類算法將聚類過程分爲兩個階段
Stage1、聚類最耗費計算的地方是計算對象相似性的時候,Canopy Method在第一階段選擇簡單、計算代價較低的方法計算對象相似性,將相似的對象放在一個子集中,這個子集被叫做Canopy ,通過一系列計算得到若干Canopy,Canopy之間可以是重疊的,但不會存在某個對象不屬於任何Canopy的情況,可以把這一階段看做數據預處理;
Stage2、在各個Canopy 內使用傳統的聚類方法(如K-means),不屬於同一Canopy 的對象之間不進行相似性計算。
從這個方法起碼可以看出兩點好處:首先,Canopy 不要太大且Canopy 之間重疊的不要太多的話會大大減少後續需要計算相似性的對象的個數;其次,類似於K-means這樣的聚類方法是需要人爲指出K的值的,通過Stage1得到的Canopy 個數完全可以作爲這個K值,一定程度上減少了選擇K的盲目性。
其他方法如貝葉斯信息準則方法(BIC)可參看文獻[5]。
(2)初始質心的選取
(3)距離的度量
(4)質心的計算
(5)算法停止條件
(6)空聚類的處理
3.適用範圍及缺陷
4.實現
- #include <iostream>
- #include <sstream>
- #include <fstream>
- #include <vector>
- #include <math.h>
- #include <stdlib.h>
- #define k 3//簇的數目
- using namespace std;
- //存放元組的屬性信息
- typedef vector<double> Tuple;//存儲每條數據記錄
- int dataNum;//數據集中數據記錄數目
- int dimNum;//每條記錄的維數
- //計算兩個元組間的歐幾裏距離
- double getDistXY(const Tuple& t1, const Tuple& t2)
- {
- double sum = 0;
- for(int i=1; i<=dimNum; ++i)
- {
- sum += (t1[i]-t2[i]) * (t1[i]-t2[i]);
- }
- return sqrt(sum);
- }
- //根據質心,決定當前元組屬於哪個簇
- int clusterOfTuple(Tuple means[],const Tuple& tuple){
- double dist=getDistXY(means[0],tuple);
- double tmp;
- int label=0;//標示屬於哪一個簇
- for(int i=1;i<k;i++){
- tmp=getDistXY(means[i],tuple);
- if(tmp<dist) {dist=tmp;label=i;}
- }
- return label;
- }
- //獲得給定簇集的平方誤差
- double getVar(vector<Tuple> clusters[],Tuple means[]){
- double var = 0;
- for (int i = 0; i < k; i++)
- {
- vector<Tuple> t = clusters[i];
- for (int j = 0; j< t.size(); j++)
- {
- var += getDistXY(t[j],means[i]);
- }
- }
- //cout<<"sum:"<<sum<<endl;
- return var;
- }
- //獲得當前簇的均值(質心)
- Tuple getMeans(const vector<Tuple>& cluster){
- int num = cluster.size();
- Tuple t(dimNum+1, 0);
- for (int i = 0; i < num; i++)
- {
- for(int j=1; j<=dimNum; ++j)
- {
- t[j] += cluster[i][j];
- }
- }
- for(int j=1; j<=dimNum; ++j)
- t[j] /= num;
- return t;
- //cout<<"sum:"<<sum<<endl;
- }
- void print(const vector<Tuple> clusters[])
- {
- for(int lable=0; lable<k; lable++)
- {
- cout<<"第"<<lable+1<<"個簇:"<<endl;
- vector<Tuple> t = clusters[lable];
- for(int i=0; i<t.size(); i++)
- {
- cout<<i+1<<".(";
- for(int j=0; j<=dimNum; ++j)
- {
- cout<<t[i][j]<<", ";
- }
- cout<<")\n";
- }
- }
- }
- void KMeans(vector<Tuple>& tuples){
- vector<Tuple> clusters[k];//k個簇
- Tuple means[k];//k箇中心點
- int i=0;
- //一開始隨機選取k條記錄的值作爲k個簇的質心(均值)
- srand((unsigned int)time(NULL));
- for(i=0;i<k;){
- int iToSelect = rand()%tuples.size();
- if(means[iToSelect].size() == 0)
- {
- for(int j=0; j<=dimNum; ++j)
- {
- means[i].push_back(tuples[iToSelect][j]);
- }
- ++i;
- }
- }
- int lable=0;
- //根據默認的質心給簇賦值
- for(i=0;i!=tuples.size();++i){
- lable=clusterOfTuple(means,tuples[i]);
- clusters[lable].push_back(tuples[i]);
- }
- double oldVar=-1;
- double newVar=getVar(clusters,means);
- cout<<"初始的的整體誤差平方和爲:"<<newVar<<endl;
- int t = 0;
- while(abs(newVar - oldVar) >= 1) //當新舊函數值相差不到1即準則函數值不發生明顯變化時,算法終止
- {
- cout<<"第 "<<++t<<" 次迭代開始:"<<endl;
- for (i = 0; i < k; i++) //更新每個簇的中心點
- {
- means[i] = getMeans(clusters[i]);
- }
- oldVar = newVar;
- newVar = getVar(clusters,means); //計算新的準則函數值
- for (i = 0; i < k; i++) //清空每個簇
- {
- clusters[i].clear();
- }
- //根據新的質心獲得新的簇
- for(i=0; i!=tuples.size(); ++i){
- lable=clusterOfTuple(means,tuples[i]);
- clusters[lable].push_back(tuples[i]);
- }
- cout<<"此次迭代之後的整體誤差平方和爲:"<<newVar<<endl;
- }
- cout<<"The result is:\n";
- print(clusters);
- }
- int main(){
- char fname[256];
- cout<<"請輸入存放數據的文件名: ";
- cin>>fname;
- cout<<endl<<" 請依次輸入: 維數 樣本數目"<<endl;
- cout<<endl<<" 維數dimNum: ";
- cin>>dimNum;
- cout<<endl<<" 樣本數目dataNum: ";
- cin>>dataNum;
- ifstream infile(fname);
- if(!infile){
- cout<<"不能打開輸入的文件"<<fname<<endl;
- return 0;
- }
- vector<Tuple> tuples;
- //從文件流中讀入數據
- for(int i=0; i<dataNum && !infile.eof(); ++i)
- {
- string str;
- getline(infile, str);
- istringstream istr(str);
- Tuple tuple(dimNum+1, 0);//第一個位置存放記錄編號,第2到dimNum+1個位置存放實際元素
- tuple[0] = i+1;
- for(int j=1; j<=dimNum; ++j)
- {
- istr>>tuple[j];
- }
- tuples.push_back(tuple);
- }
- cout<<endl<<"開始聚類"<<endl;
- KMeans(tuples);
- return 0;
- }
#include <iostream>
#include <sstream>
#include <fstream>
#include <vector>
#include <math.h>
#include <stdlib.h>
#define k 3//簇的數目
using namespace std;
//存放元組的屬性信息
typedef vector<double> Tuple;//存儲每條數據記錄
int dataNum;//數據集中數據記錄數目
int dimNum;//每條記錄的維數
//計算兩個元組間的歐幾裏距離
double getDistXY(const Tuple& t1, const Tuple& t2)
{
double sum = 0;
for(int i=1; i<=dimNum; ++i)
{
sum += (t1[i]-t2[i]) * (t1[i]-t2[i]);
}
return sqrt(sum);
}
//根據質心,決定當前元組屬於哪個簇
int clusterOfTuple(Tuple means[],const Tuple& tuple){
double dist=getDistXY(means[0],tuple);
double tmp;
int label=0;//標示屬於哪一個簇
for(int i=1;i<k;i++){
tmp=getDistXY(means[i],tuple);
if(tmp<dist) {dist=tmp;label=i;}
}
return label;
}
//獲得給定簇集的平方誤差
double getVar(vector<Tuple> clusters[],Tuple means[]){
double var = 0;
for (int i = 0; i < k; i++)
{
vector<Tuple> t = clusters[i];
for (int j = 0; j< t.size(); j++)
{
var += getDistXY(t[j],means[i]);
}
}
//cout<<"sum:"<<sum<<endl;
return var;
}
//獲得當前簇的均值(質心)
Tuple getMeans(const vector<Tuple>& cluster){
int num = cluster.size();
Tuple t(dimNum+1, 0);
for (int i = 0; i < num; i++)
{
for(int j=1; j<=dimNum; ++j)
{
t[j] += cluster[i][j];
}
}
for(int j=1; j<=dimNum; ++j)
t[j] /= num;
return t;
//cout<<"sum:"<<sum<<endl;
}
void print(const vector<Tuple> clusters[])
{
for(int lable=0; lable<k; lable++)
{
cout<<"第"<<lable+1<<"個簇:"<<endl;
vector<Tuple> t = clusters[lable];
for(int i=0; i<t.size(); i++)
{
cout<<i+1<<".(";
for(int j=0; j<=dimNum; ++j)
{
cout<<t[i][j]<<", ";
}
cout<<")\n";
}
}
}
void KMeans(vector<Tuple>& tuples){
vector<Tuple> clusters[k];//k個簇
Tuple means[k];//k箇中心點
int i=0;
//一開始隨機選取k條記錄的值作爲k個簇的質心(均值)
srand((unsigned int)time(NULL));
for(i=0;i<k;){
int iToSelect = rand()%tuples.size();
if(means[iToSelect].size() == 0)
{
for(int j=0; j<=dimNum; ++j)
{
means[i].push_back(tuples[iToSelect][j]);
}
++i;
}
}
int lable=0;
//根據默認的質心給簇賦值
for(i=0;i!=tuples.size();++i){
lable=clusterOfTuple(means,tuples[i]);
clusters[lable].push_back(tuples[i]);
}
double oldVar=-1;
double newVar=getVar(clusters,means);
cout<<"初始的的整體誤差平方和爲:"<<newVar<<endl;
int t = 0;
while(abs(newVar - oldVar) >= 1) //當新舊函數值相差不到1即準則函數值不發生明顯變化時,算法終止
{
cout<<"第 "<<++t<<" 次迭代開始:"<<endl;
for (i = 0; i < k; i++) //更新每個簇的中心點
{
means[i] = getMeans(clusters[i]);
}
oldVar = newVar;
newVar = getVar(clusters,means); //計算新的準則函數值
for (i = 0; i < k; i++) //清空每個簇
{
clusters[i].clear();
}
//根據新的質心獲得新的簇
for(i=0; i!=tuples.size(); ++i){
lable=clusterOfTuple(means,tuples[i]);
clusters[lable].push_back(tuples[i]);
}
cout<<"此次迭代之後的整體誤差平方和爲:"<<newVar<<endl;
}
cout<<"The result is:\n";
print(clusters);
}
int main(){
char fname[256];
cout<<"請輸入存放數據的文件名: ";
cin>>fname;
cout<<endl<<" 請依次輸入: 維數 樣本數目"<<endl;
cout<<endl<<" 維數dimNum: ";
cin>>dimNum;
cout<<endl<<" 樣本數目dataNum: ";
cin>>dataNum;
ifstream infile(fname);
if(!infile){
cout<<"不能打開輸入的文件"<<fname<<endl;
return 0;
}
vector<Tuple> tuples;
//從文件流中讀入數據
for(int i=0; i<dataNum && !infile.eof(); ++i)
{
string str;
getline(infile, str);
istringstream istr(str);
Tuple tuple(dimNum+1, 0);//第一個位置存放記錄編號,第2到dimNum+1個位置存放實際元素
tuple[0] = i+1;
for(int j=1; j<=dimNum; ++j)
{
istr>>tuple[j];
}
tuples.push_back(tuple);
}
cout<<endl<<"開始聚類"<<endl;
KMeans(tuples);
return 0;
}
這裏是隨機選取的初始質心,以鳶尾花的數據集爲例,原數據集中1-50爲一個簇,51-100爲第二個簇,101到150爲第三個簇:
第二次運行結果 SSE=98.1404
。。。
第五次運行結果 SSE=123.397
由於初始質心是隨機選取的,前兩次還算正常,運行到第五次時,第一個簇基本包括了後51-150個記錄,第二個簇和第三個簇包含了第1-50個記錄,可能的原因就是隨機選擇初始點時,有兩個初始點都選在了1-50個記錄中。
轉:http://blog.csdn.net/qll125596718/article/details/8243404/
參考:
[1]Pang-Ning Tan等著,《數據挖掘導論》,2011
[2]Jiawei Han等著,《數據挖掘概念與技術》,2008
[3]聚類分析中類數估計方法的實驗比較
[4]http://www.cnblogs.com/vivounicorn/archive/2011/09/23/2186483.html
[5]一種基於貝葉斯信息準則的k均值聚類方法
[6]http://www.zhihu.com/question/19640394?nr=1¬i_id=8736954