【python】双向二维PCA(2D-2D PCA)算法实现

本文深入讲解了2D-2DPCA的原理及其在图像处理中的应用,对比了传统PCA与2DPCA的方法,介绍了如何通过特征向量降低图像数据的维度,并给出了Python实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

原理介绍


PCA

这篇教程讲的非常详细。

以图片为例,将每张图片(假设宽高为 m n)转化为m*n的一维向量,然后减去每张图片像素的均值(使得每张图片均值为零),然后数据集中的所有图片一起合成一个大的矩阵X。

计算图片矩阵的方差-协方差矩阵,我们的优化目标就是将这个矩阵对角化,使得字段(代表一张图片)两两之间的协方差为零,也就是相关性最小,字段与自身的方差尽可能大。如下:
D =1mYYT=1m(PX)(PX)T=1mPXXTPT=P(1mXXT)PT=PCPT

Y=PX,P为一组基,Y为在P上映射后的数据,D为映射后数据的协方差矩阵,也就是我们的优化目标矩阵,X为数据矩阵。 C=1mXXT ,也就是原数据的协方差矩阵。我们的目标现在是找到能将矩阵C对角化,使得D成为一个对角矩阵。

数学上矩阵对角化只需计算出C的特征值和特征向量,并取前k大的特征值对应的特征向量,也就是投影之后方差最大的那些基,即可得到P矩阵,k就是降维后的维数。


2D PCA

相较于传统PCA,2D PCA不用将图片转换为1D向量,极大的减少了数据的维度。基本思路和PCA相同,也是将数据投影到某组基上,然后使得投影之后数据的协方差矩阵成为对角阵。投影后数据的协方差矩阵为:

Sx=E[(YEY)(YEY)T]=E[AXE(AX)][AXE(AX)]T=E[(AEA)X][(AEA)X]T

Y为投影之后的数据,Y=AX,A是原数据,X是一组正交基,Sx也就是协方差矩阵。上述公式变形之后之后可得(利用tr(AB)=tr(BA),只要保证迹不变即可,这里还有点没搞懂为什么):

Sx=XT[E(AEA)T(AEA)]X

然后再定义矩阵Gt为:

Gt=E(AEA)T(AEA)
也就是原数据的协方差矩阵。

类似于传统PCA,我们优化目标就是将使矩阵S_x成为对角阵,并找出对应的特征向量和特征值,也就是将G_t对角化,取前K大的特征向量。


2D-2DPCA

双向2D PCA的改进之处在于,普通2D PCA中的变换只提取了数据矩阵行内的特征,对行进行了变换,而双向PCA则加上了对列进行的变换。

Sx=E[(BEB)(BEB)T]=E[ZTAE(ZTX)][ZTAE(ZTA)]T=ZTE[(AEA)(AEA)T]Z

列变换对应的Gt矩阵为:

Gt=E[(AEA)(AEA)T]

对列进行2D PCA也就是对上述矩阵提取特征向量,得到一组基Z,然后对原始数据进行投影。

为了将列和行的变换合并,最后进行如下变换:

C=ZTAX

X为行方向上2D PCA得到的变换矩阵,Z为列方向上2D PCA得到的变换矩阵,A为原数据。要还原图片数据也很简单,已知C矩阵:

A=ZCXT

因为特征向量相互正交,Z,X均为正交阵,正交阵的逆矩阵就是其转置。

python 实现

用python实现了一下算法:

# a implementation of 2D^2 PCA algorithm

import numpy as np
from PIL import Image

def PCA2D_2D(samples, row_top, col_top):
    '''samples are 2d matrices'''
    size = samples[0].shape
    # m*n matrix
    mean = np.zeros(size)

    for s in samples:
        mean = mean + s

    # get the mean of all samples
    mean /= float(len(samples))

    # n*n matrix
    cov_row = np.zeros((size[1],size[1]))
    for s in samples:
        diff = s - mean;
        cov_row = cov_row + np.dot(diff.T, diff)
    cov_row /= float(len(samples))
    row_eval, row_evec = np.linalg.eig(cov_row)
    # select the top t evals
    sorted_index = np.argsort(row_eval)
    # using slice operation to reverse
    X = row_evec[:,sorted_index[:-row_top-1 : -1]]

    # m*m matrix
    cov_col = np.zeros((size[0], size[0]))
    for s in samples:
        diff = s - mean;
        cov_col += np.dot(diff,diff.T)
    cov_col /= float(len(samples))
    col_eval, col_evec = np.linalg.eig(cov_col)
    sorted_index = np.argsort(col_eval)
    Z = col_evec[:,sorted_index[:-col_top-1 : -1]]

    return X, Z


samples = []
for i in range(1,6):
    im = Image.open('image/'+str(i)+'.png')
    im_data  = np.empty((im.size[1], im.size[0]))
    for j in range(im.size[1]):
        for k in range(im.size[0]):
            R = im.getpixel((k, j))
            im_data[j,k] = R/255.0
    samples.append(im_data)

X, Z = PCA2D_2D(samples, 90, 90)

res = np.dot(Z.T, np.dot(samples[0], X))
res = np.dot(Z, np.dot(res, X.T))

row_im = Image.new('L', (res.shape[1], res.shape[0]))
y=res.reshape(1, res.shape[0]*res.shape[1])

row_im.putdata([int(t*255) for t in y[0].tolist()])
row_im.save('X.png')

在实现过程中遇到了一个小坑,特此记录一下。PIL图片中的size是图片的(宽,高)二元组,而numpy中的array的shape则是矩阵的(行数,列数)二元组,索引的时候也是按照这种二元组来索引的。

在从图片读取像素然后转存到array中的时候很容易混淆这两个概念,实际上矩阵的行数相当于图片中的高,列数相当于宽,二者的概念刚好颠倒,很容易出错。

用下面5张图片做了实验:

这里写图片描述

下面分别是X,Z矩阵分别取前5,10,20,40个特征向量时,图片还原的结果:

这里写图片描述

可以看出用前40个特征向量时,已经能够还原出很细致的原图了,原图分辨率为96*118,就是说,经过双向2D PCA后,用40*40的特征就能很好地表达96*118的数据,极大的降低了维度。

评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值