Как сверлить две капли в кофе

В кофе, слой convolution берет один нижний блок и свертывает его с помощью изученных фильтров (которые инициализируются с использованием весового типа - "Xavier", "MSRA" и т.д.). Однако, мой вопрос заключается в том, можем ли мы просто сверлить две нижние капли и создать верхний капля. Какой был бы самый элегантный способ сделать это? Цель этого: один из нижних blob будет data, а другой будет динамическим фильтром (изменяющимся в зависимости от data), созданным предыдущими слоями (я пытаюсь реализовать динамическая свертка).

Моя попытка:

Один из способов, который пришел мне на ум, состоял в том, чтобы изменить filler.hpp и назначить нижнюю blob как матрицу filler (вместо "Xavier", "MSRA" и т.д.). Потом я подумал, что слой свертки поднимется оттуда. Мы можем установить lr = 0, чтобы указать, что вес, инициализированный нашим пользовательским наполнителем, не должен изменяться. Однако, посмотрев исходный код, я все еще не знаю, как это сделать. С другой стороны, я не хочу нарушать работу кофе. Я все еще хочу, чтобы conv-слои функционировали нормально, если я хочу их.

Очевидно, что более утомительным способом является использование комбинации слоев Slice, tile и/или Scale, чтобы буквально реализовать свертку. Я думаю, что это сработает, но это будет беспорядочно. Любые другие мысли?

Изменить 1:

Я написал новый слой, изменив слой свертки с кофе. В частности, в src/caffe/layers/conv_layer.cpp, в строке 27, он принимает вес, определенный filler, и свертывает его с нижним блобом. Поэтому вместо того, чтобы заполнять этот blob из filler, я изменил слой таким образом, что теперь он принимает два нижних. Одно из дна непосредственно назначается наполнителю. Теперь мне пришлось внести некоторые другие изменения, например:

  • weight blob имеет одинаковое значение для всех образцов. Здесь он будет иметь другое значение для разных образцов. Поэтому я изменил строку 32 из:
this->forward_cpu_gemm(
    bottom_data + n * this->bottom_dim_, 
    weight, 
    top_data + n * this->top_dim_);

в

this->forward_cpu_gemm(
    bottom_data + n * bottom[1]->count(1),
    bottom[0]->cpu_data() + n * bottom[0]->count(1), 
    top_data + n * this->top_dim_);

Чтобы сделать вещи проще, я предположил, что нет никакого смещения, участвующего в этом вопросе, шаг всегда 1, заполнение всегда равно 0, группа всегда будет 1 и т.д. Однако, когда я тестировал передний проход, это дало мне странный ответ (с простым ядром свертки = np.ones((1,1,3,3))). Скорости обучения были установлены равными нулю для этого ядра, так что он не изменяется. Однако я не могу получить правильный ответ. Любые предложения будут оценены.

Пожалуйста, не предлагайте решения с использованием существующих слоев, таких как Slice, Eltwise, Crop. Я уже реализовал - он работает, но он невероятно сложный и неэффективный.

Ответ 1

Я думаю, что вы на правильном пути в целом.

Для "странных" результатов свертки я думаю, что ошибка, возможно, следующая:

Рассмотрим двумерную свертку

и пусть bottom[1] форма (num, channels, height, width),

так как свертка в caffe выполняется как умножение на две матрицы, weight (представляющие ядра свертки) и col_buffer (реорганизованные из свернутых данных), а weight имеет строки num_out и channels / this->group_ * kernel_h * kernel_w, col_buffer имеет столбцы channels / this->group_ * kernel_h * kernel_w и столбцы height_out * width_out, так что weight blob динамического уровня свертки, форма bottom[0] должна быть лучше (num, num_out, channels/group, kernel_h, kernel_w) для удовлетворения

bottom[0]->count(1) == num_out * channels / this->group_ * kernel_h * kernel_w

в котором num_out - это число динамических карт выходных характеристик свертки.

Это означает, что для выполнения функции свертки

this->forward_cpu_gemm(bottom_data + n * bottom[1]->count(1) 
                     , bottom[0]->cpu_data() + n * bottom[0]->count(1)
                     , top_data + n * this->top_dim_);

работайте правильно, вы должны убедиться, что

bottom[0]->shape(0) == bottom[1]->shape(0) == num
bottom[0]->count(1) == num_out * channels / this->group_ * kernel_h * kernel_w

Таким образом, возможно, что простое сверточное ядро ​​4-мерного np.ones((1,1,3,3)), которое вы использовали, может не удовлетворять указанному выше условию и приводить к неправильным результатам свертки.

Надеюсь, что это ясно и поможет вам.

########## Обновление 1, 10 октября 2016 года, время в Пекине ##########

Я добавляю динамический уровень свертки здесь, но без unit test. Этот слой не нарушает рабочий процесс caffe и только изменяет некоторые частные члены класса BaseConvolution для защиты.

Используемые файлы:

include/caffe/layers/dyn_conv_layer.hpp,base_conv_layer.hpp
src/caffe/layers/dyn_conv_layer.cpp(cu)

Он растет почти одинаково со слоем свертки в caffe, и различия в основном следующие:

  • Отмените функцию LayerSetUp(), чтобы инициализировать this->kernel_dim_, this->weight_offset_ и т.д. правильно для свертки и игнорировать инициализацию this->blobs_, используемую слоем Convolution, чтобы содержать вес и смещение;
  • Отмените функцию Reshape(), чтобы проверить, что bottom[1] как контейнер ядра имеет правильную форму для свертки.

Поскольку у меня нет времени на его тестирование, могут быть ошибки, и я буду очень рад видеть ваши отзывы.

########## Обновление 2, 12 октября 2016 года, по пекинскому времени ##########

Я обновил тестовый пример динамическая свертка только сейчас. Причастный файл src/caffe/test/test_dyn_convolution_layer.cpp. Кажется, что это нормально, но, возможно, нужно провести более тщательные тесты.

Вы можете создать этот caffe cd $CAFFE_ROOT/build && ccmake .., cmake -DBUILD_only_tests="dyn_convolution_layer" .. и make runtest, чтобы проверить его.