Prerequisites

Install Theano and Lasagne using the following commands:

pip install -r https://raw.githubusercontent.com/Lasagne/Lasagne/master/requirements.txt
pip install https://github.com/Lasagne/Lasagne/archive/master.zip

Working in a virtual environment is recommended.

Data preparation

Current code allows to generate geodesic patches from a collection of shapes represented as triangular meshes. To get started with the pre-processing:

git clone https://github.com/jonathanmasci/ShapeNet_data_preparation_toolbox.git

The usual processing pipeline is show in run_forrest_run.m. We will soon update this preparation stage, so perhaps better to start with our pre-computed dataset, and stay tuned! :-)

Prepared data

All it is required to train on the FAUST_registration dataset for this demo is available for download at https://www.dropbox.com/s/aamd98nynkvbcop/EG16_tutorial.tar.bz2?dl=0

ICNN Toolbox

git clone https://github.com/jonathanmasci/EG16_tutorial.git


In [1]:
import sys
import os
import numpy as np
import scipy.io
import time

import theano
import theano.tensor as T
import theano.sparse as Tsp

import lasagne as L
import lasagne.layers as LL
import lasagne.objectives as LO
from lasagne.layers.normalization import batch_norm

sys.path.append('..')
from icnn import aniso_utils_lasagne, dataset, snapshotter


/usr/local/lib/python2.7/dist-packages/nose_parameterized/__init__.py:7: UserWarning: The 'nose-parameterized' package has been renamed 'parameterized'. For the two step migration instructions, see: https://github.com/wolever/parameterized#migrating-from-nose-parameterized-to-parameterized (set NOSE_PARAMETERIZED_NO_WARN=1 to suppress this warning)
  "The 'nose-parameterized' package has been renamed 'parameterized'. "

Data loading


In [2]:
base_path = '/home/shubham/Desktop/IndependentStudy/EG16_tutorial/dataset/FAUST_registrations/data/diam=200/'

# train_txt, test_txt, descs_path, patches_path, geods_path, labels_path, ...
        # desc_field='desc', patch_field='M', geod_field='geods', label_field='labels', epoch_size=100
ds = dataset.ClassificationDatasetPatchesMinimal(
    'FAUST_registrations_train.txt', 'FAUST_registrations_test.txt',
    os.path.join(base_path, 'descs', 'shot'),
    os.path.join(base_path, 'patch_aniso', 'alpha=100_nangles=016_ntvals=005_tmin=6.000_tmax=24.000_thresh=99.900_norm=L1'), 
    None, 
    os.path.join(base_path, 'labels'),
    epoch_size=50)


Loading train descs
elapsed time 1.061450
Loading test descs
elapsed time 1.797301
Loading train patches
elapsed time 3.096992
Loading test patches
elapsed time 5.607564
Loading train labels
elapsed time 0.006568
Loading test labels
elapsed time 0.010800

In [3]:
# inp = LL.InputLayer(shape=(None, 544))
# print(inp.input_var)
# patch_op = LL.InputLayer(input_var=Tsp.csc_fmatrix('patch_op'), shape=(None, None))
# print(patch_op.shape)
# print(patch_op.input_var)
# icnn = LL.DenseLayer(inp, 16)
# print(icnn.output_shape)
# print(icnn.output_shape)
# desc_net = theano.dot(patch_op, icnn)

Network definition


In [3]:
nin = 544
nclasses = 6890
l2_weight = 1e-5

def get_model(inp, patch_op):
    icnn = LL.DenseLayer(inp, 16)
    icnn = batch_norm(aniso_utils_lasagne.ACNNLayer([icnn, patch_op], 16, nscale=5, nangl=16))
    icnn = batch_norm(aniso_utils_lasagne.ACNNLayer([icnn, patch_op], 32, nscale=5, nangl=16))
    icnn = batch_norm(aniso_utils_lasagne.ACNNLayer([icnn, patch_op], 64, nscale=5, nangl=16))
    ffn = batch_norm(LL.DenseLayer(icnn, 512))
    ffn = LL.DenseLayer(icnn, nclasses, nonlinearity=aniso_utils_lasagne.log_softmax)

    return ffn

