目录
4 K均值算法簇中心的随机初始化 Random initialization
5 K均值算法聚类数K的选择 Choosing the Number of Cluters
1 无监督学习
- 监督学习:在一个典型的监督学习中,我们有一个有标签的训练集,我们的目标是找到能够区分正样本和负样本的决策边界,在监督学习中,我们有一系列标签,我们需要据此拟合一个假设函数:
- 非监督学习:与此不同的是,在非监督学习中,我们的数据没有附带任何标签,我们拿到的数据就是这样的:
在这里我们有一系列点,却没有标签。因此,我们的训练集可以写成只有x(1),x(2),x(3)…一直到x(m),而没有任何标签y。因此,图上画的这些点没有标签信息。也就是说,在非监督学习中,我们需要将一系列无标签的训练数据,输入到一个算法中,然后我们告诉这个算法,快去为我们找找这个数据的内在结构给定数据。 图上的数据看起来可以分成两个分开的点集(称为簇),一个能够找到我圈出的这些点集的算法,就被称为聚类算法。
聚类算法的应用:
- 市场分割 :也许你在数据库中存储了许多客户的信息,而你希望将他们分成不同的客户群,这样你可以对不同类型的客户分别销售产品或者分别提供更适合的服务。
- 社交网络分析 : 社交网络,例如 Facebook, Google+,或者是其他的一些信息,比如说:你经常跟哪些人联系,而这些人又经常给哪些人发邮件,由此找到关系密切的人群。因此,这可能需要另一个聚类算法,你希望用它发现社交网络中关系密切的朋友。
- 优化网络集群结构 : 使用聚类算法能够更好的组织计算机集群,或者更好的管理数据中心。因为如果你知道数据中心,那些计算机经常协作工作。那么,你可以重新分配资源,重新布局网络。由此优化数据中心,优化数据通信。
- 了解银河系的构成
2 K-均值算法K-means Algorithm
2.1 算法步骤综述
K-均值是一个迭代算法,假设我们想要将数据聚类成n个组,其方法为:
- 首先选择k个随机的点,称为聚类中心(cluster centroids);
- 簇分配(cluster assignment) 对于数据集中的每一个数据,按照距离K个中心点的距离,将其与距离最近的中心点关联起来,与同一个中心点关联的所有点聚成一类。
- 移动聚类中心(move centroids) 计算 每一个组 的平均值,将该组所关联的中心点移动到平均值的位置。
- 重复步骤 2-4 直至中心点不再变化。
2.2 步骤详解
假设我有如图下方的无标签的数据集,并且想将其分为 两个簇(clusters)即K=2
(1)第一步是随机生成 两点(K点,可改变),这两点被称为 聚类中心(cluster centroids)
(2)簇分配(cluster assignment) 遍历每个样本,然后根据样本到两个不同的聚类中心的距离哪个更近,来将每个数据点分配给两个聚类中心之一,使用来计算距离,其中
表示无标签的样本点,
表示 簇中心 。
(3)移动聚类中心(move centroids) 将聚类中心分别移动到各自簇的中心处。即图中计算所有红点均值 ,然后将红色聚类中心点移动至均值处,蓝色点同理。
(4)重复2-3过程,直到聚类中心不再移动
K-means算法接收两个输入:
- K值,即聚类中簇的个数,
- 一系列无标签的数据,使用N维向量X表示
2.3 算法图示
K-均值算法也可以很便利地用于将数据分为许多不同组,即使在没有非常明显区分的组群的情况下也可以。下图所示的数据集包含身高和体重两项特征构成的,利用 K-均值算法将数据分为三类,用于帮助确定将要生产的 T-恤衫的三种尺寸。
3 K-means算法损失函数
3.1 定义损失函数变量
- 假设有K个簇,
表示样本
当前所属的簇的索引编号 ,
表示 第k个聚类中心 的位置,其中
- 根据以上定义:则
表示样本
所属簇的中心的位置坐标
3.2 K-means算法的优化目标
损失函数为 每个样本到其所属簇的中心的距离和的平均值 ,优化函数的输入参数为 每个样本所属的簇的编号和每个簇中心的坐标
, 这两个都是在聚类过程中不断变化的变量。此代价函数也被称为 畸变函数(Distortion function),失真代价函数、K均值算法的失真。
3.3 K-means算法步骤与优化函数
- 对于K-means算法中的 簇分配(将每个样本点分配到距离最近的簇) 的步骤实际上就是在最小化代价函数J,即在
固定的条件下调整
的值,以使损失函数的值最小。
- 对于K-means算法中的 移动聚类中心(将聚类中心移动到分配样本簇的平均值处) ,即在
固定的条件下调整
的值,以使损失函数的值最小。
4 K均值算法簇中心的随机初始化 Random initialization
4.1 随机初始化遵循法则
- 我们应该选择 K < m,即聚类中心点的个数要小于所有训练集实例的数量
- 随机选择 K 个训练实例,然后令 K 个聚类中心分别与这 K 个训练实例相等
4.2 随机初始化的局限性
以下两种都是随机初始化的结果,发现随机初始化很容易把 初始化簇中心 分到相近的样本中,这种初始化方式有其局限性。
K-均值的一个问题在于,它有可能会停留在一个局部最小值处,而这取决于初始化的情况。
4.3 改进初始化方式–多次随机初始化
- 假如随机初始化K-means算法100 (一般是50-1000) 次之间,每次都使用不同的随机初始化方式,然后运行K-means算法,得到100种不同的聚类方式,都计算其损失函数,选取代价最小的聚类方式作为最终的聚类方式。
- 这种方法在 K 较小的时候(2–10)还是可行的,但是如果 K 较大,这么做也可能不会有明显地改善。(不同初始化方式得到的结果趋于一致)。
4.4 改进算法:K-means++算法
K-means++按照如下的思想选取K个聚类中心。假设已经选取了n个初始聚类中心(0<n<K),则在选取第n+1个聚类中心时,距离当前n个聚类中心越远的点会有更高的概率被选为第n+1个聚类中心。在选取第一个聚类中心(n=1)时同样通过随机的方法。可以说这也符合我们的直觉,聚类中心当然是互相离得越远越好。当选择完初始点后,K-means++后续的执行和经典K均值算法相同,这也是对初始值选择进行改进的方法等共同点。
5 K均值算法聚类数K的选择 Choosing the Number of Cluters
没有所谓最好的选择聚类数的方法,通常是需要根据不同的问题,人工进行选择的。选择的时候思考我们运用 K-均值算法聚类的动机是什么,然后选择能最好服务于该目标的聚类数。
5.1 肘部法则(Elbow method)
改变聚类数K,然后进行聚类,计算损失函数,拐点处即为推荐的聚类数 (即通过此点后,聚类数的增大也不会对损失函数的下降带来很大的影响,所以会选择拐点)
由图可见,K值越大,距离和越小;并且,当K=3时,存在一个拐点,就像人的肘部一样;当K (1,3)时,曲线急速下降;当K>3时,曲线趋于平稳。手肘法认为拐点就是K的最佳值。
但是也有损失函数随着K的增大平缓下降的例子,此时通过肘部法则选择K的值就不是一个很有效的方法了(下图中的拐点不明显,k=3,4,5有类似的功能) 。
手肘法是一个经验方法,缺点就是不够自动化。
5.2 目标法则
通常K均值聚类是为下一步操作做准备,例如:市场分割,社交网络分析,网络集群优化 ,下一步的操作都能给你一些评价指标,那么决定聚类的数量更好的方式是:看哪个聚类数量能更好的应用于后续目的。
例如对于T恤衫的尺码进行聚类的方法,如左图将其聚为3类(S,M,L),右图将其聚为5类(XS,S,M,L,XL)进行表示,这两种聚类都是可行的,我们可以 根据聚类后用户的满意程度或者是市场的销售额来决定最终的聚类数量 。
5.3 间隔统计量 Gap Statistic
Gap Statistic方法的优点是,不再需要肉眼判断,而只需要找到最大的Gap statistic所对应的K即可,因此该方法也适用于批量化作业。在这里我们继续使用上面的损失函数,当分为K簇时, 对应的损失函数记为Dk。Gap Statistic定义为:
其中E(logDk)是logDk的期望,一般通过蒙特卡洛模拟产生。我们在样本所在的区域 内按照均匀分布随机地产生和原始样本数一样多的随机样本,并对这个随机样本做K-Means,从而得到一个Dk。如此往复多次,通常20次,我们可以得到20个logDk。对这20个数值求平均值,就得到了E(logDk)的近似值。那么Gap(K)有什么物理含义呢?它可以视为随机样本的损失与实际样本的损失之差。试想实际样本对应的最佳簇数为K,那么实际样本的损失应该相对较小,随机样本损失与实际样本损失之差也相应地达到最大值,从而Gap(K)取得最大值所对应的K值就是最佳的簇数。
5.4 关于K值选择的改进算法——ISODATA算法
当K值的大小不确定时,可以使用ISODATA算法。ISODATA的全称是迭代自组织数据分析法。在K均值算法中,聚类个数K的值需要预先人为地确定,并且在整个算法过程中无法更改。而当遇到高维度、海量的数据集时,人们往往很难准确地估计出K的大小。ISODATA算法就是针对这个问题进行了改进,它的思想也很直观。当属于某个类别的样本数过少时,把该类别去除;当属于某个类别的样本数过多、分散程度较大时,把该类别分为两个子类别。ISODATA算法在K均值算法的基础之上增加了两个操作,一是分裂操作,对应着增加聚类中心数;二是合并操作,对应着减少聚类中心数。ISODATA算法是一个比较常见的算法,其缺点是需要指定的参数比较多,不仅仅需要一个参考的聚类数量Ko,还需要制定3个阈值。下面介绍ISODATA算法的各个输入参数。
(1)预期的聚类中心数目。在ISODATA运行过程中聚类中心数可以变化,
是一个用户指定的参考值,该算法的聚类中心数目变动范围也由其决定。具体地,最终输出的聚类中心数目常见范围是从
的一半,到两倍
。
(2)每个类所要求的最少样本数目。如果分裂后会导致某个子类别所包含样本数目小于该阈值,就不会对该类别进行分裂操作。
(3)最大方差。用于控制某个类别中样本的分散程度。当样本的分散程度超过这个阈值时,且分裂后满足(1),进行分裂操作。
(4)两个聚类中心之间所允许最小距离。如果两个类靠得非常近(即这两个类别对应聚类中心之间的距离非常小),小于该阈值时,则对这两个类进行合并操作。
如果希望样本不划分到单一的类中,可以使用模糊C均值或者高斯混合模型。
6 k-means优缺点
- 优点:
- 属于无监督学习,无须准备训练集
- 原理简单,实现起来较为容易
- 结果可解释性较好
- 缺点:
- 聚类数目k是一个输入参数。选择不恰当的k值可能会导致糟糕的聚类结果。这也是为什么要进行特征检查来决定数据集的聚类数目了。
- 可能收敛到局部最小值, 在大规模数据集上收敛较慢
- 对于异常点、离群点敏感
- 样本点只能被划分到单一的类中。
7 python实现
# -*- coding: utf-8 -*-
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from sklearn.datasets import load_iris
class Cluster:
def fit(self, train_data, clu_num, iter_num):
min_distortion_all = float("inf")
cluster_result_all = 0
for total_iter in range(iter_num):
if_cluster_change = True
data_num, feature_num = np.shape(train_data)
# print(data_num, feature_num)
# 初始化簇中心,簇中心从训练集中随机抽取,选择不重复的随机数
cluster_index = np.random.choice(range(data_num),
size=clu_num,
replace=False)
cluster_cent = train_data[cluster_index]
# print(cluster_index)
cluster_result = {}
iter = 0
# 初始化每个聚类中心包括哪些样本,先设为空
while if_cluster_change:
# 因为上一步可能会存在某个簇中的样本数为0,就会删除该簇,所以需要重新计算簇的个数
clu_num = len(cluster_cent)
for i in range(clu_num):
cluster_result[i] = []
# 将每个样本点分配到距离最近的簇
for i in range(data_num):
min_distortion = float("inf") # 初始化该样本到簇中心的距离值为无穷大
min_index = -1 # 该样本属于哪个簇
for j in range(clu_num):
distortion = self.distortion_function(train_data[i],
cluster_cent[j])
if min_distortion > distortion:
min_distortion = distortion
min_index = j
cluster_result[min_index].append(i)
cluster_cent_old = cluster_cent
cluster_cent = []
# 更新簇中心
for num in range(clu_num):
cluster_num_index = cluster_result[num]
# 如果该簇下样本为空,则删除该簇
if len(cluster_num_index) == 0:
del cluster_result[num]
else:
cluster_num_data = train_data[cluster_num_index]
cluster_cent.append(
np.average(cluster_num_data, axis=0))
# 判断簇中心是否还变化
if len(cluster_cent) == len(cluster_cent_old):
if (np.array(cluster_cent) ==
np.array(cluster_cent_old)).all():
if_cluster_change = False
iter += 1
# print("iter: ", iter)
# 计算代价函数的值
distortion_all = 0
for num in range(clu_num):
distortion_num = self.distortion_function(
train_data[cluster_result[num]], cluster_cent[num])
distortion_all += distortion_num
distortion_all = distortion_all / data_num
# print(distortion_all)
# print(cluster_result.keys())
# print(cluster_result)
if min_distortion_all > distortion_all:
min_distortion_all = distortion_all
cluster_result_all = cluster_result
# print(min_distortion_all)
# print(cluster_result_all.keys())
# print(cluster_result_all)
return min_distortion_all, cluster_result_all
def distortion_function(self, t_data, cent):
distortion = np.sum(np.power((t_data - cent), 2))
return distortion
if __name__ == "__main__":
data = load_iris()
iris_data = data.data
# print(data.target)
# feature_names:
# ['sepal length (cm)', 'sepal width (cm)', 'petal length (cm)', 'petal width (cm)']
cluster_num = 3
total_iter_num = 100
cluster = Cluster()
min_distortion_all, cluster_result_all = cluster.fit(
iris_data, cluster_num, total_iter_num)
print(min_distortion_all)
print(cluster_result_all.keys())
print(cluster_result_all)
# 取训练集中3维数据,画三维图
x, y, z = iris_data[:, 0], iris_data[:, 1], iris_data[:, 3]
fig = plt.figure(figsize=(8, 6))
plt.subplot(111)
axes3d = Axes3D(fig)
axes3d.scatter(x, y, z)
# 坐标轴的名字
plt.xlabel('X')
plt.ylabel('Y')
axes3d.set_zlabel('Z')
# 保存图片
plt.savefig('fig.png', bbox_inches="tight")