2017-10-11 157 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