[Eigen中文文档] 归约、访问者和广播

简介: 本文介绍了Eigen的归约、访问者和广播,以及它们如何与矩阵和数组一起使用。

专栏总目录

英文原文(Reductions, visitors and broadcasting)

本文介绍了Eigen的归约、访问者和广播,以及它们如何与矩阵和数组一起使用。

归约

在Eigen,归约是把一个矩阵和数组变成一个标量的方法。一个经常用到的归约方法是sum(),它返回给定矩阵或数组内所有系数的总和。

// 代码索引 3-7-1-1
#include <iostream>
#include <Eigen/Dense>

using namespace std;
int main()
{
   
  Eigen::Matrix2d mat;
  mat << 1, 2,
         3, 4;
  cout << "Here is mat.sum():       " << mat.sum()       << endl;
  cout << "Here is mat.prod():      " << mat.prod()      << endl;
  cout << "Here is mat.mean():      " << mat.mean()      << endl;
  cout << "Here is mat.minCoeff():  " << mat.minCoeff()  << endl;
  cout << "Here is mat.maxCoeff():  " << mat.maxCoeff()  << endl;
  cout << "Here is mat.trace():     " << mat.trace()     << endl;
}

输出:

Here is mat.sum():       10
Here is mat.prod():      24
Here is mat.mean():      2.5
Here is mat.minCoeff():  1
Here is mat.maxCoeff():  4
Here is mat.trace():     5

矩阵的迹可以通过trace()来得到,也可以通过a.diagonal().sum()得到。

范数计算

向量的平方范数可以通过 squaredNorm() 来计算。它等于向量本身的点积,等于其系数的绝对值平方和。

Eigen还提供了 norm() 方法,该方法返回squaredNorm()的平方根。

这些方法也可以对矩阵进行操作,在这种情况下,n×p 矩阵被视为大小为(n*p)的向量,因此例如 norm() 方法返回 FrobeniusHilbert-Schmidt 范数,即 $L_2$ 范数。我们避免谈论 Euclidean 范数,因为这可能意味着不同的事情。

如果想要其他系数的范数,可以使用lpNorm\()方法。如果你想要 $\infty$ 范数,模板参数 p 可以取特殊值 Infinity,这将返回系数绝对值的最大值。如下:

// 代码索引 3-7-2-1
#include <Eigen/Dense>
#include <iostream>

int main()
{
   
  Eigen::VectorXf v(2);
  Eigen::MatrixXf m(2,2), n(2,2);

  v << -1,
       2;

  m << 1,-2,
       -3,4;

  std::cout << "v.squaredNorm() = " << v.squaredNorm() << std::endl;
  std::cout << "v.norm() = " << v.norm() << std::endl;
  std::cout << "v.lpNorm<1>() = " << v.lpNorm<1>() << std::endl;
  std::cout << "v.lpNorm<Infinity>() = " << v.lpNorm<Eigen::Infinity>() << std::endl;

  std::cout << std::endl;
  std::cout << "m.squaredNorm() = " << m.squaredNorm() << std::endl;
  std::cout << "m.norm() = " << m.norm() << std::endl;
  std::cout << "m.lpNorm<1>() = " << m.lpNorm<1>() << std::endl;
  std::cout << "m.lpNorm<Infinity>() = " << m.lpNorm<Eigen::Infinity>() << std::endl;
}

输出:

v.squaredNorm() = 5
v.norm() = 2.23607
v.lpNorm<1>() = 3
v.lpNorm<Infinity>() = 2

m.squaredNorm() = 30
m.norm() = 5.47723
m.lpNorm<1>() = 10
m.lpNorm<Infinity>() = 4

1-范数和 $\infty$-范数矩阵运算符范数可以很容易地计算如下:

// 代码索引 3-7-2-2
#include <Eigen/Dense>
#include <iostream>

int main()
{
   
  Eigen::MatrixXf m(2,2);
  m << 1,-2,
       -3,4;

  std::cout << "1-norm(m)     = " <是他的::< m.cwiseAbs().colwise().sum().maxCoeff()
            << " == "             << m.colwise().lpNorm<1>().maxCoeff() << std::endl;

  std::cout << "infty-norm(m) = " << m.cwiseAbs().rowwise().sum().maxCoeff()
            << " == "             << m.rowwise().lpNorm<1>().maxCoeff() << std::endl;
}

输出:

1-norm(m)     = 6 == 6
infty-norm(m) = 7 == 7

