C++中的NumPy风格数组?

120

是否有类似NumPy的数组支持切片、向量化操作、元素逐个相加和相减等功能的C++(或C)库?


3
Armadillo是一个C++线性代数库,提供了高效且易于使用的矩阵操作和线性代数函数。它支持快速的数值操作,并具有与MATLAB类似的语法。Armadillo还可以与其他C++库和框架集成,例如OpenBLAS和MPI。 - Oliver Charlesworth
2
我还没有在评论中看到Boost.MultiArray - Dmitry Ledentsov
8
这是一道非常有价值的软件编码问题。为什么被关闭了? - stackoverflowuser2010
1
@stackoverflowuser2010 因为很多好的问题在stackoverflow上被关闭了。 - kristjan
2
将NumPy Python代码转换为C++时,我发现std::valarray非常有帮助。它在标准库中,并支持对所有元素进行切片、算术操作以及两个valarrays之间的操作。 - Synck
显示剩余4条评论
13个回答

84
这里有几个免费软件,可能适合您的需求。
  1. GNU Scientific Library是一款使用GPL协议编写的C语言软件。因此,它有类似于C语言的分配和编程方式(指针等)。通过GSLwrap,您可以拥有C++编程方式,同时仍然使用GSL。 GSL具有BLAS实现,但如果您想要更高的性能,则可以使用ATLAS代替默认的CBLAS。

  2. boost/uBLAS库是一个BSL库,用C ++编写并作为boost软件包分发。这是实现BLAS标准的C ++方式。uBLAS带有一些线性代数函数,并且有一个实验性绑定到ATLAS

  3. eigen是一个线性代数库,用C ++编写,根据MPL2许可证分发(从版本3.1.1开始)或LGPL3 / GPL2(旧版本)。这是一种C ++编程方式,但比其他两种更集成(提供了更多算法和数据结构)。Eigen 声称比上述BLAS实现更快,但不遵循事实上的标准BLAS API。Eigen似乎没有在并行实现方面投入太多努力。

  4. Armadillo是用于C ++的LGPL3库。它具有LAPACK的绑定(numpy使用的库)。它使用递归模板和模板元编程,这是一个好点(我不知道其他库是否也在这样做?)。

  5. xtensor是一个BSD许可的C ++库。它提供了与NumPy非常相似的C ++ API。请参见https://xtensor.readthedocs.io/en/latest/numpy.html以获取速查表。

这些替代方案非常适合获取数据结构和基本线性代数。根据您对风格、许可证或系统管理员挑战(安装大型库,如LAPACK可能很困难)的偏好,您可以选择最适合您需求的方案。

20
信不信由你,我的回答是我几个月前自己搜索的结果。我相信收集有助于我做出选择的信息会有一定的帮助。我不确定是将多个信息分散在答案中更好,还是集中在一起更好。如果你更关注伦理而不是效率,你仍然可以给每个人点赞。 - nojhan
24
不幸的是,这些选项都不提供类似于NumPy数组的通用且方便的功能。NumPy数组是任意维度的,并支持诸如a[:4,::-1,:,19] = b[None,-5:,None]a[a>5]=0等操作,同时拥有大量的数组和索引操作函数可用。我真切地希望有一天能为C++开发出类似的工具。 - amaurea
4
OpenCV还有一个矩阵类型,可以具有任意维度大小;使用列/行范围 (a.colRange(4,7).rowRange(4,8) 表示 a[4:7,4,8]) 和条件掩码 (a.setTo(cv::Scalar(0), a>5) 表示 a[a>5]=0)。 - xaedes
5
请查看下面有关xtensor的答案,它可以实现以上所有功能。 - Quant
2
我最近在一个项目中不得不使用Eigen,我必须说,虽然它似乎很高效,但语法绝对糟糕。没有那种令人惊叹的Python切片语法可用。例如,如果你有一个一维向量x并且想要操作前n个元素,你必须使用x.head(n)。甚至不要问如何操作x的任意切片,你需要一个好老的for循环来做到这一点。这只是我可以列举出的许多笨拙和不方便的例子之一。 - Alex
@amaurea,下面的关于xtensor 的新回答可能满足您的要求。 - Eduardo Pignatelli

66

试用xtensor。(请查看NumPy转Xtensor备忘单)。

xtensor是一个C++库,用于多维数组表达式的数值分析。

xtensor提供了:

  • 可扩展的表达式系统,支持类似numpy的广播方式。
  • API遵循C++标准库的惯例。
  • 工具来操作数组表达式并构建基于xtensor的应用。

