【机器学习】k-means算法介绍及简单实现

前言

k-means算法是经典的基于划分的聚类方法,所以我们不得不先了解一下什么是聚类方法
1、对于"监督学习"(supervised learning),其训练样本是带有标记信息的,并且监督学习的目的是:对带有标记的数据集进行模型学习,从而便于对新的样本进行分类。而在“无监督学习”(unsupervised learning)中,训练样本的标记信息是未知的,目标是通过对无标记训练样本的学习来揭示数据的内在性质及规律,为进一步的数据分析提供基础。对于无监督学习,应用最广的便是"聚类"(clustering)。
2、“聚类算法”试图将数据集中的样本划分为若干个通常是不相交的子集,每个子集称为一个“簇”(cluster),通过这样的划分,每个簇可能对应于一些潜在的概念或类别。

了解了以上两个概念之后,我们现在开始学习K-means算法。


一、什么是k-means算法

【不说废话,直奔主题】k-means算法又被称为k均值算法。它的算法思想是: 先从样本集中随机选取k个样本作为簇中心,并计算所有样本与这个k个“簇中心”的距离,对于每个样本,将其划分到其距离最近的“簇中心”所在的簇中,对于新的簇计算各个簇的型的“簇中心”。说的有些绕口,k-means聚类算法的思想打个比方的话就是:物以类聚,人以群分。
小结一下上述的k-means算法的思想:

1、k个样本作为簇中心
2、各个样本点到“簇中心”的距离
3、根据新划分的簇,更新“簇中心”(这里其实就是迭代的思想奥)

好吧,大体的思想就是如所述所说,但是,难免您会觉得空乏无味,难以理解,那我们继续更生动的再次理解一下:

1、首先输入k个值,即我们指定希望通过聚类得到k个分组
2、从数据集中随机选取k个数据点作为初始大佬(质心)
3、对数据集中的每个小弟,计算每个小弟距离每个大佬的距离,离大佬距离近,就跟那个大佬
4、执行完步骤3后,每个大佬都会有一帮各自的小弟,这时候每一群选出一个新的大佬(即通过算法选出新的质心)
5、如果新大佬和老大佬之间的距离小于某一个设置的阈值(表示重新计算的质心的位置变化不大,趋于稳定,或者说收敛),可以认为我们进行的聚类已经达到了期望的结果,算法终止。
6、如果新大佬和老大佬之间的距离变化大,需要迭代3-5步。

更多解释可参考:https://www.cnblogs.com/qingyunzong/p/9760913.html#_label1

关于k-means的算法思想我们就解释到这里啦,接下来我们就说说关于k-means算法的计算过程
分割线秀一波—


二、k-means的算法过程

先走一个流程框图大体悄悄:

在这里插入图片描述

一、输入
A、输入训练集:
D = x ( 1 ) , x ( 2 ) , … , x ( m ) D=x^{(1)}, x^{(2)}, \ldots, x^{(m)} D=x(1),x(2),,x(m)
B、输入k个样本作为簇中心

二、过程:函数kMeans(D,k,maxIter)

