AXTut CPTAX Program

From Emergent

Jump to: navigation, search

(back to AX Tutorial)

Contents

CPT-AX Program

The next challenge is to write a program that will generate the CPT-AX task (CPT stands for continuous performance task), which is the logical extension of our simple AX task to the sequential domain. Instead of A and X each being targets, the target is now an A followed by an X in sequence. Other sequences such as A followed by Y or B followed by X are non-target sequences, which nevertheless overlap with the target sequence. In our simplified version of this task (and in several of the actual experiments on people), we restrict the sequences to cue-probe pairs, where cues are A,B,C and probes are X,Y,Z. See e.g., Braver, T.S., Barch, D.M. & Cohen, J.D. (1999). Cognition and control in schizophrenia: A computational model of dopamine and prefrontal function. Biological Psychiatry, 46, 312-328, for an application of this task and further discussion and references.

One implementational detail in how this task is run is key for generating interesting behavioral and neural data: the frequency of the A-X target sequence is set to be relatively high (typically 70%), so that it becomes the default expectation. Then, the two related non-target sequences become much more interesting. For A-Y, there should be a strong expectation of getting an X, which will be influenced by the extent to which the A cue is well remembered. Errors on this trial type, where people might press "target" at the Y, would actually suggest strong maintenance of the A cue. A similar argument applies to B-X, where the X is typically a target, but if you remember the B cue, you should not press the target key. The C,Z items serve as baseline controls, as does the B-Y sequence.

Plan for the Program

With the above in mind, we can sketch out the logic of our overall program:

  • Flip a weighted coin to determine whether we want to generate a target sequence or not. 70% of the time we generate a target sequence, in which case we just produce A followed by X and that is easy.
  • Generating a non-target sequence is harder. We need to randomly select from the cues (A,B,C) and the probes (X,Y,Z), while ensuring that we don't randomly pick A-X. We'll discuss a couple of different strategies for this.
  • We can do the above cue-probe generation process multiple times to generate a larger set of trials that we will run on a given epoch worth of network training. That is just a simple for loop around the above code.

List of Variables

Once we have this plan in place, we can create a set of variables that we'll need -- the general flow of programming in this system involves creating variables and then opearating on them, so getting the variables down is the key first step:

  • pct_target -- how frequent should the target sequence be? this is actually a proportion, but pct is a much simpler label -- for the default case it should be .7
  • rnd_number -- a random number between 0 and 1 (floating point or Real) that we'll generate to simulate the flipping of a weighted coin.
  • cue -- the identity of the cue input (A,B, or C) represented as a DynEnum of type Input, taking on values I_A, I_B, or I_C.
  • probe -- the identity of the probe input (X,Y, or Z), represented by an Input DynEnum as well.
  • output_unit -- correct answer for the output layer (DynEnum of type Output) -- we'll have the model respond "non-target" for all the cue items, and "target" for the targets.

Getting Started: Copy and Modify

The easiest way to get started is to duplicate and modify the existing AXTaskGen program (this is a general rule -- if there is a program that has several elements that you want, just copy and modify instead of starting from scratch). To do this, click on the AXTaskGen program in the left browser, and use the context menu to select Duplicate. In the new program, enter the name as CPTAXGen, and update the description to reflect what we're doing.

Now go to EditProgram, and click on the ForLoop object in the prog_code, and use the context menu to Delete that object -- this will also delete everything within it, which is almost all of the code from the previous program. All that should remain is the ResetDataRows at the start (which we can use in any case).

