diff --git a/src/mlpack/methods/ann/layer/hard_tanh_layer.hpp b/src/mlpack/methods/ann/layer/hard_tanh_layer.hpp new file mode 100644 index 0000000000..89114b0e8d --- /dev/null +++ b/src/mlpack/methods/ann/layer/hard_tanh_layer.hpp @@ -0,0 +1,282 @@ +/** + * @file hard_tanh_layer.hpp + * @author Dhawal Arora + * + * Implementation of hard_tanh activation function. The function is mentioned below. + */ + +#ifndef __MLPACK_METHODS_ANN_LAYER_HARD_TANH_LAYER_HPP +#define __MLPACK_METHODS_ANN_LAYER_HARD_TANH_LAYER_HPP + +#include + +namespace mlpack { +namespace ann /** Artificial Neural Network. */ { + +/** + * The Hard Tanh activation function, defined by + * + * @f{eqnarray*}{ + * f(x) &=& \left\{ + * \begin{array}{lr} + * max & : x > maxValue \\ + * min & : x \le minValue \\ + * x & : otherwise + * \end{array} + * \right. + * f'(x) &=& \left\{ + * \begin{array}{lr} + * 0 & : x > maxValue \\ + * 0 & : x \le minValue \\ + * 1 & : otherwise + * \end{array} + * \right. + * @f} + */ + +template < + typename InputDataType = arma::mat, + typename OutputDataType = arma::mat +> + +class HardTanHLayer +{ + public: + + /** + * Constructor. Default maxValue is set to 1 and default minValue is set to -1. + * + */ + + HardTanHLayer(const double maxValue = 1.00, const double minValue = -1.00) : maxValue(maxValue), minValue(minValue) + { + // Nothing to do here. + } + + /** + * Ordinary feed forward pass of a neural network, evaluating the function + * f(x) by propagating the activity forward through f. + * + * @param x Input data used for evaluating the specified function. This is for just one input value. + * @return f(x) The activation value for the input. + */ + + double Forward(const double x) + { + return fn(x); + } + + /** + * Ordinary feed forward pass of a neural network, evaluating the function + * f(x) by propagating the activity forward through f. + * + * @param input Input data used for evaluating the specified function. + * @param output Resulting output activation. + */ + + template + void Forward(const InputType& input, OutputType& output) + { + fn(input, output); + } + + /** + * Ordinary feed backward pass of a neural network, calculating the function + * f(x) by propagating x backwards through f. Using the results from the feed + * forward pass. + * + * @param input The propagated input activation. This function is for just a single input. + * @param gy The backpropagated error. + * @return The calculated gradient. + */ + + double Backward(const double input, + const double gy) + { + return gy * deriv(input); + } + + /** + * Ordinary feed backward pass of a neural network, calculating the function + * f(x) by propagating x backwards through f. Using the results from the feed + * forward pass. + * + * @param input The propagated input activation. + * @param gy The backpropagated error. + * @param g The calculated gradient. + */ + template + void Backward(const DataType& input, + const DataType& gy, + DataType& g) + { + DataType derivative; + deriv(input, derivative); + g = gy % derivative; + } + + /** + * Ordinary feed backward pass of a neural network, calculating the function + * f(x) by propagating x backwards through f. Using the results from the feed + * forward pass. + * + * @param input The propagated input activation. + * @param gy The backpropagated error. + * @param g The calculated gradient. + */ + template + void Backward(const arma::Cube& input, + const arma::Mat& gy, + arma::Cube& g) + { + // Generate a cube using the backpropagated error matrix. + arma::Cube mappedError = arma::zeros(input.n_rows, + input.n_cols, input.n_slices); + + for (size_t s = 0, j = 0; s < mappedError.n_slices; s+= gy.n_cols, j++) + { + for (size_t i = 0; i < gy.n_cols; i++) + { + arma::Col temp = gy.col(i).subvec( + j * input.n_rows * input.n_cols, + (j + 1) * input.n_rows * input.n_cols - 1); + + mappedError.slice(s + i) = arma::Mat(temp.memptr(), + input.n_rows, input.n_cols); + } + } + + arma::Cube derivative; + deriv(input, derivative); + g = mappedError % derivative; + } + + //! Get the input parameter. + InputDataType const& InputParameter() const { return inputParameter; } + //! Modify the input parameter. + InputDataType& InputParameter() { return inputParameter; } + + //! Get the output parameter. + OutputDataType const& OutputParameter() const { return outputParameter; } + //! Modify the output parameter. + OutputDataType& OutputParameter() { return outputParameter; } + + //! Get the delta. + OutputDataType const& Delta() const { return delta; } + //! Modify the delta. + OutputDataType& Delta() { return delta; } + + //! Get the Maximum value. + double const& MaxValue() const { return maxValue; } + //! Modify the Maximum value. + double& MaxValue() { return maxValue; } + + //! Get the Minimum value. + double const& MinValue() const { return minValue; } + //! Modify the Minimum value. + double& MinValue() { return minValue; } + + + /** + * Serialize the layer. + */ + template + void Serialize(Archive& /* ar */, const unsigned int /* version */) + { + /* Nothing to do here */ + } + + private: + + + /** + * Computes the HardTanH function. + * + * @param x Input data. + * @return f(x). + */ + double fn(const double x) + { + if (x > maxValue) + return maxValue; + else if (x < minValue) + return minValue; + return x; + } + + /** + * Computes the HardTanH function using a dense matrix as input. + * + * @param x Input data. + * @param y The resulting output activation. + */ + + template + void fn(const arma::Mat& x, arma::Mat& y) + { + y = x; + y = y.transform( [&](eT val) { return std::min( std::max( val, minValue ), maxValue ); } ); + } + + /** + * Computes the HardTanH function using a 3rd-order tensor as input. + * + * @param x Input data. + * @param y The resulting output activation. + */ + template + void fn(const arma::Cube& x, arma::Cube& y) + { + y = x; + for (size_t s = 0; s < x.n_slices; s++) + fn(x.slice(s), y.slice(s)); + } + + /** + * Computes the first derivative of the HardTanH function. + * + * @param x Input data. + * @return f'(x) + */ + double deriv(const double x) + { + return (x > maxValue || x < minValue) ? 0 : 1; + } + + /** + * Computes the first derivative of the HardTanH function. + * + * @param y Input activations. + * @param x The resulting derivatives. + */ + template + void deriv(const InputType& x, OutputType& y) + { + y = x; + + for (size_t i = 0; i < x.n_elem; i++) + y(i) = deriv(x(i)); + } + + //! Locally-stored delta object. + OutputDataType delta; + + //! Locally-stored input parameter object. + InputDataType inputParameter; + + //! Locally-stored output parameter object. + OutputDataType outputParameter; + + //! Maximum value for the HardTanH function. + double maxValue; + + //! Minimum value for the HardTanH function. + double minValue; + + +}; // class HardTanHLayer + +} // namespace ann +} // namespace mlpack + +#endif diff --git a/src/mlpack/tests/activation_functions_test.cpp b/src/mlpack/tests/activation_functions_test.cpp index 5c40e2ee66..986e89a853 100644 --- a/src/mlpack/tests/activation_functions_test.cpp +++ b/src/mlpack/tests/activation_functions_test.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include "old_boost_test_definitions.hpp" @@ -120,6 +121,64 @@ void CheckInverseCorrect(const arma::colvec input) } } + +/* + * Implementation of the HardTanH activation function test. The function is implemented as a HardTanH Layer + * in file hard_tanh_layer.hpp + * @param input Input data used for evaluating the HardTanH activation function. + * @param target Target data used to evaluate the HardTanH activation. + * + */ +void CheckHardTanHActivationCorrect(const arma::colvec input, const arma::colvec target) +{ + HardTanHLayer<> htf; + // Test the activation function using a single value as input. + for (size_t i = 0; i < target.n_elem; i++) + { + BOOST_REQUIRE_CLOSE(htf.Forward(input.at(i)), + target.at(i), 1e-3); + } + + // Test the activation function using the entire vector as input. + arma::colvec activations; + htf.Forward(input, activations); + for (size_t i = 0; i < activations.n_elem; i++) + { + BOOST_REQUIRE_CLOSE(activations.at(i), target.at(i), 1e-3); + } +} + +/* + * Implementation of the HardTanH activation function derivative test. The derivative is implemented in HardTanH Layer + * in file hard_tanh_layer.hpp + * @param input Input data used for evaluating the HardTanH activation function. + * @param target Target data used to evaluate the HardTanH activation. + * + */ + +void CheckHardTanHDerivativeCorrect(const arma::colvec input, const arma::colvec target) +{ + HardTanHLayer<> htf; + // Test the calculation of the derivatives using a single value as input. + for (size_t i = 0; i < target.n_elem; i++) + { + BOOST_REQUIRE_CLOSE(htf.Backward(input.at(i), 1), + target.at(i), 1e-3); + } + + // Test the calculation of the derivatives using the entire vector as input. + arma::colvec derivatives; + // This error vector will be set to 1 to get the derivatives. + arma::colvec error(input.n_elem); + htf.Backward(input, (arma::colvec)error.ones(), derivatives); + for (size_t i = 0; i < derivatives.n_elem; i++) + { + BOOST_REQUIRE_CLOSE(derivatives.at(i), target.at(i), 1e-3); + } +} + + + /** * Basic test of the tanh function. */ @@ -199,4 +258,21 @@ BOOST_AUTO_TEST_CASE(RectifierFunctionTest) desiredDerivatives); } +/** + * Basic test of the HardTanH function. + */ +BOOST_AUTO_TEST_CASE(HardTanHFunctionTest) +{ + const arma::colvec desiredActivations("-1 1 1 -1 \ + 1 -1 1 0"); + + const arma::colvec desiredDerivatives("0 0 0 0 \ + 1 1 0 1"); + + CheckHardTanHActivationCorrect(activationData, desiredActivations); + CheckHardTanHDerivativeCorrect(activationData, desiredDerivatives); +} + + + BOOST_AUTO_TEST_SUITE_END();