Python 聚类算法在矢量量化案例详解
关注微信公共号:小程在线
关注****博客:程志伟的博客
KMeans算法将一组N个样本的特征矩阵X划分为K个无交集的簇,直观上来看是簇是一组一组聚集在一起的数据,在一个簇中的数据就认为是同一类。簇就是聚类的结果表现。
簇中所有数据的均值 通常被称为这个簇的“质心”(centroids)。在一个二维平面中,一簇数据点的质心的横坐标就是这一簇数据点的横坐标的均值,质心的纵坐标就是这一簇数据点的纵坐标的均值。同理可推广至高维空间。
顺序 过程
1 随机抽取K个样本作为最初的质心
2 开始循环:
2.1 将每个样本点分配到离他们最近的质心,生成K个簇
2.2 对于每个簇,计算所有被分到该簇的样本点的平均值作为新的质心
3 当质心的位置不再发生变化,迭代停止,聚类完成
簇内误差平方和:
对于一个簇来说,所有样本点到质心的距离之和越小,我们就认为这个簇中的样本越相似,簇内差异就越小
from sklearn.datasets import make_blobs
import matplotlib.pyplot as plt
#自己创建数据集
X, y = make_blobs(n_samples=500,n_features=2,centers=4,random_state=1)
fig, ax1 = plt.subplots(1)
ax1.scatter(X[:, 0], X[:, 1]
,marker='o' #点的形状
,s=8 #点的大小
)
plt.show()

全部一个颜色,查看这些点的分布
color = ["red","pink","orange","gray"]
fig, ax1 = plt.subplots(1)
for i in range(4):
ax1.scatter(X[y==i, 0], X[y==i, 1]
,marker='o' #点的形状
,s=8 #点的大小
,c=color[i]
)
plt.show()

