From: Francois Fleuret Date: Mon, 26 Dec 2016 10:20:00 +0000 (+0100) Subject: Moved a part of the code to fftb.lua. X-Git-Url: https://fleuret.org/cgi-bin/gitweb/gitweb.cgi?a=commitdiff_plain;h=8cce872485111eaa79ce60041715227a8ff4d45f;p=dyncnn.git Moved a part of the code to fftb.lua. --- diff --git a/dyncnn.lua b/dyncnn.lua index 6625579..0bb780c 100755 --- a/dyncnn.lua +++ b/dyncnn.lua @@ -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 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