Numpy 线性代数库
Numpy 是一个非常强大的数学库,拥有许多针对线性代数问题的函数和类,这使得我们能够方便地进行矩阵计算,如矩阵乘法,矩阵分解等等。其中,Singular Value Decomposition(SVD)是其中一个非常有用的矩阵分解方法。在本文中,我们将介绍 Numpy 中所提供的 np.linalg.svd 方法,并通过一系列的实际应用来深入理解这个算法。
阅读更多:Numpy 教程
矩阵分解
矩阵分解(Matrix Decomposition)是将矩阵分解为其它矩阵的乘积的过程。这种分解过程对于计算来说通常是更加简单的,因为一个复杂的矩阵问题可以被拆分为多个简单的矩阵问题来解决。
对于 Numpy 库,我们通常关注的是三种矩阵分解方法:奇异值分解(SVD),QR 分解和特征值分解(Eigendecomposition)。
奇异值分解
奇异值分解是将一个矩阵分解成三部分的乘积的过程,如下所示:
A = U . Sigma . V^T
A
是要进行分解的矩阵。U
是一个 M \times M 的方形矩阵,其中的列向量要满足 UU^T=I 以及 U^TU=I。这些列向量是A
的左奇异向量(Left Singular Vectors)。V
是一个 N \times N 的方形矩阵,其中的列向量要满足 VV^T=I 以及 V^TV=I。这些列向量是A
的右奇异向量(Right Singular Vectors)。Sigma
是一个 M \times N 的矩形对角阵,对角线上的元素被称作奇异值(Singular Values)。
在 Numpy 中,我们通常使用 linalg.svd
这个方法来实现奇异值分解。下面我们通过一个例子来看看 linalg.svd
的用法。
假设我们有一个 3 \times 2 的矩阵 A
,如下所示:
A = np.array([[1, 2], [3, 4], [5, 6]])
现在我们需要对 A
进行奇异值分解:
U, S, V_T = np.linalg.svd(A)
print("U:")
print(U)
print("Sigma:")
print(S)
print("V_T:")
print(V_T)
输出结果如下所示:
U:
[[-0.22984626 -0.88346102 0.40824829]
[-0.52474482 -0.24078249 -0.81649658]
[-0.81964338 0.40189604 0.40824829]]
Sigma:
[9.52551809 0.51430058]
V_T:
[[-0.61962948 -0.78489445]
[ 0.78489445 -0.61962948]]
我们可以发现,在返回结果中,U
包含了对应左奇异向量的列向量,Sigma
是奇异值的对角矩阵,而 V
被分解为 V_T
,其中 V_T
包含右奇异向量的行向量。
利用奇异值分解做降维
奇异值分解不仅应用于矩阵的分解,还可以作为一种数据降维的方法。在数据分析中,我们希望通过降维,去除数据中的冗余信息,从而达到简化数据、减少计算量的目的。
下面我们通过一个具体的例子来说明如何使用奇异值分解进行数据降维。
假设我们有一个 4 \times 3 的矩阵 B
,表示有四个样本,每个样本有三个特征:
B = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]])
现在我们使用 linalg.svd
来对 B
进行奇异值分解:
U, S, V_T = np.linalg.svd(B)
得到 S
后,我们可以计算奇异值的平方和,即 \sum_{i=1}^{r} \sigma_i^2,其中 r 是矩阵 B
的秩:
sigma_squared_sum = np.sum(S**2)
然后我们可以挑选一部分奇异值,比如说前两个,然后根据下面的式子计算出 k 维的近似矩阵:
B_k = U[:, :k] @ np.diag(S[:k]) @ V_T[:k, :]
其中,U[:, :k]
是 U
的前 k
列,S[:k]
是 S
的前 k
个元素组成的向量,V_T[:k, :]
是 V_T
的前 k
行。
我们可以通过调整参数 k
来改变降维后的维度。下面我们将 k
设置为 2,并对比降维前后的数据:
B_2 = U[:, :2] @ np.diag(S[:2]) @ V_T[:2, :]
print("原始数据 B:")
print(B)
print("降维后的数据 B_2:")
print(B_2)
输出结果如下所示:
原始数据 B:
[[ 1 2 3]
[ 4 5 6]
[ 7 8 9]
[10 11 12]]
降维后的数据 B_2:
[[ 1.19383386 2.80210394 3.41093648]
[ 3.81729016 5.4327048 5.58225509]
[ 6.44074645 8.06330566 7.7535737 ]
[ 9.06420275 10.69390651 9.92489232]]
我们可以看到,通过将原始数据降到二维,数据的信息仍然保留得很好。这样做的好处除了提高计算效率外,还能对数据进行压缩,节省存储空间。
利用奇异值分解进行去噪
奇异值分解还可以用来进行去噪操作。在图像处理中,有些图像可能受到了一些因素影响,导致噪点出现。为了提高图像的质量,我们需要对这些噪点进行去除。
在奇异值分解中,我们可以通过将奇异值较小的部分截断来实现去噪。由于奇异值的作用类似于图像中的能量损失,因此决定截断的阈值越小,去噪效果就越明显。
下面我们通过一个实际的例子来说明如何利用奇异值分解进行图像去噪。
首先,我们可以使用 matplotlib
库来显示一张图像,例如这里我们选择一张猫的图片 cat.jpg
:
import matplotlib.pyplot as plt
cat = plt.imread("cat.jpg")
plt.imshow(cat)
plt.show()
接着,我们将这张图像转化为灰度图,并且加入噪声:
import numpy as np
cat_gray = np.mean(cat, axis=-1)
noisy_cat = cat_gray + 0.2 * np.random.randn(*cat_gray.shape)
在上面的代码中,np.mean(cat, axis=-1)
将三通道的 RGB 图像转化为单通道灰度图,np.random.randn(*cat_gray.shape)
生成与 cat_gray
同样形状的随机噪声,并乘以一个系数 0.2,保证噪声不至于太强。
现在我们利用奇异值分解对噪声图像进行去噪处理:
U, S, V_T = np.linalg.svd(noisy_cat)
sigma_squared_sum = np.sum(S ** 2)
for i in range(len(S)):
if np.sum(S[:i+1] ** 2) > 0.95 * sigma_squared_sum: # 保留能量的 95%
k = i + 1
break
clean_cat = U[:, :k] @ np.diag(S[:k]) @ V_T[:k, :]
我们设置保留能量 95%,即只保留前 70 个奇异值,然后根据前述的公式获得去噪后的图像。最后我们将去噪后的图像进行显示:
plt.imshow(clean_cat, cmap="gray")
plt.show()
可以看到,经过去噪处理的图像比起噪声图像有了极大的改善,而且保留了图像的重要特征。
总结
在本文中,我们介绍了 Numpy 中的线性代数库,重点讲解了奇异值分解这种矩阵分解方法的理论,并通过一些实际的应用场景来加深对奇异值分解的理解。奇异值分解既可以用于矩阵分解,也可以用于数据降维和图像去噪等实际问题。在实际应用过程中,我们可以根据具体的问题来使用奇异值分解,并结合一些智能算法进行优化。