背景与原理:

聚类问题与分类问题有一定的区别,分类问题是对每个训练数据,我给定了类别的标签,现在想要训练一个模型使得对于测试数据能输出正确的类别标签,更多见于监督学习;而聚类问题则是我们给出了一组数据,我们并没有预先的标签,而是由机器考察这些数据之间的相似性,将相似的数据聚为一类,是无监督学习的一个典型应用。

而k-means算法则是非常常见的聚类算法,其思想是如果我们想把这些数据聚为k类,那么我们预先选择k个中心,然后计算每个数据点与这k个中心之间的“距离”(也就是这个数据点与这个中心的“相似度”),那么非常自然地,每个数据点应当被划分进离他距离最近的那个中心点对应的类。

但是这是最优的聚类方法吗?如果我们初始选的k个点很糟糕,其实有些数据点离这k个点都很远,而这种划分只是一种“矮子里面拔将军”的划分,因此可能会把两个差异巨大的点划分到一个聚类里面去,因此我们需要迭代上述过程,即当我们选中了一些数据点聚为一类之后,我们取这些数据点的“质心”作为新的中心点,这样我们会得到k个新的中心点,然后我们重复上述过程,直到中心点不再移动为止。

那么我们就要解决几个问题:

一.我们如何度量“距离”或“相似度”?

距离的度量其实有很多方式,对于两个数据点$(x_{1},x_{2},...,x_{n}),(y_{1},y_{2},...,y_{n})$,常见的距离度量有如下的方式:

欧氏距离:$d(x,y)=\sqrt{\sum_{i=1}^{n}(x_{i}-y_{i})^{2}}$

曼哈顿距离:$d(x,y)=\sum_{i=1}^{n}|x_{i}-y_{i}|$

切比雪夫距离:$d(x,y)=max_{i=1}^{n}|x_{i}-y_{i}|$

余弦距离:$d(x,y)=\cos \theta =\dfrac{\sum_{i=1}^{n}x_{i}y_{i}}{\sqrt{\sum_{i=1}^{n}x_{i}^{2}} \sqrt{\sum_{i=1}^{n}y_{i}^{2}}}$

相关系数:$\rho_{XY}=\dfrac{Cov(X,Y)}{\sqrt{D(X)} \sqrt{D(Y)}}=\dfrac{E((X-EX)(Y-EY))}{\sqrt{D(X)} \sqrt{D(Y)}}$

在这里我们选用欧氏距离来度量点之间的距离,选取SSE(误差平方和)作为损失函数,即设我们有$k$类,第$i$类的中心点为$c_{i}$,那么损失函数为:

$J(c)=\sum_{i=1}^{k}\sum_{x\in C_{i}}d(x,c_{i})^{2}$

那么此时我们每次迭代过程中选取的新中心点是什么呢?

我们对第$i$个中心点$c_{i}$求偏导:

$\dfrac{\partial J(c)}{\partial c_{i}}=\dfrac{\partial \sum_{x\in C_{i}}|x-c_{i}|^{2}}{\partial c_{i}}=-2\sum_{x\in C_{i}} (x-c_{i})$

而取得极小值时,上述偏导数为零,即:

$c_{i}=\dfrac{\sum_{x\in C_{i}}x}{|C_{i}|}$

也即新的中心点应该是聚在这一类里的所有点的算术平均值

设计、调整与评价:

如何选取初始聚类中心?

(1)凭经验直接选取

(2)将数据随机分成k类,计算每类中心作为初始聚类中心

(3)求以每个数据点为球心,某个半径内的特征点个数,选取密度最大的特征点为第一个聚类中心,然后在离这个聚类中心距离大于某个距离$d$的特征点中选取另一个密度最大的特征点,以此类推直至选出k个点

(4)用距离最远的k个点作为初始中心

(5)n较大时,先随机选出一部分聚成k类,再将这k个中心作为初始聚类中心

如何选取聚类个数?

(1)按聚类目标(比如手写数字识别)等先验知识确定k

