Torch 中的自定义空间卷积

Custom Spatial Convolution In Torch

我需要在 Torch 中执行自定义空间卷积。与其简单地将每个输入像素乘以该像素的权重,然后将它们与过滤器的偏差相加以形成每个输出像素,不如在将它们相加之前对输入像素执行更复杂的数学函数。

我知道怎么做,但我不知道一个好的方法。我想出的最好方法是采用完整的输入张量,创建一堆原始张量的 "views" 而不分配额外的内存,将它们放入复制层(输出过滤器计数为复制计数),并将其馈送到包含一堆常规层的 ParallelTable 层,这些常规层的参数在过滤器之间共享。

问题是,尽管这在内存方面很好,开销非常可控,但我们说的是 inputwidth^inputheight ^inputdepth^outputdepth 迷你网络,在这里。也许有一些方法可以创建大规模 "long and tall" 网络,这些网络可以同时处理整个复制的输入集,但是我如何创建部分连接(如卷积)而不是完全连接的层?

我本来想只使用继承来创建常规 SpatialConvolution "class" 的特殊副本并修改它,但我什至无法尝试,因为它是在外部 C 库中实现。我不能只在常规 SpatialConvolution 层之前使用常规层,因为我需要对每个过滤器使用不同的权重和偏差进行数学计算(在同一过滤器的应用程序之间共享到不同的输入坐标)。

好问题。你让我认真考虑一下。 您的方法有一个缺陷:它不允许利用矢量化计算,因为每个迷你网络都是独立工作的。

我的思路是这样的:

假设网络的 inputoutput 是二维张量。我们可以(高效地,无需内存复制)生成一个辅助 4D 张量 rf_input (kernel_size x kernel_size x output_h x output_w) 这样 rf_input[:, :, k, l] 是一个大小为 kernel_size x kernel_size 的二维张量,其中包含一个 output[k, l] 将从中获取的感受野。然后我们迭代内核 rf_input[i, j, :, :] 内的位置,获取所有感受野内位置 (i, j) 处的像素,并使用矢量化立即计算它们对每个 output[k, l] 的贡献。

示例:

让我们的 "convolving" 函数是,例如,和的正切的乘积:

然后是它的偏导数w.r.t。 input 位置 (s,t) 的像素在其感受野中是

导数w.r.t。 weight同理

最后,当然,我们必须对来自不同 output[k,l] 个点的梯度求和。例如,每个 input[m, n] 作为其感受野的一部分最多贡献 kernel_size^2 个输出,每个 weight[i, j] 贡献所有 output_h x output_w 个输出。

简单的实现可能是这样的:

require 'nn'
local CustomConv, parent = torch.class('nn.CustomConv', 'nn.Module')

-- This module takes and produces a 2D map. 
-- To work with multiple input/output feature maps and batches, 
-- you have to iterate over them or further vectorize computations inside the loops.

function CustomConv:__init(ker_size)
    parent.__init(self)

    self.ker_size = ker_size
    self.weight = torch.rand(self.ker_size, self.ker_size):add(-0.5)
    self.gradWeight = torch.Tensor(self.weight:size()):zero()
end

function CustomConv:_get_recfield_input(input)
    local rf_input = {}
    for i = 1, self.ker_size do
        rf_input[i] = {}
        for j = 1, self.ker_size do
            rf_input[i][j] = input[{{i, i - self.ker_size - 1}, {j, j - self.ker_size - 1}}]
        end
    end
    return rf_input
end

function CustomConv:updateOutput(_)
    local output = torch.Tensor(self.rf_input[1][1]:size())
    --  Kernel-specific: our kernel is multiplicative, so we start with ones
    output:fill(1)                                              
    --
    for i = 1, self.ker_size do
        for j = 1, self.ker_size do
            local ker_pt = self.rf_input[i][j]:clone()
            local w = self.weight[i][j]
            -- Kernel-specific
            output:cmul(ker_pt:add(w):tan())
            --
        end
    end
    return output
end

function CustomConv:updateGradInput_and_accGradParameters(_, gradOutput)
    local gradInput = torch.Tensor(self.input:size()):zero()
    for i = 1, self.ker_size do
        for j = 1, self.ker_size do
            local ker_pt = self.rf_input[i][j]:clone()
            local w = self.weight[i][j]
            -- Kernel-specific
            local subGradInput = torch.cmul(gradOutput, torch.cdiv(self.output, ker_pt:add(w):tan():cmul(ker_pt:add(w):cos():pow(2))))
            local subGradWeight = subGradInput
            --
            gradInput[{{i, i - self.ker_size - 1}, {j, j - self.ker_size - 1}}]:add(subGradInput)
            self.gradWeight[{i, j}] = self.gradWeight[{i, j}] + torch.sum(subGradWeight)
        end
    end
    return gradInput
end

function CustomConv:forward(input)
    self.input = input
    self.rf_input = self:_get_recfield_input(input)
    self.output = self:updateOutput(_)
    return self.output
end

function CustomConv:backward(input, gradOutput)
    gradInput = self:updateGradInput_and_accGradParameters(_, gradOutput)
    return gradInput
end

如果稍微更改此代码:

updateOutput:                                             
    output:fill(0)
    [...]
    output:add(ker_pt:mul(w))

updateGradInput_and_accGradParameters:
    local subGradInput = torch.mul(gradOutput, w)
    local subGradWeight = torch.cmul(gradOutput, ker_pt)

然后它将完全像 nn.SpatialConvolutionMM 一样工作,零 bias(我已经测试过了)。