好问题。你让我认真思考。
您的方法有一个缺陷:它不允许利用矢量化计算,因为每个迷你网络都是独立工作的。
我的想法如下:
假设网络的input 和output 是二维张量。我们可以(高效,无需内存复制)生成一个辅助 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] 的贡献。
示例:
例如,让我们的“卷积”函数是和的切线的乘积:
然后它的偏导数 w.r.t.其感受野中(s,t) 位置处的input 像素是
衍生 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 为零(我已经测试过了)。