为什么Armadillo的SVD结果与NumPy不同?

4

在我的Python代码中,我正在使用numpy.linalg.svd计算一些数据的奇异值分解:

from numpy import linalg
(_, _, v) = linalg.svd(m)

这返回的 V 矩阵是:
[[ 0.4512937  -0.81992002 -0.35222884]
 [-0.22254721  0.27882908 -0.93419863]
 [ 0.86417981  0.4999855  -0.05663711]]

在将我的代码移植到C++时,我转而使用Armadillo来计算SVD:

#include <armadillo>

arma::fmat M; // Input data
arma::fmat U;
arma::fvec S;
arma::fmat V;
arma::svd(U, S, V, M);

相同数据的结果 V 是:
  0.4513  -0.2225  -0.8642
 -0.8199   0.2788  -0.5000
 -0.3522  -0.9342   0.0566

我们可以看到,Armadillo的V的转置与NumPy的V相匹配。除了Armadillo的V的最后一列之外。这些值与NumPy结果的最后一行的值的符号相反。
发生了什么?为什么两个流行库的SVD结果会有这样的差异?哪一个是正确的结果?

你应用SVD的原始矩阵是什么?你能保证两个系统之间完全相同吗?通常唯一的区别可能是特征向量的比例尺度… - Alexander L. Belikoff
@AlexanderL.Belikoff:是的,输入数据是相同的。在C++中,类型为float,在Python中应该是double吧。但是,这种精度差异不应该有影响吧? - Ashwin Nanjappa
从技术上讲,SVD分解并不是唯一的(对于符号变化不确定)。除此之外,可能其中一个返回的是V*而不是V,因此需要转置。此外,在实方阵的SVD中产生的U和V都是旋转矩阵,armadillo是正确的。你能发一下原始矩阵吗? - sbabbi
@sbabbi:输入矩阵不是方阵。它在这里共享:http://pastebin.com/bPgKER7E - Ashwin Nanjappa
Armadillo返回V,而numpy返回V*。至于符号的变化,我不知道,但可能两个结果都是正确的,并且V中的符号变化被U中的另一个符号变化所平衡。您可以计算USV并检查它是否等于您的原始矩阵。 - sbabbi
1个回答

6
两种方式都是正确的... 你从numpy获得的v的行是M.dot(M.T)的特征向量(在复数情况下,转置将是共轭转置)。 特征向量在一般情况下仅定义为多个常数的乘积,因此您可以将v的任何行乘以不同的数字,它仍将是特征向量矩阵。 v还有一个额外的约束条件,即它是一个酉矩阵,它松散地转化为其行正交归一。这将使每个特征向量的可用选择仅为2:指向任一方向的标准化特征向量。但是,您仍然可以将任何行乘以-1并仍然拥有有效的v
如果您想为加载为a的矩阵进行测试,则可以这样做:
>>> u, d, v = np.linalg.svd(a)
>>> D = np.zeros_like(a)
>>> idx = np.arange(a.shape[1])
>>> D[idx, idx] = d
>>> np.allclose(a, u.dot(D).dot(v))
True
>>> v[2] *= -1
>>> np.allclose(a, u.dot(D).dot(v))
True

实际上,在实数域中,您只能将v的行乘以-1,但在复数情况下,您可以将其乘以任何绝对值为1的复数:

>>> vv = v.astype(np.complex)
>>> vv[0] *= (1+1.j)/np.sqrt(2)
>>> np.allclose(a, u.dot(D).dot(v))
True

谢谢Jaime!我正在使用v来旋转点,就像在PCA中一样。所以,我想使用从Armadillo获取的V也没有问题吧? - Ashwin Nanjappa
是的,你的轴的对齐方式不会改变,唯一的区别在于你的基向量的方向,这在大多数情况下不应该是一个相关的问题。请注意,在这种情况下,至少是numpy给出了通常的右手定则基向量的方向。再次强调,我认为这种差异对于数据对齐并不重要。 - Jaime

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接