Kmeans是一种划分聚类的方法,基本K-Means算法的思想很简单,事先确定常数K,常数K意味着最终的聚类类别数,首先随机选定初始点为质心,并通过计算每一个样本与质心之间的相似度(这里为欧式距离),将样本点归到最相似的类中,接着,重新计算每个类的质心(即为类中心),重复这样的过程,直到质心不再改变,最终就确定了每个样本所属的类别以及每个类的质心。
由于每次都要计算所有的样本与每一个质心之间的相似度,故在大规模的数据集上,K-Means算法的收敛速度比较慢。
(8). 在 Kmeans 中,各个样本点只属于与其相似度最高的那个 cluster ,这实际上是一种 hard clustering 。
针对Kmeans算法的缺点,很多前辈提出了一些改进的算法。例如 K-modes 算法,实现对离散数据的快速聚类,保留了Kmeans算法的效率同时将Kmeans的应用范围扩大到离散数据。还有K-Prototype算法,可以对离散与数值属性两种混合的数据进行聚类,在K-prototype中定义了一个对数值与离散属性都计算的相异性度量标准。当然还有其它的一些算法,这里我 就不一一列举了。
Kmeans 与 GMM 更像是一种 top-down 的思想,它们首先要解决的问题是,确定 cluster 数量,也就是 k 的取值。在确定了 k 后,再来进行数据的聚类。而 hierarchical clustering 则是一种 bottom-up 的形式,先有数据,然后通过不断选取最相似的数据进行聚类。
from sklearn.cluster import KMeans
import numpy as np
import matplotlib.pyplot as plt
X = np.array([[15,17],[12,18],[14,15],[13,16],[12,15],
[16,12],[4,6],[5,8],[5,3],[7,4],[7,2],[6,5]])
y_pred = KMeans(n_clusters=2, random_state=0).fit_predict(X)
#方法fit_predict的作用是计算聚类中心,并为输入的数据加上分类标签
plt.figure(figsize=(20, 20))
color = ("red", "green")
colors=np.array(color)[y_pred]
plt.scatter(X[:, 0], X[:, 1], c=colors)
plt.show()
结果如下:
图像压缩领域,正如sklearn官方文档中对颐和园图片的压缩,针对含有9万多种颜色的图片,定义压缩后颜色种类数为64,用kmeans跑过一遍后,得到了64种颜色的簇,而图像的整体质量没有明显的变化。
开发原因: 弥补层次聚类算法和划分式聚类算法往往只能发现凸型的聚类簇的缺陷。
核心思想: 只要一个区域中的点的密度大过某个阈值,就把它加到与之相近的聚类中去。
两个关键词:稠密样本点、低密度区域(noise)
凸型的聚类簇:基于距离的聚类因为算法的原因,最后实现的聚类通常是“类圆形”的,一旦面对具有复杂形状的簇的时候,此类方法通常会无法得到满意的结果。这种时候,基于密度的聚类方法就能有效地避免这种窘境。 与K-means比较起来,你不必输入你要划分的聚类个数,也就是说,你将不受聚类数目的限制;在需要时可以输入过滤噪声的参数;
这类算法认为,在整个样本空间点中,各类目标簇是由一群稠密样本点组成的,,而这些稠密样本点被低密度区域分割,我们通常把这类低密度区域称为噪声,而算法的目的就是要过滤低密度区域,发现稠密样本点。
密度的定义:传统基于中心的密度定义为:
数据集中特定点的密度通过该点Eps半径之内的点计数(包括本身)来估计。
核心点(core point) :在半径Eps内含有超过MinPts数目的点,则该点为核心点,这些点都是在簇内的
边界点(border point):在半径Eps内点的数量小于MinPts,但是在核心点的邻居,即在某个核心点的邻域内。
噪音点(noise point):任何不是核心点或边界点的点.
MinPts:给定点在E领域内成为核心对象的最小领域点数 ,简单说就是核心点eps邻域内点数量的最小值。
下面两张图可以更好理解相关的定义。
直接密度可达:给定一个对象集合D,如果p在q的Eps邻域内,而q是一个核心对象,则称对象p 从对象q出发时是直接密度可达的(directly density-reachable)。
密度可达:如果存在一个对象链 p1,p2...pn, p1=q, pn=p,pi+1是从 pi关于Eps和MinPts直接密度可达的,则对象p是从对象q关于Eps和MinPts密度可达的(density-reachable)
密度相连:如果存在对象O∈D,使对象p和q都是从O关于Eps和MinPts密度可达的,那么对象p到q是关于Eps和MinPts密度相连的(density-connected)。
根据以上概念知道:由于有标记的各点M、P、O和R的Eps近邻均包含3个以上的点,因此它们都是核对象(核心点);M是从P“直接密度可达”;而Q则是从M“直接密度可达”;基于上述结果,Q是从P“密度可达”;但P从Q无法“密度可达”(非对称)。类似地,S和R从O是“密度可达”的;O、R和S均是“密度相连”的.
当没有新的点添加到任何簇时,该过程结束.
伪代码:
输入:n个对象的数据集,半径Eps,阈值MinPts
输出:所有生成的簇
REPEAT
从数据库选取未处理的点:
If :该点是核心点 then:找出它所有直接密度可达的点,形成一个簇
Else:该点是非核心点 ,寻找下一点。
Until:所有点都被处理
优点:对噪音不敏感、可以处理不同形状和大小的数据
不足:
1.对输入参数敏感
确定参数eps和MinPts比较困难,一般由用户在算法运行前指定这两个参数的大小,若选择不当,往往会影响聚类的质量。
2.难以发现密度相差较大的簇
由于参数eps和MinPts是全局唯一的,因此数据集中所有对象的邻域大小是一致的。 当数据集密度不均匀时,若根据高密簇选取较小的eps值,那么属于低密簇的对象的邻域中的数据点数将可能要小于MinPts,那么这些对象将被认为是边界点,从而不被用于所在类的进一步扩展,结果造成低密簇被划分成多个性质相似的簇。若根据低密簇选取较大的eps值,那么离得较近且高密的簇很可能被合并为一个簇。由此可知选取一个合适的eps值是比较困难的。因为参数eps和MinPts全局唯一,导致算法只能发现密度近似的簇
3.数据量增大时,要求较大的内存支持,I/O消耗大。
由于DBSCAN算法进行聚类时需要把所有数据都载入内存,因此在数据量很庞大且没有任何预处理的情况下对主存要求较高。
# -*- coding: utf-8 -*-
import numpy as np
import matplotlib.pyplot as plt
import math
import time
UNCLASSIFIED = False
NOISE = 0
def loadDataSet(fileName, splitChar='\t'):
"""
输入:文件名
输出:数据集
描述:从文件读入数据集
"""
dataSet = []
with open(fileName) as fr:
for line in fr.readlines():
curline = line.strip().split(splitChar)
fltline = list(map(float, curline))
dataSet.append(fltline)
return dataSet
def dist(a, b):
"""
输入:向量A, 向量B
输出:两个向量的欧式距离
"""
return math.sqrt(np.power(a - b, 2).sum())
def eps_neighbor(a, b, eps):
"""
输入:向量A, 向量B
输出:是否在eps范围内
"""
return dist(a, b) < eps
def region_query(data, pointId, eps):
"""
输入:数据集, 查询点id, 半径大小
输出:在eps范围内的点的id
"""
nPoints = data.shape[1]
seeds = []
for i in range(nPoints):
if eps_neighbor(data[:, pointId], data[:, i], eps):
seeds.append(i)
return seeds
def expand_cluster(data, clusterResult, pointId, clusterId, eps, minPts):
"""
输入:数据集, 分类结果, 待分类点id, 簇id, 半径大小, 最小点个数
输出:能否成功分类
"""
seeds = region_query(data, pointId, eps)
if len(seeds) < minPts: # 不满足minPts条件的为噪声点
clusterResult[pointId] = NOISE
return False
else:
clusterResult[pointId] = clusterId # 划分到该簇
for seedId in seeds:
clusterResult[seedId] = clusterId
while len(seeds) > 0: # 持续扩张
currentPoint = seeds[0]
queryResults = region_query(data, currentPoint, eps)
if len(queryResults) >= minPts:
for i in range(len(queryResults)):
resultPoint = queryResults[i]
if clusterResult[resultPoint] == UNCLASSIFIED:
seeds.append(resultPoint)
clusterResult[resultPoint] = clusterId
elif clusterResult[resultPoint] == NOISE:
clusterResult[resultPoint] = clusterId
seeds = seeds[1:]
return True
def dbscan(data, eps, minPts):
"""
输入:数据集, 半径大小, 最小点个数
输出:分类簇id
"""
clusterId = 1
nPoints = data.shape[1]
clusterResult = [UNCLASSIFIED] * nPoints
for pointId in range(nPoints):
point = data[:, pointId]
if clusterResult[pointId] == UNCLASSIFIED:
if expand_cluster(data, clusterResult, pointId, clusterId, eps, minPts):
clusterId = clusterId + 1
return clusterResult, clusterId - 1
def plotFeature(data, clusters, clusterNum):
nPoints = data.shape[1]
matClusters = np.mat(clusters).transpose()
fig = plt.figure()
scatterColors = ['black', 'blue', 'green', 'yellow', 'red', 'purple', 'orange', 'brown']
ax = fig.add_subplot(111)
for i in range(clusterNum + 1):
colorSytle = scatterColors[i % len(scatterColors)]
subCluster = data[:, np.nonzero(matClusters[:, 0].A == i)]
ax.scatter(subCluster[0, :].flatten().A[0], subCluster[1, :].flatten().A[0], c=colorSytle, s=50)
def main():
dataSet = loadDataSet('788points.txt', splitChar=',')
dataSet = np.mat(dataSet).transpose()
# print(dataSet)
clusters, clusterNum = dbscan(dataSet, 2, 15)
print("cluster Numbers = ", clusterNum)
# print(clusters)
plotFeature(dataSet, clusters, clusterNum)
if __name__ == '__main__':
start = time.clock()
main()
end = time.clock()
print('finish all in %s' % str(end - start))
plt.show()
结果如下: