k-means聚类分割
算法的思想是初始選取M個種子點,將周圍點的特征和種子點特征進行距離的測定,距離最小的點和該種子點歸為一類。則可以分為M個類別,計算這些類別特征的質心作為新的種子點,再次分類,如此迭代n次后的結果便是聚類分割的結果。
? ? ?K-means也是聚類算法中最簡單的一種了,但是里面包含的思想卻是不一般。最早我使用并實現這個算法是在學習韓爺爺那本數據挖掘的書中,那本書比較注重應用。看了Andrew Ng的這個講義后才有些明白K-means后面包含的EM思想。
???? 聚類屬于無監督學習,以往的回歸、樸素貝葉斯、SVM等都是有類別標簽y的,也就是說樣例中已經給出了樣例的分類。而聚類的樣本中卻沒有給定y,只有特征x,比如假設宇宙中的星星可以表示成三維空間中的點集。聚類的目的是找到每個樣本x潛在的類別y,并將同類別y的樣本x放在一起。比如上面的星星,聚類后結果是一個個星團,星團里面的點相互距離比較近,星團間的星星距離就比較遠了。
???? 在聚類問題中,給我們的訓練樣本是,每個,沒有了y。
???? K-means算法是將樣本聚類成k個簇(cluster),具體算法描述如下:
| 1、 隨機選取k個聚類質心點(cluster centroids)為。 2、 重復下面過程直到收斂 { ?????????????? 對于每一個樣例i,計算其應該屬于的類 ??????????????? ?????????????? 對于每一個類j,重新計算該類的質心 ??????????????? } |
???? K是我們事先給定的聚類數,代表樣例i與k個類中距離最近的那個類,的值是1到k中的一個。質心代表我們對屬于同一個類的樣本中心點的猜測,拿星團模型來解釋就是要將所有的星星聚成k個星團,首先隨機選取k個宇宙中的點(或者k個星星)作為k個星團的質心,然后第一步對于每一個星星計算其到k個質心中每一個的距離,然后選取距離最近的那個星團作為,這樣經過第一步每一個星星都有了所屬的星團;第二步對于每一個星團,重新計算它的質心(對里面所有的星星坐標求平均)。重復迭代第一步和第二步直到質心不變或者變化很小。
???? 下圖展示了對n個樣本點進行K-means聚類的效果,這里k取2。
?????
???? K-means面對的第一個問題是如何保證收斂,前面的算法中強調結束條件就是收斂,可以證明的是K-means完全可以保證收斂性。下面我們定性的描述一下收斂性,我們定義畸變函數(distortion function)如下:
?????
???? J函數表示每個樣本點到其質心的距離平方和。K-means是要將J調整到最小。假設當前J沒有達到最小值,那么首先可以固定每個類的質心,調整每個樣例的所屬的類別來讓J函數減少,同樣,固定,調整每個類的質心也可以使J減小。這兩個過程就是內循環中使J單調遞減的過程。當J遞減到最小時,和c也同時收斂。(在理論上,可以有多組不同的和c值能夠使得J取得最小值,但這種現象實際上很少見)。
???? 由于畸變函數J是非凸函數,意味著我們不能保證取得的最小值是全局最小值,也就是說k-means對質心初始位置的選取比較感冒,但一般情況下k-means達到的局部最優已經滿足需求。但如果你怕陷入局部最優,那么可以選取不同的初始值跑多遍k-means,然后取其中最小的J對應的和c輸出。
???? 下面累述一下K-means與EM的關系,首先回到初始問題,我們目的是將樣本分成k個類,其實說白了就是求每個樣例x的隱含類別y,然后利用隱含類別將x歸類。由于我們事先不知道類別y,那么我們首先可以對每個樣例假定一個y吧,但是怎么知道假定的對不對呢?怎么評價假定的好不好呢?我們使用樣本的極大似然估計來度量,這里是就是x和y的聯合分布P(x,y)了。如果找到的y能夠使P(x,y)最大,那么我們找到的y就是樣例x的最佳類別了,x順手就聚類了。但是我們第一次指定的y不一定會讓P(x,y)最大,而且P(x,y)還依賴于其他未知參數,當然在給定y的情況下,我們可以調整其他參數讓P(x,y)最大。但是調整完參數后,我們發現有更好的y可以指定,那么我們重新指定y,然后再計算P(x,y)最大時的參數,反復迭代直至沒有更好的y可以指定。
???? 這個過程有幾個難點,第一怎么假定y?是每個樣例硬指派一個y還是不同的y有不同的概率,概率如何度量。第二如何估計P(x,y),P(x,y)還可能依賴很多其他參數,如何調整里面的參數讓P(x,y)最大。這些問題在以后的篇章里回答。
???? 這里只是指出EM的思想,E步就是估計隱含類別y的期望值,M步調整其他參數使得在給定類別y的情況下,極大似然估計P(x,y)能夠達到極大值。然后在其他參數確定的情況下,重新估計y,周而復始,直至收斂。
???? 上面的闡述有點費解,對應于K-means來說就是我們一開始不知道每個樣例對應隱含變量也就是最佳類別。最開始可以隨便指定一個給它,然后為了讓P(x,y)最大(這里是要讓J最小),我們求出在給定c情況下,J最小時的(前面提到的其他未知參數),然而此時發現,可以有更好的(質心與樣例距離最小的類別)指定給樣例,那么得到重新調整,上述過程就開始重復了,直到沒有更好的指定。這樣從K-means里我們可以看出它其實就是EM的體現,E步是確定隱含類別變量,M步更新其他參數來使J最小化。這里的隱含類別變量指定方法比較特殊,屬于硬指定,從k個類別中硬選出一個給樣例,而不是對每個類別賦予不同的概率。總體思想還是一個迭代優化過程,有目標函數,也有參數變量,只是多了個隱含變量,確定其他參數估計隱含變量,再確定隱含變量估計其他參數,直至目標函數最優。
#include "opencv/cxcore.h"
#include "opencv/highgui.h"#include<opencv2\opencv.hpp>
#include <iostream>
using namespace std;
//Set element in Matrix
void SetMatElem(CvMat *mat, int row, int col, int channel, int val)
{
((float*)(mat->data.ptr+mat->step*row))[col*mat->step+channel]= val;
}
class Color
{
int num;
public:
int b, g, r;
Color(){ b= 0; g= 0; r=0;num=0;}
Color(int x, int y, int z) { b=x; g= y; r =z; num=0;}
void Add( int x, int y, int z)//Calcuate average color
{
b=( b*num+x)/(num+1);
g=( g*num+y)/(num+1);
r= (r*num+z)/(num+1);
++num;
cout << num<<endl;
}
};
int main( int argc, char** argv )
{
IplImage *src,*dst;
int cluster_num;
//Load image
? ? if (argc==2){
? src= cvLoadImage(argv[1]);
? ?}
? ?else{
? src= cvLoadImage("nature4.jpg");
? ?}
? ?if( src == 0) { cout<<"Cannot open the file!"; return(1000);}
? ?//Read input, get the number of clusters
? ?cout<<"Please input cluster number:";
? ?cin>>cluster_num;
? ?
? ?Color *color_tab= new Color[cluster_num]();
? ?CvMat *sample = cvCreateMat(src->height*src->width, 1, CV_32FC(5));
? ?CvMat *cluster = cvCreateMat(src->height*src->width, 1, CV_32SC1);
//Initalize Matrix for 5-D vector to store each pixel info.in image
? ?for( int i=0; i<src->height ;i++){ //row
? for( int j=0; j<src->width; j++){ //col
?long int index= i*src->width+j;
?SetMatElem(sample,index,0,0,i);
?SetMatElem(sample,index,0,1,j);
?SetMatElem(sample,index,0,2,CV_IMAGE_ELEM(src,uchar,i,j*3));
?SetMatElem(sample,index,0,3,CV_IMAGE_ELEM(src,uchar,i,j*3+1));
?SetMatElem(sample,index,0,4,CV_IMAGE_ELEM(src,uchar,i,j*3+2));
? }
? ?}
? ?//Do KMeans to Matrix sample, and store result in Matrix cluster
? ? ?cvKMeans2(sample, cluster_num, cluster,
? ? ? ? ? ? ? ? ? cvTermCriteria( CV_TERMCRIT_EPS+CV_TERMCRIT_ITER, 10, 10 ));
dst = cvCreateImage(cvGetSize(src),8,3);
cvZero(dst);
//Visit each pixel in original image, calculate average color in each clusters
//and store result in Table color_tab
? ?for(int i=0; i<dst->height; i++){
? for( int j=0; j<dst->width; j++){
? long int index= i* dst->width+j;
? int cluster_idx= cluster->data.i[index];
? int x= CV_IMAGE_ELEM(src,uchar, i, j*3);
? int y= ?CV_IMAGE_ELEM(src,uchar, i, j*3+1);
? int z= CV_IMAGE_ELEM(src,uchar, i, j*3+2);
? color_tab[cluster_idx].Add(x,y,z);
? }
? ?}
//Assign average color in cluster to destination image
? ?for(int i=0; i<dst->height; i++){
? for( int j=0; j<dst->width; j++){
? long int index= i* dst->width+j;
? int cluster_idx= cluster->data.i[index];
? CV_IMAGE_ELEM(dst,uchar, i, j*3) = color_tab[cluster_idx].b;
? CV_IMAGE_ELEM(dst,uchar, i, j*3+1) = color_tab[cluster_idx].g;
? CV_IMAGE_ELEM(dst,uchar, i, j*3+2) = color_tab[cluster_idx].r;
? }
? ?}
//Show image
? ?cvNamedWindow("K-means Segmentation Result");
? ?cvShowImage("K-means Segmentation Result", dst);
? ?cv::Mat dst1(dst->width,dst->height,CV_8UC1);
? ?for (int i = 0; i<dst->height; i++){
? for (int j = 0; j<dst->width; j++){
? long int index = i* dst->width + j;
? int cluster_idx = cluster->data.i[index];
? dst1.at<uchar>(i, j) = cluster_idx*50;
? }
? ?}
? ?cv::imshow("dst1", dst1);
? ?cvWaitKey(-1);
? ?cvReleaseMat(&sample);
? ?cvReleaseMat(&cluster);
? ?
? ?cvReleaseImage(&src);
? ?cvReleaseImage(&dst);
? ?
} 與50位技術專家面對面20年技術見證,附贈技術全景圖
總結
以上是生活随笔為你收集整理的k-means聚类分割的全部內容,希望文章能夠幫你解決所遇到的問題。