降维算法PCA和SVD - 属性和重要接口(三)

330 阅读8分钟

根据菜菜的课程进行整理,方便记忆理解

代码位置如下:

属性components_

现在我们了解了,V(k,n)是新特征空间,是我们要将原始数据进行映射的那些新特征向量组成的矩阵。我们用它来计算新的特征矩阵,但我们希望获取的毕竟是X_dr,为什么我们要把V(k,n)这个矩阵保存在n_components这个属性当中来让大家调取查看呢?

之前谈到过PCA与特征选择的区别,即特征选择后的特征矩阵是可解读的,而PCA降维后的特征矩阵式不可解读的:PCA是将已存在的特征进行压缩,降维完毕后的特征不是原本的特征矩阵中的任何一个特征,而是通过某些方式组合起来的新特征。通常来说,在新的特征矩阵生成之前,我们无法知晓PCA都建立了怎样的新特征向量,新特征矩阵生成之后也不具有可读性,我们无法判断新特征矩阵的特征是从原数据中的什么特征组合而来,新特征虽然带有原始数据的信息,却已经不是原数据上代表着的含义了。

但是其实,在矩阵分解时,PCA是有目标的:在原有特征的基础上,找出能够让信息尽量聚集的新特征向量。在sklearn使用的PCA和SVD联合的降维方法中,这些新特征向量组成的新特征空间其实就是V(k,n)。当V(k,n)是数字时,我们无法判断V(k,n)和原有的特征究竟有着怎样千丝万缕的数学联系。但是,如果原特征矩阵是图像,V(k,n)这个空间矩阵也可以被可视化的话,我们就可以通过两张图来比较,就可以看出新特征空间究竟从原始数据里提取了什么重要的信息。

让我们来看一个,人脸识别中属性components_的运用。

  • 导入需要的库和模块
from sklearn.datasets import fetch_lfw_people
from sklearn.decomposition import PCA
import matplotlib.pyplot as plt
import numpy as np
  • 实例化数据集,探索数据
    • 这个地方会运行很长的时间,我们可以将数据先准备好,数据准备的过程在这:juejin.cn/post/708449…
# min_faces_per_person 每个人需要的最少的照片数
faces = fetch_lfw_people(min_faces_per_person=60)

# (1277,62,47) 1277张图片,高是62,宽是47
faces.images.shape

#怎样理解这个数据的维度?
faces.data.shape
# 行是样本
# 列是样本相关的所有特征

#换成特征矩阵之后,这个矩阵是什么样?
X = faces.data
  • 看看图像什么样?将原特征矩阵进行可视化
#数据本身是图像,和数据本身只是数字,使用的可视化方法不同
#创建画布和子图对象
fig, axes = plt.subplots(3,8
                       ,figsize=(8,4)
                       ,subplot_kw = {"xticks":[],"yticks":[]} #不要显示坐标轴
                       )

fig

image.png

axes
"""
array([[<AxesSubplot:>, <AxesSubplot:>, <AxesSubplot:>, <AxesSubplot:>,
        <AxesSubplot:>, <AxesSubplot:>, <AxesSubplot:>, <AxesSubplot:>],
       [<AxesSubplot:>, <AxesSubplot:>, <AxesSubplot:>, <AxesSubplot:>,
        <AxesSubplot:>, <AxesSubplot:>, <AxesSubplot:>, <AxesSubplot:>],
       [<AxesSubplot:>, <AxesSubplot:>, <AxesSubplot:>, <AxesSubplot:>,
        <AxesSubplot:>, <AxesSubplot:>, <AxesSubplot:>, <AxesSubplot:>]],
      dtype=object)
"""

#不难发现,axes中的一个对象对应fig中的一个空格
#我们希望,在每一个子图对象中填充图像(共24张图),因此我们需要写一个在子图对象中遍历的循环
axes.shape
# (3, 8)

#二维结构,可以有两种循环方式,一种是使用索引,循环一次同时生成一列上的三个图
#另一种是把数据拉成一维,循环一次只生成一个图
#在这里,究竟使用哪一种循环方式,是要看我们要画的图的信息,储存在一个怎样的结构里
#我们使用 子图对象.imshow 来将图像填充到空白画布上
#而imshow要求的数据格式必须是一个(m,n)格式的矩阵,即每个数据都是一张单独的图
#因此我们需要遍历的是faces.images,其结构是(1277, 62, 47)
#要从一个数据集中取出24个图,明显是一次性的循环切片[i,:,:]来得便利
#因此我们要把axes的结构拉成一维来循环
axes.flat

#填充图像
for i, ax in enumerate(axes.flat):
    ax.imshow(faces.images[i,:,:],cmap="gray") #选择色彩的模式
    
fig

image.png

  • 建模降维,提取新特征空间矩阵
#原本有2900维,我们现在来降到150维
pca = PCA(150).fit(X)

# V是我们的新的特征空间
V = pca.components_

V.shape
# (150, 2914)
  • 将新特征空间矩阵可视化
fig, axes = plt.subplots(3,8,figsize=(8,4),subplot_kw = {"xticks":[],"yticks":[]})
for i, ax in enumerate(axes.flat):
    ax.imshow(V[i,:].reshape(62,47),cmap="gray")

image.png

比起降维前的数据,新特征空间可视化后的人脸非常模糊,这是因为原始数据还没有被映射到特征空间中。但是可以看出,整体比较亮的图片,获取的信息较多,整体比较暗的图片,却只能看见黑漆漆的一块。在比较亮的图片中,眼睛,鼻子,嘴巴,都相对清晰,脸的轮廓,头发之类的比较模糊。

