数据分析——K-Means(K均值聚类算法)——糖潮丽子

申明:文章内容是作者自己的学习笔记,教学来源是开课吧讲师梁勇老师

讲师介绍:梁老师 《细说Java》与《Java深入解析》图书作者。一线互联网资深数据分析专家,超过十年软件开发与培训经验。精通Python与Java开发,擅长网络爬虫,数据分析与可视化,机器学习,自然语言处理等技术。曾参与阿里云大学数据分析,机器学习,自然语言处理等课程开发与认证设计,担任阿里云学院导师。

进入正文:

在数据挖掘中,聚类是一个很重要的概念。
传统的聚类分析计算方法主要有如下几种:划分方法、层次方法、基于密度的方法、基于网格的方法、基于模型的方法等。其中K-Means算法是划分方法中的一个经典的算法。

K-均值聚类(K-Means)概述
1、聚类
“类”指的是具有相似性的集合,聚类是指将数据集划分为若干类,使得各个类之内的数据最为相似,而各个类之间的数据相似度差别尽可能的大。 聚类分析就是以相似性为基础,在一个聚类中的模式之间比不在同一个聚类中的模式之间具有更多的相似性。对数据集进行聚类划分,属于无监督学习。

2、K-Means:
K-Means算法是一种简单的迭代型聚类算法,采用距离作为相似性指标,从而发现给定数据集中的K个类,且每个类的中心是根据类中所有数值的均值得到的,每个类的中心用聚类中心来描述。对于给定的一个(包含n个一维以及一维以上的数据点的)数据集X以及要得到的类别数量K,选取欧式距离作为相似度指标,聚类目标实施的个类的聚类平反和最小,即最小化:
在这里插入图片描述

1、主题

本次我们来讲解K-Means聚类算法与算法的改进与优化。

2、目标

阅读并且练习后,我们希望可以达到如下目标:

  • 熟知K-Means算法的原理与步骤。
  • 熟知K-Means++算法的原理与初始化方式。
  • 熟知Mini Batch K-Means算法的原理与步骤。
  • 能够选择最佳的值。

3、聚类

之前我们接触的算法,都是监督学习,即训练数据是包含我们要预测的结果(训练数据中是含有样本的标签)。我们对含有标签的训练集建立模型,从而能够对未知标签的样本进行预测。

与监督学习对应的,聚类属于无监督学习,即训练数据中是不含有标签的。聚类的目的是根据样本数据内部的特征,将数据划分为若干个类别,每个类别就是一个簇。 结果为,使得同一个簇内的数据,相似度较大,而不同簇内的数据,相似度较小。聚类也称为“无监督的分类”。其样本的相似性是根据距离来度量的。

4、K-Means算法(K-均值算法)

4.1 算法步骤

K-Mean算法,即均值算法,是最常见的一种聚类算法。顾名思义,该算法会将数据集分为个簇,每个簇使用簇内所有样本的均值来表示,我们将该均值称为“质心”。具体步骤如下:

  1. 从样本中选择K个点作为初始质心。
  2. 计算每个样本到各个质心的距离,将样本划分到距离最近的质心所对应的簇中。
  3. 计算每个簇内所有样本的均值,并使用该均值更新簇的质心。
  4. 重复步骤2与3,直到达到以下条件之一结束:
    - 质心的位置变化小于指定的阈值。
    - 达到最大迭代次数。
4.2 过程演示

下图给出了使用K-Means算法聚类的过程。

4.3 优化目标

**KMeans算法的目标就是选择合适的质心,使得在每个簇内,样本距离质心的距离尽可能的小。**这样就可以保证簇内样本具有较高的相似性。我们可以使用最小化簇内误差平方和(within-cluster sum-ofsquares SSE)来作为优化算法的量化目标(目标函数),簇内误差平方和也称为簇惯性(inertia)
在这里插入图片描述
在这里插入图片描述
练习:

在样本数量相同的前提下,如果模型A聚类后的SSE大于模型B,则说明( D
A 模型A的聚类效果好于模型B。
B 模型B的聚类效果好于模型A。
C 模型A与模型B的聚类效果一样好。
D 无法判断两个模型的聚类效果谁更好。
解析:
为什么无法判断,因为我们还不知道K的值,K越多,SSE越小。K未知比较SSE是没有意义的。

4.4 算法优点与缺点

KMeans优点如下:

  1. 理解与实现简单。
  2. 可以很好的扩展到大量样本中。
  3. 广泛应用于不同领域。

同时,KMeans算法也具有一定的缺点,如下:

  1. 需要事先指定K值,如果K值选择不当,聚类效果可能不佳。
  2. 聚类结果受初始质心的影响,可能会收敛到局部最小值。
  3. 适用于凸形的数据分布,对于条形或不规则形状的数据,效果较差。

练习:

可以通过聚类实现的有( ABCD )。【不定项】
A 商业领域中,发现不同的用户群体,从而进行有效的产品定位,商品推荐等。
B 在文档处理中,对相似内容的文档进行划分。
C 社交网络的分析,自动识别属于同一个圈子的朋友,判断哪些人可能互相认识。
D 在数据处理中,对数据集中的异常值进行检测。

4.5 程序演示

假设班级有50名学生参加期末考试,考试科目为语文与数学,我们现在想根据分数将学员分成若干个类别。

4.5.1 数据一览

首先,我们来绘制一下学生成绩的散点图。

import numpy as np
import matplotlib.pyplot as plt
plt.rcParams["font.family"] = "SimHei"
plt.rcParams["axes.unicode_minus"] = False
plt.rcParams["font.size"] = 12
np.random.seed(1)
# 生成样本数据
X = np.random.randint(70, 100, size=(50, 2))
plt.scatter(X[:, 0], X[:, 1])
plt.xlabel("语文")
plt.ylabel("数学")

结果:
Text(0, 0.5, ‘数学’)
在这里插入图片描述

4.5.2 训练

我们使用scikit-learn中提供的KMeans类,实现均值聚类。当完成训练后,我们可以通过KMeans对象获取相关属性值。

from sklearn.cluster import KMeans
# n_clusters:簇的数量。
kmeans = KMeans(n_clusters=4)
kmeans.fit(X)
# 获取聚类后的质心。
print("质心:", kmeans.cluster_centers_)
# 获取每个样本所属的簇。标签的数值对应所属簇的索引。
print("标签:", kmeans.labels_)
# 获取SSE(簇惯性)。
print("SSE:", kmeans.inertia_)
# 获取迭代次数。
print("迭代次数:", kmeans.n_iter_)
# 聚类的分值,分值越大,效果越好。直接取SSE的相反数。
print("分值:", kmeans.score(X))

结果:
在这里插入图片描述

练习:

关于聚类,结合上面运行的结果,以下说法正确的是( ABC )。【不定项】
A 质心是随机初始化的,因此,多次运行程序,聚类后,即使几个质心的值相同,顺序也可能不同。
B 在聚类中,相同标签值的样本,会聚类到同一个簇中。
C 重复运行程序多次,可能会得到不同的结果。
D 标签值与质心之间,是没有关联的。

4.5.3 预测

我们也可以对训练集之外的样本进行预测。KMeans算法的预测非常简单,就是看待预测样本与哪个质心的距离最近,就将其预测为哪个类别

unknown = np.array([[76, 99], [94, 80]])
y_hat = kmeans.predict(unknown)
print(y_hat)

两个样本对应上面4个质心的哪一个,判断属于哪个簇中。
结果:
[2 3]

4.5.4 数据可视化

最后,我们可以绘制聚类后的效果。

# 定义绘制聚类结果的函数。
def plot_cluster(model, train, test=None):
	color = np.array(["r", "g", "b", "orange"])
	cc = model.cluster_centers_  # 质心
	label = model.labels_
	plt.scatter(cc[:, 0], cc[:, 1], marker="+", s=150, c=color)
	plt.scatter(train[:, 0], train[:, 1], c=color[label])
	if test is not None:
		y_hat = model.predict(test)
		plt.scatter(test[:, 0], test[:, 1], marker="*", s=150,c=color[y_hat])
	plt.xlabel("语文")
	plt.ylabel("数学")
	plt.title(f"SSE:{model.inertia_:.2f} 迭代次数:{model.n_iter_}")
	
plot_cluster(kmeans, X, unknown)

结果:
在这里插入图片描述

4.6 初始质心的影响

之前我们提到过,KMeans算法的一个缺点是,容易受到初始质心的影响,即初始质心的不同,可能会导致最后的聚类结果不同。

seed = [10, 17]
plt.figure(figsize=(15, 5))
for i in range(1, 3):
	plt.subplot(1, 2, i)
	kmeans = KMeans(n_clusters=4, init="random", n_init=1,random_state=seed[i - 1])
	kmeans.fit(X)
	plot_cluster(kmeans, X)

结果:
在这里插入图片描述

通常情况下,我们会随机初始化多组质心,执行多次,然后选择SSE最小的初始化质心。该操作可以通过 n_init 参数来指定,该参数默认值为10。

plt.figure(figsize=(15, 5))
for i in range(1, 3):
	plt.subplot(1, 2, i)
	kmeans = KMeans(n_clusters=4, init="random", n_init=10,
random_state=seed[i - 1])
	kmeans.fit(X)
	plot_cluster(kmeans, X)

结果:
在这里插入图片描述
我们将 n_init 参数调整到10,则聚类效果稳定了很多。

练习:

关于K-Means算法,说法正确的是( ACD)。【不定项】
A 算法容易受到初始质心的影响。
B 算法较为复杂,不易实现。
C 算法聚类时,可能会出现空簇(簇内没有样本)。
D 算法可能会收敛到局部最小值。

5、K-Means++

5.1 算法步骤

K-Means算法对初始质心是敏感的,不同的初始质心,可能会导致不同的聚类效果(SSE)与收敛速度。虽然随机初始化多组质心,可以缓解这一问题,但是,这通常限于聚类数量(簇)较少的情况,如果聚类数量较多,随机初始化多组质心的方式可能就不会有效了。

鉴于此,我们完全可以在选择初始质心上,进行优化,这就是K-Means++算法。

K-Means++与K-Means的区别在于选择初始质心的方式不同,对于K-Means,其使用完全随机的方式来选择初始质心,对于K-Means++,其选择初始质心的步骤如下:

5.2 程序演示

在scikit-learn中,已经实现了K-Means++算法,只需要将init参数设置为“k-means++”即可(默认值)。

kmeans = KMeans(n_clusters=4, init="k-means++")
kmeans.fit(X)
plot_cluster(kmeans, X)

结果:
在这里插入图片描述

练习:

关于K-Means++算法,说法正确的是( ACD )。【不定项】
A 算法受初始质心的影响较小。
B 算法一定会选择距离当前质心较远的样本,做为下一个质心。
C 算法在综合表现上,往往会优于经典的K-Means算法。
D K-Means++算法与K-Means算法,仅在于选择初始质心的方式不同。

6、Mini Batch K-Means

6.1 算法步骤

K-Means算法每次迭代都会使用所有数据参与运算,当数据集较大时,该算法可能会花费较多的计算时间。Mini Batch K-Means算法(小批量K-Means算法)是K-Means算法的一种变种,与K-Means算法具有相同的优化目标(目标函数)。不同的是,Mini Batch K-Means算法每次迭代使用小批量训练样本,逐批次累计的方式进行计算,这样可以大大减少计算时间。在聚类效果上,通常只会略差于KMeans算法。

Mini Batch K-Means算法步骤如下:

  1. 从数据集中随机选择部分数据,使用K-Means算法在这部分随机数据上聚类,获取质心。
  2. 从数据集中随机选择部分数据,形成一个批次,将该批次数据分配给最近的质心。
  3. 根据现有的数据集(当前批次数据 + 所有以前的数据)更新质心。
  4. 重复步骤2与3,直到质心变化小于指定的阈值或者到达最大迭代次数为止。
6.2 程序演示
6.2 1 生成数据集

在scikit-learn中,我们可以使用make_blobs方法生成用于聚类的数据集。

import time
import pandas as pd
from sklearn.cluster import MiniBatchKMeans, KMeans
from sklearn.metrics.pairwise import pairwise_distances_argmin
from sklearn.datasets import make_blobs

centers = [[1, 1], [-1, -1], [1, -1]]
# 生成用于聚类的数据。
# n_samples:样本数量。
# n_features:特征数量。
# centers:聚类中心。
# cluster_std:簇的标准差。可以统一指定,也可以为每个簇指定不同的标准差。
# random_state:随机种子,用来生成样本数据。
X, y = make_blobs(n_samples=3000, n_features=2, centers=centers,cluster_std=0.7, random_state=0)
colors = np.array(["red", 'green', 'blue'])
plt.scatter(X[:, 0], X[:, 1], c=colors[y], marker=".")

结果:
在这里插入图片描述

6.2.2 计算聚类时间与效果

我们分别K-Means与Mini Batch K-Means两种算法进行聚类,并计算消耗时间(训练所需要的时间)与SSE。

# 定义函数,用于计算模型训练时间
def elapsed_time(model, data):
	start = time.time()
	model.fit(data)
	end = time.time()
	return end - start
	
n_clusters = len(centers)
kmeans = KMeans(init='k-means++', n_clusters=n_clusters, n_init=10)
mbk = MiniBatchKMeans(init='k-means++', n_clusters=n_clusters,batch_size=100, n_init=10)
kmeans_time = elapsed_time(kmeans, X)
mbk_time = elapsed_time(mbk, X)
print("K-Means消耗时间:", kmeans_time)
print("Mini Batch K-Means消耗时间:", mbk_time)
print("K-Means SSE:", kmeans.inertia_ )
print("Mini Batch K-Means SSE:", mbk.inertia_ )
print("K-Means的质心:", kmeans.cluster_centers_)
print("Mini Batch K-Means的质心:", mbk.cluster_centers_)

结果:
在这里插入图片描述

6.2.3 绘制聚类效果

我们绘制两种聚类算法的对比图。

plt.figure(figsize=(15, 5))
model = [kmeans, mbk]
for index, m in enumerate(model, start=1):
	plt.subplot(1, 2, index)
	plt.scatter(X[:, 0], X[:, 1], c=colors[m.labels_], marker=".")
	plt.scatter(m.cluster_centers_[:, 0], m.cluster_centers_[:, 1],marker="o", s=150, c="orange")

结果:
在这里插入图片描述
因为聚类质心的随机性,因此,在每次聚类时,我们只能保证哪些样本应该在同一个簇中,但是却无法保证质心的顺序性。而由于预测的结果值(结果属于哪一个簇),是与质心的索引相对应的,因此,无法保证预测结果值的一致性。

因为上述原因,也就导致了两个算法的标签不一致,其实,即使是同一个算法,多次运行,也无法保证标签是一致的。

为了能够在可视化中,相同的簇使用相同的颜色绘制,我们这里做出一些调整。

plt.figure(figsize=(15, 5))
model = [kmeans, mbk]
# 定义列表,用于保存两个模型预测的结果。
y_hat_list = []
for index, m in enumerate(model, start=1):
	plt.subplot(1, 2, index)
	y_hat = m.predict(X)
	if m == mbk:
	# 计算从MiniBatchKMeans质心到KMeans质心的映射,目的是在可视化的时候,
	# 相同的簇可以使用相同的颜色绘制。
	# pairwise_distances_argmin(X, Y) 对于数组X中的每条记录x,计算Y中与x距离最近的元素y,返回y的索引构成的数组。
		map_ = pairwise_distances_argmin(mbk.cluster_centers_,
kmeans.cluster_centers_)
		print(map_)
		# 生成从mbk到kmeans质心的映射。
		map_ = dict(enumerate(map_))
		# 将y_hat转换成Series类型,方面调用map函数进行映射。
		y_hat = pd.Series(y_hat).map(map_).values
# 将预测结果加入到列表中。
	y_hat_list.append(y_hat)
	plt.scatter(X[:, 0], X[:, 1], c=colors[y_hat], marker=".")
	plt.scatter(m.cluster_centers_[:, 0], m.cluster_centers_[:, 1],marker="o", s=150, c="orange")

结果:
在这里插入图片描述

6.2.4 两种聚类算法的差异对比

我们来给出经过两种聚类算法后,划分为不同簇的样本。

same= y_hat_list[0] ==y_hat_list[1]
diff = y_hat_list[0] != y_hat_list[1]
plt.scatter(X[same, 0], X[same, 1], c="g", marker=".", label="预测相同样本")
plt.scatter(X[diff, 0], X[diff, 1], c="r", marker=".", label="预测不同样本")
plt.legend()
print("预测相同样本数量:", X[same].shape[0])
print("预测不同样本数量:", X[diff].shape[0])

结果:
预测相同样本数量: 2985
预测不同样本数量: 15
在这里插入图片描述
由结果可知,对于绝大多数样本,两种算法聚类的结果是相同的。仅有少部分位于簇边界位置的样本,两种算法聚类的结果是不同的。

6.3 确定合适的值

之前我们已经提及,K-Means算法需要实现指定的数量(聚类簇的数量),如果K值选择不当,会造成聚类效果不佳。因此,寻找合适的K值,也是K-Means算法一项重要的任务。

我们是否可以通过网格搜索交叉验证的方式,来寻找合适的K值?
A 可以。
B 不可以。

通常情况下,我们可以使用“肘部法则”来发现最合适的K值。

sse = []
scope = range(1, 10)
for k in scope:
	kmeans = KMeans(n_clusters=k)
	kmeans.fit(X)
	sse.append(kmeans.inertia_)
plt.xticks(scope)
plt.plot(scope, sse, marker="o")

结果:
在这里插入图片描述

7、总结

  • K-Means,K-Means++与Mini Batch K-Means算法的原理与应用。
  • K-Means,K-Means++与Mini Batch K-Means算法之间的差异性。
  • 肘部法则的使用。
  • 8
    点赞
  • 47
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值