(2)让k从小到大增加,那么损失函数显然在减少,选择损失函数下降的拐点对应的k

如何评价聚类效果?

一般我们可以用几个指标来评价,比如$P$值(纯度)和$F$值

所谓纯度,是指如果我们已知每个数据点的类别,我们不妨假设一共有$k$类,那么对于聚出的第$r$类,其纯度$P(S_{r})=\dfrac{max_{i=1}^{k}n_{ri}}{n_{r}}$,所谓$n_{ri}$,就是被聚在第$r$类的数据中心原本属于第$i$类的数据点个数,而$n_{r}$就是整个被聚出的第$r$类的元素个数,而我们整个聚类过程的总纯度为:

$P=\sum_{r=1}^{k}\dfrac{n_{r}}{n}P(S_{r})$

其中$n$为所有数据点的总数。

而所谓$F$值,是准确率(precision)和召回率(recall)的调和平均值。

这里要解决的其实是一个问题:我们已知原数据有$k$类,而我们聚出了$k$个类,那...我们怎么把聚出的$k$个类和预先想分出的$k$类对应起来?

举个例子:手写数字识别,我们要识别手写的0~9十个数字,而我们聚出了十个类,那我们要怎么知道每个类对应的是哪个数字呢?

那么我们定义$precision(i,r)=\dfrac{n_{ri}}{n_{r}}$,即如果我们认为聚出的第$r$类对应于原数据中的第$i$类,那么其准确率即为这个类里确实属于第$i$类的数据占比

而$recall(i,r)=\dfrac{n_{ri}}{n_{i}}$,其中$n_{i}$表示在原标签中属于第$i$类的数据个数,即如果我们认为聚出的第$r$类对应于原数据中的第$i$类,那么召回率即为这个类里确实属于第$i$类里的数据占所有第$i$类数据的占比

那么我们定义$f(i,r)=\dfrac{2*precision(i,r)*recall(i,r)}{precision(i,r)+recall(i,r)}$,而整个数据集的$F$定义为:

$F=\sum_{i=1}^{k}\dfrac{n_{i}}{n}f(i,r)$

当然,这个值取决于如何对聚类和原始类别对应,因此我们想最大化这个值,我们就要使用一种二分图匹配算法,常用的是KM算法。

KM算法可以查看别的介绍,这里简要介绍下其功能:我们把聚出的k类和原始分好的k类分在两侧,那么这可以看做一个二分图模型,而我们构造的聚类与分好类的对应关系就是一种二分图的匹配,而这个匹配过程的要求是我们要最大化$F$值,那么如果我们设聚出的第$r$类和原始的第$i$类之间的边权为$n_{i}f(i,r)$,我们进行的就是二分图最佳匹配,而这个匹配可以用KM算法计算出来。

代码实现:

  1. from mpl_toolkits.mplot3d import Axes3D
  2. import matplotlib.pyplot as plt
  3.  
  4. plt.figure()
  5. img = plt.imread('./cat.jpeg')
  6. plt.imshow(img)
  7.  
  8. def kmeans_iteration(l):
  9. oril=[]
  10. for i in l:
  11. oril.append(i)
  12. flag=0
  13. for i in dic:
  14. p=0
  15. mind=10000000
  16. for j in range(0,16):
  17. d=pow((i[0]-l[j][0]),2)+pow((i[1]-l[j][1]),2)+pow((i[2]-l[j][2]),2)
  18. if d<mind:
  19. p=j
  20. mind=d
  21. if dic[i]!=p:
  22. flag=1
  23. dic[i]=p
  24. if flag==0:
  25. return l
  26. else:for i in range(0,16):
  27. cnt=0
  28. r=0
  29. b=0
  30. g=0
  31. for j in dic:
  32. if dic[j]==i:
  33. r+=j[0]
  34. b+=j[1]
  35. g+=j[2]
  36. cnt+=1
  37. r/=cnt
  38. b/=cnt
  39. g/=cnt
  40. l[i]=(r,b,g)
  41.  
  42. for i in range(0,16):
  43. d=pow((oril[i][0]-l[i][0]),2)+pow((oril[i][1]-l[i][1]),2)+pow((oril[i][2]-l[i][2]),2)
  44. if d>1:
  45. flag=0
  46. if flag==1:
  47. return l
  48. else:
  49. return kmeans_iteration(l)
  50.  
  51. templ=[]
  52. for i in range(1,17):
  53. templ.append((i*10,i*10,i*10))
  54.  
  55. retl=kmeans_iteration(templ)
  56. re=np.zeros((1080,1080,3))
  57. for i in range(0,1080):
  58. for j in range(0,1080):
  59. p=0
  60. mind=1000000
  61. for k in range(0,16):
  62. d=pow((img[i][j][0]-retl[k][0]),2)+pow((img[i][j][1]-retl[k][1]),2)+pow((img[i][j][2]-retl[k][2]),2)
  63. if d<mind:
  64. p=k
  65. mind=d
  66. for k in range(0,3):
  67. re[i][j][k]=retl[p][k]
  68. plt.imshow(re/255)

