分享

奇异值分解(SVD)及其应用

 老马的程序人生 2020-08-17

奇异值分解的定义

SVD(Singular Value Decomposition)可以理解为:将一个比较复杂的矩阵用更小更简单的3个子矩阵的相乘来表示,这3个小矩阵描述了大矩阵重要的特性。

定义:矩阵的奇异值分解是指将一个秩为的实矩阵分解为三个实矩阵乘积的形式:

其中阶正交矩阵的列向量称为左奇异向量),阶正交矩阵的列向量称为右奇异向量),矩形对角矩阵,称为奇异值矩阵,对角线上的元素称为奇异值

是一个的对角阵,的对角线元素是的前个奇异值(非负,降序)。

知识点:任意一个实矩阵可以由其外积展开式表示

其中矩阵,是列向量和行向量的外积,为奇异值,通过矩阵的奇异值分解得到。

知识点:奇异值在矩阵中按照从大到小排列,在很多情况下,前10%甚至1%的奇异值的和就占了全部的奇异值之和的99%以上的比例。我们可以用最大的个奇异值的矩阵和相乘来近似描述矩阵,从而实现了降维、减少数据存储、提升计算性能等效果。

奇异值分解的计算

设矩阵的奇异值分解为,则有

即对称矩阵的特征分解可以由矩阵的奇异值分解矩阵表示。

证明的特征值非负。

矩阵,那么是对称矩阵且可以正交对角化,让的单位正交基且构成的特征向量,对应的特征值,那么对

所以的所有特征值都非负,如果必要,通过重新编号我们可以假设特征值的重新排列满足

的奇异值是的特征值的平方根,记为,且它们递减顺序排列。

可见,对进行奇异值分解需要求矩阵的特征值及其对应的标准正交的特征向量来构成正交矩阵的列,特征值的平方根得到奇异值也即得到奇异值矩阵

证明:假设是包含特征向量的上的标准正交基,重新整理使得对应的特征值满足。若个非零奇异值,则的一个正交基,且

不等于时,

所以,是一个正交基。由于向量的长度是的奇异值,且因为有个非零奇异值,为非零向量。所以线性无关,且属于

对任意属于,如,我们可以写出,且

这样,中,这说明的一个正交基,因此

由于的一个正交基,将每一个单位化得到一个标准正交基,此处

扩充为的单位正交基

由构造可知,是正交矩阵,

即:,从而得到

知识点:任意给定一个实矩阵,其奇异值分解一定存在,但并不唯一。

奇异值分解的实现

1. 手动实现

# 实现奇异值分解, 输入一个numpy矩阵,输出 U, sigma, V
import numpy as np


# 基于矩阵分解的结果,复原矩阵
def rebuildMatrix(U, sigma, V):
    a = np.dot(U, sigma)
    a = np.dot(a, np.transpose(V))
    return a


# 基于特征值的大小,对特征值以及特征向量进行倒序排列。
def sortByEigenValue(Eigenvalues, EigenVectors):
    index = np.argsort(-1 * Eigenvalues)
    Eigenvalues = Eigenvalues[index]
    EigenVectors = EigenVectors[:, index]
    return Eigenvalues, EigenVectors


# 对一个矩阵进行奇异值分解
def SVD(matrixA, NumOfLeft=None):
    # NumOfLeft是要保留的奇异值的个数,也就是中间那个方阵的宽度
    # 首先求transpose(A)*A
    matrixAT_matrixA = np.dot(np.transpose(matrixA), matrixA)
    # 然后求右奇异向量
    lambda_V, X_V = np.linalg.eig(matrixAT_matrixA)
    lambda_V, X_V = sortByEigenValue(lambda_V, X_V)
    # 求奇异值
    sigmas = lambda_V
    # python里很小的数有时候是负数
    sigmas = list(map(lambda x: np.sqrt(x) if x > 0 else 0, sigmas))

    sigmas = np.array(sigmas)
    sigmasMatrix = np.diag(sigmas)
    if NumOfLeft is None:
        # 大于0的特征值的个数
        rankOfSigmasMatrix = len(list(filter(lambda x: x > 0, sigmas)))
    else:
        rankOfSigmasMatrix = NumOfLeft

    # 特征值为0的奇异值就不要了
    sigmasMatrix = sigmasMatrix[0:rankOfSigmasMatrix, :]

    # 计算左奇异向量
    # 初始化一个左奇异向量矩阵,这里直接进行裁剪
    X_U = np.zeros((matrixA.shape[0], rankOfSigmasMatrix))
    for i in range(rankOfSigmasMatrix):
        X_U[:, i] = np.transpose(np.dot(matrixA, X_V[:, i]) / sigmas[i])

    # 对右奇异向量和奇异值矩阵进行裁剪
    X_V = X_V[:, 0:rankOfSigmasMatrix]
    sigmasMatrix = sigmasMatrix[0:rankOfSigmasMatrix, 0:rankOfSigmasMatrix]

    return X_U, sigmasMatrix, X_V