inp = LL.InputLayer(shape=(None, nin))
patch_op = LL.InputLayer(input_var=Tsp.csc_fmatrix('patch_op'), shape=(None, None))

ffn = get_model(inp, patch_op)

# L.layers.get_output -> theano variable representing network
output = LL.get_output(ffn)
pred = LL.get_output(ffn, deterministic=True)  # in case we use dropout

# target theano variable indicatind the index a vertex should be mapped to wrt the latent space
target = T.ivector('idxs')

# to work with logit predictions, better behaved numerically
cla = aniso_utils_lasagne.categorical_crossentropy_logdomain(output, target, nclasses).mean()
acc = LO.categorical_accuracy(pred, target).mean()

# a bit of regularization is commonly used
regL2 = L.regularization.regularize_network_params(ffn, L.regularization.l2)


cost = cla + l2_weight * regL2


SparseVariable{csc,float32}
Elemwise{mul,no_inplace}.0
StructuredDot.0
Reshape{4}.0
Subtensor{int64}.0
SparseVariable{csc,float32}
Elemwise{mul,no_inplace}.0
StructuredDot.0
Reshape{4}.0
Subtensor{int64}.0
SparseVariable{csc,float32}
Elemwise{mul,no_inplace}.0
StructuredDot.0
Reshape{4}.0
Subtensor{int64}.0
SparseVariable{csc,float32}
Elemwise{mul,no_inplace}.0
StructuredDot.0
Reshape{4}.0
Subtensor{int64}.0
SparseVariable{csc,float32}
Elemwise{mul,no_inplace}.0
StructuredDot.0
Reshape{4}.0
Subtensor{int64}.0
SparseVariable{csc,float32}
Elemwise{mul,no_inplace}.0
StructuredDot.0
Reshape{4}.0
Subtensor{int64}.0

Define the update rule, how to train


In [4]:
params = LL.get_all_params(ffn, trainable=True)
grads = T.grad(cost, params)
# computes the L2 norm of the gradient to better inspect training
grads_norm = T.nlinalg.norm(T.concatenate([g.flatten() for g in grads]), 2)

# Adam turned out to be a very good choice for correspondence
updates = L.updates.adam(grads, params, learning_rate=0.001)

Compile


In [5]:
funcs = dict()
funcs['train'] = theano.function([inp.input_var, patch_op.input_var, target],
                                 [cost, cla, l2_weight * regL2, grads_norm, acc], updates=updates,
                                 on_unused_input='warn')
funcs['acc_loss'] = theano.function([inp.input_var, patch_op.input_var, target],
                                    [acc, cost], on_unused_input='warn')
funcs['predict'] = theano.function([inp.input_var, patch_op.input_var],
                                   [pred], on_unused_input='warn')


WARNING (theano.tensor.blas): We did not found a dynamic library into the library_dir of the library we use for blas. If you use ATLAS, make sure to compile it with dynamics library.

Training (a bit simplified)


In [6]:
n_epochs = 50
eval_freq = 1

start_time = time.time()
best_trn = 1e5
best_tst = 1e5

kvs = snapshotter.Snapshotter('demo_training.snap')

