数据压缩·课前任务二(PCA)

徘徊边缘 提交于 2020-03-03 05:10:55

要求:主成分分析:步骤、应用及代码实现

  • 目的: 降维。

  • 简介: 通俗易懂见详解:https://www.matongxue.com/madocs/1025.html
    这里举一个例子帮助理解。
    首先我们观察一下下面这个矩阵:
    在这里插入图片描述
    会发现,这个矩阵的第一列,第二列,第四列这三个列向量在空间中的指向是没有变的,仅仅只是缩放了相应的倍数而已,所以这个看起来是四维度矩阵其实是个二维矩阵。看到这你可能开始迷惑了,为啥是二维,这个矩阵不是有四个列向量吗?鲁迅曾经说过:不要被表象迷惑了双眼。好吧,不管这句话是不是鲁迅说的,但总之,仔细想想我们就会发现,第一、二、四个列向量它们都处在同一条直线上,用线性代数的语言来说,就是这三个列向量张成的空间是一条直线,那在加上第三个列向量所张成的直线,那这个矩阵不就只代表了一个二维平面嘛!所以说,这个四维的矩阵其实只是一个二维矩阵而已!
    到这里,我们就会顺理成章产生这样的想法:既然是一个二维的矩阵,干嘛不用二维的形式呢?所以自然地我们就会想到要找一个办法让这个矩阵降维,让它把冗余数据给去掉,只留下它的主成分。我们可以把上面的矩阵看成是一个四维空间中的二维平面,既然是二维平面,就应该在二维平面上重建一个坐标系,这样就可以把原来的列向量都表示出来,也就是说,这四个列向量在空间中没有变,只是我们换了一个参考系,表征它们的值也就变了,原来需要四个数(x,y,z,r)才能表示的向量,现在只需要两个数(a,b)就可以表示出来。所以,我们要对这个矩阵进行一个基变换(变换矩阵A),让四维的矩阵变换成二维的矩阵。
    总结一下,就是我们需要找到一种变换,让高维数据降到低维上,这样就可以达到去除冗余、压缩数据的目的,同时将高维降低到低维有利于数据可视化从而帮助我们分析理解问题。
    这种方法就是PCA,而PCA不仅应用于上述的简单问题,还可以应用到各种地方比如神经科学、计算机图形学等等。

  • 方法: 假设需要我们处理的是一个线性系统,那么要找到一个基变换使数据降维。(为了方便可视化和理解,需要使用互相垂直的基向量即正交基)
    在线性系统中有各种数据混在一起,我们需要分离出主成分,相关性大的数据为我们所需要的数据,相关性较小的则是次要变量。我们可以通过计算数据或者说变量之间的协方差,组成协方差矩阵来进行判断处理。将协方差对角化,得到的对角线上的元素即位协方差的特征值,值越大,越说明这是主成分。将主元排序以后我们就可以对数据进行降维压缩。

  • 步骤:

  1. 中心化/零均值化:对矩阵的每一列求平均值得到meanVal,并让矩阵中的每一个元素减去对应列的均值得到newData;
  2. 求协方差矩阵covMat;
  3. 求协方差矩阵的特征向量(要归一化为单位向量)和特征值;
  4. 将特征值从大到小排序,选取最大的k个,然后将其对应的k个特征向量分别作为列向量组成特征向量矩阵;
  5. 将样本值投影到选取的特征向量上
  • 总代码
import numpy as np

dataMat=[[1,2,3,4,3],[5,10,15,20,15],[3,6,2,12,20],[4,18,12,16,12]]
dataMat=np.mat(dataMat)
print(dataMat)

# 进行中心化/零均值化
def centralize(dataMat):
    meanVal=np.mean(dataMat,axis=0) # 先对dataMat的每一列求均值,注意np.mean的用法
    newData=dataMat-meanVal #dataMat中每列的每个数都减去对应列的均值
    return  newData,meanVal

# 求协方差矩阵
def covmat(dataMat):
    newData=centralize(dataMat)[0]
    covMat=np.cov(newData,rowvar=0)
    return covMat

# 如何选择k值
def selectK(eigVal, percentage):
    sum = 0
    sum_eigVal=0
    for i in range(len(eigVal)):
        sum_eigVal += abs(eigVal[i])
    for i in range(len(eigVal)):
        sum += abs(eigVal[i])
        if sum >= sum_eigVal * percentage:
            return i+1
            break

