Search code examples
c++conv-neural-networkeigentensoreigen3

How to use Eigen::Tensor::convolve with multiple kernels?


Convolving an input tensor of shape (3, 20, 30) (channel-first notation) with 8 filters of shape (3, 5, 7) should result in a tensor of shape (8, 24, 16). I'm trying to implement this using Eigen::Tensor::convolve, but the resulting shape is (1, 24, 16). So it seems like only one of the filters is applied instead of all 8.

Here is a minimal example:

#include <cassert>
#include <iostream>
#include <eigen3/unsupported/Eigen/CXX11/Tensor>

int main() {
    int input_height = 20;
    int input_width = 30;
    int input_channels = 3;

    int kernels_height = 5;
    int kernels_width = 7;
    int kernels_channels = 3;
    int kernel_count = 8;

    assert(kernels_channels == input_channels);

    int expected_output_height = input_height + 1 - kernels_height;
    int expected_output_width = input_width + 1 - kernels_width;
    int expected_output_channels = kernel_count;

    Eigen::Tensor<float, 3> input(input_channels, input_width, input_height);
    Eigen::Tensor<float, 4> filters(kernels_channels, kernels_width, kernels_height, kernel_count);

    Eigen::array<ptrdiff_t, 3> dims({0, 1, 2});
    Eigen::Tensor<float, 3> output = input.convolve(filters, dims);

    const Eigen::Tensor<float, 3>::Dimensions& d = output.dimensions();

    std::cout << "Expected output shape: (" << expected_output_channels << ", " << expected_output_width << ", " << expected_output_height << ")" << std::endl;
    std::cout << "Actual shape: (" << d[0] << ", " << d[1] << ", " << d[2] << ")" << std::endl;
}

And its output:

Expected output shape: (8, 24, 16)
Actual shape: (1, 24, 16)

Sure, one could iterate over the filters one by one and call .convolve for each one but this

  • would result in a tensor with the channels not being the first dimension
  • might not be as performance-optimized as doing all in one call
  • requires more custom code

So I guess I'm doing something wrong in my usage of the Eigen library. How is it done correctly?


Solution

  • It doesn't support convolution with several kernels at once (docs):

    The dimension size for dimensions of the output tensor which were part of the convolution will be reduced by the formula: output_dim_size = input_dim_size - kernel_dim_size + 1 (requires: input_dim_size >= kernel_dim_size). The dimension sizes for dimensions that were not part of the convolution will remain the same.

    According to above expected_output_channels should be equal to 1 = 3 - 3 + 1.

    I don't think it should be possible to do as you wish, because convolution operation is a mathematical one and well defined, so it would be strange if it wouldn't follow math definition.

    Not tested solution

    I didn't check, but I believe the next code produces output as you wish:

    Eigen::Tensor<float, 3> input(input_channels, input_width, input_height);
    Eigen::Tensor<float, 4> filters(kernels_channels, kernels_width, kernels_height, kernel_count);
    Eigen::Tensor<float, 3> output(kernel_count, expected_output_width, expected_output_height);
    
    Eigen::array<ptrdiff_t, 3> dims({0, 1, 2});
    
    for (int i = 0; i < kernel_count; ++i){
        output.chip(i, 0) = input.convolve(filters.chip(i, 3), dims).chip(0, 0);
    }
    

    As you can see, the first and third issues are not a big problem. Hope you will be lucky and this part of code will not be your bottleneck:)