K-means聚类 & 降维PCA
以下实验所使用的数据集是吴恩达机器学习提供的,是第七次作业(ex7)K-means聚类 & 降维PCA。
实验目录如图所示:
(一)K-均值聚类 K-means Clustering
在这部分实验中,将实现K-means算法,并使用它来压缩图像。首先从一个简单的2D数据集开始,来了解K-means算法是如何工作的。然后将其应用于图像压缩去减少图像中颜色的数量,只留下图像中常见的颜色。
说明:在这部分实验中,所使用的数据集是ex7data2.mat
,并将以下的代码存放于k-means-ex7data2.py
中。(没有说明需要重新建立新的py文件时,所有的代码都存放于同一个文件里。)
(1)运行K-均值 Implementing K-means
K-means算法是自动地将相似数据聚集在一起的一种方式。首先给定数据集 { x ( 1 ) , x ( 2 ) , ⋯ , x ( m ) } \left \{ x^{(1)},x^{(2)},\cdots ,x^{(m)} \right \} {x(1),x(2),⋯,x(m)},想要将组合成一些簇。K-means算法通过猜测每个簇的初始聚类中心开始,然后重复将数据样本分配给最近的簇,并重新计算该簇的聚类中心。
- Finding closest centroids 寻找最近的质心
在K-means算法的分配簇的阶段,算法将每一个训练样本 x ( i ) x^{(i)} x(i)分配给最接近的簇中心。对于每一个例子 i i i都设为: c ( i ) : = k t h a t m i n i m i z e s ∥ x ( i ) − μ k ∥ 2 \begin{matrix} c^{(i)}:= k & that & minimizes & \left \| x^{(i)}-\mu _{k} \right \|^{2} \end{matrix} c(i):=kthatminimizes∥∥x(i)−μk∥∥2 其中, c ( i ) c^{(i)} c(i)是最靠近 x ( i ) x^{(i)} x(i)簇的索引(即聚类中心), μ k \mu _{k} μk是第 k k k个聚类中心点的位置(值)。
首先,导入所需要的库:
# -*- coding: utf-8 -*-
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import scipy.optimize as opt
from scipy.io import loadmat
编写寻找最近的质心的代码:
def findClosestCentroids(X, centroids):
'''找出聚类中心,其中输入是数据X与初始聚类中心centroids,
输出是距离聚类中心最接近的索引.'''
m = X.shape[0]
k = centroids.shape[0]
index = np.zeros(m) #以上代码是获取矩阵的维度
for i in range(m):
# 计算每个训练样本到哪个聚类中心距离最小
index[i] = np.argmin(np.sum(np.power((X[i] - centroids), 2), axis = 1))
return index
data = loadmat('data/ex7data2.mat')
X = data['X']
K = 3
max_iters = 10
INIT_centroids = np.array([[3, 3], [6, 2], [8, 5]]) #初始化聚类中心
index = findClosestCentroids(X, INIT_centroids)
print(index[0:3])
运行结果为:
[0. 2. 1.]
课程提供的结果如图所示。
说明: 初始化聚类中心的值是吴恩达机器学习中提供的。但是为什么输出结果与提供的结果不同呢?是由于在python中数组是从0开始索引的,而不是从1开始的,所以值比课程中的值小1。
解释一下这条语句:
index[i] = np.argmin(np.sum(np.power((X[i] - centroids), 2), axis = 1))
np.power((X[i] - centroids), 2)
表示这个公式 ∥ x ( i ) − μ k ∥ 2 \left \| x^{(i)}-\mu _{k} \right \|^{2} ∥∥x(i)−μk∥∥2。
np.sum(np.power((X[i] - centroids), 2), axis = 1)
表示有 i i i个训练样本,将所有的数据求和,axis=1是指对于按行求和(axis=0按列)。
np.argmin()
表示最小值在数组中所在的位置。
- Computing centroid means 计算质心平均值
将分配好每一个数据对应的聚类中心后,第二阶段是需要重新计算每个聚类中心,该值为这个聚类中心所有点位置的均值。对于每一个聚类中心 k k k都设为: μ ( k ) : = 1 ∣ C k ∣ ∑ i ∈ C k x ( i ) \begin{matrix} \mu ^{(k)}:= \frac{1}{\left | C_{k}\right |} \sum_{i\in C_{k}}^{ }x^{(i)} \end{matrix} μ(k):=∣Ck∣1∑i∈Ckx(i) 其中, C k C_{k} Ck分配给聚类中心 k k k的样本集。
编写计算质心均值的代码为:
def computeCentroids(X, index, K):
'''计算聚类中心'''
(m,n) = X.shape
centroids = np.zeros((K,n))
for i in range(K):
centroids[i] = np.mean(X[np.where(index == i)], axis = 0)
return centroids
centroids = computeCentroids(X, index, K)
print('Centroids computed after initial finding of closest centroids:\n{}'.format(centroids))
print('the centroids should be')
print('[[ 2.428301 3.157924 ]')
print(' [ 5.813503 2.633656 ]')
print(' [ 7.119387 3.616684 ]]')
运行结果为:
Centroids computed after initial finding of closest centroids:
[[2.42830111 3.15792418]
[5.81350331 2.63365645]
[7.11938687 3.6166844 ]]
the centroids should be
[[ 2.428301 3.157924 ]
[ 5.813503 2.633656 ]
[ 7.119387 3.616684 ]]
说明:
np.mean()
表示求取均值。如果是二维矩阵,axis=0
返回纵轴的平均值,axis=1
返回横轴的平均值。详情请参考np.mean()
np.where(index == i)
表示返回的是符合条件的函数,如这里返回的是
i
n
d
e
x
=
i
index=i
index=i对应的在index中的位置。请参考np.where()
(2)在数据集上使用K-均值 K-means on example dataset
在这部分将进行利用样本集使用K-means算法的迭代次数和可视化结果。为了使用算法,需要将样本分配给最近的簇并重新计算簇的聚类中心。
编写可视化数据的代码,并进行使用:
def plot_Data(X, centroids, index=None):
'''可视化数据,然后各自着色
centroids:含每次中心点的记录
index:最后一次迭代生成的index向量,存储每个样本分配的聚类中心点的值'''
color = ['red','g','b','gold','darkorange','salmon','olivedrab',
'maroon', 'navy', 'sienna', 'tomato', 'lightgray', 'gainsboro']
subX = [] #存放已分好的样本点
if index is not None:
for i in range(centroids[0].shape[0]):
x_i = X[index == i]
subX.append(x_i)
else:
subX = [X] # 将X转化为一个元素的列表,每个元素为每个簇的样本集
plt.figure()
for i in range(len(subX)):
XX = subX[i]
plt.scatter(XX[:,0], XX[:,1], c = color[i],s = 20, label = 'Cluster %d'%i)
#plt.scatter(centroids[:,0], centroids[:,1],c = 'black',marker = 'x')
plt.legend()
plt.xlabel('x_1')
plt.ylabel('x_2')
plt.title('Iteration number {}'.format(i + 1))
XX, YY =[], []
for centroid_k in centroids:
XX.append(centroid_k[:,0])
YY.append(centroid_k[:,1])
plt.plot(XX, YY, 'kx-') #绘制聚类中心移动轨迹
plot_Data(X, [INIT_centroids])
可视化数据的运行结果如下:
编写使用K-means算法的代码,如下所示:
def run_Kmeans(X, centroids, max_iters):
#数值初始化
(m, n) = X.shape
K = centroids.shape[0]
centroids_all = []
centroids_all.append(centroids)
centroids_i = centroids
index = np.zeros(m)
#运行K-means
for i in range(max_iters):
print('K-Means iteration {}/{}'.format((i + 1),max_iters))
#找每个训练样本距离最近的聚类中心
index = find_Closest_Centroids(X, centroids_i)
#重新计算新的聚类中心
centroids_i = compute_Centroids(X, index, 3)
centroids_all.append(centroids_i)
return centroids_all, index
centroids_all, index = run_Kmeans(X, INIT_centroids, 10)
plot_Data(X, centroids_all, index)
运行结果为:
K-Means iteration 1/10
K-Means iteration 2/10
K-Means iteration 3/10
K-Means iteration 4/10
K-Means iteration 5/10
K-Means iteration 6/10
K-Means iteration 7/10
K-Means iteration 8/10
K-Means iteration 9/10
K-Means iteration 10/10
说明: 从上面Figure1经过多次迭代得到Figure2,黑色的叉叉表示的是聚类中心的位置,黑线表示聚类中心移动的过程,最后聚类中心能够找到合适的位置。
(3)随机初始化 Random initialization
在运行 K-均值算法的之前,我们首先要随机初始化所有的聚类中心点,下面介绍怎样
做:
我们应该选择K < m,即聚类中心点的个数要小于所有训练集实例的数量
随机选择K个训练实例,然后令K个聚类中心分别与这K个训练实例相等
K-均值的一个问题在于,它有可能会停留在一个局部最小值处,而这取决于初始化的情况。
为了解决这个问题,我们通常需要多次运行 K-均值算法,每一次都重新进行随机初始化,最后再比较多次运行 K-均值的结果,选择代价函数最小的结果。但是如果K较大,这么做也可能不会有明显地改善。
在运行 K-Means 算法之前,首先要随机初始化所有的聚类中心点,步骤如下:
- 应该选择 K < m K<m K<m,即聚类中心点的个数要小于所有训练集实例的数量。
- 随机选择 K K K 个训练实例,然后令 K K K 个聚类中心分别与这 K K K 个训练实例相等。
K-means算法存在一个问题:它有可能会停留在一个局部最小值处,这往往取决于初始化的状态。因此改进初始化的方式是进行多次随机初始化,比较多次运行K-means的结果,最终选择损失函数最小的结果。
编写随机初始化的函数代码,并进行三次随机初始化操作:
def init_Centroids(X, K):
'''进行随机初始化'''
(m, n) = X.shape
index = np.random.choice(m, K)
centroids = X[index]
return centroids
for i in range(3):
centroids = init_Centroids(X, 3)
centroids_all, index = run_Kmeans(X, centroids, 10)
plot_Data(X, centroids_all, index)
print(centroids)
说明: np.random.choice()
表示从
m
m
m中抽取随机数。
numpy.random.choice(a, size=None, replace=True, p=None)
从a(数组)中选取size(维度)大小的随机数,replace=True表示可重复抽取,p是a中每个数出现的概率。若a是整数,则a代表的数组是arange(a)。
运行结果如下:
调用三次随机初始化函数,得到如下三种不同的结果。对比三组结果,发现第一组结果最理想,这是因为聚类中心落在了局部最优的范围里。结果输出的是聚类后的结果图、聚类中心的移动方向和位置以及随机选取的聚类中心。
[[4.01714917 1.16070647]
[1.52334113 4.87916159]
[2.12169543 5.20854212]]
[[6.44765183 3.16560945]
[3.81422865 4.73526796]
[5.78769095 3.29255127]]
[[6.78110732 3.05676866]
[4.37271861 1.02914092]
[5.132009 2.19812195]]
(4)K-均值图像压缩 Image compression with K-means
在这部分练习中,将会用到K-means算法来进行图像的压缩。有一幅24位颜色的图像,每一个像素被表示为三个8位的无符号整数(0~255),并指定了红、绿和蓝的亮度值。这样的编码通常被称为RGB编码。
在这里,将会把图像的颜色数量减少到16种。这可以有效地压缩图像,具体操作是只要存储所选择的16种RGB的值,对于图像中的每一个像素,只需要将这个颜色的索引存储在这个颜色的位置(即,只需要4 bits就能表示16种可能性)。下面,用K-means算法选择16种颜色,用于图像压缩。具体地,要把原始图像的每个像素看成一个数据样本,并使用K-means算法找出在RGB中分组最好的16种颜色。
在下面的实验中,需要重新建立一个文件,命名为k-means-bird.py
,所使用的数据是bird_small.mat
以及bird_small.png
。
同样的,首先导入所需要的库:
# -*- coding: utf-8 -*-
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import scipy.optimize as opt
from scipy.io import loadmat
#from skimage import io
from PIL import Image
from pylab import *
import k_means
再把ex7data1.py
中所编写的函数代码打包放入一个文件里,命名为k_means.py
。需要注意的是,同时也要保存一个 __init__.py
,不然调用时会出错。使用里面的函数代码时,用import k_means
导入即可。
k_means.py代码如下:
# -*- coding: utf-8 -*-
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import scipy.optimize as opt
from scipy.io import loadmat
def find_Closest_Centroids(X, centroids):
'''找出最近的聚类中心,其中输入是数据X与初始聚类中心centroids,
输出是距离聚类中心最接近的索引.'''
m = X.shape[0]
k = centroids.shape[0]
index = np.zeros(m) #以上代码是获取矩阵的维度
for i in range(m):
# 计算每个训练样本到哪个聚类中心距离最小
'''np.argmin()'''
#index[i] = np.argmin(np.sqrt(np.sum(np.power((centroids - X[i]), 2), axis = 1)))
index[i] = np.argmin(np.sum(np.power((X[i] - centroids), 2), axis = 1))
return index
def compute_Centroids(X, index, K):
'''计算聚类中心'''
(m,n) = X.shape
centroids = np.zeros((K,n))
for i in range(K):
centroids[i] = np.mean(X[np.where(index == i)], axis = 0)
return centroids
def init_Centroids(X, K):
'''进行随机初始化'''
(m, n) = X.shape
index = np.random.choice(m, K)
centroids = X[index]
return centroids
def run_Kmeans(X, centroids, max_iters):
#数值初始化
(m, n) = X.shape
K = centroids.shape[0]
centroids_all = []
centroids_all.append(centroids)
centroids_i = centroids
index = np.zeros(m)
#运行K-means
for i in range(max_iters):
print('K-Means iteration {}/{}'.format((i + 1),max_iters))
#找每个训练样本距离最近的聚类中心
index = find_Closest_Centroids(X, centroids_i)
#重新计算新的聚类中心
centroids_i = compute_Centroids(X, index, 3)
centroids_all.append(centroids_i)
return centroids_all, index
编写导入数据集的代码,并显示其维数:
'''--------------------Image compression-------------------------'''
img = array(Image.open('data/bird_small.png'))
image = loadmat('data/bird_small.mat')
A = image['A']
print('A.shape:',A.shape)
plt.imshow(img)
plt.title('Orginal Image')
#数据归一化(除以255,使所有值都在0-1范围内)
A = A / 255
# 重置矩阵大小
#reshape(-1,3)含义是将A的矩阵转换为3列,尚不知多少行,用-1代替
X = A.reshape(-1, 3)
print('X.shape:',X.shape)
运行结果为:
A.shape: (128, 128, 3)
X.shape: (16384, 3)
对数据进行预处理,并提供给K-means算法:
#设定聚类中心的个数
K = 16
# 随机初始化聚类中心
centroids = k_means.init_Centroids(X, K)
#运行聚类算法,计算聚类中心
centroids_all, index = k_means.run_Kmeans(X, centroids, 10)
print('聚类中心的位置数据:',centroids_all)
print('聚类中心的索引位置:',index)
计算聚类中心的结果如下所示:
聚类中心的位置数据: [array([[0.09803922, 0.10588235, 0.09019608],
[0.9254902 , 0.89019608, 0.57254902],
[0.85098039, 0.7372549 , 0.63137255],
[0.50588235, 0.55294118, 0.49411765],
[0.08627451, 0.08235294, 0.0745098 ],
[0.0745098 , 0.07843137, 0.05490196],
[0.65882353, 0.61176471, 0.34509804],
[0.13333333, 0.15294118, 0.14117647],
[0.0627451 , 0.07058824, 0.05882353],
[0.63137255, 0.50588235, 0.45490196],
[0.16470588, 0.19215686, 0.17254902],
[0.71764706, 0.79215686, 0.76078431],
[0.22745098, 0.19607843, 0.21176471],
[0.05490196, 0.06666667, 0.04705882],
[0.08235294, 0.09019608, 0.05882353],
[0.91764706, 0.59215686, 0.25490196]]), array([[0.101575 , 0.10939901, 0.09886817],
[0.9718552 , 0.90304274, 0.68977577],
[0.8466393 , 0.71762462, 0.57088886]]), array([[0.19583382, 0.17840197, 0.15554039],
[0.95647132, 0.9043221 , 0.75181969],
[0.733496 , 0.60494906, 0.43958703]]), array([[0.18352791, 0.16966828, 0.15096799],
[0.92891878, 0.86948529, 0.73522867],
[0.71382974, 0.58173699, 0.40909762]]), array([[0.17525048, 0.16371317, 0.14725446],
[0.91733273, 0.85440553, 0.72193783],
[0.70158133, 0.56866173, 0.39550039]]), array([[0.1706969 , 0.16039071, 0.14497911],
[0.91129305, 0.84528114, 0.70955593],
[0.69248969, 0.55939693, 0.38800919]]), array([[0.16780542, 0.15832677, 0.14341371],
[0.90652383, 0.83707744, 0.69785578],
[0.68448029, 0.55143868, 0.38197067]]), array([[0.16510236, 0.15632815, 0.14202429],
[0.90330373, 0.83093315, 0.68810843],
[0.67698552, 0.54438961, 0.37722125]]), array([[0.16262774, 0.15451815, 0.14064666],
[0.90133543, 0.82621118, 0.67975541],
[0.66998029, 0.53818858, 0.37382635]]), array([[0.16089779, 0.15320011, 0.13959154],
[0.89885004, 0.82238714, 0.67395452],
[0.66497339, 0.53338781, 0.37081295]]), array([[0.16000868, 0.1526235 , 0.13915844],
[0.89700421, 0.81925909, 0.66933667],
[0.66146354, 0.52995331, 0.36855779]])]
聚类中心的索引位置: [2. 1. 1. ... 0. 0. 0.]
# 把每一个像素值与聚类中心的结果进行匹配
X_recovered = centroids[index.astype(int),:]
# reshape to the original dimensions
X_recovered = np.reshape(X_recovered, (A.shape[0], A.shape[1], A.shape[2]))
print('X_recovered.shape:',X_recovered)
运行结果如下所示:
X_recovered.shape: [[[0.85098039 0.7372549 0.63137255]
[0.9254902 0.89019608 0.57254902]
[0.9254902 0.89019608 0.57254902]
...
[0.09803922 0.10588235 0.09019608]
[0.09803922 0.10588235 0.09019608]
[0.09803922 0.10588235 0.09019608]]
[[0.9254902 0.89019608 0.57254902]
[0.9254902 0.89019608 0.57254902]
[0.9254902 0.89019608 0.57254902]
...
[0.09803922 0.10588235 0.09019608]
[0.09803922 0.10588235 0.09019608]
[0.09803922 0.10588235 0.09019608]]
[[0.9254902 0.89019608 0.57254902]
[0.9254902 0.89019608 0.57254902]
[0.9254902 0.89019608 0.57254902]
...
[0.09803922 0.10588235 0.09019608]
[0.09803922 0.10588235 0.09019608]
[0.09803922 0.10588235 0.09019608]]
...
[[0.09803922 0.10588235 0.09019608]
[0.09803922 0.10588235 0.09019608]
[0.09803922 0.10588235 0.09019608]
...
[0.09803922 0.10588235 0.09019608]
[0.09803922 0.10588235 0.09019608]
[0.09803922 0.10588235 0.09019608]]
[[0.09803922 0.10588235 0.09019608]
[0.09803922 0.10588235 0.09019608]
[0.09803922 0.10588235 0.09019608]
...
[0.09803922 0.10588235 0.09019608]
[0.09803922 0.10588235 0.09019608]
[0.09803922 0.10588235 0.09019608]]
[[0.09803922 0.10588235 0.09019608]
[0.09803922 0.10588235 0.09019608]
[0.09803922 0.10588235 0.09019608]
...
[0.09803922 0.10588235 0.09019608]
[0.09803922 0.10588235 0.09019608]
[0.09803922 0.10588235 0.09019608]]]
编写可视化图像的代码:
plt.subplot(1, 2, 1)
plt.imshow(img)
plt.title('Original Image')
plt.subplot(1, 2, 2)
plt.imshow(X_recovered)
plt.title('Compressed, with {} colors'.format(K))
运行结果如下所示:
说明: 不同的随机化初始化值得到不同结果,进行多次随机初始化,得到如下的不同结果。很明显看出聚类效果并不好,应该是由于随机初始化聚类中心有关,掉入了局部最优解。在这里也可以调用scikit-learn来实现K-means算法,代码如下所示。
这部分代码可以一起存放于k-means-bird.py
文件里。
img1 = mpimg.imread('data/bird_small.png') #读取图片
print('输出img1的数值:',img1) #查看下是不是0-1之间
data1 = img1 #如果是0-255的话需要除以255,如果已经是0-1就不需要
#data1 = img1 / 255 #若不是在0~1之间,则需要用这条语句
data1 = data1.reshape(data1.shape[0]*data1.shape[1],3)
#导入K-means库
#from sklearn.cluster import KMeans
from sklearn.cluster import MiniBatchKMeans
K = 16
kmeans = MiniBatchKMeans(K)
kmeans.fit(data1) #首先进行fit
# 16个簇的簇中心
picture_recolored = kmeans.cluster_centers_[kmeans.predict(data1)] #再进行predict,然后再分配标记的聚类中心的RGB编码
picture_recolored = picture_recolored.reshape(picture.shape)
fig, ax = plt.subplots(1, 2)
ax[0].imshow(picture)
ax[1].imshow(picture_recolored)
plt.show()
运行结果为:由于图像的数值已经是0~1之间,因此不需要再进行除以255。若不是归一化的值,则需要进行归一化再进行后面的步骤。
输出img1的数值: [[[0.85882354 0.7058824 0.40392157]
[0.9019608 0.7254902 0.45490196]
[0.8862745 0.7294118 0.43137255]
...
[0.05490196 0.05882353 0.05098039]
[0.05098039 0.05882353 0.04705882]
[0.04705882 0.05490196 0.04705882]]
[[0.9019608 0.75686276 0.46666667]
[0.8784314 0.7529412 0.47058824]
[0.8862745 0.7529412 0.4862745 ]
...
[0.0627451 0.0627451 0.05098039]
[0.05490196 0.05882353 0.03921569]
[0.04313726 0.05490196 0.03529412]]
[[0.89411765 0.7490196 0.48235294]
[0.89411765 0.7490196 0.4745098 ]
[0.8627451 0.7254902 0.4627451 ]
...
[0.05490196 0.0627451 0.05098039]
[0.05098039 0.05098039 0.04313726]
[0.04313726 0.05882353 0.03921569]]
...
[[0.05882353 0.07058824 0.0627451 ]
[0.07058824 0.08235294 0.07058824]
[0.07058824 0.07450981 0.0627451 ]
...
[0.31764707 0.1764706 0.1764706 ]
[0.27450982 0.16862746 0.13725491]
[0.28235295 0.2 0.16862746]]
[[0.0627451 0.06666667 0.06666667]
[0.06666667 0.07058824 0.07450981]
[0.07843138 0.07450981 0.07843138]
...
[0.3137255 0.14901961 0.15686275]
[0.26666668 0.15294118 0.15686275]
[0.23137255 0.16862746 0.16470589]]
[[0.05882353 0.07450981 0.07450981]
[0.07843138 0.07843138 0.07058824]
[0.07058824 0.07450981 0.06666667]
...
[0.25490198 0.16862746 0.15294118]
[0.22745098 0.14509805 0.14901961]
[0.20392157 0.15294118 0.13333334]]]
对比结果:第二幅图的聚类结果比第一幅的结果效果要好一些。
特别说明:
在当前大数据的背景下,工程师们往往为了追求更短的计算时间,不得不在一定程度上减少算法本身的计算精度。在一定程度上,所以肯定不能只追求速度而不顾其它。在KMeans聚类中,为了降低计算时间,KMeans算法的变种Mini Batch KMeans算法应运而生。
Mini Batch KMeans算法是一种能尽量保持聚类准确性下但能大幅度降低计算时间的聚类模型,采用小批量的数据子集减少计算时间,同时仍试图优化目标函数,这里所谓的Mini Batch是指每次训练算法时随机抽取的数据子集,采用这些随机选取的数据进行训练,大大的减少了计算的时间,减少的KMeans算法的收敛时间,但要比标准算法略差一点,建议当样本量大于一万做聚类时,就需要考虑选用Mini Batch KMeans算法。
这种对时间优化的思路不仅应用在KMeans聚类,还广泛应用于梯度下降、深度网络等机器学习和深度学习算法。在sklearn.cluster 中MiniBatchKMeans与KMeans方法的使用基本是一样的。
(二)主成分分析法 Principal Component Analysis
在这部分实验中,将运用PCA来实现降维。首先通过一个2D数据集进行实验,来感受PCA是如何进行工作的,然后在一个5000张脸的数据集中进行使用。
(1)示例数据集 Example Dataset
为了帮助理解PCA是怎么样工作的,首先从一个二维的数据集开始,这个数据集有一个大的变化方向与一个小的变化方向。在这部分实验中,当使用PCA将数据从2D减少到1D时,会看到会发生什么。可能将256维减少到50维,使用更低的维度可以可视化数据,使得算法进行得更好。
重新建立一个py文件,命名为pca-ex7data1.py
,所使用的数据集是ex7data1.mat
。
首先导入需要的库,然后载入数据集并可视化数据集,最后输出数据集的维数,代码如下所示:
# -*- coding: utf-8 -*-
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import scipy.optimize as opt
from scipy.io import loadmat
data = loadmat('data/ex7data1.mat')
X = data['X']
print('X.shape:',X.shape)
#plt.axis('equal')
plt.scatter(X[:,0], X[:,1], facecolors='none', edgecolors='b',s = 15)
运行结果为:
X.shape: (50, 2)
(2)运行PCA Implementing PCA
PCA需要进行下面四个步骤:
第一步,数据预处理(均值归一化或者特征缩放);
第二步,计算数据的斜方差矩阵,公式为: Σ = 1 m X T X \Sigma =\frac{1}{m}X^{T}X Σ=m1XTX其中 X X X是以行为单位的数据矩阵, m m m是样本数。注意 ∑ ∑ ∑是一个 n × n n×n n×n矩阵,不是求和运算符;
第三步,求出协方差矩阵的特征值与正交单位特征向量(或者,也可以使用SVD奇异值分解计算特征向量 U 1 , U 2 , ⋯ , U n U_{1},U_{2},\cdots,U_{n} U1,U2,⋯,Un或 e i g eig eig求特征向量);
第四步,取 U U U矩阵(特征向量矩阵)中前 k k k个向量得到一个 n n n× k k k维的矩阵 U r e d u c e U_{reduce} Ureduce,转置后乘以数据 x x x就可得到降维后的数据 z = X U r e d u c e z=XUreduce z=XUreduce ,注意 z z z值仅仅是近似值。
编写第一步数据预处理(均值归一化)的代码:
def feature_Normalize(X): #数据预处理
means = np.mean(X, 0) #获取平均值
sigma = np.std(X, 0, ddof=1) #获取标准差
X_norm = (X - means) / sigma # 归一化数据
return X_norm, means, sigma
print('Running PCA on example dataset.')
#归一化数据
X_norm, means, sigma =feature_Normalize(X)
#查看归一化后的数据
print('X_norm前五个数据:',X_norm[:5])
#检查归一化前的数据
print('X前五个数据:',X[:5])
#平均值
print('平均值means:',means)
#标准差
print('标准差sigma:',sigma)
运行结果为:
Running PCA on example dataset.
X_norm前五个数据: [[-0.5180535 -1.57678415]
[ 0.45915361 0.83189934]
[-1.13685138 -0.57729787]
[-1.04345995 -1.25794647]
[-0.97413176 -0.80837709]]
X前五个数据: [[3.38156267 3.38911268]
[4.52787538 5.8541781 ]
[2.65568187 4.41199472]
[2.76523467 3.71541365]
[2.84656011 4.17550645]]
平均值means: [3.98926528 5.00280585]
标准差sigma: [1.17304991 1.02340778]
编写第二步计算协方差的代码 & 第三步使用SVD奇异值分解计算特征向量,并运行输出PCA图像:
def pca(X): #协方差矩阵计算公式
#获取原始数据的维度
(m, n) = X.shape
#需要返回正确的变量
U = np.zeros(n)
S = np.zeros(n)
#计算数据的斜方差矩阵
sigma = np.dot(X.T, X) / m
U, S, V = np.linalg.svd(sigma, full_matrices=True, compute_uv=True)
return U, S, V
#调用函数进行主成分分析,并输出结果
U, S, V = pca(X_norm)
print('主成分矩阵U:',U) #主成分矩阵
print('对角矩阵S:',S) #对角矩阵
print('V:',V)
#绘制
plt.figure()
plt.scatter(X[:,0], X[:,1], facecolors='none', edgecolors='b', s = 15)
plt.plot([means[0], means[0] + 1.5*S[0]*U[0,0]],
[means[1], means[1] + 1.5*S[0]*U[0,1]],
c='r', linewidth=2, label='First Principal Component')
plt.plot([means[0], means[0] + 1.5*S[1]*U[1,0]],
[means[1], means[1] + 1.5*S[1]*U[1,1]],
c='g', linewidth=2, label='Second Principal Component')
plt.grid()
plt.axis("equal")
plt.legend()
print('Top eigenvector: \nU[:, 0] = {}'.format(U[:, 0]))
运行结果为:
主成分矩阵U: [[-0.70710678 -0.70710678]
[-0.70710678 0.70710678]]
对角矩阵S: [1.70081977 0.25918023]
V: [[-0.70710678 -0.70710678]
[-0.70710678 0.70710678]]
分别去除两个点,以便可以绘制出PCA的图像:
Top eigenvector:
U[:, 0] = [-0.70710678 -0.70710678]
(3)PCA降维 Dimensionality Reduction with PCA
把数据进行规范化,并显示出来:
plt.figure()
plt.scatter(X_norm[:, 0], X_norm[:, 1], facecolors='none', edgecolors='b', s=20)
plt.axis('equal')
plt.axis([-4, 3, -4, 3])
下图显示原始数据图像与规范化后的数据图像:
第四步,取
U
U
U矩阵(特征向量矩阵)中前
k
k
k个向量得到一个
n
n
n×
k
k
k维的矩阵
U
r
e
d
u
c
e
U_{reduce}
Ureduce.
在上面已经说明了主成分(
U
U
U矩阵),可以用这些函数将原始数据投影到一个比较低维的空间中,编写实验代码来实现投影数据:
#投影数据函数(降维)
def project_Data(X, U, K):
'''X是原始数据矩阵,U是特征向量矩阵,K是降维所降到的维数'''
Z = np.dot(X, U[:, 0:K])
return Z
# 将数据投影到k=1维度上(降维)
K = 1
Z = project_Data(X_norm, U, K)
print('Projection of the first example: {}'.format(Z[0])) #1.481274
print('Z.shape:\n', Z.shape) #(50,1)
运行结果为:
Projection of the first example: [1.48127391]
Z.shape:
(50, 1)
数学计算公式为:
X
a
p
p
r
o
x
=
U
r
e
d
u
c
e
⋅
z
,
X
a
p
p
r
o
x
≈
X
X_{approx}=U_{reduce}\cdot z,X_{approx}\approx X
Xapprox=Ureduce⋅z,Xapprox≈X
编写重建数据的代码,通过反向计算来恢复数据:
#重建数据函数
def recover_Data(Z, U, K):
X_rec = np.dot(Z, U[:,0:K].T)
return X_rec
X_rec = recover_Data(Z, U, K)
print('Approximation of the first example: {}'.format(X_rec[0])) #-1.047419 -1.047419
运行结果为:
Approximation of the first example: [-1.04741883 -1.04741883]
编写可视化投影的代码,如下所示:
# 绘制将投影点连接到原始点的线
plt.figure()
plt.scatter(X_norm[:, 0], X_norm[:, 1], facecolors='none', edgecolors='b', s=20, label='Original Data Points')
plt.scatter(X_rec[:, 0], X_rec[:, 1], facecolors='none', edgecolors='r', s=20, label='PCA Reduced Data Points')
for i in range(len(X_norm)):
plt.plot([X_norm[i,0], X_rec[i,0]], [X_norm[i,1], X_rec[i,1]], 'k--')
plt.title('The normalized and projected data after PCA')
plt.legend()
运行结果为:
红色的点代表数据点投影后的点,蓝色的点代表原始的数据点。
(4)人脸图像数据集 Face Image Dataset
在这部分练习中,需要使用上面(2)和(3)编写的函数代码,将这些函数存放于一个命名为pca.py
的文件中。同样的,由于这个py文件与k_means.py
放置于同一个文件夹中,则不再重复重建一个 __init__.py
。使用所编写的函数应用于人脸图像数据集中,创建一个为pca_faces.py
的文件。
首先,导入需使用的库及人脸数据集,并进行可视化前100张人脸图像:
# -*- coding: utf-8 -*-
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import scipy.optimize as opt
from scipy.io import loadmat
import pca
faces_data = loadmat('data/ex7faces.mat')
faces_X = faces_data['X']
print('faces_X.shape:',faces_X.shape)
def dis_Data(X, row, col):
'''显示人脸数据集'''
fig, ax = plt.subplots(row, col, sharex=True, sharey=True)
for r in range(row):
for c in range(col):
ax[r, c].imshow(X[r * col + c].reshape(32,32).T, cmap = 'gray')
plt.xticks([])
plt.yticks([])
dis_Data(faces_X, 10, 10)
运行结果为:
faces_X.shape: (5000, 1024)
接下来就在这些人脸图像上运行PCA,代码如下:
# 在运行PCA之前,需要对数据标准化
faces_X_norm, means, sigma = pca.feature_Normalize(faces_X)
# 运行PCA
U, S, V = pca.pca(faces_X_norm)
# 可视化数据中的前36个特征向量
dis_Data(U[:,:36].T, 6, 6)
运行结果为:
从1024维中取100维作为主成分,再从100维恢复到1024维,并绘制前后对比图:
Z = pca.project_Data(faces_X_norm, U, K=100)
print('Z.shape:',Z.shape) #(5000, 100)
X_rec = pca.recover_Data(Z, U, K=100)
print('X_rec.shape:',X_rec.shape) #(5000, 1024)
dis_Data(faces_X, 10, 10)
dis_Data(X_rec, 10, 10)
结果如下:
Z.shape: (5000, 100)
X_rec.shape: (5000, 1024)
再运行一组实验,若将所需要降的维数设置为K=10
以及K=1000
,那么情况是如何的呢?
当K=10
时,
当K=1000
时,
说明:通过上述两个实验的对比,当实验中选择的主成分越小时,那么从降维后恢复出原始图像的效果就越差,反之,当主成分越大,就能够很好地恢复出原始图像。很明显得看出,当K=10时,基本上恢复出来的图像能还原轮廓,但是眼睛嘴巴等细节就没有能够很好地还原回来;当K=1000时,复原的图像能很好的重构眼睛、嘴巴等细节,由于降维丢失了一些细节,色调黑与白在复原后的图像上就没有能够还原出来。
(5)PCA用在数据可视化 PCA for visualization
在这部分实验中,建立一个新的文件将其命名为pca_visualization.py
,使用的数据集依然是之前使用过的bird_small.mat
以及bird_small.png
。
编写可视化数据的代码,如下所示:
首先载入所需要的库以及数据集:
# -*- coding: utf-8 -*-
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import scipy.optimize as opt
from scipy.io import loadmat
import k_means
import pca
import matplotlib.image as mpimg
import mpl_toolkits.mplot3d as Axes3D #用来画三维图
#读取图片
img1 = mpimg.imread('data/bird_small.png')
data1 = img1
print('data1.shape:',data1.shape)
运行结果:
data1.shape: (128, 128, 3)
k-means部分,并绘制三维图:
#k-means
X = data1.reshape(data1.shape[0]*data1.shape[1],3)
print('X.shape:',X.shape)
#设定聚类中心的个数
K = 16
#最大迭代次数
max_iters = 10
# 随机初始化聚类中心
initial_centroids = k_means.init_Centroids(X, K)
centroids, index = k_means.run_Kmeans(X, initial_centroids, max_iters)
# 随机选择1000条数据进行演示
selected = np.random.randint(X.shape[0], size=1000)
print('selected.shape:',selected.shape)
#设置颜色
cm = plt.cm.get_cmap('Set1')
#绘制三维图
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.scatter(X[selected, 0], X[selected, 1], X[selected, 2], c=index[selected].astype(np.float64), cmap=cm, s=15)
plt.title('Pixel dataset plotted in 3D. Color shows centroid memberships')
运行结果:
X.shape: (16384, 3)
K-Means iteration 1/10
K-Means iteration 2/10
K-Means iteration 3/10
K-Means iteration 4/10
K-Means iteration 5/10
K-Means iteration 6/10
K-Means iteration 7/10
K-Means iteration 8/10
K-Means iteration 9/10
K-Means iteration 10/10
selected.shape: (1000,)
PCA部分,并绘制二维图:
# PCA
# 标准化(规范化)
X_norm, means, sigma = pca.feature_Normalize(X)
# 主成分分析(PCA)
U, S, V= pca.pca(X_norm)
# 投影数据函数(降维)
Z = pca.project_Data(X_norm, U, 2)
# 绘制三维图
plt.figure()
plt.scatter(Z[selected, 0], Z[selected, 1], c=index[selected], cmap=cm, s=15)
plt.title('Pixel dataset plotted in 2D, using PCA for dimensionality reduction')
实验结果为: