KMeans聚類算法

KMeans算法是很典型的基於距離的聚類算法,採用距離作爲相似性的評價指標,即認爲兩個對象的距離越近,其相似度就越大。該算法認爲簇是由距離靠近的對象組成的,因此把得到緊湊且獨立的簇作爲最終目標。


k-means 算法基本步驟

(1)  從 n個數據對象任意選擇 k 個對象作爲初始聚類中心;

(2)  根據每個聚類對象的均值(中心對象),計算每個對象與這些中心對象的距離;並根據最小距離重新對相應對象進行劃分;

(3)  重新計算每個(有變化)聚類的均值(中心對象);

(4)  計算標準測度函數,當滿足一定條件,如函數收斂時,則算法終止;如果條件不滿足則回到步驟(2)。


以下是C++實現:

頭文件kmeans.h:

#ifndef KMEANS
#define KMEASN
/*********************************************\
  *           KMeans 聚類算法
  * 1.數據格式爲文本文件,每行一條數據
  * 2.開頭爲數據名,string類型。後面緊接着是屬性信息,
      此處爲double,每個屬性之間用tab或空格隔開。
  * 3.每個數據在計算時僅使用了第一列的屬性信息
  * 4.距離使用的是絕對值距離
  * 5.如果使用更多的屬性,則需要修改相應的函數
  * 6.測試中顯示了每次的計算結果,不需要的話可以在
      KMeans::cluster()中刪除printResult()即可。
  * Author: yuyang
  * Date: 2013-03-30
/ ********************************************/
#include <vector>
#include <iostream>

using namespace std;

///需要聚類的數據類型
class   Item
{///data item
public:
    string  name;
    vector<double>  vovalue;
};
bool    operator==(const Item &ia, const Item &ib);
bool    operator!=(const Item &ia, const Item &ib);

class KMeans
{
public:
    KMeans(int km=2);
    int     setData(string name);///讀入數據
    void    cluster();///聚類函數
    void    printData();///輸出原始數據
    void    printResult();///輸出聚類結果
    virtual ~KMeans();

protected:
    int     ptcDistence(const Item & item);///某個點到類中心的距離
            ///返回到最近的類的類編號
    int     stable();///判斷是否收斂

private:

    void    classify();///將每個數據放到最近的類中
    void    init();///初始化,選取前k個數據作爲k類
    void    calCen();///計算當前每個類的中心

    int     k;
    vector<Item>  datas;///保存的原始數據
    vector<double>  ocen;///本輪聚類前類中心
    vector<double>  cen;///本輪聚類後的聚類中心,size=k
    vector<int>    *kclass;///kclass中有k個指針,分別指向k個vector<int>,
    ///每個vector<int>爲該類在vector<Item>中的編號

    static  const   int     READ_ERR=1;
    static  const   int     OK=0;
    static  const   double  AC_ERR = 1E-3;
};
#endif // KMEANS


實現文件kmeans.cpp:

#include "kmeans.h"
#include <fstream>
#include <sstream>
#include <cmath>
#include <algorithm>

///判斷2個Item是否相等
bool    operator==(const Item &ia, const Item &ib)
{
    if(abs(ia.vovalue[0] - ib.vovalue[0])<1E-4)
        return  true;
    return false;
}
bool    operator!=(const Item &ia, const Item &ib)
{
    return !(ia==ib);
}