for it_count in xrange(n_epochs):
    tic = time.time()
    b_l, b_c, b_s, b_r, b_g, b_a = [], [], [], [], [], []
    for x_ in ds.train_iter():
        tmp = funcs['train'](*x_)

        # do some book keeping (store stuff for training curves etc)
        b_l.append(tmp[0])
        b_c.append(tmp[1])
        b_r.append(tmp[2])
        b_g.append(tmp[3])
        b_a.append(tmp[4])
    epoch_cost = np.asarray([np.mean(b_l), np.mean(b_c), np.mean(b_r), np.mean(b_g), np.mean(b_a)])
    print(('[Epoch %03i][trn] cost %9.6f (cla %6.4f, reg %6.4f), |grad| = %.06f, acc = %7.5f %% (%.2fsec)') %
                 (it_count, epoch_cost[0], epoch_cost[1], epoch_cost[2], epoch_cost[3], epoch_cost[4] * 100, 
                  time.time() - tic))

    if np.isnan(epoch_cost[0]):
        print("NaN in the loss function...let's stop here")
        break

    if (it_count % eval_freq) == 0:
        v_c, v_a = [], []
        for x_ in ds.test_iter():
            tmp = funcs['acc_loss'](*x_)
            v_a.append(tmp[0])
            v_c.append(tmp[1])
        test_cost = [np.mean(v_c), np.mean(v_a)]
        print(('           [tst] cost %9.6f, acc = %7.5f %%') % (test_cost[0], test_cost[1] * 100))

        if epoch_cost[0] < best_trn:
            kvs.store('best_train_params', [it_count, LL.get_all_param_values(ffn)])
            best_trn = epoch_cost[0]
        if test_cost[0] < best_tst:
            kvs.store('best_test_params', [it_count, LL.get_all_param_values(ffn)])
            best_tst = test_cost[0]
print("...done training %f" % (time.time() - start_time))


---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-6-4f11d2d24f2a> in <module>()
     12     b_l, b_c, b_s, b_r, b_g, b_a = [], [], [], [], [], []
     13     for x_ in ds.train_iter():
---> 14         tmp = funcs['train'](*x_)
     15 
     16         # do some book keeping (store stuff for training curves etc)

/usr/local/lib/python2.7/dist-packages/theano/compile/function_module.pyc in __call__(self, *args, **kwargs)
    869                     node=self.fn.nodes[self.fn.position_of_error],
    870                     thunk=thunk,
--> 871                     storage_map=getattr(self.fn, 'storage_map', None))
    872             else:
    873                 # old-style linkers raise their own exceptions

/usr/local/lib/python2.7/dist-packages/theano/gof/link.pyc in raise_with_op(node, thunk, exc_info, storage_map)
    312         # extra long error message in that case.
    313         pass
--> 314     reraise(exc_type, exc_value, exc_trace)
    315 
    316 

/usr/local/lib/python2.7/dist-packages/theano/compile/function_module.pyc in __call__(self, *args, **kwargs)
    857         t0_fn = time.time()
    858         try:
--> 859             outputs = self.fn()
    860         except Exception:
    861             if hasattr(self.fn, 'position_of_error'):

ValueError: Input dimension mis-match. (input[0].shape[1] = 256, input[1].shape[1] = 16)
Apply node that caused the error: Elemwise{mul,no_inplace}(Elemwise{sub,no_inplace}.0, InplaceDimShuffle{x,0}.0, InplaceDimShuffle{x,0}.0)
Toposort index: 98
Inputs types: [TensorType(float64, matrix), TensorType(float64, row), TensorType(float64, row)]
Inputs shapes: [(6890, 256), (1, 16), (1, 256)]
Inputs strides: [(2048, 8), (128, 8), (2048, 8)]
Inputs values: ['not shown', 'not shown', 'not shown']
Outputs clients: [[Elemwise{add,no_inplace}(Elemwise{mul,no_inplace}.0, InplaceDimShuffle{x,0}.0), Elemwise{Composite{(i0 * (Abs(i1) + i2 + i3))}}[(0, 1)](TensorConstant{(1, 1) of 0.5}, Elemwise{add,no_inplace}.0, Elemwise{mul,no_inplace}.0, InplaceDimShuffle{x,0}.0)]]