有关这些表达式的语法的更多解释,请参见下文。

布尔归约

下列是关于布尔值的归约操作:

  • all():给定Matrix或Array所有的系数都为true时,该函数返回true
  • any():给定Matrix或Array至少有一个是true,函数返回true
  • count():返回给定Matrix或Array中true的个数

这些操作通常与相等操作符、Array提供的按系数比较操作等一起使用。例如,array>0 是一个数组,它与array大小相同且每一个元素都是布尔值,如果array的某个元素满足大于零,则数组array>0对应位置为true,否则为false。因此,(array>0).all() 检验是否array的所有的系数都是大于0的。

示例如下:

// 代码索引 3-7-3-1
#include <Eigen/Dense>
#include <iostream>

int main()
{
   
  Eigen::ArrayXXf a(2,2);

  a << 1,2,
       3,4;

  std::cout << "(a > 0).all()   = " << (a > 0).all() << std::endl;
  std::cout << "(a > 0).any()   = " << (a > 0).any() << std::endl;
  std::cout << "(a > 0).count() = " << (a > 0).count() << std::endl;
  std::cout << std::endl;
  std::cout << "(a > 2).all()   = " << (a > 2).all() << std::endl;
  std::cout << "(a > 2).any()   = " << (a > 2).any() << std::endl;
  std::cout << "(a > 2).count() = " << (a > 2).count() << std::endl;
}

输出如下:

(a > 0).all()   = 1
(a > 0).any()   = 1
(a > 0).count() = 4

(a > 2).all()   = 0
(a > 2).any()   = 1
(a > 2).count() = 2

用户自定义的归约

这里官方没有给出解释,建议参考函数 DenseBase::redux()

访问者函数

当想要获取元素在 Matrix 或 Array 中的位置时,访问者函数很有用。例如 maxCoeff(&x,&y)minCoeff(&x,&y),它们可用于查找 Matrix 或 Array 中最大或最小元素的位置,位置通过传入要存储行和列位置变量的指针返回。这些变量应该是 Index 类型,如下所示:

// 代码索引 3-7-4-1
#include <iostream>
#include <Eigen/Dense>

int main()
{
   
  Eigen::MatrixXf m(2,2);

  m << 1, 2,
       3, 4;

  //get location of maximum
  Eigen::Index maxRow, maxCol;
  float max = m.maxCoeff(&maxRow, &maxCol);

  //get location of minimum
  Eigen::Index minRow, minCol;
  float min = m.minCoeff(&minRow, &minCol);

  std::cout << "Max: " << max <<  ", at: " <<
     maxRow << "," << maxCol << std::endl;
  std:: cout << "Min: " << min << ", at: " <<
     minRow << "," << minCol << std::endl;
}

输出为:

Max: 4, at: 1,1
Min: 1, at: 0,0

这两个函数还会通过函数返回值返回最小或最大元素的值。

局部归约

局部归约是可以对 Matrix 或 Array 按列或行进行操作的归约,对每一列或行应用归约操作并返回具有相应值的列或行向量。使用colwise()rowwise()方法进行部分归约。

如下示例,获取给定矩阵中每一列中元素的最大值,并将结果存储在行向量中:

// 代码索引 3-7-5-1
#include <iostream>
#include <Eigen/Dense>

using namespace std;
int main()
{
   
  Eigen::MatrixXf mat(2,4);
  mat << 1, 2, 6, 9,
         3, 1, 7, 2;

  std::cout << "Column's maximum: " << std::endl
   << mat.colwise().maxCoeff() << std::endl;
}

输出:

Column's maximum: 
3 2 7 9

类似的,可得到每一行中元素的最大值,如下:

// 代码索引 3-7-5-2
#include <iostream>
#include <Eigen/Dense>

using namespace std;
int main()
{
   
  Eigen::MatrixXf mat(2,4);
  mat << 1, 2, 6, 9,
         3, 1, 7, 2;

  std::cout << "Row's maximum: " << std::endl
   << mat.rowwise().maxCoeff() << std::endl;
}

请注意,按列操作返回行向量,而按行操作返回列向量。

将局部归约与其他操作结合

也可以将局部归约的结果做进一步处理。

如下示例,查找矩阵中元素总和最大的列:

// 代码索引 3-7-5-3
#include <iostream>
#include <Eigen/Dense>

