首页 > 解决方案 > 使用特征张量复制 TensorFlow 的 Conv2D 操作

问题描述

我正在尝试在 c++ 中实现 TensorFlow 图的轻量级(最小库依赖项)版本,并且我正在尝试使用 Eigen Tensor 对象来执行图操作。现在我一直在尝试使用 EigenTensor.convolve()方法来尝试复制 TensorFlow 的 Conv2D 操作的行为。为了简单起见,我最初的 Conv2D 操作没有填充和步幅。

卷积层的输入是一个 51x51x1 的张量,它与一个大小为 3x3x1x16 的滤波器组进行卷积。在 tensorflow 中,这会生成大小为 49x49x16 的输出张量。使用下面的 Eigen 代码在 C++ 中设置相同的操作仅填充输出张量的第一个通道,因此顶部的 49x49x1 单元格包含正确的值,但未填充剩余的 1-15 个通道。

  Eigen::TensorMap<Eigen::Tensor<float,4> > filter(filter, 3, 3, 1, 16 );
  Eigen::TensorMap<Eigen::Tensor<float,3> > input(inputBuffer, 51, 51, 1 );
  Eigen::TensorMap<Eigen::Tensor<float,3> > output(outputBuffer, 49, 49, 16);

  Eigen::array<ptrdiff_t, 2> convDims({0, 1});
  output = input.convolve(filter, convDims);

我假设我误解了这些函数的作用,并且它们没有执行相同的操作。为了使我的实现正常工作,我尝试循环遍历 16 个过滤器通道并将卷积方法单独应用于每个通道,但是我遇到了编译器错误,下面的代码我无法理解:

  for (int s=0; s<16; ++s)
  {
    Eigen::array<int, 4> fOffset = {0, 0, 0, s};
    Eigen::array<int, 4> fExtent = {3, 3, 1, 1};

    Eigen::array<int, 3> oOffset = {0, 0, s};
    Eigen::array<int, 3> oExtent = {49, 49, 1};

    auto filterSlice = filter.slice(fOffset, fExtent);

    output.slice(oOffset, oExtent) = input.convolve(filterSlice, convDims);
  }

此代码从 Eigen Tensor 代码中的某处产生以下错误,它可能与 slice 方法的结果的分配有关,但我不确定。如果将结果分配给自动类型,则它会编译,但如果稍后评估结果则不会。

如果有人知道如何解决此错误或更一般地知道如何使用特征张量复制 Conv2D 操作,那将是一个很大的帮助。

