Skip to content

Commit

Permalink
Minimize Neural Layer Unit Test Redundancies (shogun-toolbox#4424)
Browse files Browse the repository at this point in the history
* Fixed redundancies in RectifiedLinear and Softmax Layer unit tests
* Add common fixture class + formatting
* Remove random seed
  • Loading branch information
saatvikshah authored and vigsterkr committed Mar 9, 2019
1 parent a317771 commit 7349e5b
Show file tree
Hide file tree
Showing 3 changed files with 233 additions and 241 deletions.
110 changes: 110 additions & 0 deletions tests/unit/neuralnets/NeuralLayerTestFixture.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/*
* This software is distributed under BSD 3-clause license (see LICENSE file).
*
* Authors: Saatvik Shah
*/

#ifndef NEURAL_LAYER_TEST_FIXTURE_H
#define NEURAL_LAYER_TEST_FIXTURE_H

#include <shogun/lib/SGMatrix.h>
#include <shogun/lib/SGVector.h>
#include <shogun/mathematics/Math.h>
#include <shogun/neuralnets/NeuralInputLayer.h>
#include <shogun/neuralnets/NeuralLinearLayer.h>

#include <gtest/gtest.h>

#include <memory>
#include <tuple>

using namespace shogun;

class NeuralLayerTestFixture : public ::testing::Test
{
public:
template <typename T>
auto create_rand_matrix(
int32_t num_rows, int32_t num_cols, T lower_bound, T upper_bound)
{
auto data_batch = SGMatrix<T>(num_rows, num_cols);
for (size_t i = 0; i < num_rows * num_cols; i++)
{
data_batch[i] = CMath::random(lower_bound, upper_bound);
}
return data_batch;
}

/**
* Generates a random dataset according to provided specifications.
* Then performs the boilerplate needed to setup the input layer
* for this dataset.
* @tparam T: Type of input data eg. float64_t
* @param num_features: For the randomized dataset
* @param num_samples: For the randomized dataset
* @param lower_bound/upper_bound: Dataset generated has values between:
* [lower_bound, upper_bound]
* @param add_to_layers: Should this layer be added to the dynamic layer
* list(`m_layers`)
* This is normally done when expecting to connect this layer ahead to some
* other layer
* @return: The randomized dataset and corresponding Neural input layer
*/
template <typename T>
auto setup_input_layer(
int32_t num_features, int32_t num_samples, T lower_bound, T upper_bound,
bool add_to_layers = true)
{
auto data_batch = create_rand_matrix(
num_features, num_samples, lower_bound, upper_bound);
auto input_layer = new CNeuralInputLayer(data_batch.num_rows);
input_layer->set_batch_size(data_batch.num_cols);
input_layer->compute_activations(data_batch);
if (add_to_layers)
{
m_layers->append_element(input_layer);
}
return std::make_tuple(data_batch, input_layer);
}

/**
* Initializes Linear layer metadata according to specifications provided
* @param layer: The layer to initialize
* @param input_indices: the indices of layers from `m_layers` that are
* inputs to this layer
* @param batch_size: Batch size for this layer
* @param sigma: The parameters are initialized as Normal(0 mean, sigma
* stdev)
* @param add_to_layers: Whether this layer should be added to the dynamic
* layer list(`m_layers`)
* This is normally done when expecting to connect this layer ahead to some
* other layer.
* @return: The initialized parameters
*/
auto init_linear_layer(
CNeuralLinearLayer* layer, const SGVector<int32_t>& input_indices,
int32_t batch_size, double sigma, bool add_to_layers) const
{
if (add_to_layers)
{
m_layers->append_element(layer);
}
layer->initialize_neural_layer(m_layers.get(), input_indices);
SGVector<float64_t> params(layer->get_num_parameters());
SGVector<bool> param_regularizable(layer->get_num_parameters());
layer->initialize_parameters(params, param_regularizable, sigma);
layer->set_batch_size(batch_size);
layer->compute_activations(params, m_layers.get());
return params;
}

// dynamic list of layers
std::unique_ptr<CDynamicObjectArray> m_layers;

protected:
void SetUp() final
{
m_layers = std::make_unique<CDynamicObjectArray>();
}
};
#endif // NEURAL_LAYER_TEST_FIXTURE_H
164 changes: 55 additions & 109 deletions tests/unit/neuralnets/NeuralRectifiedLinearLayer_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -31,172 +31,118 @@
* Written (W) 2014 Khaled Nasr
*/

#include <shogun/neuralnets/NeuralRectifiedLinearLayer.h>
#include <shogun/neuralnets/NeuralInputLayer.h>
#include <shogun/lib/SGVector.h>
#include "NeuralLayerTestFixture.h"
#include <shogun/lib/SGMatrix.h>
#include <shogun/mathematics/Math.h>
#include <gtest/gtest.h>
#include <shogun/lib/SGVector.h>
#include <shogun/mathematics/linalg/LinalgNamespace.h>
#include <shogun/mathematics/linalg/LinalgSpecialPurposes.h>
#include <shogun/neuralnets/NeuralInputLayer.h>
#include <shogun/neuralnets/NeuralRectifiedLinearLayer.h>

#include <tuple>

using namespace shogun;

using NeuralRectifiedLinearLayerTest = NeuralLayerTestFixture;

/** Compares the activations computed using the layer against manually computed
* activations
*/
TEST(NeuralRectifiedLinearLayer, compute_activations)
TEST_F(NeuralRectifiedLinearLayerTest, compute_activations)
{
CNeuralRectifiedLinearLayer layer(9);

// initialize some random inputs
CMath::init_random(100);
SGMatrix<float64_t> x(12,3);
for (int32_t i=0; i<x.num_rows*x.num_cols; i++)
x[i] = CMath::random(-10.0,10.0);

CNeuralInputLayer* input = new CNeuralInputLayer (x.num_rows);
input->set_batch_size(x.num_cols);

CDynamicObjectArray* layers = new CDynamicObjectArray();
layers->append_element(input);
SGMatrix<float64_t> x;
CNeuralInputLayer* input;
std::tie(x, input) = setup_input_layer<float64_t>(12, 3, -10.0, 10.0);

// initialize the rectified linear layer
CNeuralRectifiedLinearLayer layer(9);
SGVector<int32_t> input_indices(1);
input_indices[0] = 0;

// initialize the layer
layer.initialize_neural_layer(layers, input_indices);
SGVector<float64_t> params(layer.get_num_parameters());
SGVector<bool> param_regularizable(layer.get_num_parameters());
layer.initialize_parameters(params, param_regularizable, 1.0);
layer.set_batch_size(x.num_cols);

// compute the layer's activations
input->compute_activations(x);
layer.compute_activations(params, layers);
auto params =
init_linear_layer(&layer, input_indices, x.num_cols, 1.0, false);
SGMatrix<float64_t> A = layer.get_activations();

// manually compute the layer's activations
// Manually compute Recitified linear activations
auto biases =
SGVector<float64_t>(params.vector, layer.get_num_neurons(), 0);
auto weights = SGMatrix<float64_t>(
params.vector, layer.get_num_neurons(), x.num_rows,
layer.get_num_neurons());
SGMatrix<float64_t> A_ref(layer.get_num_neurons(), x.num_cols);

float64_t* biases = params.vector;
float64_t* weights = biases + layer.get_num_neurons();

for (int32_t i=0; i<A_ref.num_rows; i++)
{
for (int32_t j=0; j<A_ref.num_cols; j++)
{
A_ref(i,j) = biases[i];

for (int32_t k=0; k<x.num_rows; k++)
A_ref(i,j) += weights[i+k*A_ref.num_rows]*x(k,j);

A_ref(i,j) = CMath::max<float64_t>(0, A_ref(i,j));
}
}
shogun::linalg::add_vector(
shogun::linalg::matrix_prod(weights, x), biases, A_ref);
shogun::linalg::rectified_linear(A_ref, A_ref);

// compare
EXPECT_EQ(A_ref.num_rows, A.num_rows);
EXPECT_EQ(A_ref.num_cols, A.num_cols);
for (int32_t i=0; i<A.num_rows*A.num_cols; i++)
for (int32_t i = 0; i < A.num_rows * A.num_cols; i++)
EXPECT_NEAR(A_ref[i], A[i], 1e-12);

SG_UNREF(layers);
}

/** Compares the parameter gradients computed using the layer, when the layer
* is used as a hidden layer, against gradients computed using numerical
* approximation
*/
TEST(NeuralRectifiedLinearLayer, compute_parameter_gradients_hidden)
TEST_F(NeuralRectifiedLinearLayerTest, compute_parameter_gradients_hidden)
{
SGMatrix<float64_t> x1(12,3);
for (int32_t i=0; i<x1.num_rows*x1.num_cols; i++)
x1[i] = CMath::random(-10.0,10.0);

CNeuralInputLayer* input1 = new CNeuralInputLayer (x1.num_rows);
input1->set_batch_size(x1.num_cols);

SGMatrix<float64_t> x2(7,3);
for (int32_t i=0; i<x2.num_rows*x2.num_cols; i++)
x2[i] = CMath::random(-10.0,10.0);

CNeuralInputLayer* input2 = new CNeuralInputLayer (x2.num_rows);
input2->set_batch_size(x2.num_cols);
// initialize some random inputs
SGMatrix<float64_t> x1, x2;
CNeuralInputLayer *input1, *input2;
std::tie(x1, input1) = setup_input_layer<float64_t>(12, 3, -10.0, 10.0);
std::tie(x2, input2) = setup_input_layer<float64_t>(7, 3, -10.0, 10.0);

// initialize hidden the layer
// initialize the hidden rectified linear layer
CNeuralLinearLayer* layer_hid = new CNeuralRectifiedLinearLayer(5);

CDynamicObjectArray* layers = new CDynamicObjectArray();
layers->append_element(input1);
layers->append_element(input2);
layers->append_element(layer_hid);

SGVector<int32_t> input_indices_hid(2);
input_indices_hid[0] = 0;
input_indices_hid[1] = 1;

SGVector<int32_t> input_indices_out(1);
input_indices_out[0] = 2;

SGMatrix<float64_t> y(9,3);
for (int32_t i=0; i<y.num_rows*y.num_cols; i++)
y[i] = CMath::random(0.0,1.0);

// initialize the hidden layer
layer_hid->initialize_neural_layer(layers, input_indices_hid);
SGVector<float64_t> param_hid(layer_hid->get_num_parameters());
SGVector<bool> param_regularizable_hid(layer_hid->get_num_parameters());
layer_hid->initialize_parameters(param_hid, param_regularizable_hid, 0.01);
layer_hid->set_batch_size(x1.num_cols);
auto param_hid = init_linear_layer(
layer_hid, input_indices_hid, x1.num_cols, 0.01, true);

// initialize the output layer
auto y = create_rand_matrix<float64_t>(9, 3, 0.0, 1.0);
CNeuralLinearLayer layer_out(y.num_rows);
layer_out.initialize_neural_layer(layers, input_indices_out);
SGVector<float64_t> param_out(layer_out.get_num_parameters());
SGVector<bool> param_regularizable_out(layer_out.get_num_parameters());
layer_out.initialize_parameters(param_out, param_regularizable_out, 0.01);
layer_out.set_batch_size(x1.num_cols);

// compute activations
input1->compute_activations(x1);
input2->compute_activations(x2);
layer_hid->compute_activations(param_hid, layers);
layer_out.compute_activations(param_out, layers);
SGVector<int32_t> input_indices_out(1);
input_indices_out[0] = 2;
auto param_out = init_linear_layer(
&layer_out, input_indices_out, x1.num_cols, 0.01, false);

// compute gradients
layer_hid->get_activation_gradients().zero();
SGVector<float64_t> gradients_out(layer_out.get_num_parameters());
layer_out.compute_gradients(param_out, y, layers, gradients_out);
layer_out.compute_gradients(param_out, y, m_layers.get(), gradients_out);

SGVector<float64_t> gradients_hid(layer_hid->get_num_parameters());
layer_hid->compute_gradients(param_hid, SGMatrix<float64_t>(),
layers, gradients_hid);
layer_hid->compute_gradients(
param_hid, SGMatrix<float64_t>(), m_layers.get(), gradients_hid);

// manually compute parameter gradients
SGVector<float64_t> gradients_hid_numerical(layer_hid->get_num_parameters());
SGVector<float64_t> gradients_hid_numerical(
layer_hid->get_num_parameters());
float64_t epsilon = 1e-9;
for (int32_t i=0; i<layer_hid->get_num_parameters(); i++)
for (int32_t i = 0; i < layer_hid->get_num_parameters(); i++)
{
param_hid[i] += epsilon;
input1->compute_activations(x1);
input2->compute_activations(x2);
layer_hid->compute_activations(param_hid, layers);
layer_out.compute_activations(param_out, layers);
layer_hid->compute_activations(param_hid, m_layers.get());
layer_out.compute_activations(param_out, m_layers.get());
float64_t error_plus = layer_out.compute_error(y);

param_hid[i] -= 2*epsilon;
param_hid[i] -= 2 * epsilon;
input1->compute_activations(x1);
input2->compute_activations(x2);
layer_hid->compute_activations(param_hid, layers);
layer_out.compute_activations(param_out, layers);
layer_hid->compute_activations(param_hid, m_layers.get());
layer_out.compute_activations(param_out, m_layers.get());
float64_t error_minus = layer_out.compute_error(y);
param_hid[i] += epsilon;

gradients_hid_numerical[i] = (error_plus-error_minus)/(2*epsilon);
gradients_hid_numerical[i] = (error_plus - error_minus) / (2 * epsilon);
}

// compare
for (int32_t i=0; i<gradients_hid_numerical.vlen; i++)
for (int32_t i = 0; i < gradients_hid_numerical.vlen; i++)
EXPECT_NEAR(gradients_hid_numerical[i], gradients_hid[i], 1e-6);

SG_UNREF(layers);
}

0 comments on commit 7349e5b

Please sign in to comment.