偶然接触到了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>¢roids)
{
//构建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;
}