numpy.frombuffer
函数的文档特别指出生成的数组将是一维的:
将缓冲区解释为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)
从对象buf构造单一类型的一维ndarray,它导出(单段)缓冲区协议(或者具有返回导出缓冲区的对象的__buffer__属性协议)。首先尝试可写缓冲区,然后是只读缓冲区。返回数组的NPY_ARRAY_WRITEABLE标志将反映哪一个成功。假设数据从对象的存储器位置的起始处的偏移字节开始。将根据数据类型描述符dtype解释缓冲区中数据的类型。如果count为负,则将根据缓冲区的大小和请求的itemsize确定,否则count表示应从缓冲区转换的元素数。
是"单段"是否意味着它不能包含使用的填充,例如,对齐矩阵的行?在那种情况下,我搞砸了,因为我的矩阵可以很好地使用需要填充的对齐策略。
回到原来的问题:
有没有办法让我创建一个与预先存在的缓冲区共享内存的NumPy数组?
备注:github上有一个名为Eigen3ToPython的项目,旨在将eigen与python连接,但它不允许内存共享(强调我的):
此库允许:[...]以透明方式转换为Numpy数组(
np.array
)(但内存不在两个表示之间共享)
修改 有人可能会指出类似标题的问题Numpy 2D- Array from Buffer?。不幸的是,那里给出的解决方案似乎不适用于我的情况,因为生成的2D数组不与原始缓冲区共享内存。
编辑:如何在Eigen中组织数据
使用跨步访问,在1D内存缓冲区中对2个矩阵进行特征映射。例如,双精度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 :(得分:1)
我在这里回答了我自己的问题。感谢@ 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 (?)
);
}
typenum
是numpy type id number。
此调用创建一个新的numpy数组,为其提供一个缓冲区(通过data
参数),使用dims
和strides
参数描述缓冲区(前者也设置形状)返回数组),描述数据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的流运算符打印矩阵。我可以得到一个ndarray
a
,它与Eigen矩阵完全对应。当我修改a
(In[7]
)时,不仅修改了numpy数组(Out[8]
),还修改了底层的Eigen数组(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的方法更通用。