矩阵降维与矩阵分解是两个不同的概念。矩阵降维指就是把数据从高维空间投影到一个低维空间,这个过程可以通过线性或者非线性的映射来完成。目的是挖掘出高维数据中富含原始信息的低维嵌入表示。降维显然是有代价的,它造成了原始信息的损失。所以降维算法的重点和难点在于如何在对原始数据进行数据降维的过程中还能尽可能地保持高维数据的几何结构信息或本征的有区别性的信息,并在此前提下找到高维数据的最优低维表示。对于传统的降维算法来说,它们通常的考虑角度都是找到最大可能地保持某种信息的投影方向或者低维空间。而矩阵分解是将矩阵拆解为数个矩阵的乘积,并没有改变矩阵的维数,但通过矩阵分解往往可以从复杂的数据中提取出相对重要的特征信息,如在特征向量为基的各个方向上的投影,然后通过保留较大的投影,删除较小的投影,便可实现降维的目的;例如特征分解
,进而可写成秩逼近的形式:
其中
PCA
PCA即主成分分析,即设法将原来n个有一定相关的指标,重新组合成一组新的线性无关的综合指标来代替原来指标。也就是将n维特征映射到k维上,这k维是全新的正交特征也被称为主成分,是在原有n维特征的基础上重新构造出来的k维特征。
理解矩阵乘法
内积与投影与
的内积等于
在
上的投影长度乘以
的模,若设向量
的模为1,则
与
的内积值等于
向
所在直线投影的矢量长度!即
基向量
要准确描述向量,首先要确定一组基,然后给出在基所在的各个直线上的投影值,就可以了。 例如向量实际上表示线性组合:
基变换的矩阵表示
如果我们有个
维向量,想将其变换为由
个
维向量表示的新空间中,那么首先将
个基按行组成矩阵
,然后将向量按列组成矩阵
,那么两矩阵的乘积
就是变换结果,其中
的第
列为
中第
两个矩阵相乘的意义是将右边矩阵中的每一列列向量变换到左边矩阵中每一行行向量为基所表示的空间中去。列变换后的结果。
因而PCA的核心,既是要寻找一组基组成的矩阵左乘原矩阵,使得n维特征映射到k维上。那么我们应该如何选择K个基才能最大程度保留原有的信息?
一种直观的看法是:希望投影后的投影值尽可能分散。而这种分散程度,可以用数学上的方差来表述,即投影后的数值的方差
在高纬度中,首先我们希望找到一个方向使得投影后方差最大,这样就完成了第一个方向的选择,继而我们选择第二个投影方向。如果我们还是单纯只选择方差最大的方向,很明显,这个方向与第一个方向应该是“几乎重合在一起”,显然这样的维度是没有用的,因此应该有其他约束条件。从直观上说,让投影尽可能表示更多的原始信息,我们是不希望它们之间存在(线性)相关性的,因为相关性意味着两个字段不是完全独立,必然存在重复表示的信息。协方差表示其相关性,由于已经让每个变量均值为0,则:
PCA 本质:
设原始数据矩阵
SVD
SVD 即奇异值分解,是特征分解在任意矩阵上的推广。因为特征分解,是有约束条件的,矩阵为方阵,并需存在可逆矩阵A,使得
推导过程:对于任意矩阵,
在大部分介绍中,一般会这样求解,若
同样的,通过SVD,矩阵也能写成秩逼近的形式,
PCA与SVD相似之处:
- PCA主要是针对
,即
的协方差举证进行特征分解。
- SVD则是针对
进行奇异值分解,算的是
的特征值和特征向量,缺少了系数
。从求解方面来说SVD与PCA是等价的。
不同之处:
PCA 是寻找
SVD的优点:一般
SVD 应用实例
对lena进行图片压缩:

代码如下(网上搜寻),该图片实质可以看成(512,512,4)的一个3维矩阵,每一个元素为4个元素的数组,表示红,绿,蓝,灰度。
import numpy as np
import os
from PIL import Image
import matplotlib.pyplot as plt
import matplotlib as mpl
from pprint import pprint
def restore(sigma, u, v, K): # 奇异值、左特征向量、右特征向量
m = u.shape[0]
n = v.shape[0]
a = np.zeros((m, n))
for k in range(K):
uk = u[:, k].reshape(m, 1)
vk = v[k].reshape(1, n)
a += sigma[k] * np.dot(uk, vk) # 通过秩逼近,近似原矩阵
# clip这个函数将将数组中的元素限制在a_min, a_max之间,
# 大于a_max的就使得它等于 a_max,小于a_min,的就使得它等于a_min
a = a.clip(0, 255)
return np.rint(a).astype('uint8')
if __name__ == "__main__":
print(os.getcwd())
A = Image.open("./lena.jpg", 'r')
print(A)
output_path = './lena_output'
if not os.path.exists(output_path):
os.mkdir(output_path)
a = np.array(A)
#进行SVD分解
u_r, sigma_r, v_r = np.linalg.svd(a[:, :, 0])
u_g, sigma_g, v_g = np.linalg.svd(a[:, :, 1])
u_b, sigma_b, v_b = np.linalg.svd(a[:, :, 2])
plt.figure(figsize=(8, 8), facecolor='w')
# matploblib 解决中文显示的问题
mpl.rcParams['font.sans-serif'] = ['simHei']
mpl.rcParams['axes.unicode_minus'] = False
# 分别获取1到num个奇异值恢复图像
num = 20
sigma = 0
for k in range(1, num+1):
R = restore(sigma_r, u_r, v_r, k)
G = restore(sigma_g, u_g, v_g, k)
B = restore(sigma_b, u_b, v_b, k)
sigma += sigma_r[k]
# (512, 512)
# 增加了一个维度,这个维度上的索引0,1,2分别存放R,G,B
# axis从0开始索引,2表示第三个维度
I = np.stack((R, G, B), axis=2)
# (512,512,3)
print(I.shape)
# 将array保存为三通道彩色图像
Image.fromarray(I).save('%s/svd_%d.png' % (output_path, k))
# 按照三行四列显示前12图像
if k <= 12:
plt.subplot(3, 4, k)
plt.imshow(I)
plt.axis('off')
plt.title('奇异值个数:%d' % k)
print (sigma/sum(sigma_r))
plt.suptitle('SVD与图像分解', fontsize=20)
plt.tight_layout(0.3, rect=(0, 0, 1, 0.92))
plt.show()
输出图片为:

前12个奇异向量已经能很好的保存图片的特征,在512个奇异值中,前20个奇异值(3.9%)所占比为0.29956,奇异值的贡献度减少的很快,一般前10%的奇异值就有99%以上的贡献度。
在实战过程中,想到既然是方阵,用特征值分解是什么样子,因而将代码进行了修改
u_r, sigma_r, v_r = np.linalg.svd(a[:, :, 0])
u_g, sigma_g, v_g = np.linalg.svd(a[:, :, 1])
u_b, sigma_b, v_b = np.linalg.svd(a[:, :, 2])
更改为:
sigma_r, u_r = np.linalg.eig(a[:, :, 0])
#求逆
v_r = np.matrix(u_r).I
sigma_g, u_g = np.linalg.eig(a[:, :, 1])
v_g = np.matrix(u_g).I
sigma_b, u_b = np.linalg.eig(a[:, :, 2])
v_b = np.matrix(u_b).I
但报错,因为特征值有虚数,故也可看出,特征值分解有其局限性。
下一章,学习LDA,MDS,NMF