Kmeans算法
介绍
- 给定一个有M个对象的数据集,构建一个具有K个簇的模型,其中k<=M。满足:
- 每个簇至少包含一个对象
- 每个对象属于且仅属于一个簇
- 将满足上述条件的K个簇称为一个合理的聚类划分
- 基本思想:对于给定的类别数目k,首先给定初始划分,通过迭代改变样本和簇的隶属关系,使得每次处理后得到的划分方式比上一次的好(总的数据集之间的距离和变小了)
算法理论
假设输入样本为 T = x 1 , x 2 , . . . , x m T=x_1,x_2,...,x_m T=x1,x2,...,xm;则算法步骤为(使用欧氏距离):
- 随机选择样本初始化的k个聚类中心, a 1 , a 2 , . . . , a k a_1,a_2,...,a_k a1,a2,...,ak;
- 对于每个样本 x i x_{i} xi,将其标记为距离类别中心 a j a_{j} aj最近的类别 j j j
- l a b e l i = arg min 1 < j < k { ∑ i = 1 n ( x i − a j ) 2 } l a b e l_{i}=\underset{1<j<k}{\arg \min }\{\sqrt{\sum_{i=1}^{n}\left(x_{i}-a_{j}\right)^{2}}\} labeli=1<j<kargmin{∑i=1n(xi−aj)2}
- 更新每个类别的中心点
a
j
a_{j}
aj为隶属该类别的所有样本的均值
μ i = 1 ∣ C i ∣ ∑ x ∈ C i x \mu_{i}=\frac{1}{|C_i|} \sum_{x \in C_i} x μi=∣Ci∣1∑x∈Cix - 重复上述两步操作,知道达到某个终止条件
- 终止条件:
- 迭代次数
- 最小平方误差MSE
- 簇中心点变化率
例子
对数据集 { ( 1 , 2 ) , ( 2 , 2 ) , ( 6 , 8 ) , ( 7 , 8 ) } \{(1,2),(2,2),(6,8),(7,8) \} {(1,2),(2,2),(6,8),(7,8)}聚成2类
- 随机选取聚类中心 C = { ( 1 , 2 ) , ( 2 , 2 ) } C=\{(1,2),(2,2)\} C={(1,2),(2,2)}
- 分别计算样本点到每个中心点的欧式距离
- 对距离从小到大进行排序,将样本点进行归类
- 各类使用样本均值更新样本中心点
- 重复2、3、4
- 当聚类中心不再变化时,停止迭代,完成聚类
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import copy
import sklearn.datasets as ds
import matplotlib.colors
from sklearn.cluster import KMeans,DBSCAN,AgglomerativeClustering
import scipy.cluster.hierarchy as sch
# v测量,轮廓系数
from sklearn.metrics import v_measure_score, silhouette_score
plt.rcParams['font.sans-serif'] = 'SimHei'
plt.rcParams['axes.unicode_minus'] = 'False'
def example1():
X = np.array([[1, 2], [2, 2], [6, 8], [7, 8]])
# 初始聚类中心
C = np.array([[1.0, 2.0], [2.0, 2.0]])
plt.scatter(X[:, 0], X[:, 1])
plt.show()
'''
1、计算所有样本点到每个中心的距离
2、比较样本点与哪个中心点距离最近,记为该簇
3、采用均值方式,更新中心位置
'''
iters = 5
x = 0
while x < 5:
iters -= 1
x += 1
# 保存每个样本点到中心的距离
B = []
C_ = copy.deepcopy(C)
for c in C:
# 计算欧氏距离
dis = np.sqrt((X - c) ** 2).sum(axis=1)
B.append(dis)
# 求样本点属于哪一个类别
min_idx = np.argmin(np.array(B), axis=0)
for i in range(len(C)):
# 更新每个中心的位置
C[i] = np.mean(X[min_idx == i], axis=0)
if np.sum(C - C_) == 0:
break
plt.scatter(X[:,0].X[:,1],c=min_idx)
plt.scatter(C[:,0].C[:,1],c='red',marker='*')
plt.legend()
plt.show()
K-means衍生算法
K-means缺点
- 缺点一:聚类中心的个数K需要事先给定,但在实际中K值的选定是非常困难的,很多时候我们并不 知道给定的数据集应该聚成多少个类别才最合适
- 缺点二:k-means算法需要随机地确定初始聚类中心,不同的初始聚类中心可能导致完全不同的聚 类结果,有可能导致算法收敛很慢甚至出现聚类出错的情况
- 针对第一个缺点:很难在k-means算法以及其改进算法中解决,一般来说,我们会根据对数据的先 验经验选择一个合适的k值,如果没有什么先验知识,则可以通过“肘方法”选择一个合适的k值
- 针对第二个缺点:可以通过k-means++算法来解决
K-means++算法
K-Means++算法和K-Means算法的区别主要在于 初始的K个中心点的选择方面,K-Means算法使用随机给定的方式,K-Means++算法采用下列步 骤给定K个初始质点:
-
从数据集中任选一个节点作为第一个聚类中心
-
对数据集中的每个点x,计算x到所有已有聚类中心点的距离和 D ( X ) D(X) D(X),基于 D ( X ) D(X) D(X)采用线性概率选择出下一个 聚类中心点(距离较远的一个点成为新增的一个聚类中心点)
-
重复步骤2直到找到k个聚类中心点
-
缺点:
由于聚类中心点选择过程中的内在有序性,在扩展方面存在着性能方面的问题(第k个聚类中 心点的选择依赖前k-1个聚类中心点的值) k-means++ 最主要的缺点在于其内在的顺序执行特性, 得到 k 个聚类中心必须遍历数据集 k 次,并且当前聚类中心的计算依赖于前面得到的所有聚类中心, 这使得算法无法并行扩展,极大地限制了算法在大规模数据集上的应用。因此有了K-means||算法
K-means与K-means++
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import copy
import sklearn.datasets as ds
import matplotlib.colors
from sklearn.cluster import KMeans,DBSCAN,AgglomerativeClustering
import scipy.cluster.hierarchy as sch
# v测量,轮廓系数
from sklearn.metrics import v_measure_score, silhouette_score
plt.rcParams['font.sans-serif'] = 'SimHei'
plt.rcParams['axes.unicode_minus'] = 'False'
#指定输出边界
def expand(a, b):
return a-1, b+1#最小值-1,最大值+1
def K_means_plus():
N = 400 # 400个样本点
centers = 4 # 4个中心
# 样本点数量,特征数,中心数,随机种子
data, y = ds.make_blobs(N, n_features=2, centers=centers, random_state=6)
# 指定四个中心的颜色,红绿蓝,粉
cm = matplotlib.colors.ListedColormap(list('rgbm'))
plt.title(u'原始数据')
plt.scatter(data[:, 0], data[:, 1], c=y, s=30, cmap=cm)
x1_min, x2_min = np.min(data, axis=0)
x1_max, x2_max = np.max(data, axis=0)
x1_min, x1_max = expand(x1_min, x1_max)
x2_min, x2_max = expand(x2_min, x2_max)
plt.xlim((x1_min, x1_max))
plt.ylim((x2_min, x2_max))
plt.grid()
plt.show()
# Kmeans的调用, 默认形式是采用K-means++进行处理的,如果使用K-means操作,就需要init='random'
cls = KMeans(n_clusters=4, init='random')
y_hat = cls.fit_predict(data)
plt.title(u'KMeans聚类')
plt.scatter(data[:, 0], data[:, 1], c=y_hat, s=30, cmap=cm)
plt.xlim((x1_min, x1_max))
plt.ylim((x2_min, x2_max))
plt.grid()
plt.show()
#K-means++
cls = KMeans(n_clusters=4, init='k-means++')
y_hat = cls.fit_predict(data)
plt.title(u'KMeans++聚类')
plt.scatter(data[:, 0], data[:, 1], c=y_hat, s=30, cmap=cm)
plt.xlim((x1_min, x1_max))
plt.ylim((x2_min, x2_max))
plt.grid()
plt.show()
K-means||
- 主要思路是改变每次遍历时候的取样规则,并非按 照K-Means++算法每次遍历只获取一个样本,而是每次获取K个样本,重复该取样操作O(logn)次, 然后再将这些抽样出来的样本聚类出K个点,最后使用这K个点作为K-Means算法的初始聚簇中心 点。
- k-means||是k-means++的变体,k-means||在初始化中心点时对kmeans++的缺点做了规避,主 要体现在不需要根据k的个数严格地寻找k个点,突破了算法在大规模数据集上的应用瓶颈
聚类算法评估
均一性、完整性
- 均一性:一个簇中只包含一个类别的样本,则满足均一性;其实也可以认为就是正确率(每个聚簇中 正确分类的样本数占该聚簇总样本数的比例和)
p = 1 k ∑ i = 1 k N ( C i = K i ) N ( K i ) \mathrm{p}=\frac{1}{k} \sum_{i=1}^{k} \frac{N\left(C_{i}=K_{i}\right)}{N\left(K_{i}\right)} p=k1i=1∑kN(Ki)N(Ci=Ki) - 完整性:同类别样本被归类到相同簇中,则满足完整性;每个聚簇中正确分类的样本数占该类型的 总样本数比例的和
r = 1 k ∑ i = 1 k N ( C i = K i ) N ( C i ) r=\frac{1}{k} \sum_{i=1}^{k} \frac{N\left(C_{i}=K_{i}\right)}{N\left(C_{i}\right)} r=k1i=1∑kN(Ci)N(Ci=Ki) - V-measure:均一性和完整性的加权平均
v β = ( 1 + β 2 ) ⋅ p r β 2 ⋅ p + r v_{\beta}=\frac{\left(1+\beta^{2}\right) \cdot \mathrm{p} r}{\beta^{2} \cdot p+r} vβ=β2⋅p+r(1+β2)⋅pr
肘部法
- 核心指标是SSE(sum of the squared errors,误差平方和)
S S E = ∑ i = 1 k ∑ x ∈ C i ∥ x − μ i ∥ 2 SSE=\sum_{i=1}^{k}{\sum_{x \in C_{i}}}\left\|x-\mu_{i}\right\|^{2} SSE=i=1∑kx∈Ci∑∥x−μi∥2
其中, C i C_i Ci是第 i i i个簇, x x x是 C i C_i Ci中的样本点, μ i μ_i μi是Ci的质心(KaTeX parse error: Expected group after '_' at position 3: Ci_̲中所有样本的均值),SSE是所有样本的 聚类误差,代表了聚类效果的好坏
- 手肘法的核心思想是:
- 随着聚类数k的增大,样本划分会更加精细,每个簇的聚合程度会逐渐提高, 那么误差平方和SSE自然会逐渐变小。
- 当k小于真实聚类数时,由于k的增大会大幅增加每个 簇的聚合程度,故SSE的下降幅度会很大
- 当k到达真实聚类数时,再增加k所得到的聚合程度回 报会迅速变小,所以SSE的下降幅度会骤减,然后随着k值的继续增大而趋于平缓,也就是说SSE和 k的关系图是一个手肘的形状,而这个肘部对应的k值就是数据的真实聚类数。
轮廓系数
- 簇内不相似度:计算样本i到同簇其它样本的平均距离为ai,ai越小,表示样本i越应 该被聚类到该簇,簇C中的所有样本的ai的均值被称为簇C的凝聚度。
- 簇间不相似度:计算样本i到其它簇C_{j}的所有样本的平均距离 b i j , b i = m i n b i 1 , b i 2 , . . . , b i k ; b i b_{ij},b_{i}=min{b_{i1},b_{i2},...,b_{ik}};b_{i} bij,bi=minbi1,bi2,...,bik;bi越大,表示样本i越不属于其它簇。
- 轮廓系数: s i s_{i} si值取值范围为[-1,1],越接近1表示样本i聚类越合理,越接近-1,表示样 本i应该分类到另外的簇中,近似为0,表示样本i应该在边界上;所有样本的si的均 值被称为聚类结果的轮廓系数。
-
s
(
i
)
=
b
(
i
)
−
a
(
i
)
max
{
a
(
i
)
,
b
(
i
)
}
s(i)=\frac{b(i)-a(i)}{\max \{a(i), b(i)\}}
s(i)=max{a(i),b(i)}b(i)−a(i)
s ( i ) = { 1 − a ( i ) b ( i ) , a ( i ) < b ( i ) 0 , a ( i ) = b ( i ) b ( i ) a ( i ) − 1 , a ( i ) > b ( i ) s(i)=\left\{\begin{array}{l}{1-\frac{a(i)}{b(i)}, \quad a(i)<b(i)} \\ {0, \quad a(i)=b(i)} \\ {\frac{b(i)}{a(i)}-1, \quad a(i)>b(i)}\end{array}\right. s(i)=⎩⎪⎨⎪⎧1−b(i)a(i),a(i)<b(i)0,a(i)=b(i)a(i)b(i)−1,a(i)>b(i)算法评估指标的sklearn使用
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import copy
import sklearn.datasets as ds
import matplotlib.colors
from sklearn.cluster import KMeans,DBSCAN,AgglomerativeClustering
import scipy.cluster.hierarchy as sch
# v测量,轮廓系数
from sklearn.metrics import v_measure_score, silhouette_score
def Evaluation():
X = np.array([[1, 2, 1], [2, 2, 1], [6, 8, 0], [7, 8, 0]]) # 第三个数值是实际标签
C = np.array([[1, 1], [2, 1]])
x = X[:, :-1] # 前两个数值作为特征
label = X[:, -1] # 最后一个数值作为标签
# 生成2个聚类
model = KMeans(n_clusters=2)
model.fit(X, C)
# 质心位置
print(model.cluster_centers_)
labels = model.labels_
# 分类结果
print(labels)
print(silhouette_score(X, labels, metric='euclidean')) # 轮廓得分(轮廓系数法,后面会提到)
print(model.inertia_) # 最小化平方误差E SSE
print(v_measure_score(label, labels)) # 均一性和完整性的加权平均
def Elbow():
data = pd.read_csv('../datas/drink.txt')
record = [] # 记录每一次生成SSE
for i in range(2, 10): # k值范围2-10
kmeans = KMeans(i)
kmeans.fit(data)
record.append(kmeans.inertia_) # inertia簇内误差平方和 SSE
plt.plot(range(2, 10), record, range(2, 10), record, marker='o', c='b')
plt.show() # k=3是最优k值点
DBSCAN密度聚类
- K-means算法原理简单,容易实现,但是对噪音和异常点比较的敏感
- 密度聚类: 只要样本点的密度大于某个阈值,则将该样本添加到最近的簇中。这类算法可以克服基于距离的算法只能发现凸聚类的缺点,可以发现任意形状的聚类,而且对噪声数据不敏感。但是计算复杂度高,计算量大
算法理论
- ε邻域(ε neighborhood,也称为Eps):给定对象在半径ε内的区域
N c ( x ) = { y ∈ X : d i s t ( x , y ) ≤ ε } N_{c}(x)=\{y \in X: d i s t(x, y) \leq \varepsilon\} Nc(x)={y∈X:dist(x,y)≤ε} - 密度(density):ε邻域中x的密度,是一个整数值,依赖于半径ε
p ( x ) = ∣ N ε ( x ) ∣ p(x)=\left|N_{\varepsilon}(x)\right| p(x)=∣Nε(x)∣ - MinPts定义核心点时的阈值,也简记为M
- 核心点(core point):如果p(x)>=M,那么称x为X的核心点;记由X中所有核心点构成的集合为 X c X_c Xc, 并记 X n c = { X ∣ x ̸ X c } X_{nc}=\{X|x \not X_{c}\} Xnc={X∣xXc}表示由X中所有非核心点构成的集合。直白来讲,核心点对应于稠密区域内部的点。
- 边界点(border point): 如果非核心点x的ε邻域中存在核心点,那么认为x为X的边界点。由X中所有 的边界点构成的集合为Xbd。直白来将,边界点对应稠密区域边缘的点。
x ∈ X n c ∗ ∃ y ∈ X ; y ∈ N ε ( x ) ∩ X c x \in X_{n c^{*}} \exists y \in X ; \quad y \in N_{\varepsilon}(x) \cap X_{c} x∈Xnc∗∃y∈X;y∈Nε(x)∩Xc - 噪音点(noise point):集合中除了边界点和核心点之外的点都是噪音点,所有噪音点组成的集合叫 做Xnoi;直白来讲,噪音点对应稀疏区域的点。
X n o l = X \ ( X c ∪ X n t ) X_{n o l}=X \backslash\left(X_{c} \cup X_{n t}\right) Xnol=X\(Xc∪Xnt) - 簇(cluster):一个基于密度的簇是最大的密度相连对象的集合C;满足以下两个条件:
- Maximality:若x属于C,而且y是从x密度可达的,那么y也属于C
- Connectivity:若x属于C,y也属于C,则x和y是密度相连的
- 算法流程:
- 如果一个点x的ε邻域包含超过m个对象,则创建一个x作为核心对象的新簇;
- 寻找并合并与核心对象直接密度可达的对象;
- 没有新点可以更新簇的时候,算法结束
- 算法特征描述:
- 每个簇至少包含一个核心对象
- 非核心对象可以是簇的一部分,构成簇的边缘
- 包含过少对象的簇被认为是噪声
−
−
−
−
−
−
−
−
−
−
----------
−−−−−−−−−−
简单的说就是
- 以A点位圆心,以ε为半径画圆,如果这个圆包含的样本数大于阈值m,那么A就称为核心点。
- 对圆内的点进行循环画圆。以 A 1 A_1 A1为圆心,ε为半径画圆,如果圆内样本数大于m,则 A 1 A_1 A1也是核心点,以此类推
- 当画到B或者C点,圆内个数不足m个,那么B点和C点称为边界点
- 当边界点画完后,没接触到的点,即N点,称为噪音点
− − − − − − − − − − ---------- −−−−−−−−−−
优缺点
- 优点:
- 不需要事先给定cluster的数目
- 可以发现任意形状的cluster
- 能够找出数据中的噪音,且对噪音不敏感
- 算法只需要两个输入参数
- 聚类结果几乎不依赖节点的遍历顺
- 缺点:
- DBSCAN算法聚类效果依赖距离公式的选取,最常用的距离公式为欧几里得距离。但是对于高维数据,由于维数太多,距离的度量已变得不是那么重要
- DBSCAN算法不适合数据集中密度差异很小的情况
sklearn实现,K-means与DBSCAN对比
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import copy
import sklearn.datasets as ds
import matplotlib.colors
from sklearn.cluster import KMeans,DBSCAN,AgglomerativeClustering
import scipy.cluster.hierarchy as sch
# v测量,轮廓系数
from sklearn.metrics import v_measure_score, silhouette_score
plt.rcParams['font.sans-serif'] = 'SimHei'
plt.rcParams['axes.unicode_minus'] = 'False'
def Dbscan():
X1, y1 = ds.make_circles(n_samples=5000, factor=.6, noise=.05)
X2, y2 = ds.make_blobs(n_samples=1000, n_features=2, centers=[[1.2, 1.2]],
cluster_std=[[.1]], random_state=9)
X = np.concatenate((X1, X2))
plt.scatter(X[:, 0], X[:, 1], marker='o')
plt.title('原始数据样本点分布')
plt.show()
y_pred = KMeans(n_clusters=3, random_state=9).fit_predict(X)
plt.scatter(X[:, 0], X[:, 1], c=y_pred)
plt.title('K-means算法进行聚类处理')
plt.show()
y_pred = DBSCAN().fit_predict(X)
plt.scatter(X[:, 0], X[:, 1], c=y_pred)
plt.title('默认参数密度聚类')
plt.show()
y_pred = DBSCAN(eps=0.08, min_samples=3).fit_predict(X) # eps = 0.08 半径, min_samples=3样本数量
plt.scatter(X[:, 0], X[:, 1], c=y_pred)
plt.title('设置参数的密度聚类')
plt.show()
层次聚类简介
概述
- 层次聚类方法对给定的数据集进行层次的分解,直到满足某种条件为止,传统的层次聚类算法主要 分为两大类算法:
- 凝聚的层次聚类:AGNES算法(AGglomerative NESting)==>采用自底向上的策略。最初将每个对象作 为一个簇,然后这些簇根据某些准则被一步一步合并,两个簇间的距离可以由这两个不同簇中距离最近的 数据点的相似度来确定;聚类的合并过程反复进行直到所有的对象满足簇数目。
- 分裂的层次聚类:DIANA算法(DIvisive ANALysis)==>采用自顶向下的策略。首先将所有对象置于一个 簇中,然后按照某种既定的规则逐渐细分为越来越小的簇(比如最大的欧式距离),直到达到某个终结条件 (簇数目或者簇距离达到阈值)。
优缺点
简单,理解容易
合并点/分裂点选择不太容易
合并/分类的操作不能进行撤销
大数据集不太适合
执行效率较低O(t*n2),t为迭代次数,n为样本点数
优化算法
- BIRCH算法(平衡迭代削减聚类法):聚类特征使用3元组进行一个簇的相关信息,通过构建满足分枝 因子和簇直径限制的聚类特征树来求聚类,聚类特征树其实是一个具有两个参数分枝因子和类直径 的高度平衡树;分枝因子规定了树的每个节点的子女的最多个数,而类直径体现了对这一类点的距 离范围;非叶子节点为它子女的最大特征值;聚类特征树的构建可以是动态过程的,可以随时根据 数据对模型进行更新操作。
- 适合大规模数据集,线性效率;
- 只适合分布呈凸形或者球形的数据集、需要给定聚类个数和簇之间的相关参数;
- CURE算法(使用代表点的聚类法):该算法先把每个数据点看成一类,然后合并距离最近的类直至类 个数为所要求的个数为止。但是和AGNES算法的区别是:取消了使用所有点或用中心点+距离来表 示一个类,而是从每个类中抽取固定数量、分布较好的点作为此类的代表点,并将这些代表点乘以 一个适当的收缩因子,使它们更加靠近类中心点。代表点的收缩特性可以调整模型可以匹配那些非 球形的场景,而且收缩因子的使用可以减少噪音对聚类的影响
- 能够处理非球形分布的应用场景
- 采用随机抽样和分区的方式可以提高算法的执行效率