int main()
{
   
  Eigen::MatrixXf mat(2,4);
  mat << 1, 2, 6, 9,
         3, 1, 7, 2;

  Eigen::Index   maxIndex;
  float maxNorm = mat.colwise().sum().maxCoeff(&maxIndex);

  std::cout << "Maximum sum at position " << maxIndex << std::endl;

  std::cout << "The corresponding vector is: " << std::endl;
  std::cout << mat.col( maxIndex ) << std::endl;
  std::cout << "And its sum is is: " << maxNorm << std::endl;
}

输出如下:

Maximum sum at position 2
The corresponding vector is: 
6
7
And its sum is is: 13

上面示例通过 colwise() 对每一列进行 sum() 归约,得到大小为 1x4 的新矩阵,如:
$$ m = \begin{bmatrix} 1 & 2 & 6 & 9 \\ 3 & 1 & 7 & 2 \\ \end{bmatrix} $$
则有,
$$ m.colwise().sum() = \begin{bmatrix} 4 & 3 & 13 & 11 \end{bmatrix} $$
最后,使用 maxCoeff() 方法得到最大总和的列索引。

广播

广播的概念类似于局部归约,不同之处在于广播构造了一个表达式,通过在一个方向上复制向量(列或行)将其解释为矩阵。

如下示例,将某个列向量添加到矩阵中的每一列:

// 代码索引 3-7-6-1
#include <iostream>
#include <Eigen/Dense>

using namespace std;
int main()
{
   
  Eigen::MatrixXf mat(2,4);
  Eigen::VectorXf v(2);

  mat << 1, 2, 6, 9,
         3, 1, 7, 2;

  v << 0,
       1;

  //add v to each column of m
  mat.colwise() += v;

  std::cout << "Broadcasting result: " << std::endl;
  std::cout << mat << std::endl;
}

输出:

Broadcasting result: 
1 2 6 9
4 2 8 3

用两种等价的方式解释语句 mat.colwise() += v。可以解释为它将向量 v 添加到矩阵的每一列,或者,也可以解释为将向量 v 重复四次形成一个 4*2 的矩阵,然后将其加到 mat,如下:
$$ \begin{bmatrix} 1 & 2 & 6 & 9 \\ 3 & 1 & 7 & 2 \\ \end{bmatrix} + \begin{bmatrix} 0 & 0 & 0 & 0 \\ 1 & 1 & 1 & 1 \\ \end{bmatrix} = \begin{bmatrix} 1 & 2 & 6 & 9 \\ 4 & 2 & 8 & 3 \\ \end{bmatrix} $$
运算符 -=+- 也可以按列和行使用。在数组中,还可以使用运算符 *=/=*/ 来执行按元素乘法和按列或行进行除法,但这些运算符在矩阵上不能使用。如果要将矩阵 mat 的第 0 列与v(0) 相乘,第 1 列与 v(1) 相乘,依此类推,可以使用 mat = mat * v.asDiagonal()

需要指出的是,要按列或行相加的向量必须是 Vector 类型,不能是 Matrix,否则编译时会报错。这也意味着在使用 Matrix 操作时,广播操作只能应用于 Vector 类型的对象。这同样适用于 Array 类,其中 VectorXf 的等效项是 ArrayXf。同样不应该在同一个表达式中混合使用数组和矩阵。

如下示例,按行执行相同的操作:

// 代码索引 3-7-6-2
#include <iostream>
#include <Eigen/Dense>

using namespace std;
int main()
{
   
  Eigen::MatrixXf mat(2,4);
  Eigen::VectorXf v(4);

  mat << 1, 2, 6, 9,
         3, 1, 7, 2;

  v << 0,1,2,3;

  //add v to each row of m
  mat.rowwise() += v.transpose();

  std::cout << "Broadcasting result: " << std::endl;
  std::cout << mat << std::endl;
}

输出:

Broadcasting result: 
 1  3  8 12
 3  2  9  5

将广播与其他操作结合

广播还可以与其他操作相结合,例如矩阵或数组操作、归约和局部归约。

前面已经介绍了广播、归约和局部归约,现在可以深入研究一个更高级的示例,即在矩阵 m 的列中找到向量 v 的最近邻向量。本例将使用欧几里得距离,使用名为 squaredNorm() 的局部归约计算二次欧几里得距离:

// 代码索引 3-7-7-1
#include <iostream>
#include <Eigen/Dense>

