SVD 公式:$M = UΣV^T$ 即 $MV = UΣ$。 我们以二维空间为例,通过观察解释为什么正交基$U$和$V$一定存在。
在二维空间里 SVD 需要找到2个互相垂直的单位向量(即 $V$),在经过M的转化(相乘)后依然互相垂直(即 $UΣ$)。 这个2个互相垂直的单位向量一定位于一个半径为1的, 以$[0,0]$为圆心的圆上。 那么个圆在经过M的空间转换后会是什么形状呢,答案是:一个椭圆。
当 $M = [[0.5, -1], [2, -0.5]]$ 时,我们可以观察到左边的圆转化成了右边的椭圆: Screenshot 2024-07-01 at 10.26.24 pm.png 左边的圆变为右边的椭圆可以拆分成三步:

  • 放大/缩小圆
  • 旋转/翻转圆
  • 从某一角度"压扁"圆成椭圆

我们可以观察到椭圆的最长半径和最短半径在压扁前后都是互相垂直的,它们也就是我们要找的转化后的2个向量。我们也可以观察到这样的向量存在且只存在4组。进行上面三步的逆向操作,我们也就能在左边的圆上找到对应的2个向量。

延伸

那么为什么 $U$ 和 $V$ 可以用来表达 $M$ 所携带的信息呢? 这是因为 $U$ 和 $V$ 都是正交矩阵(orthonormal matrix),他们在空间转换上的效果只有旋转和翻转,也就意味着他们不会改变原空间中"物体"的形状。 而 $Σ$ 仅仅是放大/缩小了各个坐标,就跟我们将数据normilize不会导致信息丢失是一样的。 所以 $U$ 在跟 $Σ$ 和 $V$ 相乘之后,并没有带来任何其携带的信息的变化。既然 $M = UΣV^T$,那也就意味着 $U$ 和 $M$ 携带的信息是一样的。 我们也可以对比理解成将一张图片拉伸/翻转/斜着压扁并不会导致图片上的信息丢失一样,也因为我们进行逆向操作就可以恢复图片。这也意味着 $V$ 只需要是可逆矩阵就不会影响 $U$ 所表达的信息。

测试代码:

import numpy as np

import matplotlib.pyplot as plt
import math

pi = math.pi


def pointsInCircum(r, n=100):
    return [(math.cos(2 * pi / n * x) * r, math.sin(2 * pi / n * x) * r) for x in range(0, n + 1)]


points = pointsInCircum(1, 32)

points = np.asarray(points)
M = np.asarray([[0.5, -1]
                   , [2, -0.5]])
points_after = points.dot(M)
print(M)
# points_1 = points_1.dot([[1,0]
#                       , [0,2]])


plt.rcParams['figure.figsize'] = [12, 6]
f, axarr = plt.subplots(nrows=1, ncols=2)
for ax in axarr:
    # ax.axis('off')
    ax.spines['left'].set_position('center')
    ax.spines['bottom'].set_position('center')
    ax.spines['right'].set_color('none')
    ax.spines['top'].set_color('none')
    ax.axis('equal')
    ax.set_xlim(-2.5, 2.5)
    ax.set_ylim(-2.5, 2.5)

for i in range(len(points)):
    # print(point)
    if i < 8:
        color = 'red'
    elif i < 16:
        color = 'green'
    elif i < 24:
        color = 'yellow'
    else:
        color = 'blue'
    point = points[i]
    axarr[0].plot([0, point[0]], [0, point[1]], color=color)
    point = points_after[i]
    axarr[1].plot([0, point[0]], [0, point[1]], color=color)
    # axarr[1].plot([0, point[0]],[0, point[1]],)

# # here must be something like circle.plot() or not?
plt.show()