python Kmeans算法原理深入解析


Posted in Python onAugust 23, 2019

一. 概述

首先需要先介绍一下无监督学习,所谓无监督学习,就是训练样本中的标记信息是位置的,目标是通过对无标记训练样本的学习来揭示数据的内在性质以及规律。通俗得说,就是根据数据的一些内在性质,找出其内在的规律。而这一类算法,应用最为广泛的就是“聚类”。

聚类算法可以对数据进行数据归约,即在尽可能保证数据完整的前提下,减少数据的量级,以便后续处理。也可以对聚类数据结果直接应用或分析。

而Kmeans 算法可以说是聚类算法里面较为基础的一种算法。

二. 从样例开始

我们现在在二维平面上有这样一些点

x         y
1.658985    4.285136
-3.453687    3.424321
4.838138    -1.151539
-5.379713    -3.362104
0.972564    2.924086
-3.567919    1.531611
0.450614    -3.302219
-3.487105    -1.724432
2.668759    1.594842
-3.156485    3.191137
3.165506    -3.999838
-2.786837    -3.099354
4.208187    2.984927
-2.123337    2.943366
0.704199    -0.479481
-0.392370    -3.963704
2.831667    1.574018
-0.790153    3.343144
2.943496    -3.357075
...

它在二维平面上的分布大概是这样的:

python Kmeans算法原理深入解析

好,这些点看起来隐约分成4个“簇”,那么我们可以假定它就是要分成4个“簇”。(虽然我们可以“看”出来是要分成4个“簇”,但实际上也可以分成其他个,比如说5个。)这里分成“4个簇“是我们看出来的。而在实际应用中其实应该由机器算得,下面也会有介绍的。

找出4个”簇”之后,就要找出每个“簇”的中心了,我们可以“看出”大概的中心点,但机器不知道啊。那么机器是如何知道的呢?答案是通过向量距离,也叫向量相似性。这个相似性计算有多种方法,比如欧式距离,曼哈顿距离,切比雪夫距离等等。

我们这里使用的是欧式距离,欧式距离其实就是反应空间中两点的直线距离。

知道这些后,我们就可以开始让机器计算出4个“簇”了。

主要做法是这样,先随机生成4个点,假设这4个点就是4个“簇”的中心。计算平面中每个点到4个中心点的距离,平面中每个点选取距离最近的那个中心作为自己的中心。

此时我们就完成第一步,将平面中所有点分成4个”簇“。但是刚刚那几个中心都是随机的,这样分成的4个簇明显不是我们想要的结果。怎么办呢?做法如下:

现在有4个簇,根据每个簇中所有点计算出每个簇的新中心点。这个新中心点就会比上一个旧的中心点更优,因为它更加中心。然后使用新中心点重复第一步的步骤。即再对平面中所有点算距离,然后分发到4个新簇中。不断迭代,直到误差较小。

这就是 Kmeans 算法的过程了。

三. 知识点浅析

3.1 确定“簇”的个数

上面所说的分成 4 个簇,这个 4 其实就是 Kmeans 中的K。要使用 Kmeans 首先就是要选取一个 K 作为聚类个数。而上面的例子其实是我们主观”看“出来的,但多数情况下我们是无法直观”看“出分多少个 K 比较好。那怎么办呢?

我们可以从较低的 K 值开始。使用较简单的 Kmeans 算法的结果(即较少的迭代次数,不求最佳结果,但求最快)。计算每个点到其归属的“簇”的中心点的距离,然后求和,求和结果就是误差值。

然后再增加 K 值,再计算误差值。比如上面的例子,我们可以从 K=2 开始,计算 K 值从 2 到 7 的 Kmeans 算法的误差值。
这样会得到类似这样一张图:

python Kmeans算法原理深入解析

里面的 Error 可以理解未 Kmeans 的误差,而当分成越多“簇”的适合,误差肯定是越来越小。

但是不是“簇”越多越好呢?答案是否定的,有时候“簇”过多的话是不利于我们得到想要的结果或是做下一步操作的。

