Skip to content

Commit

Permalink
Merge pull request #2054 from mtamburrano/filter_layer_rebased
Browse files Browse the repository at this point in the history
  • Loading branch information
jeffdonahue committed Jun 3, 2015
2 parents b051ce4 + 0ccf336 commit 21b6bf8
Show file tree
Hide file tree
Showing 4 changed files with 388 additions and 0 deletions.
63 changes: 63 additions & 0 deletions include/caffe/common_layers.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,69 @@ class EltwiseLayer : public Layer<Dtype> {
bool stable_prod_grad_;
};

/**
* @brief Takes two+ Blobs, interprets last Blob as a selector and
* filter remaining Blobs accordingly with selector data (0 means that
* the corresponding item has to be filtered, non-zero means that corresponding
* item needs to stay).
*/
template <typename Dtype>
class FilterLayer : public Layer<Dtype> {
public:
explicit FilterLayer(const LayerParameter& param)
: Layer<Dtype>(param) {}
virtual void LayerSetUp(const vector<Blob<Dtype>*>& bottom,
const vector<Blob<Dtype>*>& top);
virtual void Reshape(const vector<Blob<Dtype>*>& bottom,
const vector<Blob<Dtype>*>& top);

virtual inline const char* type() const { return "Filter"; }
virtual inline int MinBottomBlobs() const { return 2; }
virtual inline int MinTopBlobs() const { return 1; }

protected:
/**
* @param bottom input Blob vector (length 2+)
* -# @f$ (N \times C \times H \times W) @f$
* the inputs to be filtered @f$ x_1 @f$
* -# ...
* -# @f$ (N \times C \times H \times W) @f$
* the inputs to be filtered @f$ x_K @f$
* -# @f$ (N \times 1 \times 1 \times 1) @f$
* the selector blob
* @param top output Blob vector (length 1+)
* -# @f$ (S \times C \times H \times W) @f$ ()
* the filtered output @f$ x_1 @f$
* where S is the number of items
* that haven't been filtered
* @f$ (S \times C \times H \times W) @f$
* the filtered output @f$ x_K @f$
* where S is the number of items
* that haven't been filtered
*/
virtual void Forward_cpu(const vector<Blob<Dtype>*>& bottom,
const vector<Blob<Dtype>*>& top);
virtual void Forward_gpu(const vector<Blob<Dtype>*>& bottom,
const vector<Blob<Dtype>*>& top);

/**
* @brief Computes the error gradient w.r.t. the forwarded inputs.
*
* @param top output Blob vector (length 1+), providing the error gradient with
* respect to the outputs
* @param propagate_down see Layer::Backward.
* @param bottom input Blob vector (length 2+), into which the top error
* gradient is copied
*/
virtual void Backward_cpu(const vector<Blob<Dtype>*>& top,
const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& bottom);
virtual void Backward_gpu(const vector<Blob<Dtype>*>& top,
const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& bottom);

bool first_reshape_;
vector<int> indices_to_forward_;
};

