Cython memoryview 转置:Typeerror
Cython memoryview transpose: Typeerror
我正在尝试使用 python 开发一个小型的卷积神经网络框架。
卷积节点的代码已经可以(缓慢地)运行,我想加快速度。
热点是卷积滤波器在图像上移动的循环。我选择使用 cython 来加速这些循环。
明显的小注释、所有局部变量的 cdef 和删除 boundscheck,几乎减少了 10% 的运行时间。这对我来说很奇怪,根据我在网上阅读的内容,cython 应该已经能够发挥它的魔力了。
不幸的是,代码在 class 中并且严重依赖 class 的属性。我决定将其转换为 cdef class。这意味着所有 class 属性都必须用 cdef 声明。显然 cython 不支持 numpy 数组,所以我将所有 numpy 数组声明为 double[:,:,...]
到目前为止,代码运行良好,所有单元测试都通过了。现在 .pyd 的编译(我在 windows 下工作)仍然有效。但是 运行 代码创建了一个 Typeerror:
TypeError: only length-1 arrays can be converted to Python scalars
这是一些代码。这是我的卷积节点的整个前向方法,可能太多了,不容易阅读。您可能只需要最后一行。那是错误发生的原因:
@cython.boundscheck(False)
@cython.nonecheck(False)
def forward(self):
# im2col: x -> in_cols
# padding
cdef np.ndarray[DTYPE_t, ndim=4] x_padded = np.zeros((self.batch_size, self.in_colors, self.in_width + self.padding*2, self.in_height + self.padding*2))
if self.padding>0:
x_padded[:, :, self.padding:self.in_width+self.padding, self.padding:self.in_height+self.padding] = self.x
else:
x_padded[:]=self.x
# allocating new field
cdef np.ndarray[DTYPE_t, ndim=4] rec_fields = np.empty((self.filter_size**2* self.in_colors, self.batch_size, self.out_width, self.out_height))
# copying receptive fields
cdef int w,h
for w, h in np.ndindex((self.out_width, self.out_height)):
rec_fields[:, :, w, h] = x_padded[:, :, w*self.stride:w*self.stride + self.filter_size, h*self.stride:h*self.stride + self.filter_size] \
.reshape((self.batch_size, self.filter_size**2* self.in_colors)) \
.T
self.in_cols = rec_fields.reshape((self.filter_size**2 * self.in_colors, self.batch_size * self.out_width * self.out_height))
# linear node: in_cols -> out_cols
cdef np.ndarray[DTYPE_t, ndim=2] out_cols=np.dot(self.W,self.in_cols)+self.b
# col2im: out_cols -> out_image -> y
cdef np.ndarray[DTYPE_t, ndim=4] out_image = out_cols.reshape((self.out_colors, self.batch_size, self.out_width, self.out_height))
self.y[:] = out_image.transpose(1, 0, 2, 3)
最后一次转置调用在异常中被标记。我无法解释这个。转置时内存视图的行为是否不同?
更新:
我确定尺寸定义正确。如果维度不匹配,则会产生不同的运行时错误。现在无法检查,但它类似于 "got 4-dim, expected 2-dim"。
我不得不说,cython 的类型系统给我留下了极其深刻的印象。 python 异常中的这种运行时类型信息非常有用。
遗憾的是,它没有解释为什么上面的转置失败。
更新:
数组有些复杂:它们不能被覆盖,只能用作参考。
有点难解释:
神经网络的核心是一个循环,它在网络中的所有节点上连续调用 forward() 方法。
for node in self.nodes:
node.forward()
在此方法中,节点查看其输入数据,进行一些计算并写入其输出。它依赖于输入已经包含正确数据的事实。
为了设置我的网络,我以正确的顺序存储节点。我手动连接它们。
node2.x=node1.y
现在如果我写
self.y[:]= data
在node1的forward方法中,node2自动输入正确。
这需要仔细的编程:必须以正确的顺序调用前向方法并且输出绝不能被覆盖,只能写入。
替代方案是一个巨大的结构,我在其中存储每个节点的输出并传递这些数据。这将创建大量样板代码并弄乱前向和后向传递。
更新:
前面的最后几行现在看起来像这样:
cdef np.ndarray[DTYPE_t, ndim=4] out_image = out_cols.reshape((self.out_colors, self.batch_size, self.out_width, self.out_height))
cdef double[:,:,:,:] temp
temp=out_image.transpose(1,0,2,3)
self.y[...] = temp
对 temp 的分配失败,并显示相同的 TypeError 消息。
self.y[...] = some_array
# or equivalently self.y[:,:,:,:] = some_array
将 some_array
复制到 self.y
中,它必须已经初始化为正确的大小。它似乎也只有在 some_array
已经是一个内存视图时才有效(这对我来说意义不大,但似乎是这种情况)。
(self.y[:] = some_array
仅适用于一维数组)
如果你只想制作 self.y
"look at" 一个你只想做的 numpy 数组
self.y = some_array
# in your case:
# self.y = out_image.transpose(1, 0, 2, 3)
很可能这对您的目的来说没问题!
如果你特别热衷于制作副本(可能如果你已经将 C 指针指向 self.y
或类似的东西)那么你必须强制 some_array
成为内存视图.你会做类似
的事情
cdef double[:,:,:,:] temporary_view_of_transpose
# temporary_view_of_transpose now "looks at" the memory allocated by transpose
# no square brackets!
temporary_view_of_transpose = out_image.transpose(1, 0, 2, 3)
# data is copied from temporary_view_of_transpose to self.y
self.y[...] = temporary_view_of_transpose # (remembering that self.y must be the correct shape before this assignment).
我同意看到的错误消息没有帮助!
编辑: 以下是适用于我的最小完整示例(Cython 0.24、Python 3.5.1、Linux - 我不能在 Anaconda 上轻松测试)在这个阶段我不清楚你的代码有什么不同。
# memview.pyx
cimport numpy as np
import numpy as np
cdef class MemviewClass:
cdef double[:,:,:,:] y
def __init__(self):
self.y = np.zeros((2,3,4,5))
def do_something(self):
cdef np.ndarray[np.float64_t,ndim=4] out_image = np.ones((3,2,4,5))
cdef double[:,:,:,:] temp
temp = out_image.transpose(1,0,2,3)
self.y[...] = temp
def print_y(self):
# just to check it gets changed
print(np.asarray(self.y))
和 test_script.py 以证明它有效:
# use pyximport for ease of testing
import numpy
import pyximport; pyximport.install(setup_args=dict(include_dirs=numpy.get_include()))
import memview
a = memview.MemviewClass()
a.print_y() # prints a big array of 0s
a.do_something()
a.print_y() # prints a big array of 1s
我正在尝试使用 python 开发一个小型的卷积神经网络框架。 卷积节点的代码已经可以(缓慢地)运行,我想加快速度。 热点是卷积滤波器在图像上移动的循环。我选择使用 cython 来加速这些循环。
明显的小注释、所有局部变量的 cdef 和删除 boundscheck,几乎减少了 10% 的运行时间。这对我来说很奇怪,根据我在网上阅读的内容,cython 应该已经能够发挥它的魔力了。
不幸的是,代码在 class 中并且严重依赖 class 的属性。我决定将其转换为 cdef class。这意味着所有 class 属性都必须用 cdef 声明。显然 cython 不支持 numpy 数组,所以我将所有 numpy 数组声明为 double[:,:,...]
到目前为止,代码运行良好,所有单元测试都通过了。现在 .pyd 的编译(我在 windows 下工作)仍然有效。但是 运行 代码创建了一个 Typeerror:
TypeError: only length-1 arrays can be converted to Python scalars
这是一些代码。这是我的卷积节点的整个前向方法,可能太多了,不容易阅读。您可能只需要最后一行。那是错误发生的原因:
@cython.boundscheck(False)
@cython.nonecheck(False)
def forward(self):
# im2col: x -> in_cols
# padding
cdef np.ndarray[DTYPE_t, ndim=4] x_padded = np.zeros((self.batch_size, self.in_colors, self.in_width + self.padding*2, self.in_height + self.padding*2))
if self.padding>0:
x_padded[:, :, self.padding:self.in_width+self.padding, self.padding:self.in_height+self.padding] = self.x
else:
x_padded[:]=self.x
# allocating new field
cdef np.ndarray[DTYPE_t, ndim=4] rec_fields = np.empty((self.filter_size**2* self.in_colors, self.batch_size, self.out_width, self.out_height))
# copying receptive fields
cdef int w,h
for w, h in np.ndindex((self.out_width, self.out_height)):
rec_fields[:, :, w, h] = x_padded[:, :, w*self.stride:w*self.stride + self.filter_size, h*self.stride:h*self.stride + self.filter_size] \
.reshape((self.batch_size, self.filter_size**2* self.in_colors)) \
.T
self.in_cols = rec_fields.reshape((self.filter_size**2 * self.in_colors, self.batch_size * self.out_width * self.out_height))
# linear node: in_cols -> out_cols
cdef np.ndarray[DTYPE_t, ndim=2] out_cols=np.dot(self.W,self.in_cols)+self.b
# col2im: out_cols -> out_image -> y
cdef np.ndarray[DTYPE_t, ndim=4] out_image = out_cols.reshape((self.out_colors, self.batch_size, self.out_width, self.out_height))
self.y[:] = out_image.transpose(1, 0, 2, 3)
最后一次转置调用在异常中被标记。我无法解释这个。转置时内存视图的行为是否不同?
更新:
我确定尺寸定义正确。如果维度不匹配,则会产生不同的运行时错误。现在无法检查,但它类似于 "got 4-dim, expected 2-dim"。 我不得不说,cython 的类型系统给我留下了极其深刻的印象。 python 异常中的这种运行时类型信息非常有用。 遗憾的是,它没有解释为什么上面的转置失败。
更新:
数组有些复杂:它们不能被覆盖,只能用作参考。
有点难解释: 神经网络的核心是一个循环,它在网络中的所有节点上连续调用 forward() 方法。
for node in self.nodes:
node.forward()
在此方法中,节点查看其输入数据,进行一些计算并写入其输出。它依赖于输入已经包含正确数据的事实。
为了设置我的网络,我以正确的顺序存储节点。我手动连接它们。
node2.x=node1.y
现在如果我写
self.y[:]= data
在node1的forward方法中,node2自动输入正确。 这需要仔细的编程:必须以正确的顺序调用前向方法并且输出绝不能被覆盖,只能写入。
替代方案是一个巨大的结构,我在其中存储每个节点的输出并传递这些数据。这将创建大量样板代码并弄乱前向和后向传递。
更新:
前面的最后几行现在看起来像这样:
cdef np.ndarray[DTYPE_t, ndim=4] out_image = out_cols.reshape((self.out_colors, self.batch_size, self.out_width, self.out_height))
cdef double[:,:,:,:] temp
temp=out_image.transpose(1,0,2,3)
self.y[...] = temp
对 temp 的分配失败,并显示相同的 TypeError 消息。
self.y[...] = some_array
# or equivalently self.y[:,:,:,:] = some_array
将 some_array
复制到 self.y
中,它必须已经初始化为正确的大小。它似乎也只有在 some_array
已经是一个内存视图时才有效(这对我来说意义不大,但似乎是这种情况)。
(self.y[:] = some_array
仅适用于一维数组)
如果你只想制作 self.y
"look at" 一个你只想做的 numpy 数组
self.y = some_array
# in your case:
# self.y = out_image.transpose(1, 0, 2, 3)
很可能这对您的目的来说没问题!
如果你特别热衷于制作副本(可能如果你已经将 C 指针指向 self.y
或类似的东西)那么你必须强制 some_array
成为内存视图.你会做类似
cdef double[:,:,:,:] temporary_view_of_transpose
# temporary_view_of_transpose now "looks at" the memory allocated by transpose
# no square brackets!
temporary_view_of_transpose = out_image.transpose(1, 0, 2, 3)
# data is copied from temporary_view_of_transpose to self.y
self.y[...] = temporary_view_of_transpose # (remembering that self.y must be the correct shape before this assignment).
我同意看到的错误消息没有帮助!
编辑: 以下是适用于我的最小完整示例(Cython 0.24、Python 3.5.1、Linux - 我不能在 Anaconda 上轻松测试)在这个阶段我不清楚你的代码有什么不同。
# memview.pyx
cimport numpy as np
import numpy as np
cdef class MemviewClass:
cdef double[:,:,:,:] y
def __init__(self):
self.y = np.zeros((2,3,4,5))
def do_something(self):
cdef np.ndarray[np.float64_t,ndim=4] out_image = np.ones((3,2,4,5))
cdef double[:,:,:,:] temp
temp = out_image.transpose(1,0,2,3)
self.y[...] = temp
def print_y(self):
# just to check it gets changed
print(np.asarray(self.y))
和 test_script.py 以证明它有效:
# use pyximport for ease of testing
import numpy
import pyximport; pyximport.install(setup_args=dict(include_dirs=numpy.get_include()))
import memview
a = memview.MemviewClass()
a.print_y() # prints a big array of 0s
a.do_something()
a.print_y() # prints a big array of 1s