2017-10-11 111 views
1

numpy.frombuffer函数的文档具体说,所产生的阵列将是一维:如何从多维缓冲区初始化一个NumPy数组?

解释一个缓冲区作为1维阵列。

我不确定此报价的后果。文档只是告诉我,生成的数组将是一维的,但从不说输入缓冲区必须描述一维对象。

我在C++中有一个(2D)Eigen matrix。我想创建一个Python buffer,它描述矩阵的内容。然后,我想用这个缓冲区来初始化我的NumPy数组,并将它提供给我的Python脚本。目标是将信息传递给Python而不复制数据并允许python修改矩阵(例如初始化矩阵)。

numpy.frombuffer的C-API当量PyArray_FromBuffer,并且它也共享单维短语,但它有更多的文档(重点煤矿):

PyObject* PyArray_FromBuffer(PyObject* buf, PyArray_Descr* dtype, npy_intp count, npy_intp offset)

构建的一维从导出缓冲区协议(或具有返回导出缓冲区协议的对象的属性__buffer__)的对象buf导出单个类型的ndarray。首先会尝试写入缓冲区,然后再尝试读取缓冲区。返回数组的NPY_ARRAY_WRITEABLE标志将反映哪一个成功。假定数据从对象的内存位置开始的偏移量字节处开始。缓冲区中数据的类型将根据数据类型描述符dtype进行解释。如果count为负,那么它将根据缓冲区的大小和请求的itemsize来确定,否则,count表示应该从缓冲区转换多少个元素。

“单段”是指它不能包含使用的填充,例如对齐矩阵的行吗?在这种情况下,我搞砸了,因为我的矩阵可以很好地使用需要填充的对齐策略。

回到原来的问题:

有我的方式来创建一个与NumPy阵列,其与预先存在的缓冲共享内存?


备注:有在GitHub上的一个项目叫Eigen3ToPython,其目的是与蟒蛇连接征,但它并不支持内存共享(重点煤矿):

这个库允许:[...]从numpy的阵列(np.array)以透明的方式转换为/

(然而, 内存不是两种表示之间共享)

编辑 有人可能会指出了同样的同名问题Numpy 2D- Array from Buffer?。不幸的是,这里给出的解决方案似乎不适合我的情况,因为生成的二维数组不会与原始缓冲区共享内存。


编辑:如何在数据本征

征2D矩阵映射在一维缓冲存储器,通过使用跨距访问组织的。例如,双精度3×2矩阵需要6倍,即48个字节。一个48字节的缓冲区被分配。此缓冲区中的第一个元素表示矩阵中的[0, 0]条目。

为了访问该元素[i, j],下面的公式:

double* v = matrix.data() + i*matrix.rowStride() + j*matrix.colStride() 

,其中matrix是矩阵对象和它的成员函数data()rowStride()colStride()返回分别的起始地址缓冲区,两个连续行之间的距离以及两个连续列之间的距离(浮点格式大小的倍数)。

默认情况下,Eigen使用列主要格式,因此rowStride() == 1,但它也可以配置为使用行主格式,并使用colStride() == 1

另一个重要的配置选项是对齐。数据缓冲区很可能包含一些不需要的值(即,不是矩阵的一部分的值),以使列或行开始于对齐的地址。这使得矩阵上的操作可以进行矢量化。在上面的例子中,假设列优先格式和16字节对齐,下面的矩阵

3 7 
1 -2 
4 5 

可以存储赢取以下缓冲液:

0 0 3 1 4 0 7 -2 5 0 

的0值被称为填充。开始处的两个0可能是必要的,以确保实际数据的开始对齐到相同的边界。 (请注意,data()成员函数将返回3的地址)。在这种情况下,为步幅的行和列是

rowStride: 1 
colStride: 4 

(而在未对齐的情况下,它们将是1和3分别。)

Numpy需要一个C连续缓冲区,即没有填充的行主结构。如果Eigen没有插入填充,那么可以非常容易地解决列主要特征矩阵的行主要问题的问题:将缓冲区传递给一个numpy数组,然后重构和转置结果ndarray。我设法完美地完成了这项工作。

