【问题标题】:Eigen and C++11 type inference fails for Cholesky of matrix product矩阵乘积的 Cholesky 的特征和 C++11 类型推断失败
【发布时间】:2014-11-24 20:07:49
【问题描述】:

我正在尝试使用 Eigen 和 C++11“自动”类型对矩阵乘积及其转置进行 Cholesky 分解。当我尝试做时问题就来了

auto c = a * b
auto cTc = c.tranpose() * c;
auto chol = cTc.llt();

我正在使用 XCode 6.1,Eigen 3.2.2。我得到的类型错误是here

这个最小的例子显示了我机器上的问题。将 c 的类型从 auto 更改为 MatrixXd 以查看它的工作原理。

#include <iostream>
#include <Eigen/Eigen>
using namespace std;
using namespace Eigen;


int main(int argc, const char * argv[]) {
    MatrixXd a = MatrixXd::Random(100, 3);
    MatrixXd b = MatrixXd::Random(3, 100);
    auto c = a * b;
    auto cTc = c.transpose() * c;
    auto chol = cTc.llt();
    return 0;
}

有没有办法在使用 auto 的同时完成这项工作?

作为一个附带问题,是否有性能理由不断言矩阵在每个阶段都是MatrixXd?使用 auto 将允许 Eigen 将答案保留为它喜欢的任何奇怪的模板表达式。我不确定将其输入为 MatrixXd 是否会导致问题。

【问题讨论】:

    标签: c++ c++11 auto eigen eigen3


    【解决方案1】:

    问题是第一次乘法返回 Eigen::GeneralProduct 而不是 MatrixXd 并且 auto 正在选择返回类型。您可以从Eigen::GeneralProduct 隐式创建MatrixXd,因此当您显式声明类型时它可以正常工作。见http://eigen.tuxfamily.org/dox/classEigen_1_1GeneralProduct.html

    编辑:我不是 Eigen 产品或铸造性能特征方面的专家。我只是从错误消息中推测出答案,并从在线文档中确认。分析始终是检查代码不同部分性能的最佳选择。

    【讨论】:

    • 投射到MatrixXd 是否会影响性能?显然,在这个受限制的例子中它是最小的,但在现实生活中呢?你会说解决方案在这里 - 在执行 c.tranpose() * c 步骤时使用 ProductReturnType 似乎有点疯狂。
    【解决方案2】:

    让我总结一下发生了什么以及为什么会出错。首先,让我们用它们所采用的类型来实例化auto 关键字:

    typedef GeneralProduct<MatrixXd,MatrixXd> Prod;
    Prod c = a * b;
    GeneralProduct<Transpose<Prod>,Prod> cTc = c.transpose() * c;
    

    请注意,Eigen 是一个表达式模板库。这里,GeneralProduct&lt;&gt; 是代表产品的抽象类型。不执行任何计算。因此,如果将cTc 复制到MatrixXdas:

    MatrixXd d = cTc;
    

    相当于:

    MatrixXd d = c.transpose() * c;
    

    那么产品a*b会进行两次!因此,在任何情况下,最好在显式临时内评估 a*b,对于 c^T*c 也是如此:

    MatrixXd c = a * b;
    MatrixXd cTc = c.transpose() * c;
    

    最后一行:

    auto chol = cTc.llt();
    

    也是相当错误的。如果 cTc 是一个抽象产品类型,那么它会尝试实例化一个 Cholesky 分解在一个抽象产品类型上工作,这是不可能的。现在,如果cTcMatrixXd,那么您的代码应该可以工作,但这仍然不是首选方式,因为llt() 方法是实现单行表达式,例如:

    VectorXd b = ...;
    VectorXd x = cTc.llt().solve(b);
    

    如果你想要一个命名的 Cholesky 对象,不如使用它的构造函数:

    LLT<MatrixXd> chol(cTc);
    

    甚至:

    LLT chol(c.transpose() * c);

    这是等效的,除非您必须在其他计算中使用c.transpose() * c

    最后,根据ab 的大小,最好将cTc 计算为:

    MatrixXd cTc = b.transpose() * (a.transpose() * a) * b;
    

    在未来(即 Eigen 3.3),Eigen 将能够看到:

    auto c = a * b;
    MatrixXd cTc = c.transpose() * c;
    

    作为四个矩阵的乘积m0.transpose() * m1.transpose() * m2 * m3 并将括号放在正确的位置。但是,它无法知道m0==m3m1==m2,因此如果首选方法是暂时评估a*b,那么您仍然需要自己进行。

    【讨论】:

    • 谢谢 - 很高兴收到库开发人员的来信!我这样做的原因是查看 Eigen 是否可以在 m0.transpose() * m1.transpose() * m2 * m3 具有有用的形状时正确优化它们 - 因此我想将所有内容都保留在表达式空间中直到最后一分钟。是因为模板的工作原理,我不能对 GeneralProduct 进行胆怯分解,只是没有人足够关心将它添加到 Eigen 中,还是有理由这样做是愚蠢的?
    【解决方案3】:

    我不是Eigen 的专家,但是像这样的库通常会从操作中返回代理对象,然后使用隐式转换或构造函数来强制执行实际工作。 (表达式模板是一个极端的例子。)这避免了在许多情况下不必要地复制完整的数据矩阵。不幸的是,auto 很高兴只创建一个代理类型的对象,通常库的用户永远不会明确声明。由于您最终需要计算数字,因此从转换为MatrixXd 本身不会对性能造成影响。 (Scott Meyers 在“Effective Modern C++”中给出了使用autovector&lt;bool&gt; 的相关示例,其中operator[](size_t i) 返回一个代理。)

    【讨论】:

      【解决方案4】:

      不要在 Eigen 表达式中使用 auto。我之前遇到过更“戏剧性”的问题,请参阅

      eigen auto type deduction in general product

      一位 Eigen 创建者 (Gael) 建议不要将 auto 与 Eigen 表达式一起使用。

      从表达式到特定类型(如MatrixXd)的转换应该非常快,除非您想要延迟评估(因为在进行转换时会评估结果)。

      【讨论】:

      • 我认为这与您的略有不同。您从 Id 返回一个对象,而该对象又被 autoresult 引用 - 然后来自 Id 的对象消失了,autoresult 引用了不会退出的东西。 c.transpose() 引用 c,它仍然存在,所以我的 auto cTc 不应该引用任何不存在的东西 - 类似于 ggael 提供的使用 auto id = Id(Foo, 4) 的一种解决方案。我想测试 Eigen 是否能正确注意到 c.transpose() * c = b.transpose() * a.transpose() * a * b 这简化了数学,所以理想情况下它会被保留为一个表达式。
      • 是的,你是对的,但是,如果我没有得到解释,我绝对不知道为什么代码会出现异常。而且由于 Eigen 有很多类型(基本上每个表达式都是不同的类型),加上间接引用,auto 使事情变得复杂,因为您并不真正了解幕后发生了什么,以及这些表达式是如何评估的。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-01-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-11-08
      相关资源
      最近更新 更多