背景与原理:

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

而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算法计算出来。

代码实现:

from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt plt.figure()
img = plt.imread('./cat.jpeg')
plt.imshow(img) def kmeans_iteration(l):
oril=[]
for i in l:
oril.append(i)
flag=0
for i in dic:
p=0
mind=10000000
for j in range(0,16):
d=pow((i[0]-l[j][0]),2)+pow((i[1]-l[j][1]),2)+pow((i[2]-l[j][2]),2)
if d<mind:
p=j
mind=d
if dic[i]!=p:
flag=1
dic[i]=p
if flag==0:
return l
else:for i in range(0,16):
cnt=0
r=0
b=0
g=0
for j in dic:
if dic[j]==i:
r+=j[0]
b+=j[1]
g+=j[2]
cnt+=1
r/=cnt
b/=cnt
g/=cnt
l[i]=(r,b,g) for i in range(0,16):
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)
if d>1:
flag=0
if flag==1:
return l
else:
return kmeans_iteration(l) templ=[]
for i in range(1,17):
templ.append((i*10,i*10,i*10)) retl=kmeans_iteration(templ)
re=np.zeros((1080,1080,3))
for i in range(0,1080):
for j in range(0,1080):
p=0
mind=1000000
for k in range(0,16):
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)
if d<mind:
p=k
mind=d
for k in range(0,3):
re[i][j][k]=retl[p][k]
plt.imshow(re/255)

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

这是原始图片:

这是处理后的图片:

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

from sklearn import datasets, preprocessing
from sklearn.decomposition import PCA
from sklearn.cluster import KMeans, MeanShift
import pandas as pd
import numpy as np
from matplotlib import pyplot as plt
%matplotlib inline
X = pd.read_csv('./train_X.csv') # 为了方便起见,这里只采用前6000个MNIST数据
y = pd.read_csv('./train_y.csv')
X, y = np.array(X), np.array(y)
print(X.shape)
print(y.shape) pca2d = PCA(n_components=2)
X_std = preprocessing.scale(X) # 数据标准化
X_2d = pca2d.fit_transform(X_std)# 数据降维至两维便于可视化
plt.scatter(X_2d[:, 0], X_2d[:, 1], c=y) y_pred_std = KMeans(n_clusters=10, random_state=9).fit_predict(X_std)
plt.scatter(X_2d[:,0],X_2d[:,1],c=y_pred_std)
l2=[]
for i in range(0,10):
l2.append(dict())
for j in range(0,10):
l2[i][j]=0
for i in range(0,6000):
l2[y_pred_std[i]][y[i][0]]+=1 P2=0
for i in range(0,10):
p=0
for j in range(1,10):
if l2[i][j]>l2[i][p]:
p=j
P2+=l2[i][p]/6000
print(P2)

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

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

dic1=dict()#ni
dic3=dict()#nr
graph2=[]
for i in range(0,10):
dic1[i]=0
dic3[i]=0
graph2.append([]) for i in range(0,6000):
dic1[y[i][0]]+=1
dic3[y_pred_std[i]]+=1 for i in range(0,10):
for j in range(0,10):
graph2[i].append(2*l2[j][i]*dic1[i]/(dic1[i]+dic3[j])) def find_path(graph,i, visited_left, visited_right, slack_right):
visited_left[i] = True
for j, match_weight in enumerate(graph[i]):
if visited_right[j]:
continue
gap = label_left[i] + label_right[j] - match_weight
if abs(gap)<1e-3 :
visited_right[j] = True
if j not in T or find_path(graph,T[j], visited_left, visited_right, slack_right):
T[j] = i
S[i] = j
return True else:
slack_right[j] = min(slack_right[j], gap)
return False def KM(graph):
m = len(graph)
for i in range(m):
# 重置辅助变量
slack_right = [float('inf') for _ in range(m)]
while True:
visited_left = [False for _ in graph]
visited_right = [False for _ in graph]
if find_path(graph,i,visited_left,visited_right, slack_right):
break
d = float('inf')
for j, slack in enumerate(slack_right):
if not visited_right[j] and slack < d:
d = slack
for k in range(m):
if visited_left[k]:
label_left[k] -= d
if visited_right[k]:
label_right[k] += d
return S, T label_left, label_right = [max(g) for g in graph2], [0 for _ in graph2]
S, T = {}, {} visited_left = [False for _ in graph2]
visited_right = [False for _ in graph2]
slack_right = [float('inf') for _ in graph2]
KM(graph2)
ans=0
for i in S:
ans+=graph2[i][S[i]]
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. redis缓存一致性

    redis缓存一致性 redis是目前使用最广泛的分布式缓存系统,几乎每家公司都在用.它使用简单,吞吐量高,单机 qps 可以达到 10 万每秒,但在使用redis缓存时存在一个问题,即如何保证缓存数 ...

  2. LightOJ - 1162 Min Max Roads

    LightOJ - 1162 Min Max Roads 题解:在线倍增LCA和模拟ST表 让我们求从\(u->v\)最短路径上的边权最大值和边权最小值,那么我们可以利用倍增思想,类似其\(fa ...

  3. C# 字符串数组去重(去除数组中重复元素)

    1. 去重distinct()或者GroupBy(p => p).Select(p => p.Key),去重的对象都需要为数组,具体看代码 string itemfileids = &qu ...

  4. 使用FormData上传图片

    document.querySelector('.file').addEventListener('change', function(e) { //1.可以通过this拿到这个file的DOM元素 ...

  5. make vscode portable together with its extensions

    0. the goal make vscode poratable together with its extensions, so that the offline pc could make us ...

  6. go 使用mysql

    package main /* 下划线(如:import _ hello/imp)的作用:当导入一个包时,该包下的文件里所有init()函数都会被执行, 然而,有些时候我们并不需要把整个包都导入进来, ...

  7. 数据库常用sql

    1.创建表 create table 表名( 字段名 类型 约束, 字段名 类型 约束 ... ) 如:create table students( id int unsigned primary k ...

  8. centos7上搭建Jenkins环境

    选择在centos 上安装jdk和tomcat来搭建Jenkins 1.jdk安装 查看有没有java环境:rpm -qa | grep java 如果没有java环境的话,接着就去查找java-1. ...

  9. python之路5:常用模块

    模块简介 time & datetime模块 random os sys shutil json & pickle shelve xml处理 configparser hashlib ...

  10. npm 更改在线仓库镜像地址

    node 安装后,npm 的默认在线仓库镜像地址为: https://registry.npmjs.org/ 使用 npm get registry 命令可以获取到: 为了使用 npm 能够更快的下载 ...