所以我们通常会选择误差减小速度比较平缓的那个临界点,比如上图中的 4。

可以发现,在分成 4 个簇之后,再增加簇的数量,误差也不会有很大的减少。而取 4 个簇也和我们所看到的相符。

3.2 欧式距离

欧氏距离是一个通常采用的距离定义,指在m维空间中两个点之间的真实距离,计算公式如下:

python Kmeans算法原理深入解析

而本例种的是在二维空间种,故而本例的计算公式如下:

python Kmeans算法原理深入解析

四. 代码和结果

加载数据的代码,使用了 numpy ,先是将代码加载成 matrix 类型。

import numpy as np

def loadDataSet(fileName):
  '''
  加载数据集
  :param fileName:
  :return:
  '''
  # 初始化一个空列表
  dataSet = []
  # 读取文件
  fr = open(fileName)
  # 循环遍历文件所有行
  for line in fr.readlines():
    # 切割每一行的数据
    curLine = line.strip().split('\t')
    # 将数据转换为浮点类型,便于后面的计算
    # fltLine = [float(x) for x in curLine]
    # 将数据追加到dataMat
    fltLine = list(map(float,curLine))  # 映射所有的元素为 float(浮点数)类型
    dataSet.append(fltLine)
  # 返回dataMat
  return np.matrix(dataSet)

接下来需要生成 K 个初始的质点,即中心点。这里采用随机生成的方法生成 k 个“簇”。

def randCent(dataMat, k):
  '''
  为给定数据集构建一个包含K个随机质心的集合,
  随机质心必须要在整个数据集的边界之内,这可以通过找到数据集每一维的最小和最大值来完成
  然后生成0到1.0之间的随机数并通过取值范围和最小值,以便确保随机点在数据的边界之内
  :param dataMat:
  :param k:
  :return:
  '''
  # 获取样本数与特征值
  m, n = np.shape(dataMat)
  # 初始化质心,创建(k,n)个以零填充的矩阵
  centroids = np.mat(np.zeros((k, n)))
  # 循环遍历特征值
  for j in range(n):
    # 计算每一列的最小值
    minJ = min(dataMat[:, j])
    # 计算每一列的范围值
    rangeJ = float(max(dataMat[:, j]) - minJ)
    # 计算每一列的质心,并将值赋给centroids
    centroids[:, j] = np.mat(minJ + rangeJ * np.random.rand(k, 1))
  # 返回质心
  return centroids

欧式距离计算

def distEclud(vecA, vecB):
  '''
  欧氏距离计算函数
  :param vecA:
  :param vecB:
  :return:
  '''
  return np.sqrt(sum(np.power(vecA - vecB, 2)))

cost 方法将执行一个简化的 kMeans ,即较少次数的迭代,计算出其中的误差(即当前点到簇质心的距离,后面会使用该误差来评价聚类的效果)

def cost(dataMat, k, distMeas=distEclud, createCent=randCent,iterNum=300):
  '''
  计算误差的多少,通过这个方法来确定 k 为多少比较合适,这个其实就是一个简化版的 kMeans
  :param dataMat: 数据集
  :param k: 簇的数目
  :param distMeans: 计算距离
  :param createCent: 创建初始质心
  :param iterNum:默认迭代次数
  :return:
  '''
  # 获取样本数和特征数
  m, n = np.shape(dataMat)
  # 初始化一个矩阵来存储每个点的簇分配结果
  # clusterAssment包含两个列:一列记录簇索引值,第二列存储误差(误差是指当前点到簇质心的距离,后面会使用该误差来评价聚类的效果)
  clusterAssment = np.mat(np.zeros((m, 2)))
  # 创建质心,随机K个质心
  centroids = createCent(dataMat, k)
  clusterChanged = True
  while iterNum > 0:
    clusterChanged = False
    # 遍历所有数据找到距离每个点最近的质心,
    # 可以通过对每个点遍历所有质心并计算点到每个质心的距离来完成
    for i in range(m):
      minDist = np.inf
      minIndex = -1
      for j in range(k):
        # 计算数据点到质心的距离
        # 计算距离是使用distMeas参数给出的距离公式,默认距离函数是distEclud
        distJI = distMeas(centroids[j, :], dataMat[i, :])
        # print(distJI)
        # 如果距离比minDist(最小距离)还小,更新minDist(最小距离)和最小质心的index(索引)
        if distJI < minDist:
          minDist = distJI
          minIndex = j
      # 更新簇分配结果为最小质心的index(索引),minDist(最小距离)的平方
      clusterAssment[i, :] = minIndex, minDist ** 2
      iterNum -= 1;
    # print(centroids)
    # 遍历所有质心并更新它们的取值
    for cent in range(k):
      # 通过数据过滤来获得给定簇的所有点
      ptsInClust = dataMat[np.nonzero(clusterAssment[:, 0].A == cent)[0]]
      # 计算所有点的均值,axis=0表示沿矩阵的列方向进行均值计算
      centroids[cent, :] = np.mean(ptsInClust, axis=0)
  # 返回给定迭代次数后误差的值
  return np.mat(clusterAssment[:,1].sum(0))[0,0]