示例

初始化一个二维数组并计算其中一行和一个一维数组的总和。

#include <iostream>
#include "xtensor/xarray.hpp"
#include "xtensor/xio.hpp"

xt::xarray<double> arr1
  {{1.0, 2.0, 3.0},
   {2.0, 5.0, 7.0},
   {2.0, 5.0, 7.0}};

xt::xarray<double> arr2
  {5.0, 6.0, 7.0};

xt::xarray<double> res = xt::view(arr1, 1) + arr2;

std::cout << res;

输出

{7, 11, 14}

初始化一个一维数组并在原地重塑它。

#include <iostream>
#include "xtensor/xarray.hpp"
#include "xtensor/xio.hpp"

xt::xarray<int> arr
  {1, 2, 3, 4, 5, 6, 7, 8, 9};

arr.reshape({3, 3});

std::cout << arr;

输出

{{1, 2, 3},
 {4, 5, 6},
 {7, 8, 9}}

2
@Llamageddon,你认为这应该是被选中的答案吗? - Quant

8

DyND 旨在成为一个类似于NumPy的C++库,支持广播、算术运算和切片等功能。然而,它仍处于实验阶段,许多功能尚未实现。

以下是使用DyND数组在C++中实现de Casteljau算法的简单示例:

#include <iostream>
#include <dynd/array.hpp>

using namespace dynd;

nd::array decasteljau(nd::array a, double t){
    size_t e = a.get_dim_size();
    for(size_t i=0; i < e-1; i++){
        a = (1.-t) * a(irange()<(e-i-1)) + t * a(0<irange());
    }
    return a;
}

int main(){
    nd::array a = {1., 2., 2., -1.};
    std::cout << decasteljau(a, .25) << std::endl;
}

我之前写过一篇博客文章,其中包含更多的例子和Fortran 90、C++中的DyND和Python中的NumPy语法的并排比较。

免责声明:我是当前DyND开发人员之一。


7

这是一个老问题,但仍然觉得有必要回答。我认为可能会帮助许多人,特别是在用C++编写Python代码的开发者。

如果您已经使用过Python NumPy,则NumCpp是一个很好的选择。它的语法非常简洁,并且具有与py numpy相似的函数或方法。

比较部分在自述文件中也非常不错。

NumCpp

nc::NdArray<int> arr = {{4, 2}, {9, 4}, {5, 6}};
arr.reshape(5, 3);
arr.astype<double>();

6

使用LibTorch(C++的PyTorch前端)并感到高兴。


有没有类似这样的示例:a[:4,::-1,:,19] = b[None,-5:,None] - Cypher
1
@Cypher 答案:a.index({Slice(None, 4), Slice(None, None, -1), Slice(), 19}) = b.index({None, Slice(-5, None), None}) 文档:https://pytorch.org/cppdocs/notes/tensor_indexing.html - Martin Trenkmann
谢谢。我希望有更好的语法方式来做这件事。在C++中,运算符重载是否允许实现类似于Numpy数组的功能? - Cypher

4
如果您想要使用多维数组(例如numpy)进行图像处理或神经网络,可以使用OpenCVcv::Mat以及大量的图像处理算法。如果您只想将其用于矩阵运算,则只需编译相应的opencv模块以减小大小并拥有小型的OpenCV库。 cv :: Mat (Matrix)是一个n维数组,可用于存储各种类型的数据,例如RGB、HSV或灰度图像、具有实数或复数值的向量、其他矩阵等。
Mat包含以下信息:width,height,type,channels,data,flags,datastart,dataend等等。
它具有几种矩阵操作方法。额外的奖励是您可以在CUDA核心上创建它们,以及cv :: cuda :: GpuMat
假设我想创建一个具有10行、20列、类型为CV_32FC3的矩阵:
int R = 10, C = 20;
Mat m1; 
m1.create(R, C, CV_32FC3); //creates empty matrix

Mat m2(cv::Size(R, C), CV_32FC3); // creates a matrix with R rows, C columns with data type T where R and C are integers, 

Mat m3(R, C, CV_32FC3); // same as m2

BONUS:

编译仅包含矩阵运算的精简版OpenCV库,可以参考这篇文章中提到的方法之一。

或者,可以使用下面的cmake命令编译OpenCV源代码:

$ git clone https://github.com/opencv/opencv.git
$ cd opencv
$ git checkout <version you want to checkout>
$ mkdir build
$ cd build
$ cmake -D WITH_CUDA=OFF -D WITH_MATLAB=OFF -D BUILD_ANDROID_EXAMPLES=OFF -D BUILD_DOCS=OFF -D BUILD_PERF_TESTS=OFF -D BUILD_TESTS=OFF -DANDROID_STL=c++_shared -DBUILD_SHARED_LIBS=ON -D BUILD_opencv_objdetect=OFF -D BUILD_opencv_video=OFF -D BUILD_opencv_videoio=OFF -D BUILD_opencv_features2d=OFF -D BUILD_opencv_flann=OFF -D BUILD_opencv_highgui=OFF -D BUILD_opencv_ml=OFF -D BUILD_opencv_photo=OFF -D BUILD_opencv_python=OFF -D BUILD_opencv_shape=OFF -D BUILD_opencv_stitching=OFF -D BUILD_opencv_superres=OFF -D BUILD_opencv_ts=OFF -D BUILD_opencv_videostab=OFF -D BUILD_opencv_dnn=OFF -D BUILD_opencv_imgproc=OFF ..
$ make -j $nproc
$ sudo make install


尝试以下示例:
 #include "opencv2/core.hpp"
 #include<iostream>

 int main()
 {
     std::cout << "OpenCV Version " << CV_VERSION << std::endl;

     int R = 2, C = 4;
     cv::Mat m1;
     m1.create(R, C, CV_32FC1); //creates empty matrix

     std::cout << "My Mat : \n" << m1 << std::endl;
 }

使用以下命令编译代码:

$ g++ -std=c++11 opencv_mat.cc -o opencv_mat `pkg-config --libs opencv` `pkg-config --cflags opencv`

运行可执行文件:

$ ./opencv_mat

OpenCV Version 3.4.2
My Mat :
[0, 0, 0, 0;
 0, 0, 0, 0]

3

Eigen是一个很好的线性代数库。

http://eigen.tuxfamily.org/index.php?title=Main_Page

这是一个仅包含头文件的库,安装非常简单。它依赖于模板以生成优化良好的代码,并自动向量化矩阵操作。
此外,它还完全支持系数操作,例如两个矩阵之间的“逐元素乘法”。这符合您的需求吗?

4
Eigen的语法相当糟糕。它没有Numpy中平滑切片语法的简便性。而且它不是一个通用的n维数组库,更多地是针对1D向量和2D矩阵。他们将一维数组称为VectorXd,将二维数组称为MatrixXd,这个事实已经让我望而却步了。 - Alex
在Eigen中有一个针对n维数组的“不支持”张量实现,但使用起来并不像numpy.ndarray那样直观。 - gisil

3

xtensor很好用,但我最终还是自己写了一个C++20迷你库作为玩具项目,尽量保持接口简单易用。这是它的链接:https://github.com/gbalduzz/NDArray

示例代码:

using namespace nd;
NDArray<int, 2> m(3, 3); // 3x3 matrix
m = 2; // assign 2 to all
m(-1, all) = 1; // assign 1 to the last row.

auto tile = m(range{1, end}, range{1, end}); // 2x2 tile
std::sort(tile.begin(), tile.end());

std::cout << m; // prints [[2, 2, 2], [2, 1, 1], [1, 2, 2]]

它目前还没有提供花式算术运算符来折叠多个操作,但是您可以将任意lambda函数广播到具有相同形状的张量集中,或使用惰性求值的算术运算符。
请告诉我您认为接口如何以及与其他选项相比如何,并且如果有希望的话,您希望看到实现哪些操作。
免费许可证,无依赖!
补充说明:我成功编译并运行了xtensor,结果是在迭代视图时我的库要快得多(2到3倍)。

2
VIGRA包含一个良好的N维数组实现:

http://ukoethe.github.io/vigra/doc/vigra/Tutorial.html

我广泛使用它,发现它非常简单和有效。它也只有头文件,非常容易集成到您的开发环境中。就API而言,它是我遇到的最接近NumPy的东西。
主要的缺点是它没有其他库那么广泛使用,所以你在网上找不到太多帮助。还有它的命名方式有些笨拙(尝试搜索一下!)。

2

Blitz++ 支持任意数量维度的数组,而 Armadillo 只支持三个维度(向量、矩阵和立方体)。Eigen 只支持向量和矩阵(不支持立方体)。缺点是 Blitz++ 没有线性代数函数除了基本的逐元素运算和张量收缩。开发似乎已经相当长时间没有进展,但可能是因为该库已经实现其功能,不需要太多更改。


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