- 1 Introduction: What SessionModel is and When You Would Use It
- 2 The Framework: How SessionModel works
- 3 Installing the SessionModel plugin
- 4 The Session Automator window and basic How-To's
- 5 Defining Training Stages: A Demo
- 6 How End-of-Day Logic works
- 7 The good charioteer: Tips on running many animals every day and keeping your sanity
Introduction: What SessionModel is and When You Would Use It
SessionModel is, in short, a plugin to coordinate within-session and across-session training automation.
First, a word about where SessionModel fits in the Solo system.The SessionModel module is at uppermost layer - the 'Plugins' layer - of the Solo training software system; you could successfully use the Solo system to write and run protocols without ever using this module.
When, then, would you use this module?
Suppose you have written a behavioural protocol using the Solo system. You have painstakingly determined the training steps required to teach naïve animals the task of interest. This probably involves a sequence of training stages at each level of which, you would monitor the animals’ performance and decide whether to move to the next training step. Individual animals learn at different rates so you would adapt the progression of training steps to each animal’s performance. All of this would be done manually with the trainer observing each animal’s performance and accordingly adjusting the training parameters (SoloParamHandles).
SessionModel formalizes this sequence of training stages into a framework which can be programmed and so allows the progression of training to occur automatically (ie without manual intervention).
It allows the user to:
- Create a sequence of separate training stages
- Define what happens within any given training stage
- Determine performance criteria for changing parameter values (e.g. if animal does “badly”, make task “easier”)
- Determine performance criteria for moving to the next stage (e.g. animal “has learnt” where reward is received; now start pairing CS with reward, or alternating between blocks of trials with different task contingencies)
- Execute special instructions at the end of each session to set up the automation for the next day’s session
Automating a session has practical as well as scientific advantages: many more animals can be simultaneously trained, new protocols could be tested, the trainer is free to do other experiments, and it removes subjective intervention (which could differ between animals) on the part of the trainer.
This section must end with a caveat: Eventually, the trainer is not going to be monitoring an automated session as it happens. Unless the proper analysis tools are in place, she will not know whether the training is progressing as intended or whether a parameter has (unintentionally) been changed to an undesirable value. Such things can go unnoticed for days. See the section “Good Charioteer: Tips for Smoother Daily Automation” for some ideas on how to monitor complex protocols for several animals day after day without risking your sanity.
The Framework: How SessionModel works
Let’s work with a simple training example.
The goal is to acquaint a rat with a water port. In each trial of the session, we will dispense some water from this port, and we will monitor whether the rat licked (hit) the water port or not (miss). We will say that if a rat has licked for five consecutive trials, he is “acquainted” with the port; we can move on.
A training stage: as seen by trainer
The figure to the right shows what the trainer would be doing when monitoring the rat:
- Count the number of recentmost consecutive “hits”
- When this number goes above a decided threshold, she moves to the next training step.
- If the number has not achieved threshold, she allows the next trial to run.
This would happen after every trial until the rat has become acquainted with the port; thereafter, a fresh set of rules is applied, one applicable to the next training stage.
Formally, this training stage consists of the following steps:
- An algorithm defining the training stage: Count the number of recentmost consecutive “hits”
- A test for when the stage has met its goal:When this number goes above a decided threshold, she moves to the next training step
- If the number has not achieved threshold, she allows the next trial to run.
SessionModel is the framework which does the above automatically. It runs at the end of every trial, executing the algorithm for the current training stage and at the same time, testing to see if criteria for stage completion have been met. If they have, it moves to the next training stage (if any); if not, it stays at the same training stage.
The difference is that instead of describing a training stage in the English language (as in the example above), we describe it in Matlab Solo code, the language in which protocols are written.
A training stage: as seen by SessionModel
In Matlab terms, each training stage has the following components (there are more pieces than you saw in the previous section but don't worry, the logic is still the same):
- “Training Stage”: Matlab code that evaluates at the end of every trial.
- “Completion Test”: A boolean expression that, when true, will move to the next training stage
- “Vars”: Helper SoloParamHandle variables defined for the training stage (eg the “hit counter” from our example above)
- “End-of-Day Logic”: A piece of code that evaluates only at the end of each session (NOT at the end of each trial). Its purpose is to set up parameter values for the next training session.
The figure on the left shows an abstract view of training stages as defined in Matlab. Notice that the training stages have an order and that each training stage is composed of the pieces listed above.
- TS eval'ed in stages. Status: (Complete), (Active), ().
Installing the SessionModel plugin
Step 1: Inherit from 'sessionmodel' in the 'class' statement at the top of your protocol constructor file.
obj = class(obj, mfilename, sessionmodel);
Step 1b: Make sure the following hack code (which will be unnecessary one day when this gets fixed) is called in your protocol:
hackvar = 10; SoloFunctionAddVars('SessionModel', 'ro_args', 'hackvar');
Step 2: Invoke SessionDefinition after defining all your other SoloParamHandles (ie after you have set up all supporting .m files for your protocol).
SessionDefinition(obj, 'init', x,y,value(myfig));
SessionDefinition takes a max of 5 input parameters. They are:
- obj : Object handle of the protocol making the call
- action : A string specifying what function this particular call requests from SessionDefinition
('init' is probably the only call you will ever make, unless you use EOD logic; all other 'actions' are invoked by SessionDefinition calling itself)
- x,y : x & y coordinates on the parent protocol window where the next GUI element is to be placed
- f : Figure handle to the parent protocol figure
... and that should be it!
The Session Automator window and basic How-To's
Adding SessionDefinition to your protocol will cause an additional window to be added to your view: the SessionDefinition window. This section will familiarize you with the aspects of the Session Automator window.
Load your protocol to which you installed SessionModel. Here is what you should see:
- In your main protocol window, a dropdown box labelled “Session Control”. Its position will depend on where in your constructor you initialized SessionDefinition; if you did it at the very end, the dropdown box should be in the upper-right corner of your GUI controls.
- Select the “view” option in the “Session Control” dropdown box. A new window should pop up (see figure below).
- “Parameters” (top-left): This textbox displays all the protocol parameters that SessionModel has access to for session automation.
Parameters are referenced as a combination of the SoloFunction to which they belong and their names. e.g. If you want to reference the variable “tone_length” in the file “ChordSection.m”, you would use the name “ChordSection_tone_length” (note the underscore connecting the function name and parameter name). However, if a variable has been declared global (using DeclareGlobal), it does not need to be prefixed with its variable name (e.g. n_done_trials)
- “Training Stages” (top-right): This section allows you to manipulate/view training stages as a whole. Using this section, you can:
- Load/save a set of training stages from/to a file (see “Defining Stages in Files”)
- View the sequence of training stages (Grey listbox) and select one for manipulation
- Add (+) or Remove (-) a training stage
- Update (U) a component of a training stage
- Note: One component of a training stage may be edited at a time. Use the orange buttons at the bottom to show a specific part of a training stage. Make your changes and then click the U button. The update will only be made to the particular piece of the selected training stage. If you edit using the textbox and don't update using the 'U' button, your changes will not be saved!
- Delete all training stages (FLUSH)
- Change which stage is the currently active one (“Change Active Stage”).
- “Stage-specific components” (bottom-half):
This section allows you to view/edit different pieces of a training stage (algorithm, completion test, end-of-day logic, etc.,). The big white textbox shows information pertaining to the stage selected in the “Training Stages” listbox. By clicking the respective button below the textbox, you will see corresponding training stage information fill the textbox.
Defining Training Stages: A Demo
This section will show code for how to:
- set values for variables
- use if/else statements to evaluate trial-by-trial performance and change parameters
- write a completion test
- use helper variables
- (Subsection): What the training stages file looks like
- (Subsection): Gradually coalescing training stages into a huge mega-file.
By default, training stages progress one-by-one, in the 'forward' direction (ie down your training file). However, you can also 'jump' forward or backward an arbitrary number of training stages.
Example 1: Jumping back when a contingency is met (defined in training stage):
if performance > 90, make_task_harder; elseif performance < 50 sm = value(SessionDefinition_my_session_model); SessionDefinition_my_session_model.value = jump(sm, 'offset',-3); else do_something_else; end;
The jump method (in @SessionModel) changes the currently active training stage in absolute or relative terms.
- jump(sm,'to', 24): The next trial after execution of this code will have active stage set to stage #24 in your sequence.
- jump(sm,'offset', -1): The next trial will have active stage set to (this stage) + (offset). In this case, SessionModel would jump back one stage. Positive offsets cause forward jumps while negative offsets cause backward jumps.
In the above boxed example, if performance drops below 50, SessionModel would jump back 3 stages.
See EOD Logic section to learn how to jump stages at the end of a session.
How End-of-Day Logic works
"End-of-Day Logic" (EOD) allows a piece of code to be run only when a session ends and data/settings are saved.
Here are some scenarios where such functionality may come in handy:
- When using a staircase method, you may want to start every session using a stimulus value "slightly easier" than the value at which the last session ended
- When collecting psychometric curve data, perhaps you want every session to start with 100 'practice' trials before doing psychometric trials.
In a protocol using EOD, at the end of a session, three things happen in the following order:
- The EOD code associated with the currently active training stage executes.
- A data file with the current session's data is automatically saved in the particular experimenter/ratname folder.
- A settings file for the next day is also automatically saved in the particular experimenter/ratname folder.
How to Use EOD in Your Protocol
STEP 1: , Give SessionDefinition the name of the animal and experimenter for which you want to use EOD.
SessionDefinition(obj,'set_info', ratname, experimenter)
This information will be used to define the name of the data/settings file.
Example: if ratname = "LittleBear", experimenter = "Goldilocks",
Data directory: <DISPATCHER_ROOTDIR> / SoloData / Data / Goldilocks / LittleBear/
Settings directory: <DISPATCHER_ROOTDIR> / SoloData / Settings / Goldilocks / LittleBear /
Note: Be sure to make this call *after* you have loaded any necessary settings (ie that you don't call right after the init call to SessionDefinition). A good place to set the value would be in a piece of code that runs after the first trial is completed (like the prepare_next_trial action in your constructor or in your make_my_state_matrix file).
STEP 2: Call the function which evaluates EOD logic and saves files: There are two ways to run the EOD logic code:
- Using the big red button created in your protocol window by SessionDefinition (see fig on right), and
- Calling SessionDefinition and requesting an eod_save
To call eod_save in code, the syntax is:
When you make the call depends on where in your protocol you test for completion of the session.
Example scenario: Say you have a variable named MaxTrials which sets the upper-bound for the number of trials in a given session.
If you check for session completion in your state_matrix_maker.m file, then in state_matrix_maker.m, you might have a piece of code like this:
if number_completed_trials >= MaxTrials % do other stuff here SessionDefinition(obj, 'eod_save'); end;
Yes, it's that easy.
Both of these methods lead to the execution of EOD logic and the saving of data/settings files.
Example 1: Using EOD to change the value of a parameter
The EOD code is another piece of a particular training stage, just like a Completion Test or Helper Vars. Therefore, if you want the same EOD logic to run for multiple training stages, you have to enter that same code as the 'EOD Logic' for each of those stages.
Here is a sample training stage (as defined in a .m file) with EOD logic:
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % ------------ STAGE SEPARATOR ------- (do not edit this line) % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % --- STAGE NAME: --- (do not edit this line) Sharpening: Hop by 0.1 -- Slow
% --- VAR NAMES: --- (do not edit this line)
% --- TRAINING STRING: --- (do not edit this line) hrate50 = value(RewardsSection_Last50num); hrate30 = value(RewardsSection_Last30num); last_change = value(SessionDefinition_last_change); lprob = value(SidesSection_LeftProb);
if ((last_change > 50 && hrate50 >= 0.9) || (last_change > 30 && hrate30 >= 0.95) && (lprob > 0.4 && lprob < 0.6)), plus_cbk(ChordSection_logdiff, -0.1); SessionDefinition_last_change.value = 0; elseif last_change > 100 && hrate50 < 0.6, ChordSection_logdiff.value_callback = min(0.95, value(ChordSection_logdiff)+0.1); SessionDefinition_last_change.value = 0; else SessionDefinition_last_change.value = last_change +1; end;
% --- END-OF-DAY LOGIC: -- (do not edit this line) ChordSection_logdiff.value = min(value(ChordSection_logdiff)+0.1, 0.9);
% --- COMPLETION STRING: --- (do not edit this line) value(ChordSection_logdiff) < 0.4
In this example, a staircase method is being used to keep decreasing or increasing the value of ChordSection_logdiff depending on the animal's performance. If a session ends on this training stage, the value of ChordSection_logdiff will be incremented by 0.1 before saving settings for the next day's session.
Example 2: Using EOD to jump to another training stage
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % ------------ STAGE SEPARATOR ------- (do not edit this line) % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % --- STAGE NAME: --- (do not edit this line) Collect psychometric trials
% --- VAR NAMES: --- (do not edit this line)
% --- TRAINING STRING: --- (do not edit this line) my_var = my_var+1;
% --- END-OF-DAY LOGIC: -- (do not edit this line) SessionDefinition_eod_jumper.value_callback = 32;
% --- COMPLETION STRING: --- (do not edit this line) 0;
At this training stage, nothing much happens from trial to trial during a session: my_var gets incremented and the stage never completes. However, at the end of a session, the active stage is set back to stage 32.
To jump using EOD, you have to set the value of a special variable called eod_jumper.
The good charioteer: Tips on running many animals every day and keeping your sanity
Let's say all is well and you now have 20 rats that you run every day. These rats are going to be in various phases of training/testing/surgical recovery/what not. How do you track their progress and keep an eye out for any bugs that may pop up unexpectedly?
Here are some ideas for organizing and managing your behaviour pipeline:
Keep succinct, informative notes
I use a spreadsheet to keep track of my rats' progress on a daily basis. Although Microsoft Excel serves me pretty well, there are probably great alternative spreadsheet tools too. Here are some advantages of monitoring data this way:
- Tabular format
- Cells can be colour-coded to indicate a rat's task (eg. pitch discrimination versus duration discrimination) or status in the pipeline (eg. recovering from surgery, error in settings on day X, psychometric trials started on day D)
- Easy-to-use drag-n-drop GUI
- Panes can be frozen so that the horizontal extent of the sheet can stretch for months while keeping the leftmost column (rat names) always in view
- Tracking the animal's performance using your own notes allows a quick glance at recent history to see any unwanted trends in an individual
Keep a code-accessible rat database
This is important when training occurs in phases which are analyzed separately or compared to one another (e.g. performance at phase P before/after a manipulation) and doubly so if experiments are being done on more than one task.
Here's an example scenario:
Train different sets of animals on task T1 and T2, each of which occur in phases.
- T1 phases: Training, Testing_Before, Testing_After
- T2 phases: Training, Testing_Before, Testing_After
A simple solution is to create a driver file in Matlab which serves two purposes:
- to track rat-relevant information in a lookup table
- to serve this information to requesting analysis tools via 'get' calls
With this infrastructure, analysis tools can then simply get data from different named training phases for multiple rats.
Example columns for the above-mentioned lookup table are:
- rat name
- task designation
- for each phase, a cell array containing start and end dates of the training phase
- boolean to indicate whether it's a rat currently training (1) or not (0)
- dates to exclude from any analyses
Once such an infrastructure is place, writing driver analyses tools (see next tip) becomes much easier.
Monitor Daily Progress with a Behaviour Dashboard
* "Ack! My rat's been running on localized sound for a week! No wonder he was doing so well!" * "I wonder if rat X has had his bias correction off after I updated settings last night." * "The rat's doing well but the training hasn't progressed for 3 days. Did automation inadvertently get turned off?"
(Many rats) X (Many training parameters) X (Automation) = A lot more room for things to go awry.
Minimize these concerns by making an analysis tool that shows your key variables of interest/concern for a rat at the click of a button.
It's a simple idea but a time- and hippocampal-cell-saver.
Write a session-monitor script which, given a ratname and date does the following:
- Plots variable_value versus trial_# for your variables of interest so you can see the session progression
- Groups them into different figures (windows) based on their function in the task (sound params in a window, side-selection params in a window, bias-correctors in a window)
- Highlights BAAAD combination of parameters by drawing attention to a troublesome axis
- e.g. if testing_phase > 0 && bias_corrector_is_on OR
- e.g. if sum(session_automator_tracker(1:end)) == 0
(I find that setting the background colour of axes yellow gets my attention)
- Shows graphs relevant to the particular phase the rat is in
- e.g. if rat W happens to be doing psychometric trials, throw up a psychometric graph along with everything else
Write an analysis tool driver
Create a GUI which allows access to analysis tools of different granularities. To the right is an example of 'Behaviour View', a Matlab-based GUI I've used as a driver to analyze data.
It has the following features:
- It has a dropdown list of rats (either current or all of them)
- Access to daily tools
- The Behaviour Dashboard
- A standalone pokes-plot viewer
- Note the ability to select dates by either offsets (from today) or by date
- Access to aggregative tools
- Summary of psychometric data collection before/after lesion
- Significance tests of before/after comparisons
All-in-all, a one-stop shop for quick daily viewing.