基于这个分布,我们来使用Kmeans进行聚类,首选选取3类
from sklearn.cluster import KMeans
n_clusters = 3
cluster = KMeans(n_clusters=n_clusters, random_state=0).fit(X)
#查看分类情况
y_pred = cluster.labels_
y_pred
Out[4]:
array([0, 0, 2, 1, 2, 1, 2, 2, 2, 2, 0, 0, 2, 1, 2, 0, 2, 0, 1, 2, 2, 2,
2, 1, 2, 2, 1, 1, 2, 2, 0, 1, 2, 0, 2, 0, 2, 2, 0, 2, 2, 2, 1, 2,
......
1, 2, 2, 2, 1, 1, 1, 2, 2, 2, 0, 2, 1, 2, 0, 1, 0, 1, 0, 2, 1, 1,
0, 2, 2, 0, 2, 2, 2, 0, 2, 1, 2, 2, 0, 0, 0, 2])
#Kmeans也有predict和fit_predict函数,表示学习数据X并对X的类进行预测,和labels一模一样
pre = cluster.fit_predict(X)
pre == y_pred
Out[5]:
array([ True, True, True, True, True, True, True, True, True,
True, True, True, True, True, True, True, True, True,
True, True, True, True, True, True, True, True, True,
......
True, True, True, True, True, True, True, True, True,
True, True, True, True, True, True, True, True, True,
True, True, True, True, True])
#当数据量太大时,不必使用所有的数据寻找质心,调用部分数据就可以,使用predict来调用,提高效率
#因为数据量太小的原因,结果不会太准确,但是当数据量增大时,可以提升准确度
cluster_smallsub = KMeans(n_clusters=n_clusters, random_state=0).fit(X[:200])
y_pred_ = cluster_smallsub.predict(X)
y_pred == y_pred_
Out[6]:
array([False, False, True, False, True, False, True, True, True,
True, False, False, True, False, True, False, True, False,
False, True, True, True, True, False, True, True, False,
......
True, False, True, True, True, False, True, False, True,
True, False, False, False, True])
#确定质心的位置
centroid = cluster.cluster_centers_
centroid
Out[7]:
array([[-7.09306648, -8.10994454],
[-1.54234022, 4.43517599],
[-8.0862351 , -3.5179868 ]])
centroid.shape
Out[8]: (3, 2)
#重要属性inertia_,查看总距离平方和
inertia = cluster.inertia_
inertia
Out[9]: 1903.4503741659223
#对聚类结果进行画图
color = ["red","pink","orange","gray"]
fig, ax1 = plt.subplots(1)
for i in range(n_clusters):
ax1.scatter(X[y_pred==i, 0], X[y_pred==i, 1]
,marker='o'
,s=8
,c=color[i]
)
ax1.scatter(centroid[:,0],centroid[:,1]
,marker="x"
,s=15
,c="black")
plt.show()
#把簇的类似改成4类,查看总距离平方和
n_clusters = 4
cluster_ = KMeans(n_clusters=n_clusters, random_state=0).fit(X)
inertia_ = cluster_.inertia_
inertia_
Out[11]: 908.3855684760613
#把簇的类似改成5类,查看总距离平方和
n_clusters = 5
cluster_ = KMeans(n_clusters=n_clusters, random_state=0).fit(X)
inertia_ = cluster_.inertia_
inertia_
Out[12]: 811.0841324482415
#把簇的类似改成6类,查看总距离平方和
n_clusters = 6
cluster_ = KMeans(n_clusters=n_clusters, random_state=0).fit(X)
inertia_ = cluster_.inertia_
inertia_
Out[13]: 733.153835008308
可以看出簇的数越大,总距离平方和越少。极限时,簇的个数为500,总距离平方和为0,不能对簇做出最优选择。
#聚类算法的模型评估指标
#当真实标签未知的时候:轮廓系数
在sklearn中,我们使用模块metrics中的类silhouette_score来计算轮廓系数,它返回的是一个数据集中,所有样
本的轮廓系数的均值。但我们还有同在metrics模块中的silhouette_sample,它的参数与轮廓系数一致,但返回的
是数据集中每个样本自己的轮廓系数
from sklearn.metrics import silhouette_score
from sklearn.metrics import silhouette_samples
X.shape
Out[15]: (500, 2)
#簇等于6时的轮廓系数
silhouette_score(X,y_pred)
Out[16]: 0.5882004012129721
#簇等于4时的轮廓系数
n_clusters = 4
cluster_ = KMeans(n_clusters=n_clusters, random_state=0).fit(X)
inertia_ = cluster_.inertia_
inertia_
silhouette_score(X,cluster_.labels_)
Out[17]: 0.6505186632729437
#簇等于5时的轮廓系数
n_clusters = 5
cluster_ = KMeans(n_clusters=n_clusters, random_state=0).fit(X)
inertia_ = cluster_.inertia_
inertia_
silhouette_score(X,cluster_.labels_)
Out[18]: 0.5746932321727457
#数据集中每个样本自己的轮廓系数的个数与平均值
silhouette_samples(X,y_pred).shape
Out[19]: (500,)
silhouette_samples(X,y_pred).mean()
Out[20]: 0.5882004012129721
#当真实标签未知的时候:Calinski-Harabaz Index
from sklearn.metrics import calinski_harabaz_score
calinski_harabaz_score(X, y_pred)
H:\Anaconda3\lib\site-packages\sklearn\utils\deprecation.py:85: DeprecationWarning: Function calinski_harabaz_score is deprecated; Function 'calinski_harabaz_score' has been renamed to 'calinski_harabasz_score' and will be removed in version 0.23.
warnings.warn(msg, category=DeprecationWarning)
Out[21]: 1809.991966958033
#卡林斯基-哈拉巴斯指数越高越好,比起轮廓系数,它有一个巨大的优点,就是计算非常快速
t0 = time()
calinski_harabaz_score(X, y_pred)
time() - t0
H:\Anaconda3\lib\site-packages\sklearn\utils\deprecation.py:85: DeprecationWarning: Function calinski_harabaz_score is deprecated; Function 'calinski_harabaz_score' has been renamed to 'calinski_harabasz_score' and will be removed in version 0.23.
warnings.warn(msg, category=DeprecationWarning)
Out[23]: 0.0009794235229492188
t0 = time()
silhouette_score(X,y_pred)
time() - t0
Out[24]: 0.009987354278564453
import datetime
datetime.datetime.fromtimestamp(t0).strftime("%Y-%m-%d %H:%M:%S")
Out[25]: '2020-03-30 22:01:27'
案例:基于轮廓系数来选择n_clusters
n_clusters = 4
#创建一个画布,2个图,
fig, (ax1, ax2) = plt.subplots(1, 2)
fig.set_size_inches(18, 7)
#第一个图是轮廓系数图像,由各个簇的轮廓系数组成的条形图,横坐标是轮廓系数,纵坐标是每个样本
#轮廓系数在[-1,1]之间,但是要大于0,效果好
#设置横坐标
ax1.set_xlim([-0.1, 1])
#纵坐标从0开始,最大值是x.shape(0)的取值
#每个簇排在一起,但是有间隙
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_score返回得是所有样本点的均值
#矩阵X和聚类完毕之后的标签
silhouette_avg = silhouette_score(X, cluster_labels)
print("For n_clusters =", n_clusters,
"The average silhouette_score is :", silhouette_avg)
#silhouette_samples返回每个样本的轮廓系数
sample_silhouette_values = silhouette_samples(X, cluster_labels)
#Y轴的初始值
y_lower = 10
#对每个簇进行循环
for i in range(n_clusters):
#从每个样本的轮廓系数结果中抽取第i个簇的轮廓系数,并进行排序
ith_cluster_silhouette_values = sample_silhouette_values[cluster_labels == i]
#注意,.sort会直接改掉原数据的顺序
ith_cluster_silhouette_values.sort()
#查看簇中究竟有多少个样本
size_cluster_i = ith_cluster_silhouette_values.shape[0]
#一个簇在y州的取值是由初始值(y_lower)开始,到初始值加上这个簇中的样本数量结束(y_upper)
y_upper = y_lower + size_cluster_i
#用i的浮点数除以n_clusters,在不同的i下生成不同的小数,以确保所有的簇都有不同的颜色
color = cm.nipy_spectral(float(i)/n_clusters)
#fill_between是让一个范围的柱状图都统一颜色的函数,
#fill_betweenx的范围是在纵坐标上,参数输入(纵坐标的下限,纵坐标的上限,X轴上的取值,柱状图的颜色)
ax1.fill_betweenx(np.arange(y_lower, y_upper)
,ith_cluster_silhouette_values
,facecolor=color
,alpha=0.7)
#为每个簇的轮廓系数写上编号,并让簇的编号显示在坐标轴每个条形图的中间位置
#text参数(要显示编号位置的横坐标,纵坐标,编号内容)
ax1.text(-0.05, y_lower + 0.5 * size_cluster_i, str(i))
#为下一个簇计算新的y轴上的初始值,每一次迭代后y再加上10以保证不同簇的图像之间显示有空隙
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="--")
#让y轴不显示任何刻度
ax1.set_yticks([])
#让X轴上的刻度显示为规定的列表
ax1.set_xticks([-0.1, 0, 0.2, 0.4, 0.6, 0.8, 1])
#开始处理第二个图,首先获取新的颜色,由于没有循环需要一次性生成多个小数来获取多个颜色
#cluster_labels.astype(float) 生成浮点数,nipy_spectral只能用浮点数,500个值只有4个颜色
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_
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()
#将上述过程包装称循环
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"
"with n_clusters = %d" % n_clusters),fontsize=14, fontweight='bold')
plt.show()
For n_clusters = 2 The average silhouette_score is : 0.7049787496083262
For n_clusters = 3 The average silhouette_score is : 0.5882004012129721
For n_clusters = 4 The average silhouette_score is : 0.6505186632729437
For n_clusters = 5 The average silhouette_score is : 0.56376469026194
For n_clusters = 6 The average silhouette_score is : 0.4504666294372765
For n_clusters = 7 The average silhouette_score is : 0.39092211029930857
当簇等于2时,可以看出有一个簇的明显高于平均值,当实施精准营销时,可以只考虑该类人员的特征。
当簇等于4时,也可以把数据分成很好的类。
重要参数init & random_state & n_init
init 默认输入"kmeans++":一种为K均值聚类选择初始聚类中心的聪明的办法,以加速收敛。如果输入了n维数组,数组的形状应
该是(n_clusters,n_features)并给出初始质心。
plus = KMeans(n_clusters = 10).fit(X)
plus.n_iter_
Out[28]: 17
random = KMeans(n_clusters = 10,init="random",random_state=420).fit(X)
random.n_iter_
Out[29]: 19
重要参数max_iter & tol:让迭代停下来
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)
Out[30]: 0.3952586444034157
random = KMeans(n_clusters = 10,init="random",max_iter=20,random_state=420).fit(X)
y_pred_max20 = random.labels_
silhouette_score(X,y_pred_max20)
Out[31]: 0.3401504537571701
#聚类算法用于降维,KMeans的矢量量化应用
矢量量化的降维是在同等样本量上压缩信息的大小,即不改变特征的数目也不改变样本的数目,只改变在这些特征下的样本上的信息量。
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
china = load_sample_image("china.jpg")
china
Out[33]:
array([[[174, 201, 231],
[174, 201, 231],
[174, 201, 231],
...,
[250, 251, 255],
[250, 251, 255],
[250, 251, 255]],
[[172, 199, 229],
[173, 200, 230],
[173, 200, 230],
...,
[251, 252, 255],
[251, 252, 255],
[251, 252, 255]],
[[174, 201, 231],
[174, 201, 231],
[174, 201, 231],
...,
[252, 253, 255],
[252, 253, 255],
[252, 253, 255]],
...,
[[ 88, 80, 7],
[147, 138, 69],
[122, 116, 38],
...,
[ 39, 42, 33],
[ 8, 14, 2],
[ 6, 12, 0]],
[[122, 112, 41],
[129, 120, 53],
[118, 112, 36],
...,
[ 9, 12, 3],
[ 9, 15, 3],
[ 16, 24, 9]],
[[116, 103, 35],
[104, 93, 31],
[108, 102, 28],
...,
[ 43, 49, 39],
[ 13, 21, 6],
[ 15, 24, 7]]], dtype=uint8)
china.dtype
Out[34]: dtype('uint8')
china.shape
Out[35]: (427, 640, 3)
china[0][0]
Out[36]: array([174, 201, 231], dtype=uint8)
newimage = china.reshape((427 * 640,3))
newimage.shape
Out[37]: (273280, 3)
import pandas as pd
pd.DataFrame(newimage).drop_duplicates().shape
Out[38]: (96615, 3)
plt.figure(figsize=(15,15))
plt.imshow(china)
Out[39]: <matplotlib.image.AxesImage at 0x1d286ad0f28>

