-1

I'm a bit new to generic programming with templates in C++ and have a question on how to return an object from a templated function. This is part of the neural network module of mlpack library. This is from the feedforward_network_test.cpp which can be found here. If I understand this correctly, the way the templated function BuildVanillaNetwork is setup, its possible to pass different types of network parameters to build the neural network. What I would like is for this function to return the FFN object that it builds, so that I can access it from where I call it. I made some small changes to the code over there:

template <typename PerformanceFunction,
         typename OutputLayerType,
         typename PerformanceFunctionType,
         typename MatType = arma::mat
         >
mlpack::ann::FFN<> BuildVanillaNetwork(MatType& trainData,
        MatType& trainLabels,
        MatType& testData,
        MatType& testLabels,
        const size_t hiddenLayerSize,
        const size_t maxEpochs,
        const double classificationErrorThreshold)
{
    // input layer
    mlpack::ann::LinearLayer<> inputLayer(trainData.n_rows, hiddenLayerSize);
    mlpack::ann::BiasLayer<> inputBiasLayer(hiddenLayerSize);
    mlpack::ann::BaseLayer<PerformanceFunction> inputBaseLayer;

    // hidden layer
    mlpack::ann::LinearLayer<> hiddenLayer1(hiddenLayerSize, trainLabels.n_rows);
    mlpack::ann::BiasLayer<> hiddenBiasLayer1(trainLabels.n_rows);
    mlpack::ann::BaseLayer<PerformanceFunction> outputLayer;

    // output layer
    OutputLayerType classOutputLayer;

    auto modules = std::tie(inputLayer, inputBiasLayer, inputBaseLayer, hiddenLayer1, hiddenBiasLayer1, outputLayer);
    mlpack::ann::FFN<decltype(modules), decltype(classOutputLayer), mlpack::ann::RandomInitialization, PerformanceFunctionType> net(modules, classOutputLayer);
    net.Train(trainData, trainLabels);
    MatType prediction;
    net.Predict(testData, prediction);

    double classificationError;
    for (size_t i = 0; i < testData.n_cols; i++)
    {
        if (arma::sum(arma::sum(arma::abs(prediction.col(i) - testLabels.col(i)))) != 0)
        {
            classificationError++;
        }
    }

     classificationError = double(classificationError) / testData.n_cols;

    std::cout << "Classification Error = " << classificationError * 100 << "%" << std::endl;

    return net;
}

And here is the main function:

int main(int argc, char** argv)
{
    arma::mat dataset;
    mlpack::data::Load("../data/thyroid_train.csv", dataset, true);
    arma::mat trainData = dataset.submat(0, 0, dataset.n_rows - 4, dataset.n_cols - 1);
    arma::mat trainLabels = dataset.submat(dataset.n_rows - 3, 0, dataset.n_rows - 1, dataset.n_cols - 1);

    mlpack::data::Load("../data/thyroid_test.csv", dataset, true);
    arma::mat testData = dataset.submat(0, 0, dataset.n_rows - 4, dataset.n_cols - 1);
    arma::mat testLabels = dataset.submat(dataset.n_rows - 3, 0, dataset.n_rows - 1, dataset.n_cols - 1);

    const size_t hiddenLayerSize = 8;
    const size_t maxEpochs = 200;
    const double classificationErrorThreshold = 0.1;

    auto myFFN = BuildVanillaNetwork<mlpack::ann::LogisticFunction, mlpack::ann::BinaryClassificationLayer, mlpack::ann::MeanSquaredErrorFunction>
        (trainData, trainLabels, testData, testLabels, hiddenLayerSize, maxEpochs, classificationErrorThreshold);

    return 0;
}

When I compile this, I get the following error:

