Torch 中的自定义空间卷积
Custom Spatial Convolution In Torch
我需要在 Torch 中执行自定义空间卷积。与其简单地将每个输入像素乘以该像素的权重,然后将它们与过滤器的偏差相加以形成每个输出像素,不如在将它们相加之前对输入像素执行更复杂的数学函数。
我知道怎么做,但我不知道一个好的方法。我想出的最好方法是采用完整的输入张量,创建一堆原始张量的 "views" 而不分配额外的内存,将它们放入复制层(输出过滤器计数为复制计数),并将其馈送到包含一堆常规层的 ParallelTable 层,这些常规层的参数在过滤器之间共享。
问题是,尽管这在内存方面很好,开销非常可控,但我们说的是 inputwidth^inputheight ^inputdepth^outputdepth 迷你网络,在这里。也许有一些方法可以创建大规模 "long and tall" 网络,这些网络可以同时处理整个复制的输入集,但是我如何创建部分连接(如卷积)而不是完全连接的层?
我本来想只使用继承来创建常规 SpatialConvolution "class" 的特殊副本并修改它,但我什至无法尝试,因为它是在外部 C 库中实现。我不能只在常规 SpatialConvolution 层之前使用常规层,因为我需要对每个过滤器使用不同的权重和偏差进行数学计算(在同一过滤器的应用程序之间共享到不同的输入坐标)。
好问题。你让我认真考虑一下。
您的方法有一个缺陷:它不允许利用矢量化计算,因为每个迷你网络都是独立工作的。
我的思路是这样的:
假设网络的 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]
的贡献。
示例:
让我们的 "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
(我已经测试过了)。
我需要在 Torch 中执行自定义空间卷积。与其简单地将每个输入像素乘以该像素的权重,然后将它们与过滤器的偏差相加以形成每个输出像素,不如在将它们相加之前对输入像素执行更复杂的数学函数。
我知道怎么做,但我不知道一个好的方法。我想出的最好方法是采用完整的输入张量,创建一堆原始张量的 "views" 而不分配额外的内存,将它们放入复制层(输出过滤器计数为复制计数),并将其馈送到包含一堆常规层的 ParallelTable 层,这些常规层的参数在过滤器之间共享。
问题是,尽管这在内存方面很好,开销非常可控,但我们说的是 inputwidth^inputheight ^inputdepth^outputdepth 迷你网络,在这里。也许有一些方法可以创建大规模 "long and tall" 网络,这些网络可以同时处理整个复制的输入集,但是我如何创建部分连接(如卷积)而不是完全连接的层?
我本来想只使用继承来创建常规 SpatialConvolution "class" 的特殊副本并修改它,但我什至无法尝试,因为它是在外部 C 库中实现。我不能只在常规 SpatialConvolution 层之前使用常规层,因为我需要对每个过滤器使用不同的权重和偏差进行数学计算(在同一过滤器的应用程序之间共享到不同的输入坐标)。
好问题。你让我认真考虑一下。 您的方法有一个缺陷:它不允许利用矢量化计算,因为每个迷你网络都是独立工作的。
我的思路是这样的:
假设网络的 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]
的贡献。
示例:
让我们的 "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
(我已经测试过了)。