We can now setup our variables as indicated above. Just rename input_unit to cue, then duplicate it and call it probe. Then drag a new var (from Var/Fun toolbox) into vars and call it "pct_target", and set the type to Real, and enter a value of .7 (don't miss that decimal point!). Duplicate it, and call it "rnd_number". It would be a good idea to enter the descriptions for each variable in their desc fields (you can copy and paste from the above text if you want).

Flipping a Weighted Coin For the Target

The first step in our actual program code is to flip a weighted coin to decide if it is a target sequence or not. We do this by generating a random number (rnd_number) which is uniformly distributed between 0 and 1. We then see if this number is less than our target percent value -- this will be true 70% of the time for a value of .7, and that is what we want!

  • in the Toolbox, Misc Fun, there is a random() guy -- grab that and put it at the end of your program (drop on prog_code or after the reset data rows). Set the result_var to rnd_number, and click on the method -- in the browser window that comes up, you can select different categories (in the menu at the top) of random numbers to generate -- select "Float", and then pick ZeroOne. This will set rnd_number = a random number between 0 and 1.
  • Go to Ctrl toolbox and drag if to the end of your program. In the cond expression, type/select: rnd_number < pct_target.

The true_code for this if is now the target case, and the false_code is the non-target. To set the target values, we just need to assign cue and probe to A and X respectively. Drag var= from the Var/Fun toolbox into true_code, and set the result_var to cue, and choose the I_A enum from the enum_lookup button. Drag var= again and set probe = I_X. Finally, drag var= and set output_unit = O_T.

Generating the Non-Target

There are two strategies for generating the non-target sequence that excludes A-X:

  • Brute force: randomly generate a cue and a probe and check that they aren't A-X -- if they are, then repeat the process until they aren't. This is not particularly efficient, but it is easy to code.
  • Choose from a list: generate a list of all possible cue-probe combinations, remove A-X from this list, and then randomly select an item from this list. This is more efficient overall, but harder to code. It is left as an excercise for later, as it demonstrates some important techniques.

To do the brute-force method, you need to enclose the random generation code in a "do" loop, which does some things (generates the random cue/probe) and then tests whether it should loop again (if it is A-X) or not.

Drag the do guy from Ctrl into your false_code of the target if test. Enter/lookup cue == I_A && probe == I_X as the test for continuing to loop ( == is the equality operator, and && is logical AND).

Now inside the loop_code, we need to randomly generate a cue and a probe. Drag the random() guy from Misc Fun in there, and set the result_var to cue. For the method, select the Int category, and choose IntMinMax -- we'll specify a minimum and maximum value to generate random numbers between. Notice that the min and max arguments open up below this element -- these are the values that will be passed to the IntMinMax function. Click on min, and enter/lookup I_A. For the max, enter I_C + 1, because this IntMinMax function generates values exclusive of max (this is consistent with the C programming language convention, where values go between 0 and n-1 instead of 1 to n).

Just duplicate this RandomCall element, and change cue -> probe and min = I_X, max = I_Z+1. Finally, drag var= and set output_unit = O_N to show that this is a non-target case (quicker to drag AssignExpr guy from true_code and change O_T to O_N).

To test the program at this point, you can drag a print var object to the end of the code, and select cue, probe, and output_unit for the vars to print, and do Init and Run and see that it tends to produce a predominance of A-X and O_T. To really test it, set pct_target = 0, and Run some more. You should never see an A-X, and only O_N.

Generating the Input Data Patterns

The last step is to produce the input data patterns for the values we have generated. This is just a matter of adding a couple of new row guys from the Data toolbox and set units var from the Network toolbox to set the units.

  • drag the new row from Data Toolbox to the end of the program (drop on prog_code) and set data_var to input_data.
  • drag the set units var from Network toobox to the end, and set unit 1 to the cue variable.
  • Because the output is a literal in this case (O_N or non-target), we cannot set it using this element, which requires a variable. So, you need to drag the set units lit to the end, and set the enum_type for unit 1 to Output, and the value to O_N.
  • Drag and drop these AddNewDataRow and SetUnitsVar guys that you have just made on top of prog_code and select Copy Into to duplicate them at the end of the program. In SetUnitsVar, change unit 1 from cue to probe, and in unit 2, select output_unit.
  • Finally, drag a row done from the Data toolbox to the end to tell it to update the view. Select input_data as the data_var.

Init and Run the program while looking at the .T3Tab.StdInputData view tab. You should see it generate a valid cue-probe input that matches what is printed out on the console. Keep running to see a range of inputs.

Generating Multiple Cue-Probe Trials

The last bit of programming needed is to simply loop over the existing set of code multiple times to create several cue-probe sets per epoch for the network to train on. Drag a for loop from Ctrl on top of the 2nd line of the program (RandomCall). Then, multi-select the rest of the program code (only the guys at the main level, not the true_code or false_code within the if statement (this is done with alt-click on linux or mac-command-click on the mac -- note that order matters so select down in order!), and then drag the whole thing into the loop_code of the for guy. Pretty slick.

If you just Run it like this, you'll get 10 trials. It would be good to make the number of trials a variable that can be set. Drag a var into vars and call it "n_trials", and set it to 50 (Int = integer type). Then, click on the for loop and replace the 10 in the test expression with n_trials.

You can probably turn off the console printout by now -- just click on the PrintVar guy and click the OFF flag -- this keeps it around in case you want to do some debugging or something later, but it is not actually used in the code.

Updating the Control Panel

If you go back to the Program Ctrl tab for the CPTAXGen program (instead of Edit Program where we've been), you'll see that all of the args and vars are present there. However, some of those vars are actually more internal variables that the end-user doesn't need to set or configure. So, we should remove those from the control panel, leaving only the pct_target and n_trials variables. To do this, go back to Edit Program and click on each of the vars, and turn off the CTRL_PANEL flag for all but pct_target and n_trials. Then go back and marvel at the clean interface you've provided for your grateful user! The mouse-over tooltip even shows whatever comment you entered in the desc field for that variable. This can provide a quick but quite usable interface for many different programs.

Calling from the Epoch Program

The final final step is to call the CPTAXGen program every epoch, so that we get a new random selection of trials every epoch (keeps the network from simply memorizing the particular sample we happen to have generated). To do this, we go to the .programs.gp.LeabraAll_Std.LeabraEpoch program in the LeabraAll_Std subgroup of programs, and do Edit Program. Then drag the prog() guy from the Var/Fun toolbox just after the 3rd line of the prog_code, which starts the timer recording how long the epoch takes to process (it is a MethodCall, in blue). Then select CPTAXGen for the target. Note that this automatically brings up the input_data arg, and it even automatically fills this in with the input_data variable in the LeabraEpoch program -- if an arg has the same name as a variable in the calling program, it is used automatically (of course you can always change it if that isn't right).

There is one additional and very critical final step with the LeabraEpoch program. Go to the Program Ctrl tab, and observe the set of program vars available for you to set. The first one, called data_loop_order is set to PERMUTED by default -- this means that the trials (rows of the input data table) are presented in a shuffled random order (without replacment, so each trial only appears once). Clearly this is not going to be good for our sequential input data. So, change it to SEQUENTIAL, which will present the trials in sequential order. Given that we're doing the randomization within our program, this should be just fine. There is also another way of doing this that involves creating grouped trials (i.e., cue-probe), where you can randomize the order of the groups, but present the trials within the group in sequential order. The LeabraEpochGpData program available in the standard program library does this, but that is beyond the scope of this project.

Running the Network

Now you're finally ready to run the network on this CPT-AX task! Go back to the LeabraTrain process, do Init and Run on it (initialize the weights) and see what happens!?

You should see that it will run and run and never fully learn the task (it will stop training if the error goes to zero). There is some chance that it might get there just by virtue of a lucky set of trials -- try hitting Run again -- it should not stay at zero, and will keep running.

You probably want to turn off the network and trial output data views at some point -- just click off the display button on their respective control panels under the Network_0 view tab.

It shouldn't come as that much of a surprise that the network doesn't fully learn the task -- this task requires working memory, and this network hasn't got any! In fact, it is quite surprising that it is able to learn as well as it can. Turns out it can learn to use weight changes as a kind of fairly unreliable form of working memory. Plus, the default target of AX is highly frequent, so it can get pretty far by focusing a lot on that.

In the next and final segment, we give this network some working memory, and see if that helps: AXTut_PfcBg

Personal tools