svd分解 复原 sklearn和numpy实现

时间:2022-12-08 10:55:26

网上有很多分析svd分解原理的文章,例如下面的链接,
svd原理解释,本文主要介绍在sklearn和numpy中,如何进行svd分解以及如何复原,可以利用到图像压缩和复原等任务中。

原理

1.pca的主要过程

pca可以将 m ∗ n m*n mn的矩阵 A A A进行降维,需要先求出A的协方差矩阵 C = 1 m A T A C=\frac{1}{m}A^{T}A C=m1ATA,再求出 C C C的特征值和特征向量,并将前 k k k大的特征值对应的特征向量按列拼接,得到 P ∈ n ∗ k P \in n*k Pnk,最后得到降维后的矩阵 A n e w = A P ∈ m ∗ k A_{new}=AP \in m*k Anew=APmk

2.svd的主要过程

svd的主要作用就也是将一个 m ∗ n m*n mn的矩阵 A A A进行降维,与pca不同,svd可以将一个矩阵 A A A分解成 A = U S V T A=USV^{T} A=USVT的形式, U U U是m ∗ * m的矩阵, V V V是n ∗ * n的矩阵, S S S是m ∗ * n的对角矩阵,进行降维时,只需要计算 A ∗ V T A*V^{T} AVT即可(需要取 V t V^{t} Vt的前k行,才可以降维成m ∗ * k的新矩阵)

代码实现

接下来就是具体实现了,主要对一个随机构造的矩阵 x x x,进行svd降维和复原。

1. 使用sklearn进行降维和复原

首先构造数据,

from sklearn.decomposition import TruncatedSVD
from scipy.sparse import random as r
# 假设数据是5*10的
x=np.random.randint(1,10,size=[5,10])
print("原始数据\n",x)
原始数据
 [[4 5 5 5 9 1 4 7 3 9]
 [7 7 3 5 2 7 3 7 8 3]
 [3 2 5 8 4 9 4 2 6 7]
 [7 5 2 5 8 6 5 5 9 9]
 [5 3 4 8 9 9 1 1 1 2]]

降维,假设只保留5个主要维度,首先使用原生svd,得到降维后的结果 new_x :

#假设只保留5个主要维度
n_com=5
svd=TruncatedSVD(n_components=n_com)
svd.fit(x)
new_x=svd.transform(x)
# 降维后的结果
print(new_x)
[[16.34513059 -4.8106942  -5.82035261 -0.90765052 -1.73020042]
 [16.20910717 -1.61394637  6.43060373 -2.77069517 -1.27695179]
 [16.36007388  3.50982968  1.07136253  4.50162336 -1.61760551]
 [19.80727866 -3.17339531  0.55105757  1.28356345  3.26344521]
 [14.51824224  7.59233143 -2.58588072 -2.70863565  0.744083  ]]

也可以进行手工降维,主要用到svd.components_:

# 手工降维,right即为svd分解后的右矩阵
right=svd.components_
print(right.shape)
# 计算
print(np.dot(x,right[:,:].T))
(5, 10)
[[16.34513059 -4.8106942  -5.82035261 -0.90765052 -1.73020042]
 [16.20910717 -1.61394637  6.43060373 -2.77069517 -1.27695179]
 [16.36007388  3.50982968  1.07136253  4.50162336 -1.61760551]
 [19.80727866 -3.17339531  0.55105757  1.28356345  3.26344521]
 [14.51824224  7.59233143 -2.58588072 -2.70863565  0.744083  ]]

复原,可以直接得到原始的矩阵:

# sklearn可以直接复原
print(svd.inverse_transform(new_x))
[[4. 5. 5. 5. 9. 1. 4. 7. 3. 9.]
 [7. 7. 3. 5. 2. 7. 3. 7. 8. 3.]
 [3. 2. 5. 8. 4. 9. 4. 2. 6. 7.]
 [7. 5. 2. 5. 8. 6. 5. 5. 9. 9.]
 [5. 3. 4. 8. 9. 9. 1. 1. 1. 2.]]

2.使用numpy进行降维和复原

首先得到 u 、 s 、 v u、s、v usv 三个矩阵

# 可以得到三个矩阵
u,s,v=np.linalg.svd(x)
print("u,s,v的形状:")
print("u:",u.shape)
print("s:",s.shape)
print("v:",v.shape)
u,s,v的形状:
u: (5, 5)
s: (5,)
v: (10, 10)

降维,利用 x x x v v v 即可实现降维:

# 降维结果与sklearn基本一致,假设降维到n_com=5
print(np.dot(x,v[:n_com,:].T))
[[-16.34513059  -4.8106942    5.82035261  -0.90765052  -1.73020042]
 [-16.20910717  -1.61394637  -6.43060373  -2.77069517  -1.27695179]
 [-16.36007388   3.50982968  -1.07136253   4.50162336  -1.61760551]
 [-19.80727866  -3.17339531  -0.55105757   1.28356345   3.26344521]
 [-14.51824224   7.59233143   2.58588072  -2.70863565   0.744083  ]]

复原,需要依次取出 u 、 v u、v uv 中对应的行或列,并按照 s s s 矩阵中的权重值进行加权计算,可以得到原始的矩阵 x x x

# m为原始行数,n为原始列数
m=5
n=10
# 将u、v、s三个矩阵进行运算,将结果累加到a中并返回
a=np.zeros([m,n])

for i in range(0,n_com):
    # 依次取出u和v矩阵的对应数据,并reshape 
    ui=u[:,i].reshape(m,1)
    vi=v[i].reshape(1,n)
    # 将其按照s的权重进行累加
    a+=s[i]*np.dot(ui,vi)
    
# 结果与原始数据基本一致    
print(a)
[[4. 5. 5. 5. 9. 1. 4. 7. 3. 9.]
 [7. 7. 3. 5. 2. 7. 3. 7. 8. 3.]
 [3. 2. 5. 8. 4. 9. 4. 2. 6. 7.]
 [7. 5. 2. 5. 8. 6. 5. 5. 9. 9.]
 [5. 3. 4. 8. 9. 9. 1. 1. 1. 2.]]