I tried to design an interface for the trainer that doesn’t deviate from the original trainer interface designed by karpathy‘s convnet.js library.

We can reuse the typical structure of the library to setup our neural network in javascript:


// setup neural network:
var layer_defs, net;
var layer_defs = [];
layer_defs.push({type:'input', out_sx:1, out_sy:1, out_depth:1});
layer_defs.push({type:'fc', num_neurons:12, activation:'relu'});
layer_defs.push({type:'fc', num_neurons:8, activation:'sigmoid'});
layer_defs.push({type:'regression', num_neurons:1});
net = new convnetjs.Net();
net.makeLayers(layer_defs);

Here, we define a neural network with 1 input signal, a fully connected ‘relu’ (rectified linear unit layer of 12 neurons, a fully connected sigmoid layer of 8 neurons, and a regression based output layer to calculate a real number output.

Our network looks like:

network visualisation

Next, we would like to construct a trainer to train the network, say to do a simple data fitting exercise Y = f(X), in the datafit demo example.


var opt = {
  // we want 100 random networks to test out
  population_size : 100,
  // each weight will get mutated with 10% prob
  mutation_rate : 0.10,
  // we will keep the best 20% of the networks
  elite_percentage : 0.2,
  // add noise with stdev 0.02 during mutation
  mutation_size : 0.02,
  // stop if network achieves better score
  target_fitness : -0.03
}
var trainer = new convnetjs.GATrainer(net, opt);

 

When we want to train the network for a generation, we need to first define a fitnessFunction() in js that will take a network, get it to perform a task, fit some data, or whatnot, and return a ‘fitness’ score on how that network performed so we can rank it. Please note that the fitness score must be non-positive, and the more negative the score, the worse the network is doing its job. For the case of the data fitting exercise, we can just set the fitness as the negative square error of the network output vs training samples. we can simply use the train function:


// evaluates the fitness of using a nn to fit (x, y) data
var fitnessFunction = function(net) {
  var N = trainingData.length;
  var i;
  var netx = new convnetjs.Vol(1,1,1);
  var error = 0;
  var sum_sq_error = 0;
    for (i = 0; i < N; i++) {
    netx.w[0] = trainingData[i];
    var a = net.forward(netx);
    var y = a.w[0];
    error = (y - trainingLabel[i]);
    sum_sq_error += (error * error);
  }
  return (-1 * sum_sq_error / N);
};

After defining the fitnessFunction(), we can simply call the line:

fitness = trainer.train(fitnessFunction);

to perform one generation of conventional neuroevolution. After the train() function is performed:

  1. the network passed to the trainer will be set to the best of 100 networks attempted.
  2. the fitness value of the best network will be returned
  3. the population of 100 networks will have evolved and have been sorted

So if the train() function is run again, it will perform evolution again for another generation.

A useful way to use this trainer is that we can actually then use it in conjunction with the existing trainers in the convnet.js library. A useful way to come up with the initial network, is to use CNE for a few generations to come up with a good initial network choice first, and then use SGD trainers to fine tune.

Citation

If you find this work useful, please cite it as:

@article{ha2015cne,
  title   = "conventional neuroevolution (CNE) trainer (Part II)",
  author  = "Ha, David",
  journal = "blog.otoro.net",
  year    = "2015",
  url     = "http://blog.otoro.net/2015/02/06/conventional-neuroevolution-cne-trainer-part-ii/"
}