在进行主成分分析(PCA)的时候我们用到了特征值分解(EVD)的方法,这个方法很重要和很高效,但是同时也存在局限性。
那就是特征值分解要求矩阵必须是 方阵 并且一定能够被 对角化 。
那么扩展到一般情况,对于任意形状的矩阵的情况怎么办呢?比如说图片、数据表格等等。
那么奇异值分解(SVD)就进入了我们的视野,它可以对任意形状的矩阵进行分解,适用范围更广。
我们一开始是获得一组原始的M X N 的数据采样矩阵A,其中M代表特征的个数,N代表样本个数。
矩阵通过与自身的转置矩阵相乘,得到M阶的样本特征的协方差矩阵。
然后获取协方差矩阵的一组标准正交特征向量以及对应的特征值。
此时,我们对协方差矩阵进行特征值分解,将矩阵分解为这样的形式:
最终通过获取前面k个特征值对应的特征向量,依次构成数据压缩矩阵的各行,通过矩阵相乘进行投影达到数据压缩的目的。
我们可以看到,想要完成特征值分解,最终还是要回到这个式子上来。
如果不进协方差矩阵的获取,直接对原始的数据采样矩阵进行矩阵分解,进行降维操作,显然是不行的。
特征值分解有两个大前提,一是必须是方阵,二是必须能够满足对角化。
但是对于原始的m x n 矩阵可能连基本方阵的要求都达不到,根本无法进行特征值分解。
对于一个任意形状的m x n形状矩阵,我们有以下普遍意义的性质:
① 假设m>n,就有r≤n
②在空间中一定有一组正交向量,在空间中一定有一组正交向量,
使之满足。
在此基础上可以将
进一步转换为以下形式:
在这个分解的式子下,我们发现基向量没有包含在内。
将其添加到矩阵右侧,得到完整的m阶方阵
接下来,在对角矩阵下方加上 m - n 个全零行,形成m x n 的矩阵
由于右下方全是全零行,所以结果不变。
此时得到等式
由于矩阵各列是标准正交向量,有,那么有一个矩阵分解的最终式子:
矩阵和矩阵是标准正交向量构成的m阶和n阶方阵,是一个m x n 的对角矩阵,但不是方阵。
接下来方阵和方阵要怎么求呢?矩阵的各个值应该是多少?
我们先获取的转置矩阵
由此,可以获取两个对称矩阵和
由对称矩阵的性质可知,
矩阵一定有n个标准正交特征向量
矩阵一定有m个标准正交特征向量。这里与 一 一对应。通过对应,即可求出和。
对应的,矩阵也很好求,只要求出或者的非零特征值,从大到小排列,
那么矩阵中对角线上的特征值 依次为
对角线上 之后的特征值元素均为0
那么隐藏零特征值,得到完整的奇异值分解(SVD)的结果:
以下是一个抽象的示意图:
奇异值分解(SVD)还有一个亮点就是,它可以从行或者列两个不同的维度同时进行对数据的降维处理。
一个采样数据的矩阵的行和列通常代表着不同的特征。因此这种特性可以带来不同的处理效果。
若要对行数据进行压缩,我们从矩阵的奇异值分解式子入手。
将等式两边同时乘以左奇异矩阵的转置矩阵,得到。
左侧表达式表示把矩阵的n个m维列向量并排放置:
此时,可以把每一列看作一个样本,各行是样本的不同特征,各行之间彼此无关,
此时可以选择最大的k个奇异值对应的k个标准正交向量,形成行压缩矩阵
通过式子
列压缩数据降维也是同理,
我们也是从矩阵的奇异值分解式子出发,在式子两边同时乘以右奇异矩阵。
得到等式
我们对左边的式子进行转置处理,
在矩阵中,从小到大去前k个特征值所对应的标准正交特征向量,就构成了另一个压缩矩阵
通过乘法运算就能够实现将矩阵的各列由n维压缩到k维。
如果是要从矩阵整体处理视角来进行数据压缩,思路可以类似级数。
将一个m x n 的原始数据矩阵分解为若干个相同维度矩阵乘以各自权重后相加的形式。
同样的,我们从入手,展开:
将矩阵相乘的式子展开得到:
展开式中每一个都代表一个等维的 m x n 的矩阵
前面系数 表示每个矩阵“重要程度”,因此可以按照主成分贡献率的最低要求选择前k个项进行叠加,得到近似式子:
原理抽象示意图如图所示:
这里可以不按照推导奇异值分解的过程:先求对称矩阵和,再依次求得要的矩阵,,。
我们直接通过Python提供的工具一次性获得所有结果。
假设有一个矩阵
import numpy as np A = [[0,0,0,2,2], [0,0,0,3,3], [0,0,0,1,1], [1,1,1,0,0], [2,2,2,0,0], [5,5,5,0,0], [1,1,1,0,0]] U,sigma,V_T = np.linalg.svd(A) print(U) print(sigma) print(V_T)
[[ 8.33888363e-17 -5.34522484e-01 -8.19688300e-01 -1.42578545e-02 1.17589781e-01 -1.68291600e-01 6.64076653e-03] [-5.27910020e-33 -8.01783726e-01 4.81453408e-01 -3.68497530e-03 -3.51336168e-01 -4.34953513e-02 1.71632139e-03] [ 0.00000000e+00 -2.67261242e-01 1.95016375e-01 3.95706349e-02 8.18828941e-01 4.67069254e-01 -1.84304972e-02] [-1.79605302e-01 4.55126095e-17 8.85078821e-02 8.82844675e-01 1.60750385e-01 -3.92953497e-01 1.55058983e-02] [-3.59210604e-01 6.56005654e-17 1.77015764e-01 -4.17392305e-01 3.21500770e-01 -6.18874662e-01 -4.23140914e-01] [-8.98026510e-01 1.48333183e-18 -1.06209459e-01 3.21272177e-02 -1.92900462e-01 3.79211394e-01 -1.49636365e-02] [-1.79605302e-01 3.28002827e-17 8.85078821e-02 -2.08696153e-01 1.60750385e-01 -2.65354150e-01 9.05594112e-01]] [9.64365076e+00 5.29150262e+00 6.49628424e-16 1.43063514e-16 2.79192092e-17] [[-5.77350269e-01 -5.77350269e-01 -5.77350269e-01 0.00000000e+00 0.00000000e+00] [-1.70408510e-16 8.52042552e-17 8.52042552e-17 -7.07106781e-01 -7.07106781e-01] [-6.19518612e-01 -1.50835436e-01 7.70354049e-01 2.48999108e-16 1.37976805e-16] [-0.00000000e+00 8.56793076e-17 -8.56793076e-17 7.07106781e-01 -7.07106781e-01] [ 5.31848997e-01 -8.02443355e-01 2.70594358e-01 5.04241948e-17 -6.05981077e-17]]
这样就获得了奇异值分解所有的结果
sigma不是一个矩阵,是5个奇异值从大到小排序的一个列表
通过上面奇异值分解的结果
我们观察到前两个奇异值在数量级上有优势,所以我们选择k=2来进行行压缩和列压缩。
利用将矩阵的行数由7行压缩到2行。
利用将矩阵的列数由5列压缩到2列。
import numpy as np A = [[0,0,0,2,2], [0,0,0,3,3], [0,0,0,1,1], [1,1,1,0,0], [2,2,2,0,0], [5,5,5,0,0], [1,1,1,0,0]] U,sigma,V_T = np.linalg.svd(A) U_T_2x7 = U.T[:2,:] print(np.dot(U_T_2x7,A)) V_T_2x5 = V_T[:2,:] print(np.dot(V_T_2x5,np.mat(A).T).T)
[[-5.56776436e+00 -5.56776436e+00 -5.56776436e+00 1.66777673e-16 1.66777673e-16] [ 2.16930682e-16 2.16930682e-16 2.16930682e-16 -3.74165739e+00 -3.74165739e+00]] [[ 0.00000000e+00 -2.82842712e+00] [ 0.00000000e+00 -4.24264069e+00] [ 0.00000000e+00 -1.41421356e+00] [-1.73205081e+00 1.23259516e-32] [-3.46410162e+00 2.46519033e-32] [-8.66025404e+00 4.93038066e-32] [-1.73205081e+00 1.23259516e-32]]
接下来从整体维度进行数据压缩,取前两个贡献率高的奇异值 和 ,
利用进行矩阵近似
import numpy as np A = [[0,0,0,2,2], [0,0,0,3,3], [0,0,0,1,1], [1,1,1,0,0], [2,2,2,0,0], [5,5,5,0,0], [1,1,1,0,0]] U,sigma,V_T = np.linalg.svd(A) A_1 = sigma[0] * np.dot(np.mat(U[:, 0]).T, np.mat(V_T[0, :])) A_2 = sigma[1] * np.dot(np.mat(U[:, 1]).T, np.mat(V_T[1, :])) print(A_1+A_2)
[[ 1.76986622e-17 -7.05283417e-16 -7.05283417e-16 2.00000000e+00 2.00000000e+00] [ 7.22982080e-16 -3.61491040e-16 -3.61491040e-16 3.00000000e+00 3.00000000e+00] [ 2.40994027e-16 -1.20497013e-16 -1.20497013e-16 1.00000000e+00 1.00000000e+00] [ 1.00000000e+00 1.00000000e+00 1.00000000e+00 -1.70292592e-16 -1.70292592e-16] [ 2.00000000e+00 2.00000000e+00 2.00000000e+00 -2.45454840e-16 -2.45454840e-16] [ 5.00000000e+00 5.00000000e+00 5.00000000e+00 -5.55011948e-18 -5.55011948e-18] [ 1.00000000e+00 1.00000000e+00 1.00000000e+00 -1.22727420e-16 -1.22727420e-16]]
从得到的矩阵结果来看,近似矩阵为
与原来的矩阵
对比,发现基本一致。实现了矩阵近似的效果。