Python Cython memoryview转置:类型错误

Python Cython memoryview转置:类型错误,python,numpy,neural-network,cython,conv-neural-network,Python,Numpy,Neural Network,Cython,Conv Neural Network,我正试图用python开发一个小型的卷积神经网络框架。 卷积节点的代码已经运行(缓慢),我想加快它的运行速度。 热点是卷积滤波器在图像中移动的环路。我选择使用cython来加速这些循环 显而易见的小注释,所有局部变量的cdef和删除boundscheck,几乎使我的运行时减少了10%。这对我来说似乎很奇怪,根据我在网上看到的,cython应该已经能够发挥它的魔力了 不幸的是,代码在一个类中,并且严重依赖于该类的属性。我决定将其转换为cdef类。这意味着所有类属性都必须用cdef声明。显然cyth

我正试图用python开发一个小型的卷积神经网络框架。 卷积节点的代码已经运行(缓慢),我想加快它的运行速度。 热点是卷积滤波器在图像中移动的环路。我选择使用cython来加速这些循环

显而易见的小注释,所有局部变量的cdef和删除boundscheck,几乎使我的运行时减少了10%。这对我来说似乎很奇怪,根据我在网上看到的,cython应该已经能够发挥它的魔力了

不幸的是,代码在一个类中,并且严重依赖于该类的属性。我决定将其转换为cdef类。这意味着所有类属性都必须用cdef声明。显然cython不支持numpy数组,所以我将所有numpy数组声明为
double[:,:,…]

到目前为止,代码运行良好,所有单元测试都通过了。现在编译到.pyd(我在windows下工作)仍然有效。但是运行代码会产生一个类型错误:

TypeError:只有长度为1的数组才能转换为Python标量

下面是一些代码。这是我的卷积节点的整个正向方法,可能太多,不容易阅读。你可能只需要最后一行。这就是发生错误的原因:

    @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)
对转置的最后一次调用在异常中标记。我无法解释这一点。在转换时,MemoryView的行为是否不同

更新:

我确信尺寸定义正确。如果维度不匹配,则会产生不同的运行时错误。现在无法检查,但它类似于“得到4-dim,预期为2-dim”。 我得说我对cython的类型系统印象深刻。python异常中的这种运行时类型信息非常有用。 遗憾的是,这并不能解释上面的转置失败的原因

更新:

数组有一些复杂之处:它们不能被覆盖,只能用作引用

有点难以解释: 神经网络的核心是一个循环,它在网络中的所有节点上连续调用方法forward()

for node in self.nodes:
    node.forward()
在此方法中,节点查看其输入数据,进行一些计算并写入其输出。它依赖于输入已经包含正确的数据这一事实

为了设置我的网络,我以正确的顺序存储节点。我手动连接它们

node2.x=node1.y
现在如果我写信

self.y[:]= data
在节点1的正向方法中,节点2自动具有正确的输入。 这需要仔细编程:必须以正确的顺序调用forward方法,并且输出不能被覆盖,只能被写入

另一种选择是一个巨大的结构,在这里我存储每个节点的输出并传递这些数据。这将创建大量样板代码,并将前后传球弄得一团糟

更新:

前进中的最后几行现在如下所示:

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_数组
复制到
self.y
,该数组必须已初始化为正确的大小。只有当某个数组已经是memoryview(这对我来说没有太大意义,但似乎是这样)时,它才起作用

self.y[:]=某些_数组
仅适用于1D数组)

如果您只想制作
self.y
“查看”您只想制作的numpy数组

self.y = some_array
# in your case:
# self.y = out_image.transpose(1, 0, 2, 3) 
很有可能这对您的目的来说是好的


如果您特别喜欢制作副本(可能是您使用了指向
self.y
或类似内容的C指针),那么您必须强制
某些数组成为内存视图。你会这样做的

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).

我同意看到的错误消息没有帮助


Edit:以下是对我来说最简单的完整示例(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))
并测试_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

避免这种情况的一个选项是不键入类属性(例如
cdef object y
)。这样,它们可以是任何类型的。您可能会损失一些速度,但可以通过在使用前分配给键入的局部变量来恢复一些速度。但是如果您想将它们作为MemoryView保存,我认为您只需要更改分配语法以匹配维度数:
self.y[,:,:,:,:]=…
分配的维度数正确。它总是4维(批量大小、特征、宽度、高度)或2维(筛选大小**2*特征、批量大小宽度、高度)。如果维度错误,也会出现运行时错误。但是它明确地说维度不匹配。我也犯了那个错误…哦,对不起,我误解了你说的话。我以为你是说声明。我会尝试更新任务,这很奇怪。我本以为这个中间步骤与self.y的任务完全相同。毕竟,self.y是一个4维的记忆视图。时间视图是一个4维的记忆视图。为什么一项作业失败,而另一项作业失败?看来问题已经转移到别的地方了。谢谢你的提示,我一到家就试试。正如预期的那样:代码现在在临时的转换中失败了。相同的错误消息“TypeError:只能将长度为1的数组转换为python标量”。这对你有用吗?我将Python3.5与anaconda发行版和cython 0.24Yes一起使用——使用编写的最后一块代码(cython 0.24)。我对它进行了轻微的编辑,以使它更清楚地了解正在发生的事情。你必须弄清楚
mview=something
mview
现在被设置为查看something所持有的内存)与
mview[…]=something
(内存中的数据查看)之间的区别