Merge pull request #2739 from NippunSharma/iss2071

Adding clear errors to RNN's and FFN's when the input shape is wrong
This commit is contained in:
Omar Shrit
2020-12-23 11:45:05 +01:00
committed by GitHub
34 changed files with 500 additions and 0 deletions
+1
View File
@@ -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)
+18
View File
@@ -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<OutputLayerType, InitializationRuleType, CustomLayers...>::Train(
OptimizerType& optimizer,
CallbackTypes&&... callbacks)
{
CheckInputShape<std::vector<LayerTypes<CustomLayers...> > >(network,
predictors.n_rows,
"FFN<>::Train()");
ResetData(std::move(predictors), std::move(responses));
WarnMessageMaxIterations<OptimizerType>(optimizer, this->predictors.n_cols);
@@ -131,6 +137,10 @@ double FFN<OutputLayerType, InitializationRuleType, CustomLayers...>::Train(
arma::mat responses,
CallbackTypes&&... callbacks)
{
CheckInputShape<std::vector<LayerTypes<CustomLayers...> > >(network,
predictors.n_rows,
"FFN<>::Train()");
ResetData(std::move(predictors), std::move(responses));
OptimizerType optimizer;
@@ -217,6 +227,10 @@ template<typename OutputLayerType, typename InitializationRuleType,
void FFN<OutputLayerType, InitializationRuleType, CustomLayers...>::Predict(
arma::mat predictors, arma::mat& results)
{
CheckInputShape<std::vector<LayerTypes<CustomLayers...> > >(network,
predictors.n_rows,
"FFN<>::Predict()");
if (parameter.is_empty())
ResetParameters();
@@ -250,6 +264,10 @@ template<typename PredictorsType, typename ResponsesType>
double FFN<OutputLayerType, InitializationRuleType, CustomLayers...>::Evaluate(
const PredictorsType& predictors, const ResponsesType& responses)
{
CheckInputShape<std::vector<LayerTypes<CustomLayers...> > >(network,
predictors.n_rows,
"FFN<>::Evaluate()");
if (parameter.is_empty())
ResetParameters();
@@ -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.
*/
@@ -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.
*/
@@ -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.
*/
@@ -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
*/
+6
View File
@@ -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.
*/
+6
View File
@@ -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
*/
+6
View File
@@ -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.
*/
@@ -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.
*/
@@ -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<T> 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
+6
View File
@@ -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
*/
@@ -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
*/
@@ -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
*/
+6
View File
@@ -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
*/
@@ -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.
*/
@@ -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;
@@ -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; }
@@ -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
*/
@@ -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.
*/
@@ -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
*/
@@ -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<InputDataType, OutputDataType, CustomLayers...>::Recurrent(
this->network.push_back(recurrentModule);
}
template<typename InputDataType, typename OutputDataType,
typename... CustomLayers>
size_t Recurrent<InputDataType, OutputDataType, CustomLayers...>::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<typename InputDataType, typename OutputDataType,
typename... CustomLayers>
template<typename eT>
@@ -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
*/
@@ -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
*/
@@ -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<typename InputDataType, typename OutputDataType, bool Residual,
typename... CustomLayers>
size_t Sequential<InputDataType, OutputDataType, Residual, CustomLayers...>::
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<typename InputDataType, typename OutputDataType, bool Residual,
typename... CustomLayers>
template<typename eT>
@@ -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
{
+14
View File
@@ -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<OutputLayerType, InitializationRuleType, CustomLayers...>::Train(
OptimizerType& optimizer,
CallbackTypes&&... callbacks)
{
CheckInputShape<std::vector<LayerTypes<CustomLayers...> > >(network,
predictors.n_rows,
"RNN<>::Train()");
numFunctions = responses.n_cols;
this->predictors = std::move(predictors);
@@ -191,6 +197,10 @@ double RNN<OutputLayerType, InitializationRuleType, CustomLayers...>::Train(
arma::cube responses,
CallbackTypes&&... callbacks)
{
CheckInputShape<std::vector<LayerTypes<CustomLayers...> > >(network,
predictors.n_rows,
"RNN<>::Train()");
numFunctions = responses.n_cols;
this->predictors = std::move(predictors);
@@ -223,6 +233,10 @@ template<typename OutputLayerType, typename InitializationRuleType,
void RNN<OutputLayerType, InitializationRuleType, CustomLayers...>::Predict(
arma::cube predictors, arma::cube& results, const size_t batchSize)
{
CheckInputShape<std::vector<LayerTypes<CustomLayers...> > >(network,
predictors.n_rows,
"RNN<>::Predict()");
ResetCells();
if (parameter.is_empty())
@@ -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)
@@ -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 <mlpack/methods/ann/visitor/input_shape_visitor.hpp>
namespace mlpack {
namespace ann /** Artificial Neural Network. */{
template<typename T>
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
@@ -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.
@@ -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 <mlpack/methods/ann/layer/layer_traits.hpp>
#include <mlpack/methods/ann/layer/layer_types.hpp>
#include <boost/variant.hpp>
namespace mlpack {
namespace ann {
/**
* InShapeVisitor returns the input shape a Layer expects.
*/
class InShapeVisitor : public boost::static_visitor<size_t>
{
public:
//! Return the input shape of layer.
template<typename LayerType>
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 T>
typename std::enable_if<
!HasInputShapeCheck<T>::value, size_t>::type
LayerInputShape(T* layer) const;
//! If the module implements the InputShape() function returns the input shape.
template<typename T>
typename std::enable_if<
HasInputShapeCheck<T>::value, size_t>::type
LayerInputShape(T* layer) const;
};
} // namespace ann
} // namespace mlpack
// Include implementation.
#include "input_shape_visitor_impl.hpp"
#endif
@@ -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<typename LayerType>
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<typename T>
inline typename std::enable_if<
!HasInputShapeCheck<T>::value, std::size_t>::type
InShapeVisitor::LayerInputShape(T* /* layer */) const
{
return 0;
}
template<typename T>
inline typename std::enable_if<
HasInputShapeCheck<T>::value, std::size_t>::type
InShapeVisitor::LayerInputShape(T* layer) const
{
return layer->InputShape();
}
} // namespace ann
} // namespace mlpack
#endif
@@ -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<NegativeLogLikelihood<>, RandomInitialization, CustomLayer<> > model;
// Purposely putting wrong input shape so that error is thrown.
model.Add<Linear<> >(trainData.n_rows - 3, 8);
model.Add<CustomLayer<> >();
model.Add<Linear<> >(8, 3);
model.Add<LogSoftMax<> >();
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);
}
@@ -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<arma::cube>(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<IdentityLayer<> >();
model.Add(recurrent);
model.Add<Linear<> >(4, 10);
model.Add<LogSoftMax<> >();
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);
}