Moved a part of the code to fftb.lua.
authorFrancois Fleuret <francois@fleuret.org>
Mon, 26 Dec 2016 10:20:00 +0000 (11:20 +0100)
committerFrancois Fleuret <francois@fleuret.org>
Mon, 26 Dec 2016 10:20:00 +0000 (11:20 +0100)
dyncnn.lua
fftb.lua [new file with mode: 0644]

index 6625579..0bb780c 100755 (executable)
@@ -29,43 +29,7 @@ require 'nn'
 require 'optim'
 require 'image'
 
-require 'img'
-
-----------------------------------------------------------------------
-
-function printf(f, ...)
-   print(string.format(f, unpack({...})))
-end
-
-colors = sys.COLORS
-
-function printfc(c, f, ...)
-   print(c .. string.format(f, unpack({...})) .. colors.black)
-end
-
-function logCommand(c)
-   print(colors.blue .. '[' .. c .. '] -> [' .. sys.execute(c) .. ']' .. colors.black)
-end
-
-----------------------------------------------------------------------
--- Environment variables
-
-local defaultNbThreads = 1
-local defaultUseGPU = false
-
-if os.getenv('TORCH_NB_THREADS') then
-   defaultNbThreads = os.getenv('TORCH_NB_THREADS')
-   print('Environment variable TORCH_NB_THREADS is set and equal to ' .. defaultNbThreads)
-else
-   print('Environment variable TORCH_NB_THREADS is not set, default is ' .. defaultNbThreads)
-end
-
-if os.getenv('TORCH_USE_GPU') then
-   defaultUseGPU = os.getenv('TORCH_USE_GPU') == 'yes'
-   print('Environment variable TORCH_USE_GPU is set and evaluated as ' .. tostring(defaultUseGPU))
-else
-   print('Environment variable TORCH_USE_GPU is not set, default is ' .. tostring(defaultUseGPU))
-end
+require 'fftb'
 
 ----------------------------------------------------------------------
 -- Command line arguments
@@ -77,6 +41,7 @@ cmd:text('General setup')
 cmd:option('-seed', 1, 'initial random seed')
 cmd:option('-nbThreads', defaultNbThreads, 'how many threads (environment variable TORCH_NB_THREADS)')
 cmd:option('-useGPU', defaultUseGPU, 'should we use cuda (environment variable TORCH_USE_GPU)')
+cmd:option('-fastGPU', true, 'should we go as fast as possible, possibly non-deterministically')
 
 cmd:text('')
 cmd:text('Log')
@@ -109,68 +74,21 @@ cmd:text('Problem to solve')
 
 cmd:option('-dataDir', './data/10p-mg', 'data directory')
 
-------------------------------
--- Log and stuff
-
 cmd:addTime('DYNCNN','%F %T')
 
 params = cmd:parse(arg)
 
-if params.rundir == '' then
-   params.rundir = cmd:string('exp', params, { })
-end
-
-paths.mkdir(params.rundir)
-
-if not params.noLog then
-   -- Append to the log if there is one
-   cmd:log(io.open(params.rundir .. '/log', 'a'), params)
-end
-
-----------------------------------------------------------------------
--- The experiment per se
-
-if params.predictGrasp then
-   params.targetDepth = 2
-else
-   params.targetDepth = 1
-end
-
 ----------------------------------------------------------------------
--- Initializations
-
-torch.setnumthreads(params.nbThreads)
-torch.setdefaulttensortype('torch.FloatTensor')
-torch.manualSeed(params.seed)
-
-----------------------------------------------------------------------
--- Dealing with the CPU/GPU
-
--- mynn will take entries in that order: mynn, cudnn, cunn, nn
-
-mynn = {}
 
-setmetatable(mynn,
-             {
-                __index = function(table, key)
-                   return (cudnn and cudnn[key]) or (cunn and cunn[key]) or nn[key]
-                end
-             }
-)
+fftbInit(cmd, params)
 
