From 7929a7a98927dfe334ddb909bce16a295dc97c68 Mon Sep 17 00:00:00 2001 From: SIDHARTH-7 Date: Tue, 27 Feb 2024 01:00:16 +0530 Subject: [PATCH 1/3] create mobilenet.hpp and mobilenet_impl.hpp --- .vscode/settings.json | 82 +++++++++++++++ models/mobilenet/mobilenet.hpp | 140 ++++++++++++++++++++++++++ models/mobilenet/mobilenet_impl.hpp | 150 ++++++++++++++++++++++++++++ 3 files changed, 372 insertions(+) create mode 100644 .vscode/settings.json create mode 100644 models/mobilenet/mobilenet.hpp create mode 100644 models/mobilenet/mobilenet_impl.hpp diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..1a16c3ba --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,82 @@ +{ + "files.associations": { + "cctype": "cpp", + "clocale": "cpp", + "cmath": "cpp", + "csetjmp": "cpp", + "csignal": "cpp", + "cstdarg": "cpp", + "cstddef": "cpp", + "cstdio": "cpp", + "cstdlib": "cpp", + "cstring": "cpp", + "ctime": "cpp", + "cwchar": "cpp", + "cwctype": "cpp", + "any": "cpp", + "array": "cpp", + "atomic": "cpp", + "strstream": "cpp", + "bit": "cpp", + "*.tcc": "cpp", + "bitset": "cpp", + "chrono": "cpp", + "codecvt": "cpp", + "compare": "cpp", + "complex": "cpp", + "concepts": "cpp", + "condition_variable": "cpp", + "coroutine": "cpp", + "cstdint": "cpp", + "deque": "cpp", + "list": "cpp", + "map": "cpp", + "set": "cpp", + "string": "cpp", + "unordered_map": "cpp", + "unordered_set": "cpp", + "vector": "cpp", + "exception": "cpp", + "algorithm": "cpp", + "functional": "cpp", + "iterator": "cpp", + "memory": "cpp", + "memory_resource": "cpp", + "numeric": "cpp", + "optional": "cpp", + "random": "cpp", + "ratio": "cpp", + "regex": "cpp", + "source_location": "cpp", + "string_view": "cpp", + "system_error": "cpp", + "tuple": "cpp", + "type_traits": "cpp", + "utility": "cpp", + "fstream": "cpp", + "future": "cpp", + "initializer_list": "cpp", + "iomanip": "cpp", + "iosfwd": "cpp", + "iostream": "cpp", + "istream": "cpp", + "limits": "cpp", + "mutex": "cpp", + "new": "cpp", + "numbers": "cpp", + "ostream": "cpp", + "semaphore": "cpp", + "span": "cpp", + "sstream": "cpp", + "stdexcept": "cpp", + "stop_token": "cpp", + "streambuf": "cpp", + "thread": "cpp", + "cfenv": "cpp", + "cinttypes": "cpp", + "typeindex": "cpp", + "typeinfo": "cpp", + "valarray": "cpp", + "variant": "cpp" + } +} \ No newline at end of file diff --git a/models/mobilenet/mobilenet.hpp b/models/mobilenet/mobilenet.hpp new file mode 100644 index 00000000..c7863bc4 --- /dev/null +++ b/models/mobilenet/mobilenet.hpp @@ -0,0 +1,140 @@ +/** + * @file mobilenet.hpp + * @author Aakash Kaushik + * + * Definition of MobileNet model. + * + * For more information, kindly refer to the following paper. + * + * @code + * @article{Andrew G2017, + * author = {Andrew G. Howard, Menglong Zhu, Bo Chen, Dmitry Kalenichenko, + * Weijun Wang, Tobias Weyand, Marco Andreetto, Hartwig Adam}, + * title = {MobileNets: Efficient Convolutional Neural Networks for Mobile + * Vision Applications}, + * year = {2017}, + * url = {https://arxiv.org/pdf/1704.04861} + * } + * @endcode + * + * 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 MODELS_MODELS_MOBILENET_MOBILENET_HPP +#define MODELS_MODELS_MOBILENET_MOBILENET_HPP + +#define MLPACK_ENABLE_ANN_SERIALIZATION +#include + +#include "./../../utils/utils.hpp" + +namespace mlpack { +namespace models { + +/** + * Definition of a MobileNet CNN. + * + * @tparam OutputLayerType The output layer type used to evaluate the network. + * @tparam InitializationRuleType Rule used to initialize the weight matrix. + */ +template< + typename MatType = arma::mat +> +class MobileNetType : public MultiLayer +{ + public: + //! Create the MobileNet model. + MobileNetType(); + + /** + * MobileNetType constructor initializes input shape and number of classes. + * + * @param numClasses Number of classes to classify images into, + * only to be specified if includeTop is true. + * @param includeTop Must be set to true if classifier layers are set. + * * @param alpha Controls the number of output channels in pointwise + * convolution: outSize * depthMultiplier. + * @param depthMultiplier Controls the number of output channels in depthwise + * convolution: inSize * depthMultiplier. + */ + MobileNetType(const size_t numClasses = 1000, + const bool includeTop = true, + const float alpha = 1.0, + const size_t depthMultiplier = 1.0); + + //! Copy the given MobileNetType. + MobileNetType(const MobileNetType& other); + //! Take ownership of the layers of the given MobileNetType. + MobileNetType(MobileNetType&& other); + //! Copy the given MobileNetType. + MobileNetType& operator=(const MobileNetType& other); + //! Take ownership of the given MobileNetType. + MobileNetType& operator=(MobileNetType&& other); + + //! Virtual destructor: delete all held layers. + virtual ~MobileNetType() + { /* Nothing to do here. */ } + + //! Create a copy of the MobileNetType (this is safe for polymorphic use). + MobileNetType* Clone() const { return new MobileNetType(*this); } + + /** + * Get the FFN object representing the network. + * + * @tparam OutputLayerType The output layer type used to evaluate the network. + * @tparam InitializationRuleType Rule used to initialize the weight matrix. + */ + template< + typename OutputLayerType = CrossEntropyError, + typename InitializationRuleType = RandomInitialization + > + FFN* GetModel() + { + FFN* mobileNet = + new FFN(); + mobileNet->Add(this); + return mobileNet; + } + + //! Serialize the MobileNetType. + template + void serialize(Archive& ar, const uint32_t /* version */); + + private: + //! Generate the layers of the AlexNet. + void MakeModel(); + + //! Locally stored number of output classes. + size_t numClasses; + + //! Locally stored if classidier layers are included or not. + bool includeTop; + + //! Locally stored alpha for mobileNet block creation. + float alpha; + + //! Locally stored Depth multiplier for mobileNet block creation. + float depthMultiplier; + + //! Locally stored map to construct mobileNetV1 blocks. + std::map mobileNetConfig = { + {128, 2}, + {256, 2}, + {512, 6}, + {1024, 2}, + }; +}; // MobileNetType class + +// convenience typedef. +typedef MobileNetType Mobilenet; + +} // namespace models +} // namespace mlpack + +CEREAL_REGISTER_TYPE(mlpack::models::MobileNetType); + +#include "mobilenet_impl.hpp" + +#endif diff --git a/models/mobilenet/mobilenet_impl.hpp b/models/mobilenet/mobilenet_impl.hpp new file mode 100644 index 00000000..ce0f7600 --- /dev/null +++ b/models/mobilenet/mobilenet_impl.hpp @@ -0,0 +1,150 @@ +/** + * @file mobilenet_impl.hpp + * @author Aakash Kaushik + * + * Implementation of MobileNet using mlpack. + * + * 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 MODELS_MODELS_MOBILENET_MOBILENET_IMPL_HPP +#define MODELS_MODELS_MOBILENET_MOBILENET_IMPL_HPP + +#include "mobilenet.hpp" + +namespace mlpack { +namespace models { + +template +MobileNetType::MobileNetType( + const size_t numClasses, + const bool includeTop, + const float alpha, + const size_t depthMultiplier) : + MultiLayer(), + numClasses(numClasses), + includeTop(includeTop), + alpha(alpha), + depthMultiplier(depthMultiplier) +{ + MakeModel(); +} + +template +MobileNetType::MobileNetType( + const MobileNetType& other) : + MultiLayer(other), + numClasses(other.numClasses), + includeTop(other.includeTop), + alpha(other.alpha), + depthMultiplier(other.depthMultiplier) +{ + // Nothing to do here. +} + +template +MobileNetType::MobileNetType( + MobileNetType&& other) : + MultiLayer(std::move(other)), + numClasses(std::move(other.numClasses)), + includeTop(std::move(other.includeTop)), + alpha(std::move(other.alpha)), + depthMultiplier(std::move(other.depthMultiplier)) +{ + // Nothing to do here. +} + +template +MobileNetType& +MobileNetType::operator=( + const MobileNetType& other) +{ + if (this != &other) + { + MultiLayer::operator=(other); + numClasses = other.numClasses; + includeTop = other.includeTop; + alpha = other.alpha; + depthMultiplier = other.depthMultiplier; + } + + return *this; +} + +template +MobileNetType& +MobileNetType::operator=( + MobileNetType&& other) +{ + if (this != &other) + { + MultiLayer::operator=(std::move(other)); + numClasses = std::move(other.numClasses); + includeTop = std::move(other.includeTop); + alpha = std::move(other.alpha); + depthMultiplier = std::move(other.depthMultiplier); + } + + return *this; +} + +template +template +void MobileNetType::serialize( + Archive& ar, const uint32_t /* version */) +{ + ar(cereal::base_class>(this)); + + ar(CEREAL_NVP(numClasses)); + ar(CEREAL_NVP(includeTop)); + ar(CEREAL_NVP(alpha)); + ar(CEREAL_NVP(depthMultiplier)); +} + +template +void MobileNetType::MakeModel() +{ + this->template Add(32, 3, 3, 2, 2, 0, 0, "none", false); + this->template Add(); + + size_t outSize = size_t(32 * alpha); + + this->template Add(outSize, outSize*depthMultiplier, + alpha, depthMultiplier); + outSize = size_t(64 * alpha); + for (const auto& blockConfig : mobileNetConfig){ + this->template Add(outSize, outSize*depthMultiplier, + alpha, depthMultiplier, 2); + this->template Add(); + this->template Add(); + + outSize = size_t(blockConfig.first * alpha); + + for (size_t numBlock = 1; numBlock < blockConfig.second; ++numBlock) + { + this->template Add(outSize, outSize*depthMultiplier, + alpha, depthMultiplier); + this->template Add(); + this->template Add(); + + outSize = size_t(blockConfig.first * alpha); + } + + this->template Add(1,1); + } + + if (includeTop) + { + this->template Add(1e-3); + this->template Add(size_t(1024 * alpha), numClasses, + 1, 1, 1, 0, 0, 1, 1, "same"); + this->template Add(); + } +} + +} // namespace models +} // namespace mlpack + +#endif // MODELS_MODELS_MOBILENET_MOBILENET_IMPL_HPP From 3f241f0a198437360e3b9ab0db399d2bd90ea1fd Mon Sep 17 00:00:00 2001 From: SIDHARTH-7 Date: Tue, 27 Feb 2024 11:50:37 +0530 Subject: [PATCH 2/3] Update Layers in mobilenet_impl.hpp --- models/mobilenet/mobilenet_impl.hpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/models/mobilenet/mobilenet_impl.hpp b/models/mobilenet/mobilenet_impl.hpp index ce0f7600..fc10e652 100644 --- a/models/mobilenet/mobilenet_impl.hpp +++ b/models/mobilenet/mobilenet_impl.hpp @@ -113,10 +113,13 @@ void MobileNetType::MakeModel() this->template Add(outSize, outSize*depthMultiplier, alpha, depthMultiplier); + outSize = size_t(64 * alpha); + for (const auto& blockConfig : mobileNetConfig){ + this->template Add(0, 1, 0, 1); this->template Add(outSize, outSize*depthMultiplier, - alpha, depthMultiplier, 2); + 3, 3, 2, 2, 0, 0, 1, 1, "valid"); this->template Add(); this->template Add(); @@ -125,7 +128,7 @@ void MobileNetType::MakeModel() for (size_t numBlock = 1; numBlock < blockConfig.second; ++numBlock) { this->template Add(outSize, outSize*depthMultiplier, - alpha, depthMultiplier); + 3, 3, 1, 1, 0, 0, 0, 0, "same"); this->template Add(); this->template Add(); From 29632d27b7c043d23803a79846e1cff5ed0a6888 Mon Sep 17 00:00:00 2001 From: SIDHARTH-7 Date: Tue, 30 Apr 2024 20:39:26 +0530 Subject: [PATCH 3/3] Add tests for mobilenet --- models/mobilenet/mobilenet.hpp | 8 +- models/mobilenet/mobilenet_impl.hpp | 8 +- models/mobilenet/separable_convolution.hpp | 441 ++++++++++++++++ .../mobilenet/separable_convolution_impl.hpp | 479 ++++++++++++++++++ tests/CMakeLists.txt | 3 +- tests/mobilenet_tests.cpp | 147 ++++++ 6 files changed, 1078 insertions(+), 8 deletions(-) create mode 100644 models/mobilenet/separable_convolution.hpp create mode 100644 models/mobilenet/separable_convolution_impl.hpp create mode 100644 tests/mobilenet_tests.cpp diff --git a/models/mobilenet/mobilenet.hpp b/models/mobilenet/mobilenet.hpp index c7863bc4..dd02d70d 100644 --- a/models/mobilenet/mobilenet.hpp +++ b/models/mobilenet/mobilenet.hpp @@ -28,6 +28,8 @@ #define MLPACK_ENABLE_ANN_SERIALIZATION #include +#include "separable_convolution.hpp" + #include "./../../utils/utils.hpp" namespace mlpack { @@ -46,7 +48,7 @@ class MobileNetType : public MultiLayer { public: //! Create the MobileNet model. - MobileNetType(); + //MobileNetType(); /** * MobileNetType constructor initializes input shape and number of classes. @@ -103,13 +105,13 @@ class MobileNetType : public MultiLayer void serialize(Archive& ar, const uint32_t /* version */); private: - //! Generate the layers of the AlexNet. + //! Generate the layers of the MobileNet. void MakeModel(); //! Locally stored number of output classes. size_t numClasses; - //! Locally stored if classidier layers are included or not. + //! Locally stored if classifier layers are included or not. bool includeTop; //! Locally stored alpha for mobileNet block creation. diff --git a/models/mobilenet/mobilenet_impl.hpp b/models/mobilenet/mobilenet_impl.hpp index fc10e652..25ffc85a 100644 --- a/models/mobilenet/mobilenet_impl.hpp +++ b/models/mobilenet/mobilenet_impl.hpp @@ -106,7 +106,7 @@ void MobileNetType::serialize( template void MobileNetType::MakeModel() { - this->template Add(32, 3, 3, 2, 2, 0, 0, "none", false); + this->template Add(32, 3, 3, 2, 2, 0, 0); this->template Add(); size_t outSize = size_t(32 * alpha); @@ -119,7 +119,7 @@ void MobileNetType::MakeModel() for (const auto& blockConfig : mobileNetConfig){ this->template Add(0, 1, 0, 1); this->template Add(outSize, outSize*depthMultiplier, - 3, 3, 2, 2, 0, 0, 1, 1, "valid"); + 3, 3, 2, 2, 0, 0, 1, 1, 1, "valid"); this->template Add(); this->template Add(); @@ -128,7 +128,7 @@ void MobileNetType::MakeModel() for (size_t numBlock = 1; numBlock < blockConfig.second; ++numBlock) { this->template Add(outSize, outSize*depthMultiplier, - 3, 3, 1, 1, 0, 0, 0, 0, "same"); + 3, 3, 1, 1, 0, 0, 0, 0, 1, "same"); this->template Add(); this->template Add(); @@ -142,7 +142,7 @@ void MobileNetType::MakeModel() { this->template Add(1e-3); this->template Add(size_t(1024 * alpha), numClasses, - 1, 1, 1, 0, 0, 1, 1, "same"); + 1, 1, 1, 0, 0); this->template Add(); } } diff --git a/models/mobilenet/separable_convolution.hpp b/models/mobilenet/separable_convolution.hpp new file mode 100644 index 00000000..5d2a14bf --- /dev/null +++ b/models/mobilenet/separable_convolution.hpp @@ -0,0 +1,441 @@ +/** + * @file methods/ann/layer/separable_convolution.hpp + * @author Kartik Dutt + * @author Aakash Kaushik + * @author Sidharth + * + * For more information, kindly refer to the following paper. + * + * @code + * @article{ + * author = {Laurent Sifre, Stéphane Mallat}, + * title = {Rigid-Motion Scattering for Texture Classification}, + * year = {2014}, + * url = {https://arxiv.org/pdf/1403.1687} + * } + * @endcode + * + * Definition of the Separable Convolution module class. + * 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_LAYER_SEPARABLE_CONVOLUTION_HPP +#define MLPACK_METHODS_ANN_LAYER_SEPARABLE_CONVOLUTION_HPP + +#include +#include +#include +#include + +namespace mlpack { + +/** + * Depthwise separable convolution is a neural network operation that + * splits standard convolutions into two stages: depthwise convolution + * and pointwise convolution. In the depthwise stage, individual channels + * of the input are convolved separately, reducing computation. + * + * The pointwise stage combines these channel-wise results through + * a 1x1 convolution, preserving inter-channel relationships. + * + * This approach significantly reduces model parameters and computations, + * making it computationally efficient for mobile and edge devices. + * + * The expected input for a depthwise separable convolution layer is a + * 3D tensor with dimensions (height, width, channels). + * + * @tparam ForwardConvolutionRule Convolution to perform forward process. + * @tparam BackwardConvolutionRule Convolution to perform backward process. + * @tparam GradientConvolutionRule Convolution to calculate gradient. + * @tparam MatType Type of the input data (arma::colvec, arma::mat, + * arma::sp_mat or arma::cube). + */ +template < + typename ForwardConvolutionRule = NaiveConvolution, + typename BackwardConvolutionRule = NaiveConvolution, + typename GradientConvolutionRule = NaiveConvolution, + typename MatType = arma::mat +> +class SeparableConvolutionType: public Layer +{ + public: + //! Create the Separable Convolution object. + SeparableConvolutionType(); + + /** + * Create the Separable Convolution object using the specified number of input maps, + * output maps, filter size, stride, padding parameter and number of groups. + * + * @param inSize The number of input maps. + * @param outSize The number of output maps. + * @param kernelWidth Width of the filter/kernel. + * @param kernelHeight Height of the filter/kernel. + * @param strideWidth Stride of filter application in the x direction. + * @param strideHeight Stride of filter application in the y direction. + * @param padW Padding width of the input. + * @param padH Padding height of the input. + * @param inputWidth The width of the input data. + * @param inputHeight The height of the input data. + * @param numGroups The number of groups in which input maps will be divided. + * numGroups = inSize implies depthwise convolution. + * Defaults to 1. + * @param paddingType The type of padding (Valid or Same). Defaults to None. + */ + SeparableConvolutionType(const size_t inSize, + const size_t outSize, + const size_t kernelWidth, + const size_t kernelHeight, + const size_t strideWidth = 1, + const size_t strideHeight = 1, + const size_t padW = 0, + const size_t padH = 0, + const size_t inputWidth = 0, + const size_t inputHeight = 0, + const size_t numGroups = 1, + const std::string& paddingType = "None"); + + /** + * Create the Separable Convolution object using the specified number of input maps, + * output maps, filter size, stride, padding parameter and number of groups. + * + * @param inSize The number of input maps. + * @param outSize The number of output maps. + * @param kernelWidth Width of the filter/kernel. + * @param kernelHeight Height of the filter/kernel. + * @param strideWidth Stride of filter application in the x direction. + * @param strideHeight Stride of filter application in the y direction. + * @param padW A two-value tuple indicating padding widths of the input. + * First value is padding at left side. Second value is padding on + * right side. + * @param padH A two-value tuple indicating padding heights of the input. + * First value is padding at top. Second value is padding on + * bottom. + * @param inputWidth The width of the input data. + * @param inputHeight The height of the input data. + * @param numGroups The number of groups in which input maps will be divided. + * numGroups = inSize implies depthwise convolution. + * Defaults to 1. + * @param paddingType The type of padding (Valid or Same). Defaults to None. + */ + SeparableConvolutionType(const size_t inSize, + const size_t outSize, + const size_t kernelWidth, + const size_t kernelHeight, + const size_t strideWidth, + const size_t strideHeight, + const std::tuple padW, + const std::tuple padH, + const size_t inputWidth = 0, + const size_t inputHeight = 0, + const size_t numGroups = 1, + const std::string& paddingType = "None"); + + /* + * Set the weight and bias term. + */ + void Reset(); + + //! Clone the SeparableConvolutionType object. This handles polymorphism + //! correctly. + SeparableConvolutionType* Clone() const override + { + return new SeparableConvolutionType(*this); + } + /** + * 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. + */ + void Forward(const MatType& input, MatType& 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. + * @param gy The backpropagated error. + * @param g The calculated gradient. + */ + void Backward(const MatType& /* input */, + const MatType& gy, + MatType& g); + + /** + * Calculate the gradient using the output delta and the input activation. + * + * @param input The input parameter used for calculating the gradient. + * @param error The calculated error. + * @param gradient The calculated gradient. + */ + void Gradient(const MatType& /* input */, + const MatType& error, + MatType& gradient); + + //! Get the input parameter. + MatType const& InputParameter() const { return inputParameter; } + //! Modify the input parameter. + MatType& InputParameter() { return inputParameter; } + + //! Get the output parameter. + MatType const& OutputParameter() const { return outputParameter; } + //! Modify the output parameter. + MatType& OutputParameter() { return outputParameter; } + + //! Get the parameters. + MatType const& Parameters() const { return weights; } + //! Modify the parameters. + MatType& Parameters() { return weights; } + + //! Get the delta. + MatType const& Delta() const { return delta; } + //! Modify the delta. + MatType& Delta() { return delta; } + + //! Get the gradient. + MatType const& Gradient() const { return gradient; } + //! Modify the gradient. + MatType& Gradient() { return gradient; } + + //! Get the bias of the layer. + arma::mat const& Bias() const { return bias; } + //! Modify the bias of the layer. + arma::mat& Bias() { return bias; } + + //! Get the input width. + size_t InputWidth() const { return inputWidth; } + //! Modify input the width. + size_t& InputWidth() { return inputWidth; } + + //! Get the input height. + size_t InputHeight() const { return inputHeight; } + //! Modify the input height. + size_t& InputHeight() { return inputHeight; } + + //! Get the output width. + size_t OutputWidth() const { return outputWidth; } + //! Modify the output width. + size_t& OutputWidth() { return outputWidth; } + + //! Get the output height. + size_t OutputHeight() const { return outputHeight; } + //! Modify the output height. + size_t& OutputHeight() { return outputHeight; } + + //! Get the number of input maps. + size_t InputSize() const { return inSize; } + + //! Get the number of output maps. + size_t OutputSize() const { return outSize; } + + //! Get the kernel width. + size_t KernelWidth() const { return kernelWidth; } + //! Modify the kernel width. + size_t& KernelWidth() { return kernelWidth; } + + //! Get the kernel height. + size_t KernelHeight() const { return kernelHeight; } + //! Modify the kernel height. + size_t& KernelHeight() { return kernelHeight; } + + //! Get the stride width. + size_t StrideWidth() const { return strideWidth; } + //! Modify the stride width. + size_t& StrideWidth() { return strideWidth; } + + //! Get the stride height. + size_t StrideHeight() const { return strideHeight; } + //! Modify the stride height. + size_t& StrideHeight() { return strideHeight; } + + //! Get number of Groups for Grouped Convolution. + size_t NumGroups() const { return numGroups; } + //! Modify the number of Groups. + size_t& NumGroups() { return numGroups; } + + //! Get the top padding height. + size_t PadHTop() const { return padHTop; } + //! Modify the top padding height. + size_t& PadHTop() { return padHTop; } + + //! Get the bottom padding height. + size_t PadHBottom() const { return padHBottom; } + //! Modify the bottom padding height. + size_t& PadHBottom() { return padHBottom; } + + //! Get the left padding width. + size_t PadWLeft() const { return padWLeft; } + //! Modify the left padding width. + size_t& PadWLeft() { return padWLeft; } + + //! Get the right padding width. + size_t PadWRight() const { return padWRight; } + //! Modify the right padding width. + size_t& PadWRight() { return padWRight; } + + /** + * Serialize the layer. + */ + template + void serialize(Archive& ar, const uint32_t /* version */); + + private: + /** + * Return the convolution output size. + * + * @param size The size of the input (row or column). + * @param k The size of the filter (width or height). + * @param s The stride size (x or y direction). + * @param pSideOne The size of the padding (width or height) on one side. + * @param pSideTwo The size of the padding (width or height) on another side. + * @return The convolution output size. + */ + size_t ConvOutSize(const size_t size, + const size_t k, + const size_t s, + const size_t pSideOne, + const size_t pSideTwo) + { + return std::floor(size + pSideOne + pSideTwo - k) / s + 1; + } + + /* + * Function to assign padding such that output size is same as input size. + */ + void InitializeSamePadding(); + + /** + * Rotates a 3rd-order tensor counterclockwise by 180 degrees. + * + * @param input The input data to be rotated. + * @param output The rotated output. + */ + template + void Rotate180(const arma::Cube& input, arma::Cube& output) + { + output = arma::Cube(input.n_rows, input.n_cols, input.n_slices); + + // * left-right flip, up-down flip */ + for (size_t s = 0; s < output.n_slices; s++) + output.slice(s) = arma::fliplr(arma::flipud(input.slice(s))); + } + + /** + * Rotates a dense matrix counterclockwise by 180 degrees. + * + * @param input The input data to be rotated. + * @param output The rotated output. + */ + + void Rotate180(const MatType& input, MatType& output) + { + /* left-right flip, up-down flip */ + output = arma::fliplr(arma::flipud(input)); + } + + //! Locally-stored number of input channels. + size_t inSize; + + //! Locally-stored number of output channels. + size_t outSize; + + //! Locally-stored number of input units. + size_t batchSize; + + //! Locally-stored filter/kernel width. + size_t kernelWidth; + + //! Locally-stored filter/kernel height. + size_t kernelHeight; + + //! Locally-stored stride of the filter in x-direction. + size_t strideWidth; + + //! Locally-stored stride of the filter in y-direction. + size_t strideHeight; + + //! Locally-stored left-side padding width. + size_t padWLeft; + + //! Locally-stored right-side padding width. + size_t padWRight; + + //! Locally-stored bottom padding height. + size_t padHBottom; + + //! Locally-stored top padding height. + size_t padHTop; + + //! Locally-stored weights object. + MatType weights; + + //! Locally-stored weight object. + arma::cube weight; + + //! Locally-stored bias object. + arma::mat bias; + + //! Locally-stored input width. + size_t inputWidth; + + //! Locally-stored input height. + size_t inputHeight; + + //! Locally-stored output width. + size_t outputWidth; + + //! Locally-stored output height. + size_t outputHeight; + + //! Locally stored number of Groups parameter. + size_t numGroups; + + //! Locally-stored transformed output parameter. + arma::cube outputTemp; + + //! Locally-stored transformed input parameter. + arma::cube inputTemp; + + //! Locally-stored transformed padded input parameter. + arma::cube inputPaddedTemp; + + //! Locally-stored transformed error parameter. + arma::cube gTemp; + + //! Locally-stored transformed gradient parameter. + arma::cube gradientTemp; + + //! Locally-stored padding layer. + Padding padding; + + //! Locally-stored delta object. + MatType delta; + + //! Locally-stored gradient object. + MatType gradient; + + //! Locally-stored input parameter object. + MatType inputParameter; + + //! Locally-stored output parameter object. + MatType outputParameter; + +}; // Separable Convolution Class. + +typedef SeparableConvolutionType< + NaiveConvolution, + NaiveConvolution, + NaiveConvolution, + arma::mat> + SeparableConvolution; +} // namespace mlpack + +// Include implementation. +#include "separable_convolution_impl.hpp" + +#endif + diff --git a/models/mobilenet/separable_convolution_impl.hpp b/models/mobilenet/separable_convolution_impl.hpp new file mode 100644 index 00000000..97a68553 --- /dev/null +++ b/models/mobilenet/separable_convolution_impl.hpp @@ -0,0 +1,479 @@ +/** + * @file methods/ann/layer/separable_convolution_impl.hpp + * @author Kartik Dutt + * @author Aakash Kaushik + * @author Sidharth + * + * Implementation of the Separable Convolution module class. + * + * 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_LAYER_SEPARABLE_CONVOLUTION_IMPL_HPP +#define MLPACK_METHODS_ANN_LAYER_SEPARABLE_CONVOLUTION_IMPL_HPP + +// In case it hasn't yet been included. +#include "separable_convolution.hpp" + +namespace mlpack { + +template < + typename ForwardConvolutionRule, + typename BackwardConvolutionRule, + typename GradientConvolutionRule, + typename MatType +> +SeparableConvolutionType< + ForwardConvolutionRule, + BackwardConvolutionRule, + GradientConvolutionRule, + MatType +>::SeparableConvolutionType() +{ + // Nothing to do here. +} + +template < + typename ForwardConvolutionRule, + typename BackwardConvolutionRule, + typename GradientConvolutionRule, + typename MatType +> +SeparableConvolutionType< + ForwardConvolutionRule, + BackwardConvolutionRule, + GradientConvolutionRule, + MatType +>::SeparableConvolutionType( + const size_t inSize, + const size_t outSize, + const size_t kernelWidth, + const size_t kernelHeight, + const size_t strideWidth, + const size_t strideHeight, + const size_t padW, + const size_t padH, + const size_t inputWidth, + const size_t inputHeight, + const size_t numGroups, + const std::string &paddingType) : + inSize(inSize), + outSize(outSize), + kernelWidth(kernelWidth), + kernelHeight(kernelHeight), + strideWidth(strideWidth), + strideHeight(strideHeight), + padWLeft(padW), + padWRight(padW), + padHBottom(padH), + padHTop(padH), + inputWidth(inputWidth), + inputHeight(inputHeight), + outputWidth(0), + outputHeight(0), + numGroups(numGroups) +{ + if (inSize % numGroups != 0 || outSize % numGroups != 0) + { + Log::Fatal << "The output maps / input maps is not possible given " + << "the number of groups. Input maps / output maps must be " + << " divisible by number of groups." << std::endl; + } + + weights.set_size((outSize * (inSize / numGroups) * kernelWidth * + kernelHeight) + outSize, 1); + + // Transform paddingType to lowercase. + std::string paddingTypeLow = paddingType; + std::transform(paddingType.begin(), paddingType.end(), paddingTypeLow.begin(), + [](unsigned char c){ return std::tolower(c); }); + + if (paddingTypeLow == "valid") + { + padWLeft = 0; + padWRight = 0; + padHTop = 0; + padHBottom = 0; + } + else if (paddingTypeLow == "same") + { + InitializeSamePadding(); + } + + padding = Padding(padWLeft, padWRight, padHTop, padHBottom); +} + +template < + typename ForwardConvolutionRule, + typename BackwardConvolutionRule, + typename GradientConvolutionRule, + typename MatType +> +SeparableConvolutionType< + ForwardConvolutionRule, + BackwardConvolutionRule, + GradientConvolutionRule, + MatType +>::SeparableConvolutionType( + const size_t inSize, + const size_t outSize, + const size_t kernelWidth, + const size_t kernelHeight, + const size_t strideWidth, + const size_t strideHeight, + const std::tuple padW, + const std::tuple padH, + const size_t inputWidth, + const size_t inputHeight, + const size_t numGroups, + const std::string &paddingType) : + inSize(inSize), + outSize(outSize), + kernelWidth(kernelWidth), + kernelHeight(kernelHeight), + strideWidth(strideWidth), + strideHeight(strideHeight), + padWLeft(std::get<0>(padW)), + padWRight(std::get<1>(padW)), + padHBottom(std::get<1>(padH)), + padHTop(std::get<0>(padH)), + inputWidth(inputWidth), + inputHeight(inputHeight), + outputWidth(0), + outputHeight(0), + numGroups(numGroups) +{ + if (inSize % numGroups != 0 || outSize % numGroups != 0) + { + Log::Fatal << "The output maps / input maps is not possible given " + << "the number of groups. Input maps / output maps must be " + << " divisible by number of groups." << std::endl; + } + + weights.set_size((outSize * (inSize / numGroups) * kernelWidth * + kernelHeight) + outSize, 1); + + // Transform paddingType to lowercase. + std::string paddingTypeLow = paddingType; + std::transform(paddingType.begin(), paddingType.end(), paddingTypeLow.begin(), + [](unsigned char c){ return std::tolower(c); }); + + if (paddingTypeLow == "valid") + { + padWLeft = 0; + padWRight = 0; + padHTop = 0; + padHBottom = 0; + } + else if (paddingTypeLow == "same") + { + InitializeSamePadding(); + } + + padding = Padding(padWLeft, padWRight, padHTop, padHBottom); +} + +template< + typename ForwardConvolutionRule, + typename BackwardConvolutionRule, + typename GradientConvolutionRule, + typename MatType +> +void SeparableConvolutionType< + ForwardConvolutionRule, + BackwardConvolutionRule, + GradientConvolutionRule, + MatType +>::Reset() +{ + weight = arma::cube(weights.memptr(), kernelWidth, kernelHeight, + outSize * (inSize / numGroups), false, false); + bias = arma::mat(weights.memptr() + weight.n_elem, + outSize, 1, false, false); +} + +template< + typename ForwardConvolutionRule, + typename BackwardConvolutionRule, + typename GradientConvolutionRule, + typename MatType +> +void SeparableConvolutionType< + ForwardConvolutionRule, + BackwardConvolutionRule, + GradientConvolutionRule, + MatType +>::Forward(const MatType& input, MatType& output) +{ + typedef typename arma::Cube CubeType; + batchSize = input.n_cols; + inputTemp = arma::cube(const_cast(input).memptr(), + inputWidth, inputHeight, inSize * batchSize, false, false); + + if (padWLeft != 0 || padWRight != 0 || padHTop != 0 || padHBottom != 0) + { + inputPaddedTemp.set_size(inputTemp.n_rows + padWLeft + padWRight, + inputTemp.n_cols + padHTop + padHBottom, inputTemp.n_slices); + + for (size_t i = 0; i < inputTemp.n_slices; ++i) + { + padding.Forward(inputTemp.slice(i), inputPaddedTemp.slice(i)); + } + } + + size_t wConv = ConvOutSize(inputWidth, kernelWidth, strideWidth, padWLeft, + padWRight); + size_t hConv = ConvOutSize(inputHeight, kernelHeight, strideHeight, padHTop, + padHBottom); + + output.set_size(wConv * hConv * outSize, batchSize); + outputTemp = CubeType(output.memptr(), wConv, hConv, + outSize * batchSize, false, false); + outputTemp.zeros(); + + for (size_t curGroup = 0; curGroup < numGroups; curGroup++) + { + for (size_t outMap = outSize * curGroup * batchSize / numGroups, + outMapIdx = outSize * curGroup / numGroups, batchCount = 0; + outMap < outSize * (curGroup + 1) * batchSize / numGroups; outMap++) + { + if (outMap != 0 && outMap % outSize == 0) + { + batchCount++; + outMapIdx = 0; + } + + for (size_t inMap = inSize * curGroup / numGroups; + inMap < inSize * (curGroup + 1) / numGroups; inMap++, outMapIdx++) + { + MatType convOutput; + if (padWLeft != 0 || padWRight != 0 || padHTop != 0 || padHBottom != 0) + { + ForwardConvolutionRule::Convolution(inputPaddedTemp.slice(inMap + + batchCount * inSize), weight.slice(outMapIdx), convOutput, + strideWidth, strideHeight); + } + else + { + ForwardConvolutionRule::Convolution(inputTemp.slice(inMap + + batchCount * inSize), weight.slice(outMapIdx), convOutput, + strideWidth, strideHeight); + } + + outputTemp.slice(outMap) += convOutput; + } + outputTemp.slice(outMap) += bias(outMap % outSize); + } + } + outputWidth = outputTemp.n_rows; + outputHeight = outputTemp.n_cols; +} + +template< + typename ForwardConvolutionRule, + typename BackwardConvolutionRule, + typename GradientConvolutionRule, + typename MatType +> +void SeparableConvolutionType< + ForwardConvolutionRule, + BackwardConvolutionRule, + GradientConvolutionRule, + MatType +>::Backward(const MatType& /* input */, + const MatType& gy, + MatType& g) +{ + typedef typename arma::Cube CubeType; + arma::cube mappedError(const_cast(gy).memptr(), outputWidth, + outputHeight, outSize * batchSize, false, false); + + g.set_size(inputTemp.n_rows * inputTemp.n_cols * inSize, batchSize); + gTemp = CubeType(g.memptr(), inputTemp.n_rows, + inputTemp.n_cols, inputTemp.n_slices, false, false); + gTemp.zeros(); + + for (size_t curGroup = 0; curGroup < numGroups; curGroup++) + { + for (size_t outMap = outSize * curGroup * batchSize / numGroups, + outMapIdx = outSize * curGroup / numGroups, batchCount = 0; + outMap < outSize * (curGroup + 1) * batchSize / numGroups; outMap++) + { + if (outMap != 0 && outMap % outSize == 0) + { + batchCount++; + outMapIdx = 0; + } + + for (size_t inMap = inSize * curGroup / numGroups; + inMap < inSize * (curGroup + 1) / numGroups; inMap++, outMapIdx++) + { + MatType output, rotatedFilter; + Rotate180(weight.slice(outMapIdx), rotatedFilter); + BackwardConvolutionRule::Convolution(mappedError.slice(outMap), + rotatedFilter, output, strideWidth, strideHeight); + + if (padWLeft != 0 || padWRight != 0 || padHTop != 0 || padHBottom != 0) + { + gTemp.slice(inMap + batchCount * inSize) += output.submat(padWLeft, + padHTop, padWLeft + gTemp.n_rows - 1, padHTop + gTemp.n_cols - 1); + } + else + { + gTemp.slice(inMap + batchCount * inSize) += output; + } + } + } + } +} + +template< + typename ForwardConvolutionRule, + typename BackwardConvolutionRule, + typename GradientConvolutionRule, + typename MatType +> +void SeparableConvolutionType< + ForwardConvolutionRule, + BackwardConvolutionRule, + GradientConvolutionRule, + MatType +>::Gradient(const MatType& /* input */, + const MatType& error, + MatType& gradient) +{ + typedef typename arma::Cube CubeType; + arma::cube mappedError(const_cast(error).memptr(), + outputWidth, outputHeight, outSize * batchSize, false, false); + + gradient.set_size(weights.n_elem, 1); + gradientTemp = CubeType(gradient.memptr(), weight.n_rows, + weight.n_cols, weight.n_slices, false, false); + gradientTemp.zeros(); + + for (size_t curGroup = 0; curGroup < numGroups; curGroup++) + { + for (size_t outMap = outSize * curGroup * batchSize / numGroups, + outMapIdx = outSize * curGroup / numGroups, batchCount = 0; + outMap < outSize * (curGroup + 1) * batchSize / numGroups; outMap++) + { + if (outMap != 0 && outMap % outSize == 0) + { + batchCount++; + outMapIdx = 0; + } + + for (size_t inMap = inSize * curGroup / numGroups; + inMap < inSize * (curGroup + 1) / numGroups; inMap++, outMapIdx++) + { + MatType inputSlice; + if (padWLeft != 0 || padWRight != 0 || padHTop != 0 || padHBottom != 0) + { + inputSlice = inputPaddedTemp.slice(inMap + batchCount * inSize); + } + else + { + inputSlice = inputTemp.slice(inMap + batchCount * inSize); + } + + MatType deltaSlice = mappedError.slice(outMap); + + MatType output; + GradientConvolutionRule::Convolution(inputSlice, deltaSlice, + output, strideWidth, strideHeight); + + if (gradientTemp.n_rows < output.n_rows || + gradientTemp.n_cols < output.n_cols) + { + gradientTemp.slice(outMapIdx) += output.submat(0, 0, + gradientTemp.n_rows - 1, gradientTemp.n_cols - 1); + } + else if (gradientTemp.n_rows > output.n_rows || + gradientTemp.n_cols > output.n_cols) + { + gradientTemp.slice(outMapIdx).submat(0, 0, output.n_rows - 1, + output.n_cols - 1) += output; + } + else + { + gradientTemp.slice(outMapIdx) += output; + } + } + gradient.submat(weight.n_elem + (outMap % outSize), 0, weight.n_elem + + (outMap % outSize), 0) = arma::accu(mappedError.slice(outMap)); + } + } +} + +template< + typename ForwardConvolutionRule, + typename BackwardConvolutionRule, + typename GradientConvolutionRule, + typename MatType +> +template +void SeparableConvolutionType< + ForwardConvolutionRule, + BackwardConvolutionRule, + GradientConvolutionRule, + MatType +>::serialize(Archive& ar, const uint32_t /* version*/) +{ + ar(CEREAL_NVP(inSize)); + ar(CEREAL_NVP(outSize)); + ar(CEREAL_NVP(batchSize)); + ar(CEREAL_NVP(kernelWidth)); + ar(CEREAL_NVP(kernelHeight)); + ar(CEREAL_NVP(strideWidth)); + ar(CEREAL_NVP(strideHeight)); + ar(CEREAL_NVP(padWLeft)); + ar(CEREAL_NVP(padWRight)); + ar(CEREAL_NVP(padHBottom)); + ar(CEREAL_NVP(padHTop)); + ar(CEREAL_NVP(inputWidth)); + ar(CEREAL_NVP(inputHeight)); + ar(CEREAL_NVP(outputWidth)); + ar(CEREAL_NVP(outputHeight)); + ar(CEREAL_NVP(numGroups)); + ar(CEREAL_NVP(padding)); + + if (cereal::is_loading()) + { + weights.set_size((outSize * (inSize / numGroups) * kernelWidth * + kernelHeight) + outSize, 1); + } +} + +template< + typename ForwardConvolutionRule, + typename BackwardConvolutionRule, + typename GradientConvolutionRule, + typename MatType +> +void SeparableConvolutionType< + ForwardConvolutionRule, + BackwardConvolutionRule, + GradientConvolutionRule, + MatType +>::InitializeSamePadding() +{ + /* + * Using O = (W - F + 2P) / s + 1; + */ + size_t totalVerticalPadding = (strideWidth - 1) * inputWidth + kernelWidth - + strideWidth; + size_t totalHorizontalPadding = (strideHeight - 1) * inputHeight + + kernelHeight - strideHeight; + + padWLeft = totalVerticalPadding / 2; + padWRight = totalVerticalPadding - totalVerticalPadding / 2; + padHTop = totalHorizontalPadding / 2; + padHBottom = totalHorizontalPadding - totalHorizontalPadding / 2; +} + +} // namespace mlpack + +#endif + diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 8c61abcf..07d31482 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -22,7 +22,8 @@ add_executable(models_test serialization.cpp serialization.hpp test_catch_tools.hpp -# alexnet_tests.cpp + alexnet_tests.cpp + mobilenet_tests.cpp # squeezenet_tests.cpp # vgg_tests.cpp # xception_tests.cpp diff --git a/tests/mobilenet_tests.cpp b/tests/mobilenet_tests.cpp new file mode 100644 index 00000000..127f35c7 --- /dev/null +++ b/tests/mobilenet_tests.cpp @@ -0,0 +1,147 @@ +#include "../models/mobilenet/mobilenet.hpp" + +#include "./test_catch_tools.hpp" +#include "./catch.hpp" +#include "./serialization.hpp" + +using namespace mlpack; + +/** + * Checks for the output dimensions of the model. + * + * @tparam ModelType Type of model to check. + * + * @param model The model to test. + * @param input Input to pass to the model. + * @param n_rows Output rows to check against. + * @param n_cols Output columns to check against. + */ +template +void ModelDimTest(ModelType& model, + arma::mat& input, + const size_t n_rows = 1000, + const size_t n_cols = 1) +{ + arma::mat output; + model.Predict(input, output); + REQUIRE(output.n_rows == n_rows); + REQUIRE(output.n_cols == n_cols); +} + +/** + * Checks for the output sum of the model for a + * single and multiple batch input. + * + * @tparam ModelType Type of model to check. + * + * @param model The model to test. + * @param input Input to pass to the model. + * @param singleBatchOutput Sum of the output of a single batch. + * @param multipleBatchOutput Sum of the output of Multiple batches. + * @param numBatches Number of batches to create for input. + */ +template +void PreTrainedModelTest(ModelType& model, + arma::mat& input, + const double singleBatchOutput, + const double multipleBatchOutput, + const size_t numBatches = 4) +{ + arma::mat multipleBatchInput(input.n_rows, numBatches), output; + input.ones(); + multipleBatchInput.ones(); + + // Run prediction for single batch. + model.Predict(input, output); + REQUIRE(arma::accu(output) == Approx(singleBatchOutput).epsilon(1e-2)); + + // Run prediction for multiple batch. + model.Predict(multipleBatchInput, output); + REQUIRE(arma::accu(output) == Approx(multipleBatchOutput).epsilon(1e-2)); +} + +// General ANN serialization test. +template +void ModelSerializationTest(LayerType& layer) +{ + arma::mat input(224 * 224 * 3, 10, arma::fill::randu); + arma::mat output(1000, 10, arma::fill::randu); + + FFN<> model; + model.Add(layer); + + model.InputDimensions() = std::vector({224, 224, 3}); + + // Takes only one pass over the input data. + ens::StandardSGD opt(0.1, 1, 5, -100, false); + model.Train(input, output, opt); + + arma::mat originalOutput; + model.Predict(input, originalOutput); + + // Now serialize the model. + FFN<> xmlModel, jsonModel, binaryModel; + SerializeObjectAll(model, xmlModel, jsonModel, binaryModel); + + // Ensure that predictions are the same. + arma::mat modelOutput, xmlOutput, jsonOutput, binaryOutput; + model.Predict(input, modelOutput); + xmlModel.Predict(input, xmlOutput); + jsonModel.Predict(input, jsonOutput); + binaryModel.Predict(input, binaryOutput); + + CheckMatrices(originalOutput, modelOutput, 1e-5); + CheckMatrices(originalOutput, xmlOutput, 1e-5); + CheckMatrices(originalOutput, jsonOutput, 1e-5); + CheckMatrices(originalOutput, binaryOutput, 1e-5); +} + +TEST_CASE("MobileNetSerializationTest", "[MobileNetTests]") +{ + models::Mobilenet model; + ModelSerializationTest(model); +} + +TEST_CASE("MobileNetTest", "[MobileNetTests]") +{ + arma::mat input(224 * 224 * 3, 1, arma::fill::randu); + arma::mat output; + models::Mobilenet mobilenetLayer; + FFN<> model; + model.InputDimensions() = std::vector({224, 224, 3}); + model.Add(mobilenetLayer); + ModelDimTest(model, input); +} + +TEST_CASE("MobileNetMultiBatchTest", "[MobileNetTests]") +{ + arma::mat input(224 * 224 * 3, 10, arma::fill::randu); + arma::mat output; + models::Mobilenet mobilenetLayer; + FFN<> model; + model.InputDimensions() = std::vector({224, 224, 3}); + model.Add(mobilenetLayer); + ModelDimTest(model, input, 1000, 10); +} + +TEST_CASE("MobileNetCustomTest", "[MobileNetTests]") +{ + arma::mat input(224 * 224 * 3, 10, arma::fill::randu); + arma::mat output; + models::Mobilenet mobilenetLayer(512); + FFN<> model; + model.InputDimensions() = std::vector({224, 224, 3}); + model.Add(mobilenetLayer); + ModelDimTest(model, input, 512, 10); +} + +TEST_CASE("MobileNetNoTopTest", "[MobileNetTests]") +{ + arma::mat input(224 * 224 * 3, 10, arma::fill::randu); + arma::mat output; + models::Mobilenet mobilenetLayer(512, false); + FFN<> model; + model.InputDimensions() = std::vector({224, 224, 3}); + model.Add(mobilenetLayer); + ModelDimTest(model, input, 9216, 10); +}