ioam / topographica

A general-purpose neural simulator focusing on topographic maps.
topographica.org
BSD 3-Clause "New" or "Revised" License
53 stars 32 forks source link

Why _createcfs() in sparsecf.py does not use SparseConnectionFielf.__set_weights() to set the initial weghts? #640

Closed mjabri closed 8 years ago

mjabri commented 8 years ago

Opps hit the wrong button. I am trying to understand why _createcfs() in sparsecf.py does not use SparseConnectionFielf.set_weights() to set the initial weights? Obviously there must be a reason as when i try to use __set_weights() I dont get the same results, and wanted to understand when setTriplets() approach should be used instead of directly calling set_weights() to set the weights values.

philippjfr commented 8 years ago

The main reason is because it's inefficient to set each CF individually, instead I generate the CFs in chunks and then set the corresponding triplets. You should also note that __set_weights has only been implemented for the CPU sparse implementation. It could also be that the current __set_weights implementation isn't quite correct. Are you using the sparse CPU or GPU implementation?

What's missing from the sparse GPU implemention is SparseGPUConnectionField class. Looking at the GPUSparseCFProjection it seems like it's missing a lot of functionality, it lets the SparseCFProjection handle the creation of all the CFs along with SparseConnectionFields and uses the .weights sparse matrix transferring it to the GPU (.weights_gpu). Since the SparseConnectionFields are still pointing the CPU weights and they aren't being synced you're getting the wrong data.

The correct solution would be to rewrite GPUSparseProjection to populate the GPU sparse matrix directly without going via the intermediate storage in memory. The lazier solution would be to simple parameterize the SparseConnectionField type on the SparseCFProjection class and write a SparseGPUConnectionField implementation that gets and sets the data via the .weights_gpu attribute.

mjabri commented 8 years ago