flower = load_sample_image("flower.jpg")
plt.figure(figsize=(15,15))
plt.imshow(flower)
Out[40]: <matplotlib.image.AxesImage at 0x1d286b43278>
3. 决定超参数,数据预处理
china = np.array(china, dtype=np.float64) / china.max()
w, h, d = original_shape = tuple(china.shape)
w, h, d
Out[42]: (427, 640, 3)
#设置d=3,不等于3报错
assert d == 3
d_ = 5
assert d_ == 3, "一个格子中的特征数目不等于3种"
Traceback (most recent call last):
File "<ipython-input-44-6bfdf4addbf4>", line 2, in <module>
assert d_ == 3, "一个格子中的特征数目不等于3种"
AssertionError: 一个格子中的特征数目不等于3种
#reshape改变数据结果,只要总数据量不变,维度都可以变化
a = np.random.random((2,4))
a
Out[45]:
array([[0.17545705, 0.61403347, 0.65398707, 0.32697789],
[0.84574926, 0.42294712, 0.33591104, 0.49572982]])
a.reshape((4,2))
Out[46]:
array([[0.17545705, 0.61403347],
[0.65398707, 0.32697789],
[0.84574926, 0.42294712],
[0.33591104, 0.49572982]])
a.reshape((4,2))
Out[47]:
array([[0.17545705, 0.61403347],
[0.65398707, 0.32697789],
[0.84574926, 0.42294712],
[0.33591104, 0.49572982]])
np.reshape(a,(4,2))
Out[48]:
array([[0.17545705, 0.61403347],
[0.65398707, 0.32697789],
[0.84574926, 0.42294712],
[0.33591104, 0.49572982]])
np.reshape(a,(2,2,2))
Out[49]:
array([[[0.17545705, 0.61403347],
[0.65398707, 0.32697789]],
[[0.84574926, 0.42294712],
[0.33591104, 0.49572982]]])
np.reshape(a,(3,2))
Traceback (most recent call last):
File "<ipython-input-50-a620d2495b00>", line 1, in <module>
np.reshape(a,(3,2))
File "H:\Anaconda3\lib\site-packages\numpy\core\fromnumeric.py", line 292, in reshape
return _wrapfunc(a, 'reshape', newshape, order=order)
File "H:\Anaconda3\lib\site-packages\numpy\core\fromnumeric.py", line 56, in _wrapfunc
return getattr(obj, method)(*args, **kwds)
ValueError: cannot reshape array of size 8 into shape (3,2)
image_array = np.reshape(china, (w * h, d))
image_array
image_array.shape
Out[51]: (273280, 3)
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))
4. 对数据进行K-Means的矢量量化
#首先,使用1000个数据找出质心
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_
Out[53]:
array([[0.62570806, 0.60261438, 0.53028322],
[0.15546218, 0.1557423 , 0.12829132],
......
[0.73524384, 0.82021116, 0.91925591],
[0.20627451, 0.07816993, 0.07660131]])
kmeans.cluster_centers_.shape
Out[54]: (64, 3)
#找出质心之后,对所有点数据进行聚类
labels = kmeans.predict(image_array)
labels.shape
Out[55]: (273280,)
#使用质心来替换所有的样本
image_kmeans = image_array.copy()
for i in range(w*h):
image_kmeans[i] = kmeans.cluster_centers_[labels[i]]
#查看新图片的信息
image_kmeans
Out[58]:
array([[0.73524384, 0.82021116, 0.91925591],
[0.73524384, 0.82021116, 0.91925591],
[0.73524384, 0.82021116, 0.91925591],
...,
[0.15546218, 0.1557423 , 0.12829132],
[0.07058824, 0.0754637 , 0.0508744 ],
[0.07058824, 0.0754637 , 0.0508744 ]])
#去重
pd.DataFrame(image_kmeans).drop_duplicates().shape
Out[59]: (64, 3)
#恢复图片
image_kmeans = image_kmeans.reshape(w,h,d)
image_kmeans.shape
Out[60]: (427, 640, 3)
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
Out[61]: (273280,)
len(set(labels_random))
Out[62]: 64
#使用随机质心替换样本
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
Out[64]: (427, 640, 3)
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()
第一张为原图,第二张为聚类之后的图,第三张是随机质心的图;
从以上的三幅图中可以看出,第二张聚类之后在塔的颜色上没有明显缺失,天空出现锯齿形,第三张的塔的颜色发生变化。