1. 程式人生 > >機器學習筆記(二)矩陣和線性代數 例:用Python實現SVD分解進行圖片壓縮

機器學習筆記(二)矩陣和線性代數 例:用Python實現SVD分解進行圖片壓縮

線性代數基本只要是理工科,都是必修的一門課。當時學習的時候總是有一個疑惑,這個東西到底是幹嘛用的?為什麼數學家發明出這麼一套方法呢,感覺除了解方程沒發現有什麼大用啊!但隨著學習的深入,慢慢發現矩陣的應用其實還是挺廣泛的,無論是訊號處理,特徵提取,還是圖片處理等方面都有著非常廣泛的應用。

上週寫了概率論的內容,發現根本沒有人看啊,心好塞……所以還是不講太多理論的內容了,就簡單介紹一下線性代數中比較基本的概念,然後通過一個小應用即通過SVD分解提取圖片主要特徵來認識一下線性代數在機器學習中的作用。

基本概念

行列式:矩陣中任一行(或列)的各元素與其對應的代數餘子式的乘積之和。

伴隨矩陣:各元素代數餘子式構成的矩陣的轉置。

其中,伴隨矩陣左乘原矩陣就可以得到一個對角元素全是行列式的對角陣。

矩陣的乘法:對應的行和列的元素相乘相加。

特別要提的是矩陣和向量的乘法,比如矩陣A(m行n列)和一個列向量(n行1列)相乘得到一個m維的列向量,那麼這樣其實矩陣A就對應了一個從n維空間到m維空間的線性對映(旋轉,平移,側切等)

矩陣的秩:矩陣A中有一個不等於0的r階子式D,並且所有的r+1階子式全等於0,那麼D稱為A的最高階非零子式,r為矩陣的秩。

這個解釋其實很不友好,完全不知道它的意義在哪兒。其實所謂秩,對應的是該矩陣象空間的維數,這樣就可以理解為什麼可以用秩來判斷方程的解的個數。對於Ax=b,如果(A,b)的秩大於A的秩,那麼說明引入b後,象空間維數上升,因此原來的A的象空間無法表達b,方程無解。而如果(A,b)的秩和A相等,說明b的引入不改變A象空間的維數,因此b可在A的象空間中表示,而一旦這個秩比未知數個數還小,那麼就有無窮多解了。

