PCA, SVD以及代碼示例
本文是對PCA和SVD學習的整理筆記,為了避免很多重復內容的工作,我會在介紹概念的時候引用其他童鞋的工作和內容,具體來源我會標記在參考資料中。
一.PCA (Principle component analysis)
PCA(主成分分析)通過線性變換將原始數據變換為一組各維度線性無關的表示,可用於提取數據的主要特征分量,常用於高維數據的降維。
為什麽需要降維?以下圖為例,圖c中的點x y 呈現明顯線性相關,假如以數據其實以數據點分布的方向的直線上的投影(一維)已經能夠很好的描述這組數據特點了 。明顯的,將數據維度降低:1能夠降低數據計算量 2壓縮數據重構 3.部分情況下甚至能夠改善數據特征。
那麽如何在降維時盡量保留源數據的特征,PCA就是一種。關於如何理解,PCA,通常可以用兩種方式進行理解:一是讓降維後的數據分布盡量分散能夠保留信息(方差盡量大) 二是降維導致的信息損失盡量小。關於第一種理解方式,大家可以參考這裏,細致而清晰。第二種方法通常需要簡單的公式推導,利用拉格朗日乘子將帶約束的優化轉化為無約束優化後求導,有興趣的童鞋可以參考這裏.
上面兩篇文章關於兩個不同方向解釋PCA,那麽這裏就直接寫出PCA的降維方法,假設原數據為X:
設有m條n個特征的數據。
1)將原始數據按列組成n行m列矩陣X,即每一列代表一組數據
2)將X的每一行(代表一個屬性字段)進行零均值化,即減去這一行的均值
3)求出協方差矩陣\[C=\frac{1}{m}XX^{T}\]
4)求出協方差矩陣的特征值及對應的特征向量(對\[XX^{T}\]進行特征分解)
5)將特征向量按對應特征值大小從上到下按行排列成矩陣,取前k行組成矩陣P
6)Y=PX即為降維到k維後的數據
好了,PCA的流程中似乎和奇異值似乎沒有什麽關系。但是,首先\[XX^{T}\]計算過程中如果有較小的值很容易造成精度損失,其次特征分解只能處理方針,有沒有更方便的方式獲得降維矩陣P,這就要用到SVD了。
二 SVD(Singular value decomposition)
首先,關於特征分解和奇異值分解的物理意義理解,我推薦看這裏:
總結一下,特征值分解和奇異值分解都是給一個矩陣(線性變換)找一組特殊的基,特征值分解找到了特征向量這組基,在這組基下該線性變換只有縮放效果。而奇異值分解則是找到另一組基,這組基下線性變換的旋轉、縮放、投影三種功能獨立地展示出來了, 簡而言之:
1.特征值分解其實是對旋轉縮放兩種效應的歸並
2.奇異值分解其實是歲旋轉縮放和投影效應的歸並
也就是說,奇異值分解可以說是包含了特征分解!來看Wikipedia的解釋:
在矩陣M的奇異值分解中
- V的列(columns)組成一套對的正交"輸入"或"分析"的基向量。這些向量是的特征向量。
- U的列(columns)組成一套對的正交"輸出"的基向量。這些向量是的特征向量。
- Σ對角線上的元素是奇異值,可視為是在輸入與輸出間進行的標量的"膨脹控制"。這些是及的特征值的非負平方根,並與U和V的行向量相對應。
這裏的*標識轉置T。看到其中U就是MM*的特征向量了,那麽也就是說利用奇異值分解也可以做PCA了,而且還不用求\[XX^{T}\]!
不僅如此,單獨觀看奇異值分解的式子,我們也可以利用主成分的思想,利用奇異值分解的公式對高維數據進行壓縮,具體看下面的代碼。
from PIL import Image import numpy as np import matplotlib.pyplot as plt def decide_k(s, ratio): sum_tmp = 0 sum_s = np.sum(s) k = 0 for i in s: k += 1 sum_tmp += i if (sum_tmp / sum_s) >= ratio: print("reduce dims is:", k) return k if k >= s.shape: raise ValueError(‘input dim could not less than compress dims‘) def svd_refactor(x, ratio=0.90): # compress to a k dims data before = x.shape[0] * x.shape[1] print("before compress:", before) # after svd, save cu cv and cs ,then we could use them to refactor picture mean_ = np.mean(x, axis=1, keepdims=True) x = x - mean_ u, s, v = np.linalg.svd(x) k = decide_k(s, ratio) c_u = u[:, :k] c_v = v[:k, :] c_s = s[0:k] after = c_u.shape[0] * c_u.shape[1] + c_v.shape[0] * c_v.shape[1] + c_s.shape[0] print("after compress:", after) print("ratio", after / before) # refactor s_s = np.diag(c_s) return np.dot(c_u, np.dot(s_s, c_v)) def pca_refactor(x, ratio=0.90): # compress to a k dims data before = x.shape[0] * x.shape[1] print("before pca:", before) # after svd, save cu cv and cs ,then we could use them to refactor picture mean_ = np.mean(x, axis=1, keepdims=True) x = x - mean_ u, s, v = np.linalg.svd(x) k = decide_k(s, ratio) c_u = u[:, :k] eig_vec = c_u.transpose() pca_result = np.dot(eig_vec, x) after = c_u.shape[0] * c_u.shape[1] + pca_result.shape[0] * pca_result.shape[1] print("after pca:", after) print("ratio", after / before) # since U*U=I return np.dot(c_u, pca_result) if __name__ == ‘__main__‘: img_file = Image.open(‘test.jpg‘).convert(‘L‘) # convert picture to gray img_array = np.array(img_file) print(img_array.shape) img_array = pca_refactor(img_array) plt.figure("beauty") plt.imshow(img_array, cmap=plt.cm.gray) plt.axis(‘off‘) plt.show()
其中 關於如何選擇降低維度到多少維的decide_k函數,采用了貢獻率。就是指當剩余特征值和的比例小於一定百分比(0.05)的時候舍棄他們。
Reference:
李航《統計學習方法》
PCA的數學原理
機器學習中的數學(5)-強大的矩陣奇異值分解(SVD)及其應用
機器學習中的SVD和PCA.知乎
奇異值的物理意義是什麽?
矩陣的奇異值與特征值有什麽相似之處與區別之處
從PCA和SVD的關系拾遺
PCA, SVD以及代碼示例