Python(使用 numpy)和 C++(使用 Eigen)中的 QR 分解结果不同吗?

QR decomposition results in Python (using numpy) and in C++ (using Eigen) are different?

提问人:skm 提问时间:10/3/2023 更新时间:10/3/2023 访问量:83

问:

示例 QR 分解代码 (Python):

import numpy as np
import matplotlib.pyplot as plt

# Define the 2D array
data = np.array([
    [12, -51, 4],
    [6, 167, -68],
    [-4, 24, -41],
    [-1, 1, 0],
    [2, 0, 3]
], dtype=float)  # specify data type as 'float' to match C++ double




q, r = np.linalg.qr(data)
print(q)
print(r)

QR 分解 C++ 代码示例(特征):

#include <iostream>
#include <Eigen/Dense>

void qrDecomposition(const Eigen::MatrixXd& A, Eigen::MatrixXd& Q, Eigen::MatrixXd& R) {
    int nRows = A.rows();
    int nCols = A.cols();

    Q = Eigen::MatrixXd::Zero(nRows, nCols);
    R = Eigen::MatrixXd::Zero(nCols, nCols);

    Eigen::MatrixXd v(nRows, nCols);
    Eigen::MatrixXd u(nRows, nCols);

    for (int j = 0; j < nCols; j++) {
        v.col(j) = A.col(j);
        for (int i = 0; i < j; i++) {
            R(i, j) = Q.col(i).dot(A.col(j));
            v.col(j) -= R(i, j) * Q.col(i);
        }
        R(j, j) = v.col(j).norm();
        Q.col(j) = v.col(j) / R(j, j);
    }
}

int main() {
    int nRows = 5;  // Number of rows
    int nCols = 3;  // Number of columns

    // Sample flattened array (replace with your data)
    Eigen::Map<Eigen::MatrixXd> A_data(new double[nRows * nCols], nRows, nCols);
    A_data <<
        12, -51, 4,
        6, 167, -68,
        -4, 24, -41,
        -1, 1, 0,
        2, 0, 3;

    Eigen::MatrixXd Q, R;

    qrDecomposition(A_data, Q, R);

    std::cout << "Matrix Q:\n" << Q << "\n";
    std::cout << "Matrix R:\n" << R << "\n";

    return 0;
}

结果:

Python 结果:

q
array([[-0.84641474,  0.39129081, -0.34312406],
       [-0.42320737, -0.90408727,  0.02927016],
       [ 0.28213825, -0.17042055, -0.93285599],
       [ 0.07053456, -0.01404065,  0.00109937],
       [-0.14106912,  0.01665551,  0.10577161]])
r
array([[ -14.17744688,  -20.66662654,   13.4015667 ],
       [   0.        , -175.04253925,   70.08030664],
       [   0.        ,    0.        ,   35.20154302]])

C++ 结果:

Matrix Q:
  0.846415  -0.391291  -0.343124
  0.423207   0.904087  0.0292702
 -0.282138   0.170421  -0.932856
-0.0705346  0.0140407 0.00109937
  0.141069 -0.0166555   0.105772
  
Matrix R:
 14.1774  20.6666 -13.4016
       0  175.043 -70.0803
       0        0  35.2015

问题:从上面的结果中可以看出,这些值的符号是不同的(并不总是翻转的)。为什么会这样?

python c++ 数学 特征 qr 分解

评论

1赞 molbdnilo 10/3/2023
QR 分解不一定是唯一的。我想你会发现,如果你将 Q 和 R 相乘,在这两种情况下你会得到相同的结果。(当然,你自己的分解代码也可能是错误的。
0赞 PaulMcKenzie 10/3/2023
主要区别在于,您不需要知道 QR 分解是什么,因为您有一个 C++ 中的手工版本,而 python 版本使用库例程。你能保证你在 C++ 中写的内容是 python 库调用正在做的事情吗?
0赞 skm 10/3/2023
@PaulMcKenzie:我理解QR分解的数学原理,如果我们把Q和R相乘,我们就会得到原始矩阵A。我尝试了不同的输入矩阵,符号总是不同的。在 C++ 方面,我使用了手写代码(使用 Gram-Schmidt 投影),也使用了 Eigen 库,但符号总是与 Python 生成的结果不同。我想知道导致这种差异的逻辑有什么区别。
0赞 molbdnilo 10/3/2023
@skm Numpy 使用 LAPACK 的分解,它不是 Gram-Schmidt(G-S 在数值上不稳定,因此不适合计算机)。可以有几种分解;例如,请参阅此处关于唯一性定理的信息。

答:

1赞 Matt 10/3/2023 #1

您正在做出使用 Gram-Schmidt 的假设。文档表明情况并非总是如此。他们提到了Householder,Gram-Schmidt和最小二乘法。如果你想看看它们的实际作用,可以在这里找到源代码。您可以使用他们的 Householder 方法直接解决问题,您将看到它并返回相同的值。numpyeigeneigennumpy

   Eigen::Matrix<double, 5, 3> data;
   data << 12,-51,4,6,167,-68,-4,24,-41,-1,1,0,2,0,3;

   auto QR = data.householderQr();  //auto may not be efficient here...
   Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> Q1 = QR.householderQ();
   Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> R1 = QR.matrixQR().triangularView<Eigen::Upper>();

    std::cout << Q1 << "\n\n" << R1 << "\n\n" << Q1 * R1 << std::endl;

输出为:

 -0.846415   0.391291  -0.343124  0.0661374 -0.0914621
 -0.423207  -0.904087  0.0292702  0.0173785 -0.0486104
  0.282138  -0.170421  -0.932856  -0.021942   0.143712
 0.0705346 -0.0140407 0.00109937   0.997401 0.00429488
 -0.141069  0.0166555   0.105772 0.00585613   0.984175

-14.1774 -20.6666  13.4016
       0 -175.043  70.0803
       0        0  35.2015      
       0        0        0      
       0        0        0      

        12        -51          4
         6        167        -68
        -4         24        -41
        -1          1 4.3715e-16
         2          0          3

您当然会注意到,它返回了完整的 Q 矩阵,该矩阵可以通过 5x3 单位矩阵进行细化。如果你实现 householder,我想你会发现它在这种情况下会匹配。eigennumpy