K-means算法

偶然接触到了K-means,在理解之后写下博客记录。

首先,K-means是一种无监督学习聚类算法。什么是聚类算法,聚类就是对大量未标注的数据集,按数据存在的内部特征特征划分为多个不同的类别。

K-means

算法接受参数k,然后将事先输入的n个数据划分为k个聚类。其中满足条件:同一聚类对象相似度高,不同聚类对象相似度较小。

算法思想

k个点为中心聚类,对靠近的对象类归类,通过迭代,逐渐更新各聚类中心。

算法描述

(1)适当选择c个类的初始中心

(2)在第k个迭代中,对任意样本,求其到各中心的距离,将样本归到距离最短的中心所在的类

(3)利用均值等方法更新中心值

(4)对于所有的聚类中心,如果利用(2)(3)步骤迭代法更新后,值不变,则迭代结束

对于以上文字来说可能不太容易理解,接下来博主会放图结合文字来说明K-means

简单的准备几个点作为数据演示:x1(1,1),x2(3,2),x3(1,3),x4(4,7),x5(6,7),x6(5,10),x7(10,4),x8(11,6),x9(12,4)

将这些点绘制出来(左上角为原点):

将这些点用矩阵表示,每一列表示一个点,第0行表示X值,第一行表示Y值,第二行表示数据所属类别:

执行算法描述的第一步:

适当选择c个类的初始中心,我们打算将数据分为三个类,则需要任意选取三个点作为聚类中心,我们取(1,1),(3,2),(1,3)作为初始聚类中心

执行算法描述第二步:

在第k个迭代中,对任意样本,求其到各中心的距离,将样本归到距离最短的中心所在的类,即需要求出事先准备好的9个点到每个聚类中心点的距离,我们构建D矩阵用于存储距离

D[i][j]表示第(i+1)个聚类点到第(j+1)个数据点的距离,例如:D[1][4] = 5.831 表示第2个聚类点(3,2)到第5个数据点(6,7)的距离

D矩阵每一行表示所有点到同一聚类中心的距离,每一列表示同一点到不同聚类中心的距离,我们将每一列进行比较,得到最小的距离,即可判断该点属于哪一类。我们构建G矩阵,将D矩阵按列进行比较,找到最小值位置,对应G矩阵位置 置为1,例如:D矩阵第一列0,2.2361,2,最小值为0,对应下标为D[0][0],则G[0][0] = 1, D矩阵第四列,6.7082,5.099,5,最小值为5,对应下标D[2][3],则G[2][3] = 1,得到G矩阵,

每一列表示一个数据,第几行为1表示该数据属于哪一类,例如x1的第一行为1,表示x1当前属于第一类,x2,x5,x7,x8,x9的第二行为1,则这些数据当前属于第二类,x3,x4,x6的第三行为1,则这些数据属于第三类。

执行算法描述第三步:

利用均值方法更新中心值,我们将同一类的数据取均值代替原来的聚类点。当前第一类只有一个数据点(1,1),所以聚类点1为(1/1,1/1)=(1,1),第二类数据有x2,x5,x7,x8,x9,则新的聚类点为((3+6+10+11+12)/5,2+7+4+6+4)/5)=(8.4,4.6),同理,新的第三个聚类点为((1+4+5)/3, (3+7+10)/3)=(3.333,6.666)

执行算法描述第四步:

对于所有的聚类中心,如果利用(2)(3)步骤迭代法更新后,值不变,则迭代结束

由于新的聚类点改变了,需要继续迭代

继续构建距离矩阵D,将所有点对新的聚类中心求距离

继续构建G矩阵,找到数据点所属类别

这次x1,x2,x3属于第一类,x4,x5,x6属于第三类,x7,x8,x9属于第二类,利用均值方法更新中心值,新的聚类点为(1.666 ,2.000)、 ( 11.000,  4.666)、(  5.000,  8.000),发现聚类点再次变化,继续迭代

继续构建距离矩阵D,将所有点对新的聚类中心求距离

继续构建G矩阵,找到数据点所属类别

新的聚类点为(1.666 ,2.000)、 ( 11.000,  4.666)、(  5.000,  8.000),发现聚类点停止改变,停止迭代。

所以我们将x1,x2,x3分为一类,x4,x5,x6分为一类,x7,x8,x9分为一类,将其绘制出来:

以上就是本人对K-means的理解

接下来为C++、opencv对K-means测试的代码

#include<iostream>
#include<opencv2\opencv.hpp>
#include<math.h>

