实战部分主要针对某一具体算法对其原理进行较为详细的介绍,然后进行简单地实现(可能对算法性能考虑欠缺),这一部分主要介绍一些常见的一些聚类算法。


K-means聚类算法

0.聚类算法算法简介

  聚类算法算是机器学习中最为常见的一类算法,在无监督学习中,可以说聚类算法有着举足轻重的地位。

  提到无监督学习,不同于前面介绍的有监督学习,无监督学习的数据没有对应的数据标签,我们只能从输入X中去进行一些知识发现或者预处理。

  过去在有监督学习中,我们(让机器)通过X去预测Y,而到了无监督学习中,我们(让机器)只能从X去发现什么,或者X中哪些输入对我们是有用的,因此:

  无监督学习中包括了两大方面:聚类和降维

  在无监督学习中,我们通过X可以发现什么。聚类就是主要回答这一类问题。而对于一个具有很多维的数据,那些维度对于我们想要知道的事情的影响比较大,这就是降维要做的事情。

  聚类算法,顾名思义,就是一种能将属于同类别的数据聚集在一起的算法,称之为“物以类聚”。聚类的目的就是将相似的对象归为同一簇中,不相似的对象归到不同簇中。

  聚类算法比较常用的有K-means、层次聚类算法、DBSCAN等,这些后面会一一介绍,本节主要是K-means算法。

  具体聚类算法有很多应用场景,如客户群体分析、社交网络分析等,还有很多间接的应用,如数据预处理、半监督学习等。

1.K-means聚类原理

  K-means聚类就是给定一组数据,以及一个k值,然后把这些数据分为k个类别的算法。其中k是事先需要给定的参数。每一个簇(类别)通过这个簇的中心(质心)进行描述。大概就是下面这样子:

  K-means算法是聚类算法中较为简单的一种,原理简单,易于实现。其原理大致是:首先给定k个中心(质心),然后将数据分别划分到k个簇中去,也就是说把每个数据分到距离其最近的那个中心所在的簇中。

  然后重新计算每个簇的中心,即属于这个簇的样本的均值即为新的簇的中心。

  具体算法流程如下:

    1.初始化k个点作为聚类中心(通常随机选择)

    2.计算每个样本距离k个聚类中心的距离,然后将每个样本分到距离其最近的中心所属的簇中;

    3.重新计算k个簇的中心,中心为簇中所包含数据点的均值,;

    4.重复2~3,知道k个聚类中心不再移动。

  图片来源于网络,这里稍微整理了一下。

    上面就是K-means算法的过程,下面我们先对上面的过程做一个简单的实现:

from numpy import *

# 定义距离计算
def cal_dist(vect_a, vect_b):
return sqrt(sum(power(vect_a, vect_b, 2))) # 随机选取聚类中心
def rand_center(data, k):
n = shape(data)[1]
centroids = mat(zeros((k, n)))
for j in range(n):
min_j = min(data[:, j])
range_j = float(max(data[:, j]) - min(data[:, j]))
# np.random.rand(k, 1) 生成size为(k,1)的0~1的随机array
centroids[:, j] = min_j + range_j * random.rand(k, 1)
return centroids def Kmeans(data, k, dis_meas=cal_dist, create_center=rand_center):
m = shape(data)[0]
# 用于保存每个样本所属类别的矩阵,第0维为所属类别,第一维为样本距离该类别的距离
clusterAssment = mat(zeros((m, 2)))
# 初始化聚类中心
centroids = create_center(data, k)
cluster_changed = True
while cluster_changed:
cluster_changed = False
for i in range(m):
min_dist = inf
min_index = -1
for j in range(k):
dist_ij = dis_meas(data[i, :], centroids[j, :])
if dist_ij < min_dist:
min_dist = dist_ij
min_index = j
# 如果样本的类别发生了变化,则继续迭代
if clusterAssment[i, 0] != min_index:
cluster_changed = True
# 第i个样本距离最近的中心j存入
clusterAssment[i, :] = min_index, min_dist ** 2
print(centroids)
# 重新计算聚类中心
for cent in range(k):
# 找出数据集中属于第k类的样本的所有数据,nonzero返回索引值
points_in_cluster = data[nonzero(clusterAssment[:, 0].A == cent)[0]]
centroids[cent, :] = mean(points_in_cluster, axis=0)
return centroids, clusterAssment

  然后定义一个读取数据的函数,使用testdata进行测试:

