Mackey-Glass attractor - signal generation

This code is available as examples/signal_generation.py

In this task, we will train a standard reservoir + readout to do autonomous signal generation. This is done by training the readout to perform one-step-ahead prediction on a teacher signal. After training, we then feed the reservoir its own prediction, and it then autonomously generates the signal for a certain time.

MDP only supports feed-forward flows, so how can we solve this? Oger provides a FeedbackFlow which is suited for precisely this case. The training and execute functions are overridden from the original Flow class. A FeedbackFlow is suitable for signal generation tasks both without and with external additional inputs. The example below uses no external inputs, so the flow generates its own output. After training, for every timestep, the output of the Flow is fed back as input again, such that it can autonomously generate the desired signal.

The train function of a FeedbackFlow will take the timeseries given as training argument and internally construct a new target signal which is shifted one timestep into the past, such that the flow is trained to do one step ahead prediction. During execution (i.e. by calling the execute function), the flow will first be teacher-forced using the first portion provided input signal, and starting from the timestep freerun_steps from the end of the signal, the flow is run in freerun mode, i.e. being fed back its own prediction. 

We start by preparing the dataset. In this case, there is no target signal, so the dataset consists of only a single time-series. We construct the target signal as the time-series shifted over one timestep to the left (we want one-step ahead prediction).

freerun_steps = 1000
training_sample_length = 5000
n_training_samples = 3
test_sample_length = 5000   
train_signals = Oger.datasets.mackey_glass(sample_len=training_sample_length, n_samples=n_training_samples)

Next, we create a reservoir with a little bit of leak rate and a readout node and combine these into a FeedbackFlow. Notice how during creation of the FeedbackFlow, we pass it the keyword argument of freerun_steps. This determines how many timesteps the FeedbackFlow will run in 'freerun mode'.

reservoir = Oger.nodes.LeakyReservoirNode(output_dim=400, leak_rate=0.8, input_scaling=.4, bias_scaling=.2, reset_states=False) 
readout = Oger.nodes.RidgeRegressionNode()
flow = Oger.nodes.FreerunFlow([reservoir, readout], freerun_steps=freerun_steps)

We instantiate an Optimizer for finding the best regularization constant for the readout. This is necessary to achieve a suitable robustness and stability of the generated model. 

gridsearch_parameters = {readout:{'ridge_param': 10 ** scipy.arange(-4, 0, .3)}}
loss_function = Oger.utils.timeslice(range(training_sample_length - freerun_steps, training_sample_length), Oger.utils.nrmse)
opt = Oger.evaluation.Optimizer(gridsearch_parameters, loss_function)

We run a gridsearch of the ridge parameter, providing the set of training signals as dataset, and using leave one out cross-validation.

opt.grid_search([[], train_signals], flow, cross_validate_function=Oger.evaluation.leave_one_out)

We then retrieve the optimal flow, train it on the full training set and evaluate its performance on the unseen test signals.

opt_flow = opt.get_optimal_flow(verbose=True)
opt_flow.train([[], train_signals])
freerun_output = opt_flow.execute(test_signals[0][0])

To check the results, we can plot an overlay of the signal generated by the reservoir, and the actual target signal:

pylab.plot(scipy.concatenate((test_signals[0][0][-2 * freerun_steps:])))
pylab.plot(scipy.concatenate((freerun_output[-2 * freerun_steps:])))