using namespace std;
using namespace cv;
#define K 3
//找到两点之间距离
float getDistance(Point2f A, Point2f B)
{
	float distance = 0.0;
	distance = sqrt(pow(A.x - B.x, 2) + pow(A.y - B.y,2));
	return distance;
}
//找出vector中的最小值
int getMinIndex(vector<float>data)
{
	float index = 0;
	float min = 10000;
	for (int i = 0; i < K; i++)
	{
		if (data[i] < min)
		{
			min = data[i];
			index = i;
		}
	}
	return index;
}
//找出vector中值为1的下标
vector<int> getIndexIsOne(vector<float>data)
{
	vector<int>index;
	for (int i = 0; i < data.size(); i++)
	{
		if (data[i] == 1)
			index.push_back(i);
	}
	return index;
}
//是否停止迭代
bool shouldStop(vector<Point2f>oldCentroids, vector<Point2f>centroids, int iterations, int maxIt)
{
	if (iterations > maxIt)
		return true;
	return oldCentroids == centroids;

}
//更新数据类别
void updateLabels(Mat &dataset, vector<Point2f>points, vector<Point2f>&centroids)
{
	//构建D0矩阵  K行N列,用与记录每个点与聚类点的距离
	Mat D0 = Mat::zeros(Size(points.size(), K), CV_32F);
	//计算每个点与聚类点之间的距离,D0[i][j]表示第i个数据点与第j个聚类点的距离
	for (int i = 0; i < K; i++)
	{
		for (int j = 0; j < points.size(); j++)
		{
			D0.at<float>(i, j) = getDistance(points[j], centroids[i]);
		}
	}
	//构建G矩阵  K行N列,按列进行比较,找到最小值位置,对应G矩阵位置 置为1
	Mat G0 = Mat::zeros(Size(points.size(), K), CV_32F);
	for (int i = 0; i < points.size(); i++)
	{
		Mat col;
		//获取每一列数据后,使用reshape转换成vector便于计算
		D0.colRange(i, i + 1).copyTo(col);
		//reshape(cn,row) 
		vector<float>colsVec(col.reshape(1, 1));
		G0.at<float>(getMinIndex(colsVec), i) = 1;
	}
	for (int i = 0; i < K; i++)
	{
		Mat row;
		//获取每一行数据后,使用reshape转换成vector便于计算
		G0.rowRange(i, i + 1).copyTo(row);
		vector<float>rowsVec(row.reshape(1, 1));
		vector<int>indexVec;
		indexVec = getIndexIsOne(rowsVec);
		int xSum = 0.0;
		int ySum = 0.0;
		//利用均值更新聚类点
		for (int j = 0; j < indexVec.size(); j++)
		{
			dataset.at<float>(2, indexVec[j]) = i*1.0;//bug
			xSum += points[indexVec[j]].x;
			ySum += points[indexVec[j]].y;
		}
		centroids[i].x = xSum*1.0 / indexVec.size();
		centroids[i].y = ySum*1.0 / indexVec.size();
	}
}
Mat Kmeans(vector<Point2f>points, int classification, int maxIt)
{
	//创建一个 3行,N列的数据,多出来的一行用于表示数据类别
	Mat dataset = Mat::zeros(Size(points.size(), 3), CV_32FC1);
	for (int i = 0; i < points.size(); i++)
	{
		dataset.at<float>(0, i) = points[i].x;
		dataset.at<float>(1, i) = points[i].y;
	}
	vector<Point2f>centroids(3);
	//初始化聚类点,任意取K个数据作为初始数据
	for (int i = 0; i < K; i++)
	{
		centroids[i] = points[i];
	}
	int iterations = 0;
	//用于比较聚类点是否发生变化
	vector<Point2f>oldCentroids(3,Point(0,0));

	while (!shouldStop(oldCentroids, centroids, iterations, maxIt))
	{
		iterations++;
		oldCentroids.assign(centroids.begin(), centroids.end());

		updateLabels(dataset, points, centroids);
	}
	return dataset;
}
//绘图
void DrawMat(Mat dataset,Mat &drawingBoard)
{
	
	for (int i = 0; i < dataset.cols; i++)
	{
		if (dataset.at<float>(2, i) == 0)
			//circle(drawingBoard,Point(dataset.at<float>(1, i),)
			drawingBoard.at<Vec3b>(dataset.at<float>(0, i), dataset.at<float>(1, i)) = Vec3b(0, 0, 255);
		else if (dataset.at<float>(2, i) == 1)
			drawingBoard.at<Vec3b>(dataset.at<float>(0, i), dataset.at<float>(1, i)) = Vec3b(0, 255, 0);
		else if (dataset.at<float>(2, i) == 2)
			drawingBoard.at<Vec3b>(dataset.at<float>(0, i), dataset.at<float>(1, i)) = Vec3b(255, 0, 0);
	}
	imshow("散点分类图",drawingBoard);
}
int main()
{

	Point2f x1 = Point2f(1, 1);
	Point2f x2 = Point2f(3, 2);
	Point2f x3 = Point2f(1, 3);
	Point2f x4 = Point2f(4, 7);
	Point2f x5 = Point2f(6, 7);
	Point2f x6 = Point2f(5, 10);
	Point2f x7 = Point2f(10, 4);
	Point2f x8 = Point2f(11, 6);
	Point2f x9 = Point2f(12, 4);
	//将数据放入容器中,便于计算
	vector<Point2f>points;
	points.push_back(x1);
	points.push_back(x2);
	points.push_back(x3);
	points.push_back(x4);
	points.push_back(x5);
	points.push_back(x6);
	points.push_back(x7);
	points.push_back(x8);
	points.push_back(x9);


	Mat dataset1 = Mat::zeros(20, 20, CV_8UC3);
	for (int i = 0; i < points.size(); i++)
	{
		dataset1.at<Vec3b>(points[i]) = Vec3b(255, 255, 255);
	}

	Mat dataset = Kmeans(points, K, 200);
	Mat drawingBoard = Mat::zeros(20, 20, CV_8UC3);
	DrawMat(dataset, drawingBoard);
	waitKey(0);
	return 0;
}

 

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