From 3190ec0be23bb5d3a3dd692f11fffdc9de00d8f8 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Sat, 6 Aug 2022 19:30:37 +0530 Subject: [PATCH] changes acc to suggestions --- .../methods/ann/layer/grouped_convolution.hpp | 58 +++++------ .../ann/layer/grouped_convolution_impl.hpp | 5 +- .../tests/ann/layer/grouped_convolution.cpp | 99 +++++++++++++++++++ 3 files changed, 128 insertions(+), 34 deletions(-) diff --git a/src/mlpack/methods/ann/layer/grouped_convolution.hpp b/src/mlpack/methods/ann/layer/grouped_convolution.hpp index 03003a0916..9ed92fd7f4 100644 --- a/src/mlpack/methods/ann/layer/grouped_convolution.hpp +++ b/src/mlpack/methods/ann/layer/grouped_convolution.hpp @@ -28,38 +28,19 @@ namespace mlpack { namespace ann /** Artificial Neural Network. */ { /** - * Implementation of the Grouped Convolution class. The Grouped Convolution - * class represents a single layer of a neural network. - * Example usage: - * - * Suppose we want to pass a matrix M (2744x100) to a `Convolution` layer; - * in this example, `M` was obtained from "flattening" 100 images (or Mel - * cepstral coefficients, if we talk about speech, or whatever you like) of - * dimension 196x14. In other words, the first 196 columns of each row of M - * will be made of the 196 columns of the first row of each of the 100 images - * (or Mel cepstral coefficients). Then the next 295 columns of M (196 - 393) - * will be made of the 196 columns of the second row of the 100 images (or Mel - * cepstral coefficients), etc. Given that the size of our 2-D input images is - * 196x14, the parameters for our `Convolution` layer will be something like - * this: - * - * ``` - * GroupedConvolution<> c(1, // Number of input activation maps. - * 14, // Number of output activation maps. - * 3, // Filter width. - * 3, // Filter height. - * 1, // Number of groups. - * 1, // Stride along width. - * 1, // Stride along height. - * 0, // Padding width. - * 0, // Padding height. - * 196, // Input width. - * 14); // Input height. - * ``` - * - * This `Convolution<>` layer will treat each column of the input matrix `M` as - * a 2-D image (or object) of the original 196x14 size, using this as the input - * for the 14 filters of this example. + * Implementation of the Grouped Convolution class. + * + * For information on convolution, please refer to the convolution layer. + * + * A Grouped Convolution uses a group of convolutions - multiple kernels per + * layer - resulting in multiple channel outputs per layer. This leads to wider + * networks helping a network learn a varied set of low level and high level + * features. The original motivation of using Grouped Convolutions in AlexNet + * was to distribute the model over multiple GPUs as an engineering compromise. + * But later, with models such as ResNeXt, it was shown this module could be + * used to improve classification accuracy. Specifically by exposing a new + * dimension through grouped convolutions, cardinality (the size of set of + * transformations), we can increase accuracy by increasing it. * * The `groups` parameter controls the connections between inputs and outputs. * inMaps and outMaps must both be divisible by groups. @@ -70,6 +51,19 @@ namespace ann /** Artificial Neural Network. */ { * the output channels, and both subsequently concatenated. * At groups= inMaps, each input channel is convolved with its own set of * filters (of size \frac{\text{out\_channels}}{\text{in\_channels}}). + * + * For more information, kindly refer to the following paper. + * + * Paper for Grouped Convolution. + * + * @code + * @article{Huang2018, + * author = {Gao Huang, Shichen Liu, Laurens van der Maaten, Kilian Q. Weinberger}, + * title = {CondenseNet: An Efficient DenseNet Using Learned Group Convolutions}, + * year = {2018}, + * url = {https://openaccess.thecvf.com/content_cvpr_2018/papers/Huang_CondenseNet_An_Efficient_CVPR_2018_paper.pdf} + * } + * @endcode * * @tparam ForwardConvolutionRule Convolution to perform forward process. * @tparam BackwardConvolutionRule Convolution to perform backward process. diff --git a/src/mlpack/methods/ann/layer/grouped_convolution_impl.hpp b/src/mlpack/methods/ann/layer/grouped_convolution_impl.hpp index 434da8a72b..e35441d376 100644 --- a/src/mlpack/methods/ann/layer/grouped_convolution_impl.hpp +++ b/src/mlpack/methods/ann/layer/grouped_convolution_impl.hpp @@ -608,8 +608,9 @@ void GroupedConvolutionType< if ((inMaps % groups != 0) || (maps % groups != 0)) { - Log::Fatal << "Both input maps and output maps should be divisible by " - << "groups." << std::endl; + Log::Fatal << "Both input maps (" << inMaps << ") and output maps (" + << maps << ") should be divisible by groups (" << groups << ")!" + << std::endl; } // Compute and cache the total number of input maps. diff --git a/src/mlpack/tests/ann/layer/grouped_convolution.cpp b/src/mlpack/tests/ann/layer/grouped_convolution.cpp index 4f3842c1c1..21c3f99b44 100644 --- a/src/mlpack/tests/ann/layer/grouped_convolution.cpp +++ b/src/mlpack/tests/ann/layer/grouped_convolution.cpp @@ -68,6 +68,105 @@ TEST_CASE("GroupedConvolutionLayerTest", "[ANNLayerTest]") REQUIRE(arma::accu(delta) == Approx(686.7855224609).epsilon(1e-5)); } +/** + * Test for testing equivalence of grouped convolution (groups = 1) + * with convolution layer. + */ +TEST_CASE("GroupedConvolutionEquivalenceTest", "[ANNLayerTest]") +{ + arma::mat input, output, outputG; + + // The input test matrix is of the form 3 x 2 x 2 x 2 where + // number of images are 3 and number of feature maps are 2. + input = { { 1, 446, 42 }, + { 2, 16, 63 }, + { 3, 13, 63 }, + { 4, 21, 21 }, + { 1, 13, 11 }, + { 32, 45, 42 }, + { 22, 16 , 63 }, + { 32, 13 , 42 } }; + + Convolution layer(2, 2, 2, 1, 1, 0, 0); + layer.InputDimensions() = std::vector({ 2, 2, 2 }); + layer.ComputeOutputDimensions(); + arma::mat layerWeights(layer.WeightSize(), 1); + layerWeights(0) = 0.23757622; + layerWeights(1) = -0.11899071; + layerWeights(2) = 0.10450475; + layerWeights(3) = -0.1303806; + layerWeights(4) = -0.34706244; + layerWeights(5) = -0.09472395; + layerWeights(6) = 0.04117536; + layerWeights(7) = -0.23012237; + layerWeights(8) = -0.02827594; + layerWeights(9) = -0.24280427; + layerWeights(10) = 0.33375624; + layerWeights(11) = -0.12285174; + layerWeights(12) = -0.05546845; + layerWeights(13) = -0.01502632; + layerWeights(14) = -0.25894147; + layerWeights(15) = -0.2283206; + layerWeights(16) = 0.3204123974; + layerWeights(17) = 0.2334779799; + layer.SetWeights(layerWeights.memptr()); + output.set_size(layer.OutputSize(), 3); + + GroupedConvolution layerG(2, 2, 2, 1, 1, 1, 0, 0); + layerG.InputDimensions() = std::vector({ 2, 2, 2 }); + layerG.ComputeOutputDimensions(); + arma::mat layerWeightsG(layerG.WeightSize(), 1); + layerWeightsG(0) = 0.23757622; + layerWeightsG(1) = -0.11899071; + layerWeightsG(2) = 0.10450475; + layerWeightsG(3) = -0.1303806; + layerWeightsG(4) = -0.34706244; + layerWeightsG(5) = -0.09472395; + layerWeightsG(6) = 0.04117536; + layerWeightsG(7) = -0.23012237; + layerWeightsG(8) = -0.02827594; + layerWeightsG(9) = -0.24280427; + layerWeightsG(10) = 0.33375624; + layerWeightsG(11) = -0.12285174; + layerWeightsG(12) = -0.05546845; + layerWeightsG(13) = -0.01502632; + layerWeightsG(14) = -0.25894147; + layerWeightsG(15) = -0.2283206; + layerWeightsG(16) = 0.3204123974; + layerWeightsG(17) = 0.2334779799; + layerG.SetWeights(layerWeightsG.memptr()); + outputG.set_size(layerG.OutputSize(), 3); + + layer.Forward(input, output); + layerG.Forward(input, outputG); + + // Value calculated using torch.nn.Conv2d(). + CheckMatrices(output, outputG); + REQUIRE(arma::accu(output) == Approx(12.6755657196).epsilon(1e-5)); + REQUIRE(arma::accu(outputG) == Approx(12.6755657196).epsilon(1e-5)); + + arma::mat delta, deltaG; + delta.set_size(8, 3); + deltaG.set_size(8, 3); + layer.Backward(input, output, delta); + layerG.Backward(input, outputG, deltaG); + + CheckMatrices(delta, deltaG); + REQUIRE(arma::accu(delta) == Approx(-1.9237523079).epsilon(1e-5)); + REQUIRE(arma::accu(deltaG) == Approx(-1.9237523079).epsilon(1e-5)); +} + +TEST_CASE("EdgeCaseFailGroupedConvolutionTest", "[ANNLayerTest]") +{ + GroupedConvolution layer(2, 2, 2, 0, 1, 1, 0, 0); + layer.InputDimensions() = std::vector({ 2, 2, 2 }); + REQUIRE_THROWS(layer.ComputeOutputDimensions()); + + GroupedConvolution layer2(3, 2, 2, 2, 1, 1, 0, 0); + layer2.InputDimensions() = std::vector({ 2, 2, 2 }); + REQUIRE_THROWS(layer2.ComputeOutputDimensions()); +} + /** * Grouped Convolution layer numerical gradient test. */