聚类分析之K-means算法
文章目录
聚类分析是一类非常经典的无监督学习算法。聚类分析就是根据样本内部样本“子集”的之间的 特征找到相似度最接近的一堆堆“子集”,将相似度最接近的样本各自分为一类。
一.距离度量和相似度度量方法
根据上面的阐述,这个特征找得好、找的合适,我聚类的效果也就可能更好,那么一般来说这些特征是:相似度或者距离,但是一般来说这里的距离指的是样本距离。距离一般采用闵可夫斯基和马氏距离,而相似度则经常采用相关系数和夹角余弦。
1.距离度量
对于m维度的两个样本:
x i = ( x 1 i , x 2 i , . . . , x 3 m ) x j = ( x 1 j , x 2 j , . . . , x 3 m ) x_i=(x_{1i},x_{2i},...,x_{3m}) \\ x_j=(x_{1j},x_{2j},...,x_{3m}) xi=(x1i,x2i,...,x3m)xj=(x1j,x2j,...,x3m)
解释一下:xi,xj是m维度实数样本空间中的两个样本也就是说:
1 ≤ i , j ≤ n u m b e r s a m p l e a n d x i , x j ⊂ R m 1\le i,j \le number_{sample}\quad and\quad x_i,x_j\sub R^m 1≤i,j≤numbersampleandxi,xj⊂Rm
那么**闵可夫斯基距离(Minkowski distance)**定义为:
d i j = ( ∑ k = 1 m ∣ x k i − x k j ∣ p ) 1 p , ( d i j 为 x i , x j 这 两 个 样 本 之 间 距 离 ) d_{ij}=(\sum_{k=1}^m|x_{ki}-x_{kj}|^p)^{\frac{1}{p}},(d_{ij}为x_i,x_j这两个样本之间距离) dij=(k=1∑m∣xki−xkj∣p)p1,(dij为xi,xj这两个样本之间距离)
用中文解释一下就是:对于这个样本在每个维度上的表现(特征值),进行对应相减并取p次方后相加,求得的和再取1/p次方。至于这个p,其实闵可夫斯基公式具有普适性,也就是说对于不同维度数据集有不同表达,但是都可以写成这样的一个通式。
比如说:当p=1、2时,分别退化为曼哈顿距离和欧氏距离:
d i j = ∑ i = 1 m ∣ x k i − x k j ∣ d i j = ( ∑ i = 1 m ∣ x k i − x k j ∣ 2 ) 1 2 d_{ij}=\sum_{i=1}^{m}|x_{ki}-x_{kj}| \\ d_{ij}=(\sum_{i=1}^{m}|x_{ki}-x_{kj}|^2)^{\frac{1}{2}} dij=i=1∑m∣xki−xkj∣dij=(i=1∑m∣xki−xkj∣2)21
如果p=无穷大时,则进化为:切比雪夫距离(Chebyshev distance):
d
i
j
=
m
a
x
∣
x
k
i
−
x
k
j
∣
d_{ij}=max|x_{ki}-x_{kj}|
dij=max∣xki−xkj∣
下面介绍另外一种距离——马氏距离(Mahalanobis distance)——马哈拉诺比斯距离,也是一种衡量各个特征之间相关性的聚类度量方式,如果我给定一个样本集合:
X
=
(
x
i
j
)
m
×
n
X=(x_{ij})_{m\times n}
X=(xij)m×n
可以姑且看作n个样本m个特征。其计算公式如下:
d
i
j
=
[
(
x
i
−
x
j
)
T
S
−
1
(
x
i
−
x
j
)
]
1
2
d_{ij}=[(x_i-x_j)^T S^{-1} (x_i-x_j)]^{\frac{1}{2}}
dij=[(xi−xj)TS−1(xi−xj)]21
其中的S为该样本集合的协方差矩阵:
其中的c是对于每一个维度(列)而言,用每一列中每一个值分别减去该列的均值,再进行如下计算:
便可以得到协方差矩阵,其中cov是协方差计算。
2.相似度
同样的,相似度也可以作为聚类指标。常用的就包括两类——相关系数和夹角余弦。
相关系数越接近1,那么两个样本就越相似;越接近于0,那么两个样本越不相似。
相关系数公式如下:
r i j = ∑ m k = 1 ( x k i − x i e ) ( x k j − x j e ) [ ∑ k = 1 m ( x k i − x i e ) 2 ∑ k = 1 m ( x k j − x j e ) 2 ] 1 2 ( x e 指 的 是 均 值 ) r_{ij}=\frac{ \sum_{m}^{k=1}(x_{ki}-x_{i}^e)(x_{kj}-x_j^e) }{ [ \sum_{k=1}^m(x_{ki}-{x_{i}^e})^2 \sum_{k=1}^{m}(x_{kj}-{x_{j}^e})^2 ]^\frac{1}{2} }(x^e指的是均值) rij=[∑k=1m(xki−xie)2∑k=1m(xkj−xje)2]21∑mk=1(xki−xie)(xkj−xje)(xe指的是均值)
中文翻译一下就是:先用一个样本在一个维度上的特征值减去该样本的所有特征值均值乘以另一个样本在该维度上的特征值减去这个样本的特征值均值,将其作为分子。然后分母就是将这两个样本进行分别计算各个维度特征值与均值误差的平方,各自相加,最后将两个样本分别计算的结果相乘。
夹角余弦也是度量两个样本相似度的方法之一:
A
C
i
j
=
∑
k
=
1
m
x
k
i
x
k
j
[
∑
k
=
1
m
x
k
i
2
∑
k
=
1
m
x
k
j
2
]
1
2
AC_{ij}=\frac{ \sum_{k=1}^m x_{ki}x_{kj} }{ [\sum_{k=1}^{m}x_{ki}^2 \sum_{k=1}^{m}x_{kj}^2]^\frac{1}{2} }
ACij=[∑k=1mxki2∑k=1mxkj2]21∑k=1mxkixkj
中文翻译一下:
将两个样本各个维度特征值相乘后累加得到分子,然后分母就是各自特征值的平方和,然后相乘。
二.K-means算法原理
import numpy as np
import matplotlib.pyplot as plt
from tqdm import tqdm
了解了评判指标后,我们就可以开始了解k-means算法原理了。
1.选取度量方法
我们选取欧氏距离作为我们的度量方法:
d i j = ∑ k = 1 m ( x k i − x k j ) 2 d_{ij}=\sum_{k=1}^{m}(x_{ki}-x_{kj})^2 dij=k=1∑m(xki−xkj)2
def get_distance(xi,xj):
# 使用欧式距离
"""
xi:第一个样本
xj:另一个样本
xi和xj都要有相同形状大小,(dims,)即一个长为维度数的向量。
输出:两个样本之间的距离
"""
distance = 0
for i in range(len(xi)):
distance += pow( (xi[i]-xj[i]), 2 )
distance = np.sqrt(distance)
return distance
2.定义损失函数
我们定义样本与其所属的类别中心的距离总和为最终损失函数:
l
o
s
s
(
C
)
=
∑
i
=
1
k
∣
∣
x
i
−
x
l
∣
∣
2
loss(C)=\sum_{i=1}^{k}||x_i-x_l||^2
loss(C)=i=1∑k∣∣xi−xl∣∣2
也就是对于每一个类中心:
x l x_l xl
用集合形式表示所有的质心为:
X
l
=
{
x
1
l
,
x
2
l
,
x
3
l
,
.
.
.
x
n
l
}
X_l=\{x_{1l},x_{2l},x_{3l},...x_{nl}\}
Xl={x1l,x2l,x3l,...xnl}
3.初始化质心
在第一次迭代时,随机选取k个值作为k个类别的质心。
M ( 0 ) = { m 1 ( 0 ) , m 2 ( 0 ) , m 3 ( 0 ) , . . . , m n ( 0 ) } M^{(0)}=\{m^{(0)}_1,m^{(0)}_2,m^{(0)}_3,...,m^{(0)}_n\} M(0)={m1(0),m2(0),m3(0),...,mn(0)}
# 质心初始化函数
def center_init(X,k):
"""
X:是一个(num_sample,dims)的样本矩阵
k:定义是几折k-means算法
输出:质心矩阵(k折(个)长为m的特征向量)
"""
n,m = X.shape[0],X.shape[1]
centers = np.zeros((k,m))
for i in range(k):
# 随机从样本中选取7个实例(样本记录)作为k个cluster的质心
center = X[np.random.choice(range(0,n))]
centers[i] = center
return centers
4.按照样本到质心的距离进行聚类
计算每一个样本到每一个质心的距离,将这个样本划分为离它最近的一个类。
# 根据当前的样本,为单个样本添加它
# 应该属于的cluster的类别索引下标
def get_sample_cluster_idx(x, centers):
"""
x:单个样本实例(记录)
centers:k个cluster的质心记录
输出:
样本的类别索引下标
"""
closest_i,closest_dist = 0,float('inf') # 初始化最近的索引和距离
# 遍历每一个(k个中)质心对于单个样本的距离
for i,center in enumerate(centers):
distance = get_distance(x, center) # x,center's shape=(dimes,)
if distance < closest_dist:
closest_i = i # 索引下标更新
closest_dist = distance
return closest_i # 返回单个样本所属簇下标
# 为每一个簇定义其类别标签
def build_cluster(centers, k, X):
"""
centers:当前epoch的质心
k:多少个簇
X:样本矩阵
输出:
簇列表(shape=(k,-1))——有k个簇列表,每个簇列表存储的
是样本序号,表示这个样本序号i代表的样本属于第i类簇。
"""
clusters = [[] for _ in range(k)]
# 枚举样本下标和样本
for x_i, x in enumerate(X):
center_i = get_sample_cluster_idx(x, centers)
# 将当前的样本序号添加到应该属于的簇列表
clusters[center_i].append(x_i)
return clusters
5.更新质心
对聚类的结果,也就是k个类别,分别计算k个类别的均值并作为新的质心。
M e p o c h = { m 1 e p o c h , m 2 e p o c h , m 3 e p o c h , m 4 e p o c h , . . . , m n e p o c h } M^{epoch}=\{m^{epoch}_1,m^{epoch}_2,m^{epoch}_3,m^{epoch}_4,...,m^{epoch}_n\} Mepoch={m1epoch,m2epoch,m3epoch,m4epoch,...,mnepoch}
# 计算当前的质心并更新
def update_centers(clusters, k, X):
"""
cluster:k个簇列表集合
k:质心个数
X:样本矩阵
输出:
更新的质心
"""
m = X.shape[1] # 特征数
centers = np.zeros((k, m)) # (dims, 特征数)
# 遍历当前的簇列表
for i,cluster in enumerate(clusters):
center = np.mean(X[cluster], axis=0)
centers[i] = center
return centers
# 获取每个样本所属的聚类类别
def get_cluster_label(clusters, X):
"""
clusters:当前的簇列表
X:样本矩阵
"""
y_pred = np.zeros(X.shape[0])
for cluster_i, cluster in enumerate(clusters):
for sample_i in cluster:
y_pred[sample_i] = cluster_i
return y_pred
6.继续迭代 or 收敛后停止
重复4-5步操作,直到聚类后的loss达到了指定要求,或者是其他要求、情况,eg:模型收敛,那么可以停止训练并输出结果;否则就进行下一步的迭代训练。
# K-means 算法封装
def kmeans(X, k, epoches):
"""
X:训练的numpy.ndarray数组
k:质心个数
epoches:迭代次数
"""
# 1.初始化质心
centers = center_init(X,k)
for epoch in tqdm(range(epoches), ncols=150, colour='blue'):
# 2.根据当前质心进行聚类
clusters = build_cluster(centers, k, X)
# 保存当前质心
cur_centers = centers
# 3.更新质心
centers = update_centers(clusters, k, X)
# 4.判断是否发生变化——是否已经收敛了
diff = centers - cur_centers
if not diff.any():
break
return get_cluster_label(clusters, X)
一个2折k-means聚类分析.(基于(1000,2)的随机随机数矩阵)