Closed andrewheusser closed 5 years ago
I'm working on this now. I'm porting over all of the fingerprint logic from js to python. The way I'm planning to set this up is as a class within quail. Using it would be very similar to how its currently used in the js code, e.g.:
from quail import Fingerprint
# initialize fingerprint
fingerprint = Fingerprint(state='random', features=features, prior_weights=weights, alpha=4, tau=1)
# collect some data...
# compute some weights based on data
weights = fingerprint.compute_weights(pres_list, rec_list, feature_list)
# update the fingerprint class
fingerprint.update_weights(weights)
# reorder a list based on the fingerprint and the fingerprint state
reordered_list = fingerprint.reorder_list(next_list)
open to alternate APIs, @jeremymanning let me know what you think!
A few questions/comments:
state='random'
flag do? alpha
and tau
do? My memory is that they're used for re-ordering based on the fingerprint, not for computing the fingerprint itself...so the parameterization is a bit strange (e.g. one might want to use the same fingerprint to reorder lists with different alpha
s and tau
s, but with the current syntax that would require a new fingerprint object, which seems clunky).permute
flagcompute_weights
function is really what computes the fingerprint, and the Fingerprint
function is really what re-orders the list given the fingerprint. So the names are a bit off, I think.Do you want to take another stab at this given the above comments?
What does the state='random' flag do?
if the fingerprint object is in the random state, when you call fingerprint.reorder_list
it will simply return a shuffled version of the original list. another possibility is that we could move to a 'stateless' architecture, where fingerprint.reorder_list
would have to be called with a state
parameter that determines its behavior.
Do we need to explicitly specify the features? Won't the features be computed from the stimuli directly? We could set it up this way, but specifying the features here would allow the user to store many features of the stimulus, and only reorder based on a subset of them.
Can you remind me what alpha and tau do? My memory is that they're used for re-ordering based on the fingerprint, not for computing the fingerprint itself...so the parameterization is a bit strange (e.g. one might want to use the same fingerprint to reorder lists with different alphas and taus, but with the current syntax that would require a new fingerprint object, which seems clunky).
Alpha is a gain parameter for the feature stick and tau is a gain parameter for the stimulus stick i.e. ['size']*round(size_weight**alpha)*100
Instead, alpha
and tau
could be parameters of the reorder_list
function. this would be more flexible
We should have a permute flag agree
_The computeweights function is really what computes the fingerprint, and the Fingerprint function is really what re-orders the list given the fingerprint. So the names are a bit off, I think.
hmmm, not sure exactly what you mean. in my current setup, fingerprint
is an instance of the Fingerprint
class that stores the fingerprint state, past fingerprint weights, and some parameters. the class instance can compute weights and reorder lists by using the compute_weights
and reorder_list
methods, respectively.
Ok, I think I understand how the current "fingerprint" API is designed. I'd recommend changing it a bit. First, I think it's worth drawing a distinction between two concepts that I think are conflated with the current naming system:
The reason these are distinct is that they can potentially operate independently. Some examples:
I think a more accurate name for the "Fingerprint" object would be something like an "OptimalPresenter" object. This could be initialized with the following flags:
strategy
-- either a flag (e.g. "random," "forward," "reverse") or a function that accepts a fingerprint (or a list of fingerprints) and a list of to-be-presented items and returns a new order for the list of to-be-presented items. (Default: "random")params
-- a dictionary of parameter values used to determine how the fingerprint affects the reordering given the specified strategy. This would include the "alpha" and "tau" parameters, plus a newly initialized Fingerprint object (see below). (Default: reasonable default values for alpha and tau, and Fingerprint with all default values as specified below.)features
-- a list of features to be considered, specified as a list of strings. If None
, all features are considered. If the user specifies a subset of features to be considered, all others are ignored. If the user specifies a feature that doesn't exist in the stimulus set, only consider the other user-specified features. If none of the user-specified features exist in the stimulus set, the function should behave like the "random" state. (Default: None)In addition, there should be a Fingerprint
object that stores and updates the fingerprint. This could be initialized using: fingerprint = Fingerprint()
. The fingerprint object could have the following fields (any can be set when initializing the new object, using keyword arguments):
state
: the current fingerprint (an array of real numbers between 0 and 1, inclusive); initialized to all 0.5sspermute
: a boolean flag specifying whether to use permutations to compute the fingerprint (default: True)n
: a counter specifying how many lists went into estimating the current fingerprint (initialize to 0)features
: behaves just like for the OptimalPresenter
object (see above; default: None-- consider all features)The OptimalPresenter object should also include
set_params
function that takes a parameter name and value, and updates that parameter to have the given valueget_params
function that takes a parameter name and returns its valueset_params
and get_params
should also operate on lists of parameter names/values (e.g. to set/get multiple parameters in one function call)set_strategy
function that takes in a value and sets strategy
to that valueorder
function that takes in a new list and, using strategy
and params
, returns a new ordered listThe fingerprint could be updated by calling fingerprint.update(presented, recalled)
, where presented
is a list of presented stimuli and recalled
is an ordered list of recalled stimuli. Each item in both of these lists should be a dictionary specifying the item's feature values. Updating the fingerprint does the following:
f
. Only the dimensions specified in features
should be considered.state
by n
, adds f
to state
, and divides the sum by n+1
. This is the new state
.n
by 1.Finally, to re-order a list using the fingerprint, use:
presenter = OptimalPresenter()
fingerprint = presenter.get_params('fingerprint')
#repeat for each list:
fingerprint.update(next_presented, next_recalled) #this call should update the fingerprint object in presenter-- i.e. it's stored by reference, not by value
next_order = presenter.order(next_list)
In different conditions, we can use set_param
to change the strategy (i.e. the way the fingerprint affects the ordering)-- e.g. presenter.set_strategy("forward")
, etc.
awesome, thanks for the feedback. i think this makes sense. i'll keep you posted if i run into any issues while implementing this
Currently, only implemented in javascript - need to pipe over to python so that we can run permutation analyses where we fix the temporal clustering score and see where feature clustering fall within that distribution