def loadData(filename):
data_mat = []
fr = open(filename)
for line in fr.readlines():
cur_line = line.strip().split('\t')
flt_line = [float(example) for example in cur_line]
data_mat.append(flt_line)
return mat(data_mat)
data = loadData('.\testSet.txt')
centroids, cluster_ass = Kmeans(data, 4, dis_meas=cal_dist, create_center=rand_center) import matplotlib.pyplot as plt data_0 = data[nonzero(cluster_ass[:, 0].A == 0)[0]]
data_1 = data[nonzero(cluster_ass[:, 0].A == 1)[0]]
data_2 = data[nonzero(cluster_ass[:, 0].A == 2)[0]]
data_3 = data[nonzero(cluster_ass[:, 0].A == 3)[0]]
plt.scatter(data_0[:, 0].A[:, 0], data_0[:, 1].A[:, 0])
plt.plot(centroids[0, 0], centroids[0, 1], '*', markersize=20)
plt.scatter(data_1[:, 0].A[:, 0], data_1[:, 1].A[:, 0])
plt.plot(centroids[1, 0], centroids[1, 1], '*', markersize=20)
plt.scatter(data_2[:, 0].A[:, 0], data_2[:, 1].A[:, 0])
plt.plot(centroids[2, 0], centroids[2, 1], '*', markersize=20)
plt.scatter(data_3[:, 0].A[:, 0], data_3[:, 1].A[:, 0])
plt.plot(centroids[3, 0], centroids[3, 1], '*', markersize=20)

2.关于K-means算法的问题和改进

  K-means的损失函数为数据点与数据点所在的聚类中心之间的距离的平方和,也就是:

  其中μ为数据点所在的类别的聚类中心,我们期望最小化损失,从而找到最佳的聚类中心和数据所属的类别。

2.1 陷入局部最小值问题及改进

  然而,上面说到,在K-means算法的第一步是随机选取k个位置作为聚类中心,这可能就会导致,不同的初始位置,对最终的聚类结果有着很大的影响,比如当把k设置为2时:

  第一次随机选取的两个聚类中心为左边这张图的位置,那么结果可能分为上下两个簇,当第二次选取的为右边那张图片的位置时,可能最终的聚类结果为左右两个簇。

  因此,聚类中心的初始位置,对于我们最终的结果影响很大。再比如:

  上面通过K-means将数据聚为3类,但由于聚类中心的问题,导致效果不好,“+”为最终聚类中心位置,此时聚类中心已不再更新。

  这是因为K-means算法收敛到了局部最小值,而非全局最小值。

  因此,我们需要一定的处理方法,来处理这样的问题:

  一种最直观的做法是,我们通过多次初始化聚类中心,运行K-means聚类,得到多个结果,然后比较最后的损失(上面损失函数计算方法),选择其中最小的那一个结果。

  然而这种对于k取值比较的少的时候可以这么做,但是如果k值过大,这样做也不会得到较好的改善。

  另一种做法是对生成的簇进行后处理,通过将具有最大的损失的那个簇,再分成两个簇,也就是对于损失最大的那个簇再运用一次K-means算法,将k值设为2

  但为了保证簇的总数不变,再将某两个簇进行合并,比如上面图中,将下面圆圈和方块进行合并,但是如果是一个多维数据,无法进行可视化时,我们无法查看应该去合并哪几个簇

  因此,可以有两种方法去衡量:(1)将最近的聚类中心对应的类进行合并;(2)合并两个使总的损失增加幅度最小的簇。

  第一种方法是计算聚类中心(这里已经将最大的簇又分为两个簇)之间的距离,将最近的两个簇进行合并;

  第二种方法需要计算合并两个簇之后的损失的大小,找出最佳的合并结果。

下面介绍一种利用上面划分簇的技术所改善的K-means算法。

二分K-Means算法

  二分K-means算法是一种能够解决算法收敛到局部最小值的算法,算法思想是:首先将所有的点作为一个簇,然后分成2个,接下来,在其中选择一个簇进行划分,具体选择哪个,

  要根据选择划分的簇能够使总损失降低程度最大的那个,此时簇被分为3个,然后重复上述过程,直到达到所指定的k个簇即停止。

  具体算法过程如下:

    1.将所有数据看做为一个簇;

    2.当簇的总数目小于k时:

      对于每一个簇:

        (1)计算此时的损失;

        (2)在此簇上采用K-means聚类(k=2)

        (3)计算划分后的损失;

      选择使划分后损失最小的簇,将其进行划分

  上述过程中也可以选择损失最大的那个簇进行划分。

下面是具体实现过程:

def bi_kmeans(data, k, dist_measure=cal_dist):
m = shape(data)[0]
cluster_ass = mat(zeros((m, 2)))
# 初始化聚类中心,此时聚类中心只有一个,因此对数据取平均
centroid0 = mean(data, axis=0).tolist()[0]
# 存储每个簇的聚类中心的列表
centList = [centroid0]
for j in range(m):
cluster_ass[j, 1] = dist_measure(mat(centroid0), data[j, :]) ** 2 while (len(centList)) < k:
lowestSSE = inf
for i in range(len(centList)):
# 在当前簇中的样本点
point_in_current_cluster = data[nonzero(cluster_ass[:, 0].A == i)[0], :]
# 在当前簇运用kmeans算法,分为两个簇,返回簇的聚类中心和每个样本点距离其所属簇的中心的距离
centroid_mat, split_cluster_ass = Kmeans(point_in_current_cluster, 2, dist_measure)
# 计算被划分的簇,划分后的损失
sse_split = sum(split_cluster_ass[:, 1])
# 计算没有被划分的其它簇的损失
sse_not_split = sum(cluster_ass[nonzero(cluster_ass[:, 0].A != i)[0], 1])
# 选择最小的损失的簇,对其进行划分
if sse_split + sse_not_split < lowestSSE:
# 第i个簇被划分
best_cent_to_split = i
# 第i个簇被划分后的聚类中心
best_new_centers = centroid_mat
# 第i个簇的样本,距离划分后所属的类别(只有0和1)以及距离聚类中心的距离
best_cluster_ass = split_cluster_ass
lowestSSE = sse_split + sse_not_split
# 把新划分出来的簇,属于1类的簇重新进行编号,编号为原先的总类别数目,比如原先有两类,选择一个进行划分后,又分成两类,等于1的那一类编号为2
best_cluster_ass[nonzero(best_cluster_ass[:, 0].A == 1)[0], 0] = len(centList)
# 同理,属于第0类的重新编号,编号为所选的那一类的编号
best_cluster_ass[nonzero(best_cluster_ass[:, 0].A == 0)[0], 0] = best_cent_to_split
# 将原来的聚类中心进行替换
centList[best_cent_to_split] = best_new_centers[0, :]
# 并加入新的1类的聚类中心
centList.append(best_new_centers[1, :])
# 将之前被选到划分的那一类的结果全部替换成被划分后的结果
cluster_ass[nonzero(cluster_ass[:, 0].A == best_cent_to_split[0]), :] = best_cluster_ass return mat(centList), cluster_ass