--- These are the tensors that can be kept on the CPU
-mynn.SlowTensor = torch.Tensor
-
--- These are the tensors that should be moved to the GPU
-mynn.FastTensor = torch.Tensor
-
-if params.useGPU then
-   require 'cutorch'
-   require 'cunn'
-   require 'cudnn'
-   cudnn.benchmark = true
-   cudnn.fastest = true
-   mynn.FastTensor = torch.CudaTensor
+for _, c in pairs({
+      'date',
+      'uname -a',
+      'git log -1 --format=%H'
+                 })
+do
+   logCommand(c)
 end
 
 ----------------------------------------------------------------------
@@ -185,8 +103,8 @@ function loadData(first, nb, name)
    data.width = 64
    data.height = 64
 
-   data.input = mynn.SlowTensor(data.nbSamples, 2, data.height, data.width)
-   data.target = mynn.SlowTensor(data.nbSamples, 1, data.height, data.width)
+   data.input = ffnn.SlowTensor(data.nbSamples, 2, data.height, data.width)
+   data.target = ffnn.SlowTensor(data.nbSamples, 1, data.height, data.width)
 
    for i = 1, data.nbSamples do
       local n = i-1 + first-1
@@ -226,8 +144,8 @@ function collectAllOutputs(model, collection, which)
 end
 
 function saveInternalsImage(model, data, n)
-   -- Explicitely copy to keep input as a mynn.FastTensor
-   local input = mynn.FastTensor(1, 2, data.height, data.width)
+   -- Explicitely copy to keep input as a ffnn.FastTensor
+   local input = ffnn.FastTensor(1, 2, data.height, data.width)
    input:copy(data.input:narrow(1, n, 1))
 
    local output = model:forward(input)
@@ -278,8 +196,8 @@ function saveResultImage(model, data, nbMax)
       criterion:cuda()
    end
 
-   local input = mynn.FastTensor(1, 2, data.height, data.width)
-   local target = mynn.FastTensor(1, 1, data.height, data.width)
+   local input = ffnn.FastTensor(1, 2, data.height, data.width)
+   local target = ffnn.FastTensor(1, 1, data.height, data.width)
 
    local nbMax = nbMax or 50
 
@@ -293,14 +211,14 @@ function saveResultImage(model, data, nbMax)
 
    for n = 1, nb do
 
-      -- Explicitely copy to keep input as a mynn.FastTensor
+      -- Explicitely copy to keep input as a ffnn.FastTensor
       input:copy(data.input:narrow(1, n, 1))
       target:copy(data.target:narrow(1, n, 1))
 
       local output = model:forward(input)
       local loss = criterion:forward(output, target)
 
-      output = mynn.SlowTensor(output:size()):copy(output)
+      output = ffnn.SlowTensor(output:size()):copy(output)
 
       -- We use our magical img.lua to create the result images
 
@@ -338,61 +256,60 @@ function createTower(filterSize, nbChannels, nbBlocks)
 
    else
 
-      tower = mynn.Sequential()
+      tower = ffnn.Sequential()
 
       for b = 1, nbBlocks do
-         local block = mynn.Sequential()
+         local block = ffnn.Sequential()
 
