Перенос памяти Cython: Typeerror

Я пытаюсь разработать небольшую структуру сверточной нейронной сети с помощью Python. Код для сверточного узла уже работает (медленно), и я хотел бы его ускорить. Горячие точки - это петли, в которых сверточный фильтр перемещается по изображению. Я решил использовать cython, чтобы ускорить эти циклы.

Очевидные небольшие аннотации, cdef для всех локальных переменных и удаление boundscheck, почти на 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-тусклое, ожидалось 2-тусклое». Должен сказать, что меня очень впечатлила система типов Cython. Такая информация о типе среды выполнения в исключении python весьма полезна. К сожалению, это не объясняет, почему вышеуказанное транспонирование не удается.

ОБНОВИТЬ:

Есть некоторая сложность с массивами: их нельзя перезаписывать, их можно использовать только как ссылки.

Сложно объяснить: в основе нейронной сети лежит цикл, который последовательно вызывает метод forward () на всех узлах сети.

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

В этом методе узел просматривает свои входные данные, выполняет некоторые вычисления и записывает в свой выход. Он полагается на тот факт, что ввод уже содержит правильные данные.

Для настройки моей сети я храню узлы в правильном порядке. И подключаю их вручную.

node2.x=node1.y

Теперь, если я напишу

self.y[:]= data

в прямом методе node1, 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.


person lhk    schedule 11.05.2016    source источник
comment
Один из способов избежать этого - не вводить атрибуты класса (например, cdef object y). Таким образом, они могут быть любого типа. Вы можете немного потерять скорость, но вы можете частично ее вернуть, присвоив типизированной локальной переменной перед использованием.   -  person DavidW    schedule 12.05.2016
comment
Но если вы хотите сохранить их как представления памяти, я думаю, вам просто нужно изменить синтаксис присваивания, чтобы он соответствовал количеству измерений: self.y[:,:,:,:] = ...   -  person DavidW    schedule 12.05.2016
comment
Задания имеют правильное количество измерений. Это всегда либо 4 тусклых (размер партии, функции, ширина, высота), либо 2 тусклых (размер_фильтра ** 2 * функции, размер партии ширина высота)   -  person lhk    schedule 12.05.2016
comment
Если размеры неправильные, тоже есть ошибка времени выполнения. но в нем прямо сказано, что размеры не совпадают. У меня тоже была эта ошибка ...   -  person lhk    schedule 12.05.2016
comment
Ой, извините, я неправильно понял то, что вы сказали. Я думал, вы имели в виду декларации. Попробую обновить задание   -  person lhk    schedule 12.05.2016
comment
Интересная идея, поскольку в других местах я использовал [:,:,:,:]. Иногда используется нарезка, и я хотел подчеркнуть, что в этих случаях нарезки нет. Задания с [:,:,:,:] не прошли. Не могу дождаться, чтобы попробовать это   -  person lhk    schedule 12.05.2016
comment
Не уверен, что я понял это правильно - ndarray для назначения нарезки памяти, похоже, не работает. Смотрите ответ ...   -  person DavidW    schedule 12.05.2016


Ответы (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
person DavidW    schedule 12.05.2016
comment
Это странно. Я ожидал, что этот промежуточный шаг будет точно таким же, как присвоение self.y. В конце концов, self.y - это четырехмерное представление памяти. А временное представление - это четырехмерное представление памяти. Почему одно задание не выполняется, а другое работает? Похоже, проблема просто перенесена в другое место. Спасибо за совет, попробую, как только приду домой. - person lhk; 12.05.2016
comment
Как и ожидалось: код теперь не работает при назначении временному_просмотру_транспозиции. То же сообщение об ошибке TypeError: только массивы длины 1 могут быть преобразованы в скаляры Python. Это работает для вас? Я использую python 3.5 с дистрибутивом anaconda и cython 0.24 - person lhk; 13.05.2016
comment
Да - использование последнего блока кода работает как написано (Cython 0.24). Я немного отредактировал его, чтобы было немного понятнее, что происходит. Вы должны четко понимать разницу между mview = something (mview теперь настроен на просмотр памяти, удерживаемой чем-то) и mview[...] = something (данные в памяти, просматриваемой something, копируются в память, просматриваемую mview, и они оба должны быть в памяти) - person DavidW; 13.05.2016
comment
У меня не работает. Я обновил вопрос, указав точный код, который не работает. - person lhk; 13.05.2016
comment
Я добавил полный рабочий пример, который, как мне кажется, охватывает упрощенную версию того, что вы пытаетесь сделать. В данном случае я не знаю, что у вас не работает. - person DavidW; 13.05.2016
comment
Спасибо большое, теперь работает. Я в недоумении, почему не с первого раза. Полагаю, я испортил повторную компиляцию. Это было бы неловко. К сожалению, это решение создает новую ошибку. В прямом проходе в моей сети я должен транспонировать outcols, чтобы получить y. В обратном проходе он точно перевернут. И у memoryviews, похоже, нет метода транспонирования (только .T). И я не могу преобразовать memoryview в ndarray .... Что ж, я принял ваш ответ, так как это идеальный ответ на мой вопрос. - person lhk; 14.05.2016
comment
Я создал вопрос по новой задаче. Вы, кажется, являетесь экспертом по cython, не могли бы вы взглянуть: stackoverflow.com/questions/37226813/ - person lhk; 14.05.2016