Backtrace when the node is created(use Theano flag traceback.limit=N to make it longer):
  File "/usr/local/lib/python2.7/dist-packages/ipykernel/ipkernel.py", line 196, in do_execute
    res = shell.run_cell(code, store_history=store_history, silent=silent)
  File "/usr/local/lib/python2.7/dist-packages/ipykernel/zmqshell.py", line 501, in run_cell
    return super(ZMQInteractiveShell, self).run_cell(*args, **kwargs)
  File "/usr/local/lib/python2.7/dist-packages/IPython/core/interactiveshell.py", line 2717, in run_cell
    interactivity=interactivity, compiler=compiler, result=result)
  File "/usr/local/lib/python2.7/dist-packages/IPython/core/interactiveshell.py", line 2821, in run_ast_nodes
    if self.run_code(code, result):
  File "/usr/local/lib/python2.7/dist-packages/IPython/core/interactiveshell.py", line 2881, in run_code
    exec(code_obj, self.user_global_ns, self.user_ns)
  File "<ipython-input-3-13e1dbe28221>", line 21, in <module>
    output = LL.get_output(ffn)
  File "/usr/local/lib/python2.7/dist-packages/lasagne/layers/helper.py", line 190, in get_output
    all_outputs[layer] = layer.get_output_for(layer_inputs, **kwargs)
  File "/usr/local/lib/python2.7/dist-packages/lasagne/layers/normalization.py", line 319, in get_output_for
    normalized = (input - mean) * (gamma * inv_std) + beta

HINT: Use the Theano flag 'exception_verbosity=high' for a debugprint and storage map footprint of this apply node.

Test phase

Now that the model is train it is enough to take the fwd function and apply it to new data.


In [7]:
rewrite = True

out_path = '/tmp/EG16_tutorial/dumps/' 
print "Saving output to: %s" % out_path

if not os.path.isdir(out_path) or rewrite==True:
    try:
        os.makedirs(out_path)
    except:
        pass
    
    a = []
    for i,d in enumerate(ds.test_iter()):
        fname = os.path.join(out_path, "%s" % ds.test_fnames[i])
        print fname,
        tmp = funcs['predict'](d[0], d[1])[0]
        a.append(np.mean(np.argmax(tmp, axis=1).flatten() == d[2].flatten()))
        scipy.io.savemat(fname, {'desc': tmp})
        print ", Acc: %7.5f %%" % (a[-1] * 100.0)
    print "\nAverage accuracy across all shapes: %7.5f %%" % (np.mean(a) * 100.0)
else:
    print "Model predictions already produced."


Saving output to: /tmp/EG16_tutorial/dumps/
/tmp/EG16_tutorial/dumps/tr_reg_080.mat , Acc: 65.68940 %
/tmp/EG16_tutorial/dumps/tr_reg_081.mat , Acc: 58.96952 %
/tmp/EG16_tutorial/dumps/tr_reg_082.mat , Acc: 62.80116 %
/tmp/EG16_tutorial/dumps/tr_reg_083.mat , Acc: 60.97242 %
/tmp/EG16_tutorial/dumps/tr_reg_084.mat , Acc: 62.45283 %
/tmp/EG16_tutorial/dumps/tr_reg_085.mat , Acc: 62.72859 %
/tmp/EG16_tutorial/dumps/tr_reg_086.mat , Acc: 50.04354 %
/tmp/EG16_tutorial/dumps/tr_reg_087.mat , Acc: 58.75181 %
/tmp/EG16_tutorial/dumps/tr_reg_088.mat , Acc: 63.68650 %
/tmp/EG16_tutorial/dumps/tr_reg_089.mat , Acc: 65.32656 %
/tmp/EG16_tutorial/dumps/tr_reg_090.mat , Acc: 66.93759 %
/tmp/EG16_tutorial/dumps/tr_reg_091.mat , Acc: 65.45718 %
/tmp/EG16_tutorial/dumps/tr_reg_092.mat , Acc: 61.59652 %
/tmp/EG16_tutorial/dumps/tr_reg_093.mat , Acc: 65.48621 %
/tmp/EG16_tutorial/dumps/tr_reg_094.mat , Acc: 60.76923 %
/tmp/EG16_tutorial/dumps/tr_reg_095.mat , Acc: 66.98113 %
/tmp/EG16_tutorial/dumps/tr_reg_096.mat , Acc: 62.27866 %
/tmp/EG16_tutorial/dumps/tr_reg_097.mat , Acc: 61.81422 %
/tmp/EG16_tutorial/dumps/tr_reg_098.mat , Acc: 48.14224 %
/tmp/EG16_tutorial/dumps/tr_reg_099.mat , Acc: 65.50073 %

Average accuracy across all shapes: 61.81930 %

Results