如何讓奇異值分解(SVD)變得不“奇異”?

红色石头發表於2018-08-29

在之前的一篇文章:通俗解釋協方差與相關係數,紅色石頭為大家通俗化地講解了協方差是如何定義的,以及如何直觀理解協方差,並且比較了協方差與相關係數的關係。

本文紅色石頭將繼續使用白話語言,介紹機器學習中應用十分廣泛的矩陣分解方法:奇異值分解(SVD)。本文不注重詳細的數學推導,只注重感性的理解以及如何在實際應用中使用它們。

1. 普通方陣的矩陣分解(EVD)

我們知道如果一個矩陣 A 是方陣,即行列維度相同(mxm),一般來說可以對 A 進行特徵分解:

其中,U 的列向量是 A 的特徵向量,Λ 是對角矩陣,Λ 對角元素是對應特徵向量的特徵值。

舉個簡單的例子,例如方陣 A 為:

那麼對其進行特徵分解,相應的 Python 程式碼為:

import numpy as np

A = np.array([[2,2],[1,2]])
lamda,U=np.linalg.eig(A)
print('方陣 A: ',A)
print('特徵值 lamda: ',lamda)
print('特徵向量 U: ',U)

執行輸出:

方陣 A: [[2 2]
[1 2]]
特徵值 lamda: [ 3.41421356 0.58578644]
特徵向量 U: [[ 0.81649658 -0.81649658]
[ 0.57735027 0.57735027]]

特徵分解就是把 A 拆分,如下所示:

其中,特徵值 λ1=3.41421356,對應的特徵向量 u1=[0.81649658 0.57735027];特徵值 λ2=0.58578644,對應的特徵向量 u2=[-0.81649658 0.57735027],特徵向量均為列向量。

值得注意的是,特徵向量都是單位矩陣,相互之間是線性無關的,但是並不正交。得出的結論是對於任意方陣,不同特徵值對應的特徵向量必然線性無關,但是不一定正交。

2.對稱矩陣的矩陣分解(EVD)

如果方陣 A 是對稱矩陣,例如:

對稱矩陣特徵分解滿足以下公式:

那麼對其進行特徵分解,相應的 Python 程式碼為:

A = np.array([[2,1],[1,1]])
lamda,U=np.linalg.eig(A)
print('方陣 A: ',A)
print('特徵值 lamda: ',lamda)
print('特徵向量 U: ',U)

執行輸出:

方陣 A: [[2 1]
[1 1]]
特徵值 lamda: [ 2.61803399 0.38196601]
特徵向量 U: [[ 0.85065081 -0.52573111]
[ 0.52573111 0.85065081]]

特徵分解就是把 A 拆分,如下所示:

其中,特徵值 λ1=2.61803399,對應的特徵向量 u1=[0.85065081 0.52573111];特徵值 λ2=0.38196601,對應的特徵向量 u2=[-0.52573111 0.85065081],特徵向量均為列向量。

注意,我們發現對陣矩陣的分解和非對稱矩陣的分解除了公式不同之外,特徵向量也有不同的特性。對稱矩陣的不同特徵值對應的特徵向量不僅線性無關,而且是相互正交的。什麼是正交呢?就是特徵向量內積為零。驗證如下:

0.85065081 * -0.52573111 + 0.52573111 * 0.85065081 = 0

重點來了,對稱矩陣 A 經過矩陣分解之後,可以寫成以下形式:

對上式進行驗證:

這種分解形式非常有用,待會紅色石頭即將介紹。

3. 奇異值分解(SVD)

我們發現,在矩陣分解裡的 A 是方陣或者是對稱矩陣,行列維度都是相同的。但是實際應用中,很多矩陣都是非方陣、非對稱的。那麼如何對這類矩陣進行分解呢?因此,我們就引入了針對維度為 mxn 矩陣的分解方法,稱之為奇異值分解(Singular Value Decomposition)。

假設矩陣 A 的維度為 mxn,雖然 A 不是方陣,但是下面的矩陣卻是方陣,且維度分別為 mxm、nxn。

因此,我們就可以分別對上面的方陣進行分解:

