SVD(奇异值分解)的通俗理解和实际应用

博客

2019-06-30

1143

0

特征值和奇异值在大部分人的印象中,往往是停留在纯粹的数学计算中。而且线性代数或者矩阵论里面,也很少讲任何跟特征值与奇异值有关的应用背景。

 

奇异值分解是一个有着很明显的物理意义的一种方法,它可以将一个比较复杂的矩阵用更小更简单的几个子矩阵的相乘来表示,这些小矩阵描述的是矩阵的重要的特性。就像是描述一个人一样,给别人描述说这个人长得浓眉大眼,方脸,络腮胡,而且带个黑框的眼镜,这样寥寥的几个特征,就让别人脑海里面就有一个较为清楚的认识,实际上,人脸上的特征是有着无数种的,之所以能这么描述,是因为人天生就有着非常好的抽取重要特征的能力,让机器学会抽取重要的特征,SVD是一个重要的方法。

 

在机器学习领域,有相当多的应用与奇异值都可以扯上关系,比如做feature reductionPCA,做数据压缩(以图像压缩为代表)的算法,还有做搜索引擎语义层次检索的LSILatent Semantic Indexing) 

 

 

一、特征值与奇异值 

 

特征值分解和奇异值分解在机器学习领域都是属于满地可见的方法。两者有着很紧密的关系,接下来会谈到特征值分解和奇异值分解的目的都是一样,就是提取出一个矩阵最重要的特征。先谈特征值分解。

 

1.1 特征值 

如果说一个向量v是方阵A的特征向量,将一定可以表示成下面的形式:640?wx_fmt=png

 

这时候λ就被称为特征向量v对应的特征值,一个矩阵的一组特征向量是一组正交向量。特征值分解是将一个矩阵分解成下面的形式:

640?wx_fmt=png

 

其中Q是这个矩阵A的特征向量组成的矩阵,Σ是一个对角阵,每一个对角线上的元素就是一个特征值。我这里引用了一些参考文献中的内容来说明一下。

 

首先,要明确的是,一个矩阵其实就是一个线性变换,因为一个矩阵乘以一个向量后得到的向量,其实就相当于将这个向量进行了线性变换。比如说下面的一个矩阵:

640?wx_fmt=png

 

它其实对应的线性变换是下面的形式:

640?wx_fmt=png

 

因为这个矩阵M乘以一个向量(x,y)的结果是:

640?wx_fmt=png

 

上面的矩阵是对称的,所以这个变换是一个对xy轴的方向一个拉伸变换(每一个对角线上的元素将会对一个维度进行拉伸变换,当值>1时,是拉长,当值<1时时缩短),当矩阵不是对称的时候,假如说矩阵是下面的样子:

640?wx_fmt=png

 

它所描述的变换是下面的样子:

 

640?wx_fmt=png

 

这其实是在平面上对一个轴进行的拉伸变换(如蓝色的箭头所示),在图中,蓝色的箭头是一个最主要的变化方向(变化方向可能有不止一个),如果我们想要描述好一个变换,那我们就描述好这个变换主要的变化方向就好了。反过头来看看之前特征值分解的式子,分解得到的Σ矩阵是一个对角阵,里面的特征值是由大到小排列的,这些特征值所对应的特征向量就是描述这个矩阵变化方向(从主要的变化到次要的变化排列)。

 

考虑更一般的非对称矩阵 640?wx_fmt=png

 

很遗憾,此时我们再也找不到一组网格,使得矩阵作用在该网格上之后只有拉伸变换(找不到背后的数学原因是对一般非对称矩阵无法保证在实数域上可对角化,不明白也不要在意)。

 

我们退而求其次,找一组网格,使得矩阵作用在该网格上之后允许有拉伸变换和旋转变换,但要保证变换后的网格依旧互相垂直,这是可以做到的,如下图所示。

640?wx_fmt=jpeg

640?wx_fmt=jpeg

简言之,当矩阵是高维的情况下,那么这个矩阵就是高维空间下的一个线性变换,这个变换也同样有很多的变换方向,我们通过特征值分解得到的前N个特征向量,那么就对应了这个矩阵最主要的N个变化方向。我们利用这前N个变化方向,就可以近似这个矩阵(变换)。

 

也就是之前说的:提取这个矩阵最重要的特征。总结一下,特征值分解可以得到特征值与特征向量,特征值表示的是这个特征到底有多重要,而特征向量表示这个特征是什么,可以将每一个特征向量理解为一个线性的子空间,我们可以利用这些线性的子空间干很多的事情。不过,特征值分解也有很多的局限,比如说变换的矩阵必须是方阵。

 

下面我们就可以自然过渡到奇异值分解的引入。

 

1.2 奇异值 