# 求协方差矩阵的特征向量与特征值,并对原数据dataMat进行降维以及数据重构
def pca(dataMat,percentage):
    newData,meanVal=centralize(dataMat)
    eigVal,eigVec=np.linalg.eig(covmat(dataMat))#求特征值及特征向量
    eigVal=sorted(eigVal,reverse=True)#对特征值进行由大到小的排序
    k=selectK(eigVal,percentage)

    # 取对应k个特征向量
    sort=np.argsort(eigVal)#对eigVal进行排序,结果是排好序元素对下标
    eigVal_sort=sort[-1:-(k+1):-1]#取最大的k个特征值的下标
    k_eigVec=eigVec[:,eigVal_sort]#这k个特征值对应的特征向量

    lowData=np.dot(newData,k_eigVec) #把经过中心化的样本值降维
    reconData=np.dot(lowData,np.transpose(k_eigVec))+meanVal #重构数据,k_eigVec为正交矩阵,所以它的逆等于它的转制
    return lowData,reconData

lowdata,recondata=pca(dataMat,0.99)
print(lowdata)

#结果:
[[ 1  2  3  4]
 [ 5 10 15 20]
 [ 3  6  2 12]]
[[  9.52768035  -2.58219897]
 [-12.33411712  -1.14629811]
 [  2.80643676   3.72849708]]
  • 应用于图片压缩
import numpy as np
from PIL import Image

# 将图像转化为可处理的矩阵
def loadImage(path):
    img = Image.open(path)
    # 将图像转换成灰度图
    img=img.convert('L')
    # 图像的大小在size中是(宽,高)
    # 所以width取size的第一个值,height取第二个
    width = img.size[0]
    height = img.size[1]
    dataMat = np.mat(img.getdata())

    print(dataMat)
    # 为了避免溢出,这里对数据进行一个缩放,缩小100倍
    # dataMat = np.array(dataMat).reshape(height,width)/100
    dataMat = dataMat.reshape(height, width) / 100

    # 查看原图的话,需要还原数据
    new_im = Image.fromarray(dataMat*100)
    new_im.show()
    return dataMat

dataMat=loadImage('path')

# 进行中心化/零均值化
def centralize(dataMat):
    meanVal=np.mean(dataMat,axis=0) # 先对dataMat的每一列求均值,注意np.mean的用法
    newData=dataMat-meanVal #dataMat中每列的每个数都减去对应列的均值
    return  newData,meanVal

# 求协方差矩阵
def covmat(dataMat):
    newData=centralize(dataMat)[0]
    covMat=np.cov(newData,rowvar=0)
    return covMat

# 如何选择k值
def selectK(eigVal, percentage):
    sum = 0
    sum_eigVal=0
    for i in range(len(eigVal)):
        sum_eigVal += abs(eigVal[i])
    for i in range(len(eigVal)):
        sum += abs(eigVal[i])
        if sum >= sum_eigVal * percentage:
            return i+1
            break

# 求协方差矩阵的特征向量与特征值,并对原数据dataMat进行降维以及数据重构
def pca(dataMat,percentage):
    newData,meanVal=centralize(dataMat)
    eigVal,eigVec=np.linalg.eig(covmat(dataMat))#求特征值及特征向量
    eigVal=sorted(eigVal,reverse=True)
    k=selectK(eigVal,percentage)

    # 取对应k个特征向量
    sort=np.argsort(eigVal)
    eigVal_sort=sort[-1:-(k+1):-1]
    k_eigVec=eigVec[:,eigVal_sort]

    lowData=np.dot(newData,k_eigVec) #把经过中心化的样本值降维
    reconData=np.real(np.dot(lowData,np.transpose(k_eigVec))+meanVal) #重构数据,k_eigVec为正交矩阵,所以它的逆等于它的转制,
                                                               # 为了保证可以重构数据,取实值
    print(reconData)
    newImage = Image.fromarray(reconData * 100)
    newImage.show()

    return reconData

pca(dataMat,0.99)

结果:
原图:
在这里插入图片描述

转为灰度后:
在这里插入图片描述
降维处理后:
在这里插入图片描述

很明显的我们发现降维后的图片在图像横向方向进行了压缩(因为我们处理的时候去掉了一些特征值较小的对应的列向量)。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!