- 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如何实现压缩数据大小,却不损失太多信息量
一、图像探索
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.dtype # dtype('uint8') 编程中常用的图片数据类型
china.shape # (427, 640, 3) [H * W * C]
china
china[0][0] # 第一个像素点的三通道值
# 查看图片包含多少不同的颜色
newimage = china.reshape((427 * 640,3)) # 将所有像素点展开排列
newimage.shape # (273280, 3)
import pandas as pd
pd.DataFrame(newimage).drop_duplicates().shape
# 独一无二颜色数:96615
# 图片展示
plt.figure(figsize=(8,8))
plt.imshow(china) # 必须输入三维数据,且格式为[H * W * C]
# 输入为整数,范围须在[0,255]
# 输入为浮点数,范围须在[0,1]
# 查看其他图片
flower = load_sample_image("flower.jpg")
plt.imshow(flower)
- 图像探索完毕了解到,图像现在有9W多种颜色。
- 希望试试,能否使用K-Means将颜色压缩到64种,还不严重损耗图像的质量。为此,要使用K-Means来将9W种颜色聚类成64类,然后使用64个簇的质心来替代全部的9W种颜色,记得质心有着这样的性质:簇中的点都是离质心最近的样本点。
- 为了比较,还要画出随机压缩到64种颜色的矢量量化图像。需要随机选取64个样本点作为随机质心,计算原数据中每个样本到它们的距离来找出离每个样本最近的随机质心,然后用每个样本所对应的随机质心来替换原本的样本。两种状况下,观察图像可视化之后的状况,以查看图片信息的损失。
- 在这之前,需要把数据处理成sklearn中的K-Means类能够接受的数据
二、数据预处理,确定超参数
2.1、KMeans不接受三维数组作为特征矩阵,必须要把数据变成二维数据
n_clusters = 64
china = np.array(china,dtype=np.float64) / china.max()
# 将china转为浮点数 并归一化
# 将原始维度单独保存和以元组形式保存
w,h,d = original_shape = tuple(china.shape)
# assert相当于 raise error if not ,表示不为True就报错
assert d == 3
image_array = np.reshape(china,(w * h,d))
2.2、对数据进行K-Means的矢量量化
# 先使用1000个数据来寻找质心,其他的根据找到的质心来添加标签
image_array_sample = shuffle(image_array,random_state=0)[:1000] # 随机打乱后选取1000份数据
kmeans = KMeans(n_clusters=n_clusters,random_state=0).fit(image_array_sample)
kmeans.cluster_centers_ # 返回64个质心 (64, 3)
# 根据上面用1000个数据训练的模型来预测全部簇的分类
labels = kmeans.predict(image_array)
print(labels)
labels.shape
# 使用质心来替换所有的样本
image_kmeans = image_array.copy() # 27W个样本点 9W种不同的颜色
image_kmeans
for i in range(w*h):
image_kmeans[i] = kmeans.cluster_centers_[labels[i]]
image_kmeans
# 替换后只剩下64种颜色
pd.DataFrame(image_kmeans).drop_duplicates().shape
# 恢复图片结构
image_kmeans = image_kmeans.reshape(w,h,d)
image_kmeans.shape
2.2、对数据进行随机的矢量量化
# 随机选择质心
centroid_random = shuffle(image_array,random_state=0)[:n_clusters]
centroid_random
labels_random = pairwise_distances_argmin(centroid_random,image_array,axis=0)
# 函数pairwise_distances_argmin(x1,x2,axis) x1,x2分别表示序列
# 函数用来计算x2中每个样本到x1中每个样本的距离,并对应返回与x1样本最近的x1的样本的索引
# 使用随机质心来替换所有样本
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
2.3、将原图按K-Means矢量量化和随机矢量量化的图像绘制出来
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()