这段代码是kmeans的一个手写实现,实现了将一张图片压缩至16色,原理是将所有颜色点聚成16类,每个颜色用其聚类中心取代

这是原始图片:

这是处理后的图片:

可以看到压缩效果还是很不错的

  1. from sklearn import datasets, preprocessing
  2. from sklearn.decomposition import PCA
  3. from sklearn.cluster import KMeans, MeanShift
  4. import pandas as pd
  5. import numpy as np
  6. from matplotlib import pyplot as plt
  7. %matplotlib inline
  8. X = pd.read_csv('./train_X.csv') # 为了方便起见,这里只采用前6000个MNIST数据
  9. y = pd.read_csv('./train_y.csv')
  10. X, y = np.array(X), np.array(y)
  11. print(X.shape)
  12. print(y.shape)
  13.  
  14. pca2d = PCA(n_components=2)
  15. X_std = preprocessing.scale(X) # 数据标准化
  16. X_2d = pca2d.fit_transform(X_std)# 数据降维至两维便于可视化
  17. plt.scatter(X_2d[:, 0], X_2d[:, 1], c=y)
  18.  
  19. y_pred_std = KMeans(n_clusters=10, random_state=9).fit_predict(X_std)
  20. plt.scatter(X_2d[:,0],X_2d[:,1],c=y_pred_std)
  21. l2=[]
  22. for i in range(0,10):
  23. l2.append(dict())
  24. for j in range(0,10):
  25. l2[i][j]=0
  26. for i in range(0,6000):
  27. l2[y_pred_std[i]][y[i][0]]+=1
  28.  
  29. P2=0
  30. for i in range(0,10):
  31. p=0
  32. for j in range(1,10):
  33. if l2[i][j]>l2[i][p]:
  34. p=j
  35. P2+=l2[i][p]/6000
  36. print(P2)

这个代码展示了使用sklearn里面的KMeans包直接进行kmeans聚类,而同样还进行了一个PCA降维,这个降维的过程主要是用来可视化,即把聚类结果花在一张图上,同时这里还计算了P值