int main()
{
   
  Eigen::MatrixXf m(2,4);
  Eigen::VectorXf v(2);

  m << 1, 23, 6, 9,
       3, 11, 7, 2;

  v << 2,
       3;

  Eigen::Index index;
  // find nearest neighbour
  (m.colwise() - v).colwise().squaredNorm().minCoeff(&index);

  std::cout << "Nearest neighbour is column " << index << ":" << std::endl;
  std::cout << m.col(index) << std::endl;
}

输出:

Nearest neighbour is column 0:
1
3

其中,语句 (m.colwise() - v).colwise().squaredNorm().minCoeff(&index); 解释如下:

  • m.colwise() - v: 是一个广播操作,从 m 中的每一列中减去 v。此操作的结果是一个新矩阵,其大小与矩阵 m 相同,如下:

$$ m.colwise - v = \begin{bmatrix} -1 & 21 & 4 & 7 \\ 0 & 8 & 4 & -1 \\ \end{bmatrix} $$

  • (m.colwise() - v).colwise().squaredNorm():是一个局部归约操作,按列计算平方范数。此操作的结果是一个行向量,其中每个元素是 m 和 v 中每列之间的二次欧几里得距离,如下:

$$ (m.colwise() - v).colwise().squaredNorm() = \begin{bmatrix} 1 & 505 & 32 & 50 \end{bmatrix} $$

  • minCoeff(&index):用于获取 m 中最接近 v 的列索引。
相关文章
|
6月前
|
Java
Albert 源码解析:分组复用
Albert 源码解析:分组复用
46 0
|
6月前
|
算法 Python 容器
Python编程 - 不调用相关choose库函数,“众数“挑选器、随机挑选器 的源码编程实现
Python编程 - 不调用相关choose库函数,“众数“挑选器、随机挑选器 的源码编程实现
86 0
|
存储 算法 NoSQL
[Eigen中文文档] 存储顺序
矩阵和二维数组有两种不同的存储顺序:列优先和行优先。本节解释了这些存储顺序以及如何指定应该使用哪一种。
165 0
|
C++
[Eigen中文文档] 按值将Eigen对象传递给函数
对于 Eigen,这一点更为重要:按值传递固定大小的可向量化 Eigen 对象不仅效率低下,而且可能是非法的或使程序崩溃! 原因是这些 Eigen 对象具有对齐修饰符,在按值传递时会不遵守这些修饰符。
165 0
[Eigen中文文档] 编写以特征类型为参数的函数(一)
Eigen使用表达式模板的方式导致每个表达式的类型可能都不同。如果将这样的表达式传递给一个需要Matrix类型参数的函数,则表达式将隐式地被评估为一个临时Matrix,然后再传递给函数。这意味着失去了表达式模板的好处。
144 0
|
6月前
|
编译器 程序员 C语言
C++11『右值引用 ‖ 完美转发 ‖ 新增类功能 ‖ 可变参数模板』
C++11『右值引用 ‖ 完美转发 ‖ 新增类功能 ‖ 可变参数模板』
85 0
|
存储 编译器
[Eigen中文文档] 编写以特征类型为参数的函数(二)
Eigen使用表达式模板的方式导致每个表达式的类型可能都不同。如果将这样的表达式传递给一个需要Matrix类型参数的函数,则表达式将隐式地被评估为一个临时Matrix,然后再传递给函数。这意味着失去了表达式模板的好处。
87 0
[Eigen中文文档] 固定大小的可向量化Eigen对象
本文主要解释 固定大小可向量化 的含义。
112 0
|
算法 C++ 容器
【STL终极奥义❀解耦合思想的实现❀】函数对象、谓词与函数适配器——从for_each、transform、count_if、sort算法源码的角度分析(二)
【STL终极奥义❀解耦合思想的实现❀】函数对象、谓词与函数适配器——从for_each、transform、count_if、sort算法源码的角度分析
139 0
【STL终极奥义❀解耦合思想的实现❀】函数对象、谓词与函数适配器——从for_each、transform、count_if、sort算法源码的角度分析(二)
|
算法 C++ 容器
【STL终极奥义❀解耦合思想的实现❀】函数对象、谓词与函数适配器——从for_each、transform、count_if、sort算法源码的角度分析(一)
【STL终极奥义❀解耦合思想的实现❀】函数对象、谓词与函数适配器——从for_each、transform、count_if、sort算法源码的角度分析
149 0
【STL终极奥义❀解耦合思想的实现❀】函数对象、谓词与函数适配器——从for_each、transform、count_if、sort算法源码的角度分析(一)