最后可以调用 Kmeans 算法来进行计算。

def kMeans(dataMat, k, distMeas=distEclud, createCent=randCent):
  '''
  创建K个质心,然后将每个店分配到最近的质心,再重新计算质心。
  这个过程重复数次,直到数据点的簇分配结果不再改变为止
  :param dataMat: 数据集
  :param k: 簇的数目
  :param distMeans: 计算距离
  :param createCent: 创建初始质心
  :return:
  '''
  # 获取样本数和特征数
  m, n = np.shape(dataMat)
  # 初始化一个矩阵来存储每个点的簇分配结果
  # clusterAssment包含两个列:一列记录簇索引值,第二列存储误差(误差是指当前点到簇质心的距离,后面会使用该误差来评价聚类的效果)
  clusterAssment = np.mat(np.zeros((m, 2)))
  # 创建质心,随机K个质心
  centroids = createCent(dataMat, k)
  # 初始化标志变量,用于判断迭代是否继续,如果True,则继续迭代
  clusterChanged = True
  while clusterChanged:
    clusterChanged = False
    # 遍历所有数据找到距离每个点最近的质心,
    # 可以通过对每个点遍历所有质心并计算点到每个质心的距离来完成
    for i in range(m):
      minDist = np.inf
      minIndex = -1
      for j in range(k):
        # 计算数据点到质心的距离
        # 计算距离是使用distMeas参数给出的距离公式,默认距离函数是distEclud
        distJI = distMeas(centroids[j, :], dataMat[i, :])
        # 如果距离比minDist(最小距离)还小,更新minDist(最小距离)和最小质心的index(索引)
        if distJI < minDist:
          minDist = distJI
          minIndex = j
      # 如果任一点的簇分配结果发生改变,则更新clusterChanged标志
      if clusterAssment[i, 0] != minIndex: clusterChanged = True
      # 更新簇分配结果为最小质心的index(索引),minDist(最小距离)的平方
      clusterAssment[i, :] = minIndex, minDist ** 2
    # print(centroids)
    # 遍历所有质心并更新它们的取值
    for cent in range(k):
      # 通过数据过滤来获得给定簇的所有点
      ptsInClust = dataMat[np.nonzero(clusterAssment[:, 0].A == cent)[0]]
      # 计算所有点的均值,axis=0表示沿矩阵的列方向进行均值计算
      centroids[cent, :] = np.mean(ptsInClust, axis=0)
  # 返回所有的类质心与点分配结果
  return centroids, clusterAssment

选取不同的 k 值对结果影响有多大呢?我们来看看就知道了,下面给出的是 k 值为 2 到 6 的效果。
图中红色方块即为“簇”的中心点,每个“簇”所属的点用不同的颜色表示。

K = 2

python Kmeans算法原理深入解析

K = 3

python Kmeans算法原理深入解析

K = 4

python Kmeans算法原理深入解析

K = 5

python Kmeans算法原理深入解析

K = 6