新特征空间里的特征向量们,大部分是"五官"和"亮度"相关的向量,所以新特征向量上的信息肯定大部分是由原数据中和"五官"和"亮度"相关的特征中提取出来的。到这里,我们通过可视化新特征空间V,解释了一部分降维后的特征:虽然显示出来的数字看着不知所云,但画出来的图表示,这些特征是和”五官“以及”亮度“有关的。这也再次证明了,PCA能够将原始数据集中重要的数据进行聚集。

重要接口inverse_transform

神奇的接口inverse_transform,可以将我们归一化,标准化,甚至做过哑变量的特征矩阵还原回原始数据中的特征矩阵,这几乎在向我们暗示,任何有inverse_transform这个接口的过程都是可逆的。PCA应该也是如此。在sklearn中,我们通过让原特征矩阵X右乘新特征空间矩阵V(k,n)来生成新特征矩阵X_dr,那理论上来说,让新特征矩阵X_dr右乘V(k,n)的逆矩阵V(k,n)1V^{-1}_{(k,n)},就可以将新特征矩阵X_dr还原为X。

用人脸识别看PCA降维后的信息保存量

人脸识别是最容易的,用来探索inverse_transform功能的数据。我们先调用一组人脸数据X(m,n),对人脸图像进行绘制,然后我们对人脸数据进行降维得到X_dr,之后再使用inverse_transform(X_dr)返回一个X_inverse(m,n),并对这个新矩阵中的人脸图像也进行绘制。如果PCA的降维过程是可逆的,我们应当期待X(m,n)和X_inverse(m,n)返回一模一样的图像,即携带一模一样的信息。

  • 导入需要的库和模块
from sklearn.datasets import fetch_lfw_people
from sklearn.decomposition import PCA
import matplotlib.pyplot as plt
import numpy as np
  • 导入数据,探索数据
faces = fetch_lfw_people(min_faces_per_person=60)
faces.images.shape
# (1348, 62, 47)

faces.data.shape
# (1348, 2914)

X = faces.data
  • 建模降维,获取降维后的特征矩阵X_dr
pca = PCA(150)
X_dr = pca.fit_transform(X)
X_dr.shape

# (1348, 150)
  • 将降维后矩阵用inverse_transform返回原空间
X_inverse = pca.inverse_transform(X_dr)
X_inverse.shape
  • 将特征矩阵X和X_inverse可视化
fig, ax = plt.subplots(2,10,figsize=(10,2.5)
                     ,subplot_kw={"xticks":[],"yticks":[]}
                     )
#和2.3.3节中的案例一样,我们需要对子图对象进行遍历的循环,来将图像填入子图中
#那在这里,我们使用怎样的循环?
#现在我们的ax中是2行10列,第一行是原数据,第二行是inverse_transform后返回的数据
#所以我们需要同时循环两份数据,即一次循环画一列上的两张图,而不是把ax拉平
for i in range(10):
    ax[0,i].imshow(faces.images[i,:,:],cmap="binary_r")
    ax[1,i].imshow(X_inverse[i].reshape(62,47),cmap="binary_r")

image.png

这两组数据可视化后,由降维后再通过inverse_transform转换回原维度的数据画出的图像和原数据画的图像大致相似,但原数据的图像明显更加清晰。这说明,inverse_transform并没有实现数据的完全逆转。 这是因为,在降维的时候,部分信息已经被舍弃了,X_dr中往往不会包含原数据100%的信息,所以在逆转的时候,即便维度升高,原数据中已经被舍弃的信息也不可能再回来了。所以,降维不是完全可逆的

用PCA做噪音过滤

  • 降维目的

    • 希望抛弃掉对模型带来负面影响的特征,带有效信息的特征的方差应该是远大于噪音的,所以相比噪音,有效的特征所带的信息应该不会在PCA过程中被大量抛弃
  • inverse_transform能够在不恢复原始数据的情况下,将降维后的数据返回到原本的高维空间,即是说能够实现”保证维度,但去掉方差很小特征所带的信息“。利用inverse_transform的这个性质,我们能够实现噪音过滤。

  • 导入所需要的库和模块

from sklearn.decomposition import PCA
from sklearn.datasets import load_digits
import matplotlib.pyplot as plt
import numpy as np
  • 导入数据,探索数据
digits = load_digits()

digits.data.shape
# (1797, 64)

data.head()
  • 定义画图函数
# 定义绘图的函数
def plot_digits(data):
    fig,axes = plt.subplots(4,10,figsize=(10,4),subplot_kw={"xticks":[],"yticks":[]})
    for i,ax in enumerate(axes.flat):
        ax.imshow(data[i].reshape(8,8),cmap="binary")
plot_digits(digits.data)

image.png

  • 为数据加上噪音
# 为数据加上噪音
np.random.RandomState(42)
noisy = np.random.normal(digits.data,2)
plot_digits(noisy)

image.png

  • 降维
pca = PCA(0.5).fit(noisy)
X_dr = pca.transform(noisy)
X_dr.shape

# (1797, 6)
  • 逆转降维结果,实现降噪
# 进行降噪
without_noise = pca.inverse_transform(X_dr)
plot_digits(without_noise)

image.png

重要接口,参数和属性总结

  • 参数
    • n_components
    • svd_solver
    • random_state,
  • 属性:
    • components_
    • explained_variance_
    • explained_variance_ratio_
  • 接口
    • fit
    • transform
    • fit_transform
    • inverse_transform

image.png