diff --git a/src/mlpack/methods/ann/cnn.hpp b/src/mlpack/methods/ann/cnn.hpp new file mode 100644 index 0000000000..0aec3ce378 --- /dev/null +++ b/src/mlpack/methods/ann/cnn.hpp @@ -0,0 +1,645 @@ +/** + * @file cnn.hpp + * @author Shangtong Zhang + * + * Definition of the CNN class, which implements convolutional neural networks. + */ +#ifndef __MLPACK_METHODS_ANN_CNN_HPP +#define __MLPACK_METHODS_ANN_CNN_HPP + +#include + +#include + +#include +#include +#include +#include + +#include "sstream" +#include "fstream" +#include "string" + +namespace mlpack { +namespace ann /** Artificial Neural Network. */ { + +/** + * An implementation of a standard convolutional network. + * + * @tparam ConnectionTypes Tuple that contains all layer module and + * connection module which will be used to construct the network. + * These tuples should be organized as + * + * @tparam OutputLayerType The outputlayer type used to evaluate the network. + * @tparam PerformanceFunction Performance strategy used to claculate the error. + * @tparam MaType Type of the gradients. (arma::mat or arma::sp_mat). + */ +template < + typename ConnectionTypes, + typename OutputLayerType, + class PerformanceFunction = CrossEntropyErrorFunction<>, + typename MatType = arma::mat +> +class CNN +{ + public: + /** + * Construct the CNN object, which will construct a convolutional neural + * network with the specified layers. + * + * @param network The network modules used to construct net network. + * @param outputLayer The outputlayer used to evaluate the network. + */ + CNN(const ConnectionTypes& network, OutputLayerType& outputLayer) + : network(network), outputLayer(outputLayer), trainError(0), seqNum(0) + { + // Nothing to do here. + } + + /** + * Run a single iteration of the feed forward algorithm, using the given + * input and target vector, updating the resulting error into the error + * vector. + * + * @param input Input data used to evaluate the network. + * @param target Target data used to calculate the network error. + * @param error The calulated error of the output layer. + * @tparam VecType Type of data (arma::colvec, arma::mat or arma::sp_mat). + */ + template + void FeedForward(const MatType& input, + const VecType& target, + VecType& error) + { + seqNum++; + trainError += Evaluate(input, target, error); + } + + /** + * Reset all connection module and layer module in the network. + */ + void Reset() { + ResetLayer(network); + ResetConnection(network); + } + + /** + * Run a single iteration of the feed backward algorithm, using the given + * error of the output layer. + * + * @param error The calulated error of the output layer. + * @tparam VecType Type of data (arma::colvec, arma::mat or arma::sp_mat). + */ + template + void FeedBackward(const VecType& error) + { + // Initialize the gradient storage only once. + if (!gradients.size()) + InitLayer(network); + + gradientNum = 0; + FeedBackward(network, error); + UpdateGradients(network); + } + + /** + * Updating the weights using the specified optimizer. + */ + void ApplyGradients() + { + gradientNum = 0; + ApplyGradients(network); + + // Reset the overall error. + trainError = 0; + seqNum = 0; + } + + /** + * Evaluate the network using the given input. The output activation is + * stored into the output parameter. + * + * @param input Input data used to evaluate the network. + * @param output Output data used to store the output activation + * @tparam VecType Type of data (arma::colvec, arma::mat or arma::sp_mat). + */ + template + void Predict(const MatType& input, VecType& output) + { + Reset(); + + std::get<0>(std::get<0>(network)).InputActivation() = input; + + FeedForward(network); + OutputPrediction(network, output); + } + + /** + * Evaluate the trained network using the given input and compare the output + * with the given target vector. + * + * @param input Input data used to evaluate the trained network. + * @param target Target data used to calculate the network error. + * @param error The calulated error of the output layer. + * @tparam VecType Type of data (arma::colvec, arma::mat or arma::sp_mat). + */ + template + double Evaluate(const MatType& input, const VecType& target, VecType& error) + { + Reset(); + std::get<0>(std::get<0>(network)).InputActivation() = input; + FeedForward(network); + return OutputError(network, target, error); + } + + //! Get the error of the network. + double Error() const { return trainError; } + + // Save weights of all connection modules to specified file. + void SaveWeights(std::string file) + { + std::ofstream outFile(file); + std::stringstream ss; + SaveWeights(network, ss); + outFile << ss.rdbuf(); + outFile.close(); + } + + // Load saved weights for all connection modules from specified file. + void LoadWeights(std::string file) + { + std::ifstream inFile(file); + std::stringstream ss; + ss << inFile.rdbuf(); + LoadWeights(network, ss); + inFile.close(); + } + + private: + /** + * Helper function to reset all layer module + * by zeroing the layer activations + * and delta which store the passed error in backward propagation. + * + * enable_if (SFINAE) is used to iterate through the network layer + * modules. The general case peels off the first type and recurses, as usual + * with variadic function templates. + */ + template + typename std::enable_if= sizeof...(Tp), void>::type + ResetLayer(std::tuple& /* unused */) { } + + template + typename std::enable_if::type + ResetLayer(std::tuple& t) + { + ResetL(std::get(t)); + ResetLayer(t); + } + + + template + typename std::enable_if::type + ResetL(std::tuple& /* unused */) { } + + template + typename std::enable_if::type + ResetL(std::tuple& t) + { + std::get(t).InputActivation().zeros(); + std::get(t).Delta().zeros(); + ResetL(t); + } + + /** + * Helper function to reset all connection module + * by zeroing the connection weight delta + * and delta which store the passed error in backward propagation. + * + * enable_if (SFINAE) is used to iterate through the network connections. + * The general case peels off the first type and recurses, as usual with + * variadic function templates. + */ + template + typename std::enable_if= sizeof...(Tp), void>::type + ResetConnection(std::tuple& /* unused */) { } + + template + typename std::enable_if::type + ResetConnection(std::tuple& t) + { + ResetC(std::get(t)); + ResetConnection(t); + } + + template + typename std::enable_if::type + ResetC(std::tuple& /* unused */) { } + + template + typename std::enable_if::type + ResetC(std::tuple& t) + { + std::get(t).Delta().zeros(); + std::get(t).Gradient().zeros(); + ResetC(t); + } + + /** + * Run a single iteration of the feed forward algorithm, using the given + * input and target vector, updating the resulting error into the error + * vector. + * + * enable_if (SFINAE) is used to select between two template overloads of + * the get function - one for when I is equal the size of the tuple of + * connections, and one for the general case which peels off the first type + * and recurses, as usual with variadic function templates. + */ + template + typename std::enable_if= sizeof...(Tp), void>::type + FeedForward(std::tuple& /* unused */) { } + + template + typename std::enable_if::type + FeedForward(std::tuple& t) + { + ConnectionForward(std::get(t)); + + LayerForward(std::get(t)); + + FeedForward(t); + } + + /** + * Sum up all connection activations by evaluating all connections. + * + * enable_if (SFINAE) is used to iterate through the network connections. + * The general case peels off the first type and recurses, as usual with + * variadic function templates. + */ + template + typename std::enable_if::type + ConnectionForward(std::tuple& /* unused */) { } + + template + typename std::enable_if::type + ConnectionForward(std::tuple& t) + { + std::get(t).FeedForward(std::get(t).InputLayer().InputActivation()); + ConnectionForward(t); + } + + /** + * Sum up all layer activations by evaluating all layers. + * + * enable_if (SFINAE) is used to iterate through the network layers. + * The general case peels off the first type and recurses, as usual with + * variadic function templates. + */ + template + typename std::enable_if::type + LayerForward(std::tuple& /* unused */) { } + + template + typename std::enable_if::type + LayerForward(std::tuple& t) + { + std::get(t).FeedForward( + std::get(t).InputActivation(), + std::get(t).InputActivation()); + LayerForward(t); + } + + /* + * Calculate the output error and update the overall error. + */ + template + double OutputError(std::tuple& t, + const VecType& target, + VecType& error) + { + // Calculate and store the output error. + outputLayer.calculateError(std::get<0>( + std::get(t)).InputActivation(), + target, error); + + // Masures the network's performance with the specified performance + // function. + return PerformanceFunction::error(std::get<0>( + std::get(t)).InputActivation(), + target); + } + + /** + * Calculate and store the output activation. + */ + template + void OutputPrediction(std::tuple& t, VecType& output) + { + // Calculate and store the output prediction. + outputLayer.outputClass(std::get<0>( + std::get(t)).InputActivation(), + output); + } + + /** + * Run a single iteration of the feed backward algorithm, using the given + * error of the output layer. + * + * enable_if (SFINAE) is used to select between two template overloads of + * the get function - one for when I is equal the size of the tuple of + * connections, and one for the general case which peels off the first type + * and recurses, as usual with variadic function templates. + */ + template + typename std::enable_if::type + FeedBackward(std::tuple& /* unused */, VecType& /* unused */) { } + + template + typename std::enable_if::type + FeedBackward(std::tuple& t, VecType& error) + { + // Pass initial error to the last layer. + if (I == 1) + std::get<0>(std::get(t)).Delta() = error; + + LayerBackward(std::get(t)); + ConnectionBackward(std::get(t)); + + FeedBackward(t, error); + } + + /** + * Back propagate the given error through layers. + * + * enable_if (SFINAE) is used to iterate through the network layers. + * The general case peels off the first type and recurses, as usual with + * variadic function templates. + */ + + template + typename std::enable_if::type + LayerBackward(std::tuple& /* unused */) { } + + template + typename std::enable_if::type + LayerBackward(std::tuple& t) + { + std::get(t).FeedBackward(std::get(t).InputActivation(), + std::get(t).Delta(), + std::get(t).Delta()); + LayerBackward(t); + } + + /** + * Back propagate the given error through connections. + * + * enable_if (SFINAE) is used to iterate through the network connections. + * The general case peels off the first type and recurses, as usual with + * variadic function templates. + */ + template + typename std::enable_if::type + ConnectionBackward(std::tuple& /* unused */) { } + + template + typename std::enable_if::type + ConnectionBackward(std::tuple& t) + { + std::get(t).FeedBackward(std::get(t).OutputLayer().Delta()); + ConnectionBackward(t); + } + + /** + * Helper function to iterate through all connection modules and to update + * the gradient storage. + * + * enable_if (SFINAE) is used to select between two template overloads of + * the get function - one for when I is equal the size of the tuple of + * connections, and one for the general case which peels off the first type + * and recurses, as usual with variadic function templates. + */ + template + typename std::enable_if= sizeof...(Tp), void>::type + UpdateGradients(std::tuple& /* unused */) { } + + template + typename std::enable_if::type + UpdateGradients(std::tuple& t) + { + Gradients(std::get(t)); + UpdateGradients(t); + } + + /** + * Sum up all gradients and store the results in the gradients storage. + * + * enable_if (SFINAE) is used to iterate through the network connections. + * The general case peels off the first type and recurses, as usual with + * variadic function templates. + */ + template + typename std::enable_if::type + Gradients(std::tuple& /* unused */) { } + + template + typename std::enable_if::type + Gradients(std::tuple& t) + { + // A connection module must have locally-stroed gradient when applied in CNN. + gradients[gradientNum++] += std::get(t).Gradient(); + Gradients(t); + } + + /** + * Helper function to update the weights using the specified optimizer and + * the given input. + * + * enable_if (SFINAE) is used to select between two template overloads of + * the get function - one for when I is equal the size of the tuple of + * connections, and one for the general case which peels off the first type + * and recurses, as usual with variadic function templates. + */ + template + typename std::enable_if= sizeof...(Tp), void>::type + ApplyGradients(std::tuple& /* unused */) { } + + template + typename std::enable_if::type + ApplyGradients(std::tuple& t) + { + Apply(std::get(t)); + ApplyGradients(t); + } + + /** + * Update the weights using the gradients from the gradient store. + * + * enable_if (SFINAE) is used to iterate through the network connections. + * The general case peels off the first type and recurses, as usual with + * variadic function templates. + */ + template + typename std::enable_if::type + Apply(std::tuple& /* unused */) { } + + template + typename std::enable_if::type + Apply(std::tuple& t) + { + if (seqNum > 1) + gradients[gradientNum] /= seqNum; + + std::get(t).Optimzer().UpdateWeights(std::get(t).Weights(), + gradients[gradientNum], trainError); + + // Reset the gradient storage. + gradients[gradientNum++].zeros(); + Apply(t); + } + + /** + * Helper function to iterate through all connection modules and to build + * gradient storage. + * + * enable_if (SFINAE) is used to select between two template overloads of + * the get function - one for when I is equal the size of the tuple of + * connections, and one for the general case which peels off the first type + * and recurses, as usual with variadic function templates. + */ + template + typename std::enable_if= sizeof...(Tp), void>::type + InitLayer(std::tuple& /* unused */) { } + + template + typename std::enable_if::type + InitLayer(std::tuple& t) + { + Layer(std::get(t)); + InitLayer(t); + } + + /** + * Iterate through all connections and build the the gradient storage. + * + * enable_if (SFINAE) is used to select between two template overloads of + * the get function - one for when I is equal the size of the tuple of + * connections, and one for the general case which peels off the first type + * and recurses, as usual with variadic function templates. + */ + template + typename std::enable_if::type + Layer(std::tuple& /* unused */) { } + + template + typename std::enable_if::type + Layer(std::tuple& t) + { + gradients.push_back( + new MatType(std::get(t).Weights().n_rows, + std::get(t).Weights().n_cols, arma::fill::zeros)); + + Layer(t); + } + + /** + * Helper function to save weights of all connection modules. + * + * enable_if (SFINAE) is used to iterate through the network connections. + * The general case peels off the first type and recurses, as usual with + * variadic function templates. + */ + template + typename std::enable_if= sizeof...(Tp), void>::type + SaveWeights(std::tuple&, std::stringstream& /* unused */) { } + + template + typename std::enable_if::type + SaveWeights(std::tuple& t, std::stringstream& ss) + { + SaveWeightsConnection(std::get(t), ss); + SaveWeights(t, ss); + } + + template + typename std::enable_if::type + SaveWeightsConnection(std::tuple&, std::stringstream& /* unused */) { } + + template + typename std::enable_if::type + SaveWeightsConnection(std::tuple& t, std::stringstream& ss) + { + std::get(t).Weights().save(ss); + SaveWeightsConnection(t, ss); + } + + /** + * Helper function to load saved weights for all connection modules. + * + * enable_if (SFINAE) is used to iterate through the network connections. + * The general case peels off the first type and recurses, as usual with + * variadic function templates. + */ + template + typename std::enable_if= sizeof...(Tp), void>::type + LoadWeights(std::tuple&, std::stringstream& /* unused */) { } + + template + typename std::enable_if::type + LoadWeights(std::tuple& t, std::stringstream& ss) + { + LoadWeightsConnection(std::get(t), ss); + LoadWeights(t, ss); + } + + template + typename std::enable_if::type + LoadWeightsConnection(std::tuple&, std::stringstream& /* unused */) { } + + template + typename std::enable_if::type + LoadWeightsConnection(std::tuple& t, std::stringstream& ss) + { + std::get(t).Weights().load(ss); + LoadWeightsConnection(t, ss); + } + + //! The connection modules used to build the network. + ConnectionTypes network; + + //! The outputlayer used to evaluate the network + OutputLayerType& outputLayer; + + //! The current training error of the network. + double trainError; + + //! The gradient storage we are using to perform the feed backward pass. + boost::ptr_vector gradients; + + //! The index of the currently activate gradient. + size_t gradientNum; + + //! The number of the current input sequence. + size_t seqNum; +}; // class CNN + + +//! Network traits for the CNN network. +template < + typename ConnectionTypes, + typename OutputLayerType, + class PerformanceFunction +> +class NetworkTraits< + CNN > +{ + public: + static const bool IsFNN = false; + static const bool IsRNN = false; + static const bool IsCNN = true; +}; + +}; // namespace ann +}; // namespace mlpack + +#endif