无监督学习——K均值聚类(下)

之前我们讲了聚类中比较常用的K均值算法,包括原理,相关参数以及实际操作。那么本篇文章,我们来讲一下更复杂一点的内容,即K均值,PCA与NMF之间的比较。希望大家在阅读下面的内容之前,已经了解了K均值,PCA与NMF算法的基础知识。

如果不清楚的话,可以点击下面的链接,来简单阅读下:
K均值:无监督学习——K均值聚类(上)
PCA:主成分分析(PCA)应用(上)主成分分析(PCA)应用(下)
NMF:非负矩阵分解(NMF)

图像重建与矢量量化

虽然k均值是一种聚类算法,但在k均值和分解方法(比如之前讨论的PCA和NMF)之间存在一些有趣的相似之处。大家可能还记得,PCA试图找到数据中方差最大的方向,而NMF试图找到累加的分量,这通常对应于数据的“极值”或“部分”。两种方法都试图将数据点表示为一些分量之和。与之相反,k均值则尝试利用簇中心来表示每个数据点。你可以将其看作仅用一个分量来表示每个数据点,该分量有簇中心给出。这种观点将k均值看作是一种分解方法,其中每个点用单一分量来表示,这种观点被称为矢量量化实际上,对于k均值来说,重建就是在训练集中找到的最近的簇中心。

接下来,我们通过一个实际例子来比较三者之间的关系。

1.数据来源

LFW - People (Face Recognition):https://www.kaggle.com/atulanandjha/lfwpeople?select=pairs.txt

这是kaggle网站上一个专门用来做人脸识别的数据集,收录了网站上超过13000张人脸图片。接下来把这份图片数据集下载下来并解压。

PS:下载下来的图片保存在lfw-funneled.tgz文件里,”.tgz”是一种压缩文件的格式,所以我们只要解压缩就可以了。

解压完毕后,我们就可以看见图片存储在以每人的名字所命名的文件里,每个文件夹包含数量不同的照片,而每个照片又分别以名字+数字的名字命名,方便我们使用。

2.数据处理

