如果SVD确实那么好,那么该如何实现它呢?SVD实现了相关的线性代数,但这并不在本书的讨论范围之内。其实,有很多软件包可以实现SVD。NumPy有一个称为linalg的线性代数工具箱。接下来,我们了解一下如何利用该工具箱实现如下矩阵的SVD处理:
要在Python上实现该矩阵的SVD处理,请键入如下命令:
>>> from numpy import *>>> U,Sigma,VT=linalg.svd([[1, 1],[7, 7]])
接下来就可以在如下多个矩阵上进行尝试:
>>> Uarray([[-0.14142136, -0.98994949],[-0.98994949, 0.14142136]])>>> Sigmaarray([ 10., 0.])>>> VTarray([[-0.70710678, -0.70710678],[-0.70710678, 0.70710678]])
我们注意到,矩阵Sigma
以行向量array([ 10., 0.])
返回,而非如下矩阵:
array([[ 10., 0.], [ 0., 0.]]).
由于矩阵除了对角元素其他均为0,因此这种仅返回对角元素的方式能够节省空间,这就是由NumPy的内部机制产生的。我们所要记住的是,一旦看到Sigma
就要知道它是一个矩阵。好了,接下来我们将在一个更大的数据集上进行更多的分解。
建立一个新文件svdRec.py
并加入如下代码:
def loadExData:return[[1, 1, 1, 0, 0], [2, 2, 2, 0, 0], [1, 1, 1, 0, 0], [5, 5, 5, 0, 0], [1, 1, 0, 2, 2], [0, 0, 0, 3, 3], [0, 0, 0, 1, 1]]
接下来我们对该矩阵进行SVD分解。在保存好文件svdRec.py
之后,我们在Python提示符下输入:
>>> import svdRec>>> Data=svdRec.loadExData>>> U,Sigma,VT=linalg.svd(Data)>>> Sigmaarray([ 9.72140007e+00, 5.29397912e+00, 6.84226362e-01,7.16251492e-16, 4.85169600e-32])
前3个数值比其他的值大了很多(如果你的最后两个值的结果与这里的结果稍有不同,也不必担心。它们太小了,所以在不同机器上产生的结果就可能会稍有不同,但是数量级应该和这里的结果差不多)。于是,我们就可以将最后两个值去掉了。
接下来,我们的原始数据集就可以用如下结果来近似:
图14-2就是上述近似计算的一个示意图。
图14-2 SVD的示意图。矩阵Data
被分解。浅灰色区域是原始数据,深灰色区域是矩阵近似计算仅需要的数据
我们试图重构原始矩阵。首先构建一个3×3的矩阵Sig3
:
>>> Sig3=mat([[Sigma[0], 0, 0],[0, Sigma[1], 0], [0, 0, Sigma[2]]])
接下来我们重构原始矩阵的近似矩阵。由于Sig3
仅为3x3
矩阵,我们只需使用矩阵U的前3列和VT的前3行。在Python中实现这一点,输入命令:
>>> U[:,:3]*Sig3*VT[:3,:]array([[ 1., 1., 1., 0., 0. ], [ 2., 2., 2., -0., -0. ], [ 1., 1., 1., -0., -0. ], [ 5., 5., 5., 0., 0. ], [ 1., 1., -0., 2., 2. ], [ 0., 0., -0., 3., 3. ], [ 0., 0., -0., 1., 1. ]])
我们是如何知道仅需保留前3个奇异值的呢?确定要保留的奇异值的数目有很多启发式的策略,其中一个典型的做法就是保留矩阵中90%的能量信息。为了计算总能量信息,我们将所有的奇异值求其平方和。于是可以将奇异值的平方和累加到总值的90%为止。另一个启发式策略就是,当矩阵上有上万的奇异值时,那么就保留前面的2000或3000个。尽管后一种方法不太优雅,但是在实际中更容易实施。之所以说它不够优雅,就是因为在任何数据集上都不能保证前3000个奇异值就能够包含90%的能量信息。但在通常情况下,使用者往往都对数据有足够的了解,从而就能够做出类似的假设了。
现在我们已经通过三个矩阵对原始矩阵进行了近似。我们可以用一个小很多的矩阵来表示一个大矩阵。有很多应用可以通过SVD来提升性能。下面我们将讨论一个比较流行的SVD应用的例子——推荐引擎。