1. 程式人生 > >K-Means++演算法 及應用

K-Means++演算法 及應用

k-means演算法是一種基本的聚類演算法,這個演算法的先決條件是

  1)必須選擇最終結果需要聚為幾類,就是k的大小。

  2)初始化聚類中心點,也就是seeds。

  當然,我們可以在輸入的資料集中隨機的選擇k個點作為seeds,但是隨機選擇初始seeds可能會造成聚類的結果和資料的實際分佈相差很大。既然選擇初始的seeds這麼重要,那有什麼演算法可以幫助選擇初始的seeds嗎?當然有,k-means++就是選擇初始seeds的一種演算法。

      k-means++演算法選擇初始seeds的基本思想就是:初始的聚類中心之間的相互距離要儘可能的遠。wiki上對該演算法的描述是如下:

  1. 從輸入的資料點集合中隨機選擇一個點作為第一個聚類中心
  2. 對於資料集中的每一個點x,計算它與最近聚類中心(指已選擇的聚類中心)的距離D(x)
  3. 選擇一個新的資料點作為新的聚類中心,選擇的原則是:D(x)較大的點,被選取作為聚類中心的概率較大
  4. 重複2和3直到k個聚類中心被選出來
  5. 利用這k個初始的聚類中心來執行標準的k-means演算法

      從上面的演算法描述上可以看到,演算法的關鍵是第3步,如何將D(x)反映到點被選擇的概率上,一種演算法如下(詳見此地):

  1. 先從我們的資料庫隨機挑個隨機點當“種子點”
  2. 對於每個點,我們都計算其和最近的一個“種子點”的距離D(x)並儲存在一個數組裡,然後把這些距離加起來得到Sum(D(x))。
  3. 然後,再取一個隨機值,用權重的方式來取計算下一個“種子點”。這個演算法的實現是,先取一個能落在Sum(D(x))中的隨機值Random,然後用Random -= D(x),直到其<=0,此時的點就是下一個“種子點”。
  4. 重複2和3直到k個聚類中心被選出來
  5. 利用這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]);
	}
}