机器学习之K-means算法(小白入门级别)
K-means算法
算法流程描述
K-means算法又名k均值算法,K-means算法中的k表示的是聚类为k个簇,means代表取每一个聚类中数据值的均值作为该簇的中心,或者称为质心,即用每一个的类的质心对该簇进行描述。
其算法思想大致为:先从样本集中随机选取 k个样本作为簇中心,并计算所有样本与这 k个“簇中心”的距离,对于每一个样本,将其划分到与其距离最近的“簇中心”所在的簇中,对于新的簇计算各个簇的新的“簇中心”。
根据以上描述,我们大致可以猜测到实现K-means算法的主要四点:
(1)簇个数 k 的选择
(2)各个样本点到“簇中心”的距离
(3)根据新划分的簇,更新“簇中心”
(4)重复上述2、3过程,直至"簇中心"没有移动
算法流程
基本K均值算法
1.选择K个点作为初始质心。
2.repeat
3. 将每个点指派到最近的质心,形成K个簇。
4. 重新计算每个簇的质心。
5. until 质心不再发生变化
K值的选择
K值的选择影响聚类效果,在进行离群点检测时,对象是否被认为是离群点可能依赖于聚类的个数。K 的选择一般是按照实际需求进行决定,或在实现算法时直接给定 K 值。
质心的初始化
选择恰当的初始质心是基本K均值过程的关键步骤,常见的方法是随机选取,但是这样簇的质量往往很差,可能只能得到局部最优。
处理选取质心问题的一种常用技术是:多次运行,每次使用一组不同的随机初始质心,然后选取具有最小SSE的簇集。还有其他更加有效的技术,比如K-means++、二分K-means、使用后处理来“修补”所产生的簇集等,在文章最后的结论中有讲解。
在下面的实现中初始的质心是随机选取的。
距离的度量
将对象点分到距离聚类中心最近的那个簇中需要最近邻的度量策略,在欧式空间中采用的是欧式距离,在处理文档中采用的是余弦相似度函数,有时候也采用曼哈顿距离作为度量,不同的情况实用的度量公式是不同的。
欧氏距离:
曼哈顿距离:
余弦相似度函数:
新质心的计算
对于分类后的产生的k个簇,分别计算到簇内其他点距离均值最小的点作为质心(对于拥有坐标的簇可以计算每个簇坐标的均值作为质心)。经过这一步会产生K个质心,继续作为上一步的聚类中心。
是否停止K-means
当质心不再改变,或给定loop最大次数loopLimit时结束算法。
python实现代码
各方法的解释
calcDis函数用于计算欧拉距离,classify函数用于计算新的质心,kmeans函数用于kmeans分类,loadDataSet函数用于加载数据集。最初的k值为随机确定。
代码
import random
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
# 计算欧拉距离
def calcDis(dataSet, centroids, k):
clalist = []
for data in dataSet:
diff = np.tile(data, (k, 1)) - centroids # 相减 (np.tile(data,(k,1))就是把data先沿x轴复制1倍,即没有复制,比如是 [0,1,2]。
# 再把结果沿y方向复制k倍得到array([[0,1,2],[0,1,2],...]))
squaredDiff = diff ** 2 # 平方
squaredDist = np.sum(squaredDiff, axis=1) # 和 (当axis为1时,是压缩列,即将每一行的元素相加,将矩阵压缩为一列)
distance = squaredDist ** 0.5 # 开根号
clalist.append(distance)
clalist = np.array(clalist) # 返回一个每个点到质点的距离len(dateSet)*k的数组
return clalist
# 计算质心
def classify(dataSet, centroids, k):
# 计算样本到质心的距离
clalist = calcDis(dataSet, centroids, k)
# 分组并计算新的质心
minDistIndices = np.argmin(clalist, axis=1) # axis=1 表示求出每行的最小值的下标
newCentroids = pd.DataFrame(dataSet).groupby(minDistIndices).mean() # DataFramte(dataSet)对DataSet分组,groupby(min)按照min进行统计分类,mean()对分类结果求均值
newCentroids = newCentroids.values
# 计算变化量
changed = newCentroids - centroids
# 返回变化量和新的质心
return changed, newCentroids
# 使用k-means分类
def kmeans(dataSet, k):
# 随机取质心,取k个
centroids = random.sample(dataSet, k)
# 更新质心 直到变化量全为0
changed, newCentroids = classify(dataSet, centroids, k)
while np.any(changed != 0):
changed, newCentroids = classify(dataSet, newCentroids, k)
centroids = sorted(newCentroids.tolist()) # tolist()将矩阵转换成列表 sorted()排序
# 根据质心计算每个集群
cluster = []
clalist = calcDis(dataSet, centroids, k) # 调用欧拉距离
minDistIndices = np.argmin(clalist, axis=1)
for i in range(k):
cluster.append([])
for i, j in enumerate(minDistIndices): # enymerate()可同时遍历索引和遍历元素
cluster[j].append(dataSet[i])
# 返回质心和集群
return centroids, cluster
# 加载数据集
def loadDatadet(infile,k):
f=open(infile,'r')
sourceInLine=f.readlines()
dataset=[]
for line in sourceInLine:
temp1=line.strip('\n')
temp2=temp1.split(' ')
dataset.append(temp2)
for i in range(0, len(dataset)):
for j in range(k):
dataset[i].append(float(dataset[i][j]))
del(dataset[i][0:k])
return dataset
if __name__ == '__main__':
dataset = loadDatadet('./西瓜数据集', 2)
centroids, cluster = kmeans(dataset, 3)
print('质心为:%s' % centroids)
print('集群为:%s' % cluster)
for i in range(len(cluster[0])):
plt.scatter(cluster[0][i][0], cluster[0][i][1], marker='o', color='yellow', s=40, label='集群1')
# 记号形状、颜色、点的大小、设置标签
for i in range(len(cluster[1])):
plt.scatter(cluster[1][i][0], cluster[1][i][1], marker='o', color='blue', s=40, label='集群1')
# 记号形状、颜色、点的大小、设置标签
for i in range(len(cluster[2])):
plt.scatter(cluster[2][i][0], cluster[2][i][1], marker='o', color='green', s=40, label='集群1')
# 记号形状、颜色、点的大小、设置标签
for j in range(len(centroids)):
plt.scatter(centroids[j][0], centroids[j][1], marker='x', color='red', s=50, label='质心')
plt.show()
数据集介绍
使用的数据是西瓜数据集4.0,来自于西瓜书。第一列是密度,第二列是含糖量,两个变量都是连续变量,共计有30条记录。已经归一化到 [0,1] ,不用进行标准化处理,且无缺失项,异常值检测省略,直接作为模型输入即可。
结果分析
聚类结果
经过测试,得出聚类结果如下图:
如果将聚类K值改为2和4,得到的结果如下所示:
多次执行后,由于初始质心为随机选择,会造成最终聚类效果不同。
结论
- 聚类是一种无监督的学习方法。聚类区别于分类,即事先不知道要寻找的内容,没有预先设定好的目标变量。
- 聚类将数据点归到多个簇中,其中相似的数据点归为同一簇,而不相似的点归为不同的簇。相似度的计算方法(距离计算方法)有很多,具体的应用选择合适的相似度计算方法。
- K-means聚类算法,是一种广泛使用的聚类算法,其中k是需要指定的参数,即需要创建的簇的数目,K-means算法中的k个簇的质心可以通过随机的方式获得,但是这些点需要位于数据范围内。在算法中,计算每个点到质心得距离,选择距离最小的质心对应的簇作为该数据点的划分,然后再基于该分配过程后更新簇的质心。重复上述过程,直至各个簇的质心不再变化为止。
- K-means算法虽然有效,但是容易受到初始簇质心的情况而影响,有可能陷入局部最优解。为了解决这个问题,可以使用另外一种称为二分K-means的聚类算法。
改进
二分K-means
二分K-means算法首先将所有数据点分为一个簇;然后使用K-means(k=2)对其进行划分;下一次迭代时,选择使得SSE(误差平方和)下降程度最大的簇进行划分;重复该过程,直至簇的个数达到指定的数目为止。算法流程如下:
二分K均值和初始化
1.初始化簇表,使之包含由所有点组成的簇。
2.repeat
3. 从簇表中取出一个簇。
4. {对选定的簇进行多次“二分”实验。}
5. for i=1 to 实验次数 do
6. 使用基本K均值,二分选定的簇。
7. end for
8. 从二分实验中选择具有最小总SSE的两个簇。
9. 将这两个簇添加到簇表中。
10.until 簇表中包含K个簇。
实验表明,二分K-means算法的聚类效果要好于普通的K-means聚类算法。通过记录K均值二份簇所产生的簇类序列,我们还可以使用二分K均值产生层次聚类。
K-means++
K-means++的详解可见:https://blog.csdn.net/a19990412/article/details/89000149
后处理降低SSE
一种明显降低SSE(这里的误差可以是欧式距离)的方法是找出更多的簇,即使用较大的K。然而在很多情况下,我们希望降低SSE,但是不想增加K的个数。这时我们可以选择采用多种技术来“修补”结果簇。策略是关注每一个簇,因为总的SSE不过是每个簇的SSE之和。一种常用的方法是交替地使用簇分裂和簇合并,使用这种方法,常常可以避开局部极小,并且仍然可以得到期望个数的簇。
通过增加簇的个数来降低总SSE的两种策略:
- 分裂一个簇:通常选择具有最大SSE的簇,也可以分列在特定属性上具有最大标准差的簇。
- 引进一个新的质心:通常选择离所有质心最远的点。另一种方法是从所有的点或者具有最高SSE的点中随机地选择。
减少簇的个数,并且试图最小化总SSE的增长的两种策略: - 拆散一个簇:通常选择使总SSE增加最少的簇,删除簇的质心,并将簇中的点重新指派到其他的簇。
- 合并两个簇:通常选择质心最近的两个簇,显然上面的方法更好一些。(这两种方法在层次聚类中也有使用,称为志新方法和Ward方法。)
优点与缺点
基本K-means算法的缺点很明显,有如下几点:
- 受离群点影响大。当存在离群点时,结果簇的质心可能不如没有离群点时那样有代表性,并且导致SSE也比较高。解决方法:预处理时识别并删除离群点。
- 随机选择初始质心造成的簇的质量下降。解决方法:改进中的方法。
- 如果所有的点在指派步骤都未分到某一个簇中,就会得到空簇。解决办法:采用某种策略选择一个替补质心,如选择一个距离当前任何质心最远的点,或者从具有最大SSE的簇中选择一个替补质心。
- 不适用于所有的数据类型。它不能处理非球形、不同尺寸和不同密度的簇。
优点有:
- 原理比较简单,实现也是很容易,收敛速度快,聚类效果较好。
- 算法的可解释度比较强。
- 主要需要调参的参数仅仅是簇数k(对比其他聚类算法)。
本文地址:https://blog.csdn.net/weixin_43903564/article/details/107232617