2015-09-01 26 views
2

我需要在火炬中執行自定義空間卷積。我不需要簡單地將每個輸入像素乘以該像素的權重,並將它們與濾波器的偏置一起添加以形成每個輸出像素,而是需要在將輸入像素添加到一起之前對輸入像素執行更復雜的數學函數。火炬中的自定義空間卷積

我知道如何做到這一點,但我不知道一個好辦法做到這一點。我提出的最好的方法是獲取完整的輸入張量,創建一堆次級張量,它們是原始視圖的「輔助張量」,而不分配額外的內存,將它們放入複製層(輸出過濾器計數爲複製計數),並將其饋送到包含一組常規圖層的ParallelTable圖層中,這些圖層的參數在過濾器之間共享。

麻煩的是,即使這是好的內存明智的一個非常易於管理的開銷,我們談論inputwidth^inputheight^inputdepth^outputdepth小型網絡,在這裏。也許有一種方法可以創建大量「長而高」的網絡,可以一次處理整個複製輸入集,但是如何創建部分連接(如卷積)而不是完全連接的圖層?

我本來只是使用繼承來創建定期SpatialConvolution「類」的特殊副本,並修改它,但我不能去嘗試,因爲它在外部C庫實現的。我不能只在常規SpatialConvolution層之前使用常規圖層,因爲我需要爲每個過濾器(同一個過濾器的應用程序與不同輸入座標之間共享)的不同權重和偏差進行數學運算。

回答

4

好問題。你讓我給了一些認真的想法。 你的方法有一個缺陷:它不允許利用向量化計算,因爲每個迷你網絡獨立工作。

我的想法是:

假設網絡的inputoutput是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

然後其偏導數w.r.t.在(s,t)位置在其感受野的input像素是

enter image description here

衍生物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.SpatialConvolutionMMbias(我測試過它)。