好問題。你讓我給了一些認真的想法。 你的方法有一個缺陷:它不允許利用向量化計算,因爲每個迷你網絡獨立工作。
我的想法是:
假設網絡的input
和output
是2D張量。我們可以產生(有效的,不需要存儲器複製)輔助4D張量 rf_input (kernel_size x kernel_size x output_h x output_w)
使得rf_input[:, :, k, l]
是包含接受字段的二維張量,其中output[k, l]
將被獲得。然後我們遍歷內核rf_input[i, j, :, :]
中的位置,在所有接受域內獲取位置爲(i, j)
的像素,並使用向量化一次計算它們對每個output[k, l]
的貢獻。
實施例:
讓我們的 「卷積」 函數是,例如,和的切線的產物:
![enter image description here](https://i.stack.imgur.com/hRSLt.png)
然後其偏導數w.r.t.在(s,t)
位置在其感受野的input
像素是
![enter image description here](https://i.stack.imgur.com/pP8U1.png)
衍生物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
(我測試過它)。