[100%] Building CXX object CMakeFiles/ff_nn.dir/src/ff_nn.cpp.o /home/username/project-yanack/mlpack_nn/src/ff_nn.cpp:24:18: error: wrong number of template arguments (0, should be 4)  mlpack::ann::FFN<> BuildVanillaNetwork(MatType& trainData,
                  ^ In file included from /home/username/project-yanack/mlpack_nn/src/ff_nn.cpp:16:0: /usr/local/include/mlpack/methods/ann/ffn.hpp:35:7: error: provided for ‘template<class LayerTypes, class OutputLayerType, class InitializationRuleType, class PerformanceFunction> class mlpack::ann::FFN’  class FFN
       ^ /home/username/project-yanack/mlpack_nn/src/ff_nn.cpp: In instantiation of ‘int BuildVanillaNetwork(MatType&, MatType&, MatType&, MatType&, size_t, size_t, double) [with PerformanceFunction
= mlpack::ann::LogisticFunction; OutputLayerType = mlpack::ann::BinaryClassificationLayer; PerformanceFunctionType = mlpack::ann::MeanSquaredErrorFunction; MatType = arma::Mat<double>; size_t = long unsigned int]’: /home/username/project-yanack/mlpack_nn/src/ff_nn.cpp:83:112:   required from here /home/username/project-yanack/mlpack_nn/src/ff_nn.cpp:64:12: error: cannot convert ‘mlpack::ann::FFN<std::tuple<mlpack::ann::LinearLayer<arma::Mat<double>, arma::Mat<double> >&, mlpack::ann::BiasLayer<arma::Mat<double>, arma::Mat<double> >&, mlpack::ann::BaseLayer<mlpack::ann::LogisticFunction, arma::Mat<double>, arma::Mat<double> >&, mlpack::ann::LinearLayer<arma::Mat<double>, arma::Mat<double> >&, mlpack::ann::BiasLayer<arma::Mat<double>, arma::Mat<double> >&, mlpack::ann::BaseLayer<mlpack::ann::LogisticFunction, arma::Mat<double>, arma::Mat<double> >&>, mlpack::ann::BinaryClassificationLayer, mlpack::ann::RandomInitialization, mlpack::ann::MeanSquaredErrorFunction>’ to ‘int’ in return
     return net;
            ^ make[2]: *** [CMakeFiles/ff_nn.dir/src/ff_nn.cpp.o] Error 1 make[1]: *** [CMakeFiles/ff_nn.dir/all] Error 2 make: *** [all] Error 2

Any help in fixing this is appreciated. Also, it'll be great if I could get links to a tutorial that explains the various concepts used in this code.

EDIT-1

I changed the function header to this:

template <typename PerformanceFunction,
         typename OutputLayerType,
         typename PerformanceFunctionType,
         typename MatType = arma::mat
         >
mlpack::ann::FFN<PerformanceFunction, OutputLayerType, PerformanceFunctionType, MatType> BuildVanillaNetwork(MatType& trainData,
        MatType& trainLabels,
        MatType& testData,
        MatType& testLabels,
        const size_t hiddenLayerSize,
        const size_t maxEpochs,
        const double classificationErrorThreshold)

But I'm still getting error when compiling:

[100%] Building CXX object CMakeFiles/ff_nn.dir/src/ff_nn.cpp.o
In file included from /home/username/project-yanack/mlpack_nn/src/ff_nn.cpp:16:0:
/usr/local/include/mlpack/methods/ann/ffn.hpp: In instantiation of ‘class mlpack::ann::FFN<mlpack::ann::LogisticFunction, mlpack::ann::BinaryClassificationLayer, mlpack::ann::MeanSquaredErrorFunction, arma::Mat<double> >’:
/home/username/project-yanack/mlpack_nn/src/ff_nn.cpp:83:112:   required from here
/usr/local/include/mlpack/methods/ann/ffn.hpp:361:55: error: incomplete type ‘std::tuple_size<mlpack::ann::LogisticFunction>’ used in nested name specifier
       size_t Max = std::tuple_size<LayerTypes>::value - 1,
                                                       ^
/usr/local/include/mlpack/methods/ann/ffn.hpp:369:55: error: incomplete type ‘std::tuple_size<mlpack::ann::LogisticFunction>’ used in nested name specifier
       size_t Max = std::tuple_size<LayerTypes>::value - 1,
                                                       ^
/home/username/project-yanack/mlpack_nn/src/ff_nn.cpp: In instantiation of ‘mlpack::ann::FFN<PerformanceFunction, OutputLayerType, PerformanceFunctionType, MatType> BuildVanillaNetwork(MatType&, MatType&, MatType&, MatType&, size_t, size_t, double) [with PerformanceFunction = mlpack::ann::LogisticFunction; OutputLayerType = mlpack::ann::BinaryClassificationLayer; PerformanceFunctionType = mlpack::ann::MeanSquaredErrorFunction; MatType = arma::Mat<double>; size_t = long unsigned int]’:
/home/username/project-yanack/mlpack_nn/src/ff_nn.cpp:83:112:   required from here
/home/username/project-yanack/mlpack_nn/src/ff_nn.cpp:64:12: error: could not convert ‘net’ from ‘mlpack::ann::FFN<std::tuple<mlpack::ann::LinearLayer<arma::Mat<double>, arma::Mat<double> >&, mlpack::ann::BiasLayer<arma::Mat<double>, arma::Mat<double> >&, mlpack::ann::BaseLayer<mlpack::ann::LogisticFunction, arma::Mat<double>, arma::Mat<double> >&, mlpack::ann::LinearLayer<arma::Mat<double>, arma::Mat<double> >&, mlpack::ann::BiasLayer<arma::Mat<double>, arma::Mat<double> >&, mlpack::ann::BaseLayer<mlpack::ann::LogisticFunction, arma::Mat<double>, arma::Mat<double> >&>, mlpack::ann::BinaryClassificationLayer, mlpack::ann::RandomInitialization, mlpack::ann::MeanSquaredErrorFunction>’ to ‘mlpack::ann::FFN<mlpack::ann::LogisticFunction, mlpack::ann::BinaryClassificationLayer, mlpack::ann::MeanSquaredErrorFunction, arma::Mat<double> >’
     return net;
            ^
make[2]: *** [CMakeFiles/ff_nn.dir/src/ff_nn.cpp.o] Error 1
make[1]: *** [CMakeFiles/ff_nn.dir/all] Error 2
make: *** [all] Error 2

Also, the signature of the FFN class (here) seems to be different than what I have in this function. Could that be an issue? If it is, how do I fix it since those typenames are not really "types" as far as I understand.

Thanks.

coatless
  • 20,011
  • 13
  • 69
  • 84
shaun
  • 560
  • 1
  • 11
  • 29
  • Which C++ standard? 11? 14? – Joel Cornett Feb 29 '16 at 16:17
  • @Joel: C++11 asaik. Because I set CMAKE_CXX_FLAGS to -std=c++11. – shaun Feb 29 '16 at 16:21
  • On my phone, so I can't really type out a solution, but you're going to have to somehow specify the correct return type. To make it more palatable, you can break it into pieces using alias declarations. Also, if you're willing to switch to C++14, you can eschew all of that nonsense and replace the return type with `auto`. – Joel Cornett Feb 29 '16 at 16:42

3 Answers3

0

mlpack::ann::FFN<> BuildVanillaNetwork(MatType& trainData,

Change it to something that includes the parameters from the template, somthing like:

mlpack::ann::FFN<
    PerformanceFunction,
    OutputLayerType,
    PerformanceFunctionType,
    MatType
> BuildVanillaNetwork(MatType& trainData,...

Or try decltype(auto) if your compiler supports it:

auto BuildVanillaNetwork(MatType& trainData,`...) -> decltype(auto) {...
Community
  • 1
  • 1
wally
  • 10,717
  • 5
  • 39
  • 72
  • I'm not sure if thats the problem. Because even if I just call BuildVanillaNetwork without storing its return anywhere, I get the same error during compilation. Also, I'm not sure how to figure out the actual type as the first template parameter of FFN is decltype(modules) which I understand determines the type during runtime. – shaun Feb 29 '16 at 16:13
  • Ok, sorry, the first error complains about the lack of template arguments. Made a change to the answer. – wally Feb 29 '16 at 16:19
  • So this error is because `decltype(net) != mlpack::ann::FFN<>`. It doesn't matter if you store the result or not, you're still telling the compiler that the function returns the latter when in fact it returns the former. The template parameters need to match in order for the types to be the same. – Joel Cornett Feb 29 '16 at 16:29
  • @flatmouse: If I try <> I get an error stating invalid use of template-name without an argument list – shaun Feb 29 '16 at 16:32
  • And also the type is still determined at compile time – Joel Cornett Feb 29 '16 at 16:32
  • @JoelCornett: I was not aware of that. But despite that, how would I setup the function signature for returning multiple possible types? – shaun Feb 29 '16 at 16:33
  • @flatmouse: Unfortunetly, decltype(auto) was only introduced in c++14 which is available only in gcc 4.9 and I'm running gcc 4.8.4, so I don't think thats an option right now. – shaun Feb 29 '16 at 17:16
0

The problem is your definition of the BuildVanillaNetwork function as:

mlpack::ann::FFN<> BuildVanillaNetwork(...)

The error messages are usually hard to read by a human when they involve templates, but reading through the lines gives you something like this:

error: wrong number of template arguments (0, should be 4) ... provided for ‘template class mlpack::ann::FFN’

The rest of the errors are caused by this one (basically, because it doesn't understand the return type of that function, the compiler assumes it to be int, then it complains it can't convert net to int).

So you have to actually specify the template arguments for the return type. You're using decltype in the body of the function to deduce them (which happens at compile time, not runtime), but in the prototype it won't be that easy. There is a way to use decltype to declare the return type of a function, but it won't help you much in this case. So you might as well go on and write them explicitly.

Ionut
  • 6,436
  • 1
  • 17
  • 17
  • @lonut: I see. If I write them explicitly, then I would have to write multiple functions for FFN's with different parameters, correct? Also, the first template parameter is decltype(modules) which is an std::tie of a bunch of stuff (the number of args will vary). So I'm not sure how to deduce the type myself to write them out explicitly. – shaun Feb 29 '16 at 16:37
  • The template parameters for FFN probably depend on the template parameters for the function, right? In this case you won't have to write multiple functions, and you can't write multiple functions that differ only in return type. – Ionut Feb 29 '16 at 16:42
  • The parameters to std::tie can vary, but it is a template class and so the template parameters will be entirely resolved at compile time. – Ionut Feb 29 '16 at 16:44
  • @lonut: From FFN code, this seems to be the definition of FFN: FFN – shaun Feb 29 '16 at 17:17
  • @lonut: If I compile that, I get an error saying LayerTypes etc was not declared in scope. So, I'm still not sure how to define my function based on what its returning since the returning object type is deduced inside the function. – shaun Feb 29 '16 at 17:24
  • Right, but one way or another you'll have to. Basically you have a template function that builds something based on it's template parameters, so there should be a way to specify the return type based on the same template parameters. – Ionut Feb 29 '16 at 17:27
  • You can't introduce new template parameters in the return type, so you can't use LayerTypes. You can use only template parameters already declared for the function or real types. – Ionut Feb 29 '16 at 17:30
  • Ok this is interesting. I just changed the function definition to auto BuildVanillaNetwork and the code compilied with just this warning /home/sudarshan/project-yanack/mlpack_nn/src/ff_nn.cpp:30:50: warning: ‘BuildVanillaNetwork’ function uses ‘auto’ type specifier without trailing return type [enabled by default] const double classificationErrorThreshold) And it ran correctly as well. Whats going on here? – shaun Feb 29 '16 at 17:32
  • To be honest, no idea, I'll have to look into it. I didn't know this worked or was supposed to work in C++11. – Ionut Feb 29 '16 at 19:00
0

You can use the following pattern to somewhat simplify the return type inference:

template<typename PerformanceFunction, typename OutputLayerType,
    typename PerformanceFunctionType, typename MatType>
struct BuildVanillaNetworkHelper
{
    using LinearLayer = mlpack::ann::LinearLayer<>;
    using BiasLayer = mlpack::ann::BiasLayer<>;
    using BaseLayer = mlpack::ann::BaseLayer<PerformanceFunction>;
    using ModulesType = std::tuple<LinearLayer, BiasLayer, BaseLayer,
        LinearLayer, BiasLayer, BaseLayer>;
    using FFNType = mlpack::ann::FFN<ModulesType, OutputLayerType,
        mlpack::ann::RandomInitialization, PerformanceFunctionType>;
};

template <typename PerformanceFunction,
         typename OutputLayerType,
         typename PerformanceFunctionType,
         typename MatType = arma::mat,
         typename Helper = BuildVanillaNetworkHelper<
             PerformanceFunction, OutputLayerType,
             PerformanceFunctionType, MatType>
         >
typename Helper::FFNType BuildVanillaNetwork(...);
Joel Cornett
  • 24,192
  • 9
  • 66
  • 88