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描述器descriptor详解
Feb 03 Python
浅谈python内置变量-reversed(seq)
Jun 21 Python
python中使用%与.format格式化文本方法解析
Dec 27 Python
快速查询Python文档方法分享
Dec 27 Python
python中不能连接超时的问题及解决方法
Jun 10 Python
django admin 后台实现三级联动的示例代码
Jun 22 Python
Python实现基于C/S架构的聊天室功能详解
Jul 07 Python
利用Python如何批量修改数据库执行Sql文件
Jul 29 Python
python对日志进行处理的实例代码
Oct 06 Python
只需7行Python代码玩转微信自动聊天
Jan 27 Python
python生成带有表格的图片实例
Feb 03 Python
Python3.5文件读与写操作经典实例详解
May 01 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源码之explode使用说明
2011/08/05 PHP
关于js和php对url编码的处理方法
2014/03/04 PHP
php实现MySQL数据库备份与还原类实例
2014/12/09 PHP
php+croppic.js实现剪切上传图片功能
2018/08/14 PHP
PHP进阶学习之反射基本概念与用法分析
2019/06/18 PHP
Dom与浏览器兼容性说明
2010/10/25 Javascript
JavaScript Memoization 让函数也有记忆功能
2011/10/27 Javascript
jQuery插件开发全解析
2012/10/10 Javascript
在JavaScript里防止事件函数高频触发和高频调用的方法
2014/09/06 Javascript
深入学习JavaScript中的Rest参数和参数默认值
2015/07/28 Javascript
jquery模拟alert的弹窗插件
2015/07/31 Javascript
图解JavaScript中的this关键字
2020/05/28 Javascript
JS获取屏幕高度的简单实现代码
2016/05/24 Javascript
Node.js Streams文件读写操作详解
2016/07/04 Javascript
nodejs 图解express+supervisor+ejs的用法(推荐)
2017/09/08 NodeJs
vue-lazyload图片延迟加载插件的实例讲解
2018/02/09 Javascript
axios发送post请求springMVC接收不到参数的解决方法
2018/03/05 Javascript
jQuery中实现text()的方法
2019/04/04 jQuery
详解auto-vue-file:一个自动创建vue组件的包
2019/04/26 Javascript
详解如何在JS代码中消灭for循环
2019/12/11 Javascript
JavaScript this关键字指向常用情况解析
2020/09/02 Javascript
Python使用ftplib实现简易FTP客户端的方法
2015/06/03 Python
Python数据分析之真实IP请求Pandas详解
2016/11/18 Python
python实现对指定输入的字符串逆序输出的6种方法
2018/04/26 Python
在python中按照特定顺序访问字典的方法详解
2018/12/14 Python
python 在屏幕上逐字显示一行字的实例
2018/12/24 Python
python实现多层感知器MLP(基于双月数据集)
2019/01/18 Python
Python 将 QQ 好友头像生成祝福语的实现代码
2020/05/03 Python
基于python实现监听Rabbitmq系统日志代码示例
2020/11/28 Python
Python日志打印里logging.getLogger源码分析详解
2021/01/17 Python
时尚的CSS3进度条效果
2012/02/22 HTML / CSS
纽约家具、家居装饰和地毯店:ABC Carpet & Home
2017/06/21 全球购物
英国历史最悠久的DJ设备供应商:DJ Finance、DJ Warehouse、The DJ Shop
2019/09/04 全球购物
介绍一下Make? 为什么使用make
2016/07/31 面试题
母亲节寄语大全
2015/02/27 职场文书
jquery插件实现代码雨特效
2021/04/24 jQuery