From e2685eb08d5b135130f52681956b37dee57117f4 Mon Sep 17 00:00:00 2001 From: Kai Li Date: Sun, 23 Mar 2014 19:02:53 +0800 Subject: [PATCH 1/5] Implement HDF5 save dataset IO utility function --- include/caffe/util/io.hpp | 6 ++++++ src/caffe/util/io.cpp | 26 ++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/include/caffe/util/io.hpp b/include/caffe/util/io.hpp index 7bf78977d6d..e5405727ee4 100644 --- a/include/caffe/util/io.hpp +++ b/include/caffe/util/io.hpp @@ -15,6 +15,8 @@ using std::string; using ::google::protobuf::Message; +#define HDF5_NUM_DIMS 4 + namespace caffe { void ReadProtoFromTextFile(const char* filename, @@ -60,6 +62,10 @@ void hdf5_load_nd_dataset( hid_t file_id, const char* dataset_name_, int min_dim, int max_dim, Blob* blob); +template +void hdf5_save_nd_dataset( + const hid_t file_id, const string dataset_name, const Blob& blob); + } // namespace caffe #endif // CAFFE_UTIL_IO_H_ diff --git a/src/caffe/util/io.cpp b/src/caffe/util/io.cpp index 3ac69f9744e..053d7a40d44 100644 --- a/src/caffe/util/io.cpp +++ b/src/caffe/util/io.cpp @@ -142,4 +142,30 @@ void hdf5_load_nd_dataset(hid_t file_id, const char* dataset_name_, file_id, dataset_name_, blob->mutable_cpu_data()); } +template <> +void hdf5_save_nd_dataset( + const hid_t file_id, const string dataset_name, const Blob& blob) { + hsize_t dims[HDF5_NUM_DIMS]; + dims[0] = blob.num(); + dims[1] = blob.channels(); + dims[2] = blob.height(); + dims[3] = blob.width(); + herr_t status = H5LTmake_dataset_float( + file_id, dataset_name.c_str(), HDF5_NUM_DIMS, dims, blob.cpu_data()); + CHECK_GE(status, 0) << "Failed to make float dataset " << dataset_name; +} + +template <> +void hdf5_save_nd_dataset( + const hid_t file_id, const string dataset_name, const Blob& blob) { + hsize_t dims[HDF5_NUM_DIMS]; + dims[0] = blob.num(); + dims[1] = blob.channels(); + dims[2] = blob.height(); + dims[3] = blob.width(); + herr_t status = H5LTmake_dataset_double( + file_id, dataset_name.c_str(), HDF5_NUM_DIMS, dims, blob.cpu_data()); + CHECK_GE(status, 0) << "Failed to make double dataset " << dataset_name; +} + } // namespace caffe From e2beba9b55711b0511fe849b3fd95618e04f33fc Mon Sep 17 00:00:00 2001 From: Kai Li Date: Sun, 23 Mar 2014 19:03:21 +0800 Subject: [PATCH 2/5] Implement and test HDF5OutputLayer --- include/caffe/vision_layers.hpp | 30 +++++ src/caffe/layers/hdf5_output_layer.cpp | 116 ++++++++++++++++++++ src/caffe/proto/caffe.proto | 6 + src/caffe/test/test_hdf5_output_layer.cpp | 127 ++++++++++++++++++++++ 4 files changed, 279 insertions(+) create mode 100644 src/caffe/layers/hdf5_output_layer.cpp create mode 100644 src/caffe/test/test_hdf5_output_layer.cpp diff --git a/include/caffe/vision_layers.hpp b/include/caffe/vision_layers.hpp index 91a23241902..fb0c0dd6d5c 100644 --- a/include/caffe/vision_layers.hpp +++ b/include/caffe/vision_layers.hpp @@ -15,6 +15,9 @@ #include "caffe/layer.hpp" #include "caffe/proto/caffe.pb.h" +#define HDF5_DATA_DATASET_NAME "data" +#define HDF5_DATA_LABEL_NAME "label" + namespace caffe { @@ -477,6 +480,33 @@ class HDF5DataLayer : public Layer { }; +template +class HDF5OutputLayer : public Layer { + public: + explicit HDF5OutputLayer(const LayerParameter& param); + virtual ~HDF5OutputLayer(); + virtual void SetUp(const vector*>& bottom, + vector*>* top); + inline std::string file_name() const { return file_name_; } + + protected: + virtual void Forward_cpu(const vector*>& bottom, + vector*>* top); + virtual void Forward_gpu(const vector*>& bottom, + vector*>* top); + virtual Dtype Backward_cpu(const vector*>& top, + const bool propagate_down, vector*>* bottom); + virtual Dtype Backward_gpu(const vector*>& top, + const bool propagate_down, vector*>* bottom); + virtual void SaveBlobs(); + + std::string file_name_; + hid_t file_id_; + Blob data_blob_; + Blob label_blob_; +}; + + template class SoftmaxLayer : public Layer { public: diff --git a/src/caffe/layers/hdf5_output_layer.cpp b/src/caffe/layers/hdf5_output_layer.cpp new file mode 100644 index 00000000000..3bf8dc2b290 --- /dev/null +++ b/src/caffe/layers/hdf5_output_layer.cpp @@ -0,0 +1,116 @@ +// Copyright 2014 BVLC and contributors. +/* +Contributors: +- kloudkl@github, 2014. +*/ + +#include + +#include "hdf5.h" +#include "hdf5_hl.h" + +#include "caffe/blob.hpp" +#include "caffe/common.hpp" +#include "caffe/layer.hpp" +#include "caffe/util/io.hpp" +#include "caffe/vision_layers.hpp" + +namespace caffe { +using std::vector; + +template +HDF5OutputLayer::HDF5OutputLayer(const LayerParameter& param) + : Layer(param), + file_name_(param.hdf5_output_param().file_name()) { + /* create a HDF5 file */ + file_id_ = H5Fcreate(file_name_.c_str(), H5F_ACC_TRUNC, H5P_DEFAULT, + H5P_DEFAULT); + CHECK_GE(file_id_, 0) << "Failed to open HDF5 file" << file_name_; +} + +template +HDF5OutputLayer::~HDF5OutputLayer() { + herr_t status = H5Fclose(file_id_); + CHECK_GE(status, 0) << "Failed to close HDF5 file " << file_name_; +} + +template +void HDF5OutputLayer::SaveBlobs() { + // TODO: no limit on the number of blobs + LOG(INFO) << "Saving HDF5 file" << file_name_; + CHECK_EQ(data_blob_.num(), label_blob_.num()) << + "data blob and label blob must have the same batch size"; + hdf5_save_nd_dataset(file_id_, HDF5_DATA_DATASET_NAME, data_blob_); + hdf5_save_nd_dataset(file_id_, HDF5_DATA_LABEL_NAME, label_blob_); + LOG(INFO) << "Successfully saved " << data_blob_.num() << " rows"; +} + +template +void HDF5OutputLayer::SetUp(const vector*>& bottom, + vector*>* top) { + // TODO: no limit on the number of blobs + CHECK_EQ(bottom.size(), 2) << "HDF5OutputLayer takes two blobs as input."; + CHECK_EQ(top->size(), 0) << "HDF5OutputLayer takes no output blobs."; +} + +template +void HDF5OutputLayer::Forward_cpu(const vector*>& bottom, + vector*>* top) { + CHECK_GE(bottom.size(), 2); + CHECK_EQ(bottom[0]->num(), bottom[1]->num()); + data_blob_.Reshape(bottom[0]->num(), bottom[0]->channels(), + bottom[0]->height(), bottom[0]->width()); + label_blob_.Reshape(bottom[1]->num(), bottom[1]->channels(), + bottom[1]->height(), bottom[1]->width()); + const int data_datum_dim = bottom[0]->count() / bottom[0]->num(); + const int label_datum_dim = bottom[1]->count() / bottom[1]->num(); + + for (int i = 0; i < bottom[0]->num(); ++i) { + memcpy(&data_blob_.mutable_cpu_data()[i * data_datum_dim], + &bottom[0]->cpu_data()[i * data_datum_dim], + sizeof(Dtype) * data_datum_dim); + memcpy(&label_blob_.mutable_cpu_data()[i * label_datum_dim], + &bottom[1]->cpu_data()[i * label_datum_dim], + sizeof(Dtype) * label_datum_dim); + } + SaveBlobs(); +} + +template +void HDF5OutputLayer::Forward_gpu(const vector*>& bottom, + vector*>* top) { + CHECK_GE(bottom.size(), 2); + CHECK_EQ(bottom[0]->num(), bottom[1]->num()); + data_blob_.Reshape(bottom[0]->num(), bottom[0]->channels(), + bottom[0]->height(), bottom[0]->width()); + label_blob_.Reshape(bottom[1]->num(), bottom[1]->channels(), + bottom[1]->height(), bottom[1]->width()); + const int data_datum_dim = bottom[0]->count() / bottom[0]->num(); + const int label_datum_dim = bottom[1]->count() / bottom[1]->num(); + + for (int i = 0; i < bottom[0]->num(); ++i) { + CUDA_CHECK(cudaMemcpy(&data_blob_.mutable_cpu_data()[i * data_datum_dim], + &bottom[0]->gpu_data()[i * data_datum_dim], + sizeof(Dtype) * data_datum_dim, cudaMemcpyDeviceToHost)); + CUDA_CHECK(cudaMemcpy(&label_blob_.mutable_cpu_data()[i * label_datum_dim], + &bottom[1]->gpu_data()[i * label_datum_dim], + sizeof(Dtype) * label_datum_dim, cudaMemcpyDeviceToHost)); + } + SaveBlobs(); +} + +template +Dtype HDF5OutputLayer::Backward_cpu(const vector*>& top, + const bool propagate_down, vector*>* bottom) { + return Dtype(0.); +} + +template +Dtype HDF5OutputLayer::Backward_gpu(const vector*>& top, + const bool propagate_down, vector*>* bottom) { + return Dtype(0.); +} + +INSTANTIATE_CLASS(HDF5OutputLayer); + +} // namespace caffe diff --git a/src/caffe/proto/caffe.proto b/src/caffe/proto/caffe.proto index 5a73a4496e0..362764a71aa 100644 --- a/src/caffe/proto/caffe.proto +++ b/src/caffe/proto/caffe.proto @@ -125,6 +125,12 @@ message LayerParameter { // the other dimensions must be the same for all the bottom blobs. // By default it will concatenate blobs along the channels dimension. optional uint32 concat_dim = 65 [default = 1]; + + optional HDF5OutputParameter hdf5_output_param = 1001; +} + +message HDF5OutputParameter { + optional string file_name = 1; } message LayerConnection { diff --git a/src/caffe/test/test_hdf5_output_layer.cpp b/src/caffe/test/test_hdf5_output_layer.cpp new file mode 100644 index 00000000000..3cbfb3f35a8 --- /dev/null +++ b/src/caffe/test/test_hdf5_output_layer.cpp @@ -0,0 +1,127 @@ +// Copyright 2014 kloudkl@github + +#include +#include +#include + +#include "gtest/gtest.h" +#include "caffe/blob.hpp" +#include "caffe/common.hpp" +#include "caffe/util/io.hpp" +#include "caffe/vision_layers.hpp" +#include "caffe/proto/caffe.pb.h" +#include "caffe/test/test_caffe_main.hpp" + +namespace caffe { +using std::string; +using std::vector; + +extern cudaDeviceProp CAFFE_TEST_CUDA_PROP; + +template +class HDF5OutputLayerTest : public ::testing::Test { + protected: + HDF5OutputLayerTest() + : output_file_name_("/tmp/test_hdf5_output_layer-sample_data.hdf5"), + input_file_name_("src/caffe/test/test_data/sample_data.h5"), + blob_data_(new Blob()), + blob_label_(new Blob()), + num_(5), + channels_(8), + height_(5), + width_(5) { + } + virtual void SetUp() { + } + + virtual ~HDF5OutputLayerTest() { + delete blob_data_; + delete blob_label_; + } + + void CheckBlobEqual(const Blob& b1, const Blob& b2); + + string output_file_name_; + string input_file_name_; + Blob* const blob_data_; + Blob* const blob_label_; + vector*> blob_bottom_vec_; + vector*> blob_top_vec_; + int num_; + int channels_; + int height_; + int width_; +}; + +template +void HDF5OutputLayerTest::CheckBlobEqual( + const Blob& b1, const Blob& b2) { + EXPECT_EQ(b1.num(), b2.num()); + EXPECT_EQ(b1.channels(), b2.channels()); + EXPECT_EQ(b1.height(), b2.height()); + EXPECT_EQ(b1.width(), b2.width()); + for (int n = 0; n < b1.num(); ++n) { + for (int c = 0; c < b1.channels(); ++c) { + for (int h = 0; h < b1.height(); ++h) { + for (int w = 0; w < b1.width(); ++w) { + EXPECT_EQ(b1.data_at(n, c, h, w), b1.data_at(n, c, h, w)); + } + } + } + } +} + +typedef ::testing::Types Dtypes; +TYPED_TEST_CASE(HDF5OutputLayerTest, Dtypes); + +TYPED_TEST(HDF5OutputLayerTest, TestForward) { + LOG(INFO) << "Loading HDF5 file " << this->input_file_name_; + hid_t file_id = H5Fopen(this->input_file_name_.c_str(), H5F_ACC_RDONLY, + H5P_DEFAULT); + ASSERT_GE(file_id, 0) << "Failed to open HDF5 file" << + this->input_file_name_; + hdf5_load_nd_dataset(file_id, HDF5_DATA_DATASET_NAME, 0, 4, + this->blob_data_); + hdf5_load_nd_dataset(file_id, HDF5_DATA_LABEL_NAME, 0, 4, + this->blob_label_); + herr_t status = H5Fclose(file_id); + EXPECT_GE(status, 0) << "Failed to close HDF5 file " << + this->input_file_name_; + this->blob_bottom_vec_.push_back(this->blob_data_); + this->blob_bottom_vec_.push_back(this->blob_label_); + + Caffe::Brew modes[] = { Caffe::CPU, Caffe::GPU }; + for (int m = 0; m < 2; ++m) { + Caffe::set_mode(modes[m]); + LayerParameter param; + param.mutable_hdf5_output_param()->set_file_name(this->output_file_name_); + // This code block ensures that the layer is deconstructed and + // the output hdf5 file is closed. + { + HDF5OutputLayer layer(param); + EXPECT_EQ(layer.file_name(), this->output_file_name_); + layer.SetUp(this->blob_bottom_vec_, &this->blob_top_vec_); + layer.Forward(this->blob_bottom_vec_, &this->blob_top_vec_); + } + hid_t file_id = H5Fopen(this->output_file_name_.c_str(), H5F_ACC_RDONLY, + H5P_DEFAULT); + ASSERT_GE(file_id, 0) << "Failed to open HDF5 file" << + this->input_file_name_; + + Blob* blob_data = new Blob(); + hdf5_load_nd_dataset(file_id, HDF5_DATA_DATASET_NAME, 0, 4, + blob_data); + this->CheckBlobEqual(*(this->blob_data_), *blob_data); + + Blob* blob_label = new Blob(); + hdf5_load_nd_dataset(file_id, HDF5_DATA_LABEL_NAME, 0, 4, + blob_label); + this->CheckBlobEqual(*(this->blob_label_), *blob_label); + + herr_t status = H5Fclose(file_id); + EXPECT_GE(status, 0) << "Failed to close HDF5 file " << + this->output_file_name_; + } +} + +} // namespace caffe From dd9e05bff4ca382b13afe3a2a8faf87fb5932480 Mon Sep 17 00:00:00 2001 From: Kai Li Date: Sun, 23 Mar 2014 19:05:05 +0800 Subject: [PATCH 3/5] Add HDF5OutputLayer to the layer factory --- src/caffe/layer_factory.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/caffe/layer_factory.cpp b/src/caffe/layer_factory.cpp index 54e90d21034..32a5f7f59ec 100644 --- a/src/caffe/layer_factory.cpp +++ b/src/caffe/layer_factory.cpp @@ -37,6 +37,8 @@ Layer* GetLayer(const LayerParameter& param) { return new FlattenLayer(param); } else if (type == "hdf5_data") { return new HDF5DataLayer(param); + } else if (type == "hdf5_output") { + return new HDF5OutputLayer(param); } else if (type == "images") { return new ImagesLayer(param); } else if (type == "im2col") { From 2b28b2090d9e47b47ee083a5e46467761cb8941c Mon Sep 17 00:00:00 2001 From: Kai Li Date: Sun, 23 Mar 2014 20:30:52 +0800 Subject: [PATCH 4/5] Rebase and change the HDF5OutputLayer::Forward/Backward signatures --- include/caffe/vision_layers.hpp | 8 ++++---- src/caffe/layers/hdf5_output_layer.cpp | 14 ++++++++------ 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/include/caffe/vision_layers.hpp b/include/caffe/vision_layers.hpp index fb0c0dd6d5c..74b27ccfc15 100644 --- a/include/caffe/vision_layers.hpp +++ b/include/caffe/vision_layers.hpp @@ -490,13 +490,13 @@ class HDF5OutputLayer : public Layer { inline std::string file_name() const { return file_name_; } protected: - virtual void Forward_cpu(const vector*>& bottom, + virtual Dtype Forward_cpu(const vector*>& bottom, vector*>* top); - virtual void Forward_gpu(const vector*>& bottom, + virtual Dtype Forward_gpu(const vector*>& bottom, vector*>* top); - virtual Dtype Backward_cpu(const vector*>& top, + virtual void Backward_cpu(const vector*>& top, const bool propagate_down, vector*>* bottom); - virtual Dtype Backward_gpu(const vector*>& top, + virtual void Backward_gpu(const vector*>& top, const bool propagate_down, vector*>* bottom); virtual void SaveBlobs(); diff --git a/src/caffe/layers/hdf5_output_layer.cpp b/src/caffe/layers/hdf5_output_layer.cpp index 3bf8dc2b290..419c6c8317f 100644 --- a/src/caffe/layers/hdf5_output_layer.cpp +++ b/src/caffe/layers/hdf5_output_layer.cpp @@ -54,7 +54,7 @@ void HDF5OutputLayer::SetUp(const vector*>& bottom, } template -void HDF5OutputLayer::Forward_cpu(const vector*>& bottom, +Dtype HDF5OutputLayer::Forward_cpu(const vector*>& bottom, vector*>* top) { CHECK_GE(bottom.size(), 2); CHECK_EQ(bottom[0]->num(), bottom[1]->num()); @@ -74,10 +74,11 @@ void HDF5OutputLayer::Forward_cpu(const vector*>& bottom, sizeof(Dtype) * label_datum_dim); } SaveBlobs(); + return Dtype(0.); } template -void HDF5OutputLayer::Forward_gpu(const vector*>& bottom, +Dtype HDF5OutputLayer::Forward_gpu(const vector*>& bottom, vector*>* top) { CHECK_GE(bottom.size(), 2); CHECK_EQ(bottom[0]->num(), bottom[1]->num()); @@ -97,18 +98,19 @@ void HDF5OutputLayer::Forward_gpu(const vector*>& bottom, sizeof(Dtype) * label_datum_dim, cudaMemcpyDeviceToHost)); } SaveBlobs(); + return Dtype(0.); } template -Dtype HDF5OutputLayer::Backward_cpu(const vector*>& top, +void HDF5OutputLayer::Backward_cpu(const vector*>& top, const bool propagate_down, vector*>* bottom) { - return Dtype(0.); + return; } template -Dtype HDF5OutputLayer::Backward_gpu(const vector*>& top, +void HDF5OutputLayer::Backward_gpu(const vector*>& top, const bool propagate_down, vector*>* bottom) { - return Dtype(0.); + return; } INSTANTIATE_CLASS(HDF5OutputLayer); From ebf90c31c46ac5331164f9906504e93608912446 Mon Sep 17 00:00:00 2001 From: Kai Li Date: Mon, 24 Mar 2014 09:37:21 +0800 Subject: [PATCH 5/5] Separate HDF5OutputLayer::Forward_gpu/Backward_gpu into cu file --- src/caffe/layers/hdf5_output_layer.cpp | 30 --------------- src/caffe/layers/hdf5_output_layer.cu | 53 ++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 30 deletions(-) create mode 100644 src/caffe/layers/hdf5_output_layer.cu diff --git a/src/caffe/layers/hdf5_output_layer.cpp b/src/caffe/layers/hdf5_output_layer.cpp index 419c6c8317f..f8433c16680 100644 --- a/src/caffe/layers/hdf5_output_layer.cpp +++ b/src/caffe/layers/hdf5_output_layer.cpp @@ -77,42 +77,12 @@ Dtype HDF5OutputLayer::Forward_cpu(const vector*>& bottom, return Dtype(0.); } -template -Dtype HDF5OutputLayer::Forward_gpu(const vector*>& bottom, - vector*>* top) { - CHECK_GE(bottom.size(), 2); - CHECK_EQ(bottom[0]->num(), bottom[1]->num()); - data_blob_.Reshape(bottom[0]->num(), bottom[0]->channels(), - bottom[0]->height(), bottom[0]->width()); - label_blob_.Reshape(bottom[1]->num(), bottom[1]->channels(), - bottom[1]->height(), bottom[1]->width()); - const int data_datum_dim = bottom[0]->count() / bottom[0]->num(); - const int label_datum_dim = bottom[1]->count() / bottom[1]->num(); - - for (int i = 0; i < bottom[0]->num(); ++i) { - CUDA_CHECK(cudaMemcpy(&data_blob_.mutable_cpu_data()[i * data_datum_dim], - &bottom[0]->gpu_data()[i * data_datum_dim], - sizeof(Dtype) * data_datum_dim, cudaMemcpyDeviceToHost)); - CUDA_CHECK(cudaMemcpy(&label_blob_.mutable_cpu_data()[i * label_datum_dim], - &bottom[1]->gpu_data()[i * label_datum_dim], - sizeof(Dtype) * label_datum_dim, cudaMemcpyDeviceToHost)); - } - SaveBlobs(); - return Dtype(0.); -} - template void HDF5OutputLayer::Backward_cpu(const vector*>& top, const bool propagate_down, vector*>* bottom) { return; } -template -void HDF5OutputLayer::Backward_gpu(const vector*>& top, - const bool propagate_down, vector*>* bottom) { - return; -} - INSTANTIATE_CLASS(HDF5OutputLayer); } // namespace caffe diff --git a/src/caffe/layers/hdf5_output_layer.cu b/src/caffe/layers/hdf5_output_layer.cu new file mode 100644 index 00000000000..b5d10888653 --- /dev/null +++ b/src/caffe/layers/hdf5_output_layer.cu @@ -0,0 +1,53 @@ +// Copyright 2014 BVLC and contributors. +/* +Contributors: +- kloudkl@github, 2014. +*/ + +#include + +#include "hdf5.h" +#include "hdf5_hl.h" + +#include "caffe/blob.hpp" +#include "caffe/common.hpp" +#include "caffe/layer.hpp" +#include "caffe/util/io.hpp" +#include "caffe/vision_layers.hpp" + +namespace caffe { +using std::vector; + +template +Dtype HDF5OutputLayer::Forward_gpu(const vector*>& bottom, + vector*>* top) { + CHECK_GE(bottom.size(), 2); + CHECK_EQ(bottom[0]->num(), bottom[1]->num()); + data_blob_.Reshape(bottom[0]->num(), bottom[0]->channels(), + bottom[0]->height(), bottom[0]->width()); + label_blob_.Reshape(bottom[1]->num(), bottom[1]->channels(), + bottom[1]->height(), bottom[1]->width()); + const int data_datum_dim = bottom[0]->count() / bottom[0]->num(); + const int label_datum_dim = bottom[1]->count() / bottom[1]->num(); + + for (int i = 0; i < bottom[0]->num(); ++i) { + CUDA_CHECK(cudaMemcpy(&data_blob_.mutable_cpu_data()[i * data_datum_dim], + &bottom[0]->gpu_data()[i * data_datum_dim], + sizeof(Dtype) * data_datum_dim, cudaMemcpyDeviceToHost)); + CUDA_CHECK(cudaMemcpy(&label_blob_.mutable_cpu_data()[i * label_datum_dim], + &bottom[1]->gpu_data()[i * label_datum_dim], + sizeof(Dtype) * label_datum_dim, cudaMemcpyDeviceToHost)); + } + SaveBlobs(); + return Dtype(0.); +} + +template +void HDF5OutputLayer::Backward_gpu(const vector*>& top, + const bool propagate_down, vector*>* bottom) { + return; +} + +INSTANTIATE_CLASS(HDF5OutputLayer); + +} // namespace caffe