正交陣:若n階矩陣滿足A.T*A=I,則稱A為正交矩陣。(即A的行(列)都是單位向量,且兩兩正交

特徵值和特徵向量:若A是n階矩陣,若 數λ和非0列向量x滿足Ax=λx,則稱數λ為A的特徵值,x稱為λ對應的特徵向量。

矩陣A與其特徵向量相乘得到特徵值乘以該特徵向量,因此矩陣對特徵向量只做了拉伸變換,而這個特徵值的大小表明了該特徵的重要性

特徵值分解:比如對於矩陣A而言,有三個特徵向量,Ax1=λ1x1,Ax2=λ2x2,Ax3=λ3x3,則有A(x1,x2,x3)=(x1,x2,x3)B,其中B為對角線是λ1,λ2,λ3的陣,再進一步就有A=(x1,x2,x3)B(x1,x2,x3)^-1。

例:利用Python進行SVD分解進行影象壓縮

首先SVD分解主要是用於特徵的提取。特徵值分解當然也可以進行特徵提取,但它要求矩陣必須是方陣,這很多時候限制了我們對它的使用。而SVD分解則對原矩陣的形狀沒有任何要求.

只要矩陣A非奇異,那麼A.T*A必定是個對稱正定陣,

所以必然存在正交矩陣Q使得Q.T(A.T*A)Q=diag(λ1,λ2,……λn),

令σi=λi^(1/2),

所以Q.T(A.T*A)Q=diag(σ1,σ2,……σn*diag(σ1,σ2,……σn),

令P為AQdiag(σ1,σ2,……σn)^(1/2),

則有P.T*A*Q=diag(σ1,σ2,……σn),

所以A=P*diag(σ1,σ2,……σn)*Q.T。

我們利用SVD分解進行圖片壓縮,其實主要是保留係數矩陣中較大的值,舍掉較小的值,從而達到壓縮影象的作用

在Python中,SVD分解非常簡單,可利用np.linalg.svd()函式,比如u,sigma,v=np.linalg.svd(A),則u,v分別返回A的左右奇異向量,而sigma返回的並不是係數矩陣,而是一個奇異值從大到小排列的一個向量。然後對於影象而言,其實就是RGB三個圖層上矩陣的疊加,每個元素的值 為0到255之間的整數,在Python中讀取影象可以通過plt.imread()函式,這樣直接得到了一個a*b*3的矩陣,然後對三個圖層分別處理就行。

現在來整理一下思路:

1 讀取圖片,分解成RGB三個矩陣。

2 對三個矩陣分別進行SVD分解,得到對應的奇異值和奇異向量。

3 按照一定標準進行奇異值的篩選(整體數量的一定百分比,或者奇異值和的一定百分比)

4 恢復矩陣,並將RGB三個矩陣疊加起來。

5 儲存影象。

原圖,黑子和黃瀨


按整體數量百分比進行奇異值篩選

import numpy as np 
from matplotlib import pyplot as plt

def svdimage(filename,percent):
    original=plt.imread(filename)
    R0=np.array(original[:,:,0])
    G0=np.array(original[:,:,1])
    B0=np.array(original[:,:,2])
    u0,sigma0,v0=np.linalg.svd(R0)
    u1,sigma1,v1=np.linalg.svd(G0)
    u2,sigma2,v2=np.linalg.svd(B0)
    R1=np.zeros(R0.shape)
    G1=np.zeros(G0.shape)
    B1=np.zeros(B0.shape)
    for i in xrange(int(percent*len(sigma0))+1):
        R1+=sigma0[i]*np.dot(u0[:,i].reshape(-1,1),v0[i,:].reshape(1,-1))
    for i in xrange(int(percent*len(sigma1))+1):
        G1+=sigma1[i]*np.dot(u1[:,i].reshape(-1,1),v1[i,:].reshape(1,-1))
    for i in xrange(int(percent*len(sigma2))+1):
        B1+=sigma2[i]*np.dot(u2[:,i].reshape(-1,1),v2[i,:].reshape(1,-1))
    final=np.stack((R1,G1,B1),2)
    final[final>255]=255
    final[final<0]=0
    final=np.rint(final).astype('uint8')
    return final
    
if __name__=='__main__':
    filename='test3.jpg'
    for p in np.arange(.1,1,.1):
        after=svdimage(filename,p)
        plt.imsave(str(p)+'_0.jpg',after)
這裡就貼個0.1,0.5和0.9的圖吧




其實可以看到取前10%的奇異值圖片部分資訊丟失了,但到50%左右時還原度基本就很高了。

接下來我們再用奇異值總和的百分比來進行篩選。

import numpy as np 
from matplotlib import pyplot as plt

def svdimage(filename,percent):
    original=plt.imread(filename)
    R0=np.array(original[:,:,0])
    G0=np.array(original[:,:,1])
    B0=np.array(original[:,:,2])
    u0,sigma0,v0=np.linalg.svd(R0)
    u1,sigma1,v1=np.linalg.svd(G0)
    u2,sigma2,v2=np.linalg.svd(B0)
    R1=np.zeros(R0.shape)
    G1=np.zeros(G0.shape)
    B1=np.zeros(B0.shape)
    total0=sum(sigma0)
    total1=sum(sigma1)
    total2=sum(sigma2)
    sd=0
    for i,sigma in enumerate(sigma0):
        R1+=sigma*np.dot(u0[:,i].reshape(-1,1),v0[i,:].reshape(1,-1))
        sd+=sigma
        if sd>=percent*total0:
            break
    sd=0
    for i,sigma in enumerate(sigma1):
        G1+=sigma*np.dot(u1[:,i].reshape(-1,1),v1[i,:].reshape(1,-1))
        sd+=sigma
        if sd>=percent*total1:
            break
    sd=0
    for i,sigma in enumerate(sigma2):
        B1+=sigma*np.dot(u2[:,i].reshape(-1,1),v2[i,:].reshape(1,-1))
        sd+=sigma
        if sd>=percent*total2:
            break
    final=np.stack((R1,G1,B1),2)
    final[final>255]=255
    final[final<0]=0
    final=np.rint(final).astype('uint8')
    return final
    
if __name__=='__main__':
    filename='test3.jpg'
    for p in np.arange(.1,1,.1):
        after=svdimage(filename,p)
        plt.imsave(str(p)+'_1.jpg',after)

同樣還是貼個0.1,0.5,0.9的圖




用奇異值總和的百分比來篩選就可以明顯看出特徵選擇的作用了,0.1的時候我大奇蹟時代已經看不見了。

好啦,就到這裡,希望對大家有幫助,也歡迎拍磚。