好问题。你让我认真思考。您的方法有一个缺陷:它不允许利用矢量化计算,因为每个迷你网络都是独立工作的。
我的想法如下:
假设网络input
和output
是二维张量。我们可以(有效地,无需内存复制)生成一个辅助 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
(我已经测试过了)。