2.2 K值的影响及其改进

  K-means算法的K值需要人为给出所需要聚类的类别数目k,那么k的选择对于结果影响很大,通常k值的选择通常需要根据实际进行手动选择,比如给定一组身高和体重的数据,我们可以聚成三类(S、M、L)来指导生产。

  还有一种方法用于选择k值的方法,成为“手肘法则”(elbow method),其原理很简单,就是通过设定不同的k值,然后画出每一个k值对应的损失,如图所示:

  然后找出“手肘”的位置,就是最佳的聚类数目k。

  这里有个问题,损失不应该是越小越好吗?理论上是这样的,但当我们把n个数据点聚成n个类别,此时损失为0,然而这并没有什么意义(类似于过拟合)。

  因此我们选择“手肘”的位置,此时损失下降相较于后面下降速度较大,即k=3。

  具体的实现这里不再说了,就是计算不同k值,然后计算Loss损失就可以了。这里补充一个关于能够自动选择k值的库:yellowbrick,代码很简单(参考https://www.zhihu.com/question/279825061/answer/1686762604):

from sklearn.cluster import KMeans
from yellowbrick.cluster.elbow import kelbow_visualizer
from yellowbrick.datasets.loaders import load_nfl x, y = load_nfl() kelbow_visualizer(KMeans(random_state=4), x, k=(2, 10))

  可以自动选出k值(k=4),而且画出的图也很好看。

  上面就是K-means算法的基本内容,由于算法比较简单,内容不多,而且sklearn也带有Kmeans的工具包(上面那个例子里就是)。总的来说虽然K-means算法比较简单,但是用途还是比较广泛的。


最近事情比较多,学习进度有点慢~,后面会继续针对聚类算法做总结和学习,下一次主要对层次聚类和DBSCAN进行一个回顾和总结。

【Python机器学习实战】聚类算法(1)——K-Means聚类的更多相关文章

  1. Python 机器学习实战 —— 监督学习(上)

    前言 近年来AI人工智能成为社会发展趋势,在IT行业引起一波热潮,有关机器学习.深度学习.神经网络等文章多不胜数.从智能家居.自动驾驶.无人机.智能机器人到人造卫星.安防军备,无论是国家级军事设备还是 ...

  2. Python 机器学习实战 —— 无监督学习(上)

    前言 在上篇<Python 机器学习实战 -- 监督学习>介绍了 支持向量机.k近邻.朴素贝叶斯分类 .决策树.决策树集成等多种模型,这篇文章将为大家介绍一下无监督学习的使用.无监督学习顾 ...

  3. Python 机器学习实战 —— 无监督学习(下)

    前言 在上篇< Python 机器学习实战 -- 无监督学习(上)>介绍了数据集变换中最常见的 PCA 主成分分析.NMF 非负矩阵分解等无监督模型,举例说明使用使用非监督模型对多维度特征 ...

  4. python机器学习实战(一)

    python机器学习实战(一) 版权声明:本文为博主原创文章,转载请指明转载地址 www.cnblogs.com/fydeblog/p/7140974.html  前言 这篇notebook是关于机器 ...

  5. python机器学习实战(四)

    python机器学习实战(三) 版权声明:本文为博主原创文章,转载请指明转载地址 www.cnblogs.com/fydeblog/p/7364317.html 前言 这篇notebook是关于机器学 ...

  6. Python 机器学习实战 —— 监督学习(下)

    前言 近年来AI人工智能成为社会发展趋势,在IT行业引起一波热潮,有关机器学习.深度学习.神经网络等文章多不胜数.从智能家居.自动驾驶.无人机.智能机器人到人造卫星.安防军备,无论是国家级军事设备还是 ...

  7. 【Python机器学习实战】决策树和集成学习(一)

    摘要:本部分对决策树几种算法的原理及算法过程进行简要介绍,然后编写程序实现决策树算法,再根据Python自带机器学习包实现决策树算法,最后从决策树引申至集成学习相关内容. 1.决策树 决策树作为一种常 ...

  8. 机器学习理论与实战(十)K均值聚类和二分K均值聚类

    接下来就要说下无监督机器学习方法,所谓无监督机器学习前面也说过,就是没有标签的情况,对样本数据进行聚类分析.关联性分析等.主要包括K均值聚类(K-means clustering)和关联分析,这两大类 ...

  9. python机器学习实战(三)

    python机器学习实战(三) 版权声明:本文为博主原创文章,转载请指明转载地址 www.cnblogs.com/fydeblog/p/7277205.html  前言 这篇notebook是关于机器 ...

  10. 基于改进人工蜂群算法的K均值聚类算法(附MATLAB版源代码)

    其实一直以来也没有准备在园子里发这样的文章,相对来说,算法改进放在园子里还是会稍稍显得格格不入.但是最近邮箱收到的几封邮件让我觉得有必要通过我的博客把过去做过的东西分享出去更给更多需要的人.从论文刊登 ...

随机推荐

  1. 议题解析与复现--《Java内存攻击技术漫谈》(一)

    解析与复现议题 Java内存攻击技术漫谈 https://mp.weixin.qq.com/s/JIjBjULjFnKDjEhzVAtxhw allowAttachSelf绕过 在Java9及以后的版 ...

  2. 使用NLog把日志写入数据库并按天自动分表

    前言 最近用Asp.net Core开发程序的时候 因为时间的关系,就没有过多的去关注日志方面的功能 都是直接用系统的ILogger先记录着,然后看日志的时候就先在命令行看日志 在开发阶段没有什么问题 ...

  3. oracle 架构和一些工具了解

    oracle的架构大概分为3部分, 客户端:用户端 oracle instance:叫做实例,由内存结构(内存池或者叫SGA)和后台进程组成.Oracle Instance是Oracle RDBMS的 ...

  4. 测试平台系列(72) 了解ApScheduler基本用法

    大家好~我是米洛! 我正在从0到1打造一个开源的接口测试平台, 也在编写一套与之对应的完整教程,希望大家多多支持. 欢迎关注我的公众号测试开发坑货,获取最新文章教程! 回顾 上一节我们调研了一下市面上 ...

  5. 04 | 函数扩展 | es6

    函数参数的默认值 基本用法 ES6 之前,不能直接为函数的参数指定默认值,只能采用变通的方法. function log(x, y) { y = y || 'World'; console.log(x ...

  6. 学习JS的第二天

    一.数据类型间的转换 主要:数字与字符串之间的转换 1.隐式转换 // console.log(1==true);[] 字符串与数字相加,其结果就是字符串  类似于字符串拼接 concole.log( ...

  7. The 'stream().forEach()' chain can be replaced with 'forEach()' (may change semantics)

    对集合操作时,因不同的写法Idea经常会提示:The 'stream().forEach()' chain can be replaced with 'forEach()' (may change s ...

  8. Netty数据如何在 pipeline 中流动

    前言 在之前文章中,我们已经了解了pipeline在netty中所处的角色,像是一条流水线,控制着字节流的读写,本文,我们在这个基础上继续深挖pipeline在事件传播 Unsafe对象 顾名思义,u ...

  9. 关于使用idea工具debug时,断点颜色由红色变成灰色

    在使用断点调试的时候,发现断点由原来的红色变成灰色的,后来发现是由于错误操作将Debug断点t调试禁用了 ,只需要点击禁用按钮取消就可以了 

  10. 菜鸡的Java笔记 第六 - java 方法

    前提:现在所讲解的方法定义格式,只属于JAVA 方法定义的其中一种组成方式.而完整的组成方式将随着学习逐步渗透. 1.方法的基本定义 方法(Method)在一些书中也会有人将其说是  函数(Funct ...