如果SVD確實那麼好,那麼該如何實現它呢?SVD實現了相關的線性代數,但這並不在本書的討論範圍之內。其實,有很多軟件包可以實現SVD。NumPy有一個稱為linalg的線性代數工具箱。接下來,我們瞭解一下如何利用該工具箱實現如下矩陣的SVD處理:
要在Python上實現該矩陣的SVD處理,請鍵入如下命令:
>>> from numpy import *
>>> U,Sigma,VT=linalg.svd([[1, 1],[7, 7]])
接下來就可以在如下多個矩陣上進行嘗試:
>>> U
array([[-0.14142136, -0.98994949],
[-0.98994949, 0.14142136]])
>>> Sigma
array([ 10., 0.])
>>> VT
array([[-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)
>>> Sigma
array([ 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應用的例子——推薦引擎。