2

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

我知道如何做到这一点,但我不知道这样做的好方法。我想出的最好方法是获取完整的输入张量,创建一堆次级张量,它们是原始的“视图”而不分配额外的内存,将它们放入复制层(输出过滤器计数是复制count),并将其馈送到一个 ParallelTable 层中,该层包含一堆常规层,这些层的参数在过滤器之间共享。

问题是,尽管这在内存方面很好,而且开销非常可管理,但我们在这里讨论的是inputwidth ^ inputheight ^ inputdepth ^ outputdepth mini-networks。也许有一些方法可以创建大规模的“长而高”的网络,可以同时处理整个复制的输入集,但是我如何创建部分连接(如卷积)而不是完全连接的层?

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

4

1 回答 1

4

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

我的想法如下:

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

例子:

例如,让我们的“卷积”函数是和的切线的乘积:

在此处输入图像描述

那么它相对于其感受野input中位置的像素的偏导数是(s,t)

在此处输入图像描述

导数 wrtweight是一样的。

最后,当然,我们必须总结来自不同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(我已经测试过了)。

于 2015-09-02T15:39:43.890 回答