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
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')
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
----------------------------------------------------------------------
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
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)
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
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
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,
----------------------------------------------------------------------
-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
learningRateDecay = 0
}
+ local batch = {}
+
for e = startingEpoch, params.nbEpochs do
model:training()
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
--- /dev/null
+
+-- 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