【问题标题】:Cython memoryview transpose: TypeerrorCython memoryview 转置:Typeerror
【发布时间】:2016-09-07 12:43:05
【问题描述】:

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

明显的小注释,所有局部变量的 cdef 和删除边界检查,几乎减少了 10% 的运行时间。这对我来说似乎很奇怪,根据我在网上阅读的内容,cython 应该已经能够发挥它的魔力了。

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

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

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)

最后一次转置调用被标记在异常中。我无法解释这一点。转置时内存视图的行为是否有所不同?

更新:

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

更新:

数组有一些复杂性:它们不能被覆盖,只能用作参考。

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

for node in self.nodes:
    node.forward()

在此方法中,节点查看其输入数据,进行一些计算并写入其输出。它依赖于输入已经包含正确数据这一事实。

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

node2.x=node1.y

现在如果我写

self.y[:]= data

在node1的forward方法中,node2自动有正确的输入。 这需要仔细编程:必须以正确的顺序调用 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 消息。

【问题讨论】:

  • 避免这种情况的一个选择是不输入类属性(例如cdef object y)。这样它们就可以是任何类型的。您可能会损失少量速度,但可以通过在使用前分配给类型化的局部变量来恢复部分速度。
  • 但是如果你想把它们保留为内存视图,我认为你只需要改变你的赋值语法来匹配维度的数量:self.y[:,:,:,:] = ...
  • 分配的维数正确。它总是 4 dim (batch_size, features, width, height) 或 2 dim (filter_size**2 * features, batch_sizewidthheight)
  • 如果尺寸错误,也存在运行时错误。但它明确表示尺寸不匹配。我也有这个错误......
  • 哦,对不起,我误解了你的意思。我以为你的意思是声明。我会尝试更新作业

标签: python numpy neural-network cython conv-neural-network


【解决方案1】:
self.y[...] = some_array
# or equivalently self.y[:,:,:,:] = some_array

some_array 复制到self.y,它必须已经初始化为正确的大小。它似乎也只有在 some_array 已经是一个内存视图时才有效(这对我来说没有多大意义,但似乎就是这种情况)。

self.y[:] = some_array 仅适用于一维数组)

如果你只想让self.y“看看”一个你只想做的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

【讨论】:

  • 这很奇怪。我本来希望这个中间步骤与 self.y 的分配完全相同。毕竟 self.y 是 4 个维度的记忆视图。而temporaryview是4维的memoryview。为什么一项任务失败而另一项工作?似乎问题只是转移到了其他地方。谢谢你的提示,我一回家就试试。
  • 正如预期的那样:代码现在在分配给 temporary_view_of_transpose 时失败。相同的错误消息“TypeError:只有长度为 1 的数组可以转换为 python 标量”。那对你有用吗 ?我正在使用带有 anaconda 发行版和 cython 0.24 的 python 3.5
  • 是的 - 使用最后的代码块按书面方式工作(Cython 0.24)。我对它进行了一些编辑,以使其更清楚正在发生的事情。您必须清楚mview = somethingmview 现在设置为查看某物持有的内存)与mview[...] = somethingsomething 查看的内存中的数据被复制到mview查看的内存,都必须是memoryviews)
  • 对我不起作用。我已经用失败的确切代码更新了问题。
  • 我添加了一个完整的工作示例,我认为它涵盖了您正在尝试做的事情的简化版本。在这种情况下,我不知道什么不适合你。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-05-18
  • 2012-09-29
  • 1970-01-01
  • 1970-01-01
  • 2019-06-21
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多