Eigen使用矩阵作为函数参数
1 使用矩阵作为函数参数介绍
文章来源Writing Functions Taking %Eigen Types as Parameters
Eigen为了在函数中传递不同的类型使用了表达式模板技术。如果你传递一个表达式到函数时使用了Matrix作为参数,你的表达式会被隐含的作为Matrix模板传递给这个函数。这意味着你丢掉了表达式模板所带来的好处,这有如下两个缺点:
- 评估进模板的参数可能是无用或者无效的。
- 这只允许函数从表达式中读取而无法写入。
幸运的是这些表达式类型都有一个共同特点就是他们都继承于几个通用的、模板化的基类。通过让你的函数使用这些基类作为模板参数,你可以让它们很好的和表达式模板结合。
2 一些简单的例子
这一段将会提供一些简单的例子用来说明Eigen中提供的几种不同类型对象。在开始介绍这些例子之前,我们首先需要概括如何使用这些基类(当然也可以查看官方的手册类的层次)。
- MatrixBase: 它是所有稠密矩阵表达式的基类(和array表达式、稀疏矩阵表达式、特殊矩阵表达式相对应)。在函数中使用它作为参数意味着它只能传递稠密矩阵。
- ArrayBase: 它是稠密array表达式的基类(和matrix表达式相对应等)。在函数中使用它作为参数意味着它只能传递array.
- DenseBase: 它是所有密集表达式的基类,也就是说它包含了MatrixBase和ArrayBase.它可以作为函数参数传递matrices和arrays.
- EigenBase: 它是所有矩阵表达式统一的基类,它可以被应用在矩阵或者arrays,比如特殊矩阵(比如对角矩阵、转置矩阵等).它被应用在函数中作为参数意味着所有这种类型的参数都可以被传递。
2.1 EigenBase使用例程
打印Eigen中通用对象的维数。它可能是任何形式的矩阵表达式,任何稠密或稀疏或array的形式。
#include "Eigen/Core"
#include "iostream"
using namespace Eigen;
using namespace std;
template <typename Derived>
void print_size(const EigenBase<Derived>& b)
{
cout<<"size(rows,cols):"<<b.size()<<" ("<<b.rows()
<<","<<b.cols()<<")"<<std::endl;
}
int main()
{
Vector3f v;
print_size(v);
//v.asDiagonal()returns a 3x3 diagonal matrix pseudo-expression
print_size(v.asDiagonal());
}
执行结果如下:
size(rows,cols):3 (3,1)
size(rows,cols):9 (3,3)
2.2 DenseBase使用示例
打印一个稠密表达式的子块,接收任何稠密matrix和array表达式。但是稀疏类型和特殊类型的matrix类(如DiagonalMatrix)不能接收。
#include "Eigen/Core"
#include "iostream"
using namespace Eigen;
using namespace std;
template<typename Derived>
void print_block(const DenseBase<Derived>& b,int x,int y,int r,int c)
{
cout<<"block:\n"<<b.block(x,y,r,c)<<endl;
}
int main()
{
Matrix3f v;
v<< 1,2,3,
4,5,6,
7,8,9;
print_block(v,1,1,2,2);
}
执行结果如下:
block:
5 6
8 9
2.3 ArrayBase使用示例
打印array或其表达式中的最大元素。
#include "Eigen/Core"
#include "iostream"
using namespace Eigen;
using namespace std;
template <typename Derived>
void PrintMaxCoeff(const ArrayBase<Derived>& a)
{
cout<<"max: "<<a.maxCoeff()<<endl;
}
int main()
{
Array33f v;
v<< 1,2,3,
4,5,6,
7,8,9;
PrintMaxCoeff(v);
}
输出结果:
max: 9
2.4 MatrixBase使用示例
打印给定矩阵或矩阵表达式的条件数倒数(inverse condition number).
#include "Eigen/SVD"
#include "iostream"
using namespace Eigen;
using namespace std;
template <typename Derived>
void print_inv_cond(const MatrixBase<Derived>& a)
{
const typename JacobiSVD<typename Derived::PlainObject>::SingularValuesType&
sing_vals = a.jacobiSvd().singularValues();
std::cout << "inv cond: " << sing_vals(sing_vals.size()-1) / sing_vals(0) << std::endl;
}
int main()
{
Matrix3f m;
m<< 1,2,3,
4,5,6,
7,8,9;
print_inv_cond(m);
}
执行结果:
inv cond: 1.11994e-008
2.5 多模板参数使用示例
计算两点之间的欧式距离。
#include "Eigen/Core"
#include "iostream"
using namespace Eigen;
using namespace std;
template <typename Derived1,typename Derived2>
typename Derived1::Scalar SquareDist(const MatrixBase<Derived1>& p1,
const MatrixBase<Derived2>& p2)
{
return (p1-p2).squaredNorm();
}
int main()
{
Vector3f p1;
Vector3f p2;
p1<<1,2,3;
p2<<4,5,6;
cout<<"p1和p2之间的欧式距离为: "<<SquareDist(p1,p2)<<endl;
}
执行结果如下:
p1和p2之间的欧式距离为: 27
上面的几个简单的例子给读者留下一个第一印象——如何用一个普通的和不变的Matrix或Array作为函数的参数。也故意给读者留下了一个印象,在函数参数通用基类的最佳选项是什么。在下面一节的例子中我们会着眼于更加细节的地方以及实现它的不同方式,同时讨论各种实现过程中的存在的问题和优势。对于下面的讨论,Matrix和Array以及MatrixBase和ArrayBase可以被交换并且所有的参数仍然成立。
3 如何写一个通用的但是不是模板的函数?
在之前的所有例子中,函数都被写成了模板的形式。这种方法允许我们写一下非常通用的代码,但是如果能写出一种函数不使用模板同时还能保证一定程度的通用性这会是我们更加满意,因为这可以避免很多愚蠢的参数拷贝。这有一个典型的例子来写一个函数能同时接受MatrixXf和MatrixXf的块。这正是Ref类的用途。例程如下:
#include "Eigen/SVD"
#include "iostream"
using namespace Eigen;
using namespace std;
float InvCond(const Ref<const MatrixXf>& a)
{
const VectorXf sing_vals = a.jacobiSvd().singularValues();
return sing_vals(sing_vals.size() - 1)/sing_vals(0);
}
int main()
{
Matrix4f m = Matrix4f::Random();
cout<<"matrix m:"<<endl<<m<<endl<<endl;
cout<<"InvCond(m):"<<InvCond(m)<<endl;
cout<<"InvCond(m(1:3,1:3)): "<<InvCond(m.topLeftCorner(3,3))<<endl;
cout<<"InvCond(m+1): "<<InvCond(m+Matrix4f::Identity())<<endl;
}
执行结果如下:
matrix m:
-0.997497 0.170019 0.64568 0.421003
0.127171 -0.0402539 0.49321 0.0270699
-0.613392 -0.299417 -0.651784 -0.39201
0.617481 0.791925 0.717887 -0.970031
InvCond(m):0.150053
InvCond(m(1:3,1:3)): 0.163871
InvCond(m+1): 0.202112
上面的例子中前两次调用InvCond并没有产生类的拷贝,因为参数的存储布局(memory layout) 和 Ref<MatrixXf>
的存储布局相匹配。但是,最后一次调用时,我们的表达式会自动的被计算同时将值存入到一个由Ref<>
对象决定的MatrixXf临时变量中。
一个Ref对象同时也是可以被写入的。下面这个例子是计算两个矩阵的协方差,其中每一列是一个随机变量,每一行是一组测量数据。
#include "Eigen/SVD"
#include "iostream"
using namespace Eigen;
using namespace std;
void cov(const Ref<const MatrixXf> x,const Ref<const MatrixXf> y,Ref<MatrixXf> C)
{
const float num_observations = static_cast<float>(x.rows());
const RowVectorXf x_mean = x.colwise().sum()/num_observations;
const RowVectorXf y_mean = y.colwise().sum()/num_observations;
C = (x.rowwise() - x_mean).transpose()*(y.rowwise() - y_mean)/num_observations;
}
int main()
{
Matrix<float,4,3> m,n;
MatrixXf conMn;
m<< 1,2,3,
4,5,6,
7,8,9,
10,11,12;
n = m;
conMn.resize(3,3); //在函数内部不允许修改大小,必须在调用之前给出大小
cov(m,n,conMn);
cout<<conMn<<endl;
}
执行结果如下:
11.25 11.25 11.25
11.25 11.25 11.25
11.25 11.25 11.25
当然我们也可以使用矩阵的子块作为参数conv(m.leftCols<3>(),n.leftCols<3>(),conMn.topLeftCorner<3,3>());
.
Ref<>类还有其他两个模板参数运行控制存储布局(memory layout)以便接收参数的同时不进行任何拷贝。查看Ref文档以便了解更多细节。
4 什么情况下函数传递一个普通的Matrix或Array参数能工作?
即不使用模板也不使用Ref类,上面例子的一个狭窄实现可能是下面这个样子。
MatrixXf cov(const MatrixXf& x, const MatrixXf& y)
{
const float num_observations = static_cast<float>(x.rows());
const RowVectorXf x_mean = x.colwise().sum() / num_observations;
const RowVectorXf y_mean = y.colwise().sum() / num_observations;
return (x.rowwise() - x_mean).transpose() * (y.rowwise() - y_mean) / num_observations;
}
和一些人的第一直觉相反,这个实现能很好的工作除非你需要一个通用的实现让double类型的matrix也工作或者除非你不关心模板对象。为什么呢?临时变量放在哪呢?下面这段代码又是怎么运行的呢?
MatrixXf x,y,z;
MatrixXf C = conv(x,y+z);
在这个特殊情况下,这个例子能正常工作是因为所有的参数都声明为const reference.编译器创建一个临时变量并且计算并保存x+z.一旦程序运行完成,临时变量就会被释放掉同时将结果返回给C。
注意:在使用了临时变量的代价下,函数对Matrix(或Array)使用const引用可以运行表达式。
5 什么情况下函数使用普通的Matrix或Array作为参数会失败?
这里我们对上面的例子进行小小的修改,我们不想让函数有返回值而是通过传递一个额外的非const参数来保存结果,一个狭窄的实现可能是下面这个样子:
//Note: This code is flawed!
void cov(const MatrixXf& x, const MatrixXf& y, MatrixXf& C)
{
const float num_observations = static_cast<float>(x.rows());
const RowVectorXf x_mean = x.colwise().sum() / num_observations;
const RowVectorXf y_mean = y.colwise().sum() / num_observations;
C = (x.rowwise() - x_mean).transpose() * (y.rowwise() - y_mean) / num_observations;
}
接下来我们尝试执行下面的语句:
MatrixXf C = MatrixXf::Zero(3,6);
cov(x,y, C.block(0,0,3,3));
这时会编译失败,因为不可能转换一个非const的MatrixXf&表达式给MatrixXf::block().这是因为编译器为了保护你的程序安全会避免你向一个临时变量写入结果。这里虽然我们很想向::block()里写入返回结果。如何解决这个问题呢?
这里优先选择的一个解决方案需要一点骇客手段。需要传递一个const引用这个matrix然后在内部使用去const的手段去掉const。在C98编译器下一个正确的实现如下所示:
template <typename Derived, typename OtherDerived>
void cov(const MatrixBase<Derived>& x, const MatrixBase<Derived>& y, MatrixBase<OtherDerived> const & C)
{
typedef typename Derived::Scalar Scalar;
typedef typename internal::plain_row_type<Derived>::type RowVectorType;
const Scalar num_observations = static_cast<Scalar>(x.rows());
const RowVectorType x_mean = x.colwise().sum() / num_observations;
const RowVectorType y_mean = y.colwise().sum() / num_observations;
const_cast< MatrixBase<OtherDerived>& >(C) =
(x.rowwise() - x_mean).transpose() * (y.rowwise() - y_mean) / num_observations;
}
上面这个实现中不但允许向临时表达式里写入数据,同时也允许使用任意浮点类型矩阵作为参数的函数。
注意:去const的骇客手段只能在模板函数中工作,它不能在MatrixXf实现中工作因为不可能转换一个Block表达式到MatrixXf的引用。
6 在一般的实现中如何修改matrices的大小?
到这里我们所有的工作都做完了吗?前面我们例子中了解到在函数调用时不能改变matrix的大小,然后对于一般的应用,我们更习惯使用如下的方式调用前面的协方差函数。
MatrixXf x = MatrixXf::Random(100,3);
MatrixXf y = MatrixXf::Random(100,3);
MatrixXf C;
cov(x,y,C);
上面情况会因为C的维数改变而出现错误,在我们使用implementation作为MatrixBase的参数时。通常,Eigen支持自动的维数转换但不能通过表达式这样做。为什么应该允许改变一个矩阵块的维数?它是一个到子矩阵的引用,我们真的不应该去修改它的维数(试想一下,对于一个2x2的矩阵,如果我们将它子矩阵(1,1)变成2x2那结果还能是矩阵吗?)。那么如何不再MatrixBase的基础上改变维数呢?在这个实现中办法是改变derived object的维数。
#include "Eigen/SVD"
#include "iostream"
using namespace Eigen;
using namespace std;
template <typename Derived,typename OtherDerived>
void cov(const MatrixBase<Derived>& x,const MatrixBase<Derived>& y,
MatrixBase<OtherDerived> const& C_)
{
typedef typename Derived::Scalar Scalar;
typedef typename internal::plain_row_type<Derived>::type RowVectorType;
const Scalar num_observations = static_cast<Scalar>(x.rows());
const RowVectorType x_mean = x.colwise().sum()/num_observations;
const RowVectorType y_mean = y.colwise().sum()/num_observations;
MatrixBase<OtherDerived>& C = const_cast<MatrixBase<OtherDerived>&>(C_);
C.derived().resize(x.cols(),x.cols()); //resize the derived object
C = (x.rowwise() - x_mean).transpose()*(y.rowwise() - y_mean)/num_observations;
}
int main()
{
Matrix<float,4,3> m,n;
MatrixXf conMn;
m<< 1,2,3,
4,5,6,
7,8,9,
10,11,12;
n = m;
// conMn.resize(3,3); //在函数内部不允许修改大小,必须在调用之前给出大小
cov(m,n,conMn);
cout<<conMn<<endl;
}
执行结果如下:
11.25 11.25 11.25
11.25 11.25 11.25
11.25 11.25 11.25
上面这个实现中不但可以让表达式作为参数,也可以让有错误维数的矩阵作为参数。
上面的讨论对Matrix,Array,MatrixBase,ArrayBase进行交换所有结论依然成立。
7 总结
- 实现使用不可写(const referenced)对象作为参数的函数不是一个大问题,同时也不会在编译和运行时引起任何问题。但是,一个狭窄的实现可能引入不必要中间对象。通过模板使用到MatrixBase或ArrayBase(const)引用可以避免上述情况。
- 函数使用了一个可写参数必须使用const引用然后在函数体中去除const。
- 函数使用了MatrixBase(或ArrayBase)对象,如果有可能改变它们的维数,必须在derived类上调用resize()然后返回derived().
ps: 这篇随便是边看官方手册边实验边记录的,有些地方当时也不太懂,另外限于本人中文表达能力和英文阅读能力都有限,可能有些地方翻译错误,建议英语水平高的同学还是直接看官网手册比较好。
Eigen使用矩阵作为函数参数的更多相关文章
- 1.2 eigen中矩阵和向量的运算
1.2 矩阵和向量的运算 1.介绍 eigen给矩阵和向量的算术运算提供重载的c++算术运算符例如+,-,*或这一些点乘dot(),叉乘cross()等等.对于矩阵类(矩阵和向量,之后统称为矩阵 类) ...
- TensorFlow卷积网络常用函数参数详细总结
卷积操作 tf.nn.conv2d(input, filter, strides, padding, use_cudnn_on_gpu=None, name=None) 除去name参数用以指定该操作 ...
- 潭州课堂25班:Ph201805201 第八课:函数基础和函数参数 (课堂笔记)
1, 函数定义 def fun(): print('测试函数') fun() #调用函数 return 运行函数返回值 def fun(): name = [1,3,4,5] return name[ ...
- 【神经网络与深度学习】【C/C++】比较OpenBLAS,Intel MKL和Eigen的矩阵相乘性能
比较OpenBLAS,Intel MKL和Eigen的矩阵相乘性能 对于机器学习的很多问题来说,计算的瓶颈往往在于大规模以及频繁的矩阵运算,主要在于以下两方面: (Dense/Sparse) Matr ...
- C++中二维数组作为函数参数
在平时,我们经常会遇到将整个数组作为函数参数的情况,一维数组的情况,就是用数组名当形参和实参,传递的是数组的首地址.二维数组我们用的也很多,但是总是有各种问题,今天我总结一下 有个很重要的一点,字符串 ...
- Delphi_08_Delphi_Object_Pascal_基本语法_06_函数参数
发现Delphi中关于函数参数部分的内容还是比较多的,暂时说到这篇随笔为止吧,以后再继续讨论一下函数的参数部分的内容. 一 工程文件 program DefaultParameter; {$APPTY ...
- 深入理解javascript函数参数与闭包(一)
在看此文章,希望先阅读关于函数基础内容 函数定义与函数作用域 的章节,因为这篇文章或多或少会涉及函数基础的内容,而基础内容,我放在函数定义函数作用域 章节. 本文直接赘述函数参数与闭包,若涉及相关知识 ...
- php与js中函数参数的默认值设置
php函数参数默认值设置: <?phpfunction test($val=3){ echo $val."<br/>";}test(11);test();?& ...
- JS中的函数(二):函数参数(你可能不知道的参数传递)
前言: 函数分为有参有返回值,有参无返回值,无参无返回值,无参有返回值:那么对于无参数的函数你想使用函数的调用怎么办呢?如果你想封装一个代码,实现多种功能,但是形参大于实参或者实参大于形参又该如何?本 ...
随机推荐
- html5-websocket实现基于远程方法调用的数据交互
html5-websocket实现基于远程方法调用的数据交互 一般在传统网页中注册用户信息都是通过post或ajax提交到页面处理,到了HTML5后我们有另一种方法就是通过websocket进行数 ...
- android升级gradle到3.4.1
这两天把gradle升级到了gradle-3.4.1 com.android.tools.build:gradle升级到了com.android.tools.build:gradle:2.3.0 结果 ...
- WIN7 WIN10赋予文件或者文件夹完全访问权限
WIN7 WIN10赋予文件或者文件夹完全访问权限win7文件夹图标中多了一把小锁打不开文件夹怎么办?解决办法一:右击目录→取得管理员权限!该方法适用于win7旗舰版.解决办法二:添加everyone ...
- C#编程经验-选择结构和循环结构
选择结构:if elseif else ifswitch 循环结构:whiledo whilefor()foreach() 种类太多,不便记忆,人脑要记多种结构,要用的时候一种也把握不住所以,为方便记 ...
- MySQL之Haproxy+Keepalived+MySQL高可用均衡负载部署 (网络摘抄)
来源于:https://blog.csdn.net/weisong530624687/article/details/71536837?utm_source=blogxgwz3 一.安装主从MySQL ...
- 02-模拟Junit4功能
package com.day2; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; im ...
- 对窗体操作的WM消息
WM_CREATE 0x0001 应用程序创建一个窗口 WM_DESTROY 0x0002 一个窗口被销毁 WM_MOVE 0x0003 移动一个窗口 WM_SIZE 0x0005 改变一个窗口的大小 ...
- Kafka命令操作
本文主要介绍Kafka的shell命令: 查看当前服务器所有的topic [hadoop@datanode1 kafka]$ bin/kafka-topics.sh --zookeeper datan ...
- 一、python基本语法元素(温度转换)
#C=(F-32)/1.8 ; F = C * 1.8 + 32 TempStr = input("请输入带有符号的温度值:") if TempStr[-1] in ['f','F ...
- 定位JVM内存溢出问题思路总结
JVM的内存溢出问题,是个常见而有时候有非常难以定位的问题.定位内存溢出问题常见方法有很多,但是其实很多情况下可供你选择的有效手段非常有限.很多方法在一些实际场景下没有实用价值.这里总结下我的一些定位 ...