-         block:add(mynn.SpatialConvolution(nbChannels,
+         block:add(ffnn.SpatialConvolution(nbChannels,
                                            nbChannels,
                                            filterSize, filterSize,
                                            1, 1,
                                            (filterSize - 1) / 2, (filterSize - 1) / 2))
-         block:add(mynn.SpatialBatchNormalization(nbChannels))
-         block:add(mynn.ReLU(true))
+         block:add(ffnn.SpatialBatchNormalization(nbChannels))
+         block:add(ffnn.ReLU(true))
 
-         block:add(mynn.SpatialConvolution(nbChannels,
+         block:add(ffnn.SpatialConvolution(nbChannels,
                                            nbChannels,
                                            filterSize, filterSize,
                                            1, 1,
                                            (filterSize - 1) / 2, (filterSize - 1) / 2))
 
-         local parallel = mynn.ConcatTable()
-         parallel:add(block):add(mynn.Identity())
+         local parallel = ffnn.ConcatTable()
+         parallel:add(block):add(ffnn.Identity())
 
-         tower:add(parallel):add(mynn.CAddTable(true))
+         tower:add(parallel):add(ffnn.CAddTable(true))
 
-         tower:add(mynn.SpatialBatchNormalization(nbChannels))
-         tower:add(mynn.ReLU(true))
+         tower:add(ffnn.SpatialBatchNormalization(nbChannels))
+         tower:add(ffnn.ReLU(true))
       end
 
    end
 
    return tower
-
 end
 
 function createModel(imageWidth, imageHeight,
                      filterSize, nbChannels, nbBlocks)
 
-   local model = mynn.Sequential()
+   local model = ffnn.Sequential()
 
    -- Encode the two input channels (grasping image and starting
    -- configuration) into the internal number of channels
-   model:add(mynn.SpatialConvolution(2,
+   model:add(ffnn.SpatialConvolution(2,
                                      nbChannels,
                                      filterSize, filterSize,
                                      1, 1,
                                      (filterSize - 1) / 2, (filterSize - 1) / 2))
 
-   model:add(mynn.SpatialBatchNormalization(nbChannels))
-   model:add(mynn.ReLU(true))
+   model:add(ffnn.SpatialBatchNormalization(nbChannels))
+   model:add(ffnn.ReLU(true))
 
    -- Add the resnet modules
    model:add(createTower(filterSize, nbChannels, nbBlocks))
 
    -- Decode down to a single channel, which is the final image
-   model:add(mynn.SpatialConvolution(nbChannels,
+   model:add(ffnn.SpatialConvolution(nbChannels,
                                      1,
                                      filterSize, filterSize,
                                      1, 1,
@@ -403,42 +320,11 @@ end
 
 ----------------------------------------------------------------------
 
-function fillBatch(data, first, batch, permutation)
-   local actualBatchSize = math.min(params.batchSize, data.input:size(1) - first + 1)
-
-   if actualBatchSize ~= batch.input:size(1) then
-      local size = batch.input:size()
-      size[1] = actualBatchSize
-      batch.input:resize(size)
-   end
-
-   if actualBatchSize ~= batch.target:size(1) then
-      local size = batch.target:size()
-      size[1] = actualBatchSize
-      batch.target:resize(size)
-   end
-
-   for k = 1, batch.input:size(1) do
-      local i
-      if permutation then
-         i = permutation[first + k - 1]
-      else
-         i = first + k - 1
-      end
-      batch.input[k] = data.input[i]
-      batch.target[k] = data.target[i]
-   end
-end
-
 function trainModel(model, trainSet, validationSet)
 
    local criterion = nn.MSECriterion()
    local batchSize = params.batchSize
 
-   local batch = {}
-   batch.input = mynn.FastTensor(batchSize, 2, trainSet.height, trainSet.width)
-   batch.target = mynn.FastTensor(batchSize, 1, trainSet.height, trainSet.width)
-
    local startingEpoch = 1
 
    if model.epoch then
@@ -472,6 +358,8 @@ function trainModel(model, trainSet, validationSet)
       learningRateDecay = 0
    }
 
+   local batch = {}
+
    for e = startingEpoch, params.nbEpochs do
 
       model:training()
@@ -574,57 +462,39 @@ function trainModel(model, trainSet, validationSet)
 
 end
 
-function createAndTrainModel(trainSet, validationSet)
-
-   -- Load the current training state, or create a new model from
-   -- scratch
+----------------------------------------------------------------------
+-- main
 
-   if pcall(function () model = torch.load(params.rundir .. '/model_last.t7') end) then
+local trainSet = loadData(1,
+                          params.nbTrainSamples, 'train')
 
-      printfc(colors.red,
-              'Found a model with %d epochs completed, starting from there.',
-              model.epoch)
+local validationSet = loadData(params.nbTrainSamples + 1,
+                               params.nbValidationSamples, 'validation')
 
-      if params.exampleInternals ~= '' then
-         for _, i in ipairs(string.split(params.exampleInternals, ',')) do
-            saveInternalsImage(model, validationSet, tonumber(i))
-         end
-         os.exit(0)
-      end
+local model
 
-   else
+if pcall(function () model = torch.load(params.rundir .. '/model_last.t7') end) then
 
-      model = createModel(trainSet.width, trainSet.height,
-                          params.filterSize, params.nbChannels,
-                          params.nbBlocks)
+   printfc(colors.red,
+           'Found a model with %d epochs completed, starting from there.',
+           model.epoch)
 
+   if params.exampleInternals ~= '' then
+      for _, i in ipairs(string.split(params.exampleInternals, ',')) do
+         saveInternalsImage(model, validationSet, tonumber(i))
+      end
+      os.exit(0)
    end
 
-   trainModel(model, trainSet, validationSet)
-
-   return model
-
-end
+else
 
-----------------------------------------------------------------------
--- main
+   model = createModel(trainSet.width, trainSet.height,
+                       params.filterSize, params.nbChannels,
+                       params.nbBlocks)
 
-for _, c in pairs({
-      'date',
-      'uname -a',
-      'git log -1 --format=%H'
-                 })
-do
-   logCommand(c)
 end
 
-local trainSet = loadData(1,
-                          params.nbTrainSamples, 'train')
-
-local validationSet = loadData(params.nbTrainSamples + 1,
-                               params.nbValidationSamples, 'validation')
-
-local model = createAndTrainModel(trainSet, validationSet)
+trainModel(model, trainSet, validationSet)
 
 ----------------------------------------------------------------------
 -- Test
diff --git a/fftb.lua b/fftb.lua
new file mode 100644 (file)
index 0000000..86a05a8
--- /dev/null
+++ b/fftb.lua
@@ -0,0 +1,334 @@
+
+-- Francois Fleuret's Torch Toolbox
+
+require 'torch'
+require 'nn'
+
+----------------------------------------------------------------------
+
+colors = sys.COLORS
+
+function printf(f, ...)
+   print(string.format(f, unpack({...})))
+end
+
+function printfc(c, f, ...)
+   printf(c .. string.format(f, unpack({...})) .. colors.black)
+end
+
+function logCommand(c)
+   print(colors.blue .. '[' .. c .. '] -> [' .. sys.execute(c) .. ']' .. colors.black)
+end
+
+----------------------------------------------------------------------
+-- Environment variables
+
+defaultNbThreads = 1
+defaultUseGPU = false
+
+if os.getenv('TORCH_NB_THREADS') then
+   defaultNbThreads = os.getenv('TORCH_NB_THREADS')
+   print('Environment variable TORCH_NB_THREADS is set and equal to ' .. defaultNbThreads)
+else
+   print('Environment variable TORCH_NB_THREADS is not set, default is ' .. defaultNbThreads)
+end
+
+if os.getenv('TORCH_USE_GPU') then
+   defaultUseGPU = os.getenv('TORCH_USE_GPU') == 'yes'
+   print('Environment variable TORCH_USE_GPU is set and evaluated as ' .. tostring(defaultUseGPU))
+else
+   print('Environment variable TORCH_USE_GPU is not set, default is ' .. tostring(defaultUseGPU))
+end
+
+----------------------------------------------------------------------
+
+function fftbInit(cmd, params)
+
+   torch.setnumthreads(params.nbThreads)
+   torch.setdefaulttensortype('torch.FloatTensor')
+   torch.manualSeed(params.seed)
+
+   -- Logging
+
+   if params.rundir == '' then
+      params.rundir = cmd:string('experiment', params, { })
+   end
+
+   paths.mkdir(params.rundir)
+
+   if not params.noLog then
+      -- Append to the log if there is one
+      cmd:log(io.open(params.rundir .. '/log', 'a'), params)
+   end
+
+   -- Dealing with the CPU/GPU
+
+   ffnn = {}
+
+   -- By default, ffnn returns the entries from nn
+   local mt = {}
+   function mt.__index(table, key)
+      return (cudnn and cudnn[key]) or (cunn and cunn[key]) or nn[key]
+   end
+   setmetatable(ffnn, mt)
+
+   -- These are the tensors that can be kept on the CPU
+   ffnn.SlowTensor = torch.Tensor
+   ffnn.SlowStorage = torch.Storage
+   -- These are the tensors that should be moved to the GPU
+   ffnn.FastTensor = torch.Tensor
+   ffnn.FastStorage = torch.Storage
+
+   if params.useGPU then
+      require 'cutorch'
+      require 'cunn'
+      require 'cudnn'
+
+      if params.fastGPU then
+         cudnn.benchmark = true
+         cudnn.fastest = true
+      end
+
+      ffnn.FastTensor = torch.CudaTensor
+      ffnn.FastStorage = torch.CudaStorage
+   end
+end
+
+----------------------------------------------------------------------
+
+function dimAtThatPoint(model, input)
+   if params.useGPU then
+      model:cuda()
+   end
+   local i = ffnn.FastTensor(input:narrow(1, 1, 1):size()):copy(input:narrow(1, 1, 1))
+   return model:forward(i):nElement()
+end
+
+----------------------------------------------------------------------
+
+function sizeForBatch(n, x)
+   local size = x:size()
+   size[1] = n
+   return size
+end
+
+function fillBatch(data, first, batch, permutation)
+   local actualBatchSize = math.min(params.batchSize, data.input:size(1) - first + 1)
+
+   if batch.input then
+      if actualBatchSize ~= batch.input:size(1) then
+         batch.input:resize(sizeForBatch(actualBatchSize, batch.input))
+      end
+   else
+      if torch.isTypeOf(data.input, ffnn.SlowTensor) then
+         batch.input = ffnn.FastTensor(sizeForBatch(actualBatchSize, data.input));
+      else
+         batch.input = data.input.new():resize(sizeForBatch(actualBatchSize, data.input));
+      end
+   end
+
+   if batch.target then
+      if actualBatchSize ~= batch.target:size(1) then
+         batch.target:resize(sizeForBatch(actualBatchSize, batch.target))
+      end
+   else
+      if torch.isTypeOf(data.target, ffnn.SlowTensor) then
+         batch.target = ffnn.FastTensor(sizeForBatch(actualBatchSize, data.target));
+      else
+         batch.target = data.target.new():resize(sizeForBatch(actualBatchSize, data.target));
+      end
+   end
+
+   for k = 1, actualBatchSize do
+      local i
+      if permutation then
+         i = permutation[first + k - 1]
+      else
+         i = first + k - 1
+      end
+      batch.input[k] = data.input[i]
+      batch.target[k] = data.target[i]
+   end
+end
+
+----------------------------------------------------------------------
+
+--[[
+
+The combineImage function takes as input a parameter c which is the
+value to use for the background of the resulting image (padding and
+such), and t which is either a 2d tensor, a 3d tensor, or a table.
+
+ * If t is a 3d tensor, it is returned unchanged.
+
+ * If t is a 2d tensor [r x c], it is reshaped to [1 x r x c] and
+   returned.
+
+ * If t is a table, combineImage first calls itself recursively on
+   t[1], t[2], etc.
+
+   It then creates a new tensor by concatenating the results
+   horizontally if t.vertical is nil, vertically otherwise.
+
+   It adds a padding of t.pad pixels if this field is set.
+
+ * Example
+
+   x = torch.Tensor(64, 64):fill(0.5)
+   y = torch.Tensor(100, 30):fill(0.85)
+
+   i = combineImages(1.0,
+      {
+         pad = 1,
+         vertical = true,
+         { pad = 1, x },
+         {
+            y,
+            { pad = 4, torch.Tensor(32, 16):fill(0.25) },
+            { pad = 1, torch.Tensor(45, 54):uniform(0.25, 0.9) },
+         }
+      }
+   )
+
+   image.save('example.png', i)
+
+]]--
+
+function combineImages(c, t)
+
+   if torch.isTensor(t) then
+
+      if t:dim() == 3 then
+         return t
+      elseif t:dim() == 2 then
+         return torch.Tensor(1, t:size(1), t:size(2)):copy(t)
+      else
+         error('can only deal with [height x width] or [channel x height x width] tensors.')
+      end
+
+   else
+
+      local subImages = {} -- The subimages
+      local nc = 0 -- Nb of columns
+      local nr = 0 -- Nb of rows
+
+      for i, x in ipairs(t) do
+         subImages[i] = combineImages(c, x)
+         if t.vertical then
+            nr = nr + subImages[i]:size(2)
+            nc = math.max(nc, subImages[i]:size(3))
+         else
+            nr = math.max(nr, subImages[i]:size(2))
+            nc = nc + subImages[i]:size(3)
+         end
+      end
+
+      local pad = t.pad or 0
+      local result = torch.Tensor(subImages[1]:size(1), nr + 2 * pad, nc + 2 * pad):fill(c)
+      local co = 1 + pad -- Origin column
+      local ro = 1 + pad -- Origin row
+
+      for i in ipairs(t) do
+
+         result
+            :sub(1, subImages[1]:size(1),
+                 ro, ro + subImages[i]:size(2) - 1,
+                 co, co + subImages[i]:size(3) - 1)
+            :copy(subImages[i])
+
+         if t.vertical then
+            ro = ro + subImages[i]:size(2)
+         else
+            co = co + subImages[i]:size(3)
+         end
+
+      end
+
+      return result
+
+   end
+
+end
+
+--[[
+
+The imageFromTensors function gets as input a list of tensors of
+arbitrary dimensions each, but whose two last dimensions stand for
+height x width. It creates an image tensor (2d, one channel) with each
+argument tensor unfolded per row.
+
+]]--
+
+function imageFromTensors(bt, signed)
+   local gap = 1
+   local tgap = -1
+   local width = 0
+   local height = gap
+
+   for _, t in pairs(bt) do
+      local d = t:dim()
+      local h, w = t:size(d - 1), t:size(d)
+      local n = t:nElement() / (w * h)
+      width = math.max(width, gap + n * (gap + w))
+      height = height + gap + tgap + gap + h
+   end
+
+   local e = torch.Tensor(3, height, width):fill(1.0)
+   local y0 = 1 + gap
+
+   for _, t in pairs(bt) do
+      local d = t:dim()
+      local h, w = t:size(d - 1), t:size(d)
+      local n = t:nElement() / (w * h)
+      local z = t:norm() / math.sqrt(t:nElement())
+
+      local x0 = 1 + gap + math.floor( (width - n * (w + gap)) /2 )
+      local u = torch.Tensor(t:size()):copy(t):resize(n, h, w)
+      for m = 1, n do
+
+         for c = 1, 3 do
+            for y = 0, h+1 do
+               e[c][y0 + y - 1][x0     - 1] = 0.0
+               e[c][y0 + y - 1][x0 + w    ] = 0.0
+            end
+            for x = 0, w+1 do
+               e[c][y0     - 1][x0 + x - 1] = 0.0
+               e[c][y0 + h    ][x0 + x - 1] = 0.0
+            end
+         end
+
+         for y = 1, h do
+            for x = 1, w do
+               local v = u[m][y][x] / z
+               local r, g, b
+               if signed then
+                  if v < -1 then
+                     r, g, b = 0.0, 0.0, 1.0
+                  elseif v > 1 then
+                     r, g, b = 1.0, 0.0, 0.0
+                  elseif v >= 0 then
+                     r, g, b = 1.0, 1.0 - v, 1.0 - v
+                  else
+                     r, g, b = 1.0 + v, 1.0 + v, 1.0
+                  end
+               else
+                  if v <= 0 then
+                     r, g, b = 1.0, 1.0, 1.0
+                  elseif v > 1 then
+                     r, g, b = 0.0, 0.0, 0.0
+                  else
+                     r, g, b = 1.0 - v, 1.0 - v, 1.0 - v
+                  end
+               end
+               e[1][y0 + y - 1][x0 + x - 1] = r
+               e[2][y0 + y - 1][x0 + x - 1] = g
+               e[3][y0 + y - 1][x0 + x - 1] = b
+            end
+         end
+         x0 = x0 + w + gap
+      end
+      y0 = y0 + h + gap + tgap + gap
+   end
+
+   return e
+end