python Kmeans算法原理深入解析

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Python 相关文章推荐
使用Python脚本操作MongoDB的教程
Apr 16 Python
浅谈python和C语言混编的几种方式(推荐)
Sep 27 Python
Python中getpass模块无回显输入源码解析
Jan 11 Python
Python实现七彩蟒蛇绘制实例代码
Jan 16 Python
django admin 后台实现三级联动的示例代码
Jun 22 Python
DES加密解密算法之python实现版(图文并茂)
Dec 06 Python
使用PIL(Python-Imaging)反转图像的颜色方法
Jan 24 Python
Python爬虫实现“盗取”微信好友信息的方法分析
Sep 16 Python
Python阶乘求和的代码详解
Feb 14 Python
详解Windows下PyCharm安装Numpy包及无法安装问题解决方案
Jun 18 Python
python中用Scrapy实现定时爬虫的实例讲解
Jan 18 Python
教你怎么用Python生成九宫格照片
May 20 Python
Python高级特性 切片 迭代解析
Aug 23 #Python
Python 合并多个TXT文件并统计词频的实现
Aug 23 #Python
Python 调用 Windows API COM 新法
Aug 22 #Python
详解Python文件修改的两种方式
Aug 22 #Python
详解python中的生成器、迭代器、闭包、装饰器
Aug 22 #Python
python支付宝支付示例详解
Aug 22 #Python
关于python3中setup.py小概念解析
Aug 22 #Python
You might like
php提交表单发送邮件的方法
2015/03/20 PHP
Yii编程开发常见调用技巧集锦
2016/07/15 PHP
JavaScript中常用的运算符小结
2012/01/18 Javascript
Table冻结表头示例代码
2013/08/20 Javascript
jquery实现点击文字可编辑并修改保存至数据库
2014/04/15 Javascript
connect中间件session、cookie的使用方法分享
2014/06/17 Javascript
Nodejs实现的一个静态服务器实例
2014/12/06 NodeJs
javascript封装的sqlite操作类实例
2015/07/17 Javascript
jquery+CSS3模拟Path2.0动画菜单效果代码
2015/08/31 Javascript
jQuery simplePage+AJAX plus分页插件用法实例
2016/02/17 Javascript
Bootstrap Table 删除和批量删除
2017/09/22 Javascript
微信小程序实现换肤功能
2018/03/14 Javascript
javascript显示动态时间的方法汇总
2018/07/06 Javascript
详解在React.js中使用PureComponent的重要性和使用方式
2018/07/10 Javascript
微信小程序地图实现展示线路
2020/07/29 Javascript
Python将xml和xsl转换为html的方法
2015/03/10 Python
python下paramiko模块实现ssh连接登录Linux服务器
2015/06/03 Python
使用pyecharts在jupyter notebook上绘图
2020/04/23 Python
Python2.X/Python3.X中urllib库区别讲解
2017/12/19 Python
python 实现交换两个列表元素的位置示例
2019/06/26 Python
python tkinter实现彩球碰撞屏保
2019/07/30 Python
pandas 对日期类型数据的处理方法详解
2019/08/08 Python
win10下安装Anaconda的教程(python环境+jupyter_notebook)
2019/10/23 Python
利用纯css3实现的文字亮光特效的代码演示
2014/11/27 HTML / CSS
AmazeUI 点击元素显示全屏的实现
2020/08/25 HTML / CSS
马来西亚领先的在线礼品店:Giftr
2018/08/23 全球购物
什么叫应用程序域?什么是托管代码?什么是强类型系统?什么是装箱和拆箱?什么是重载?CTS、CLS和CLR分别作何解释?
2012/05/23 面试题
最新党员思想汇报
2014/01/01 职场文书
《宋庆龄故居的樟树》教学反思
2014/04/07 职场文书
党员承诺践诺书
2014/05/20 职场文书
女生节标语
2014/06/26 职场文书
幼儿园小班见习报告
2014/10/31 职场文书
违反学校规则制度检讨书
2015/01/01 职场文书
党小组鉴定意见
2015/06/02 职场文书
MySQL安装后默认自带数据库的作用详解
2021/04/27 MySQL