1:从D中随机选择k个样本作为初始"簇中心"向量: μ ( 1 ) , μ ( 2 ) , … , μ ( k ) \mu^{(1)}, \mu^{(2)}, \dots, \mu^{(k)} μ(1),μ(2),,μ(k)
2:repeat
             令 C i = ∅ ( 1 ≤ i ≤ k ) C_{i}=\emptyset(1 \leq i \leq k) Ci=(1ik)
             for j=1,2,…,m do
                       计算样本 x ( j ) x^{(j)} x(j)与各“簇中心”向量 μ i ( 1 ≤ i ≤ k ) \mu^{i}(1\leq i \leq k) μi(1ik) 的欧氏距离
                       根据距离最近的“簇中心”向量确定 x j x^{j} xj的簇标记: λ j = argmin ⁡ i ∈ { 1 , 2 , … , k } d j i \lambda_{j}=\operatorname{argmin}_{i \in\{1,2, \ldots, k\}} d_{j i} λj=argmini{1,2,,k}dji
                       将样本 x j x^{j} xj划分到相应的“粗标记”: C λ j = C λ j ∪ { x ( j ) } C_{\lambda_{j}}=C_{\lambda_{j}} \cup\left\{x^{(j)}\right\} Cλj=Cλj{x(j)}
             end for
              for i=1,2,…k do
                       计算新"簇中心"向量: ( μ ( i ) ) ′ = 1 ∣ C i ∣ ∑ x ∈ C i x \left(\mu^{(i)}\right)^{\prime}=\frac{1}{\left|C_{i}\right|} \sum_{x \in C_{i}} x (μ(i))=Ci1xCix
                       if ( μ ( i ) ) ′ \left(\mu^{(i)}\right)^{\prime} (μ(i))= μ ( i ) \mu^{(i)} μ(i) then
                                   将当前“簇中心”向量 μ ( i ) \mu^{(i)} μ(i) 更新为 ( μ ( i ) ) ′ \left(\mu^{(i)}\right)^{\prime} (μ(i))
                       else
                                   保持当前均值向量不变
                                  
                       end if
              end for
              else
    until 当前“簇中心”向量均为更新
输出: C = C 1 , C 2 , … , C K C=C_{1}, C_{2}, \ldots, C_{K} C=C1,C2,,CK

上述也就是程序实现的大体伪代码思想。

既然是算法,应该有它的优缺点,那接下来就说说k-means算法的优缺点

同样分割线来一波


三、k-means算法的优缺点

3.1 优点

1、容易理解,聚类效果不错
2、处理大数据集的时候,该算法可以保证较好的伸缩性和高效性(这里是参考的伸缩性我也不太懂啥意思),其实用大白话来说:聚类效果较好。
3、当簇接近高斯分布的时候,效果较好

3.2 缺点

1、k值是用户给定的,在绝情数据处理前,k值是不知道的,不同的k值得到的结果自然也就不一样
2、对初始“簇中心”是敏感的
3、不合适发现非凸形状的簇或者大小差别较大的簇
4、特殊值(离散值,另外数据集要求是连续的)对模型的影响较大

接下来我们继续看看使用程序来实现k-means算法的一些简单案例
老规矩,你懂得


四、k-means算法简单案例

4.1 手写实现k-means算法

1、数据集(testSet.txt)

1.658985	4.285136
-3.453687	3.424321
4.838138	-1.151539
-5.379713	-3.362104
0.972564	2.924086
-3.567919	1.531611
0.450614	-3.302219

说明:每行表示二维空间的一个点,其实就是(x,y)坐标哇
2、源码

"""
 author:jjk
 datetime:2019/5/2
 coding:utf-8
 project name:Pycharm_workstation
 Program function:
"""
from numpy import *
import numpy as np
import matplotlib.pyplot as plt

# 1、读取数据
def loadDateSet(fileName):
	dataMat = []
	fr = open(fileName)  # 打开文件
	for line in fr.readlines():  # 遍历文件的每一行(每行表示一个数据)
		curLine = line.strip().split('\t')  # 处理每行数据,返回字符串list
		fltLine = list(map(float, curLine))  # 使用float函数处理list中的字符串,使其为float类型
		dataMat.append(fltLine)  # 将该数据加入到数组中
	return dataMat

# 2、向量距离计算
def distEclud(vecA, vecB):
	return sqrt(sum(power(vecA-vecB, 2)))  # 欧氏距离


# 3、构建一个包含k个随机质心的集合
def randCent(dataSet, k):
	n = shape(dataSet)[1]  # 数据特征个数(即数据维度)
	# 创建一个0矩阵,其中zeros为创建0填充的数组,mat是转换为矩阵,用于存放k个质心
	centroids = mat(zeros((k, n)))
	for i in range(n):  # 遍历每个特征
		minI = min(dataSet[:,i])  # 获取最小值
		rangeI = float(max(dataSet[:, i]) - minI)  # 范围
		centroids[:, i] = minI + rangeI * random.rand(k, 1)  # 最小值+范围*随机数
	return centroids