我之前写了一篇文章来讲图像数据处理方面的知识,需要了解的朋友可以自行阅读这篇文章,这里就不再赘述了。(传送门:主成分分析(PCA)应用(上)

处理代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import os
all_folds = os.listdir(r'C:\Users\Administrator\Desktop\源数据-分析\lfw_funneled')
all_folds = [x for x in all_folds if '.' not in x]
import pandas as pd
numbers_img=pd.DataFrame(columns=["文件名称","图片数量"])####统计各个文件夹里面的图片数量
for i in range(len(all_folds)):
path = 'C:\\Users\\Administrator\\Desktop\\源数据-分析\\lfw_funneled\\'+all_folds[i]
all_files = os.listdir(path)
numbers_img.loc[i]=[all_folds[i],len(all_files)]
img_10=numbers_img[numbers_img["图片数量"]==10].reset_index()#####为了降低数据偏斜,选取图片数量为10的文件(否则,特征提取会被图片数量过多的数据影响)
from PIL import Image
import numpy as np
image_arr_list=[]###存放灰度值numpy数组
flat_arr_list=[]###存放灰度值一维数组
target_list=[]###存放目标值
for m in range(len(img_10["文件名称"])):
file_address='C:\\Users\\Administrator\\Desktop\\源数据-分析\\lfw_funneled\\'+img_10["文件名称"][m]+"\\"####指定特定的文件地址
image_name=os.listdir(file_address)###获得指定文件夹下的左右文件名称
for n in image_name:
image=Image.open(file_address+n)
image=image.convert('L')###RGB(红绿蓝)像素值转换成灰度值
image_arr=np.array(image,"f")###灰度值转化成numpy数组(二维)
flat_arr=image_arr.ravel()###将数组扁平化处理,返回的是一个一维数组的非副本视图,就是将几行的数据强行拉成一行
image_arr_list.append(image_arr)
flat_arr_list.append(flat_arr)
target_list.append(m)###这里的m设定是数字,如果是文本的话后面的算法会报错
faces_dict={"images":np.array(image_arr_list),"data":np.array(flat_arr_list),"target":np.array(target_list)}

3.划分数据集并进行建模

下面我们划分数据集,并利用NMF,PCA和K均值来依次进行建模,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
from sklearn.model_selection import train_test_split
train=faces_dict["data"]/255
X_train,X_test,y_train,y_test=train_test_split(train,faces_dict["target"],random_state=0)###划分训练集和测试集
from sklearn.decomposition import PCA
from sklearn.cluster import KMeans
from sklearn.decomposition import NMF
nmf=NMF(n_components=100,random_state=0)
nmf.fit(X_train)
pca=PCA(n_components=100,random_state=0)
pca.fit(X_train)
kmeans=KMeans(n_clusters=100,random_state=0)
kmeans.fit(X_train)###注意这是训练数据

然后,我们依次利用训练好的模型,依次生成重建数据集:

1
2
3
X_reconstructed_pca=pca.inverse_transform(pca.transform(X_test))###注意这是测试数据模型
X_reconstructed_kmeans=kmeans.cluster_centers_[kmeans.predict(X_test)]
X_reconstructed_nmf=np.dot(nmf.transform(X_test),nmf.components_)

4.恢复图像

为了方便接下来的图像展示,我们首先将上面的重建数据整合在一起,构成一个二维列表:

1
2
3
4
5
6
7
8
list=[X_test,X_reconstructed_nmf,X_reconstructed_kmeans,X_reconstructed_pca]
X_reconstructed=[[],[],[],[]]
shape=image_arr.shape###获得二维数组的维度
for i in range(len(list)):
for m in list[i]:
vector=np.matrix(m)####将一维数组转换成矩阵
arr2=np.asarray(vector).reshape(shape)###可以通过这个矩阵将一维数组转换为原灰度值numpy数组,即arr2=image_arr
X_reconstructed[i].append(arr2)

接下来,为了比较方便,我们只选取前5张图像,并利用多图表结构将其展现出来

1
2
3
4
5
6
7
8
9
10
11
12
13
import seaborn as sns
plt.style.use("fivethirtyeight")
sns.set_style({'font.sans-serif':['SimHei','Arial']})
fig,axes=plt.subplots(4,5,figsize=(45, 30))
plt.suptitle('K均值,PCA与NMF的图像还原比较', fontsize=80, ha='center')
for l in range(len(X_reconstructed)):
for n in range(5):
axes[l,n].imshow(X_reconstructed[l][n],cmap="gray")###通过灰度值还原图像
axes[0,0].set_ylabel("原始图片",fontsize=50)
axes[1,0].set_ylabel("NMF",fontsize=50)
axes[2,0].set_ylabel("K均值",fontsize=50)
axes[3,0].set_ylabel("PCA",fontsize=50)
plt.show()###由于之前已经划分了数据集,这是利用训练出来的模型对测试数据集进行的图像恢复,所以只有38个图像,而不是原来的150个图像

最终结果如下所示:

在这里插入图片描述

上图就是利用了100个分量(簇中心)的k均值,PCA和NMF的图像重建对比,其中k均值的每张图像中仅使用了一个簇中心。可以看出,相对来说还是k均值的表现要好一点。当然,PCA和NMF亦可以通过调整参数来提高精度,感兴趣的朋友可以自行探究。

5.矢量量化

利用K均值做矢量量化的一个有趣之处在于,可以用比输入维度更多的簇来对数据进行编码。让我们回到two_moons数据。利用PCA或NMF,我们对这个数据无能为力,因为它只有两个维度。如果使用PCA和NMF将其降维到一维,将会完全破坏数据结构。但通过使用更多的簇中心,我们可以用K均值找到一种更具表现力的表示:

1
2
3
4
5
6
7
8
from sklearn.datasets import make_moons
plt.rcParams['axes.unicode_minus'] = False###防止坐标轴符号显示不出来
X,y=make_moons(n_samples=200,noise=0.05,random_state=0)
kmeans=KMeans(n_clusters=10,random_state=0)###使用两个簇
kmeans.fit(X)
y_pred=kmeans.predict(X)###与labels_相同,为新数据点分配簇标签
plt.scatter(X[:,0],X[:,1],c=y_pred,cmap="Paired",s=60)
plt.scatter(kmeans.cluster_centers_[:,0],kmeans.cluster_centers_[:,1],s=60,marker="*",c=range(kmeans.n_clusters),linewidth=2)

在这里插入图片描述

我们使用了10个簇中心,也就是说,现在每10个点都被分配了0到9之间的一个数字。我们可以将其看作10个分量表示的数据(有10个新特征),只有表示该点对应的簇中心的那个特征不为0,其它特征均为0。利用这个10维表示,我们就可以用线性模型来划分两个半月形,而利用原始的两个特征是不可能做到这一点的。

总结

K均值是非常流行的聚类算法,因为它不仅相对容易理解和实现,而且运行速度也相对较快。同时K均值可以轻松扩展到大型数据集。

而K均值的缺点之一在于,它依赖随机初始化,也就是说,算法的输出依赖于随机种子。默认情况下scikit-learn用10种不同的随机初始化将算法运行10次,并返回最佳结果。当然,K均值还有一个缺点,就是簇形状的假设的约束性较强,而且还要求指定所要寻找的簇的个数(在现实世界中可能并不知道这个数字)。

有很多地方做的不是很好,欢迎网友来提出建议,也希望可以遇到些朋友来一起交流讨论。序

之前我们讲了聚类中比较常用的K均值算法,包括原理,相关参数以及实际操作。那么本篇文章,我们来讲一下更复杂一点的内容,即K均值,PCA与NMF之间的比较。希望大家在阅读下面的内容之前,已经了解了K均值,PCA与NMF算法的基础知识。

如果不清楚的话,可以点击下面的链接,来简单阅读下:
K均值:无监督学习——K均值聚类(上)
PCA:主成分分析(PCA)应用(上)主成分分析(PCA)应用(下)
NMF:非负矩阵分解(NMF)

图像重建与矢量量化

虽然k均值是一种聚类算法,但在k均值和分解方法(比如之前讨论的PCA和NMF)之间存在一些有趣的相似之处。大家可能还记得,PCA试图找到数据中方差最大的方向,而NMF试图找到累加的分量,这通常对应于数据的“极值”或“部分”。两种方法都试图将数据点表示为一些分量之和。与之相反,k均值则尝试利用簇中心来表示每个数据点。你可以将其看作仅用一个分量来表示每个数据点,该分量有簇中心给出。这种观点将k均值看作是一种分解方法,其中每个点用单一分量来表示,这种观点被称为矢量量化实际上,对于k均值来说,重建就是在训练集中找到的最近的簇中心。

接下来,我们通过一个实际例子来比较三者之间的关系。

1.数据来源

LFW - People (Face Recognition):https://www.kaggle.com/atulanandjha/lfwpeople?select=pairs.txt

这是kaggle网站上一个专门用来做人脸识别的数据集,收录了网站上超过13000张人脸图片。接下来把这份图片数据集下载下来并解压。

PS:下载下来的图片保存在lfw-funneled.tgz文件里,”.tgz”是一种压缩文件的格式,所以我们只要解压缩就可以了。

解压完毕后,我们就可以看见图片存储在以每人的名字所命名的文件里,每个文件夹包含数量不同的照片,而每个照片又分别以名字+数字的名字命名,方便我们使用。

2.数据处理

我之前写了一篇文章来讲图像数据处理方面的知识,需要了解的朋友可以自行阅读这篇文章,这里就不再赘述了。(传送门:主成分分析(PCA)应用(上)

处理代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import os
all_folds = os.listdir(r'C:\Users\Administrator\Desktop\源数据-分析\lfw_funneled')
all_folds = [x for x in all_folds if '.' not in x]
import pandas as pd
numbers_img=pd.DataFrame(columns=["文件名称","图片数量"])####统计各个文件夹里面的图片数量
for i in range(len(all_folds)):
path = 'C:\\Users\\Administrator\\Desktop\\源数据-分析\\lfw_funneled\\'+all_folds[i]
all_files = os.listdir(path)
numbers_img.loc[i]=[all_folds[i],len(all_files)]
img_10=numbers_img[numbers_img["图片数量"]==10].reset_index()#####为了降低数据偏斜,选取图片数量为10的文件(否则,特征提取会被图片数量过多的数据影响)
from PIL import Image
import numpy as np
image_arr_list=[]###存放灰度值numpy数组
flat_arr_list=[]###存放灰度值一维数组
target_list=[]###存放目标值
for m in range(len(img_10["文件名称"])):
file_address='C:\\Users\\Administrator\\Desktop\\源数据-分析\\lfw_funneled\\'+img_10["文件名称"][m]+"\\"####指定特定的文件地址
image_name=os.listdir(file_address)###获得指定文件夹下的左右文件名称
for n in image_name:
image=Image.open(file_address+n)
image=image.convert('L')###RGB(红绿蓝)像素值转换成灰度值
image_arr=np.array(image,"f")###灰度值转化成numpy数组(二维)
flat_arr=image_arr.ravel()###将数组扁平化处理,返回的是一个一维数组的非副本视图,就是将几行的数据强行拉成一行
image_arr_list.append(image_arr)
flat_arr_list.append(flat_arr)
target_list.append(m)###这里的m设定是数字,如果是文本的话后面的算法会报错
faces_dict={"images":np.array(image_arr_list),"data":np.array(flat_arr_list),"target":np.array(target_list)}

3.划分数据集并进行建模

下面我们划分数据集,并利用NMF,PCA和K均值来依次进行建模,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
from sklearn.model_selection import train_test_split
train=faces_dict["data"]/255
X_train,X_test,y_train,y_test=train_test_split(train,faces_dict["target"],random_state=0)###划分训练集和测试集
from sklearn.decomposition import PCA
from sklearn.cluster import KMeans
from sklearn.decomposition import NMF
nmf=NMF(n_components=100,random_state=0)
nmf.fit(X_train)
pca=PCA(n_components=100,random_state=0)
pca.fit(X_train)
kmeans=KMeans(n_clusters=100,random_state=0)
kmeans.fit(X_train)###注意这是训练数据

然后,我们依次利用训练好的模型,依次生成重建数据集:

1
2
3
X_reconstructed_pca=pca.inverse_transform(pca.transform(X_test))###注意这是测试数据模型
X_reconstructed_kmeans=kmeans.cluster_centers_[kmeans.predict(X_test)]
X_reconstructed_nmf=np.dot(nmf.transform(X_test),nmf.components_)

4.恢复图像

为了方便接下来的图像展示,我们首先将上面的重建数据整合在一起,构成一个二维列表:

1
2
3
4
5
6
7
8
list=[X_test,X_reconstructed_nmf,X_reconstructed_kmeans,X_reconstructed_pca]
X_reconstructed=[[],[],[],[]]
shape=image_arr.shape###获得二维数组的维度
for i in range(len(list)):
for m in list[i]:
vector=np.matrix(m)####将一维数组转换成矩阵
arr2=np.asarray(vector).reshape(shape)###可以通过这个矩阵将一维数组转换为原灰度值numpy数组,即arr2=image_arr
X_reconstructed[i].append(arr2)

接下来,为了比较方便,我们只选取前5张图像,并利用多图表结构将其展现出来

1
2
3
4
5
6
7
8
9
10
11
12
13
import seaborn as sns
plt.style.use("fivethirtyeight")
sns.set_style({'font.sans-serif':['SimHei','Arial']})
fig,axes=plt.subplots(4,5,figsize=(45, 30))
plt.suptitle('K均值,PCA与NMF的图像还原比较', fontsize=80, ha='center')
for l in range(len(X_reconstructed)):
for n in range(5):
axes[l,n].imshow(X_reconstructed[l][n],cmap="gray")###通过灰度值还原图像
axes[0,0].set_ylabel("原始图片",fontsize=50)
axes[1,0].set_ylabel("NMF",fontsize=50)
axes[2,0].set_ylabel("K均值",fontsize=50)
axes[3,0].set_ylabel("PCA",fontsize=50)
plt.show()###由于之前已经划分了数据集,这是利用训练出来的模型对测试数据集进行的图像恢复,所以只有38个图像,而不是原来的150个图像

最终结果如下所示:

在这里插入图片描述

上图就是利用了100个分量(簇中心)的k均值,PCA和NMF的图像重建对比,其中k均值的每张图像中仅使用了一个簇中心。可以看出,相对来说还是k均值的表现要好一点。当然,PCA和NMF亦可以通过调整参数来提高精度,感兴趣的朋友可以自行探究。

5.矢量量化

利用K均值做矢量量化的一个有趣之处在于,可以用比输入维度更多的簇来对数据进行编码。让我们回到two_moons数据。利用PCA或NMF,我们对这个数据无能为力,因为它只有两个维度。如果使用PCA和NMF将其降维到一维,将会完全破坏数据结构。但通过使用更多的簇中心,我们可以用K均值找到一种更具表现力的表示:

1
2
3
4
5
6
7
8
from sklearn.datasets import make_moons
plt.rcParams['axes.unicode_minus'] = False###防止坐标轴符号显示不出来
X,y=make_moons(n_samples=200,noise=0.05,random_state=0)
kmeans=KMeans(n_clusters=10,random_state=0)###使用两个簇
kmeans.fit(X)
y_pred=kmeans.predict(X)###与labels_相同,为新数据点分配簇标签
plt.scatter(X[:,0],X[:,1],c=y_pred,cmap="Paired",s=60)
plt.scatter(kmeans.cluster_centers_[:,0],kmeans.cluster_centers_[:,1],s=60,marker="*",c=range(kmeans.n_clusters),linewidth=2)

在这里插入图片描述

我们使用了10个簇中心,也就是说,现在每10个点都被分配了0到9之间的一个数字。我们可以将其看作10个分量表示的数据(有10个新特征),只有表示该点对应的簇中心的那个特征不为0,其它特征均为0。利用这个10维表示,我们就可以用线性模型来划分两个半月形,而利用原始的两个特征是不可能做到这一点的。

总结

K均值是非常流行的聚类算法,因为它不仅相对容易理解和实现,而且运行速度也相对较快。同时K均值可以轻松扩展到大型数据集。

而K均值的缺点之一在于,它依赖随机初始化,也就是说,算法的输出依赖于随机种子。默认情况下scikit-learn用10种不同的随机初始化将算法运行10次,并返回最佳结果。当然,K均值还有一个缺点,就是簇形状的假设的约束性较强,而且还要求指定所要寻找的簇的个数(在现实世界中可能并不知道这个数字)。

有很多地方做的不是很好,欢迎网友来提出建议,也希望可以遇到些朋友来一起交流讨论。