文章目录
- 1. K-means算法简介
- 2. K-means算法原理
- 2.1 算法具体步骤
- 2.2 k取值方法
- 2.2.1 手肘法
- 2.2.2 轮廓系数法
- 2.3 K-means++
- 2.4 算法终止条件
- 3. K-means算法特点
- 4. K-means算法应用场景
- 5. K-means算法的Python应用
- 5.1 K-means算法的Python实现
- 5.2 sklearn.cluster.Kmeans函数的应用
- 6. 源码仓库地址
1. K-means算法简介
K-means算法是一种聚类算法,所谓聚类,即根据相似性原则,将具有较高相似度的数据对象划分至同一类簇,将具有较高相异度的数据对象划分至不同类簇。聚类与分类最大的区别在于,聚类过程为无监督过程,即待处理数据对象没有任何先验知识,而分类过程为有监督过程,即存在有先验知识的训练数据集。
2. K-means算法原理
K-means算法解决的问题是,在事先不知道如何分类的情况下(即无监督),让程序根据距离的远近,把N个对象(局部)最优的划分为k个类。它是无监督算法中比较常见的一种算法,原理比较简单易懂。本质是通过循环,不断迭代类中心点,计算各个对象到新的类中心点的距离并根据距离最近的原则重新归类,当类内距离最小、类间距离最大时,即可停止迭代(使用中,常常会限定迭代次数,防止陷入死循环。当达到预先设定得循环次数或类中心点不再发生变化时,最后一次迭代得到的结果,即为最终聚类结果)。
2.1 算法具体步骤
算法具体步骤如下:
第一步:指定聚类类数k(此处涉及k的选择方法);
第二步:选定初始化聚类中心。随机或指定k个对象,作为初始化聚类中心(此处随机选的方法可以升级,以达到更好的聚类效果,比如kmeans++聚类算法);
第三步:得到初始化聚类结果。计算每个对象到k个聚类中心的距离,把每个对象分配给离它最近的聚类中心所代表的类别中,全部分配完毕即得到初始化聚类结果,聚类中心连同分配给它的对象作为一类;
第四步:重新计算聚类中心。得到初始化聚类结果后,重新计算每类的类中心点(计算均值),得到新的聚类中心;
第五步:迭代循环,得到最终聚类结果。重复第三步和第四步,直到满足迭代终止条件。
2.2 k取值方法
结合书本中理论和在实际业务中遇到的情况,总结出以下几点k值的选择方法供参考:
1、如果实际业务中,数据维度不超过三维,可先通过画散点图的方法大致确定聚类数目。
2、工作中,结合业务方需求背景或经验,可敲定聚类数目。如,做用户的RFM模型(k=3)、判断是否为作弊用户(k=2)等。
3、实际业务中做探索性分析时,没有经验等做参考,可使用肘方法(elbow method)确定分类数。此处结合第一部分提到的误差平方和最小来一起理解。要使误差平方和变小,一种方法就是增加类数,这样有助于降低每个类的类内误差平方和,从而降低整体的误差平方和。但若类数太多,一是归类后解释困难,另一个降低类内误差平方和的边际效应可能下降(即增加类数k,误差平方和降低的不显著)。此时,k取误差平方和关于k的曲线的拐点。
4、交叉验证方法。将N个对象分为m个部分,用m-1个部分建立聚类模型,并用剩下的一部分检验聚类质量。
5、轮廓系数法。计算k取不同值时的轮廓系数,选择轮廓系数较接近1的分类数。
下面具体介绍手肘法和轮廓系数法。
2.2.1 手肘法
核心公式:SSE(sum of the squared errors,误差平方和)
其中,Ci是第i个簇;x是Ci中的样本点;mi是Ci的质心(Ci中所有样本的均值);SSE是所有样本的聚类误差,代表了聚类效果的好坏。
随着聚类数k的增大,样本划分会更加精细,每个簇的聚合程度会逐渐提高,那么误差平方和SSE自然会逐渐变小。当k小于真实聚类数时,由于k的增大会大幅增加每个簇的聚合程度,故SSE的下降幅度会很大,而当k到达真实聚类数时,再增加k所得到的聚合程度回报会迅速变小,所以SSE的下降幅度会骤减,然后随着k值的继续增大而趋于平缓,也就是说SSE和k的关系图是一个手肘的形状,而这个肘部对应的k值就是数据的真实聚类数。
显然,肘部对于的k值为4,梯度最大,下降最快,故对于这个数据集的聚类而言,最佳聚类数应该选4。
2.2.2 轮廓系数法
具体方法如下:
1)计算样本i到同簇其他样本的平均距离ai。ai越小,说明样本i越应该被聚类到该簇。将ai称为样本i的簇内不相似度。簇C中所有样本的均值称为簇C的簇不相似度。
2)计算样本i到其他某簇Cj的所有样本的平均距离bij,称为样本i与簇Cj的不相似度。定义为样本i的簇间不相似度:
,bi越大,说明样本i越不属于其他簇。
3)根据样本i的簇内不相似度ai和簇间不相似度bi,定义样本i的轮廓系数。
轮廓系数范围在[-1,1]之间。该值越大,越合理。si接近1,则说明样本i聚类合理;接近-1,则说明样本i更应该分类到另外的簇;若si近似为0,则说明样本i在两个簇的边界上。
所有样本的si的均值称为聚类结果的轮廓系数,是该聚类是否合理、有效的度量。使用轮廓系数(silhouette coefficient)来确定,选择使系数较大所对应的k值。
2.3 K-means++
我们知道初始值的选取对结果的影响很大,对初始值选择的改进是很重要的一部分。在所有的改进算法中,K-means++最有名。K-means++算法步骤如下所示:(1)随机选取一个中心点a1;
(2)计算数据到之前n个聚类中心最远的距离D(x),并以一定概率
选择新中心点ai;
(3)重复第二步。
简单的来说,K-means++就是选择离已选中心点最远的点。这也比较符合常理,聚类中心当然是互相离得越远越好。但是这个算法的缺点在于,难以并行化。所以k-meansII改变取样策略,并非按照k-means++那样每次遍历只取样一个样本,而是每次遍历取样k个,重复该取样过程log(n)次,则得到klog(n)个样本点组成的集合,然后从这些点中选取k个。当然一般也不需要log(n)次取样,5次即可。
2.4 算法终止条件
终止条件一般为以下几类:
a、达到预先设定的迭代次数,如20次。
b、类中心点不再发生变化或没有对象被分配给新的类。
c、误差平方和最小,误差平方和公式:
,其中Xi代表被分到第i类的对象集合,μc(i)代表第i个聚类的均值(即类中心),c可以理解为迭代这个步骤,因为c随着迭代而发生变化,所以也是个变量。
在实际编程实现算法时,a常配合着c(误差平方和最小化)一起使用,由于使误差平方和最小有时会陷入死循环或迭代多步类中心变化不大,因此常会限制迭代次数。
3. K-means算法特点
优点:
1)容易理解,聚类效果不错,虽然是局部最优,但往往局部最优就够了。
2)处理大数据集的时候,该算法可以保证较好的伸缩性。
3)当簇近似高斯分布的时候,效果非常不错。
4)算法复杂度低。
5)主要需要调参的参数仅仅是簇数k。
缺点:
1)K值的选取不好把握。
2)结果的好坏依赖于初始类中心的选择。
3)算法常陷入局部最优,更换初始聚类中心后,新的聚类结果可能效果更优。
4)对孤立点敏感,如数据集存在异常突出点,会影响聚类效果。
5)不适合太离散的分类、样本类别不平衡的分类、非凸形状的分类。
4. K-means算法应用场景
1、隐含类别的数据较为平衡的情况,如隐含类别的数据量差别较大,则聚类的效果就较差。
2、数据最好是凸数据,即隐含类别间的差异越大,则聚类效果越好,因为中心点不再变化所需要的迭代次数较少,比较容易收敛。
3、一般作为数据预处理,或者用于辅助分类贴标签使用,因为在已经经过分类的数据上再进行聚类,准确度会非常高。
5. K-means算法的Python应用
5.1 K-means算法的Python实现
以一系列二维点作为原始数据,通过K-means算法来预测新的点属于哪一类。测试代码如下:
# -*- coding:utf-8 -*-
import random
import numpy as np
from matplotlib import pyplot
class K_Means(object):
# k是分组数;tolerance'中心点误差';max_iter是迭代次数
def __init__(self, k=2, tolerance=0.0001, max_iter=300):
self.k_ = k
self.tolerance_ = tolerance
self.max_iter_ = max_iter
def fit(self, data):
self.centers_ = {}
for i in range(self.k_):
self.centers_[i] = data[random.randint(0,len(data))]
# print('center', self.centers_)
for i in range(self.max_iter_):
self.clf_ = {} #用于装归属到每个类中的点[k,len(data)]
for i in range(self.k_):
self.clf_[i] = []
# print("质点:",self.centers_)
for feature in data:
distances = [] #装中心点到每个点的距离[k]
for center in self.centers_:
# 欧拉距离
distances.append(np.linalg.norm(feature - self.centers_[center]))
classification = distances.index(min(distances))
self.clf_[classification].append(feature)
# print("分组情况:",self.clf_)
prev_centers = dict(self.centers_)
for c in self.clf_:
self.centers_[c] = np.average(self.clf_[c], axis=0)
# '中心点'是否在误差范围
optimized = True
for center in self.centers_:
org_centers = prev_centers[center]
cur_centers = self.centers_[center]
if np.sum((cur_centers - org_centers) / org_centers * 100.0) > self.tolerance_:
optimized = False
if optimized:
break
def predict(self, p_data):
distances = [np.linalg.norm(p_data - self.centers_[center]) for center in self.centers_]
index = distances.index(min(distances))
return index
if __name__ == '__main__':
x = np.array([[1, 2], [1.5, 1.8], [5, 8], [8, 8], [1, 0.6], [9, 11]])
k_means = K_Means(k=2)
k_means.fit(x)
for center in k_means.centers_:
pyplot.scatter(k_means.centers_[center][0], k_means.centers_[center][1], marker='*', s=150)
for cat in k_means.clf_:
for point in k_means.clf_[cat]:
pyplot.scatter(point[0], point[1], c=('r' if cat == 0 else 'b'))
predict = [[2, 1], [6, 9]]
for feature in predict:
cat = k_means.predict(feature)
pyplot.scatter(feature[0], feature[1], c=('r' if cat == 0 else 'b'), marker='x')
pyplot.show()
效果如下:
上图可见,所有点被分成了两类(圆点):红色和蓝色,预测的点(×)被正确分到了所属类别。
5.2 sklearn.cluster.Kmeans函数的应用
sklearn库使用sklearn.cluster.KMeans函数来实现K-Means算法,其函数原型如下:
sklearn.cluster.KMeans(n_clusters=8,*,init='k-means++', n_init=10,max_iter=300,tol=0.0001,precompute_distances='deprecated',verbose=0,random_state=None,copy_x=True,n_jobs='deprecated',algorithm='auto')
参数说明:
n_clusters:int, default=8,簇的个数,即你想聚成几类。
init:{‘k-means++’, ‘random’, ndarray, callable}, default=’k-means++’,初始簇中心的获取方法,‘k-means ++’:以一种聪明的方式为k-mean聚类选择初始聚类中心,以加快收敛速度。有关更多详细信息,请参见k_init中的注释部分。‘random’:n_clusters从初始质心的数据中随机选择观察(行)。如果传递了ndarray,则其形状应为(n_clusters,n_features),并给出初始中心。如果传递了callable,则应使用参数X,n_clusters和随机状态并返回初始化。
n_init:int, default=10,获取初始簇中心的更迭次数,k均值算法将在不同质心种子下运行的次数。
max_iter:int, default=300,最大迭代次数(因为kmeans算法的实现需要迭代),单次运行的k均值算法的最大迭代次数。
tol:float, default=1e-4,容忍度,即kmeans运行准则收敛的条件,关于Frobenius范数的相对容差,该范数表示两个连续迭代的聚类中心的差异,以声明收敛。
precompute_distances:{‘auto’, True, False}, default=‘auto’,是否需要提前计算距离,这个参数会在空间和时间之间做权衡,如果是True 会把整个距离矩阵都放到内存中,auto 会默认在数据样本大于featurs*samples 的数量大于12e6 的时候False,False 时核心实现的方法是利用Cpython 来实现的。
verbose:int, default=0,冗长模式(不太懂是啥意思,反正一般不去改默认值)。
random_state:int, RandomState instance, default=None,确定质心初始化的随机数生成。使用整数使随机性具有确定性。
copy_x:bool, default=True, 对是否修改数据的一个标记,如果True,即复制了就不会修改数据。bool 在scikit-learn 很多接口中都会有这个参数的,就是是否对输入数据继续copy 操作,以便不修改用户的输入数据。这个要理解Python 的内存机制才会比较清楚。
n_job:sint, default=None,并行设置。
algorithm:{‘auto’, ‘full’, ‘elkan’}, default=‘auto’,kmeans的实现算法,经典的EM风格算法是’full’的。通过使用三角形不等式,‘elkan’变异对于定义良好的聚类的数据更有效。但是,由于分配了额外的形状数组(n_samples,n_clusters),因此需要更多的内存。目前,‘auto’(保持向后兼容性)选择’elkan’,但为了更好的启发式,将来可能会更改。
测试代码如下:
# 导入可视化工具包
import matplotlib.pyplot as plt
from sklearn.datasets import load_iris
from sklearn.cluster import KMeans
import numpy as np
import pandas as pd
X=load_iris().data
clf = KMeans(n_clusters=3,random_state=0)
clf.fit(X)
label = clf.predict(X)
# 颜色和标签列表
colors_list = ['red', 'blue', 'green']
labels_list = ['1','2','3']
x=X
for i in range(3):
plt.scatter(x[label==i,0], x[label== i,1], s=100,c=colors_list[i],label=labels_list[i])
# 聚类中心点
plt.scatter(clf.cluster_centers_[:,0],clf.cluster_centers_[:,1], s=300,c='black',label='Centroids')
plt.legend()
plt.xlabel('Annual Income (k$)')
plt.ylabel('Spending Score (1-100)')
plt.show()
效果如下:
6. 源码仓库地址
🌼 图像处理、机器学习的常用算法汇总