Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 30 additions & 1 deletion include/caffe/layer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,22 @@ class Layer {
}
param_propagate_down_[param_id] = value;
}

/**
* @brief Called on each layer after net's parameters have been updated
* using the solver.
*/
void PostUpdateProcessing() {
switch (Caffe::mode()) {
case Caffe::CPU:
PostUpdateProcessing_cpu();
break;
case Caffe::GPU:
PostUpdateProcessing_gpu();
break;
default:
LOG(FATAL) << "Unknown caffe mode.";
}
}

protected:
/** The protobuf that stores the layer parameters */
Expand Down Expand Up @@ -363,6 +378,20 @@ class Layer {
Backward_cpu(top, propagate_down, bottom);
}

/**
* @brief Perform any processing required after the solver has updated
* network parameters. Called only when Caffe mode is CPU.
*/
virtual void PostUpdateProcessing_cpu() { /* Default behavior: no action.*/ }
/**
* @brief Perform any processing required after the solver has updated
* network parameters. Called only when Caffe mode is GPU.
*/
virtual void PostUpdateProcessing_gpu() {
// Call cpu code as a backup.
PostUpdateProcessing_cpu();
}

/**
* Called by the parent Layer's SetUp to check that the number of bottom
* and top Blobs provided as input match the expected numbers specified by
Expand Down
10 changes: 10 additions & 0 deletions include/caffe/neuron_layers.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -795,7 +795,17 @@ class PReLULayer : public NeuronLayer<Dtype> {
virtual void Backward_gpu(const vector<Blob<Dtype>*>& top,
const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& bottom);

/*********
* @brief Perform the post-update processing to constrain the negative slopes
* of the Prelu.
*********/
virtual void PostUpdateProcessing_cpu();
virtual void PostUpdateProcessing_gpu();

bool channel_shared_;
bool constrain_neg_slope_;
Dtype min_neg_slope_;
Dtype max_neg_slope_;
Blob<Dtype> multiplier_; // dot multiplier for backward computation of params
Blob<Dtype> backward_buff_; // temporary buffer for backward computation
Blob<Dtype> bottom_memory_; // memory for in-place computation
Expand Down
4 changes: 4 additions & 0 deletions include/caffe/util/device_alternate.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ void classname<Dtype>::funcname##_##gpu(const vector<Blob<Dtype>*>& top, \
const vector<bool>& propagate_down, \
const vector<Blob<Dtype>*>& bottom) { NO_GPU; } \

#define STUB_GPU_POSTUPDATEPROCESSING(classname) \
template <typename Dtype> \
void classname<Dtype>::PostUpdateProcessing_gpu() { NO_GPU; }

#else // Normal GPU + CPU Caffe.