/**
* @brief Reshapes the input Blob into flat vectors.
*
Expand Down
127 changes: 127 additions & 0 deletions src/caffe/layers/filter_layer.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
#include <algorithm>
#include <vector>

#include "caffe/layer.hpp"
#include "caffe/util/math_functions.hpp"
#include "caffe/vision_layers.hpp"

namespace caffe {

template <typename Dtype>
void FilterLayer<Dtype>::LayerSetUp(const vector<Blob<Dtype>*>& bottom,
const vector<Blob<Dtype>*>& top) {
CHECK_EQ(top.size(), bottom.size() - 1);
first_reshape_ = true;
}

template <typename Dtype>
void FilterLayer<Dtype>::Reshape(const vector<Blob<Dtype>*>& bottom,
const vector<Blob<Dtype>*>& top) {
// bottom[0...k-1] are the blobs to filter
// bottom[last] is the "selector_blob"
int selector_index = bottom.size() - 1;
for (int i = 1; i < bottom[selector_index]->num_axes(); ++i) {
CHECK_EQ(bottom[selector_index]->shape(i), 1)
<< "Selector blob dimensions must be singletons (1), except the first";
}
for (int i = 0; i < bottom.size() - 1; ++i) {
CHECK_EQ(bottom[selector_index]->shape(0), bottom[i]->shape(0)) <<
"Each bottom should have the same 0th dimension as the selector blob";
}

const Dtype* bottom_data_selector = bottom[selector_index]->cpu_data();
indices_to_forward_.clear();

// look for non-zero elements in bottom[0]. Items of each bottom that
// have the same index as the items in bottom[0] with value == non-zero
// will be forwarded
for (int item_id = 0; item_id < bottom[selector_index]->shape(0); ++item_id) {
// we don't need an offset because item size == 1
const Dtype* tmp_data_selector = bottom_data_selector + item_id;
if (*tmp_data_selector) {
indices_to_forward_.push_back(item_id);
}
}
// only filtered items will be forwarded
int new_tops_num = indices_to_forward_.size();
// init
if (first_reshape_) {
new_tops_num = bottom[0]->shape(0);
first_reshape_ = false;
}
for (int t = 0; t < top.size(); ++t) {
int num_axes = bottom[t]->num_axes();
vector<int> shape_top(num_axes);
shape_top[0] = new_tops_num;
for (int ts = 1; ts < num_axes; ++ts)
shape_top[ts] = bottom[t]->shape(ts);
top[t]->Reshape(shape_top);
}
}

template <typename Dtype>
void FilterLayer<Dtype>::Forward_cpu(const vector<Blob<Dtype>*>& bottom,
const vector<Blob<Dtype>*>& top) {
int new_tops_num = indices_to_forward_.size();
// forward all filtered items for all bottoms but the Selector (bottom[last])
for (int t = 0; t < top.size(); ++t) {
const Dtype* bottom_data = bottom[t]->cpu_data();
Dtype* top_data = top[t]->mutable_cpu_data();
int dim = bottom[t]->count() / bottom[t]->shape(0);
for (int n = 0; n < new_tops_num; ++n) {
int data_offset_top = n * dim;
int data_offset_bottom = indices_to_forward_[n] * bottom[t]->count(1);
caffe_copy(dim, bottom_data + data_offset_bottom,
top_data + data_offset_top);
}
}
}

template <typename Dtype>
void FilterLayer<Dtype>::Backward_cpu(const vector<Blob<Dtype>*>& top,
const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& bottom) {
if (propagate_down[bottom.size() - 1]) {
LOG(FATAL) << this->type()
<< "Layer cannot backpropagate to filter index inputs";
}
for (int i = 0; i < top.size(); i++) {
// bottom[last] is the selector and never needs backpropagation
// so we can iterate over top vector because top.size() == bottom.size() -1
if (propagate_down[i]) {
const int dim = top[i]->count() / top[i]->shape(0);
int next_to_backward_offset = 0;
int batch_offset = 0;
int data_offset_bottom = 0;
int data_offset_top = 0;
for (int n = 0; n < bottom[i]->shape(0); n++) {
data_offset_bottom = n * dim;
if (next_to_backward_offset >= indices_to_forward_.size()) {
// we already visited all items that were been forwarded, so
// just set to zero remaining ones
caffe_set(dim, Dtype(0),
bottom[i]->mutable_cpu_diff() + data_offset_bottom);
} else {
batch_offset = indices_to_forward_[next_to_backward_offset];
if (n != batch_offset) { // this data was not been forwarded
caffe_set(dim, Dtype(0),
bottom[i]->mutable_cpu_diff() + data_offset_bottom);
} else { // this data was been forwarded
data_offset_top = next_to_backward_offset * dim;
next_to_backward_offset++; // point to next forwarded item index
caffe_copy(dim, top[i]->mutable_cpu_diff() + data_offset_top,
bottom[i]->mutable_cpu_diff() + data_offset_bottom);
}
}
}
}
}
}

#ifdef CPU_ONLY
STUB_GPU(FilterLayer);
#endif

INSTANTIATE_CLASS(FilterLayer);
REGISTER_LAYER_CLASS(Filter);

} // namespace caffe
70 changes: 70 additions & 0 deletions src/caffe/layers/filter_layer.cu
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
#include <vector>

#include "caffe/layer.hpp"
#include "caffe/util/math_functions.hpp"
#include "caffe/vision_layers.hpp"

namespace caffe {

template <typename Dtype>
void FilterLayer<Dtype>::Forward_gpu(const vector<Blob<Dtype>*>& bottom,
const vector<Blob<Dtype>*>& top) {
int new_tops_num = indices_to_forward_.size();
// forward all filtered items for all bottoms but the Selector (bottom[last])
for (int t = 0; t < top.size(); ++t) {
const Dtype* bottom_data = bottom[t]->gpu_data();
Dtype* top_data = top[t]->mutable_gpu_data();
int dim = bottom[t]->count() / bottom[t]->shape(0);
for (int n = 0; n < new_tops_num; ++n) {
int data_offset_top = n * dim;
int data_offset_bottom = indices_to_forward_[n] * dim;
caffe_copy(dim, bottom_data + data_offset_bottom,
top_data + data_offset_top);
}
}
}

template <typename Dtype>
void FilterLayer<Dtype>::Backward_gpu(const vector<Blob<Dtype>*>& top,
const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& bottom) {
if (propagate_down[bottom.size() - 1]) {
LOG(FATAL) << this->type()
<< "Layer cannot backpropagate to filter index inputs";
}
for (int i = 0; i < top.size(); ++i) {
// bottom[last] is the selector and never needs backpropagation
// so we can iterate over top vector because top.size() == bottom.size() -1
if (propagate_down[i]) {
const int dim = top[i]->count() / top[i]->shape(0);
int next_to_backward_offset = 0;
int batch_offset = 0;
int data_offset_bottom = 0;
int data_offset_top = 0;
for (int n = 0; n < bottom[i]->shape(0); ++n) {
if (next_to_backward_offset >= indices_to_forward_.size()) {
// we already visited all items that were been forwarded, so
// just set to zero remaining ones
data_offset_bottom = n * dim;
caffe_gpu_set(dim, Dtype(0),
bottom[i]->mutable_gpu_diff() + data_offset_bottom);
} else {
batch_offset = indices_to_forward_[next_to_backward_offset];
data_offset_bottom = n * dim;
if (n != batch_offset) { // this data was not been forwarded
caffe_gpu_set(dim, Dtype(0),
bottom[i]->mutable_gpu_diff() + data_offset_bottom);
} else { // this data was been forwarded
data_offset_top = next_to_backward_offset * dim;
++next_to_backward_offset; // point to next forwarded item index
caffe_copy(dim, top[i]->mutable_gpu_diff() + data_offset_top,
bottom[i]->mutable_gpu_diff() + data_offset_bottom);
}
}
}
}
}
}

INSTANTIATE_LAYER_GPU_FUNCS(FilterLayer);

} // namespace caffe
128 changes: 128 additions & 0 deletions src/caffe/test/test_filter_layer.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
#include <cstring>
#include <limits>
#include <vector>

#include "gtest/gtest.h"

#include "caffe/blob.hpp"
#include "caffe/common.hpp"
#include "caffe/filler.hpp"
#include "caffe/vision_layers.hpp"

#include "caffe/test/test_caffe_main.hpp"
#include "caffe/test/test_gradient_check_util.hpp"

namespace caffe {

template <typename TypeParam>
class FilterLayerTest : public MultiDeviceTest<TypeParam> {
typedef typename TypeParam::Dtype Dtype;

protected:
FilterLayerTest()
: blob_bottom_data_(new Blob<Dtype>(4, 3, 6, 4)),
blob_bottom_labels_(new Blob<Dtype>(4, 1, 1, 1)),
blob_bottom_selector_(new Blob<Dtype>(4, 1, 1, 1)),
blob_top_data_(new Blob<Dtype>()),
blob_top_labels_(new Blob<Dtype>()) {}
virtual void SetUp() {
// fill the values
Caffe::set_random_seed(1890);
FillerParameter filler_param;
GaussianFiller<Dtype> filler(filler_param);
// fill the selector blob
Dtype* bottom_data_selector_ = blob_bottom_selector_->mutable_cpu_data();
bottom_data_selector_[0] = 0;
bottom_data_selector_[1] = 1;
bottom_data_selector_[2] = 1;
bottom_data_selector_[3] = 0;
// fill the other bottom blobs
filler.Fill(blob_bottom_data_);
for (int i = 0; i < blob_bottom_labels_->count(); ++i) {
blob_bottom_labels_->mutable_cpu_data()[i] = caffe_rng_rand() % 5;
}
blob_bottom_vec_.push_back(blob_bottom_data_);
blob_bottom_vec_.push_back(blob_bottom_labels_);
blob_bottom_vec_.push_back(blob_bottom_selector_);
blob_top_vec_.push_back(blob_top_data_);
blob_top_vec_.push_back(blob_top_labels_);
}
virtual ~FilterLayerTest() {
delete blob_bottom_data_;
delete blob_bottom_labels_;
delete blob_bottom_selector_;
delete blob_top_data_;
delete blob_top_labels_;
}
Blob<Dtype>* const blob_bottom_data_;
Blob<Dtype>* const blob_bottom_labels_;
Blob<Dtype>* const blob_bottom_selector_;
// blobs for the top of FilterLayer
Blob<Dtype>* const blob_top_data_;
Blob<Dtype>* const blob_top_labels_;
vector<Blob<Dtype>*> blob_bottom_vec_;
vector<Blob<Dtype>*> blob_top_vec_;
};

TYPED_TEST_CASE(FilterLayerTest, TestDtypesAndDevices);

TYPED_TEST(FilterLayerTest, TestReshape) {
typedef typename TypeParam::Dtype Dtype;
LayerParameter layer_param;
FilterLayer<Dtype> layer(layer_param);
layer.SetUp(this->blob_bottom_vec_, this->blob_top_vec_);
layer.Reshape(this->blob_bottom_vec_, this->blob_top_vec_);
// In the test first and last items should have been filtered
// so we just expect 2 remaining items
EXPECT_EQ(this->blob_top_data_->shape(0), 2);
EXPECT_EQ(this->blob_top_labels_->shape(0), 2);
EXPECT_GT(this->blob_bottom_data_->shape(0),
this->blob_top_data_->shape(0));
EXPECT_GT(this->blob_bottom_labels_->shape(0),
this->blob_top_labels_->shape(0));
for (int i = 1; i < this->blob_bottom_labels_->num_axes(); i++) {
EXPECT_EQ(this->blob_bottom_labels_->shape(i),
this->blob_top_labels_->shape(i));
}
}

TYPED_TEST(FilterLayerTest, TestForward) {
typedef typename TypeParam::Dtype Dtype;
LayerParameter layer_param;
FilterLayer<Dtype> layer(layer_param);
layer.SetUp(this->blob_bottom_vec_, this->blob_top_vec_);
layer.Reshape(this->blob_bottom_vec_, this->blob_top_vec_);
layer.Forward(this->blob_bottom_vec_, this->blob_top_vec_);
EXPECT_EQ(this->blob_top_labels_->data_at(0, 0, 0, 0),
this->blob_bottom_labels_->data_at(1, 0, 0, 0));
EXPECT_EQ(this->blob_top_labels_->data_at(1, 0, 0, 0),
this->blob_bottom_labels_->data_at(2, 0, 0, 0));

int dim = this->blob_top_data_->count() /
this->blob_top_data_->shape(0);
const Dtype* top_data = this->blob_top_data_->cpu_data();
const Dtype* bottom_data = this->blob_bottom_data_->cpu_data();
// selector is 0 1 1 0, so we need to compare bottom(1,c,h,w)
// with top(0,c,h,w) and bottom(2,c,h,w) with top(1,c,h,w)
bottom_data += dim; // bottom(1,c,h,w)
for (size_t n = 0; n < dim; n++)
EXPECT_EQ(top_data[n], bottom_data[n]);

bottom_data += dim; // bottom(2,c,h,w)
top_data += dim; // top(1,c,h,w)
for (size_t n = 0; n < dim; n++)
EXPECT_EQ(top_data[n], bottom_data[n]);
}

TYPED_TEST(FilterLayerTest, TestGradient) {
typedef typename TypeParam::Dtype Dtype;
LayerParameter layer_param;
FilterLayer<Dtype> layer(layer_param);
GradientChecker<Dtype> checker(1e-2, 1e-3);
// check only input 0 (data) because labels and selector
// don't need backpropagation
checker.CheckGradientExhaustive(&layer, this->blob_bottom_vec_,
this->blob_top_vec_, 0);
}

} // namespace caffe

0 comments on commit 21b6bf8

Please sign in to comment.