理论部分
任何一个
m
×
n
m\times n
m×n矩阵
A
A
A,通过svd分解,可以分解为三个矩阵相乘
A
=
P
Σ
Q
T
A=P\Sigma Q^T
A=PΣQT
其中, Σ \Sigma Σ是一个 m × n m\times n m×n对角矩阵,除了对角线上的元素之外,其余位置元素全部为0。
我们假设 P = [ P 1 , P 2 , . . . , P m ] P=[P_1, P_2, ..., P_m] P=[P1,P2,...,Pm],其中, P i P_i Pi是 m m m维列向量; Q = [ Q 1 , Q 2 , . . . , Q n ] Q=[Q_1, Q_2, ..., Q_n] Q=[Q1,Q2,...,Qn],其中, Q j Q_j Qj是 n n n维列向量; Σ \Sigma Σ的对角线元素为 σ 1 , σ 2 , . . . , σ m i n ( m , n ) \sigma_1, \sigma_2, ..., \sigma_{min(m, n)} σ1,σ2,...,σmin(m,n),其中, σ 1 ≥ σ 2 ≥ . . . ≥ σ m i n ( m , n ) \sigma_1\ge\sigma_2\ge...\ge\sigma_{min(m, n)} σ1≥σ2≥...≥σmin(m,n)。
我们仅考虑
m
≥
n
m\ge n
m≥n的情形(情形
m
≤
n
m\le n
m≤n类似)。此时,矩阵
A
A
A可以分解为
A
=
P
Σ
Q
T
=
[
P
1
,
P
2
,
.
.
.
,
P
m
]
Σ
[
Q
1
T
,
Q
2
T
,
.
.
.
,
Q
n
T
]
T
=
σ
1
P
1
Q
1
T
+
σ
2
P
2
Q
2
T
+
.
.
.
+
σ
n
P
n
Q
n
T
\begin{array}{lll} A&=&P\Sigma Q^T\\ &=&[P_1, P_2, ..., P_m]\Sigma[Q_1^T, Q_2^T, ..., Q_n^T]^T\\ &=&\sigma_1 P_1 Q_1^T+\sigma_2 P_2 Q_2^T+...+\sigma_nP_nQ_n^T \end{array}
A===PΣQT[P1,P2,...,Pm]Σ[Q1T,Q2T,...,QnT]Tσ1P1Q1T+σ2P2Q2T+...+σnPnQnT
更重要的是,对角线上的元素按照从大到小,从上到下进行排列,并且往往最大的几个元素比最小的几个元素大得多,以至于较小元素可以忽略。
比如,我们有100个特征值,前10%的特征值比后90%的特征值大得多,我们就选取前10%的特征值,而将后90%的特征值略掉。这样,我们就用了10%的内容,保留了矩阵 A A A的绝大部分信息。
此时,保留前
k
k
k个特征值,
A
≈
σ
1
P
1
Q
1
T
+
σ
2
P
2
Q
2
T
+
.
.
.
+
σ
k
P
k
Q
k
T
A\approx\sigma_1 P_1 Q_1^T+\sigma_2 P_2 Q_2^T+...+\sigma_kP_kQ_k^T
A≈σ1P1Q1T+σ2P2Q2T+...+σkPkQkT
实际操作
我们随便挑选一个图片作为例子,保存为dog.jpg
我们可以从图片处理中,看看svd降维的效果。
# 第三方库
from PIL import Image
from scipy.linalg import svd
import numpy as np
# 加载图片
image = Image.open('D:/myfile/开课吧/推荐系统/第六节/dog.jpg')
# 转化为灰度图
grey_image = image.convert('L')
grey_image.show()
转化为灰度图,如下
# 转化为矩阵
A = np.array(grey_image)
# svd分解
p, s, q = svd(A)
print('左奇异矩阵p的大小为', p.shape)
print('矩阵A的特征值个数为', len(s))
print('其中,前4个特征值为', s[:4])
print('后4个特征值为', s[-4:])
print('右奇异矩阵q的大小为', q.shape)
左奇异矩阵p的大小为 (683, 683)
矩阵A的特征值个数为 683
其中,前4个特征值为 [109075.24 28300.275 16735.781 10259.204]
后4个特征值为 [28.534803 27.757359 26.593216 23.755302]
右奇异矩阵q的大小为 (1024, 1024)
可以看到,前4个特征值要比后4个特征值大得多,所以将后四个特征值忽略,并不影响图片质量。
# 定义函数get_k_features,选择矩阵A的前k个特征值,返回图像矩阵
# 注意:该函数仅适用于m<n的A矩阵
def get_k_features(k):
# 生成sigma矩阵
sigma = np.diag(list(s)+[0]*(len(q)-len(s)))[:len(s), :] # m*n矩阵,其中,对角线上是m个特征值
# 选取前k个特征值
sigma_k = sigma[:k, :] # k*n矩阵
# 近似还原A矩阵
A_approx = p[:, :k].dot(sigma_k).dot(q) # p矩阵需要取前k列,变成m*k矩阵;sigma_k是k*n矩阵;q是n*n矩阵
return A_approx
# 取前5%的特征值
image_1 = get_k_features(30)
# 取前10%的特征值
image_2 = get_k_features(60)
# 取前20%的特征值
image_3 = get_k_features(120)
# 依次生成图片
Image_1 = Image.fromarray(np.uint8(image_1))
Image_1.show()
Image_2 = Image.fromarray(np.uint8(image_1))
Image_2.show()
Image_3 = Image.fromarray(np.uint8(image_1))
Image_3.show()
选取的特征值数量从小到大,图片依次为
可以看到,原图片总共有683个特征值,当我们仅仅选取前60个特征值时,如果不追求细节,图片基本能辨认出来;当我们提取更多的特征值时,图片质量也就越来越高。
综上,svd可以通过选择较少的主要特征值来保留大部分信息,也就是达到了降维的效果。