/home/user/tensorflow_xla/bcc-2.0.2-gcc/sparc-gaisler-elf/include/unsupported/Eigen/CXX11/src/Tensor/TensorConvolution.h: In instantiation of 'void Eigen::TensorEvaluator<const Eigen::TensorConvolutionOp<Dimensions, InputXprType, KernelXprType>, Device>::preloadKernel() [with Indices = const std::array<int, 2>; InputArgType = const Eigen::TensorMap<Eigen::Tensor<float, 3> >; KernelArgType = const Eigen::TensorSlicingOp<const std::array<int, 4>, const std::array<int, 4>, Eigen::TensorMap<Eigen::Tensor<float, 4> > >; Device = Eigen::DefaultDevice]':
/home/user/tensorflow_xla/bcc-2.0.2-gcc/sparc-gaisler-elf/include/unsupported/Eigen/CXX11/src/Tensor/TensorConvolution.h:383:18:   required from 'bool Eigen::TensorEvaluator<const Eigen::TensorConvolutionOp<Dimensions, InputXprType, KernelXprType>, Device>::evalSubExprsIfNeeded(Eigen::TensorEvaluator<const Eigen::TensorConvolutionOp<Dimensions, InputXprType, KernelXprType>, Device>::Scalar*) [with Indices = const std::array<int, 2>; InputArgType = const Eigen::TensorMap<Eigen::Tensor<float, 3> >; KernelArgType = const Eigen::TensorSlicingOp<const std::array<int, 4>, const std::array<int, 4>, Eigen::TensorMap<Eigen::Tensor<float, 4> > >; Device = Eigen::DefaultDevice; Eigen::TensorEvaluator<const Eigen::TensorConvolutionOp<Dimensions, InputXprType, KernelXprType>, Device>::Scalar = float]'
/home/user/tensorflow_xla/bcc-2.0.2-gcc/sparc-gaisler-elf/include/unsupported/Eigen/CXX11/src/Tensor/TensorAssign.h:146:62:   required from 'bool Eigen::TensorEvaluator<const Eigen::TensorAssignOp<LhsXprType, RhsXprType>, Device>::evalSubExprsIfNeeded(Eigen::TensorEvaluator<const Eigen::TensorAssignOp<LhsXprType, RhsXprType>, Device>::Scalar*) [with LeftArgType = Eigen::TensorSlicingOp<const std::array<int, 3>, const std::array<int, 3>, Eigen::TensorMap<Eigen::Tensor<float, 3> > >; RightArgType = const Eigen::TensorConvolutionOp<const std::array<int, 2>, const Eigen::TensorMap<Eigen::Tensor<float, 3> >, const Eigen::TensorSlicingOp<const std::array<int, 4>, const std::array<int, 4>, Eigen::TensorMap<Eigen::Tensor<float, 4> > > >; Device = Eigen::DefaultDevice; Eigen::TensorEvaluator<const Eigen::TensorAssignOp<LhsXprType, RhsXprType>, Device>::Scalar = float]'
/home/user/tensorflow_xla/bcc-2.0.2-gcc/sparc-gaisler-elf/include/unsupported/Eigen/CXX11/src/Tensor/TensorExecutor.h:45:16:   required from 'static void Eigen::internal::TensorExecutor<Expression, Device, Vectorizable, Tileable>::run(const Expression&, const Device&) [with Expression = const Eigen::TensorAssignOp<Eigen::TensorSlicingOp<const std::array<int, 3>, const std::array<int, 3>, Eigen::TensorMap<Eigen::Tensor<float, 3> > >, const Eigen::TensorConvolutionOp<const std::array<int, 2>, const Eigen::TensorMap<Eigen::Tensor<float, 3> >, const Eigen::TensorSlicingOp<const std::array<int, 4>, const std::array<int, 4>, Eigen::TensorMap<Eigen::Tensor<float, 4> > > > >; Device = Eigen::DefaultDevice; bool Vectorizable = false; bool Tileable = false]'
/home/user/tensorflow_xla/bcc-2.0.2-gcc/sparc-gaisler-elf/include/unsupported/Eigen/CXX11/src/Tensor/TensorMorphing.h:448:65:   required from 'Eigen::TensorSlicingOp<StartIndices, Sizes, XprType>& Eigen::TensorSlicingOp<StartIndices, Sizes, XprType>::operator=(const OtherDerived&) [with OtherDerived = Eigen::TensorConvolutionOp<const std::array<int, 2>, const Eigen::TensorMap<Eigen::Tensor<float, 3> >, const Eigen::TensorSlicingOp<const std::array<int, 4>, const std::array<int, 4>, Eigen::TensorMap<Eigen::Tensor<float, 4> > > >; StartIndices = const std::array<int, 3>; Sizes = const std::array<int, 3>; XprType = Eigen::TensorMap<Eigen::Tensor<float, 3> >]'
../tfmin_generated/terrain_model.cpp:215:92:   required from here
/home/user/tensorflow_xla/bcc-2.0.2-gcc/sparc-gaisler-elf/include/unsupported/Eigen/CXX11/src/Tensor/TensorConvolution.h:527:52: error: 'Eigen::TensorEvaluator<const Eigen::TensorSlicingOp<const std::array<int, 4>, const std::array<int, 4>, Eigen::TensorMap<Eigen::Tensor<float, 4> > >, Eigen::DefaultDevice>::Dimensions {aka const struct std::array<int, 4>}' has no member named 'TotalSize'
       size_t kernel_sz = m_kernelImpl.dimensions().TotalSize() * sizeof(Scalar);

标签: pythonc++11tensorfloweigenconvolution

解决方案


所以我最终找到了如何只使用 Eigen 张量函数调用来执行 2D 卷积,而不需要任何循环。帮助我到达这里的代码是我链接到的Tensorflow eigen_spatial_convolutions.h文件 @jdehesa。我链接到的行具有对行主要和列主要数据执行 Conv2D 操作所需的特征代码,因此您可能只需要其中的一半。

从根本上讲,您需要使用 Eigen 方法 extract_image_patches 从输入张量中提取每个滤波器实例的感知场。然后你将这个和你的内核张量的输出重塑为二维张量。这意味着每个内核是重塑后的内核张量的垂直列,并且重塑后的图像块的每一行都是每个块。然后,您执行一个收缩,它实际上是这两个 2D 张量的矩阵乘法,并将结果重新整形为正确的维度以产生输出。

起初我花了一段时间才明白这一点,但这是可以做到的。

outputTensor = inputTensor
.extract_image_patches(kern_w, kern_h, stride_w, stride_h, dilation_w, dilation_h, padding)
.reshape(Eigen::array<int, 2>({patch_count, kern_w*kern_h}))
.contract(kernalTensor.reshape(Eigen::array<int, 2>({kern_w*kern_h, kern_count})), {Eigen::IndexPair < int > (1, 0)})
.reshape(Eigen::array<int, 3>({ output_w, output_h, kern_count }));

推荐阅读