在之前的一篇文章:通俗解釋協方差與相關係數,紅色石頭為大家通俗化地講解了協方差是如何定義的,以及如何直觀理解協方差,並且比較了協方差與相關係數的關係。
本文紅色石頭將繼續使用白話語言,介紹機器學習中應用十分廣泛的矩陣分解方法:奇異值分解(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