KMeans::KMeans(int km):
    k(km),ocen(km),cen(km)
{
    kclass = new vector<int> [k];
}
KMeans::~KMeans()
{
    delete[]    kclass;
    cout<<"KMeans bye."<<endl;
}
int    KMeans::setData(string name)
{
    fstream inf;
    inf.open(name.c_str(),ios::in);
    if(!inf)
    {
        cerr<<"open file "<<name<<" error !"<<endl;
        return KMeans::READ_ERR;
    }
    string line;
    while(getline(inf,line))
    {
        istringstream is(line);
        string iname,ivalue;
        Item    it;
        is>>it.name;///get class name
        double d;
        while(is>>d)///get value
        {
            it.vovalue.push_back(d);
        }
        datas.push_back(it);
    }
    inf.close();
    return KMeans::OK;
}
void    KMeans::printData()
{
    vector<Item>::iterator it = datas.begin();
    for(; it!=datas.end(); ++it)
    {
        Item i = *it;
        cout<<i.name<<"\t";
        vector<double>::iterator id=i.vovalue.begin();
        for(; id!=i.vovalue.end(); ++id)
        {
            cout<<*id<<"\t";
        }
        cout<<"\n";
    }
    cout<<endl;
}
int     KMeans::stable()///判斷是否收斂
{
    for(int i=0; i<k; ++i)
        if(abs(cen[i]-ocen[i])>AC_ERR)//cen[i]!=ocen[i]
            return  false;
    return  true;
}

void    KMeans::printResult()
{///output cluster result
    for(int i=0; i<k; ++i)
    {
        int siz = (kclass+i)->size();
        double center=cen[i];
        cout<<"class : "<< i <<" ,size= "
            <<siz
            <<" , class center: "<<center
            <<endl;
        for(int j=0; j<siz; ++j)
        {
            int index = (kclass+i)->at(j);
            cout<<datas[index].name<<"\t";
        }
        cout<<endl<<endl;
    }
}
///返回到最近的類的類編號
int KMeans::ptcDistence(const Item & item)
{///某個點到類中心的距離
    double d=10E9 ;//= new double[k];
    int c=0;
    for(int i=0; i<k; ++i)
    {///item 到第i個類中心的距離
        double poi = item.vovalue[0];
        double poc = cen[i];
        if(d>abs(poi-poc))
        {
            d = abs(poi-poc);///絕對值距離
            c = i;
        }
    }
    return c;
}
void    KMeans::classify()///將每個數據放到最近的類中
{
    ///清空上次分類信息
    for(int i=0; i<k; ++i)
    {
        (kclass+i)->clear();
    }

    unsigned    int iiter = 0;//datas.begin();
    for(; iiter<datas.size(); ++iiter)
    {
        int  c = ptcDistence(datas[iiter]);
        ///將第i個數據分到第c個類中
        (kclass+c)->push_back(iiter);
    }
}

void    KMeans::init()
{
    ///選取前k個互不相等的數據作爲初始的k個類
    for(int i=0,cnt=0; cnt<k && i<datas.size(); ++i)
    {
        vector<Item>::iterator it=
        find(datas.begin(),datas.begin()+i,datas.at(i));
        if(it!=datas.begin()+i)///find one
            continue;

        (kclass+cnt)->push_back(i);
        cen[cnt]  = datas.at(i).vovalue[0];///類中心
        ++cnt;
    }

}
void    KMeans::calCen()
{///計算當前每個類的中心
    for(int i=0; i<k; ++i)
    {///class i
        double  sum=0;
        int classnum = (kclass+i)->size();
        for(int j=0; j<classnum; ++j)
        {
            int index = (*(kclass+i))[j];
            sum += datas[index].vovalue[0];
        }
        ocen[i] = cen[i]; ///save old value
        cen[i] = sum/classnum;
    }
}

/*** 核心聚類函數 **************************\
 * 1.初始化
 * 2.判斷類中心是否穩定
 * 3.如果已經穩定,則結束算法
 * 4.如果不穩定,調用分類函數classify()
 * 5.轉2
\********************************************/
void    KMeans::cluster()
{
    init();
    int round=0;
    cout<<"init:"<<endl;
    printResult();
    while(!stable())
    {
        cout<<"round = "<<++round<<endl;
        classify();
        calCen();
        printResult();
    }
}


測試用例:

#include <iostream>
#include "kmeans.h"

using namespace std;


int main()
{
    string file="a.txt";
    KMeans  km(2);
    km.setData(file);
    km.printData();
    km.cluster();
    cout<<"\nfinished..."<<endl;
    //km.printResult();
    return 0;
}


如果要使用更多的信息量參與計算,則需要修改的地方有:距離計算,類中心計算和判斷是否穩定這些地方。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章