而如果实现类别的对应,我们可以这样写:

  1. dic1=dict()#ni
  2. dic3=dict()#nr
  3. graph2=[]
  4. for i in range(0,10):
  5. dic1[i]=0
  6. dic3[i]=0
  7. graph2.append([])
  8.  
  9. for i in range(0,6000):
  10. dic1[y[i][0]]+=1
  11. dic3[y_pred_std[i]]+=1
  12.  
  13. for i in range(0,10):
  14. for j in range(0,10):
  15. graph2[i].append(2*l2[j][i]*dic1[i]/(dic1[i]+dic3[j]))
  16.  
  17. def find_path(graph,i, visited_left, visited_right, slack_right):
  18. visited_left[i] = True
  19. for j, match_weight in enumerate(graph[i]):
  20. if visited_right[j]:
  21. continue
  22. gap = label_left[i] + label_right[j] - match_weight
  23. if abs(gap)<1e-3 :
  24. visited_right[j] = True
  25. if j not in T or find_path(graph,T[j], visited_left, visited_right, slack_right):
  26. T[j] = i
  27. S[i] = j
  28. return True
  29.  
  30. else:
  31. slack_right[j] = min(slack_right[j], gap)
  32. return False
  33.  
  34. def KM(graph):
  35. m = len(graph)
  36. for i in range(m):
  37. # 重置辅助变量
  38. slack_right = [float('inf') for _ in range(m)]
  39. while True:
  40. visited_left = [False for _ in graph]
  41. visited_right = [False for _ in graph]
  42. if find_path(graph,i,visited_left,visited_right, slack_right):
  43. break
  44. d = float('inf')
  45. for j, slack in enumerate(slack_right):
  46. if not visited_right[j] and slack < d:
  47. d = slack
  48. for k in range(m):
  49. if visited_left[k]:
  50. label_left[k] -= d
  51. if visited_right[k]:
  52. label_right[k] += d
  53. return S, T
  54.  
  55. label_left, label_right = [max(g) for g in graph2], [0 for _ in graph2]
  56. S, T = {}, {}
  57.  
  58. visited_left = [False for _ in graph2]
  59. visited_right = [False for _ in graph2]
  60. slack_right = [float('inf') for _ in graph2]
  61. KM(graph2)
  62. ans=0
  63. for i in S:
  64. ans+=graph2[i][S[i]]
  65. print(ans/6000)

这段代码用KM算法计算了上述手写数字识别kmeans的F值。

小结与优化:

kmeans算法的优点是显著的:算法简单易于实现;如果类密集且类与类之间区别明显时聚类效果很好;算法复杂度为$O(Nkt)$,对大数据集而言相对高效

但是其缺点也是显著的:结果与初始质心的选取有关(如果初始质心选取的不好迭代次数会很多,同时效果可能会比较差);必须预先给出要聚类的个数k作为超参数(因此需要调参);对噪声和孤立数据点敏感,少量这样的数据就会对平均值产生较大的影响;不适合发现非凸形状的聚类;在大数据集上收敛比较慢;可能达到局部极小值。

常见的改进有kmeans++算法,即初始选取聚类中心时要求聚类中心离得越远越好;ISODATA算法(迭代自组织数据分析法):可以调整的kmeans方法,当属于某个类的数据点过少就删掉这个类,而当属于某个类的数据过多、分散程度较大时将这个类分成两个子类