其中,Λ1 和 Λ2 是對焦矩陣,且對角線上非零元素均相同,即兩個方陣具有相同的非零特徵值,特徵值令為 σ1, σ2, … , σk。值得注意的是,k<=m 且 k<=n。

根據 σ1, σ2, … , σk 就可以得到矩陣 A 的特徵值為:

接下來,我們就能夠得到奇異值分解的公式:

其中,P 稱為左奇異矩陣,維度是 mxm,Q 稱為右奇異矩陣,維度是 nxn。Λ 並不是方陣,其維度為 mxn,Λ 對角線上的非零元素就是 A 的特徵值 λ1, λ2, … , λk。圖形化表示奇異值分解如下圖所示:

舉個簡單的例子來說明,令 A 為 3×2 的矩陣:

則有:

計算得到特徵向量 P 和對應的特徵值 σ 為:

然後,有:

計算得到特徵向量 Q 和對應的特徵值 σ 為:

則我們看可以得到 A 的特徵值為:

最後,整合矩陣相乘結果,滿足奇異值分解公式。

奇異值分解可以寫成以下和的形式:

其中,p1 和 q1 分別為左奇異矩陣和右奇異矩陣的特徵向量。

4. 如何形象化理解 SVD

奇異值分解到底有什麼用呢?如何形象化地理解奇異值?我們一起來看下面的例子。

首先放上男神的照片:

我們對該圖片進行奇異值分解,則該圖片可寫成以下和的形式:

上式中,λ1, λ2, … , λk 是按照從大到小的順序的。

首先,若我們只保留最大的奇異值 λ1,捨去其它奇異值,即 A=λ1p1q1T,然後作圖:

結果完全看不清楚,再多加幾個奇異值,取前 5 個最大的奇異值,然後作圖:

現在貌似有點輪廓了,繼續增加奇異值,取前 10 個最大的奇異值,然後作圖:

又清晰了一些,繼續將奇異值增加到 20 個,然後作圖:

現在已經比較清晰了,繼續將奇異值增加到 50 個,然後作圖:

可見,取前 50 個最大奇異值來重構影像時,已經非常清晰了。我們得到和原圖差別不大的影像。也就是說,隨著選擇的奇異值的增加,重構的影像越來越接近原影像。

基於這個原理,奇異值分解可以用來進行圖片壓縮。例如在本例中,原始圖片的維度是 870×870,總共需要儲存的畫素值是:870×870=756900。若使用 SVD,取前 50 個最大的奇異值即可,則總共需要儲存的元素個數為:

(870+1+870)*50=87050

顯然,所需儲存量大大減小了。在需要儲存許多高畫質圖片,而儲存空間有限的情況下,就可以利用 SVD,保留奇異值最大的若干項,捨去奇異值較小的項即可。

值得一提的是,奇異值從大到小衰減得特別快,在很多情況下,前 10% 甚至 1% 的奇異值的和就佔了全部的奇異值之和的 99% 以上了。這對於資料壓縮來說是個好事。下面這張圖展示了本例中奇異值和奇異值累加的分佈:

SVD 資料壓縮的演算法圖示如下:

SVD 資料壓縮的示例程式碼為:

from skimage import io
import matplotlib.pyplot as plt
from PIL import Image

img=io.imread('./ng.jpg')
m,n = img.shape
io.imshow(img)
plt.show()

P, L, Q = np.linalg.svd(img)
tmp = np.diag(L)
if m < n:
   d = np.hstack((tmp,np.zeros((m,n-m))))
else:
   d = np.vstack((tmp,np.zeros((m-n,n))))

# k = 50
img2 = P[:,:50].dot(d[:50,:50]).dot(Q[:50,:])
io.imshow(np.uint8(img2))
plt.show()

tmp = np.uint8(img2)
im = Image.fromarray(tmp)
im.save("out.jpg")

現在,你已經完全瞭解了奇異值分解了吧。是不是挺簡單也挺有意思呢?

參考文獻

https://zhuanlan.zhihu.com/p/26306568

https://www.zhihu.com/question/22237507/answer/53804902

相關文章