Skip to main content
 首页 » 编程设计

python之使用特征张量复制 TensorFlows Conv2D 操作

2024年11月24日26cloudgamer

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

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

  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 个过滤器 channel 并将卷积方法分别应用于每个 channel ,但是我遇到了编译器错误,我无法理解下面的代码:

  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 张量代码中的某处产生以下错误,它可能与切片方法的结果分配有关,但我不确定。如果将结果分配给 auto 类型,则它会编译,但如果稍后对结果求值则不会。

如果有人知道如何解决此错误或更笼统地说我如何使用 Eigen 张量复制 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); 

请您参考如下方法:

因此,我最终找到了如何仅使用 Eigen 张量函数调用来执行二维卷积,而无需任何循环。帮助我到达此处的代码是 Tensorflow eigen_spatial_convolutions.h文件@jdehesa 将我链接到。我链接到的行具有对行优先和列优先数据执行 Conv2D 操作所需的 Eigen 代码,因此您可能只需要其中的一半。

从根本上说,您需要使用 Eigen 方法 extract_image_patches 从输入张量中提取每个滤波器实例的感知场。然后你将这个和你的内核张量的输出 reshape 为二维张量。这意味着每个核都是 reshape 核张量的垂直列, reshape 图像 block 的每一行都是每个 block 。然后执行收缩,这实际上是这两个 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 }));