python机器学习——kmeans聚类算法的更多相关文章

  1. k-means聚类算法python实现

    K-means聚类算法 算法优缺点: 优点:容易实现缺点:可能收敛到局部最小值,在大规模数据集上收敛较慢使用数据类型:数值型数据 算法思想 k-means算法实际上就是通过计算不同样本间的距离来判断他 ...

  2. 机器学习六--K-means聚类算法

    机器学习六--K-means聚类算法 想想常见的分类算法有决策树.Logistic回归.SVM.贝叶斯等.分类作为一种监督学习方法,要求必须事先明确知道各个类别的信息,并且断言所有待分类项都有一个类别 ...

  3. K-means聚类算法及python代码实现

    K-means聚类算法(事先数据并没有类别之分!所有的数据都是一样的) 1.概述 K-means算法是集简单和经典于一身的基于距离的聚类算法 采用距离作为相似性的评价指标,即认为两个对象的距离越近,其 ...

  4. 转载 | Python AI 教学│k-means聚类算法及应用

    关注我们的公众号哦!获取更多精彩哦! 1.问题导入 假如有这样一种情况,在一天你想去某个城市旅游,这个城市里你想去的有70个地方,现在你只有每一个地方的地址,这个地址列表很长,有70个位置.事先肯定要 ...

  5. Python数据科学手册-机器学习: k-means聚类/高斯混合模型

    前面学习的无监督学习模型:降维 另一种无监督学习模型:聚类算法. 聚类算法直接冲数据的内在性质中学习最优的划分结果或者确定离散标签类型. 最简单最容易理解的聚类算法可能是 k-means聚类算法了. ...

  6. K-Means 聚类算法原理分析与代码实现

    前言 在前面的文章中,涉及到的机器学习算法均为监督学习算法. 所谓监督学习,就是有训练过程的学习.再确切点,就是有 "分类标签集" 的学习. 现在开始,将进入到非监督学习领域.从经 ...

  7. 转载: scikit-learn学习之K-means聚类算法与 Mini Batch K-Means算法

    版权声明:<—— 本文为作者呕心沥血打造,若要转载,请注明出处@http://blog.csdn.net/gamer_gyt <—— 目录(?)[+] ================== ...

  8. 【Python学习笔记】使用python进行kmeans聚类

    使用python进行kmeans聚类 假设我们要解决一个这样的问题. 以下是一些同学,大萌是一个学霸,而我们想要找到这些人中的潜在学霸,所以我们要把这些人分为两类--学霸与非学霸. 高数 英语 Pyt ...

  9. 第十三篇:K-Means 聚类算法原理分析与代码实现

    前言 在前面的文章中,涉及到的机器学习算法均为监督学习算法. 所谓监督学习,就是有训练过程的学习.再确切点,就是有 "分类标签集" 的学习. 现在开始,将进入到非监督学习领域.从经 ...

  10. 03-01 K-Means聚类算法

    目录 K-Means聚类算法 一.K-Means聚类算法学习目标 二.K-Means聚类算法详解 2.1 K-Means聚类算法原理 2.2 K-Means聚类算法和KNN 三.传统的K-Means聚 ...

随机推荐

  1. idea启动项目,报java.lang.OutOfMemoryError: PermGen space 和启动项目很慢的问题解决

    启动一个老的项目,报错,查是内存溢出 进入 VM options 加上   -Xms256m -Xmx256m -XX:MaxNewSize=256m -XX:MaxPermSize=256m   在 ...

  2. postgresql生产环境三大难题

    1.逻辑复制没有高可用 2.vacuum表膨胀问题严重 3.事务ID回卷,冻结清理

  3. ChainofResponsibility Pattern

    责任链模式: 参考:https://www.runoob.com/design-pattern/chain-of-responsibility-pattern.html 避免请求发送者与接收者耦合在一 ...

  4. vue3.0 dialog无法弹出的问题

    最近用elementui做了点东西,一直感觉挺好的,但是嫖的别人的框架是vue3.0,这次的dialog就弹不出来了. 经过多方查证,发现vue3.0的element为了适配移动端升级为element ...

  5. pywinauto app自动化的实践

    from pywinauto import Application app = Application(backend='uia').start("notepad") # prin ...

  6. scrcpy不使用adb远程控制android

    1.开启服务器 CLASSPATH=/data/local/tmp/scrcpy-server.jar app_process / com.genymobile.scrcpy.Server 1.23 ...

  7. linux kernel 常用函数记录

    offsetof是用来判断结构体中成员的偏移位置 container_of宏用来根据成员的地址来获取结构体的地址 bitwise 是用来确保不同位方式类型不会被弄混 (小端模式,大端模式,cpu 尾模 ...

  8. ADC采样信号RMS测量值的Verilog实现

    术语"RMS"代表"Root-Mean-Squared".大多数书籍将此定义为"产生与等效直流电源相同的加热效果的交流电量",或者沿着这些线 ...

  9. Twenty-seven

    侦听器 watch侦听器 watch侦听器允许开发者监视数据 的变化,从而针对数据的变化做特定的操作 语法如下 侦听器的格式 1.方法格式的侦听器 缺点:无法在所进入页面的时候,自动触发!!!     ...

  10. 【Python实例】用脚本自动拿一个或多个仿真环境

    注1:之前使用的是perl,现在尝试切换到python; 注2:该脚本用于实现自动拿仿真环境,里面应该还有很多不足之处,后续逐渐完善; 注3:假设脚本名字为get_env.py,直接执行脚本,会有两次 ...