但是在Eigen插入填充的情况下,使用这种技术无法解决问题,因为ndarray仍然会看到数据中的零,并认为它们是矩阵的一部分,同时丢弃了某些值数组的末尾。而是我问一个解决方案的问题。

现在,作为一个方面的评论,既然我们在循环中有@ggael的运气,谁可能会说出一些光,我不得不承认,我从来没有Eigen在我的矩阵中插入任何填充。我在Eigen文档中似乎没有发现任何填充。但是,我希望对齐策略能够对齐每一列(或行),而不仅仅是第一列。我的期望错了吗?如果我是,那么整个问题不适用于Eigen。但是它适用于我正在使用的其他图书馆,这些图书馆采用了上述的校准策略,所以请在回答问题时不要考虑最后一段。

+0

您是否尝试过直接使用['np。]创建NumPy数组。ndarray'](https://docs.scipy.org/doc/numpy-1.13.0/reference/generated/numpy.ndarray.html)?根据文档,它有一个'buffer'参数。 – unutbu

+0

'numpy'数据存储在一个平面缓冲区中。所以一个缓冲区(一组连续的字节)包含4000个字节,我们指定dtype'int32','frombuffer'应该给一个(1000)形状的数组。随后的“重塑”可以使其成为2D。在numpy多维度是由形状和步幅属性,而不是数据。 – hpaulj

+0

解释'Eigen'如何存储其数据。它是一个简单的1d缓冲区吗?或者更像是指向指针的“C”指针? – hpaulj

回答

0

我在这里回答我自己的问题。感谢@ user2357112指向正确的方向:我需要的是PyArray_NewFromDescr

以下Python目的是围绕一个本征矩阵的包装:

struct PyEigenMatrix { 
    PyObject_HEAD 
    Eigen::Matrix<RealT, Eigen::Dynamic, Eigen::Dynamic> matrix; 
}; 

RealT是我使用的浮点类型(float在我的情况)。

为了返回一个np.ndarray对象,我添加的成员函数的类:

static PyObject* 
PyEigenMatrix_as_ndarray(PyEigenMatrix* self, PyObject* args, PyObject* kwds) 
{ 
    // Extract number of rows and columns from Eigen matrix 
    npy_intp dims[] = { self->matrix.rows(), self->matrix.cols() }; 

    // Extract strides from Eigen Matrix (multiply by type size to get bytes) 
    npy_intp strides[] = { 
     self->matrix.rowStride() * (npy_intp)sizeof(RealT), 
     self->matrix.colStride() * (npy_intp)sizeof(RealT) 
    }; 

    // Create and return the ndarray 
    return PyArray_NewFromDescr(
      &PyArray_Type,     // Standard type 
      PyArray_DescrFromType(typenum), // Numpy type id 
      2,        // Number of dimensions 
      dims,       // Dimension array 
      strides,      // Strides array 
      self->matrix.data(),   // Pointer to data 
      NPY_ARRAY_WRITEABLE,   // Flags 
      (PyObject*)self     // obj (?) 
     ); 
} 

typenumnumpy type id number

此呼叫创建了一个新numpy的阵列,给它一个缓冲器(通过data参数),描述了使用dimsstrides参数缓冲器(前者还设置返回的数组的形状),描述了数据dype,设置矩阵为读写(通过flags参数。

我不知道最后的参数obj意味着虽然该文件提到它仅针对其中类型是PyArray_Type不同的情况。


为了说明这在实践中如何工作,让我显示一些python代码。

In [3]: m = Matrix(7, 3) 

In [4]: m 
Out[4]: 
    0.680375 -0.211234 0.566198 
    0.59688 0.823295 -0.604897 
-0.329554 0.536459 -0.444451 
    0.10794 -0.0452059 0.257742 
-0.270431 0.0268018 0.904459 
    0.83239 0.271423 0.434594 
-0.716795 0.213938 -0.967399 

In [5]: a = m.as_ndarray() 

In [6]: a 
Out[6]: 
array([[ 0.68 , -0.211, 0.566], 
     [ 0.597, 0.823, -0.605], 
     [-0.33 , 0.536, -0.444], 
     [ 0.108, -0.045, 0.258], 
     [-0.27 , 0.027, 0.904], 
     [ 0.832, 0.271, 0.435], 
     [-0.717, 0.214, -0.967]], dtype=float32) 

In [7]: a[2, 1] += 4 

In [8]: a 
Out[8]: 
array([[ 0.68 , -0.211, 0.566], 
     [ 0.597, 0.823, -0.605], 
     [-0.33 , 4.536, -0.444], 
     [ 0.108, -0.045, 0.258], 
     [-0.27 , 0.027, 0.904], 
     [ 0.832, 0.271, 0.435], 
     [-0.717, 0.214, -0.967]], dtype=float32) 

In [9]: m 
Out[9]: 
    0.680375 -0.211234 0.566198 
    0.59688 0.823295 -0.604897 
-0.329554 4.53646 -0.444451 
    0.10794 -0.0452059 0.257742 
-0.270431 0.0268018 0.904459 
    0.83239 0.271423 0.434594 
-0.716795 0.213938 -0.967399 

Matrix是我的PyEigenMatrix类型。我添加了一个__repr__函数,它使用Eigen的流操作符打印矩阵。我可以有一个ndarraya这完全对应于特征矩阵。当我修改aIn[7])时,numpy阵列不仅得到修改(Out[8]),而且还包含底层特征阵列(Out[9]),表明这两个对象共享相同的内存。


编辑 @ user2357112是正确的两倍。他在评论中提出的第二种方法也适用。如果类型PyEigenMatrix导出缓冲区接口(我的类型),则解决方案与创建memoryview对象(in Python或使用C-API)一样简单,并将此对象传递给np.array函数,该函数也指定copy=False

这里是它如何工作的:

In [2]: m = Matrix(7, 3) 

In [3]: mv = memoryview(m)  

In [4]: a = np.array(mv, copy=False) 

In [5]: m 
Out[5]: 
    0.680375 0.536459 0.904459 
-0.211234 -0.444451 0.83239 
    0.566198 0.10794 0.271423 
    0.59688 -0.0452059 0.434594 
    0.823295 0.257742 -0.716795 
-0.604897 -0.270431 0.213938 
-0.329554 0.0268018 -0.967399 

In [6]: a 
Out[6]: 
array([[ 0.68 , 0.536, 0.904], 
     [-0.211, -0.444, 0.832], 
     [ 0.566, 0.108, 0.271], 
     [ 0.597, -0.045, 0.435], 
     [ 0.823, 0.258, -0.717], 
     [-0.605, -0.27 , 0.214], 
     [-0.33 , 0.027, -0.967]], dtype=float32) 

In [7]: a [3, 1] += 2 

In [8]: a 
Out[8]: 
array([[ 0.68 , 0.536, 0.904], 
     [-0.211, -0.444, 0.832], 
     [ 0.566, 0.108, 0.271], 
     [ 0.597, 1.955, 0.435], 
     [ 0.823, 0.258, -0.717], 
     [-0.605, -0.27 , 0.214], 
     [-0.33 , 0.027, -0.967]], dtype=float32) 

In [9]: m 
Out[9]: 
0.680375 0.536459 0.904459 
-0.211234 -0.444451 0.83239 
0.566198 0.10794 0.271423 
    0.59688 1.95479 0.434594 
0.823295 0.257742 -0.716795 
-0.604897 -0.270431 0.213938 
-0.329554 0.0268018 -0.967399 

此方法具有不需要numpy的C-API的优势。矩阵类型只需支持缓冲协议,这比直接依赖numpy的方法更普遍。

+0

我认为'np.asarray'可以接受任何缓冲区类型,而不必先通过memoryview – DavidW