K-Means++演算法 及應用
k-means演算法是一種基本的聚類演算法,這個演算法的先決條件是
1)必須選擇最終結果需要聚為幾類,就是k的大小。
2)初始化聚類中心點,也就是seeds。
當然,我們可以在輸入的資料集中隨機的選擇k個點作為seeds,但是隨機選擇初始seeds可能會造成聚類的結果和資料的實際分佈相差很大。既然選擇初始的seeds這麼重要,那有什麼演算法可以幫助選擇初始的seeds嗎?當然有,k-means++就是選擇初始seeds的一種演算法。
k-means++演算法選擇初始seeds的基本思想就是:初始的聚類中心之間的相互距離要儘可能的遠。wiki上對該演算法的描述是如下:
- 從輸入的資料點集合中隨機選擇一個點作為第一個聚類中心
- 對於資料集中的每一個點x,計算它與最近聚類中心(指已選擇的聚類中心)的距離D(x)
- 選擇一個新的資料點作為新的聚類中心,選擇的原則是:D(x)較大的點,被選取作為聚類中心的概率較大
- 重複2和3直到k個聚類中心被選出來
- 利用這k個初始的聚類中心來執行標準的k-means演算法
從上面的演算法描述上可以看到,演算法的關鍵是第3步,如何將D(x)反映到點被選擇的概率上,一種演算法如下(詳見此地):
- 先從我們的資料庫隨機挑個隨機點當“種子點”
- 對於每個點,我們都計算其和最近的一個“種子點”的距離D(x)並儲存在一個數組裡,然後把這些距離加起來得到Sum(D(x))。
- 然後,再取一個隨機值,用權重的方式來取計算下一個“種子點”。這個演算法的實現是,先取一個能落在Sum(D(x))中的隨機值Random,然後用Random -= D(x),直到其<=0,此時的點就是下一個“種子點”。
- 重複2和3直到k個聚類中心被選出來
- 利用這k個初始的聚類中心來執行標準的k-means演算法
可以看到演算法的第三步選取新中心的方法,這樣就能保證距離D(x)較大的點,會被選出來作為聚類中心了。至於為什麼原因很簡單,如下圖 所示:
假設A、B、C、D的D(x)如上圖所示,當演算法取值Sum(D(x))*random時,該值會以較大的概率落入D(x)較大的區間內,所以對應的點會以較大的概率被選中作為新的聚類中心。
------------------------------低調的分割線-----------------------------------------
在看k-means 的時候,看了部分資料,個人覺得這位仁兄寫的通俗易懂。以下是根據這個上面描述所實現的一個例子,這段程式碼傳入若干條直線,用k、b值表示(y = k*x + b),選出line_num(為了和斜率k區分這裡就不用k了)條區別最大 的線:
struct Line {
float k;
float b;
};
#define INF 0xfffffff
#define rootX2Y2(x,y) sqrt(((x)*(x))+((y)*(y)))
//對選出的相似線進行篩選剔除
void clusteringLine(std::vector<Line> &vecLines ,int line_num){
if(!vecLines.size()){
std::cout<<"no lines date"<<std::endl;
return ;
}
//把k b轉化為 進行聚合的 x,y值,這裡的x y 分別是 (θ,b * cosθ)
std::vector<std::vector<float> > angleCoor;
for(auto x:vecLines){
std::vector<float> temp;
temp.push_back(180.0*atan(x.k)/PI);
temp.push_back(x.b*cos(atan(x.k)));
angleCoor.push_back(temp);
}
srand(unsigned(time(0)));
//聚類中心的索引
std::vector<int > selectedLine;
selectedLine.push_back(random(vecLines.size()));
//判斷是否被選
std::vector<bool> isSelected;
for(auto x:angleCoor) isSelected.push_back(false);
isSelected[selectedLine[0]] = true;
//每個點與最近聚類中心的距離,初始化為無窮大
std::vector<float > distance;
for(auto x:angleCoor) distance.push_back(INF);
while(selectedLine.size() < line_num){
//計算每個點與最近聚類中心的距離
for(int i = 0;i<angleCoor.size();++i){
for(int j = 0;j<selectedLine.size();++j){
float dis = rootX2Y2((angleCoor[i][0] - angleCoor[selectedLine[j]][0]),(angleCoor[i][1] - angleCoor[selectedLine[j]][1]));
distance[i] = MIN(distance[i],dis);
}
}
//新增一個新的聚類中心,選取的原則是:距離大的被選概率大
int selectId = -1;
float sum = 0 ;
for(int i = 0;i<distance.size();++i){
if(!isSelected[i]) sum += distance[i];
}
float selectValue = sum * random(1000)/1000.0;
for(int i = 0;i<distance.size();++i){
if(!isSelected[i]){
selectValue -= distance[i];
}
if(selectValue < 0){
selectId = i;
break;
}
}
isSelected[selectId] = true;
selectedLine.push_back(selectId);
}
std::cout<<"select line's id:";
for(auto x:selectedLine) std::cout<<x<<",";
std::cout<<std::endl;
//對vecLines進行清空重新複製,直接在原vec刪除的話,資料量大效率很低
std::vector<Line> temp;
for(int i=0;i<vecLines.size();++i){
if(isSelected[i])
temp.push_back(vecLines[i]);
}
vecLines.clear();
for(int i=0;i<temp.size();++i){
vecLines.push_back(temp[i]);
}
}