#导库
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_samples, silhouette_score
import matplotlib.pyplot as plt
import matplotlib.cm as cm #colormap
import numpy as np
import pandas as pd
n_clusters = 4
fig, (ax1, ax2) = plt.subplots(1, 2) #分成2个布
fig.set_size_inches(18,7)
ax1.set_xlim([-0.1, 1])
ax1.set_ylim([0, X.shape[0] + (n_clusters + 1) * 10])
clusterer = KMeans(n_clusters=n_clusters, random_state=10).fit(X)
cluster_labels = clusterer.labels_
silhouette_avg = silhouette_score(X, cluster_labels)
print("For n_clusters =", n_clusters,
"The average silhouette_score is :", silhouette_avg)
sample_silhouette_values = silhouette_samples(X, cluster_labels)
y_lower = 10
for i in range(n_clusters):
ith_cluster_silhouette_values = sample_silhouette_values[cluster_labels == i]
ith_cluster_silhouette_values.sort()
size_cluster_i = ith_cluster_silhouette_values.shape[0]
y_upper = y_lower + size_cluster_i
color = cm.nipy_spectral(float(i)/n_clusters)
ax1.fill_betweenx(np.arange(y_lower, y_upper)
,ith_cluster_silhouette_values
,facecolor=color
,alpha=0.7
)
ax1.text(-0.05
, y_lower + 0.5 * size_cluster_i
, str(i))
y_lower = y_upper + 10
ax1.set_title("The silhouette plot for the various clusters.")
ax1.set_xlabel("The silhouette coefficient values")
ax1.set_ylabel("Cluster label")
ax1.axvline(x=silhouette_avg, color="red", linestyle="--")
ax1.set_yticks([])
ax1.set_xticks([-0.1, 0, 0.2, 0.4, 0.6, 0.8, 1])
colors = cm.nipy_spectral(cluster_labels.astype(float) / n_clusters)
ax2.scatter(X[:, 0], X[:, 1]
,marker='o'
,s=8
,c=colors
)
centers = clusterer.cluster_centers_
# Draw white circles at cluster centers
ax2.scatter(centers[:, 0], centers[:, 1], marker='x',
c="red", alpha=1, s=200)
ax2.set_title("The visualization of the clustered data.")
ax2.set_xlabel("Feature space for the 1st feature")
ax2.set_ylabel("Feature space for the 2nd feature")
plt.suptitle(("Silhouette analysis for KMeans clustering on sample data"
"with n_clusters = %d" % n_clusters),
fontsize=14, fontweight='bold')
plt.show()
将上述过程包装成一个循环,可以得到:
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_samples, silhouette_score
import matplotlib.pyplot as plt
import matplotlib.cm as cm
import numpy as np
for n_clusters in [2,3,4,5,6,7]:
n_clusters = n_clusters
fig, (ax1, ax2) = plt.subplots(1, 2)
fig.set_size_inches(18, 7)
ax1.set_xlim([-0.1, 1])
ax1.set_ylim([0, X.shape[0] + (n_clusters + 1) * 10])
clusterer = KMeans(n_clusters=n_clusters, random_state=10).fit(X)
cluster_labels = clusterer.labels_
silhouette_avg = silhouette_score(X, cluster_labels)
print("For n_clusters =", n_clusters,
"The average silhouette_score is :", silhouette_avg)
sample_silhouette_values = silhouette_samples(X, cluster_labels)
y_lower = 10
for i in range(n_clusters):
ith_cluster_silhouette_values = sample_silhouette_values[cluster_labels == i]
ith_cluster_silhouette_values.sort()
size_cluster_i = ith_cluster_silhouette_values.shape[0]
y_upper = y_lower + size_cluster_i
color = cm.nipy_spectral(float(i)/n_clusters)
ax1.fill_betweenx(np.arange(y_lower, y_upper)
,ith_cluster_silhouette_values
,facecolor=color
,alpha=0.7
)
ax1.text(-0.05
, y_lower + 0.5 * size_cluster_i
, str(i))
y_lower = y_upper + 10
ax1.set_title("The silhouette plot for the various clusters.")
ax1.set_xlabel("The silhouette coefficient values")
ax1.set_ylabel("Cluster label")
ax1.axvline(x=silhouette_avg, color="red", linestyle="--")
ax1.set_yticks([])
ax1.set_xticks([-0.1, 0, 0.2, 0.4, 0.6, 0.8, 1])
colors = cm.nipy_spectral(cluster_labels.astype(float) / n_clusters)
ax2.scatter(X[:, 0], X[:, 1]
,marker='o'
,s=8
,c=colors
)
centers = clusterer.cluster_centers_
# Draw white circles at cluster centers
ax2.scatter(centers[:, 0], centers[:, 1], marker='x',
c="red", alpha=1, s=200)
ax2.set_title("The visualization of the clustered data.")
ax2.set_xlabel("Feature space for the 1st feature")
ax2.set_ylabel("Feature space for the 2nd feature")
plt.suptitle(("Silhouette analysis for KMeans clustering on sample data "
"with n_clusters = %d" % n_clusters),
fontsize=14, fontweight='bold')
plt.show()
3.2
重要参数
init & random_state & n_init
:初始质心怎么放好
?
在
K-Means
中有一个重要的环节,就是放置初始质心。如果有足够的时间,
K-means
一定会收敛,但
Inertia可能收敛到局部最小值。是否能够收敛到真正的最小值很大程度上取决于质心的初始化。
init就是用来帮助我们决定初始化方式的参数。
初始质心放置的位置不同,聚类的结果很可能也会不一样,一个好的质心选择可以让
K-Means避免更多的计算,让算法收敛稳定且更快。在之前讲解初始质心的放置时,我们是使用
”
随机
“
的方法在样本点中抽取
k个样本作为初始质心,这种方法显然不符合
”
稳定且更快
“
的需求。为此,我们可以使用
random_state参数来控制每次生成的初始质心都在相同位置,甚至可以画学习曲线来确定最优的
random_state
是哪个整数。
一个
random_state
对应一个质心随机初始化的随机数种子。如果不指定随机数种子,则
sklearn
中的K-means并不会只选择一个随机模式扔出结果,而会在每个随机数种子下运行多次,并使用结果最好的一个随机数种子来作为初始质心。我们可以使用参数
n_init
来选择,每个随机数种子下运行的次数。这个参数不常用到,默认
10次,如果我们希望运行的结果更加精确,那我们可以增加这个参数
n_init
的值来增加每个随机数种子下运行的次数。
然而这种方法依然是基于随机性的。
为了优化选择初始质心的方法,
2007
年
Arthur, David, and Sergei Vassilvitskii
三人发表了论文
“k-means++: The
advantages of careful seeding”
,他们开发了
”k-means ++“初始化方案,使得初始质心(通常)彼此远离,以此来引导出比随机初始化更可靠的结果。在
sklearn
中,我们使用参数
init ='k-means ++'
来选择使用
k-means ++作为质心初始化的方案。通常来说,我建议保留默认的
"k-means++"
的方法。
init
:可输入
"k-means++"
,
"random"
或者一个
n
维数组。这是初始化质心的方法,默认
"k-means++"
。输入"k-means++"
:一种为
K
均值聚类选择初始聚类中心的聪明的办法,以加速收敛。如果输入了
n维数组,数组的形状应该是
(n_clusters
,
n_features)
并给出初始质心。
random_state
:控制每次质心随机初始化的随机数种子
n_init
:整数,默认
10
,使用不同的质心随机初始化的种子来运行
k-means
算法的次数。最终结果会是基于Inertia来计算的
n_init
次连续运行后的最佳输出
X.shape
y.shape
plus = KMeans(n_clusters = 10).fit(X)
plus.n_iter_ #12
random = KMeans(n_clusters = 10,init="random",random_state=420).fit(X)
random.n_iter_ #19
3.3 重要参数max_iter & tol:让迭代停下来
在之前描述
K-Means
的基本流程时我们提到过,当质心不再移动,
Kmeans算法就会停下来。但在完全收敛之前,我们也可以使用
max_iter
,最大迭代次数,或者
tol
,两次迭代间
Inertia下降的量,这两个参数来让迭代提前停下来。有时候,当我们的
n_clusters选择不符合数据的自然分布,或者我们为了业务需求,必须要填入与数据的自然分布不合的
n_clusters
,提前让迭代停下来反而能够提升模型的表现。
max_iter
:整数,默认
300
,单次运行的
k-means
算法的最大迭代次数
tol
:浮点数,默认
1e-4
,两次迭代间
Inertia
下降的量,如果两次迭代之间
Inertia
下降的值小于
tol所设定的值,迭代就会停下
random = KMeans(n_clusters = 10,init="random",max_iter=10,random_state=420).fit(X)
y_pred_max10 = random.labels_
silhouette_score(X,y_pred_max10)
'''
0.3952586444034157
'''
random = KMeans(n_clusters = 10,init="random",max_iter=20,random_state=420).fit(X)
y_pred_max10 = random.labels_
silhouette_score(X,y_pred_max10)
'''
0.3401504537571701
'''
3.4 重要属性与重要接口
到这里,所有的重要参数就讲完了。在使用模型的过程中,我也向大家呈现了各种重要的属性与接口,在这一小节来复习一下:
3.5 函数cluster.k_means
sklearn.cluster.k_means
(
X
,
n_clusters
,
sample_weight=None
,
init=’k-means++’
,
precompute_distances=’auto’, n_init=10
,
max_iter=300
,
verbose=False
,
tol=0.0001
,
random_state=None
,
copy_x=True
,
n_jobs=None,algorithm=’auto’
,
return_n_iter=False
)
函数
k_means
的用法其实和类非常相似,不过函数是输入一系列值,而直接返回结果。一次性地,函数
k_means会依次返回质心,每个样本对应的簇的标签,
inertia
以及最佳迭代次数。
from sklearn.cluster import k_means
k_means(X,4,return_n_iter=True)
4 案例:聚类算法用于降维,KMeans的矢量量化应用
K-Means
聚类最重要的应用之一是非结构数据(图像,声音)上的矢量量化(VQ)。非结构化数据往往占用比较多的储存空间,文件本身也会比较大,运算非常缓慢,我们希望能够在保证数据质量的前提下,尽量地缩小非结构化数据的大小,或者简化非结构化数据的结构。矢量量化就可以帮助我们实现这个目的。KMeans聚类的矢量量化本质是一种降维运用,但它与我们之前学过的任何一种降维算法的思路都不相同。特征选择的降维是直接选取对模型贡献最大的特征,
PCA
的降维是聚合信息,而
矢量量化的降维是在同等样本量上压缩信息的大小,即不改变特征的数目也不改变样本的数目,只改变在这些特征下的样本上的信息量。
对于图像来说,一张图片上的信息可以被聚类如下表示:
这是一组
40
个样本的数据,分别含有
40
组不同的信息
(x1,x2)
。我们将代表所有样本点聚成
4类,找出四个质心,我们认为,这些点和他们所属的质心非常相似,因此他们所承载的信息就约等于他们所在的簇的质心所承载的信息。
于是,我们可以使用每个样本所在的簇的质心来覆盖原有的样本,有点类似四舍五入的感觉,类似于用
1
来代替0.9和
0.8
。这样,
40
个样本带有的
40
种取值,就被我们压缩了
4
组取值,虽然样本量还是
40
个,但是这
40个样本所带的取值其实只有
4
个,就是分出来的四个簇的质心。
用
K-Means聚类中获得的质心来替代原有的数据,可以把数据上的信息量压缩到非常小,但又不损失太多信息。我们接下来就通过一张图图片的矢量量化来看一看
K-Means
如何实现压缩数据大小,却不损失太多信息量。
1.
导入需要的库
import numpy as np
import matplotlib.pyplot as plt
from sklearn.cluster import KMeans
from sklearn.metrics import pairwise_distances_argmin
#对两个序列中的点进行距离匹配的函数
from sklearn.datasets import load_sample_image
#导入图片数据所用的类
from sklearn.utils import shuffle #洗牌
2.
导入数据,探索数据
# 实例化,导入颐和园的图片
china = load_sample_image("china.jpg")
#查看数据类型 dtype('uint8')
china.dtype
china.shape
#长度 x 宽度 x 像素 > 三个数决定的颜色 (427, 640, 3)
#包含多少种不同的颜色?
newimage = china.reshape((427 * 640,3))
import pandas as pd
pd.DataFrame(newimage).drop_duplicates().shape
#我们现在有9W多种颜色 (96615, 3)
# 图像可视化
plt.figure(figsize=(15,15))
plt.imshow(china) #导入3维数组形成的图片
#查看模块中的另一张图片
flower = load_sample_image("flower.jpg")
plt.figure(figsize=(15,15))
plt.imshow(flower)
图像探索完毕,我们了解了,图像现在有
9W
多种颜色。我们希望来试试看,能否使用
K-Means
将颜色压缩到64种,还不严重损耗图像的质量。为此,我们要使用
K-Means
来将
9W
种颜色聚类成
64
类,然后使用
64个簇的质心来替代全部的
9W
种颜色,记得质心有着这样的性质:簇中的点都是离质心最近的样本点。
为了比较,我们还要画出随机压缩到
64
种颜色的矢量量化图像。我们需要随机选取64个样本点作为随机质心,计算原数据中每个样本到它们的距离来找出离每个样本最近的随机质心,然后用每个样本所对应的随机质心来替换原本的样本。两种状况下,我们观察图像可视化之后的状况,以查看图片信息的损失。
在这之前,我们需要把数据处理成
sklearn
中的
K-Means
类能够接受的数据。
n_clusters = 64
china = np.array(china, dtype=np.float64) / china.max()
w, h, d = original_shape = tuple(china.shape)
assert d == 3
image_array = np.reshape(china, (w * h, d))
#plt.imshow在浮点数上表现非常优异,在这里我们把china中的数据,转换为浮点数,压缩到[0,1]之间
china = np.array(china, dtype=np.float64) / china.max()
#把china从图像格式,转换成矩阵格式
w, h, d = original_shape = tuple(china.shape)
#展示assert的功能
d_ = 3
assert d_ == 3, "一个格子中特征数不等于3"
image_array = np.reshape(china, (w * h, d)) #reshape是改变结构
image_array
image_array.shape
a = np.random.random((2,4))
a.reshape((4,2)) == np.reshape(a,(4,2))
np.reshape(a,(2,2,2)).shape
np.reshape(a,(8,1))
4. 对数据进行K-Means的矢量量化
image_array_sample = shuffle(image_array, random_state=0)[:1000]
kmeans = KMeans(n_clusters=n_clusters, random_state=0).fit(image_array_sample)
kmeans.cluster_centers_
labels = kmeans.predict(image_array)
labels.shape
image_kmeans = image_array.copy()
for i in range(w*h):
image_kmeans[i] = kmeans.cluster_centers_[labels[i]]
image_kmeans
pd.DataFrame(image_kmeans).drop_duplicates().shape
image_kmeans = image_kmeans.reshape(w,h,d)
image_kmeans.shape
5.
对数据进行随机的矢量量化
centroid_random = shuffle(image_array, random_state=0)[:n_clusters]
labels_random = pairwise_distances_argmin(centroid_random,image_array,axis=0)
labels_random.shape
len(set(labels_random))
image_random = image_array.copy()
for i in range(w*h):
image_random[i] = centroid_random[labels_random[i]]
image_random = image_random.reshape(w,h,d)
image_random.shape
6.
将原图,按
KMeans
矢量量化和随机矢量量化的图像绘制出来
plt.figure(figsize=(10,10))
plt.axis('off')
plt.title('Original image (96,615 colors)')
plt.imshow(china)
plt.figure(figsize=(10,10))
plt.axis('off')
plt.title('Quantized image (64 colors, K-Means)')
plt.imshow(image_kmeans)
plt.figure(figsize=(10,10))
plt.axis('off')
plt.title('Quantized image (64 colors, Random)')
plt.imshow(image_random)
plt.show()