介紹
「Another day has passed, and I still haven’t used y = mx + b.」
這聽起來是不是很熟悉?我經常聽到我大學的熟人抱怨他們花了很多時間的代數方程在現實世界中基本沒用。
好吧,但我可以向你保證,並不是這樣的。特別是如果你想開啟數據科學的職業生涯。
線性代數彌合了理論與概念實際實施之間的差距。對線性代數的掌握理解打開了我們認為無法理解的機器學習算法的大門。線性代數的一種這樣的用途是奇異值分解(SVD)用於降維。
你在數據科學中一定很多次遇到SVD。它無處不在,特別是當我們處理降維時。但它是什麼?它是如何工作的?SVD應用有什麼?
事實上,SVD是推薦系統的基礎,而推薦系統是谷歌,YouTube,亞馬遜,Facebook等大公司的核心。
我們將在本文中介紹SVD的五個超級有用的應用,並將探討如何在Python中以三種不同的方式使用SVD。
1. 奇異值分解(SVD)的應用
我們將在此處遵循自上而下的方法並首先討論SVD應用。如果你對它如何工作感興趣的,我在下面會講解SVD背後的數學原理。現在你只需要知道四點來理解這些應用:
SVD是將矩陣A分解為3個矩陣--U,S和V。S是奇異值的對角矩陣。將奇異值視為矩陣中不同特徵的重要性值矩陣的秩是對存儲在矩陣中的獨特信息的度量。秩越高,信息越多矩陣的特徵向量是數據的最大擴展或方差的方向在大多數應用中,我們希望將高秩矩陣縮減為低秩矩陣,同時保留重要信息。
1.1 SVD用於圖像壓縮
我們有多少次遇到過這個問題?我們喜歡用我們的智慧型手機瀏覽圖像,並隨機將照片保存。然後突然有一天 ,提示手機沒有空間了!而圖像壓縮有助於解決這一問題。
它將圖像的大小(以字節為單位)最小化到可接受的質量水平。這意味著你可以在相同磁碟空間中存儲更多圖像。
圖片壓縮利用了在SVD之後僅獲得的一些奇異值很大的原理。你可以根據前幾個奇異值修剪三個矩陣,並獲得原始圖像的壓縮近似值,人眼無法區分一些壓縮圖像。以下是在Python中編寫的代碼:
# 下載圖片 "https://cdn.pixabay.com/photo/2017/03/27/16/50/beach-2179624_960_720.jpg"import numpy as npimport pandas as pdimport matplotlib.pyplot as pltimport cv2# 灰度化讀取圖片img = cv2.imread('beach-2179624_960_720.jpg', 0)# 得到svdU, S, V = np.linalg.svd(img)# 得到矩陣的形狀print(U.shape, S.shape, V.shape)# 以不同component數繪製圖像comps = [638, 500, 400, 300, 200, 100]plt.figure(figsize = (16, 8))for i in range(6): low_rank = U[:, :comps[i]] @ np.diag(S[:comps[i]]) @ V[:comps[i], :] if(i == 0): plt.subplot(2, 3, i+1), plt.imshow(low_rank, cmap = 'gray'), plt.axis('off'), plt.title("Original Image with n_components =" + str(comps[i])) else: plt.subplot(2, 3, i+1), plt.imshow(low_rank, cmap = 'gray'), plt.axis('off'), plt.title("n_components =" + str(comps[i]))Output:
如果你說即使是最後一張圖像,看起來好像也不錯!是的,如果沒有前面的圖像對比,我也不會猜到這是經過壓縮的圖像。
1.2 SVD用於圖像恢復
我們將通過矩陣填充的概念(以及一個很酷的Netflix示例)來理解圖像恢復。
矩陣填充是在部分觀察的矩陣中填充缺失元素的過程。Netflix問題就是一個常見的例子。
給定一個評級矩陣,其中每個元素(i,j)表示客戶i對電影j的評級,即客戶i觀看了電影j,否則該值為缺失值,我們想要預測剩餘的元素以便對客戶於提出好的建議。
有助於解決這個問題的基本事實是,大多數用戶在他們觀看的電影和他們給這些電影的評級中都有一個模式。因此,評級矩陣幾乎沒有獨特的信息。這意味著低秩矩陣能夠為矩陣提供足夠好的近似。
這就是我們在SVD的幫助下所能夠實現的。
你還在哪裡看到這樣的屬性?是的,在圖像矩陣中!由於圖像是連續的,大多數像素的值取決於它們周圍的像素。因此,低秩矩陣可以是這些圖像的良好近似。
下面是結果的一張截圖:
Chen, Zihan. 「Singular Value Decomposition and its Applications in Image Processing.」 ACM, 2018
問題的整個表述可能很複雜並且需要了解其他一些概念。
1.3 SVD用於特徵臉
論文「Eigenfaces for Recognition」於1991年發表。在此之前,大多數面部識別方法都涉及識別個體特徵,如眼睛或鼻子,並根據這些特徵之間的位置,大小和關係來開發模型。
特徵臉方法試圖在面部圖像中提取相關信息,儘可能有效地對其進行編碼,並將一個面部編碼與資料庫中的模型編碼進行比較。
通過將每個面部表達為新面部空間中所選擇的特徵臉的線性組合來獲得編碼。
把這個方法分解為五個步驟:
收集面部訓練集通過找到最大方差的方向-特徵向量或特徵臉來找到最重要的特徵選擇對應於最高特徵值的M個特徵臉。這些特徵臉現在定義了一個新的面部空間將所有數據投影到此面部空間中對於新面部,將其投影到新面部空間中,找到空間中最近的面部,並將面部分類為已知或未知面部你可以使用PCA和SVD找到這些特徵臉。這是我在Labeled Faces in the Wild數據集中上執行SVD後獲得的幾個特徵臉中的第一個:
我們可以看到,只有前幾行中的圖像看起來像實際的面部。其他看起來很糟糕,因此我放棄了它們。我保留了總共120個特徵臉,並將數據轉換為新的面部空間。然後我使用k近鄰分類器來預測基於面部的姓名。
你可以在下面看到分類報告。顯然,還有改進的餘地。你可以嘗試調整特徵臉的數量或使用不同的分類器進行試驗:
看看一些預測值及其真實標籤:
1.4 SVD用於譜聚類
聚類是將類似對象劃分在一起的任務。這是一種無監督的機器學習技術。對於我們大多數人來說,聚類是K-Means聚類(一種簡單但功能強大的算法)的代名詞,但是,這並不是準確的說法。
考慮以下情況:
顯然,同心圓中有2個簇。但是,n_clusters = 2的KMeans給出了以下簇:
K-Means絕對不是這裡使用的合適算法。譜聚類是一種可以解決這個問題的技術,它源於圖論。以下是基本步驟:
從數據Affnity matrix(A)或Adjacent matrix開始。這表示一個對象與另一個對象的相似程度。在圖中,這將表示點之間是否存在邊緣找到每個對象的 Degree matrix (D) 。這是一個對角矩陣,其元素(i,i)等於對象i相似的對象數找到Affnity matrix的 Laplacian matrix(L) (L):L = A - D根據它們的特徵值找到Laplacian matrix的最高k個特徵向量在這些特徵向量上運行k-means,將對象聚類為k類你可以通過下面的連結閱讀完整的算法及其數學原理^2,而scikit-learn中譜聚類的實現類似於KMeans:
from sklearn.datasets import make_circlesfrom sklearn.neighbors import kneighbors_graphfrom sklearn.cluster import SpectralClusteringimport numpy as npimport matplotlib.pyplot as plt# s生成數據X, labels = make_circles(n_samples=500, noise=0.1, factor=.2)# 可視化數據plt.scatter(X[:, 0], X[:, 1])plt.show()# 訓練和預測s_cluster = SpectralClustering(n_clusters = 2, eigen_solver='arpack', affinity="nearest_neighbors").fit_predict(X)# 可視化結果plt.scatter(X[:, 0], X[:, 1], c = s_cluster)plt.show()你將從上面的代碼中得到以下不錯的聚類結果:
1.5 SVD用於從視頻中刪除背景
想一想如何區分視頻背景和前景。視頻的背景基本上是靜態的 - 它看不到很多變化。所有的變化都在前景中看到。這是我們用來將背景與前景分開的屬性。
以下是我們可以採用的步驟來實現此方法:
從視頻創建矩陣M -- 這是通過定期從視頻中採樣圖像快照,將這些圖像矩陣展平為數組,並將它們存儲為矩陣M的列。我們得到以下矩陣M的圖:
你認為這些水平和波浪線代表什麼?花一點時間考慮一下。
水平線表示在整個視頻中不改變的像素值。基本上,這些代表了視頻中的背景。波浪線顯示變化並代表前景。
因此,我們可以將M視為兩個矩陣的總和 - 一個表示背景,另一個表示前景背景矩陣沒有看到像素的變化,因此是多餘的,即它沒有很多獨特的信息。所以,它是一個低秩矩陣因此,M的低秩近似是背景矩陣。我們在此步驟中使用SVD我們可以通過簡單地從矩陣M中減去背景矩陣來獲得前景矩陣這是視頻一個刪除背景後的幀:
到目前為止,我們已經討論了SVD的五個非常有用的應用。但是,SVD背後的數學實際上是如何運作的?作為數據科學家,它對我們有多大用處?讓我們在下一節中理解這些要點。
2. SVD是什麼?
我在本文中大量使用了「秩」這個術語。事實上,通過關於SVD及其應用的所有文獻,你將非常頻繁地遇到術語「矩陣的秩」。那麼讓我們從了解這是什麼開始。
2.1 矩陣的秩
矩陣的秩是矩陣中線性無關的行(或列)向量的最大數量。如果向量r不能表示為r1和r2的線性組合,則稱向量r與向量r1和r2線性無關。
考慮下面的三個矩陣:
在矩陣A中,行向量r2是r1的倍數,r2 = 2 r1,因此它只有一個無關的行向量。Rank(A)= 1在矩陣B中,行向量r3是r1和r2之和,r3 = r1 + r2,但r1和r2是無關的,Rank(B)= 2在矩陣C中,所有3行彼此無關。Rank(C)= 3矩陣的秩可以被認為是由矩陣表示的獨特信息量多少的代表。秩越高,信息越高。
2.2 SVD
SVD將矩陣分解為3個矩陣的乘積,如下所示:
如果A是m x n矩陣:
U是左奇異向量的m×m矩陣S是以遞減順序排列的奇異值的m×n對角矩陣V是右奇異向量的n×n矩陣2.3 為什麼SVD用於降維?
你可能想知道我們為什麼要經歷這種看似辛苦的分解。可以通過分解的替代表示來理解原因。見下圖:
分解允許我們將原始矩陣表示為低秩矩陣的線性組合。
在實際應用中,你將觀察到的只有前幾個(比如k)奇異值很大。其餘的奇異值接近於零。因此,可以忽略除前幾個之外而不會丟失大量信息。請參見下圖中的矩陣截斷方式:
總結以下3點:
使用SVD,我們能夠用3個較小的矩陣U,S和V表示我們的大矩陣A這在大型計算中很有用我們可以得到A的k-秩近似。為此,選擇前k個奇異值並相應地截斷3個矩陣。3. 3種在Python中使用SVD的方法
我們知道什麼是SVD,它是如何工作的,以及它在現實世界中的用途。但是我們如何自己實現SVD呢?
SVD的概念聽起來很複雜。你可能想知道如何找到3個矩陣U,S和V。如果我們手動計算這些矩陣,這是一個漫長的過程。
幸運的是,我們不需要手動執行這些計算。我們可以用三種簡單的方式在Python中實現SVD。
3.1 numpy中的SVD
NumPy是Python中科學計算的基礎包。它具有有用的線性代數功能以及其他應用。
你可以使用numpy.linalg中的SVD獲取完整的矩陣U,S和V。注意,S是對角矩陣,這意味著它的大多數元素都是0。這稱為稀疏矩陣。為了節省空間,S作為奇異值的一維數組而不是完整的二維矩陣返回。
import numpy as npfrom numpy.linalg import svd# 定義二維矩陣A = np.array([[4, 0], [3, -5]])U, S, VT = svd(A)print("Left Singular Vectors:")print(U)print("Singular Values:") print(np.diag(S))print("Right Singular Vectors:") print(VT)# 檢查分解是否正確# @ 表示矩陣乘法print(U @ np.diag(S) @ VT)3.2 scikit-learn中的Truncated SVD
在大多數常見的應用中,我們不希望找到完整的矩陣U,S和V。我們在降維和潛在語義分析中看到了這一點,還記得嗎?
我們最終會修剪矩陣,所以為什麼要首先找到完整的矩陣?
在這種情況下,最好使用sklearn.decomposition中的TruncatedSVD。你可以通過n_components參數指定所需的特徵數量輸出。n_components應嚴格小於輸入矩陣中的特徵數:
import numpy as npfrom sklearn.decomposition import TruncatedSVDA = np.array([[-1, 2, 0], [2, 0, -2], [0, -2, 1]])print("Original Matrix:")print(A)svd = TruncatedSVD(n_components = 2)A_transf = svd.fit_transform(A)print("Singular values:")print(svd.singular_values_)print("Transformed Matrix after reducing to 2 features:")print(A_transf)3.3 scikit-learn中的Randomized SVD
Randomized SVD提供與Truncated SVD相同的結果,並且具有更快的計算時間。Truncated SVD使用ARPACK精確求解,但隨機SVD使用了近似技術。
import numpy as npfrom sklearn.utils.extmath import randomized_svdA = np.array([[-1, 2, 0], [2, 0, -2], [0, -2, 1]])u, s, vt = randomized_svd(A, n_components = 2)print("Left Singular Vectors:")print(u)print("Singular Values:") print(np.diag(s))print("Right Singular Vectors:") print(vt)