聚类算法****K-means
Kmeans演示:https://www.naftaliharris.com/blog/visualizing-k-means-clustering/
介绍
K-Means算法是无监督的聚类算法,算法简单,聚类效果好,即使是在巨大的数据集上也非常容
易部署实施。正因为如此,它在很多领域都得到的成功的应用,如市场划分、机器视觉、 地质统
计学、天文学和农业等。K-Means算法有大量的变体,包括初始化优化K-Means++以及大数据
应用背景下的k-means||和Mini Batch K-Means
K-Means算法的思想很简单,对于给定的样本集,按照样本之间的距离大小,将样本
集划分为K个簇。让簇内的点尽量紧密的连在一起,而让簇间的距离尽量的大。
如果用数学表达式表示,假设簇划分为(C1 ,C2 ,…Ck ),则我们的目标是最小化平方误差E:
E
=
∑
i
=
1
k
∑
x
∈
C
i
∣
∣
x
−
μ
i
∣
∣
2
2
E=\sum_{i=1}^k\sum_{x \in C_i}||x - \mu_i||_2^2
E=i=1∑kx∈Ci∑∣∣x−μi∣∣22
其中 μi 是簇 Ci 的均值向量,有时也称为质心,表达式为
u
i
=
1
∣
C
i
∣
∑
x
∈
C
i
x
u_i = \frac{1}{|C_i|}\sum_{x\in C_i}x
ui=∣Ci∣1x∈Ci∑x
K-means算法流程
输入是样本集D={x1 ,x2 ,…xN},聚类的簇数k,最大迭代次数T
输出是簇划分C={C1 ,C2 ,…Ck }
1)从数据集D中随机选择k个样本作为初始的k个质心向量: {μ1 ,μ2 ,…,μk },将每个簇初始化为空集
• 2)对于t=1,2,…,T
a) 对于i=1,2…N,计算样本 xi 和各个质心向量 μj,j=1,2,…k的欧式距离,将 xi 划分到最近的簇中,即更新Cj=Cj∪{xi}
b) 对于j=1,2,…,k,对Cj中所有的样本点重新计算新的质心
c)如果所有的k个质心向量都没有发生变化,则转到步骤3)
3) 输出簇划分C={C1 ,C2 ,…Ck }
Kmeans算法的缺点
缺点一:聚类中心的个数K需要事先给定,但在实际中K值的选定是非常困难的,很多时候我们并不知道给定的数据集应该聚成多少个类别才最合适
缺点二:k-means算法需要随机地确定初始聚类中心,不同的初始聚类中心可能导致完全不同的聚类结果,有可能导致算法收敛很慢甚至出现聚类出错的情况
针对第一个缺点:很难在k-means算法以及其改进算法中解决,一般来说,我们会根据对数据的先验经验选择一个合适的k值,如果没有什么先验知识,则可以通过“肘方法”选择一个合适的k值
针对第二个缺点:可以通过k-means++算法来解决
肘方法
手肘法的核心指标是SSE(sum of the squared errors,误差平方和),
S
S
E
=
∑
i
=
1
k
∑
x
∈
C
i
∣
∣
x
−
μ
i
∣
∣
2
2
S
S
E
=
∑
i
=
1
k
∑
x
∈
C
i
∣
∣
x
−
μ
i
∣
∣
2
2
SSE=\sum_{i=1}^k\sum_{x \in C_i}||x - \mu_i||_2^2SSE=\sum_{i=1}^k\sum_{x \in C_i}||x - \mu_i||_2^2
SSE=i=1∑kx∈Ci∑∣∣x−μi∣∣22SSE=i=1∑kx∈Ci∑∣∣x−μi∣∣22
• 其中,Ci是第i个簇,x是Ci中的样本点,μi是Ci的质心(Ci中所有样本的均值),SSE是所有样本的聚类误差,代表了聚类效果的好坏。
手肘法的核心思想是:随着聚类数k的增大,样本划分会更加精细,每个簇的聚合程度会逐渐提高,那么误差平方和SSE自然会逐渐变小。并且,当k小于真实聚类数时,由于k的增大会大幅增加每个簇的聚合程度,故SSE的下降幅度会很大,而当k到达真实聚类数时,再增加k所得到的聚合程度回报会迅速变小,所以SSE的下降幅度会骤减,然后随着k值的继续增大而趋于平缓,也就是说SSE和k的关系图是一个手肘的形状,而这个肘部对应的k值就是数据的真实聚类数。当然,这也是该方法被称为手肘法的原因。
K-means++
K-Means++的对于初始化质心的优化策略,如下:
a) 从输入的数据点集合中随机选择一个点作为第一个聚类中心 μ1
b) 对于数据集中的每一个点xi,计算它与已选择的聚类中心中最近聚类中心的距离D
c) 选择一个新的数据点作为新的聚类中心,选择的原则是:D较大的点,被选取作为聚类中心的概率较大。
d) 重复b和c直到选择出k个聚类质心
e) 利用这k个质心来作为初始化质心去运行标准的K-Means算法
K-means++的缺点
它内在的有序性特性:下一个中心点的选择依赖于已经选择的中心点
K-means||
k-means||是k-means++的变体,k-means||在初始化中心点时对kmeans++的缺点做了规避,主要体现在不需要根据k的个数严格地寻找k个点,突破了算法在大规模数据集上的应用瓶颈,同时初始化的中心点更加健壮
解决K-Means++算法缺点而产生的一种算法;主要思路是改变每次遍历时候的取样规则,并非按照K-Means++算法每次遍历只获取一个样本,而是每次获取K个样本,重复该取样操作O(logn)次,然后再将这些抽样出来的样本聚类出K个点,最后使用这K个点作为K-Means算法的初始聚簇中心点。实践证明:一般5次重复采用就可以保证一个比较好的聚簇中心点。
Mini Batch K-Means*
• 在传统的K-Means算法中,要计算所有的样本点到所有的质心的距离。如果样本量非常大,比如达到10万以上,特征有100以上,此时用传统的K-Means算法非常的耗时,就算加上elkanK-Means优化也依旧。在大数据时代,这样的场景越来越多。此时Mini Batch K-Means应运而生。
Mini Batch,也就是用样本集中的一部分的样本来做传统的K-Means,这样可以避免样本量太大时的计算难题,算法收敛速度大大加快。当然此时的代价就是我们的聚类的精确度也会有一些降低。一般来说这个降低的幅度在可以接受的范围之内
一般是通过无放回的随机采样得到的。
为了增加算法的准确性,我们一般会多跑几次Mini Batch K-Means算法,用得到不同的随机采样集来得到聚类簇,选择其中最优的聚类簇。
评估方法——轮廓系数法
簇内不相似度:计算样本i到同簇其它样本的平均距离为ai,ai越小,表示样本i越应该被聚类到该簇,簇C中的所有样本的ai的均值被称为簇C的凝聚度。
簇间不相似度:计算样本i到其它簇Cj的所有样本的平均距离bij,bi=min{bi1 ,bi2 ,…,bik };bi越大,表示样本i越不属于其它簇。
• 轮廓系数:si值取值范围为[-1,1],越接近1表示样本i聚类越合理,越接近-1,表示样本i应该分类到另外的簇中,近似为0,表示样本i应该在边界上;所有样本的si的均值被称为聚类结果的轮廓系数。
s
(
i
)
=
b
(
i
)
−
a
(
i
)
m
a
x
{
a
(
i
)
,
b
(
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)=\frac{b(i)-a(i)}{max\{a(i),b(i)\}}\\ s(i)=\begin{cases} 1-\frac{a(i)}{b(i)},a(i)<b(i)\\ 0,a(i)>b(i)\\ \frac{b(i)}{a(i)}-1,a(i)>b(i) \end{cases}
s(i)=max{a(i),b(i)}b(i)−a(i)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)
import numpy as np
X = np.array([[1.0, 2.0], [2.0, 2.0], [6.0, 8.0], [7.0, 8.0]])
C = np.array([[1.0, 2.0], [2.0, 2.0]])
iters = 5
while iters > 0:
###计算每个样本到每个聚类中心的距离
A = []
for c in C:
a = np.sqrt(np.sum((X - c) ** 2, axis=1))
A.append(a)
A = np.array(A)
print(A)
###将样本分配的所属的聚类中心(获取最小值的下标)
minidx = np.argmin(A, axis=0)
print(minidx)
for i in range(len(C)):
a = X[minidx == i]
print('a\n', a) #找到样本
C[i] = np.mean(a, axis=0) #更新聚类中心
print(C)
print('C\n', C)
# todo: 就是C不再更新停止
print('---------------------------------')
iters -= 1
import numpy as np
from sklearn.cluster import KMeans
import matplotlib.pyplot as plt
import matplotlib
matplotlib.use('TkAgg')
X = np.array([[1, 2], [2, 2], [6, 8], [7, 8]])
keans = KMeans(n_clusters=2)
keans.fit(X)
center = keans.cluster_centers_
print(center)##簇中心
x_center = center[:, 0]
y_center = center[:, 1]
print(keans.labels_)###簇的标签
print(keans.score(X))
#
x = X[:, 0]
y = X[:, 1]
print(x)
print(y)
plt.scatter(x, y, color='blue', marker='o')
plt.plot(x_center, y_center, 'r-')
plt.show()
import pandas as pd
from sklearn.cluster import KMeans
import matplotlib.pyplot as plt
import matplotlib
matplotlib.use('TkAgg')
df = pd.read_csv('D:/save/datas/iris.data', header=None)
# print(df.head())
X = df.iloc[:, :-1]
print(X)
clf = KMeans(n_clusters=3)
clf.fit(X)
print(clf.cluster_centers_)
print(clf.labels_)
print(clf.predict(X.iloc[[-1], :]))
x = X.iloc[:, 0]
y = X.iloc[:, 1]
cx = clf.cluster_centers_[:, 0]
cy = clf.cluster_centers_[:, 1]
plt.scatter(x, y, color='gray')
plt.scatter(cx, cy, color='red')
plt.show()
import pandas as pd
from sklearn.metrics import silhouette_score
from sklearn.cluster import KMeans
import matplotlib.pyplot as plt
import matplotlib
matplotlib.use('TkAgg')
df = pd.read_csv('D:/save/datas/iris.data', header=None)
X = df.iloc[:, :-1]
sses = [] # 簇内误差平方和
S = [] # 轮廓系数
for k in range(2, 10):
kmeans = KMeans(n_clusters=k, init='k-means++' , n_init=10)
kmeans.fit(X)
inertia = kmeans.inertia_ # 簇内误差平方和
sses.append(inertia)
lables = kmeans.labels_
s = silhouette_score(X, lables)
S.append(s)
plt.figure(num=1)
plt.plot(range(2, 10), sses)
plt.title('SSE vs. Number of Clusters')
plt.xlabel('Number of Clusters (K)')
plt.ylabel('SSE')
plt.figure(num=2)
plt.plot(range(2, 10), S)
plt.title('Silhouette Score vs. Number of Clusters')
plt.xlabel('Number of Clusters (K)')
plt.ylabel('Silhouette Score')
plt.show()
import random
import numpy as np
class Kmeans():
cluster_center_ = []
labels_ = []
def __init__(self, k, iters=100):
self.k = k
self.iters = iters
def fit(self, X):
# 随机获取K个初始化质心
C = []
for i in range(self.k):
n = random.choice(list(range(X.shape[0])))
c = np.array(X.iloc[n, :])[0]
C.append(c)
C = np.array(C)
iters = self.iters
temp_labels = np.zeros_like(X.shape[0])
while iters > 0:
A = []
for c in C:
a = np.sqrt(np.sum((X - c) ** 2, axis=1))
A.append(a)
A = np.array(A)
minidx = np.argmin(A, axis=0)
for i in range(len(C)):
a = X[minidx == i]
C[i] = np.mean(a, axis=0)
if sum(temp_labels==minidx)==len(minidx):
print(f"提前终止循环,iters{iters}")
break
temp_labels = minidx
iters -= 1
self.cluster_center_ = C
self.labels_ = minidx
def predict(self, x, y):
pass
if __name__ == '__main__':
pass