# 测试
#datMat = mat(loadDateSet('testSet.txt'))
#print(min(datMat[:,0]))
#print(randCent(datMat, 2))


# 4.K均值聚类算法
"""
dataSet:数据集
k:簇的个数
distMeas:距离计算
createCent:创建k个随机质心
关于距离计算方式与随机生成k个质心可以选择其他方法
"""
def kMeans(dataSet, k, distMeas=distEclud, createCent=randCent):
	m = shape(dataSet)[0]  # 数据数目
	clusterAssment = mat(zeros((m, 2)))
	# 储存每个点的簇分配结果,第一列记录簇索引,第二列记录误差,
	# 误差指当前点到簇质心的距离,可用于评估聚类的效果
	centroids = createCent(dataSet, k)  # 质心生成
	clusterChanged = True  # 标记变量,为True则继续迭代
	while clusterChanged:
		clusterChanged = False
		# 1.寻找最近的质心
		for i in range(m):  # 遍历每个数据
			minDist = inf  # 最小距离
			minIndex = -1  # 最小距离的索引
			for j in range(k):  # 遍历每个质心
				distJI = distMeas(centroids[j, :], dataSet[i, :])  # 计算该点到每个质心的距离
				if distJI < minDist:  # 与之前的最小距离比较
					minDist = distJI  # 更新最小距离
					minIndex = j  # 更新最小距离的索引
			# 到此,便得到了该点到哪个质心距离最近
			# =======================================
			if clusterAssment[i, 0] != minIndex:  # 如果之前记录的簇索引不等于目前最小距离的簇索引
				clusterChanged = True  # 设置为True,继续遍历,直到簇分配结果不再改变为止
			clusterAssment[i, :] = minIndex, minDist**2  # 记录新的簇索引和误差
		# print(centroids)
		# 2.更新质心的位置
		for cent in range(k):
			ptsInclust = dataSet[nonzero(clusterAssment[:, 0].A == cent)[0]]  # 获取给定簇的所有点
			"""
			clusterAssment[:, 0].A == cent:表示clusterAssment第一列簇索引是否等于当前的簇
			nonzero:返回一个元祖,第一个元素为True所在的行,第二个元素为True所在的列,这里取为行,即取出给定簇的数据
			例如:
			a = mat([[1,1,0],[1,1,0],[1,0,3]])
			nonzero(a)  # 返回非0元素所在行和列
			(array([0, 0, 1, 1, 2, 2], dtype=int64), array([0, 1, 0, 1, 0, 2], dtype=int64))
			"""
			centroids[cent, :] = mean(ptsInclust, axis=0)  # 然后计算均值,axis=0沿着列方向
	return centroids, clusterAssment  # 返回质心与点分配结果


datMat = mat(loadDateSet('testSet.txt'))
myCentroids, clusterAssing = kMeans(datMat, 4)

# 5.画图
marker = ['s', 'o', '^', '<']  # 散点图点的形状
color = ['b','m','c','g']  # 颜色
X = np.array(datMat)  # 数据点
CentX = np.array(myCentroids)  # 质心点4个
Cents = np.array(clusterAssing[:,0])  # 每个数据点对应的簇
for i,Centroid in enumerate(Cents):  # 遍历每个数据对应的簇,返回数据的索引即其对应的簇
	plt.scatter(X[i][0], X[i][1], marker=marker[int(Centroid[0])],c=color[int(Centroid[0])])  # 按簇画数据点
plt.scatter(CentX[:,0],CentX[:,1],marker='*',c = 'r')  # 画4个质心
plt.show()

在这里插入图片描述
获取源码:链接下文件名为:k-means_01.py文件
除此之外,还有关于改进k-means算法的二分k均值(二分k-means算法)、k-means++算法、Canopy+k-means算法、Mini Batch k-means。

五、参考链接

1、https://www.cnblogs.com/lliuye/p/9144312.html
2、https://blog.csdn.net/Daycym/article/details/84774164

  • 2
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

贾继康

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值