下面谈谈奇异值分解。特征值分解是一个提取矩阵特征很不错的方法,但是它只是对方阵而言的,在现实的世界中,我们看到的大部分矩阵都不是方阵,比如说有N个学生,每个学生有M科成绩,这样形成的一个N * M的矩阵就不可能是方阵,我们怎样才能描述这样普通的矩阵呢的重要特征呢?奇异值分解可以用来干这个事情,奇异值分解是一个能适用于任意的矩阵的一种分解的方法:

640?wx_fmt=png

 

假设A是一个N * M的矩阵,那么得到的U是一个N * N的方阵(里面的向量是正交的,U里面的向量称为左奇异向量),Σ是一个N * M的矩阵(除了对角线的元素都是0,对角线上的元素称为奇异值),V’(V的转置)是一个N * N的矩阵,里面的向量也是正交的,V里面的向量称为右奇异向量),从图片来反映几个相乘的矩阵的大小可得下面的图片

 

640?wx_fmt=png

 

那么奇异值和特征值是怎么对应起来的呢?首先,我们将一个矩阵A的转置 * A,将会得到一个方阵,我们用这个方阵求特征值可以得到:

640?wx_fmt=png

这里得到的v,就是我们上面的右奇异向量。此外我们还可以得到:

640?wx_fmt=png

这里的σ就是上面说的奇异值,u就是上面说的左奇异向量。奇异值σ跟特征值类似,在矩阵Σ中也是从大到小排列,而且σ的减少特别的快,在很多情况下,前10%甚至1%的奇异值的和就占了全部的奇异值之和的99%以上了。也就是说,我们也可以用前r大的奇异值来近似描述矩阵,这里定义一下部分奇异值分解:

640?wx_fmt=png

 

r是一个远小于mn的数,这样矩阵的乘法看起来像是下面的样子:

640?wx_fmt=png

 

右边的三个矩阵相乘的结果将会是一个接近于A的矩阵,在这儿,r越接近于n,则相乘的结果越接近于A。而这三个矩阵的面积之和(在存储观点来说,矩阵面积越小,存储量就越小)要远远小于原始的矩阵A,我们如果想要压缩空间来表示原矩阵A,我们存下这里的三个矩阵:UΣV就好了。

 

说句大白话,称作「奇异值」可能无法顾名思义迅速理解其本质,那咱们换个说法,称作「主特征值」,你可能就迅速了然了。

 

而奇异值分解的几何含义为:对于任何的一个矩阵,我们要找到一组两两正交单位向量序列,使得矩阵作用在此向量序列上后得到新的向量序列保持两两正交。

 

继续拿1.1节的例子进一步阐述,奇异值的几何含义为:这组变换后的新的向量序列的长度。

 

640?wx_fmt=jpeg

640?wx_fmt=jpeg

 

640?wx_fmt=png

640?wx_fmt=jpeg

奇异值的计算是一个难题,是一个O(N^3)的算法。在单机的情况下当然是没问题的,matlab在一秒钟内就可以算出1000 * 1000的矩阵的所有奇异值,但是当矩阵的规模增长的时候,计算的复杂度呈3次方增长,就需要并行计算参与了。Google的吴军老师在数学之美系列谈到SVD的时候,说起Google实现了SVD的并行化算法,说这是对人类的一个贡献,但是也没有给出具体的计算规模,也没有给出太多有价值的信息。

 

其实SVD还是可以用并行的方式去实现的,在解大规模的矩阵的时候,一般使用迭代的方法,当矩阵的规模很大(比如说上亿)的时候,迭代的次数也可能会上亿次,如果使用Map-Reduce框架去解,则每次Map-Reduce完成的时候,都会涉及到写文件、读文件的操作。个人猜测Google云计算体系中除了Map-Reduce以外应该还有类似于MPI的计算模型,也就是节点之间是保持通信,数据是常驻在内存中的,这种计算模型比Map-Reduce在解决迭代次数非常多的时候,要快了很多倍。

 

Lanczos迭代就是一种解对称方阵部分特征值的方法(之前谈到了,解A’* A得到的对称方阵的特征值就是解A的右奇异向量),是将一个对称的方程化为一个三对角矩阵再进行求解。按网上的一些文献来看,Google应该是用这种方法去做的奇异值分解的。请见Wikipedia上面的一些引用的论文,如果理解了那些论文,也“几乎”可以做出一个SVD了。

 

 

二、奇异值的直观应用 

 

2.1 女神图片压缩

下面,咱们从女神上野树里(Ueno Juri)的一张像素为高度450*宽度333的照片,来直观理解奇异值在物理上到底代表什么意义(请屏幕前的痴汉暂停舔屏)。

640?wx_fmt=jpeg

 

