diff --git a/include/caffe/util/math_functions.hpp b/include/caffe/util/math_functions.hpp index 62290123b8b..887c8f2e6e0 100644 --- a/include/caffe/util/math_functions.hpp +++ b/include/caffe/util/math_functions.hpp @@ -105,6 +105,8 @@ void caffe_powx(const int n, const Dtype* a, const Dtype b, Dtype* y); template void caffe_gpu_powx(const int n, const Dtype* a, const Dtype b, Dtype* y); +unsigned int caffe_rng_rand(); + template Dtype caffe_nextafter(const Dtype b); diff --git a/include/caffe/vision_layers.hpp b/include/caffe/vision_layers.hpp index 6084c27d6f6..991e3381079 100644 --- a/include/caffe/vision_layers.hpp +++ b/include/caffe/vision_layers.hpp @@ -286,6 +286,11 @@ class DataLayer : public Layer { virtual void Backward_gpu(const vector*>& top, const bool propagate_down, vector*>* bottom) { return; } + virtual void CreatePrefetchThread(); + virtual void JoinPrefetchThread(); + virtual unsigned int PrefetchRand(); + + shared_ptr prefetch_rng_; shared_ptr db_; shared_ptr iter_; int datum_channels_; @@ -467,6 +472,13 @@ class ImageDataLayer : public Layer { virtual void Backward_gpu(const vector*>& top, const bool propagate_down, vector*>* bottom) { return; } + virtual void ShuffleImages(); + + virtual void CreatePrefetchThread(); + virtual void JoinPrefetchThread(); + virtual unsigned int PrefetchRand(); + + shared_ptr prefetch_rng_; vector > lines_; int lines_id_; int datum_channels_; @@ -743,6 +755,11 @@ class WindowDataLayer : public Layer { virtual void Backward_gpu(const vector*>& top, const bool propagate_down, vector*>* bottom) { return; } + virtual void CreatePrefetchThread(); + virtual void JoinPrefetchThread(); + virtual unsigned int PrefetchRand(); + + shared_ptr prefetch_rng_; pthread_t thread_; shared_ptr > prefetch_data_; shared_ptr > prefetch_label_; diff --git a/matlab/caffe/matcaffe.cpp b/matlab/caffe/matcaffe.cpp index 5059d228eda..21f51e83994 100644 --- a/matlab/caffe/matcaffe.cpp +++ b/matlab/caffe/matcaffe.cpp @@ -272,7 +272,7 @@ static void init(MEX_ARGS) { mxFree(param_file); mxFree(model_file); - init_key = random(); + init_key = random(); // NOLINT(caffe/random_fn) if (nlhs == 1) { plhs[0] = mxCreateDoubleScalar(init_key); diff --git a/scripts/cpp_lint.py b/scripts/cpp_lint.py index 6aa1544742a..76eee4b2dbe 100755 --- a/scripts/cpp_lint.py +++ b/scripts/cpp_lint.py @@ -154,6 +154,7 @@ 'build/namespaces', 'build/printf_format', 'build/storage_class', + 'caffe/random_fn', 'legal/copyright', 'readability/alt_tokens', 'readability/braces', @@ -1560,6 +1561,38 @@ def CheckForMultilineCommentsAndStrings(filename, clean_lines, linenum, error): 'Use C++11 raw strings or concatenation instead.') +c_random_function_list = ( + 'rand(', + 'rand_r(', + 'random(', + ) + +def CheckCaffeRandom(filename, clean_lines, linenum, error): + """Checks for calls to C random functions (rand, rand_r, random, ...). + + Caffe code should (almost) always use the caffe_rng_* functions rather + than these, as the internal state of these C functions is independent of the + native Caffe RNG system which should produce deterministic results for a + fixed Caffe seed set using Caffe::set_random_seed(...). + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + for function in c_random_function_list: + ix = line.find(function) + # Comparisons made explicit for clarity -- pylint: disable=g-explicit-bool-comparison + if ix >= 0 and (ix == 0 or (not line[ix - 1].isalnum() and + line[ix - 1] not in ('_', '.', '>'))): + error(filename, linenum, 'caffe/random_fn', 2, + 'Use caffe_rng_rand() (or other caffe_rng_* function) instead of ' + + function + + ') to ensure results are deterministic for a fixed Caffe seed.') + + threading_list = ( ('asctime(', 'asctime_r('), ('ctime(', 'ctime_r('), @@ -1570,7 +1603,6 @@ def CheckForMultilineCommentsAndStrings(filename, clean_lines, linenum, error): ('getpwuid(', 'getpwuid_r('), ('gmtime(', 'gmtime_r('), ('localtime(', 'localtime_r('), - ('rand(', 'rand_r('), ('strtok(', 'strtok_r('), ('ttyname(', 'ttyname_r('), ) @@ -4530,6 +4562,7 @@ def ProcessLine(filename, file_extension, clean_lines, line, CheckForNonStandardConstructs(filename, clean_lines, line, nesting_state, error) CheckVlogArguments(filename, clean_lines, line, error) + CheckCaffeRandom(filename, clean_lines, line, error) CheckPosixThreading(filename, clean_lines, line, error) CheckInvalidIncrement(filename, clean_lines, line, error) CheckMakePairUsesDeduction(filename, clean_lines, line, error) diff --git a/src/caffe/layers/data_layer.cpp b/src/caffe/layers/data_layer.cpp index e4c34eb6ea7..6d7392a6b2a 100644 --- a/src/caffe/layers/data_layer.cpp +++ b/src/caffe/layers/data_layer.cpp @@ -10,6 +10,7 @@ #include "caffe/layer.hpp" #include "caffe/util/io.hpp" #include "caffe/util/math_functions.hpp" +#include "caffe/util/rng.hpp" #include "caffe/vision_layers.hpp" using std::string; @@ -19,7 +20,7 @@ namespace caffe { template void* DataLayerPrefetch(void* layer_pointer) { CHECK(layer_pointer); - DataLayer* layer = reinterpret_cast*>(layer_pointer); + DataLayer* layer = static_cast*>(layer_pointer); CHECK(layer); Datum datum; CHECK(layer->prefetch_data_); @@ -54,27 +55,23 @@ void* DataLayerPrefetch(void* layer_pointer) { int h_off, w_off; // We only do random crop when we do training. if (layer->phase_ == Caffe::TRAIN) { - // NOLINT_NEXT_LINE(runtime/threadsafe_fn) - h_off = rand() % (height - crop_size); - // NOLINT_NEXT_LINE(runtime/threadsafe_fn) - w_off = rand() % (width - crop_size); + h_off = layer->PrefetchRand() % (height - crop_size); + w_off = layer->PrefetchRand() % (width - crop_size); } else { h_off = (height - crop_size) / 2; w_off = (width - crop_size) / 2; } - // NOLINT_NEXT_LINE(runtime/threadsafe_fn) - if (mirror && rand() % 2) { + if (mirror && layer->PrefetchRand() % 2) { // Copy mirrored version for (int c = 0; c < channels; ++c) { for (int h = 0; h < crop_size; ++h) { for (int w = 0; w < crop_size; ++w) { - top_data[((item_id * channels + c) * crop_size + h) * crop_size - + crop_size - 1 - w] = - (static_cast( - (uint8_t)data[(c * height + h + h_off) * width - + w + w_off]) - - mean[(c * height + h + h_off) * width + w + w_off]) - * scale; + int top_index = ((item_id * channels + c) * crop_size + h) + * crop_size + (crop_size - 1 - w); + int data_index = (c * height + h + h_off) * width + w + w_off; + Dtype datum_element = + static_cast(static_cast(data[data_index])); + top_data[top_index] = (datum_element - mean[data_index]) * scale; } } } @@ -83,13 +80,12 @@ void* DataLayerPrefetch(void* layer_pointer) { for (int c = 0; c < channels; ++c) { for (int h = 0; h < crop_size; ++h) { for (int w = 0; w < crop_size; ++w) { - top_data[((item_id * channels + c) * crop_size + h) * crop_size - + w] - = (static_cast( - (uint8_t)data[(c * height + h + h_off) * width - + w + w_off]) - - mean[(c * height + h + h_off) * width + w + w_off]) - * scale; + int top_index = ((item_id * channels + c) * crop_size + h) + * crop_size + w; + int data_index = (c * height + h + h_off) * width + w + w_off; + Dtype datum_element = + static_cast(static_cast(data[data_index])); + top_data[top_index] = (datum_element - mean[data_index]) * scale; } } } @@ -98,8 +94,9 @@ void* DataLayerPrefetch(void* layer_pointer) { // we will prefer to use data() first, and then try float_data() if (data.size()) { for (int j = 0; j < size; ++j) { - top_data[item_id * size + j] = - (static_cast((uint8_t)data[j]) - mean[j]) * scale; + Dtype datum_element = + static_cast(static_cast(data[j])); + top_data[item_id * size + j] = (datum_element - mean[j]) * scale; } } else { for (int j = 0; j < size; ++j) { @@ -121,13 +118,12 @@ void* DataLayerPrefetch(void* layer_pointer) { } } - return reinterpret_cast(NULL); + return static_cast(NULL); } template DataLayer::~DataLayer() { - // Finally, join the thread - CHECK(!pthread_join(thread_, NULL)) << "Pthread joining failed."; + JoinPrefetchThread(); } template @@ -157,8 +153,8 @@ void DataLayer::SetUp(const vector*>& bottom, iter_->SeekToFirst(); // Check if we would need to randomly skip a few data points if (this->layer_param_.data_param().rand_skip()) { - // NOLINT_NEXT_LINE(runtime/threadsafe_fn) - unsigned int skip = rand() % this->layer_param_.data_param().rand_skip(); + unsigned int skip = caffe_rng_rand() % + this->layer_param_.data_param().rand_skip(); LOG(INFO) << "Skipping first " << skip << " data points."; while (skip-- > 0) { iter_->Next(); @@ -227,17 +223,45 @@ void DataLayer::SetUp(const vector*>& bottom, } data_mean_.cpu_data(); DLOG(INFO) << "Initializing prefetch"; + CreatePrefetchThread(); + DLOG(INFO) << "Prefetch initialized."; +} + +template +void DataLayer::CreatePrefetchThread() { phase_ = Caffe::phase(); + const bool prefetch_needs_rand = (phase_ == Caffe::TRAIN) && + (this->layer_param_.data_param().mirror() || + this->layer_param_.data_param().crop_size()); + if (prefetch_needs_rand) { + const unsigned int prefetch_rng_seed = caffe_rng_rand(); + prefetch_rng_.reset(new Caffe::RNG(prefetch_rng_seed)); + } else { + prefetch_rng_.reset(); + } + // Create the thread. CHECK(!pthread_create(&thread_, NULL, DataLayerPrefetch, - reinterpret_cast(this))) << "Pthread execution failed."; - DLOG(INFO) << "Prefetch initialized."; + static_cast(this))) << "Pthread execution failed."; +} + +template +void DataLayer::JoinPrefetchThread() { + CHECK(!pthread_join(thread_, NULL)) << "Pthread joining failed."; +} + +template +unsigned int DataLayer::PrefetchRand() { + CHECK(prefetch_rng_); + caffe::rng_t* prefetch_rng = + static_cast(prefetch_rng_->generator()); + return (*prefetch_rng)(); } template Dtype DataLayer::Forward_cpu(const vector*>& bottom, vector*>* top) { // First, join the thread - CHECK(!pthread_join(thread_, NULL)) << "Pthread joining failed."; + JoinPrefetchThread(); // Copy the data caffe_copy(prefetch_data_->count(), prefetch_data_->cpu_data(), (*top)[0]->mutable_cpu_data()); @@ -246,9 +270,7 @@ Dtype DataLayer::Forward_cpu(const vector*>& bottom, (*top)[1]->mutable_cpu_data()); } // Start a new prefetch thread - phase_ = Caffe::phase(); - CHECK(!pthread_create(&thread_, NULL, DataLayerPrefetch, - reinterpret_cast(this))) << "Pthread execution failed."; + CreatePrefetchThread(); return Dtype(0.); } diff --git a/src/caffe/layers/data_layer.cu b/src/caffe/layers/data_layer.cu index 3dd9f8b686e..2ff9a292b3e 100644 --- a/src/caffe/layers/data_layer.cu +++ b/src/caffe/layers/data_layer.cu @@ -19,7 +19,7 @@ template Dtype DataLayer::Forward_gpu(const vector*>& bottom, vector*>* top) { // First, join the thread - CHECK(!pthread_join(thread_, NULL)) << "Pthread joining failed."; + JoinPrefetchThread(); // Copy the data CUDA_CHECK(cudaMemcpy((*top)[0]->mutable_gpu_data(), prefetch_data_->cpu_data(), sizeof(Dtype) * prefetch_data_->count(), @@ -30,9 +30,7 @@ Dtype DataLayer::Forward_gpu(const vector*>& bottom, cudaMemcpyHostToDevice)); } // Start a new prefetch thread - phase_ = Caffe::phase(); - CHECK(!pthread_create(&thread_, NULL, DataLayerPrefetch, - reinterpret_cast(this))) << "Pthread execution failed."; + CreatePrefetchThread(); return Dtype(0.); } diff --git a/src/caffe/layers/image_data_layer.cpp b/src/caffe/layers/image_data_layer.cpp index 4182091d4a7..ed064d0608d 100644 --- a/src/caffe/layers/image_data_layer.cpp +++ b/src/caffe/layers/image_data_layer.cpp @@ -8,11 +8,15 @@ #include #include // NOLINT(readability/streams) #include // NOLINT(readability/streams) +#include #include "caffe/layer.hpp" #include "caffe/util/io.hpp" +#include "caffe/util/math_functions.hpp" +#include "caffe/util/rng.hpp" #include "caffe/vision_layers.hpp" +using std::iterator; using std::string; using std::pair; @@ -61,27 +65,23 @@ void* ImageDataLayerPrefetch(void* layer_pointer) { int h_off, w_off; // We only do random crop when we do training. if (layer->phase_ == Caffe::TRAIN) { - // NOLINT_NEXT_LINE(runtime/threadsafe_fn) - h_off = rand() % (height - crop_size); - // NOLINT_NEXT_LINE(runtime/threadsafe_fn) - w_off = rand() % (width - crop_size); + h_off = layer->PrefetchRand() % (height - crop_size); + w_off = layer->PrefetchRand() % (width - crop_size); } else { h_off = (height - crop_size) / 2; w_off = (width - crop_size) / 2; } - // NOLINT_NEXT_LINE(runtime/threadsafe_fn) - if (mirror && rand() % 2) { + if (mirror && layer->PrefetchRand() % 2) { // Copy mirrored version for (int c = 0; c < channels; ++c) { for (int h = 0; h < crop_size; ++h) { for (int w = 0; w < crop_size; ++w) { - top_data[((item_id * channels + c) * crop_size + h) * crop_size - + crop_size - 1 - w] = - (static_cast( - (uint8_t)data[(c * height + h + h_off) * width - + w + w_off]) - - mean[(c * height + h + h_off) * width + w + w_off]) - * scale; + int top_index = ((item_id * channels + c) * crop_size + h) + * crop_size + (crop_size - 1 - w); + int data_index = (c * height + h + h_off) * width + w + w_off; + Dtype datum_element = + static_cast(static_cast(data[data_index])); + top_data[top_index] = (datum_element - mean[data_index]) * scale; } } } @@ -90,13 +90,12 @@ void* ImageDataLayerPrefetch(void* layer_pointer) { for (int c = 0; c < channels; ++c) { for (int h = 0; h < crop_size; ++h) { for (int w = 0; w < crop_size; ++w) { - top_data[((item_id * channels + c) * crop_size + h) - * crop_size + w] - = (static_cast( - (uint8_t)data[(c * height + h + h_off) * width - + w + w_off]) - - mean[(c * height + h + h_off) * width + w + w_off]) - * scale; + int top_index = ((item_id * channels + c) * crop_size + h) + * crop_size + w; + int data_index = (c * height + h + h_off) * width + w + w_off; + Dtype datum_element = + static_cast(static_cast(data[data_index])); + top_data[top_index] = (datum_element - mean[data_index]) * scale; } } } @@ -105,8 +104,9 @@ void* ImageDataLayerPrefetch(void* layer_pointer) { // Just copy the whole data if (data.size()) { for (int j = 0; j < size; ++j) { - top_data[item_id * size + j] = - (static_cast((uint8_t)data[j]) - mean[j]) * scale; + Dtype datum_element = + static_cast(static_cast(data[j])); + top_data[item_id * size + j] = (datum_element - mean[j]) * scale; } } else { for (int j = 0; j < size; ++j) { @@ -124,7 +124,7 @@ void* ImageDataLayerPrefetch(void* layer_pointer) { DLOG(INFO) << "Restarting data prefetching from start."; layer->lines_id_ = 0; if (layer->layer_param_.image_data_param().shuffle()) { - std::random_shuffle(layer->lines_.begin(), layer->lines_.end()); + layer->ShuffleImages(); } } } @@ -134,8 +134,7 @@ void* ImageDataLayerPrefetch(void* layer_pointer) { template ImageDataLayer::~ImageDataLayer() { - // Finally, join the thread - CHECK(!pthread_join(thread_, NULL)) << "Pthread joining failed."; + JoinPrefetchThread(); } template @@ -161,18 +160,19 @@ void ImageDataLayer::SetUp(const vector*>& bottom, if (this->layer_param_.image_data_param().shuffle()) { // randomly shuffle data LOG(INFO) << "Shuffling data"; - std::random_shuffle(lines_.begin(), lines_.end()); + const unsigned int prefetch_rng_seed = caffe_rng_rand(); + prefetch_rng_.reset(new Caffe::RNG(prefetch_rng_seed)); + ShuffleImages(); } LOG(INFO) << "A total of " << lines_.size() << " images."; lines_id_ = 0; // Check if we would need to randomly skip a few data points if (this->layer_param_.image_data_param().rand_skip()) { - // NOLINT_NEXT_LINE(runtime/threadsafe_fn) - unsigned int skip = rand() % + unsigned int skip = caffe_rng_rand() % this->layer_param_.image_data_param().rand_skip(); LOG(INFO) << "Skipping first " << skip << " data points."; - CHECK_GT(lines_.size(), skip) << "Not enought points to skip"; + CHECK_GT(lines_.size(), skip) << "Not enough points to skip"; lines_id_ = skip; } // Read a data point, and use it to initialize the top blob. @@ -228,26 +228,65 @@ void ImageDataLayer::SetUp(const vector*>& bottom, prefetch_label_->mutable_cpu_data(); data_mean_.cpu_data(); DLOG(INFO) << "Initializing prefetch"; + CreatePrefetchThread(); + DLOG(INFO) << "Prefetch initialized."; +} + +template +void ImageDataLayer::CreatePrefetchThread() { phase_ = Caffe::phase(); + const bool prefetch_needs_rand = + this->layer_param_.image_data_param().shuffle() || + ((phase_ == Caffe::TRAIN) && + (this->layer_param_.image_data_param().mirror() || + this->layer_param_.image_data_param().crop_size())); + if (prefetch_needs_rand) { + const unsigned int prefetch_rng_seed = caffe_rng_rand(); + prefetch_rng_.reset(new Caffe::RNG(prefetch_rng_seed)); + } else { + prefetch_rng_.reset(); + } + // Create the thread. CHECK(!pthread_create(&thread_, NULL, ImageDataLayerPrefetch, - reinterpret_cast(this))) << "Pthread execution failed."; - DLOG(INFO) << "Prefetch initialized."; + static_cast(this))) << "Pthread execution failed."; +} + +template +void ImageDataLayer::ShuffleImages() { + const int num_images = lines_.size(); + for (int i = 0; i < num_images; ++i) { + const int max_rand_index = num_images - i; + const int rand_index = PrefetchRand() % max_rand_index; + pair item = lines_[rand_index]; + lines_.erase(lines_.begin() + rand_index); + lines_.push_back(item); + } +} + +template +void ImageDataLayer::JoinPrefetchThread() { + CHECK(!pthread_join(thread_, NULL)) << "Pthread joining failed."; +} + +template +unsigned int ImageDataLayer::PrefetchRand() { + caffe::rng_t* prefetch_rng = + static_cast(prefetch_rng_->generator()); + return (*prefetch_rng)(); } template Dtype ImageDataLayer::Forward_cpu(const vector*>& bottom, vector*>* top) { // First, join the thread - CHECK(!pthread_join(thread_, NULL)) << "Pthread joining failed."; + JoinPrefetchThread(); // Copy the data - memcpy((*top)[0]->mutable_cpu_data(), prefetch_data_->cpu_data(), - sizeof(Dtype) * prefetch_data_->count()); - memcpy((*top)[1]->mutable_cpu_data(), prefetch_label_->cpu_data(), - sizeof(Dtype) * prefetch_label_->count()); + caffe_copy(prefetch_data_->count(), prefetch_data_->cpu_data(), + (*top)[0]->mutable_cpu_data()); + caffe_copy(prefetch_label_->count(), prefetch_label_->cpu_data(), + (*top)[1]->mutable_cpu_data()); // Start a new prefetch thread - phase_ = Caffe::phase(); - CHECK(!pthread_create(&thread_, NULL, ImageDataLayerPrefetch, - reinterpret_cast(this))) << "Pthread execution failed."; + CreatePrefetchThread(); return Dtype(0.); } diff --git a/src/caffe/layers/image_data_layer.cu b/src/caffe/layers/image_data_layer.cu index e95550fe1ff..98047297d80 100644 --- a/src/caffe/layers/image_data_layer.cu +++ b/src/caffe/layers/image_data_layer.cu @@ -25,7 +25,7 @@ template Dtype ImageDataLayer::Forward_gpu(const vector*>& bottom, vector*>* top) { // First, join the thread - CHECK(!pthread_join(thread_, NULL)) << "Pthread joining failed."; + JoinPrefetchThread(); // Copy the data CUDA_CHECK(cudaMemcpy((*top)[0]->mutable_gpu_data(), prefetch_data_->cpu_data(), sizeof(Dtype) * prefetch_data_->count(), @@ -34,9 +34,7 @@ Dtype ImageDataLayer::Forward_gpu(const vector*>& bottom, prefetch_label_->cpu_data(), sizeof(Dtype) * prefetch_label_->count(), cudaMemcpyHostToDevice)); // Start a new prefetch thread - phase_ = Caffe::phase(); - CHECK(!pthread_create(&thread_, NULL, ImageDataLayerPrefetch, - reinterpret_cast(this))) << "Pthread execution failed."; + CreatePrefetchThread(); return Dtype(0.); } diff --git a/src/caffe/layers/window_data_layer.cpp b/src/caffe/layers/window_data_layer.cpp index 838f150ee40..862c0347082 100644 --- a/src/caffe/layers/window_data_layer.cpp +++ b/src/caffe/layers/window_data_layer.cpp @@ -18,6 +18,8 @@ #include "caffe/layer.hpp" #include "caffe/util/io.hpp" +#include "caffe/util/math_functions.hpp" +#include "caffe/util/rng.hpp" #include "caffe/vision_layers.hpp" using std::string; @@ -68,15 +70,13 @@ void* WindowDataLayerPrefetch(void* layer_pointer) { for (int is_fg = 0; is_fg < 2; ++is_fg) { for (int dummy = 0; dummy < num_samples[is_fg]; ++dummy) { // sample a window - vector window = (is_fg) - // NOLINT_NEXT_LINE(runtime/threadsafe_fn) - ? layer->fg_windows_[rand() % layer->fg_windows_.size()] - // NOLINT_NEXT_LINE(runtime/threadsafe_fn) - : layer->bg_windows_[rand() % layer->bg_windows_.size()]; + const unsigned int rand_index = layer->PrefetchRand(); + vector window = (is_fg) ? + layer->fg_windows_[rand_index % layer->fg_windows_.size()] : + layer->bg_windows_[rand_index % layer->bg_windows_.size()]; bool do_mirror = false; - // NOLINT_NEXT_LINE(runtime/threadsafe_fn) - if (mirror && rand() % 2) { + if (mirror && layer->PrefetchRand() % 2) { do_mirror = true; } @@ -214,8 +214,7 @@ void* WindowDataLayerPrefetch(void* layer_pointer) { // useful debugging code for dumping transformed windows to disk string file_id; std::stringstream ss; - // NOLINT_NEXT_LINE(runtime/threadsafe_fn) - ss << rand(); + ss << layer->PrefetchRand(); ss >> file_id; std::ofstream inf((string("dump/") + file_id + string("_info.txt")).c_str(), std::ofstream::out); @@ -253,7 +252,7 @@ void* WindowDataLayerPrefetch(void* layer_pointer) { template WindowDataLayer::~WindowDataLayer() { - CHECK(!pthread_join(thread_, NULL)) << "Pthread joining failed."; + JoinPrefetchThread(); } template @@ -405,24 +404,51 @@ void WindowDataLayer::SetUp(const vector*>& bottom, prefetch_label_->mutable_cpu_data(); data_mean_.cpu_data(); DLOG(INFO) << "Initializing prefetch"; - CHECK(!pthread_create(&thread_, NULL, WindowDataLayerPrefetch, - reinterpret_cast(this))) << "Pthread execution failed."; + CreatePrefetchThread(); DLOG(INFO) << "Prefetch initialized."; } +template +void WindowDataLayer::CreatePrefetchThread() { + const bool prefetch_needs_rand = + this->layer_param_.window_data_param().mirror() || + this->layer_param_.window_data_param().crop_size(); + if (prefetch_needs_rand) { + const unsigned int prefetch_rng_seed = caffe_rng_rand(); + prefetch_rng_.reset(new Caffe::RNG(prefetch_rng_seed)); + } else { + prefetch_rng_.reset(); + } + // Create the thread. + CHECK(!pthread_create(&thread_, NULL, WindowDataLayerPrefetch, + static_cast(this))) << "Pthread execution failed."; +} + +template +void WindowDataLayer::JoinPrefetchThread() { + CHECK(!pthread_join(thread_, NULL)) << "Pthread joining failed."; +} + +template +unsigned int WindowDataLayer::PrefetchRand() { + CHECK(prefetch_rng_); + caffe::rng_t* prefetch_rng = + static_cast(prefetch_rng_->generator()); + return (*prefetch_rng)(); +} + template Dtype WindowDataLayer::Forward_cpu(const vector*>& bottom, vector*>* top) { // First, join the thread - CHECK(!pthread_join(thread_, NULL)) << "Pthread joining failed."; + JoinPrefetchThread(); // Copy the data - memcpy((*top)[0]->mutable_cpu_data(), prefetch_data_->cpu_data(), - sizeof(Dtype) * prefetch_data_->count()); - memcpy((*top)[1]->mutable_cpu_data(), prefetch_label_->cpu_data(), - sizeof(Dtype) * prefetch_label_->count()); + caffe_copy(prefetch_data_->count(), prefetch_data_->cpu_data(), + (*top)[0]->mutable_cpu_data()); + caffe_copy(prefetch_label_->count(), prefetch_label_->cpu_data(), + (*top)[1]->mutable_cpu_data()); // Start a new prefetch thread - CHECK(!pthread_create(&thread_, NULL, WindowDataLayerPrefetch, - reinterpret_cast(this))) << "Pthread execution failed."; + CreatePrefetchThread(); return Dtype(0.); } diff --git a/src/caffe/layers/window_data_layer.cu b/src/caffe/layers/window_data_layer.cu index 69614625f7d..bc49fef6545 100644 --- a/src/caffe/layers/window_data_layer.cu +++ b/src/caffe/layers/window_data_layer.cu @@ -26,7 +26,7 @@ template Dtype WindowDataLayer::Forward_gpu(const vector*>& bottom, vector*>* top) { // First, join the thread - CHECK(!pthread_join(thread_, NULL)) << "Pthread joining failed."; + JoinPrefetchThread(); // Copy the data CUDA_CHECK(cudaMemcpy((*top)[0]->mutable_gpu_data(), prefetch_data_->cpu_data(), sizeof(Dtype) * prefetch_data_->count(), @@ -35,8 +35,7 @@ Dtype WindowDataLayer::Forward_gpu(const vector*>& bottom, prefetch_label_->cpu_data(), sizeof(Dtype) * prefetch_label_->count(), cudaMemcpyHostToDevice)); // Start a new prefetch thread - CHECK(!pthread_create(&thread_, NULL, WindowDataLayerPrefetch, - reinterpret_cast(this))) << "Pthread execution failed."; + CreatePrefetchThread(); return Dtype(0.); } diff --git a/src/caffe/proto/caffe.proto b/src/caffe/proto/caffe.proto index 284b1e29f58..6c4ebd038fe 100644 --- a/src/caffe/proto/caffe.proto +++ b/src/caffe/proto/caffe.proto @@ -84,6 +84,10 @@ message SolverParameter { optional int32 solver_mode = 17 [default = 1]; // the device_id will that be used in GPU mode. Use device_id = 0 in default. optional int32 device_id = 18 [default = 0]; + // If non-negative, the seed with which the Solver will initialize the Caffe + // random number generator -- useful for reproducible results. Otherwise, + // (and by default) initialize using a seed derived from the system clock. + optional int64 random_seed = 20 [default = -1]; } // A message that stores the solver snapshots diff --git a/src/caffe/solver.cpp b/src/caffe/solver.cpp index ca6f3cf0e3f..52d0cc7f8ae 100644 --- a/src/caffe/solver.cpp +++ b/src/caffe/solver.cpp @@ -34,6 +34,9 @@ Solver::Solver(const string& param_file) template void Solver::Init(const SolverParameter& param) { param_ = param; + if (param_.random_seed() >= 0) { + Caffe::set_random_seed(param_.random_seed()); + } // Scaffolding code LOG(INFO) << "Creating training net."; net_.reset(new Net(param_.train_net())); diff --git a/src/caffe/test/test_data_layer.cpp b/src/caffe/test/test_data_layer.cpp index 6b0838ab975..2c0fbb08ddb 100644 --- a/src/caffe/test/test_data_layer.cpp +++ b/src/caffe/test/test_data_layer.cpp @@ -26,18 +26,24 @@ class DataLayerTest : public ::testing::Test { DataLayerTest() : blob_top_data_(new Blob()), blob_top_label_(new Blob()), - filename(NULL) {} + filename_(new string(tmpnam(NULL))), + seed_(1701) {} virtual void SetUp() { blob_top_vec_.push_back(blob_top_data_); blob_top_vec_.push_back(blob_top_label_); - // Create the leveldb - filename = tmpnam(NULL); // get temp name - LOG(INFO) << "Using temporary leveldb " << filename; + } + + // Fill the LevelDB with data: if unique_pixels, each pixel is unique but + // all images are the same; else each image is unique but all pixels within + // an image are the same. + void FillLevelDB(const bool unique_pixels) { + LOG(INFO) << "Using temporary leveldb " << *filename_; leveldb::DB* db; leveldb::Options options; options.error_if_exists = true; options.create_if_missing = true; - leveldb::Status status = leveldb::DB::Open(options, filename, &db); + leveldb::Status status = + leveldb::DB::Open(options, filename_->c_str(), &db); CHECK(status.ok()); for (int i = 0; i < 5; ++i) { Datum datum; @@ -47,7 +53,8 @@ class DataLayerTest : public ::testing::Test { datum.set_width(4); std::string* data = datum.mutable_data(); for (int j = 0; j < 24; ++j) { - data->push_back((uint8_t)i); + int datum = unique_pixels ? j : i; + data->push_back(static_cast(datum)); } stringstream ss; ss << i; @@ -58,21 +65,27 @@ class DataLayerTest : public ::testing::Test { virtual ~DataLayerTest() { delete blob_top_data_; delete blob_top_label_; } - char* filename; + shared_ptr filename_; Blob* const blob_top_data_; Blob* const blob_top_label_; vector*> blob_bottom_vec_; vector*> blob_top_vec_; + int seed_; }; typedef ::testing::Types Dtypes; TYPED_TEST_CASE(DataLayerTest, Dtypes); -TYPED_TEST(DataLayerTest, TestRead) { +TYPED_TEST(DataLayerTest, TestReadCPU) { + Caffe::set_mode(Caffe::CPU); + const bool unique_pixels = false; // all pixels the same; images different + this->FillLevelDB(unique_pixels); + const TypeParam scale = 3; LayerParameter param; DataParameter* data_param = param.mutable_data_param(); data_param->set_batch_size(5); - data_param->set_source(this->filename); + data_param->set_scale(scale); + data_param->set_source(this->filename_->c_str()); DataLayer layer(param); layer.SetUp(this->blob_bottom_vec_, &this->blob_top_vec_); EXPECT_EQ(this->blob_top_data_->num(), 5); @@ -84,7 +97,6 @@ TYPED_TEST(DataLayerTest, TestRead) { EXPECT_EQ(this->blob_top_label_->height(), 1); EXPECT_EQ(this->blob_top_label_->width(), 1); - // Go through the data 100 times for (int iter = 0; iter < 100; ++iter) { layer.Forward(this->blob_bottom_vec_, &this->blob_top_vec_); for (int i = 0; i < 5; ++i) { @@ -92,14 +104,34 @@ TYPED_TEST(DataLayerTest, TestRead) { } for (int i = 0; i < 5; ++i) { for (int j = 0; j < 24; ++j) { - EXPECT_EQ(i, this->blob_top_data_->cpu_data()[i * 24 + j]) - << "debug: i " << i << " j " << j; + EXPECT_EQ(scale * i, this->blob_top_data_->cpu_data()[i * 24 + j]) + << "debug: iter " << iter << " i " << i << " j " << j; } } } +} - // Same test, in GPU mode. +TYPED_TEST(DataLayerTest, TestReadGPU) { Caffe::set_mode(Caffe::GPU); + const bool unique_pixels = false; // all pixels the same; images different + this->FillLevelDB(unique_pixels); + const TypeParam scale = 3; + LayerParameter param; + DataParameter* data_param = param.mutable_data_param(); + data_param->set_batch_size(5); + data_param->set_scale(scale); + data_param->set_source(this->filename_->c_str()); + DataLayer layer(param); + layer.SetUp(this->blob_bottom_vec_, &this->blob_top_vec_); + EXPECT_EQ(this->blob_top_data_->num(), 5); + EXPECT_EQ(this->blob_top_data_->channels(), 2); + EXPECT_EQ(this->blob_top_data_->height(), 3); + EXPECT_EQ(this->blob_top_data_->width(), 4); + EXPECT_EQ(this->blob_top_label_->num(), 5); + EXPECT_EQ(this->blob_top_label_->channels(), 1); + EXPECT_EQ(this->blob_top_label_->height(), 1); + EXPECT_EQ(this->blob_top_label_->width(), 1); + for (int iter = 0; iter < 100; ++iter) { layer.Forward(this->blob_bottom_vec_, &this->blob_top_vec_); for (int i = 0; i < 5; ++i) { @@ -107,8 +139,388 @@ TYPED_TEST(DataLayerTest, TestRead) { } for (int i = 0; i < 5; ++i) { for (int j = 0; j < 24; ++j) { - EXPECT_EQ(i, this->blob_top_data_->cpu_data()[i * 24 + j]) - << "debug: i " << i << " j " << j; + EXPECT_EQ(scale * i, this->blob_top_data_->cpu_data()[i * 24 + j]) + << "debug: iter " << iter << " i " << i << " j " << j; + } + } + } +} + +TYPED_TEST(DataLayerTest, TestReadCropTrainCPU) { + Caffe::set_phase(Caffe::TRAIN); + Caffe::set_mode(Caffe::CPU); + const bool unique_pixels = true; // all images the same; pixels different + this->FillLevelDB(unique_pixels); + const TypeParam scale = 3; + LayerParameter param; + DataParameter* data_param = param.mutable_data_param(); + data_param->set_batch_size(5); + data_param->set_scale(scale); + data_param->set_crop_size(1); + data_param->set_source(this->filename_->c_str()); + DataLayer layer(param); + layer.SetUp(this->blob_bottom_vec_, &this->blob_top_vec_); + EXPECT_EQ(this->blob_top_data_->num(), 5); + EXPECT_EQ(this->blob_top_data_->channels(), 2); + EXPECT_EQ(this->blob_top_data_->height(), 1); + EXPECT_EQ(this->blob_top_data_->width(), 1); + EXPECT_EQ(this->blob_top_label_->num(), 5); + EXPECT_EQ(this->blob_top_label_->channels(), 1); + EXPECT_EQ(this->blob_top_label_->height(), 1); + EXPECT_EQ(this->blob_top_label_->width(), 1); + + for (int iter = 0; iter < 2; ++iter) { + layer.Forward(this->blob_bottom_vec_, &this->blob_top_vec_); + for (int i = 0; i < 5; ++i) { + EXPECT_EQ(i, this->blob_top_label_->cpu_data()[i]); + } + int num_with_center_value = 0; + for (int i = 0; i < 5; ++i) { + for (int j = 0; j < 2; ++j) { + const TypeParam center_value = scale * (j ? 17 : 5); + num_with_center_value += + (center_value == this->blob_top_data_->cpu_data()[i * 2 + j]); + } + } + // Check we did not get the center crop all 10 times (occurs with + // probability 1-1/12^10 in working implementation). + EXPECT_LT(num_with_center_value, 10); + } +} + +TYPED_TEST(DataLayerTest, TestReadCropTrainGPU) { + Caffe::set_phase(Caffe::TRAIN); + Caffe::set_mode(Caffe::GPU); + const bool unique_pixels = true; // all images the same; pixels different + this->FillLevelDB(unique_pixels); + const TypeParam scale = 3; + LayerParameter param; + DataParameter* data_param = param.mutable_data_param(); + data_param->set_batch_size(5); + data_param->set_scale(scale); + data_param->set_crop_size(1); + data_param->set_source(this->filename_->c_str()); + DataLayer layer(param); + layer.SetUp(this->blob_bottom_vec_, &this->blob_top_vec_); + EXPECT_EQ(this->blob_top_data_->num(), 5); + EXPECT_EQ(this->blob_top_data_->channels(), 2); + EXPECT_EQ(this->blob_top_data_->height(), 1); + EXPECT_EQ(this->blob_top_data_->width(), 1); + EXPECT_EQ(this->blob_top_label_->num(), 5); + EXPECT_EQ(this->blob_top_label_->channels(), 1); + EXPECT_EQ(this->blob_top_label_->height(), 1); + EXPECT_EQ(this->blob_top_label_->width(), 1); + + for (int iter = 0; iter < 2; ++iter) { + layer.Forward(this->blob_bottom_vec_, &this->blob_top_vec_); + for (int i = 0; i < 5; ++i) { + EXPECT_EQ(i, this->blob_top_label_->cpu_data()[i]); + } + int num_with_center_value = 0; + for (int i = 0; i < 5; ++i) { + for (int j = 0; j < 2; ++j) { + const TypeParam center_value = scale * (j ? 17 : 5); + num_with_center_value += + (center_value == this->blob_top_data_->cpu_data()[i * 2 + j]); + } + } + // Check we did not get the center crop all 10 times (occurs with + // probability 1-1/12^10 in working implementation). + EXPECT_LT(num_with_center_value, 10); + } +} + +// Test that the sequence of random crops is consistent when using +// Caffe::set_random_seed. +TYPED_TEST(DataLayerTest, TestReadCropTrainSequenceSeededCPU) { + Caffe::set_phase(Caffe::TRAIN); + Caffe::set_mode(Caffe::CPU); + const bool unique_pixels = true; // all images the same; pixels different + this->FillLevelDB(unique_pixels); + LayerParameter param; + DataParameter* data_param = param.mutable_data_param(); + data_param->set_batch_size(5); + data_param->set_crop_size(1); + data_param->set_mirror(true); + data_param->set_source(this->filename_->c_str()); + + // Get crop sequence with Caffe seed 1701. + Caffe::set_random_seed(this->seed_); + DataLayer layer1(param); + layer1.SetUp(this->blob_bottom_vec_, &this->blob_top_vec_); + vector > crop_sequence; + for (int iter = 0; iter < 2; ++iter) { + layer1.Forward(this->blob_bottom_vec_, &this->blob_top_vec_); + for (int i = 0; i < 5; ++i) { + EXPECT_EQ(i, this->blob_top_label_->cpu_data()[i]); + } + vector iter_crop_sequence; + for (int i = 0; i < 5; ++i) { + for (int j = 0; j < 2; ++j) { + iter_crop_sequence.push_back( + this->blob_top_data_->cpu_data()[i * 2 + j]); + } + } + crop_sequence.push_back(iter_crop_sequence); + } + + // Get crop sequence after reseeding Caffe with 1701. + // Check that the sequence is the same as the original. + Caffe::set_random_seed(this->seed_); + DataLayer layer2(param); + layer2.SetUp(this->blob_bottom_vec_, &this->blob_top_vec_); + for (int iter = 0; iter < 2; ++iter) { + layer2.Forward(this->blob_bottom_vec_, &this->blob_top_vec_); + for (int i = 0; i < 5; ++i) { + EXPECT_EQ(i, this->blob_top_label_->cpu_data()[i]); + } + for (int i = 0; i < 5; ++i) { + for (int j = 0; j < 2; ++j) { + EXPECT_EQ(crop_sequence[iter][i * 2 + j], + this->blob_top_data_->cpu_data()[i * 2 + j]) + << "debug: iter " << iter << " i " << i << " j " << j; + } + } + } +} + +// Test that the sequence of random crops is consistent when using +// Caffe::set_random_seed. +TYPED_TEST(DataLayerTest, TestReadCropTrainSequenceSeededGPU) { + Caffe::set_phase(Caffe::TRAIN); + Caffe::set_mode(Caffe::GPU); + const bool unique_pixels = true; // all images the same; pixels different + this->FillLevelDB(unique_pixels); + LayerParameter param; + DataParameter* data_param = param.mutable_data_param(); + data_param->set_batch_size(5); + data_param->set_crop_size(1); + data_param->set_mirror(true); + data_param->set_source(this->filename_->c_str()); + + // Get crop sequence with Caffe seed 1701. + Caffe::set_random_seed(this->seed_); + DataLayer layer1(param); + layer1.SetUp(this->blob_bottom_vec_, &this->blob_top_vec_); + vector > crop_sequence; + for (int iter = 0; iter < 2; ++iter) { + layer1.Forward(this->blob_bottom_vec_, &this->blob_top_vec_); + for (int i = 0; i < 5; ++i) { + EXPECT_EQ(i, this->blob_top_label_->cpu_data()[i]); + } + vector iter_crop_sequence; + for (int i = 0; i < 5; ++i) { + for (int j = 0; j < 2; ++j) { + iter_crop_sequence.push_back( + this->blob_top_data_->cpu_data()[i * 2 + j]); + } + } + crop_sequence.push_back(iter_crop_sequence); + } + + // Get crop sequence after reseeding Caffe with 1701. + // Check that the sequence is the same as the original. + Caffe::set_random_seed(this->seed_); + DataLayer layer2(param); + layer2.SetUp(this->blob_bottom_vec_, &this->blob_top_vec_); + for (int iter = 0; iter < 2; ++iter) { + layer2.Forward(this->blob_bottom_vec_, &this->blob_top_vec_); + for (int i = 0; i < 5; ++i) { + EXPECT_EQ(i, this->blob_top_label_->cpu_data()[i]); + } + for (int i = 0; i < 5; ++i) { + for (int j = 0; j < 2; ++j) { + EXPECT_EQ(crop_sequence[iter][i * 2 + j], + this->blob_top_data_->cpu_data()[i * 2 + j]) + << "debug: iter " << iter << " i " << i << " j " << j; + } + } + } +} + +// Test that the sequence of random crops differs across iterations when +// Caffe::set_random_seed isn't called (and seeds from srand are ignored). +TYPED_TEST(DataLayerTest, TestReadCropTrainSequenceUnseededCPU) { + Caffe::set_phase(Caffe::TRAIN); + Caffe::set_mode(Caffe::CPU); + const bool unique_pixels = true; // all images the same; pixels different + this->FillLevelDB(unique_pixels); + LayerParameter param; + DataParameter* data_param = param.mutable_data_param(); + data_param->set_batch_size(5); + data_param->set_crop_size(1); + data_param->set_mirror(true); + data_param->set_source(this->filename_->c_str()); + + // Get crop sequence with Caffe seed 1701, srand seed 1701. + Caffe::set_random_seed(this->seed_); + srand(this->seed_); + DataLayer layer1(param); + layer1.SetUp(this->blob_bottom_vec_, &this->blob_top_vec_); + vector > crop_sequence; + for (int iter = 0; iter < 2; ++iter) { + layer1.Forward(this->blob_bottom_vec_, &this->blob_top_vec_); + for (int i = 0; i < 5; ++i) { + EXPECT_EQ(i, this->blob_top_label_->cpu_data()[i]); + } + vector iter_crop_sequence; + for (int i = 0; i < 5; ++i) { + for (int j = 0; j < 2; ++j) { + iter_crop_sequence.push_back( + this->blob_top_data_->cpu_data()[i * 2 + j]); + } + } + crop_sequence.push_back(iter_crop_sequence); + } + + // Get crop sequence continuing from previous Caffe RNG state; + // reseed srand with 1701. Check that the sequence differs from the original. + srand(this->seed_); + DataLayer layer2(param); + layer2.SetUp(this->blob_bottom_vec_, &this->blob_top_vec_); + for (int iter = 0; iter < 2; ++iter) { + layer2.Forward(this->blob_bottom_vec_, &this->blob_top_vec_); + for (int i = 0; i < 5; ++i) { + EXPECT_EQ(i, this->blob_top_label_->cpu_data()[i]); + } + int num_sequence_matches = 0; + for (int i = 0; i < 5; ++i) { + for (int j = 0; j < 2; ++j) { + num_sequence_matches += (crop_sequence[iter][i * 2 + j] == + this->blob_top_data_->cpu_data()[i * 2 + j]); + } + } + EXPECT_LT(num_sequence_matches, 10); + } +} + +// Test that the sequence of random crops differs across iterations when +// Caffe::set_random_seed isn't called (and seeds from srand are ignored). +TYPED_TEST(DataLayerTest, TestReadCropTrainSequenceUnseededGPU) { + Caffe::set_phase(Caffe::TRAIN); + Caffe::set_mode(Caffe::GPU); + const bool unique_pixels = true; // all images the same; pixels different + this->FillLevelDB(unique_pixels); + LayerParameter param; + DataParameter* data_param = param.mutable_data_param(); + data_param->set_batch_size(5); + data_param->set_crop_size(1); + data_param->set_mirror(true); + data_param->set_source(this->filename_->c_str()); + + // Get crop sequence with Caffe seed 1701, srand seed 1701. + Caffe::set_random_seed(this->seed_); + srand(this->seed_); + DataLayer layer1(param); + layer1.SetUp(this->blob_bottom_vec_, &this->blob_top_vec_); + vector > crop_sequence; + for (int iter = 0; iter < 2; ++iter) { + layer1.Forward(this->blob_bottom_vec_, &this->blob_top_vec_); + for (int i = 0; i < 5; ++i) { + EXPECT_EQ(i, this->blob_top_label_->cpu_data()[i]); + } + vector iter_crop_sequence; + for (int i = 0; i < 5; ++i) { + for (int j = 0; j < 2; ++j) { + iter_crop_sequence.push_back( + this->blob_top_data_->cpu_data()[i * 2 + j]); + } + } + crop_sequence.push_back(iter_crop_sequence); + } + + // Get crop sequence continuing from previous Caffe RNG state; + // reseed srand with 1701. Check that the sequence differs from the original. + srand(this->seed_); + DataLayer layer2(param); + layer2.SetUp(this->blob_bottom_vec_, &this->blob_top_vec_); + for (int iter = 0; iter < 2; ++iter) { + layer2.Forward(this->blob_bottom_vec_, &this->blob_top_vec_); + for (int i = 0; i < 5; ++i) { + EXPECT_EQ(i, this->blob_top_label_->cpu_data()[i]); + } + int num_sequence_matches = 0; + for (int i = 0; i < 5; ++i) { + for (int j = 0; j < 2; ++j) { + num_sequence_matches += (crop_sequence[iter][i * 2 + j] == + this->blob_top_data_->cpu_data()[i * 2 + j]); + } + } + EXPECT_LT(num_sequence_matches, 10); + } +} + +TYPED_TEST(DataLayerTest, TestReadCropTestCPU) { + Caffe::set_phase(Caffe::TEST); + Caffe::set_mode(Caffe::CPU); + const bool unique_pixels = true; // all images the same; pixels different + this->FillLevelDB(unique_pixels); + const TypeParam scale = 3; + LayerParameter param; + DataParameter* data_param = param.mutable_data_param(); + data_param->set_batch_size(5); + data_param->set_scale(scale); + data_param->set_crop_size(1); + data_param->set_source(this->filename_->c_str()); + DataLayer layer(param); + layer.SetUp(this->blob_bottom_vec_, &this->blob_top_vec_); + EXPECT_EQ(this->blob_top_data_->num(), 5); + EXPECT_EQ(this->blob_top_data_->channels(), 2); + EXPECT_EQ(this->blob_top_data_->height(), 1); + EXPECT_EQ(this->blob_top_data_->width(), 1); + EXPECT_EQ(this->blob_top_label_->num(), 5); + EXPECT_EQ(this->blob_top_label_->channels(), 1); + EXPECT_EQ(this->blob_top_label_->height(), 1); + EXPECT_EQ(this->blob_top_label_->width(), 1); + + for (int iter = 0; iter < 2; ++iter) { + layer.Forward(this->blob_bottom_vec_, &this->blob_top_vec_); + for (int i = 0; i < 5; ++i) { + EXPECT_EQ(i, this->blob_top_label_->cpu_data()[i]); + } + for (int i = 0; i < 5; ++i) { + for (int j = 0; j < 2; ++j) { + const TypeParam center_value = scale * (j ? 17 : 5); + EXPECT_EQ(center_value, this->blob_top_data_->cpu_data()[i * 2 + j]) + << "debug: iter " << iter << " i " << i << " j " << j; + } + } + } +} + +TYPED_TEST(DataLayerTest, TestReadCropTestGPU) { + Caffe::set_phase(Caffe::TEST); + Caffe::set_mode(Caffe::GPU); + const bool unique_pixels = true; // all images the same; pixels different + this->FillLevelDB(unique_pixels); + const TypeParam scale = 3; + LayerParameter param; + DataParameter* data_param = param.mutable_data_param(); + data_param->set_batch_size(5); + data_param->set_scale(scale); + data_param->set_crop_size(1); + data_param->set_source(this->filename_->c_str()); + DataLayer layer(param); + layer.SetUp(this->blob_bottom_vec_, &this->blob_top_vec_); + EXPECT_EQ(this->blob_top_data_->num(), 5); + EXPECT_EQ(this->blob_top_data_->channels(), 2); + EXPECT_EQ(this->blob_top_data_->height(), 1); + EXPECT_EQ(this->blob_top_data_->width(), 1); + EXPECT_EQ(this->blob_top_label_->num(), 5); + EXPECT_EQ(this->blob_top_label_->channels(), 1); + EXPECT_EQ(this->blob_top_label_->height(), 1); + EXPECT_EQ(this->blob_top_label_->width(), 1); + + for (int iter = 0; iter < 2; ++iter) { + layer.Forward(this->blob_bottom_vec_, &this->blob_top_vec_); + for (int i = 0; i < 5; ++i) { + EXPECT_EQ(i, this->blob_top_label_->cpu_data()[i]); + } + for (int i = 0; i < 5; ++i) { + for (int j = 0; j < 2; ++j) { + const TypeParam center_value = scale * (j ? 17 : 5); + EXPECT_EQ(center_value, this->blob_top_data_->cpu_data()[i * 2 + j]) + << "debug: iter " << iter << " i " << i << " j " << j; } } } diff --git a/src/caffe/test/test_image_data_layer.cpp b/src/caffe/test/test_image_data_layer.cpp index 9a6271cbea8..42a1d0358db 100644 --- a/src/caffe/test/test_image_data_layer.cpp +++ b/src/caffe/test/test_image_data_layer.cpp @@ -4,6 +4,7 @@ #include // NOLINT(readability/streams) #include // NOLINT(readability/streams) +#include #include #include @@ -15,6 +16,7 @@ #include "caffe/proto/caffe.pb.h" #include "caffe/test/test_caffe_main.hpp" +using std::map; using std::string; namespace caffe { @@ -27,14 +29,15 @@ class ImageDataLayerTest : public ::testing::Test { ImageDataLayerTest() : blob_top_data_(new Blob()), blob_top_label_(new Blob()), - filename(NULL) {} + filename_(new string(tmpnam(NULL))), + seed_(1701) {} virtual void SetUp() { blob_top_vec_.push_back(blob_top_data_); blob_top_vec_.push_back(blob_top_label_); + Caffe::set_random_seed(seed_); // Create a Vector of files with labels - filename = tmpnam(NULL); // get temp name - std::ofstream outfile(filename, std::ofstream::out); - LOG(INFO) << "Using temporary file " << filename; + std::ofstream outfile(filename_->c_str(), std::ofstream::out); + LOG(INFO) << "Using temporary file " << *filename_; for (int i = 0; i < 5; ++i) { outfile << "examples/images/cat.jpg " << i; } @@ -46,7 +49,8 @@ class ImageDataLayerTest : public ::testing::Test { delete blob_top_label_; } - char* filename; + int seed_; + shared_ptr filename_; Blob* const blob_top_data_; Blob* const blob_top_label_; vector*> blob_bottom_vec_; @@ -60,7 +64,7 @@ TYPED_TEST(ImageDataLayerTest, TestRead) { LayerParameter param; ImageDataParameter* image_data_param = param.mutable_image_data_param(); image_data_param->set_batch_size(5); - image_data_param->set_source(this->filename); + image_data_param->set_source(this->filename_->c_str()); image_data_param->set_shuffle(false); ImageDataLayer layer(param); layer.SetUp(this->blob_bottom_vec_, &this->blob_top_vec_); @@ -72,8 +76,8 @@ TYPED_TEST(ImageDataLayerTest, TestRead) { EXPECT_EQ(this->blob_top_label_->channels(), 1); EXPECT_EQ(this->blob_top_label_->height(), 1); EXPECT_EQ(this->blob_top_label_->width(), 1); - // Go through the data 5 times - for (int iter = 0; iter < 5; ++iter) { + // Go through the data twice + for (int iter = 0; iter < 2; ++iter) { layer.Forward(this->blob_bottom_vec_, &this->blob_top_vec_); for (int i = 0; i < 5; ++i) { EXPECT_EQ(i, this->blob_top_label_->cpu_data()[i]); @@ -85,7 +89,7 @@ TYPED_TEST(ImageDataLayerTest, TestResize) { LayerParameter param; ImageDataParameter* image_data_param = param.mutable_image_data_param(); image_data_param->set_batch_size(5); - image_data_param->set_source(this->filename); + image_data_param->set_source(this->filename_->c_str()); image_data_param->set_new_height(256); image_data_param->set_new_width(256); image_data_param->set_shuffle(false); @@ -99,8 +103,8 @@ TYPED_TEST(ImageDataLayerTest, TestResize) { EXPECT_EQ(this->blob_top_label_->channels(), 1); EXPECT_EQ(this->blob_top_label_->height(), 1); EXPECT_EQ(this->blob_top_label_->width(), 1); - // Go through the data 50 times - for (int iter = 0; iter < 5; ++iter) { + // Go through the data twice + for (int iter = 0; iter < 2; ++iter) { layer.Forward(this->blob_bottom_vec_, &this->blob_top_vec_); for (int i = 0; i < 5; ++i) { EXPECT_EQ(i, this->blob_top_label_->cpu_data()[i]); @@ -112,7 +116,7 @@ TYPED_TEST(ImageDataLayerTest, TestShuffle) { LayerParameter param; ImageDataParameter* image_data_param = param.mutable_image_data_param(); image_data_param->set_batch_size(5); - image_data_param->set_source(this->filename); + image_data_param->set_source(this->filename_->c_str()); image_data_param->set_shuffle(true); ImageDataLayer layer(param); layer.SetUp(this->blob_bottom_vec_, &this->blob_top_vec_); @@ -124,13 +128,20 @@ TYPED_TEST(ImageDataLayerTest, TestShuffle) { EXPECT_EQ(this->blob_top_label_->channels(), 1); EXPECT_EQ(this->blob_top_label_->height(), 1); EXPECT_EQ(this->blob_top_label_->width(), 1); - // Go through the data 5 times - for (int iter = 0; iter < 5; ++iter) { + // Go through the data twice + for (int iter = 0; iter < 2; ++iter) { layer.Forward(this->blob_bottom_vec_, &this->blob_top_vec_); + map values_to_indices; + int num_in_order = 0; for (int i = 0; i < 5; ++i) { - EXPECT_GE(this->blob_top_label_->cpu_data()[i], 0); - EXPECT_LE(this->blob_top_label_->cpu_data()[i], 5); + TypeParam value = this->blob_top_label_->cpu_data()[i]; + // Check that the value has not been seen already (no duplicates). + EXPECT_EQ(values_to_indices.find(value), values_to_indices.end()); + values_to_indices[value] = i; + num_in_order += (value == TypeParam(i)); } + EXPECT_EQ(5, values_to_indices.size()); + EXPECT_GT(5, num_in_order); } } diff --git a/src/caffe/test/test_math_functions.cpp b/src/caffe/test/test_math_functions.cpp index c2c896f08d5..d0265767c07 100644 --- a/src/caffe/test/test_math_functions.cpp +++ b/src/caffe/test/test_math_functions.cpp @@ -181,9 +181,8 @@ TYPED_TEST(MathFunctionsTest, TestFabsGPU) { TYPED_TEST(MathFunctionsTest, TestScaleCPU) { int n = this->blob_bottom_->count(); - // NOLINT_NEXT_LINE(runtime/threadsafe_fn) - TypeParam alpha = this->blob_bottom_->cpu_diff()[rand() % - this->blob_bottom_->count()]; + TypeParam alpha = this->blob_bottom_->cpu_diff()[caffe_rng_rand() % + this->blob_bottom_->count()]; caffe_cpu_scale(n, alpha, this->blob_bottom_->cpu_data(), this->blob_bottom_->mutable_cpu_diff()); const TypeParam* scaled = this->blob_bottom_->cpu_diff(); @@ -195,9 +194,8 @@ TYPED_TEST(MathFunctionsTest, TestScaleCPU) { TYPED_TEST(MathFunctionsTest, TestScaleGPU) { int n = this->blob_bottom_->count(); - // NOLINT_NEXT_LINE(runtime/threadsafe_fn) - TypeParam alpha = this->blob_bottom_->cpu_diff()[rand() % - this->blob_bottom_->count()]; + TypeParam alpha = this->blob_bottom_->cpu_diff()[caffe_rng_rand() % + this->blob_bottom_->count()]; caffe_gpu_scale(n, alpha, this->blob_bottom_->gpu_data(), this->blob_bottom_->mutable_gpu_diff()); const TypeParam* scaled = this->blob_bottom_->cpu_diff(); diff --git a/src/caffe/test/test_multinomial_logistic_loss_layer.cpp b/src/caffe/test/test_multinomial_logistic_loss_layer.cpp index 6fc01e3b947..aa475ca27c7 100644 --- a/src/caffe/test/test_multinomial_logistic_loss_layer.cpp +++ b/src/caffe/test/test_multinomial_logistic_loss_layer.cpp @@ -32,8 +32,7 @@ class MultinomialLogisticLossLayerTest : public ::testing::Test { filler.Fill(this->blob_bottom_data_); blob_bottom_vec_.push_back(blob_bottom_data_); for (int i = 0; i < blob_bottom_label_->count(); ++i) { - // NOLINT_NEXT_LINE(runtime/threadsafe_fn) - blob_bottom_label_->mutable_cpu_data()[i] = rand() % 5; + blob_bottom_label_->mutable_cpu_data()[i] = caffe_rng_rand() % 5; } blob_bottom_vec_.push_back(blob_bottom_label_); } diff --git a/src/caffe/test/test_softmax_with_loss_layer.cpp b/src/caffe/test/test_softmax_with_loss_layer.cpp index 2a15b84de3e..8b8be8e8b6d 100644 --- a/src/caffe/test/test_softmax_with_loss_layer.cpp +++ b/src/caffe/test/test_softmax_with_loss_layer.cpp @@ -32,8 +32,7 @@ class SoftmaxWithLossLayerTest : public ::testing::Test { filler.Fill(this->blob_bottom_data_); blob_bottom_vec_.push_back(blob_bottom_data_); for (int i = 0; i < blob_bottom_label_->count(); ++i) { - // NOLINT_NEXT_LINE(runtime/threadsafe_fn) - blob_bottom_label_->mutable_cpu_data()[i] = rand() % 5; + blob_bottom_label_->mutable_cpu_data()[i] = caffe_rng_rand() % 5; } blob_bottom_vec_.push_back(blob_bottom_label_); } diff --git a/src/caffe/util/math_functions.cpp b/src/caffe/util/math_functions.cpp index a3f1084367d..ba2492aa635 100644 --- a/src/caffe/util/math_functions.cpp +++ b/src/caffe/util/math_functions.cpp @@ -302,6 +302,10 @@ void caffe_exp(const int n, const double* a, double* y) { vdExp(n, a, y); } +unsigned int caffe_rng_rand() { + return (*caffe_rng())(); +} + template Dtype caffe_nextafter(const Dtype b) { return boost::math::nextafter(