1 K-means家族简介
历史渊源 :虽然其思想能够追溯到1957年的Hugo Steinhaus,术语“k-均值”于1967年才被James MacQueen首次使用。标准算法则是在1957年被Stuart Lloyd作为一种脉冲码调制的技术所提出,但直到1982年才被贝尔实验室公开出版。在1965年,E.W.Forgy发表了本质上相同的方法,所以这一算法有时被称为Lloyd-Forgy方法。更高效的版本则被Hartigan and Wong提出(1975/1979)
给定一个有 M M M个对象的数据集,构建一个具有 k k k个簇的模型,其中 k ≤ M k \leq M k≤M。满足以下条件:
- 每个簇至少包含一个对象
- 每个对象属于且仅属于一个簇
- 将满足上述条件的 k k k个簇成为一个合理的聚类划分
基本思想:对于给定的类别数目 k k k,首先给定初始划分,通过迭代改变样本和簇的隶属关系,每次处理后得到的划分方式比上一次的好(总的数据集之间的距离和变小了)
K-Means(K均值)算法是无监督的聚类算法,算法简单,聚类效果好,即使是在巨大的数据集上也非常容易部署实施。正因为如此,它在很多领域都得到的成功的应用,如市场划分、机器视觉、 地质统计学、天文学和农业等。K-Means算法有大量的变体,包括初始化优化K-Means++以及大数据应用背景下的k-means||和Mini Batch K-Means
2 算法流程
K-means算法,也称为K-平均或者K-均值,是一种使用广泛的最基础的聚类算法,一般作为掌握聚类算法的第一个算法
假设输入样本为 T = x 1 , x 2 , . . . , x m T=x_1,x_2,...,x_m T=x1,x2,...,xm;则算法步骤为(使用欧几里得距离公式):
-
选择初始化的k个类别中心(质心) a 1 , a 2 , . . . a k a_1,a_2,...a_k a1,a2,...ak;
-
对于每个样本 x i x_i xi,将其标记位距离类别中心 a j a_j aj最近的类别 j j j
l a b e l i = arg min 1 ≤ j ≤ k ( x i − a j ) 2 label_i=\mathop {\arg \min }\limits_{1\leq j \leq k}\sqrt{(x_i-a_j)^2} labeli=1≤j≤kargmin(xi−aj)2 -
更新隶属每个类别中心点 a j a_j aj的所有样本的均值
μ i = 1 ∣ C i ∣ ∑ x ∈ C i x \mu_i=\frac{1}{|C_i|}\sum_{x\in C_i}x μi=∣Ci∣1x∈Ci∑x -
重复上面两步操作,直到达到某个中止条件
终止条件:迭代次数、最小平方误差MSE、簇中心点变化率
K-Means算法的思想很简单,对于给定的样本集,按照样本之间的距离大小,将样本集划分为K个簇。让簇内的点尽量紧密的连在一起,而让簇间的距离尽量的大。
如果用数学表达式表示,假设簇划分为
(
C
1
,
C
2
,
.
.
.
C
k
)
(C_1,C_2,...C_k)
(C1,C2,...Ck),则我们的目标是最小化平方误差
E
E
E:
E
=
∑
i
=
1
k
∑
x
∈
C
i
∥
x
−
μ
i
∥
2
2
E = \sum\limits_{i = 1}^k {\sum\limits_{x \in {C_i}}^{} {\left\| {x - {\mu _i}} \right\|_2^2} }
E=i=1∑kx∈Ci∑∥x−μi∥22
其中
μ
i
μ_i
μi是簇
C
i
C_i
Ci的均值向量,有时也称为质心,表达式为:
μ
i
=
1
∣
C
i
∣
∑
x
∈
C
i
x
{\mu _i} = \frac{1}{{\left| {{C_i}} \right|}}\sum\limits_{x \in {C_i}}^{} x
μi=∣Ci∣1x∈Ci∑x
获取数据 n 个 m 维的数据
随机生成 K 个 m 维的点
while(t)
for(int i=0;i < n;i++)
for(int j=0;j < k;j++)
计算点 i 到类 j 的距离
for(int i=0;i < k;i++)
1. 找出所有属于自己这一类的所有数据点
2. 把自己的坐标修改为这些数据点的中心点坐标
end
- 时间复杂度: O ( t k n m ) O(tknm) O(tknm),其中, t t t 为迭代次数, k k k 为簇的数目, n n n 为样本点数, m m m 为样本点维度。
- 空间复杂度: O ( m ( n + k ) ) O(m(n+k)) O(m(n+k)) ,其中, k k k 为簇的数目, m m m 为样本点维度, n n n 为样本点数。
3 直观理解
对数据集 X = n p . a r r a y ( [ [ 1 , 2 ] , [ 2 , 2 ] , [ 6 , 8 ] , [ 7 , 8 ] ] ) X = np.array([[1, 2], [2, 2], [6, 8],[7 ,8]]) X=np.array([[1,2],[2,2],[6,8],[7,8]])聚类, K = 2 K=2 K=2,初始聚类中心为 C = n p . a r r a y ( [ [ 1 , 2 ] , [ 2 , 2 ] ] ) C = np.array([[1, 2], [2, 2]]) C=np.array([[1,2],[2,2]])
要求:打印每次迭代聚类中心的位置
import numpy as np
X = np.array([[1, 2], [2, 2], [6, 8],[7 ,8]])
C = np.array([[1.0, 2.0], [2.0, 2.0]]) # 聚类中心
iters = 5 #迭代次数
while (iters>0) : # 遍历每一个聚类中心,计算样本到每个聚类中心的距离
# 将样本分配到所属的聚类中心
iters -= 1
B = []
for c in C: #获取每个簇的样本,求质心更新C
dis = np.sqrt(((X - c)**2).sum(axis=1))
B.append(dis)
min_idx = np.argmin(np.array(B),axis=0)
for i in range(len(C)):
C[i] = np.mean(X[min_idx == i],axis=0)
# 打印所有样本的所属的簇
print(min_idx)
4 案例详解
定义:客户细分是一种市场策略,通过将潜在客户分为不同的组或段,企业可以更精准地进行产品推广或服务提供。
例子:一个在线零售商希望根据客户的年龄、购买历史和浏览行为来进行客户细分,以实施更有效的营销策略。
数据集说明
在本案例中,我们将使用一个简单的数据集,包括客户的年龄、购买频率和平均消费金额三个特征。
客户ID | 年龄 | 购买频率 | 平均消费金额
------|------|----------|--------------
1 | 25 | 5 | 50
2 | 30 | 3 | 40
3 | 35 | 1 | 20
...
Python实现代码
下面是使用Python和PyTorch来实现KMeans算法的代码。我们首先导入必要的库,然后进行数据准备、模型训练和结果可视化。
import numpy as np
import torch
import matplotlib.pyplot as plt
# 创建一个模拟数据集
data = torch.tensor([[25, 5, 50],
[30, 3, 40],
[35, 1, 20]], dtype=torch.float32)
# 初始化K个中心点
K = 2
centers = data[torch.randperm(data.shape[0])][:K]
# KMeans算法主体
for i in range(10): # 迭代10次
# 步骤2:计算每个点到各个中心点的距离,并分配到最近的中心点
distances = torch.cdist(data, centers)
labels = torch.argmin(distances, dim=1)
# 步骤3:重新计算中心点
for k in range(K):
centers[k] = data[labels == k].mean(dim=0)
# 结果可视化
plt.scatter(data[:, 0], data[:, 1], c=labels)
plt.scatter(centers[:, 0], centers[:, 1], marker='x')
plt.show()
5 KMeans在文本聚类中的应用
除了常见的数值数据聚类,KMeans也被广泛应用于文本数据的聚类。在这一节中,我们将探讨KMeans在文本聚类中的应用,特别是在自然语言处理(NLP)领域。
文本向量化
- 定义:文本向量化是将文本数据转化为数值形式,以便机器学习算法能更容易地处理它。
- 例子:例如,一个常用的文本向量化方法是TF-IDF(Term Frequency-Inverse Document Frequency)。
KMeans与TF-IDF
- 定义:结合TF-IDF和KMeans算法可以有效地对文档进行分类或主题建模。
- 例子:一个新闻网站可能有成千上万的文章,它们可以通过应用KMeans聚类算法与TF-IDF来分类成几大主题,如“政治”、“科技”、“体育”等。
Python实现代码
下面的代码使用Python的sklearn
库进行TF-IDF文本向量化,并应用KMeans进行文本聚类。
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.cluster import KMeans
# 模拟文本数据
documents = ["政治新闻1", "科技新闻1", "体育新闻1",
"政治新闻2", "科技新闻2", "体育新闻2"]
# TF-IDF向量化
vectorizer = TfidfVectorizer()
X = vectorizer.fit_transform(documents)
# KMeans聚类
model = KMeans(n_clusters=3)
model.fit(X)
labels = model.labels_
# 输出与解释
for i, label in enumerate(labels):
print(f"文档 {documents[i]} 被归类到 {label} 集群。")
'''
文档 政治新闻1 被归类到 0 集群。
文档 科技新闻1 被归类到 1 集群。
文档 体育新闻1 被归类到 2 集群。
文档 政治新闻2 被归类到 0 集群。
文档 科技新闻2 被归类到 1 集群。
文档 体育新闻2 被归类到 2 集群。
'''
6 K-means衍生算法
6.1 K-means缺点
-
缺点一:聚类中心的个数 K K K需要事先给定,但在实际中 K K K值的选定是非常困难的,很多时候我们并不知道给定的数据集应该聚成多少个类别才最合适
-
缺点二:k-means算法需要随机地确定初始聚类中心,不同的初始聚类中心可能导致完全不同的聚类结果,有可能导致算法收敛很慢甚至出现聚类出错的情况
-
针对第一个缺点:很难在k-means算法以及其改进算法中解决,一般来说,我们会根据对数据的先验经验选择一个合适的 k k k值,如果没有先验知识,则可以通过“肘方法”选择一个合适的 k k k值
-
针对第二个缺点:可以通过k-means++算法来解决
手肘法的核心指标是SSE(sum of the squared errors,误差平方和)
S
S
E
=
∑
i
=
1
k
∑
x
∈
C
i
∥
x
−
μ
i
∥
2
SSE = \sum\limits_{i = 1}^k {\sum\limits_{x \in {C_i}}^{} {\left\| {x - {\mu _i}} \right\|_{}^2} }
SSE=i=1∑kx∈Ci∑∥x−μi∥2
其中,
C
i
C_i
Ci是第
i
i
i个簇,
x
x
x是
C
i
C_i
Ci中的样本点,
μ
i
μ_i
μi是
C
i
C_i
Ci的质心(
C
i
C_i
Ci中所有样本的均值),SSE是所有样本的聚类误差,代表了聚类效果的好坏。
手肘法的核心思想是:随着聚类数 k k k的增大,样本划分会更加精细,每个簇的聚合程度会逐渐提高,那么误差平方和SSE自然会逐渐变小。并且,当 k k k小于真实聚类数时,由于k的增大会大幅增加每个簇的聚合程度,故SSE的下降幅度会很大,而当k到达真实聚类数时,再增加k所得到的聚合程度回报会迅速变小,所以SSE的下降幅度会骤减,然后随着k值的继续增大而趋于平缓,也就是说SSE和k的关系图是一个手肘的形状,而这个肘部对应的 k k k值就是数据的真实聚类数。当然,这也是该方法被称为手肘法的原因。
6.2 K-means++
针对K-Means算法对初始簇心比较敏感的问题,K-Means算法使用随机给定的方式,K-Means++算法采用下列步骤给定 k k k个初始质点,使初始的聚类中心之间的相互距离要尽可能远:
- 从数据集中任选一个节点作为第一个聚类中心 c 1 c_1 c1
- 计算每个样本点 x x x到当前已有聚类中心点之间的最短距离 D ( x ) D(x) D(x)(即与最近的一个聚类中心的距离);接着计算每个样本被选为下一个聚类中心的概率 D ( x ) 2 ∑ x ∈ X D ( x ) 2 \frac{D(x)^2}{\sum_{x \in X}D(x)^2} ∑x∈XD(x)2D(x)2;最后按照轮盘发选择出下一个聚类中心。(距离已有聚类中心较远的一个点成为新增聚类中心点)
- 重复步骤2直到找到 k k k个聚类中心点
缺点:k-means++ 最主要缺点在于其内在的顺序执行特性,得到 k k k 个聚类中心必须遍历数据集 k k k 次,并且当前聚类中心的计算依赖于前面得到的所有聚类中心(第 k k k个聚类中心点的选择依赖前 k − 1 k-1 k−1个聚类中心点的值),这使得算法无法并行扩展,极大地限制了算法在大规模数据集上的应用。
所以,在此基础上,提出了 K-means|| 算法
6.3 K-means||
k-means||改变了每次遍历时的取样规则,并非按照K-Means++算法每次遍历只获取1个样本,而是每次获取 k k k个样本,重复该取样操作 O ( log n ) O(\log n) O(logn)次,然后再将这些抽样出的样本聚类出 k k k个点,最后使用这 k k k个点作为K-Means算法的初始聚簇中心点。实践证明:一般5次重复采用就可以保证一个比较好的聚簇中心点 。
k-means||是k-means++的变体,k-means||在初始化中心点时对kmeans++的缺点做了规避,主要体现在不需要根据k的个数严格地寻找k个点,突破了算法在大规模数据集上的应用瓶颈,同时初始化的中心点更加健壮。
6.4 K means对比
K means | K means ++ | K means || | |
---|---|---|---|
初始中心选择 | 随机选择k个 | 依次有序选择k个 | 多次重复抽取样本子集,从中得到k个中心 |
Moderate size Large size data | 慢 | 非常慢 | 快 |
7 小结
-
优点:
- 原理比较简单,实现也是很容易,收敛速度快
- 聚类效果较优
- 算法的可解释度比较强
- 主要需要调参的参数仅仅是簇数 k k k
-
缺点:
- 不能处理如图情况
- 采用迭代方法,得到的结果只是局部最优。
- 对噪音和异常点比较的敏感。