我们都知道,图片实际上对应着一个矩阵,矩阵的大小就是像素大小,比如这张图对应的矩阵阶数就是450*333,矩阵上每个元素的数值对应着像素值。我们记这个像素矩阵为A 现在我们对矩阵A进行奇异值分解。直观上,奇异值分解将矩阵分解成若干个秩一矩阵之和,用公式表示就是:

 

640?wx_fmt=png

 

如果不满足的话重新排列顺序即可,这无非是编号顺序的问题。既然奇异值有从大到小排列的顺序,我们自然要问,如果只保留大的奇异值,舍去较小的奇异值,这样(1)式里的等式自然不再成立,那会得到怎样的矩阵——也就是图像?

 

640?wx_fmt=png

640?wx_fmt=jpeg

结果就是完全看不清是啥……我们试着多增加几项进来:

640?wx_fmt=png

 

再作图

640?wx_fmt=jpeg

隐约可以辨别这是短发伽椰子的脸……但还是很模糊,毕竟我们只取了5个奇异值而已。下面我们取20个奇异值试试,也就是(1)式等式右边取前20项构成640?wx_fmt=png

640?wx_fmt=jpeg

虽然还有些马赛克般的模糊,但我们总算能辨别出这是Juri酱的脸。当我们取到(1)式等式右边前50项时:

640?wx_fmt=jpeg

130项的效果:

640?wx_fmt=png

奇异值往往对应着矩阵中隐含的重要信息,且重要性和奇异值大小正相关。每个矩阵A都可以表示为一系列秩为1的“小矩阵”之和,而奇异值则衡量了这些“小矩阵”对于A的权重。

 

2.2 图像去噪 

在图像处理领域,奇异值不仅可以应用在数据压缩上,还可以对图像去噪。如果一副图像包含噪声,我们有理由相信那些较小的奇异值就是由于噪声引起的。当我们强行令这些较小的奇异值为0时,就可以去除图片中的噪声。如下是一张25*15的图像

640?wx_fmt=jpeg

但往往我们只能得到如下带有噪声的图像(和无噪声图像相比,下图的部分白格子中带有灰色):

640?wx_fmt=jpeg

通过奇异值分解,我们发现矩阵的奇异值从大到小分别为:14.154.673.000.21,……,0.05。除了前3个奇异值较大以外,其余奇异值相比之下都很小。强行令这些小奇异值为0,然后只用前3个奇异值构造新的矩阵,得到

640?wx_fmt=jpeg

可以明显看出噪声减少了(白格子上灰白相间的图案减少了)。奇异值分解还广泛的用于主成分分析(Principle Component Analysis,简称PCA)和推荐系统(如Netflex的电影推荐系统)等。在这些应用领域,奇异值也有相应的意义。

 

参考文献 

1 https://www.cnblogs.com/LeftNotEasy/archive/2011/01/19/svd-and-applications.html 

 

2 https://www.zhihu.com/question/22237507 3 We Recommend a Singular Value DecompositionFeature Column from the AMS

————

注:以上内容来自算法与数学之美-七月算法。

 

附录

1 文中生成ueno.juri图像的各项SVD合成图的python代码(参考自https://www.cnblogs.com/baby-lily/p/10758904.html):

import numpy as np
import matplotlib.image as mping
import matplotlib.pyplot as plt
import matplotlib as mpl


def image_svd(n, pic):
    a, b, c = np.linalg.svd(pic)
    svd = np.zeros((a.shape[0],c.shape[1]))
    for i in range(0, n):
        svd[i, i] = b[i]
    img = np.matmul(a, svd)
    img = np.matmul(img, c)
    img[ img >= 255] = 255
    img[  0 >= img ] = 0
    img = img.astype(np.uint8)
    return img


if __name__ == '__main__':
    mpl.rcParams['font.sans-serif'] = ['SimHei']
    mpl.rcParams['axes.unicode_minus'] = False

    path = 'ueno.juri.jpg'
    img = mping.imread(path)
    print(img.shape)
    
    r = img[:, :, 0]
    g = img[:, :, 1]
    b = img[:, :, 2]
    plt.figure(figsize=(50, 100))
    for i in range(1, 31):
        r_img = image_svd(i, r)
        g_img = image_svd(i, g)
        b_img = image_svd(i, b)
        pic = np.stack([r_img, g_img, b_img], axis=2)
        print(i)
        plt.subplot(5, 6, i)
        plt.title("特征值个数:%d" %(i))
        plt.axis('off')
        plt.imshow(pic)
    plt.suptitle("图像的SVD分解")
    plt.subplots_adjust()
    plt.show()

发表评论

全部评论:0条

帮杰

疯狂于web和智能设备开发,专注人机互联。