/* @(#)learn.c 1.4 12/9/92 */ /* * File : learn.C * Simulation of primate visual/motor conditional learning. * * Andrew H. Fagg * Center for Neural Engineering * Computer Science Department * University of Southern California * Henry Salvatori #300 * Los Angeles, CA 90089-0781 * * ahfagg@pollux.usc.edu * * For a description of the model, see : * Fagg, Andrew H., Arbib, Michael A., "A model of primate visual/motor * conditional learning", Journal of Adpative Behavior, Summer 1992, * pp. 3 - 37 * * For a description of how to use this model, see the file README * * Implementation in NSL (Neural Simulation Language), see * Weitzenfeld, A., "NSL: Neural simulation language, version 2.1", * Technical Report 91-05. Lost Angeles: Center for Neural Engineering, * University of Southern California. * * HISTORY * Date Author Description * -------- ----------------- -------------------------------- * 12/05/92 Andrew H. Fagg Doc update. * 01/15/92 Andrew H. Fagg Original. * */ #include "nsl_include.h" #include "learn.h" /* Configuration file. */ /* * void normal_row(nsl_matrix& mat) * * Normalize the rows within . If the global variable * is 1, then L1-normalization is used. Otherwise, * L2-normalization is used. * */ void normal_row(nsl_matrix& mat, int norm_mode) { int i,j; int a,b; float count; /* Get size of matrix */ a = mat.get_xn(); b = mat.get_yn(); /* Which type of normalization? */ if(norm_mode == 1) // L1_norm_mode.elem() == 1.0) { /* Loop through each row. */ for(j = 0; j < a; ++j) { count = 0.0; /* Sum elements in row j. */ for(i = 0; i < b; ++i) count += fabs(mat[j][i]); /* If too small, then don't normalize */ if(count > 0.0001) /* Apply normalization factor. */ for(i = 0; i < b; ++i) mat[j][i] = mat[j][i]/count; } } else /* L2-norm. */ { /* Loop through each row. */ for(j = 0; j < a; ++j) { count = 0.0; /* Sum elements in the row. */ for(i = 0; i < b; ++i) count += mat[j][i]*mat[j][i]; // square count = sqrt(count); /* If too small, then don't normalize. */ if(count > 0.0001) /* Apply normalization factor. */ for(i = 0; i < b; ++i) mat[j][i] = mat[j][i]/count; } } } /* * void normal_col(nsl_matrix& mat) * * Normalize the cols within . If the global variable * is 1, then L1-normalization is used. Otherwise, * L2-normalization is used. * */ void normal_col(nsl_matrix& mat, int norm_mode) { int i,j; int a,b; float count; /* Get size of matrix */ a = mat.get_xn(); b = mat.get_yn(); /* Check type of normalization. */ if(norm_mode == 1) // L1_norm_mode.elem() == 1.0) { /* Loop through each column. */ for(i = 0; i < b; ++i) { count = 0.0; /* Sum elements in column i. */ for(j = 0; j < a; ++j) count += fabs(mat[j][i]); /* If too small, then don't normalize */ if(count > 0.0001) /* Apply normalization factor. */ for(j = 0; j < a; ++j) mat[j][i] = mat[j][i]/count; } } else /* L2-norm. */ { /* Loop through each column. */ for(i = 0; i < b; ++i) { count = 0.0; /* Sum elements in column i. */ for(j = 0; j < a; ++j) count += mat[j][i]*mat[j][i]; // Square count = sqrt(count); /* If too small, then don't normalize */ if(count > 0.0001) /* Apply normalization factor. */ for(j = 0; j < a; ++j) mat[j][i] = mat[j][i]/count; } } } /* * void select_random(nsl_vector& vec, float prob) * * Set the elements of to 0s or 1s. The probability of * a 1 in any particular element is . * */ void select_random(nsl_vector& vec, float prob) { int i,r; int init; int limit; init = vec.get_x0(); limit = vec.get_x1(); for(i = init; i <= limit; ++i) { r = (nslRandom2(0.0,1.0) < prob); vec[i] = r; } } /* * overload void select_random(nsl_matrix& mat, float prob) * * Set the elements of to 0s or 1s. The probability of * a 1 in any particular element is . * */ void select_random(nsl_matrix& mat, float prob) { int i, j; float s,r; int x0, x1; int y0, y1; x0 = mat.get_x0(); x1 = mat.get_x1(); y0 = mat.get_y0(); y1 = mat.get_y1(); for(i = x0; i <= x1; ++i) for(j = y0; j <= y1; ++j) { r = nslRandom2(0.0,1.0); s = (r < prob); mat[i][j] = s; } } Feature::Feature(nsl_string str, NslModule* parent) : NslModule(str,parent), inputs("inputs", this), voting_contribution("voting_contribution", this), // Output of voting units. threshold_v("threshold_v",this), feature("feature", this), // Firing rate factor("factor",this), inputsf("inputsf", this), feature_mem("feature_mem", this), // Membrane potential threshold_f("threshold_f", this), // Threshold u_feature("u_feature", this), // Integration time constant lrate_f("lrate_f",this), // Feature w_in_feature("w_in_feature",this), // Change in feature detector weights dw_in_feature("dw_in_feature",this), // Matrix of 1s and 0s: determines nslConnectivity w_in_feature_mask("w_in_feature_mask",this), input_weight_bias("input_weight_bias",this), // Constant value added to initial weights w_in_feature_probability("w_in_feature_probability",this), // Probability of nslConnection negative_factor_f("negative_factor_f",this), // Scale factor for receiving negative reinforcement L1_norm_mode("L1_norm_mode",this) // Type of normalization applied to weights { } void Feature::memAlloc(int num_inputs,int num_columns) { inputs.memAlloc(num_inputs); inputsf.memAlloc(num_inputs); voting_contribution.memAlloc(num_columns); feature.memAlloc(num_columns); feature_mem.memAlloc(num_columns); w_in_feature.memAlloc(num_inputs,num_columns); dw_in_feature.memAlloc(num_inputs,num_columns); w_in_feature_mask.memAlloc(num_inputs,num_columns); } void Feature::initSys() { nslRandom(w_in_feature,-1.0,1.0); select_random(w_in_feature_mask, w_in_feature_probability.elem()); // Mask // Adjust weight values w_in_feature = (w_in_feature/2.0 + 0.5 + input_weight_bias) ^ w_in_feature_mask; // Normalize weights normal_col(w_in_feature, L1_norm_mode.elem()); } void Feature::initTrain() { inputsf = inputs; feature_mem = -threshold_v; feature = 0; } void Feature::simTrain() { nslDiff(feature_mem, u_feature, -feature_mem - threshold_f + NSLprod2(w_in_feature,inputs)); // mat_mult_col_vec(w_in_feature, inputs)); #ifdef LIMITED_ACTIVITY_FLAG feature = NSLsat(feature_mem, 0, 1, 0, 1); #else feature = NSLramp(feature_mem); #endif } /* * * reward: Applies positive reward to the network weights. * * Update the network weights according to the reinforcement signal . * */ void Feature::endTrain() { float f_factor; // Apply correction factor if reinforcement is negative. Discussed in paper. if(factor.elem() < 0.0) f_factor = factor.elem() * negative_factor_f.elem(); else f_factor = factor.elem(); dw_in_feature = f_factor * lrate_f * (NSLprod2(voting_contribution,inputs) // vec_mult_vec_trans ^ w_in_feature_mask); // Keep weights within [0,1] range w_in_feature = NSLramp(w_in_feature + dw_in_feature); // Normalize weights normal_col(w_in_feature,L1_norm_mode.elem()); } /* * RUN_MODULE(NOISE) * * Noise vector dynamics. The change in noise values * is intended to be slow relative to the time constant of the * feature detector and voting units. * */ Noise::Noise(nsl_string str, nsl_model* parent) : nsl_model(str,parent), noise("noise", this), gain("gain", this), change_probability("change_probability", this) { } void Noise::memAlloc(int num) { noise.memAlloc(num); } void Noise::initExec() { nslRandom(noise,-1.0,1.0); // Intialize noise vectors noise = gain * noise; } void Noise::simExec() { // Roll die to see if change is appropriate if(nslRandom2(0.0,1.0) < change_probability.elem()) { nslRandom(noise,-1.0,1.0); noise = gain * noise; } } /* * RUN_MODULE(THRESHOLD_V) * * Voting unit threshold decay. * */ Threshold::Threshold(nsl_string str, NslModule* parent) : NslModule(str,parent), s("s",this), threshold_v("threshold_v", this), // Threshold (varying over time) u_threshold_v("u_threshold_v", this),// Time constant of threshold change. init_threshold_v("init_threshold_v", this) { } void Threshold::initTrain() { threshold_v = init_threshold_v; } void Threshold::simTrain() { if(s.elem() <= 0.0) nslDiff(threshold_v,u_threshold_v, -threshold_v); } /* * RUN_MODULE(VOTING) * * Voting unit dynamics. * */ Vote::Vote(nsl_string str, NslModule* parent) : NslModule(str,parent), noise("noise",this), threshold_v("threshold_v",this), feature("feature",this), voting("voting", this), voting_contribution("voting_contribution", this), // Output of voting units voting_participation("voting_participation", this), // Binary pattern of which display_participation_mode("display_participation_mode",this), // If = 1, then display the participation vector at the end of each trial voting_mem("voting_mem", this), u_voting("u_voting", this), voting_contribution_mode("voting_contribution_mode", this), // Mapping from voting unit activity to voting unit output voting_contribution_scale("voting_contribution_scale", this), // Used for some types of mappings init_threshold_v("init_threshold_v", this) { } void Vote::memAlloc(int num_columns) { _num_columns = num_columns; noise.memAlloc(num_columns); feature.memAlloc(num_columns); voting.memAlloc(num_columns); voting_contribution.memAlloc(num_columns); voting_participation.memAlloc(num_columns); voting_mem.memAlloc(num_columns); } void Vote::initSys() { noise.initExec(); } void Vote::initTrain() { voting_mem = -init_threshold_v; voting = 0; } void Vote::simTrain() { noise.simExec(); nslDiff(voting_mem, u_voting, -voting_mem - threshold_v + feature + noise.noise); #ifdef LIMITED_VOTING_ACTIVITY_FLAG voting = NSLsat(voting_mem, 0, 1, 0, 1); #else voting = NSLramp(voting_mem); #endif voting_participation = NSLstep(voting); // Compute the effective activity of the voting units to be used in the hebbian // association. Several modes are provided for experimentation. if (voting_contribution_mode == LINEAR) voting_contribution = voting; else if (voting_contribution_mode == BINARY) voting_contribution = voting_participation; else if (voting_contribution_mode == COMPRESSED_LINEAR) voting_contribution = NSLsat(voting,0.0, voting_contribution_scale.elem(),0.0, 1.0); else if (voting_contribution_mode == JUMP_LINEAR) voting_contribution = NSLsat( NSLramp(voting, 0.0, 0.0,voting_contribution_scale.elem())); else cmd_out("Unknown voting_contribution_mode: ",voting_contribution_scale.elem()); } void Vote::endTrain() { // cmd_out("Average Voting: ",voting.sum()/_num_columns); if (display_participation_mode == 1) voting_participation.nslPrint(); } /* * RUN_MODULE(MOTOR) * * Motor selection unit dynamics. * */ Motor::Motor(nsl_string str, NslModule* parent) : NslModule(str,parent), noise("noise",this), s("s",this), motor("motor", this), voting("voting",this), voting_contribution("voting_contribution", this), // Output of voting units above_thresh_num("above_thresh_num",this), stable_num("stable_num",this), winner("winner",this), factor("factor",this), // Motor program selection units motor_mem("motor_mem", this), dmotor_mem("dmotor_mem", this), // Change in membrane potential motor_inputs("motor_inputs", this), u_motor("u_motor", this), threshold_m("threshold_m",this), voting_factor("voting_factor",this), // Scales input activity from voting units lrate_v("lrate_v",this), // Voting // voting/motor weights w_vote_motor("w_vote_motor",this), dw_vote_motor("dw_vote_motor",this), w_vote_motor_mask("w_vote_motor_mask",this), voting_weight_bias("voting_weight_bias",this), w_vote_motor_probability("w_vote_motor_probability",this), stable_detect_threshold("stable_detect_threshold",this), // Changes in activity must be less than threshold for equilibrium to be reached L1_norm_mode("L1_norm_mode",this), // Type of normalization applied to weights normalize_input_mode("normalize_input_mode",this) // Determines if w_vote_motor is input or output normailized { } void Motor::memAlloc(int num_columns,int num_choices) { _num_columns = num_columns; _num_choices = num_choices; noise.memAlloc(num_choices); motor.memAlloc(num_choices); voting.memAlloc(num_columns); voting_contribution.memAlloc(num_columns); motor_mem.memAlloc(num_choices); dmotor_mem.memAlloc(num_choices); motor_inputs.memAlloc(num_choices); w_vote_motor.memAlloc(num_columns,num_choices); dw_vote_motor.memAlloc(num_columns,num_choices); w_vote_motor_mask.memAlloc(num_columns,num_choices); } void Motor::initSys() { noise.initExec(); winner = 0; nslRandom(w_vote_motor,-1.0,1.0); select_random(w_vote_motor_mask, w_vote_motor_probability.elem()); w_vote_motor = (w_vote_motor/2.0 + 0.5 + voting_weight_bias) ^ w_vote_motor_mask; // Normalize as appropriate if (normalize_input_mode.elem() == 1.0) normal_col(w_vote_motor,L1_norm_mode.elem()); else normal_row(w_vote_motor,L1_norm_mode.elem()); } void Motor::initTrain() { motor_mem = -threshold_m; motor = 0; above_thresh_num = 0; stable_num = 0; } void Motor::simTrain() { noise.simExec(); motor_inputs = NSLprod2(w_vote_motor, voting)/_num_columns; // mat_mult_col_vec dmotor_mem = -motor_mem - threshold_m + voting_factor * motor_inputs - s + motor + noise.noise; nslDiff(motor_mem, u_motor,dmotor_mem); motor = NSLstep(motor_mem); above_thresh_num = 0; stable_num = 0; // Check the status of each motor program selection unit. for (int i = 0; i < _num_choices; ++i) { if (motor.elem(i) > 0.0) // Above threshold { above_thresh_num = above_thresh_num + 1; winner = i; } // No change in activity. - used only in standard WTA. if(fabs(dmotor_mem.elem(i)) < stable_detect_threshold.elem()) stable_num = stable_num + 1; } } /* * * Punish: Applies negative reward to the network weights. * * Update the network weights according to the reinforcement signal . * */ void Motor::endTrain() { dw_vote_motor = factor * lrate_v * (NSLprod2(motor, voting_contribution) ^ w_vote_motor_mask); // vec_mult_vec_trans // Keep weights within [0,1] range w_vote_motor = NSLramp(w_vote_motor + dw_vote_motor); // Normalize weights, depending upon mode if(normalize_input_mode.elem() == 1.0) normal_col(w_vote_motor,L1_norm_mode.elem()); else normal_row(w_vote_motor,L1_norm_mode.elem()); } /* * RUN_MODULE(S_COMPUTE) * * Inhibitory unit dynamics (for Winner Take All implementation). * */ WTA::WTA(nsl_string str, NslModule* parent) : NslModule(str,parent), motor("motor",this), s("s",this), s_mem("s_mem",this), u_s("u_s",this), threshold_s("threshold_s",this) { } void WTA::memAlloc(int num_choices) { motor.memAlloc(num_choices); } void WTA::initTrain() { s_mem = 0; s = 0; } void WTA::simTrain() { nslDiff(s_mem, u_s, - s_mem - threshold_s + motor.sum()); s = NSLramp(s_mem); } CondLearn::CondLearn(char* str, NslModule* parent) : NslModule(str,parent), inpat("inpat", this), // Current output input("input", this), // Current vector of inputs feature("feature",this), threshold("threshold",this), vote("vote",this), motor("motor",this), wta("wta",this), output("output", this), // Current output new_read("new_read",this), above_thresh_num("above_thresh_num",this), stable_num("stable_num",this), winner("winner",this), voting("voting",this), factor("factor",this), collect("collect",this), display_set_unit_flag("display_set_unit_flag",this), // If true, then display set unit activation first_pole_mode("first_pole_mode",this), // Winner-take-all parameter normalize_input_mode("normalize_input_mode",this), // Determines if W_vote_motor is input or output normailized special_punish_mode("special_punish_mode",this), // If no MPSUs are active at time of punishment, then reward to get voting activity up seed("seed",this) { } void CondLearn::memAlloc(int inSize, int num_columns, int num_choices) { _num_columns = num_columns; _num_choices = num_choices; input.memAlloc(inSize); feature.memAlloc(inSize,num_columns); vote.memAlloc(num_columns); motor.memAlloc(num_columns,num_choices); wta.memAlloc(num_choices); voting.memAlloc(num_columns); } void CondLearn::initSys() { nslConnect(feature.feature,vote.feature); nslConnect(vote.voting,motor.voting); nslConnect(threshold.threshold_v,vote.threshold_v); nslConnect(motor.motor,wta.motor); nslConnect(wta.s,motor.s); nslConnect(vote.voting_contribution,motor.voting_contribution); nslConnect(vote.voting_contribution,feature.voting_contribution); nslConnect(factor,feature.factor); nslConnect(factor,motor.factor); nslRelabel(input,feature.inputs); nslRelabel(motor.stable_num,stable_num); nslRelabel(motor.above_thresh_num,above_thresh_num); nslRelabel(motor.winner,winner); nslRelabel(vote.voting,voting); int s = seed.get_value(); // long #ifndef NSL_PC srand48(s); #else srand(s); #endif } void CondLearn::initTrain() { collect_mode = collect.elem(); if(!collect_mode) cmd_out("Cleared"); timer_flag = 0; } /* * RUN_MODULE(check_completion) * * Checks the status of the network to see if a decision has been * made or must be forced. * This is done by first counting the number of active Motor Program * Selection Units (MPSUs), and the number of stable MPSUs (only * small changes in activity). * If the network is in standard WTA mode, then exactly one unit * must be active, and all units must be stable. In first-past-the-pole * mode, the stability condition is removed. So if the network satisfies * this condition, then timer_flag is set to 1, the network emits a * motor program, the weights are updated, and the next stimulus is selected. * If the network does not satisfy these conditions, but times out, * then the NO-GO case is forced. If no MPSUs are active, timer_flag is * set to -1. If more than one is active, then timer_flag is 1. * In either case, the output is forced, the weights are updated, and * the next stimulus is selected. * */ void CondLearn::simTrain() { collect_mode = collect.elem(); // Exactly one unit active and in first pole mode or all units are stable. if(above_thresh_num.elem() == 1 && (stable_num.elem() == _num_choices || first_pole_mode.elem() != 0.0)) { timer_flag = 1; if(!collect_mode) cmd_out("Done!!"); if(collect_mode) { // Update weights and set up next trial punish_reward_func(winner.elem()); nslBreakSim(); } } } void CondLearn::endTrain() // Timeout: NO-GO case { if (timer_flag == 1) return; // On completion, set to 1 if at least 1 MPSU is active, -1 if none collect_mode = collect.elem(); if(above_thresh_num.elem() == 0) timer_flag = -1; // Signal no activity at MPSU level. else timer_flag = 1; if(!collect_mode) { NSLoutput("\nNo-pick no-go!"); if (timer_flag == 1) NSLoutput("\nMultiple picked!"); } else { // Update weights and set up next trial NSLoutput("\nNo-pick no-go!"); punish_reward_func(NO_GO_CASE); } } /* * void log_and_pick_new(int winner) * * Log the current status and pick the new stimulus to present * to the system. * */ void CondLearn::punish_reward_func(int win) { // If no MPSUs are active, then modify the reward signal so it is positive. // This is done so that some column will be more likely to be active next // time th esame input is give. NSLoutput("\n",NSL_SYSTEM->getTrainTimeStep()); NSLoutput("\n",NSL_SYSTEM->getTrainEpochStep()); // Epoch number NSLoutput(":"); NSLoutput(" p",inpat.elem()); NSLoutput(" s",output.elem()); NSLoutput(" w",win); factor = 1.0; if(output == win) { new_read = 1; if(!collect_mode) cmd_out("Rewarded"); NSLoutput(" + "); } else { new_read = 0; if (timer_flag != -1) factor = -1.0; if(!collect_mode) cmd_out("Punished"); NSLoutput(" - "); } NSLoutput(voting.sum()/_num_columns); } TrainFile::TrainFile(char* str, NslModule* parent) : NslModule(str,parent), input("input", this), // Current vector of inputs output("output", this), // Current output inpat("inpat", this), // Current output pInput("pInput", this), // Set of all input patterns pOutput("pOutput", this), // Set of expected outputs inSize("inSize",this), outSize("outSize",this), numPats("numPats",this), pName("pName",this), new_read("new_read",this), repeat("repeat",this) // If true, then repeat stimulus when response is incorrect { } /* * void bp_load_input_cmd() * * User command ("user getpat"). * Initialize the pattern matrix. If patterns have not already been * loaded, then prompt for a file name, otherwise prompt to see if * the user wants to load another file. * */ void TrainFile::readFile() { int i,iSize,oSize,nPats; int val; nsl_file tfile; // nsl_file tfile("a1.dat"); // if (tfile.open_file(NSL_INPUT) == -1) { if (tfile.new_file(pName,NSL_INPUT) == -1) { cmd_out("Bad file name"); tfile.close_file(); return; } else { tfile.read(nPats); cmd_out("num_pats = ",nPats); numPats = nPats; tfile.read(oSize); outSize = oSize; if(oSize != 1) { cmd_out("Wrong number of output units."); nPats = 0; tfile.close_file(); return; } tfile.read(iSize); // Check number of input units. inSize = iSize; if(iSize != inSize) { cmd_out("Wrong number of input units."); cmd_out(iSize); nPats = 0; tfile.close_file(); return; } input.memAlloc(iSize); pInput.memAlloc(nPats,iSize); pOutput.memAlloc(nPats); for(int pat = 0; pat < nPats; ++pat) { for(i = 0; i < inSize; ++i) { tfile.read(val); pInput.elem(pat, i) = val; } tfile.read(val); pOutput.elem(pat) = val; } cmd_out("patterns read: ", nPats); patterns_loaded_flag = 1; tfile.close_file(); } } /* * void pick_new_pattern(int new_flag) * * Randomly select a new pattern to present to the network, and * then clear the network in preparation for the next trial. * A new pattern is selected only when is TRUE, otherwise * the pattern from the last trial is used. * * Initialize the weights and network status. * */ void TrainFile::initTrain() { if(!patterns_loaded_flag) { cmd_out("Must load patterns before simulation initialization and execution"); exit(0); } if (NSL_SYSTEM->getTrainEpochStep() == 0) repeat_mode = 0; else repeat_mode = repeat.elem(); if (repeat_mode == 0 || new_read == 1) { int r = rand(); // rand()/100 int mod = (int) numPats.elem(); current_pat = r % mod; inpat = current_pat; // extract_row_vector(pInput, current_pat, input); input = pInput[current_pat]; } output = pOutput[current_pat]; } CondLearnModel::CondLearnModel() : tf("trainFile",this), cl("condLearn",this), NslModel("CondLearnModel") { makeConn(); } void CondLearnModel::makeConn() { nslConnect(tf.inpat,cl.inpat); nslConnect(tf.input,cl.input); nslConnect(tf.output,cl.output); nslConnect(cl.new_read,tf.new_read); } void CondLearnModel::initSys() { tf.readFile(); // bp_load_input_cmd(); // load patterns NslInt0 inSizeB = (NslInt0&) tf.getDataRef("inSize"); int inSize = inSizeB.getValue(); // int inSize = tf.getValue("inSize"); int num_columns = 30; int num_choices = 4; cl.memAlloc(inSize,num_columns,num_choices); } AslSchemaModel _CondLearnModel("CondLearnModel");