Closed bruno-f-cruz closed 1 month ago
This looks like a good initial plan. We will likely use np_codeocean for 4 but may use watchdog or switch at a later date. @corbennett does this look good to you?
For now, lets pack both Metrics and Curriculum state in the output_parameters
field. I suggest simply having 2 properties at the top level:
-metrics (Deserializes the Metrics class)
-curriculum (Deserializes the state for the curriculum. Still need a Model for this, I will try to come up with something over the weekend)
@bruno-f-cruz Thank you for your consistent feedback on how you expect us to use this codebase.
I finally had some time to sit down and think about the Trainer class. These are mostly my thoughts and may be a biased interpretation of what Jonathan wrote, but please correct me if I am wrong.
A curriculum instance is a static object that fully defines the possible progression through a set of stages. It can be seen as a hierarchical structure:
A trainer takes the previous Curriculum object and uses the output of a specific session (Metrics) to suggest the next Task, TaskParameters, and Policies to use in the following session. It is important to note that these are merely suggestions that may or may not be accepted by the user (more on this later). To provide this suggestion, the Trainer must be able to index a specific location/state in the full curriculum. While the curriculum has a static definition, the state of a Trainer, given a Curriculum, is defined by:
Let's call these "TrainerState". We can then write:
TaskParameters_T+1 (i.e., Suggestion) = Trainer(Curriculum, TrainerState_T, TaskParameters_T, Metrics_T)
where Metrics and TaskParameters_T must match the Stage
Importantly, as an artifact of the Trainer, a new TrainerState_T+1 is also generated. This new TrainerState is responsible for encoding the new Current Stage and new Active Policy, under the assumption that the next acquired session will run with the suggested TaskParameters_T+1.
These are the settings that were USED during an experiment. They are part of the session.json and will thus be indexed in a documentdb. They are obtained by serializing the Task subclass and stored in the StimulusEpoch.script.parameters field as per @dyf's instructions.
This is a static definition serialized to JSON. It can be recovered as long as it is saved somewhere. @dyf suggested it would be ideal to keep a common library of these curricula (not sure if this already exists?). An alternative would be to save them in the GitHub repo that implements a given curriculum. The JSON should only need to be updated if the Curriculum changes, thus bumping its version.
This is a suggestion that has yet to materialize into real data. We decided to use SLIMS as a way to store the Task object that contains these parameters as a JSON file. If the suggestion is accepted, TaskParameters_T+1 will trigger an identical pipeline to TaskParameters_T.
Similarly to TaskParameters, these should be serialized to the StimulusEpoch.output_parameters at the time of creation of session.json.
A recent discussion (see above) suggests that this class should be serialized next to Metrics. This would be "ok" assuming that only "on-curriculum" training will occur. However, this has a few problems should users want to override a suggestion and/or a specific state of the trainer. The bottom line is: I think we will need a modifiable object, somewhere, that can be used to store and, if needed, manually update the state of the curriculum. While the session.json might solve the "storing" issue, it seems like a clunky solution if the state needs to be modified, as it would require modifying the session.json itself. Maybe I am just missing something on how session.jsons are used and indexed? It may be the case that a different behavior event is needed in SLIMS perhaps? This would be simply responsible for keeping the state of the trainer for a given animal.
While thinking about this subject I also went through some simulated scenarios of the pipeline that made me write down some notes on how to recover the Trainer from a manual intervention.
A simple heuristic could be:
Is the incoming TaskParameters, Metrics, and Curriculum compatible with the current TrainerState (i.e., Stage and Policies)?
Does the user want to try to "coerce" the previous TaskParameters into the trainer? (Remember, the user might have manually set parameters that were not suggested by the trainer, OR it may have manually changed the curriculum state.)
if (1 & 2) == True run Trainer else throw exception and require manual intervention from the user.
It is important to note that manual intervention at the level of TaskParameters or TrainerState might lead to deadlocks that the experimenter did not anticipate due to, for instance, policy transitions applied on top of Policies that would never be co-active otherwise.
Alternatively, we could also only allow a "strict" mode to be used where only stage transitions are allowed. This would essentially remove any dependency on the previous session (since each stage starts from an initial set of parameters and active policies).
Anyway happy to discuss this in person, just wanted to make sure I captured these thoughts somewhere.
After today's meeting, I believe that the consensus is that the TrainerState information should be put in a container together with a suggestion for the next session and go into SLIMS.
If a manual override is performed by the user (see "How to recover from manual changes in TrainerState?" above), it is expected to lead to an update of both task settings and curriculum state. As a result, SLIMS should be seen as both a database for scheduled experiments as well as a database for ephemeral curriculum state.
Remaining questions:
I think this can easily be solved, however, by dict-indexing the json object (i.e not deserialize onto the python object), accessing the json["Container"]["Stage"]["Task"] and deserialize to the proper task at this level.
This should be made easy by following and sharing a common structure. Since we are adopting pydantic as our lib to build models, we should look into FastUI to procedurally generate UIs
Bootstrapping an animal the first time:
Running a session:
Manual intervention edge-cases:
User overrides suggestion
This results from taking the 1* approach. There is no really good way to guarantee that the mouse can go back to the curriculum automatically as the pipeline loses "control" over what task and parameters ran at the rig. This situation should be handled by an application-specific routine and left to the criteria of the developer
User manually moves the animal to a different stage.
This will reset the curriculum to a stage akin to "Bootstrapping an animal the first time 2". A first session will be suggested from no prior Metrics and the curriculum can continue.
User manually removes animal from curriculum and resumes later
Worst case scenario, this defaults back to "### User manually moves animal to a different stage.". Best case, the user resumes the animal in the same stage. If this is the case, the pipeline resumes from "Running a session. 5."