Filter layer rebased #2054
Closed
Commits
Jump to file or symbol
Failed to load files and symbols.
| @@ -0,0 +1,128 @@ | ||
| +#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) << | ||
| + "Top.size() should be equal to 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 must have all shapes == 1 (except the first one)"; | ||
| + } | ||
| + 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 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 = top[t]->offset(n); | ||
| + int data_offset_bottom = bottom[t]->offset(indices_to_forward_[n]); | ||
| + 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 = bottom[i]->offset(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 | ||
| + 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 = top[i]->offset(next_to_backward_offset); | ||
| + 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 | ||
| @@ -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 = top[t]->offset(n); | ||
| + int data_offset_bottom = bottom[t]->offset(indices_to_forward_[n]); | ||
| + 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 = top[i]->offset(n); | ||
| + caffe_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 = top[i]->offset(n); | ||
| + if (n != batch_offset) { // this data was not been forwarded | ||
| + caffe_set(dim, Dtype(0), | ||
| + bottom[i]->mutable_gpu_diff() + data_offset_bottom); | ||
| + } else { // this data was been forwarded | ||
| + data_offset_top = top[i]->offset(next_to_backward_offset); | ||
| + 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 |
| @@ -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 |
This should have something like
CHECK(!propagate_down[bottom.size() - 1]) << this->type() << "Layer cannot backpropagate to filter index inputs"(likeSoftmaxWithLossLayerfor labels, etc.). Same comment inBackward_gpu. (This will make the gradient check fail, but you give it bottom ID 0 as an additional argument to only check that input.)