#include <cublas_v2.h>
Expand Down
24 changes: 24 additions & 0 deletions src/caffe/layers/prelu_layer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ void PReLULayer<Dtype>::LayerSetUp(const vector<Blob<Dtype>*>& bottom,
<< "Number of axes of bottom blob must be >=2.";
PReLUParameter prelu_param = this->layer_param().prelu_param();
int channels = bottom[0]->channels();
constrain_neg_slope_ = prelu_param.constrain_neg_slope();
min_neg_slope_ = prelu_param.min_neg_slope();
max_neg_slope_ = prelu_param.max_neg_slope();

channel_shared_ = prelu_param.channel_shared();
if (this->blobs_.size() > 0) {
LOG(INFO) << "Skipping parameter initialization";
Expand Down Expand Up @@ -128,9 +132,29 @@ void PReLULayer<Dtype>::Backward_cpu(const vector<Blob<Dtype>*>& top,
}
}

template<typename Dtype>
void PReLULayer<Dtype>::PostUpdateProcessing_cpu() {
if (!this->constrain_neg_slope_) {
return;
}

// Constrain the slopes to be between the limits.
Dtype* slopes = this->blobs_[0]->mutable_cpu_data();
int slope_count = this->blobs_[0]->count();
for (int i = 0; i < slope_count; ++i) {
Dtype slope = *slopes;
if (slope < this->min_neg_slope_) {
*slopes = this->min_neg_slope_;
} else if (slope > this->max_neg_slope_) {
*slopes = this->max_neg_slope_;
}
slopes++;
}
}

#ifdef CPU_ONLY
STUB_GPU(PReLULayer);
STUB_GPU_POSTUPDATEPROCESSING(PReLULayer);
#endif

INSTANTIATE_CLASS(PReLULayer);
Expand Down
30 changes: 29 additions & 1 deletion src/caffe/layers/prelu_layer.cu
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,20 @@

namespace caffe {

// CUDA kernel for constraining negative slope.
template <typename Dtype>
__global__ void ConstrainNegSlope(Dtype* slopes, int slope_count,
Dtype min_slope, Dtype max_slope) {
CUDA_KERNEL_LOOP(index, slope_count) {
Dtype slope = slopes[index];
if (slope < min_slope) {
slopes[index] = min_slope;
} else if (slope > max_slope) {
slopes[index] = max_slope;
}
}
}

// CUDA kernele for forward
template <typename Dtype>
__global__ void PReLUForward(const int n, const int channels, const int dim,
Expand Down Expand Up @@ -120,8 +134,22 @@ void PReLULayer<Dtype>::Backward_gpu(const vector<Blob<Dtype>*>& top,
}
}

template<typename Dtype>
void PReLULayer<Dtype>::PostUpdateProcessing_gpu() {
if (!this->constrain_neg_slope_) {
return;
}

INSTANTIATE_LAYER_GPU_FUNCS(PReLULayer);
Dtype* slopes = this->blobs_[0]->mutable_gpu_data();
int slope_count = this->blobs_[0]->count();
// NOLINT_NEXT_LINE(whitespace/operators)
ConstrainNegSlope<Dtype><<<CAFFE_GET_BLOCKS(slope_count),
CAFFE_CUDA_NUM_THREADS>>>(
slopes, slope_count, this->min_neg_slope_, this->max_neg_slope_);
}

INSTANTIATE_LAYER_GPU_FUNCS(PReLULayer);
template void PReLULayer<float>::PostUpdateProcessing_gpu();
template void PReLULayer<double>::PostUpdateProcessing_gpu();

} // namespace caffe
3 changes: 3 additions & 0 deletions src/caffe/net.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -984,6 +984,9 @@ void Net<Dtype>::Update() {
for (int i = 0; i < learnable_params_.size(); ++i) {
learnable_params_[i]->Update();
}
for (int i = 0; i < layers_.size(); ++i) {
layers_[i]->PostUpdateProcessing();
}
}

template <typename Dtype>
Expand Down
7 changes: 7 additions & 0 deletions src/caffe/proto/caffe.proto
Original file line number Diff line number Diff line change
Expand Up @@ -1231,4 +1231,11 @@ message PReLUParameter {
optional FillerParameter filler = 1;
// Whether or not slope paramters are shared across channels.
optional bool channel_shared = 2 [default = false];
// Whether or not to constrain the negative slope between min_neg_slope and
// max_neg_slope.
optional bool constrain_neg_slope = 3 [default = true];

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since there is no migration procedure for those who have not specified constrain_neg_slope yet, I think that the default should be false. Otherwise this silently changes the behavior of any existing nets, and this can be really hard to debug.

Others may disagree though -- it's worth getting multiple opinions.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right. But it has no effect once a model is trained. If a trained model is just deployed, its behavior won't change. So to the extent that it changes behavior, it won't be until after they've done some more training. At which point they would be evaluating before deploying it anyway.

I don't mind changing the default to false. But I doubt most people will be aware that the option is there, and so will continue suffering along with crazy Prelu values (I was getting NaNs, and after instrumenting found it was due to an inf coming out of a Prelu in forward, going in to a softmax that generated NaN when dividing inf by inf). So if you're worried about hard-to-debug problems, I think a wacky negative slope is a bigger threat than a Prelu that's constrained to be somewhere in-between a ReLU (neg-slope == 0.0) and a linear unit (neg slope == 1.0).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, yeah. I agree that this is a reasonable updated default. It does change experiments that people may want to re-run, but caffe has already changed training defaults in the past (#2321).

// limits on the value that the negative slope is allowed to acquire.
// Enforced only if constrain_neg_slope = true.
optional float min_neg_slope = 4 [default = 0.0];
optional float max_neg_slope = 5 [default = 1.0];
}
39 changes: 39 additions & 0 deletions src/caffe/test/test_neuron_layer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -709,6 +709,45 @@ TYPED_TEST(NeuronLayerTest, TestPReLUInPlace) {
}
}

TYPED_TEST(NeuronLayerTest, TestPReLU_ConstrainNegSlope) {
typedef typename TypeParam::Dtype Dtype;
LayerParameter layer_param;
layer_param.mutable_prelu_param()->set_constrain_neg_slope(true);
PReLULayer<Dtype> layer(layer_param);
layer.SetUp(this->blob_bottom_vec_, this->blob_top_vec_);
FillerParameter filler_param;
filler_param.set_std(10.0f);
GaussianFiller<Dtype> filler(filler_param);
filler.Fill(layer.blobs()[0].get());

int less_than_zero = 0;
int greater_than_one = 0;
for (int i = 0; i < layer.blobs()[0]->count(); ++i) {
Dtype slope = layer.blobs()[0]->cpu_data()[i];
less_than_zero += (slope < 0.0f) ? 1 : 0;
greater_than_one += (slope > 1.0f) ? 1 : 0;
}

// Expect to have some values outside the range 0.0 to 1.0.
EXPECT_GT(less_than_zero, 0);
EXPECT_GT(greater_than_one, 0);

// Run the logic that constrains the negative slope.
layer.PostUpdateProcessing();

less_than_zero = 0;
greater_than_one = 0;
for (int i = 0; i < layer.blobs()[0]->count(); ++i) {
Dtype slope = layer.blobs()[0]->cpu_data()[i];
less_than_zero += (slope < 0.0f) ? 1 : 0;
greater_than_one += (slope > 1.0f) ? 1 : 0;
}

// Expect to have no values outside the range 0.0 to 1.0.
EXPECT_EQ(less_than_zero, 0);
EXPECT_EQ(greater_than_one, 0);
}

#ifdef USE_CUDNN
template <typename Dtype>
class CuDNNNeuronLayerTest : public GPUDeviceTest<Dtype> {
Expand Down