Yes, i can see it processes chunks (rows) at a time. I am purely looking at non-sparse and sparse (non-gpu), trying to validate some results i already have. While doing this I noticed: 1- I am not getting the same initial weights between non-sparse and sparse, even if I ensure the same seed for RandomState. The shape is different, for example, the sparse weights of top-left has top-left rounded corner while the same CF non-sparse has bottom right rounded shape. 2- When I changed sparsecf.py to do initialization of weights in a similar way (like ConnectionField), and use set_weights to set values of weights after generating them, I am finding the actual values of weights returned by __get_weights (ie by looking at flatcfs or cfs[][ to now match the weights of the non-sparse (ie using ConnectionFields) case. BUT when i examine the weights using weights.toarray().transpose()[] I find the weights to be different. Somehow what set_weights/get_weights are returning do not match the weights when I look at them through the sparse matrix (weights.toarray().transpose()[]. The matrix returned by toarray() seems to have many connection field with all zero weights. This points that there is something wrong in either case, but as i am getting the right weights using __set_weights/get_weights (compared to non-sparse architecture). 3- So, if i use __set_weights instead of the chunk initialization already there, I end up with same initial weights as the non-sparse architecture. But when i look at the output activities after 1 iteration, I can confirm the fact that the sparse matrix reflects something else as I get a zero band all around the activity (because of the zero connection fields all around the sheet in the weights sparse matrix and as the do product is using the sparse matrix directly. So I am banging my head to why initial weights are different when produced by original sparsecf.py and why when I change to __set_weights, the sparse matrix seems to be out of sync with what flatcs[].weights or cfs[][].weights return.

philippjfr commented 8 years ago

The shape is different, for example, the sparse weights of top-left has top-left rounded corner while the same CF non-sparse has bottom right rounded shape.

Can't comment in detail right now but it sounds like something is inverting the indexing of the CFs, my guess it's either __set_weights or __get_weights.

mjabri commented 8 years ago

Yes, possibly, but note also the initial weights generated by non-sparse architecture (ConnectionField) and original sparsecf.py do not match even if same seed, but this may be a different issue.

philippjfr commented 8 years ago

That just indicates to me that it's definitely __get_weights that is wrong, since we have tests to confirm that the sparse and non-sparse models generate the same activities, which wouldn't be the case if the weights didn't match.

mjabri commented 8 years ago

Can you have a look when you have a minute and let me know if this should get the weighst of the same CF?

from math import sqrt

def get_weights(c, i, j): """get weights returned by __get_weights and toarray""" cf = c.cfs[i][j] w1 = cf.weights # weights retrieved using __get_weights m = c.weights.toarray().transpose() dx,dy = c.dest.activity.shape sx,sy = c.src.activity.shape row = mi*sy + j = row.shape sz = int(sqrt(sz)) row = row.reshape((sz,sz)) x1,x2,y1,y2 = cf.input_sheet_slice w2= row[x1:x2,y1:y2] return w1,w2 # can compare if equal using np.allclose(w1,w2)

mjabri commented 8 years ago

If the code above is correct, the w1 and w2 weights retrieved on gcal_sparse.py for lgnon -> v1 (after 1 iteration so model is built) do not always match.

philippjfr commented 8 years ago

Do they match after initialization?

mjabri commented 8 years ago

If I run check_weights() below on all weights of LGNOn->V1 of gcal_sparse.ty after only 1 iteration (so model is built), then all weights match to within np.allclose tolerance . So this tells me __get_weights seems to be ok... (I don't know whether you can see the code ok)...

from math import sqrt
import numpy as np

def get_weights(c, i, j):
    """get weights returned by __get_weights and toarray"""
    cf = c.cfs[i][j]
    w1 = cf.weights # weights retrieved using __get_weights
    m = c.weights.toarray().transpose()
    dx,dy = c.dest.activity.shape
    sx,sy = c.src.activity.shape
    row = m[i*dy + j]
    (sz,) = row.shape
    sz = int(sqrt(sz))
    row = row.reshape((sz,sz))
    x1,x2,y1,y2 = cf.input_sheet_slice
    w2= row[x1:x2,y1:y2]
    return w1,w2 # can compare if equal using np.allclose(w1,w2)

def check_all_weights(c):
    dx,dy = c.dest.activity.shape
    for i in xrange(dx):
        for j in xrange(dy):
            w1,w2 = get_weights(c,i,j)
            res=np.allclose(w1,w2)
            if not res:
                print 'np.allclose is ', res, ' for ', i, j
mjabri commented 8 years ago

Ok, i think i may have at least identified an issue. I am using a coord_mapper (Magnifier). If I set the magnification to 0.0 (no mapping), then my check_weights() passes (no mismatch) on initial weights of sparse architecture, and as importantly I seem to get the same initial weights between non-sparse and sparse architectures.

To reproduce the issue in gcal_sparse.ty, I added:

magnify_mapper = 0.0 #1.5 coord_mapper_type = topo.coordmapper.MagnifyingMapper(remap_dimension='xy', k=magnify_mapper)

and then the coord_mapper arg in V1_afferent def as below

@projection_dec_type
def V1_afferent(self, src_properties, dest_properties):
    params = super(SparseGCAL, self).V1_afferent(src_properties, dest_properties)
    return dict(params[0],
                cf_type = SparseConnectionField,
                response_fn = response_fn_type,
                learning_fn = learning_fn_type,
                weights_output_fns = weights_output_fns_types,

--> coord_mapper = coord_mapper_type)

If i set the magnify_mapper to 0.0, then no problem, the check_weights passes. But if I set it to 1.5, then most weights do not match in my check_weights. So it seems the coordinate generation as per coord_mapper is somehow confusing the sparse matrix?

philippjfr commented 8 years ago

I haven't used coord mappers myself and it's perfectly possible that they aren't currently compatible with the sparse implementation. Hopefully I can look into all of this very soon.

Note that I've just moved the compilation of the sparse component into the setup.py. To compile it you'll now have to execute:

python setup.py build_ext

at the topographica root.

mjabri commented 8 years ago

Ok. Should i close this issue and open a new one on coord_mapper with sparse architecture?

BTW, What I am using for my work the 0.98 release where I have transplanted sparse (and param) from the latest which has the issues of projection and pylab plotting. I only use the latest to reproduce issues.

philippjfr commented 8 years ago

Yes, although realistically unless you're willing to put in the effort to make that work it probably won't get done in the foreseeable future. I'll have another look at fixing the GUI now though, so you can go back to using the latest version.

mjabri commented 8 years ago

I will try once I get the Sparse/GPU validated and the simulations going :) Now, if i turn off the coord_mapper, i can transfer between sparse networks b/w CPU and GPU back and forth pretty quickly, and the projections initially and after half million iterations look good.