diff --git a/src/mlpack/methods/ann/CMakeLists.txt b/src/mlpack/methods/ann/CMakeLists.txt index 3c8236809c..8888113548 100644 --- a/src/mlpack/methods/ann/CMakeLists.txt +++ b/src/mlpack/methods/ann/CMakeLists.txt @@ -20,6 +20,7 @@ add_subdirectory(gan) add_subdirectory(rbm) add_subdirectory(augmented) add_subdirectory(regularizer) +add_subdirectory(util) # Add directory name to sources. set(DIR_SRCS) diff --git a/src/mlpack/methods/ann/ffn_impl.hpp b/src/mlpack/methods/ann/ffn_impl.hpp index c16de548f4..401c094ca6 100644 --- a/src/mlpack/methods/ann/ffn_impl.hpp +++ b/src/mlpack/methods/ann/ffn_impl.hpp @@ -23,6 +23,8 @@ #include "visitor/set_input_height_visitor.hpp" #include "visitor/set_input_width_visitor.hpp" +#include "util/check_input_shape.hpp" + namespace mlpack { namespace ann /** Artificial Neural Network. */ { @@ -109,6 +111,10 @@ double FFN::Train( OptimizerType& optimizer, CallbackTypes&&... callbacks) { + CheckInputShape > >(network, + predictors.n_rows, + "FFN<>::Train()"); + ResetData(std::move(predictors), std::move(responses)); WarnMessageMaxIterations(optimizer, this->predictors.n_cols); @@ -131,6 +137,10 @@ double FFN::Train( arma::mat responses, CallbackTypes&&... callbacks) { + CheckInputShape > >(network, + predictors.n_rows, + "FFN<>::Train()"); + ResetData(std::move(predictors), std::move(responses)); OptimizerType optimizer; @@ -217,6 +227,10 @@ template::Predict( arma::mat predictors, arma::mat& results) { + CheckInputShape > >(network, + predictors.n_rows, + "FFN<>::Predict()"); + if (parameter.is_empty()) ResetParameters(); @@ -250,6 +264,10 @@ template double FFN::Evaluate( const PredictorsType& predictors, const ResponsesType& responses) { + CheckInputShape > >(network, + predictors.n_rows, + "FFN<>::Evaluate()"); + if (parameter.is_empty()) ResetParameters(); diff --git a/src/mlpack/methods/ann/layer/atrous_convolution.hpp b/src/mlpack/methods/ann/layer/atrous_convolution.hpp index 478f62abe2..daddab76f2 100644 --- a/src/mlpack/methods/ann/layer/atrous_convolution.hpp +++ b/src/mlpack/methods/ann/layer/atrous_convolution.hpp @@ -263,6 +263,12 @@ class AtrousConvolution return (outSize * inSize * kernelWidth * kernelHeight) + outSize; } + //! Get the shape of the input. + size_t InputShape() const + { + return inputHeight * inputWidth * inSize; + } + /** * Serialize the layer. */ diff --git a/src/mlpack/methods/ann/layer/bilinear_interpolation.hpp b/src/mlpack/methods/ann/layer/bilinear_interpolation.hpp index 817763e973..8595bc4d57 100644 --- a/src/mlpack/methods/ann/layer/bilinear_interpolation.hpp +++ b/src/mlpack/methods/ann/layer/bilinear_interpolation.hpp @@ -118,6 +118,12 @@ class BilinearInterpolation //! Modify the depth of the input. size_t& InDepth() { return depth; } + //! Get the shape of the input. + size_t InputShape() const + { + return inRowSize; + } + /** * Serialize the layer. */ diff --git a/src/mlpack/methods/ann/layer/convolution.hpp b/src/mlpack/methods/ann/layer/convolution.hpp index bcebf756cf..1571c3e414 100644 --- a/src/mlpack/methods/ann/layer/convolution.hpp +++ b/src/mlpack/methods/ann/layer/convolution.hpp @@ -259,6 +259,12 @@ class Convolution return (outSize * inSize * kernelWidth * kernelHeight) + outSize; } + //! Get the shape of the input. + size_t InputShape() const + { + return inputHeight * inputWidth * inSize; + } + /** * Serialize the layer. */ diff --git a/src/mlpack/methods/ann/layer/fast_lstm.hpp b/src/mlpack/methods/ann/layer/fast_lstm.hpp index 212fc8f503..80ddcabca6 100644 --- a/src/mlpack/methods/ann/layer/fast_lstm.hpp +++ b/src/mlpack/methods/ann/layer/fast_lstm.hpp @@ -182,6 +182,12 @@ class FastLSTM return 4 * outSize * inSize + 4 * outSize + 4 * outSize * outSize; } + //! Get the shape of the input. + size_t InputShape() const + { + return inSize; + } + /** * Serialize the layer */ diff --git a/src/mlpack/methods/ann/layer/glimpse.hpp b/src/mlpack/methods/ann/layer/glimpse.hpp index 661fab551d..99a268b6a8 100644 --- a/src/mlpack/methods/ann/layer/glimpse.hpp +++ b/src/mlpack/methods/ann/layer/glimpse.hpp @@ -182,6 +182,12 @@ class Glimpse //! Get the used glimpse size (height = width). size_t GlimpseSize() const { return size;} + //! Get the shape of the input. + size_t InputShape() const + { + return inSize; + } + /** * Serialize the layer. */ diff --git a/src/mlpack/methods/ann/layer/gru.hpp b/src/mlpack/methods/ann/layer/gru.hpp index b732b41af1..3d98a712d8 100644 --- a/src/mlpack/methods/ann/layer/gru.hpp +++ b/src/mlpack/methods/ann/layer/gru.hpp @@ -155,6 +155,12 @@ class GRU //! Get the number of output units. size_t OutSize() const { return outSize; } + //! Get the shape of the input. + size_t InputShape() const + { + return inSize; + } + /** * Serialize the layer */ diff --git a/src/mlpack/methods/ann/layer/highway.hpp b/src/mlpack/methods/ann/layer/highway.hpp index 7e5893c424..aa539a4972 100644 --- a/src/mlpack/methods/ann/layer/highway.hpp +++ b/src/mlpack/methods/ann/layer/highway.hpp @@ -177,6 +177,12 @@ class Highway //! Get the number of input units. size_t InSize() const { return inSize; } + //! Get the shape of the input. + size_t InputShape() const + { + return inSize; + } + /** * Serialize the layer. */ diff --git a/src/mlpack/methods/ann/layer/layer_norm.hpp b/src/mlpack/methods/ann/layer/layer_norm.hpp index ba0d3f4c29..c22408c221 100644 --- a/src/mlpack/methods/ann/layer/layer_norm.hpp +++ b/src/mlpack/methods/ann/layer/layer_norm.hpp @@ -148,6 +148,12 @@ class LayerNorm //! Get the value of epsilon. double Epsilon() const { return eps; } + //! Get the shape of the input. + size_t InputShape() const + { + return size; + } + /** * Serialize the layer. */ diff --git a/src/mlpack/methods/ann/layer/layer_traits.hpp b/src/mlpack/methods/ann/layer/layer_traits.hpp index b9e2621d89..a6a447f43d 100644 --- a/src/mlpack/methods/ann/layer/layer_traits.hpp +++ b/src/mlpack/methods/ann/layer/layer_traits.hpp @@ -120,6 +120,10 @@ HAS_MEM_FUNC(Bias, HasBiasCheck); // we can use with SFINAE to catch when a type has a MaxIterations() function. HAS_MEM_FUNC(MaxIterations, HasMaxIterations); +// This gives us a HasInShapeCheck type we can use with SFINAE to catch when +// a type has a function named InputShape. +HAS_ANY_METHOD_FORM(InputShape, HasInputShapeCheck); + } // namespace ann } // namespace mlpack diff --git a/src/mlpack/methods/ann/layer/linear.hpp b/src/mlpack/methods/ann/layer/linear.hpp index f2c8015e04..cc31117c53 100644 --- a/src/mlpack/methods/ann/layer/linear.hpp +++ b/src/mlpack/methods/ann/layer/linear.hpp @@ -152,6 +152,12 @@ class Linear return (inSize * outSize) + outSize; } + //! Get the shape of the input. + size_t InputShape() const + { + return inSize; + } + /** * Serialize the layer */ diff --git a/src/mlpack/methods/ann/layer/linear3d.hpp b/src/mlpack/methods/ann/layer/linear3d.hpp index e9a6a8615e..24e3de56e6 100644 --- a/src/mlpack/methods/ann/layer/linear3d.hpp +++ b/src/mlpack/methods/ann/layer/linear3d.hpp @@ -148,6 +148,12 @@ class Linear3D //! Modify the bias weights of the layer. OutputDataType& Bias() { return bias; } + //! Get the shape of the input. + size_t InputShape() const + { + return inSize; + } + /** * Serialize the layer */ diff --git a/src/mlpack/methods/ann/layer/linear_no_bias.hpp b/src/mlpack/methods/ann/layer/linear_no_bias.hpp index a06e97e735..7182e84238 100644 --- a/src/mlpack/methods/ann/layer/linear_no_bias.hpp +++ b/src/mlpack/methods/ann/layer/linear_no_bias.hpp @@ -123,6 +123,12 @@ class LinearNoBias //! Modify the gradient. OutputDataType& Gradient() { return gradient; } + //! Get the shape of the input. + size_t InputShape() const + { + return inSize; + } + /** * Serialize the layer */ diff --git a/src/mlpack/methods/ann/layer/lstm.hpp b/src/mlpack/methods/ann/layer/lstm.hpp index d535f0ee2b..125794cb46 100644 --- a/src/mlpack/methods/ann/layer/lstm.hpp +++ b/src/mlpack/methods/ann/layer/lstm.hpp @@ -183,6 +183,12 @@ class LSTM //! Get the number of output units. size_t OutSize() const { return outSize; } + //! Get the shape of the input. + size_t InputShape() const + { + return inSize; + } + /** * Serialize the layer */ diff --git a/src/mlpack/methods/ann/layer/minibatch_discrimination.hpp b/src/mlpack/methods/ann/layer/minibatch_discrimination.hpp index 88f8dff1ba..3448f36b5d 100644 --- a/src/mlpack/methods/ann/layer/minibatch_discrimination.hpp +++ b/src/mlpack/methods/ann/layer/minibatch_discrimination.hpp @@ -134,6 +134,12 @@ class MiniBatchDiscrimination //! Modify the gradient. OutputDataType& Gradient() { return gradient; } + //! Get the shape of the input. + size_t InputShape() const + { + return A; + } + /** * Serialize the layer. */ diff --git a/src/mlpack/methods/ann/layer/multihead_attention.hpp b/src/mlpack/methods/ann/layer/multihead_attention.hpp index 3421fa4183..ec079d197d 100644 --- a/src/mlpack/methods/ann/layer/multihead_attention.hpp +++ b/src/mlpack/methods/ann/layer/multihead_attention.hpp @@ -176,6 +176,11 @@ class MultiheadAttention //! Modify the parameters. OutputDataType& Parameters() { return weights; } + size_t InputShape() const + { + return embedDim * (tgtSeqLen + 2 * srcSeqLen); + } + private: //! Element Type of the input. typedef typename OutputDataType::elem_type ElemType; diff --git a/src/mlpack/methods/ann/layer/noisylinear.hpp b/src/mlpack/methods/ann/layer/noisylinear.hpp index 84bacbe472..993f80725f 100644 --- a/src/mlpack/methods/ann/layer/noisylinear.hpp +++ b/src/mlpack/methods/ann/layer/noisylinear.hpp @@ -139,6 +139,12 @@ class NoisyLinear //! Modify the gradient. OutputDataType& Gradient() { return gradient; } + //! Get the shape of the input. + size_t InputShape() const + { + return inSize; + } + //! Modify the bias weights of the layer. arma::mat& Bias() { return bias; } diff --git a/src/mlpack/methods/ann/layer/positional_encoding.hpp b/src/mlpack/methods/ann/layer/positional_encoding.hpp index 1e6cb445a0..8678426414 100644 --- a/src/mlpack/methods/ann/layer/positional_encoding.hpp +++ b/src/mlpack/methods/ann/layer/positional_encoding.hpp @@ -93,6 +93,11 @@ class PositionalEncoding //! Get the positional encoding vector. InputDataType const& Encoding() const { return positionalEncoding; } + size_t InputShape() const + { + return embedDim * maxSequenceLength; + } + /** * Serialize the layer */ diff --git a/src/mlpack/methods/ann/layer/radial_basis_function.hpp b/src/mlpack/methods/ann/layer/radial_basis_function.hpp index b867e9e936..2387d612ab 100644 --- a/src/mlpack/methods/ann/layer/radial_basis_function.hpp +++ b/src/mlpack/methods/ann/layer/radial_basis_function.hpp @@ -110,6 +110,12 @@ class RBF //! Modify the delta. OutputDataType& Delta() { return delta; } + //! Get the shape of the input. + size_t InputShape() const + { + return inSize; + } + /** * Serialize the layer. */ diff --git a/src/mlpack/methods/ann/layer/recurrent.hpp b/src/mlpack/methods/ann/layer/recurrent.hpp index 88f6d97ab4..b82fcb175b 100644 --- a/src/mlpack/methods/ann/layer/recurrent.hpp +++ b/src/mlpack/methods/ann/layer/recurrent.hpp @@ -18,6 +18,7 @@ #include "../visitor/delta_visitor.hpp" #include "../visitor/copy_visitor.hpp" #include "../visitor/output_parameter_visitor.hpp" +#include "../visitor/input_shape_visitor.hpp" #include "layer_types.hpp" #include "add_merge.hpp" @@ -138,6 +139,9 @@ class Recurrent //! Get the number of steps to backpropagate through time. size_t const& Rho() const { return rho; } + //! Get the shape of the input. + size_t InputShape() const; + /** * Serialize the layer */ diff --git a/src/mlpack/methods/ann/layer/recurrent_impl.hpp b/src/mlpack/methods/ann/layer/recurrent_impl.hpp index 43d807dd1a..e6b933bd20 100644 --- a/src/mlpack/methods/ann/layer/recurrent_impl.hpp +++ b/src/mlpack/methods/ann/layer/recurrent_impl.hpp @@ -19,6 +19,7 @@ #include "../visitor/backward_visitor.hpp" #include "../visitor/gradient_visitor.hpp" #include "../visitor/gradient_zero_visitor.hpp" +#include "../visitor/input_shape_visitor.hpp" namespace mlpack { namespace ann /** Artificial Neural Network. */ { @@ -125,6 +126,53 @@ Recurrent::Recurrent( this->network.push_back(recurrentModule); } +template +size_t Recurrent::InputShape() const +{ + const size_t inputShapeStartModule = boost::apply_visitor(InShapeVisitor(), startModule); + // Return the input shape of the first module that we have. + if (inputShapeStartModule != 0) + { + return inputShapeStartModule; + } + // If input shape of first module is 0. + else + { + // Return input shape of the second module that we have. + const size_t inputShapeInputModule = boost::apply_visitor(InShapeVisitor(), inputModule); + if (inputShapeInputModule != 0) + { + return inputShapeInputModule; + // If the input shape of second module is 0. + } + else + { + // Return input shape of the third module that we have. + const size_t inputShapeFeedbackModule = boost::apply_visitor(InShapeVisitor(), + feedbackModule); + if (inputShapeFeedbackModule != 0) + { + return inputShapeFeedbackModule; + // If the input shape of the third module is 0. + } + else + { + // Return the shape of the fourth module that we have. + const size_t inputShapeTransferModule = boost::apply_visitor(InShapeVisitor(), + transferModule); + if (inputShapeTransferModule != 0) + { + return inputShapeTransferModule; + } + // If the input shape of the fourth module is 0. + else + return 0; + } + } + } +} + template template diff --git a/src/mlpack/methods/ann/layer/reparametrization.hpp b/src/mlpack/methods/ann/layer/reparametrization.hpp index f6e6fe3b7f..65aa96da53 100644 --- a/src/mlpack/methods/ann/layer/reparametrization.hpp +++ b/src/mlpack/methods/ann/layer/reparametrization.hpp @@ -130,6 +130,11 @@ class Reparametrization //! Get the value of the beta hyperparameter. double Beta() const { return beta; } + size_t InputShape() const + { + return 2 * latentSize; + } + /** * Serialize the layer */ diff --git a/src/mlpack/methods/ann/layer/sequential.hpp b/src/mlpack/methods/ann/layer/sequential.hpp index f4466161a0..ebf3c6fd6a 100644 --- a/src/mlpack/methods/ann/layer/sequential.hpp +++ b/src/mlpack/methods/ann/layer/sequential.hpp @@ -23,6 +23,7 @@ #include "../visitor/output_height_visitor.hpp" #include "../visitor/output_parameter_visitor.hpp" #include "../visitor/output_width_visitor.hpp" +#include "../visitor/input_shape_visitor.hpp" #include "layer_types.hpp" #include "add_merge.hpp" @@ -184,6 +185,8 @@ class Sequential //! Modify the gradient. arma::mat& Gradient() { return gradient; } + size_t InputShape() const; + /** * Serialize the layer */ diff --git a/src/mlpack/methods/ann/layer/sequential_impl.hpp b/src/mlpack/methods/ann/layer/sequential_impl.hpp index 5e9c4cd6f6..1290a15ebb 100644 --- a/src/mlpack/methods/ann/layer/sequential_impl.hpp +++ b/src/mlpack/methods/ann/layer/sequential_impl.hpp @@ -21,6 +21,7 @@ #include "../visitor/gradient_visitor.hpp" #include "../visitor/set_input_height_visitor.hpp" #include "../visitor/set_input_width_visitor.hpp" +#include "../visitor/input_shape_visitor.hpp" namespace mlpack { namespace ann /** Artificial Neural Network. */ { @@ -94,6 +95,24 @@ Sequential< } } +template +size_t Sequential:: +InputShape() const +{ + size_t inputShape = 0; + + for (size_t l = 0; l < network.size(); ++l) + { + if (inputShape == 0) + inputShape = boost::apply_visitor(InShapeVisitor(), network[l]); + else + break; + } + + return inputShape; +} + template template diff --git a/src/mlpack/methods/ann/layer/transposed_convolution.hpp b/src/mlpack/methods/ann/layer/transposed_convolution.hpp index 04ec5c7cc5..f637ca3355 100644 --- a/src/mlpack/methods/ann/layer/transposed_convolution.hpp +++ b/src/mlpack/methods/ann/layer/transposed_convolution.hpp @@ -274,6 +274,12 @@ class TransposedConvolution //! Modify the right padding width. size_t& PadWRight() { return padWRight; } + //! Get the shape of the input. + size_t InputShape() const + { + return inputHeight * inputWidth * inSize; + } + //! Get the size of the weight matrix. size_t WeightSize() const { diff --git a/src/mlpack/methods/ann/rnn_impl.hpp b/src/mlpack/methods/ann/rnn_impl.hpp index 75749982f5..2852dcceef 100644 --- a/src/mlpack/methods/ann/rnn_impl.hpp +++ b/src/mlpack/methods/ann/rnn_impl.hpp @@ -25,6 +25,8 @@ #include "visitor/gradient_visitor.hpp" #include "visitor/weight_set_visitor.hpp" +#include "util/check_input_shape.hpp" + namespace mlpack { namespace ann /** Artificial Neural Network. */ { @@ -147,6 +149,10 @@ double RNN::Train( OptimizerType& optimizer, CallbackTypes&&... callbacks) { + CheckInputShape > >(network, + predictors.n_rows, + "RNN<>::Train()"); + numFunctions = responses.n_cols; this->predictors = std::move(predictors); @@ -191,6 +197,10 @@ double RNN::Train( arma::cube responses, CallbackTypes&&... callbacks) { + CheckInputShape > >(network, + predictors.n_rows, + "RNN<>::Train()"); + numFunctions = responses.n_cols; this->predictors = std::move(predictors); @@ -223,6 +233,10 @@ template::Predict( arma::cube predictors, arma::cube& results, const size_t batchSize) { + CheckInputShape > >(network, + predictors.n_rows, + "RNN<>::Predict()"); + ResetCells(); if (parameter.is_empty()) diff --git a/src/mlpack/methods/ann/util/CMakeLists.txt b/src/mlpack/methods/ann/util/CMakeLists.txt new file mode 100644 index 0000000000..dffec0c265 --- /dev/null +++ b/src/mlpack/methods/ann/util/CMakeLists.txt @@ -0,0 +1,14 @@ +# Define the files we need to compile +# Anything not in this list will not be compiled into mlpack. +set(SOURCES + check_input_shape.hpp +) + +# Add directory name to sources. +set(DIR_SRCS) +foreach(file ${SOURCES}) + set(DIR_SRCS ${DIR_SRCS} ${CMAKE_CURRENT_SOURCE_DIR}/${file}) +endforeach() +# Append sources (with directory name) to list of all mlpack sources (used at +# the parent scope). +set(MLPACK_SRCS ${MLPACK_SRCS} ${DIR_SRCS} PARENT_SCOPE) \ No newline at end of file diff --git a/src/mlpack/methods/ann/util/check_input_shape.hpp b/src/mlpack/methods/ann/util/check_input_shape.hpp new file mode 100644 index 0000000000..566c363e3f --- /dev/null +++ b/src/mlpack/methods/ann/util/check_input_shape.hpp @@ -0,0 +1,52 @@ +/** + * @file methods/ann/util/check_input_shape.hpp + * @author Khizir Siddiqui + * @author Nippun Sharma + * + * Definition of the CheckInputShape() function that checks + * whether the shape of input is consistent with the first layer + * of the neural network. + * + * mlpack is free software; you may redistribute it and/or modify it under the + * terms of the 3-clause BSD license. You should have received a copy of the + * 3-clause BSD license along with mlpack. If not, see + * http://www.opensource.org/licenses/BSD-3-Clause for more information. + */ + +#ifndef MLPACK_METHODS_ANN_UTIL_CHECK_INPUT_SHAPE_HPP +#define MLPACK_METHODS_ANN_UTIL_CHECK_INPUT_SHAPE_HPP + +#include + +namespace mlpack { +namespace ann /** Artificial Neural Network. */{ + +template +void CheckInputShape(const T& network, const size_t inputShape, + const std::string& functionName) +{ + for (size_t l = 0; l < network.size(); ++l) + { + size_t layerInShape = boost::apply_visitor(InShapeVisitor(), network[l]); + if (layerInShape == 0) + { + continue; + } + else if (layerInShape == inputShape) + { + break; + } + else + { + std::string estr = functionName + ": the first layer of the network " + + "expects " + std::to_string(layerInShape) + " elements, but the " + + "input has " + std::to_string(inputShape) + " dimensions!"; + throw std::logic_error(estr); + } + } +} + +} // namespace ann +} // namespace mlpack + +#endif diff --git a/src/mlpack/methods/ann/visitor/CMakeLists.txt b/src/mlpack/methods/ann/visitor/CMakeLists.txt index 43bcf71225..fa207d6092 100644 --- a/src/mlpack/methods/ann/visitor/CMakeLists.txt +++ b/src/mlpack/methods/ann/visitor/CMakeLists.txt @@ -57,6 +57,8 @@ set(SOURCES weight_set_visitor_impl.hpp weight_size_visitor.hpp weight_size_visitor_impl.hpp + input_shape_visitor.hpp + input_shape_visitor_impl.hpp ) # Add directory name to sources. diff --git a/src/mlpack/methods/ann/visitor/input_shape_visitor.hpp b/src/mlpack/methods/ann/visitor/input_shape_visitor.hpp new file mode 100644 index 0000000000..c27135aae3 --- /dev/null +++ b/src/mlpack/methods/ann/visitor/input_shape_visitor.hpp @@ -0,0 +1,58 @@ +/** + * @file methods/ann/visitor/input_shape_visitor.hpp + * @author Khizir Siddiqui + * @author Nippun Sharma + * + * This file provides an abstraction for the InputShape() function for + * different layers and automatically directs any parameter to the right layer + * type. + * + * mlpack is free software; you may redistribute it and/or modify it under the + * terms of the 3-clause BSD license. You should have received a copy of the + * 3-clause BSD license along with mlpack. If not, see + * http://www.opensource.org/licenses/BSD-3-Clause for more information. + */ +#ifndef MLPACK_METHODS_ANN_VISITOR_INPUT_SHAPE_VISITOR_HPP +#define MLPACK_METHODS_ANN_VISITOR_INPUT_SHAPE_VISITOR_HPP + +#include +#include + +#include + +namespace mlpack { +namespace ann { + +/** + * InShapeVisitor returns the input shape a Layer expects. + */ +class InShapeVisitor : public boost::static_visitor +{ + public: + //! Return the input shape of layer. + template + size_t operator()(LayerType* layer) const; + + size_t operator()(MoreTypes layer) const; + + private: + //! If the module doesn't implement the InputShape() function return 0. + template + typename std::enable_if< + !HasInputShapeCheck::value, size_t>::type + LayerInputShape(T* layer) const; + + //! If the module implements the InputShape() function returns the input shape. + template + typename std::enable_if< + HasInputShapeCheck::value, size_t>::type + LayerInputShape(T* layer) const; +}; + +} // namespace ann +} // namespace mlpack + +// Include implementation. +#include "input_shape_visitor_impl.hpp" + +#endif diff --git a/src/mlpack/methods/ann/visitor/input_shape_visitor_impl.hpp b/src/mlpack/methods/ann/visitor/input_shape_visitor_impl.hpp new file mode 100644 index 0000000000..bda5f7b604 --- /dev/null +++ b/src/mlpack/methods/ann/visitor/input_shape_visitor_impl.hpp @@ -0,0 +1,53 @@ +/** + * @file methods/ann/visitor/input_shape_visitor_impl.hpp + * @author Khizir Siddiqui + * @author Nippun Sharma + * + * Implementation of the InputShape() function layer abstraction. + * + * mlpack is free software; you may redistribute it and/or modify it under the + * terms of the 3-clause BSD license. You should have received a copy of the + * 3-clause BSD license along with mlpack. If not, see + * http://www.opensource.org/licenses/BSD-3-Clause for more information. + */ +#ifndef MLPACK_METHODS_ANN_VISITOR_INPUT_SHAPE_VISITOR_IMPL_HPP +#define MLPACK_METHODS_ANN_VISITOR_INPUT_SHAPE_VISITOR_IMPL_HPP + +// In case it hasn't been included yet. +#include "input_shape_visitor.hpp" + +namespace mlpack { +namespace ann { + +//! InShapeVisitor visitor class. +template +inline std::size_t InShapeVisitor::operator()(LayerType* layer) const +{ + return LayerInputShape(layer); +} + +inline std::size_t InShapeVisitor::operator()(MoreTypes layer) const +{ + return layer.apply_visitor(*this); +} + +template +inline typename std::enable_if< + !HasInputShapeCheck::value, std::size_t>::type +InShapeVisitor::LayerInputShape(T* /* layer */) const +{ + return 0; +} + +template +inline typename std::enable_if< + HasInputShapeCheck::value, std::size_t>::type +InShapeVisitor::LayerInputShape(T* layer) const +{ + return layer->InputShape(); +} + +} // namespace ann +} // namespace mlpack + +#endif diff --git a/src/mlpack/tests/feedforward_network_test.cpp b/src/mlpack/tests/feedforward_network_test.cpp index bd240a794f..c1399e8bf3 100644 --- a/src/mlpack/tests/feedforward_network_test.cpp +++ b/src/mlpack/tests/feedforward_network_test.cpp @@ -908,3 +908,39 @@ TEST_CASE("OptimizerTest", "[FeedForwardNetworkTest]") ens::DE opt(200, 1000, 0.6, 0.8, 1e-5); model.Train(trainData, trainLabels, opt); } + +/** + * Test to see if an exception is thrown when input with + * wrong shape is provided to a FFN. + */ +TEST_CASE("FFNCheckInputShapeTest", "[FeedForwardNetworkTest]") +{ + // Load the dataset. + arma::mat trainData; + data::Load("thyroid_train.csv", trainData, true); + + arma::mat trainLabels = trainData.row(trainData.n_rows - 1); + trainData.shed_row(trainData.n_rows - 1); + + arma::mat testData; + data::Load("thyroid_test.csv", testData, true); + + arma::mat testLabels = testData.row(testData.n_rows - 1); + testData.shed_row(testData.n_rows - 1); + + FFN, RandomInitialization, CustomLayer<> > model; + // Purposely putting wrong input shape so that error is thrown. + model.Add >(trainData.n_rows - 3, 8); + model.Add >(); + model.Add >(8, 3); + model.Add >(); + + std::string expectedMsg = "FFN<>::Train(): "; + expectedMsg += "the first layer of the network expects "; + expectedMsg += std::to_string(trainData.n_rows - 3) + " elements, "; + expectedMsg += "but the input has " + std::to_string(trainData.n_rows) + " dimensions! "; + + ens::DE opt(200, 1000, 0.6, 0.8, 1e-5); + + REQUIRE_THROWS_AS(model.Train(trainData, trainLabels, opt), std::logic_error); +} diff --git a/src/mlpack/tests/recurrent_network_test.cpp b/src/mlpack/tests/recurrent_network_test.cpp index bf75410902..0993b78d5c 100644 --- a/src/mlpack/tests/recurrent_network_test.cpp +++ b/src/mlpack/tests/recurrent_network_test.cpp @@ -868,3 +868,66 @@ TEST_CASE("LargeRhoValueRnnTest", "[RecurrentNetworkTest]") model.Train(inputs[0], targets[0], opt); INFO("Training over"); } + +/** + * Test to make sure that an error is thrown when input with + * wrong input shape is provided to a RNN. + */ +TEST_CASE("RNNCheckInputShapeTest", "[RecurrentNetworkTest]") +{ + const size_t rho = 10; + + // Generate 12 (2 * 6) noisy sines. A single sine contains rho + // points/features. + arma::cube input; + arma::mat labelsTemp; + GenerateNoisySines(input, labelsTemp, rho, 6); + + arma::cube labels = arma::zeros(1, labelsTemp.n_cols, rho); + for (size_t i = 0; i < labelsTemp.n_cols; ++i) + { + const int value = arma::as_scalar(arma::find( + arma::max(labelsTemp.col(i)) == labelsTemp.col(i), 1)) + 1; + labels.tube(0, i).fill(value); + } + + /** + * Construct a network with 1 input unit, 4 hidden units and 10 output + * units. The hidden layer is connected to itself. The network structure + * looks like: + * + * Input Hidden Output + * Layer(1) Layer(4) Layer(10) + * +-----+ +-----+ +-----+ + * | | | | | | + * | +------>| +------>| | + * | | ..>| | | | + * +-----+ . +--+--+ +-----+ + * . . + * . . + * ....... + */ + Add<> add(4); + // Purposely providing wrong input shape of 3. + // The correct input shape is 1. + Linear<> lookup(3, 4); + SigmoidLayer<> sigmoidLayer; + Linear<> linear(4, 4); + Recurrent<>* recurrent = new Recurrent<>(add, lookup, linear, + sigmoidLayer, rho); + + RNN<> model(rho); + model.Add >(); + model.Add(recurrent); + model.Add >(4, 10); + model.Add >(); + + std::string expectedMsg = "RNN<>::Train(): "; + expectedMsg += "the first layer of the network expects "; + expectedMsg += std::to_string(3) + " elements, "; + expectedMsg += "but the input has " + std::to_string(1) + " dimensions! "; + + StandardSGD opt(0.1, 1, input.n_cols /* 1 epoch */, -100); + + REQUIRE_THROWS_AS(model.Train(input, labels, opt), std::logic_error); +}