SVD(奇异值分解)Python实现

注:《SVD(奇异值分解)小结 》中分享了 SVD 原理,但其中只是利用了numpy.linalg.svd函数应用了它,并没有提到如何自己编写代码实现它,在这里,我再分享一下如何自已写一个SVD 函数。但是这里会利用到 SVD 的原理,如果大家还不明白它的原理,可以去看看《SVD(奇异值分解)小结 》,或者自行百度 /google。数据集:https://pan.baidu.com/s/1ZmpUSIscy4VltcimwwIWew

1、SVD 算法实现#

1.1 SVD 原理简单回顾#

有一个m×nm×n的实数矩阵AA,我们可以将它分解成如下的形式

A=UΣVT(1-1)(1-1)A=UΣVT

其中UUVV均为单位正交阵,即有UUT=IUUT=IVVT=IVVT=IUU称为左奇异矩阵VV称为右奇异矩阵ΣΣ仅在主对角线上有值,我们称它为奇异值,其它元素均为 0。上面矩阵的维度分别为URm×m, ΣRm×nU∈Rm×m, Σ∈Rm×n, VRn×n V∈Rn×n

正常求上面的U,V,ΣU,V,Σ不便于求,我们可以利用如下性质

AAT=UΣVTVΣTUT=UΣΣTUT(1-2)(1-2)AAT=UΣVTVΣTUT=UΣΣTUT

ATA=VΣTUTUΣVT=VΣTΣVT(1-3)(1-3)ATA=VΣTUTUΣVT=VΣTΣVT

1.2 SVD 算法#

1.1 小节,对式(1-3)和式(1-4)做特征值分解,即可得到奇异值分解的结果。但是样分开求存在一定的问题,由于做特征值分解的时候,特征向量的正负号并不影响结果,比如,我们利用式(1-3)和(1-4)做特征值分解

AATui=σiuiorAAT(ui)=σi(ui)ATAvi=σiviorATA(vi)=σi(vi)AATui=σiuiorAAT(−ui)=σi(−ui)ATAvi=σiviorATA(−vi)=σi(−vi)

如果在计算过程取,取上面的uiui组成左奇异矩阵UU,取vi−vi组成右奇异矩阵VV,此时AUΣVTA≠UΣVT。因此求vivi时,要根据uiui来求,这样才能保证A=UΣVTA=UΣVT。因此,我们可以得出如下 1.1 计算 SVD 的算法。它主要是先做特性值分解,再根据特征值分解得到的左奇异矩阵UU间接地求出部分的右奇异矩阵VRm×nV′∈Rm×n

算法 1.1:SVD

输入:样本数据
输出:左奇异矩阵,奇异值矩阵,右奇异矩阵
  1. 计算特征值: 特征值分解AATAAT,其中ARm×nA∈Rm×n为原始样本数据

    AAT=UΣΣTUTAAT=UΣΣTUT

    得到左奇异矩阵URm×mU∈Rm×m和奇异值矩阵ΣRm×mΣ′∈Rm×m

  2. 间接求部分右奇异矩阵:VRm×nV′∈Rm×n

    利用A=UΣVA=UΣ′V′可得

    V=(UΣ)1A=(Σ)1UTA(1-4)(1-4)V′=(UΣ′)−1A=(Σ′)−1UTA

  3. 返回U, Σ, VU, Σ′, V′,分别为左奇异矩阵,奇异值矩阵,右奇异矩阵。

注:这里得到的ΣΣ′VV′与式(1-2)所得到的Σ, VΣ, V有区别,它们的维度不一样。ΣΣ′是只取了前mm个奇异值形成的对角方阵,即ΣRm×mΣ′∈Rm×mVV′不是一个方阵,它只取了VRm×nV∈Rm×n的前mm行(假设m<nm<n),即有V=V(:m,)V′=V(:m,⋅)。这样一来,我们同样有类似式(1-1)的数学关系成立,即

A=UΣ(V)T(1-5)(1-5)A=UΣ′(V′)T

我们可以利用此关系重建原始数据。

2、SVD 的 Python 实现#

以下代码的运行环境为python3.6+jupyter5.4

2.1 SVD 实现过程#

读取数据#

这里面的数据集大家随便找一个数据就好,如果有需要我的数据集,可以下在面留言。

Copy Highlighter-hljs
import numpy as np import pandas as pd from scipy.io import loadmat

# 读取数据,使用自己数据集的路径。
train_data_mat = loadmat("../data/train_data2.mat")
train_data = train_data_mat["Data"]
print(train_data.shape)

特征值分解#

Copy Highlighter-hljs
# 数据必需先转为浮点型,否则在计算的过程中会溢出,导致结果不准确 train_dataFloat = train_data / 255.0 # 计算特征值和特征向量 eval_sigma1,evec_u = np.linalg.eigh(train_dataFloat.dot(train_dataFloat.T))

计算右奇异矩阵#

Copy Highlighter-hljs
#降序排列后,逆序输出 eval1_sort_idx = np.argsort(eval_sigma1)[::-1] # 将特征值对应的特征向量也对应排好序 eval_sigma1 = np.sort(eval_sigma1)[::-1] evec_u = evec_u[:,eval1_sort_idx] # 计算奇异值矩阵的逆 eval_sigma1 = np.sqrt(eval_sigma1) eval_sigma1_inv = np.linalg.inv(np.diag(eval_sigma1)) # 计算右奇异矩阵 evec_part_v = eval_sigma1_inv.dot((evec_u.T).dot(train_dataFloat))

上面的计算出的evec_u, eval_sigma1, evec_part_v分别为左奇异矩阵,所有奇异值,右奇异矩阵。

2.2 SVD 降维后重建数据#

取不同个数的奇异值,重建图片,计算出均方误差,如图 2-1 所示。从图中可以看出,随着奇异值的增加,均方误差(MSE)在减小,且奇异值和的比率正快速上升,在 100 维时,奇异值占总和的 53%。

fig2-1 exp2_svd1.svg
图 2-1 奇值分解维度和均方误差变化图

注: 均方误差 MSE 有如下计算公式

MSE=1n((y1y1)2+(y2y2)2++(ynyn)2)MSE=1n((y1−y1′)2+(y2−y2′)2+⋯+(yn−yn′)2)

我们平时听到的RMSE=MSERMSE=MSE

将图和 10、50、100 维的图进行比较,如图 2-2 所示。在直观上,100 维时,能保留较多的信息,此时能从图片中看出车辆形状。

fig2-2 exp2_svd2.svg
图 2-2 原图与降维重建后的图比较

总结#

SVD 与特征值分解(EVD)非常类似,应该说 EVD 只是 SVD 的一种特殊怀况。我们可以通过它们在实际的应用中返过来理解特征值 / 奇异值的含义:特征值 / 奇异值代表着数据的信息量,它的值越大,信息越多。

最近作业是真的多呀,冒着生命危险来分享,希望能给大家带来帮助😄