A = np.array([[41114], [87-2]])
X_U, sigmasMatrix, X_V = SVD(A)
print(A)
# [[ 4 11 14]
#  [ 8  7 -2]]

print(X_U.shape)  # (2, 2)
print(sigmasMatrix.shape)  # (2, 2)
print(X_V.shape)  # (3, 2)
print(rebuildMatrix(X_U, sigmasMatrix, X_V))
# [[ 4. 11. 14.]
#  [ 8.  7. -2.]]

2. 使用numpy.linalg.svd函数

import numpy as np

A = np.array([[41114], [87-2]])
print(A)
# [[ 4 11 14]
#  [ 8  7 -2]]

u, s, vh = np.linalg.svd(A, full_matrices=False)
print(u.shape)  # (2, 2)
print(s.shape)  # (2,)
print(vh.shape)  # (2, 3)

a = np.dot(u, np.diag(s))
a = np.dot(a, vh)
print(a)
# [[ 4. 11. 14.]
#  [ 8.  7. -2.]]

奇异值分解的应用

1. 数据压缩

奇异值分解可以有效地表示数据。例如,假设我们希望传输下面的图像,它由一个个黑色或白色像素组成的数组。

由于此图像中只有三种类型的列,如下图所示,因此可以用更紧凑的形式表示数据。

我们将图像表示为一个矩阵,矩阵的元素对应着图像的不同像素,如果像素是白色的话,就取 1,黑色的就取 0。 我们得到了一个具有375个元素的矩阵,如下图所示

如果对进行奇异值分解,我们会发现只有三个非零奇异值(非零奇异值的数目等于矩阵的秩,在这个例子中,我们看到矩阵中有三个线性无关的列,这意味着秩将是3)。

具有15个元素, 具有25个元素, 对应不同的奇异值。我们就可以用123个元素来表示具有375个元素的图像数据了。通过这种方式,奇异值分解可以发现矩阵中的冗余,并提供消除冗余的格式。

2. 去噪

前面的例子展示了如何利用许多奇异值为零的情况。一般来说,大的奇异值对应的部分会包含更多的信息。假设我们使用扫描仪将此图像输入计算机。但是,我们的扫描仪会在图像中引入一些缺陷(通常称为“噪声”)。

我们可以用同样的方法进行:用一个矩阵表示数据,并执行奇异值分解。我们发现以下奇异值:

显然,前三个奇异值是最重要的,所以我们假设其它的都是由于图像中的噪声造成的,并进行近似。

这导致下面的改进图像。

3. 数据分析

我们搜集的数据中总是存在噪声:无论采用的设备多精密,方法有多好,总是会存在一些误差的。如果你们还记得上文提到的,大的奇异值对应了矩阵中的主要信息的话,运用SVD进行数据分析,提取其中的主要部分的话,还是相当合理的。

假设我们收集了一些数据,如下所示:

我们可以将数据放入矩阵中:

经过奇异值分解后,得到

由于第一个奇异值远比第二个要大,可以假设的小值是由于数据中的噪声产生的,并且该奇异值理想情况下应为零。在这种情况下,矩阵的秩为1,意味着所有数据都位于定义的行上。

参考文献

  • https://zhuanlan.zhihu.com/p/54693391
  • http://www./publicoutreach/feature-column/fcarc-svd

    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多