/*
* File name: NeuroTranslator.java (package eas.users.lukas.evolvableNeuroGPM)
* Author(s): lko
* Java version: 7.0
* Generation date: 11.04.2013 (18:19:44)
*
* (c) This file and the EAS (Easy Agent Simulation) framework containing it
* is protected by Creative Commons by-nc-sa license. Any altered or
* further developed versions of this file have to meet the agreements
* stated by the license conditions.
*
* In a nutshell
* -------------
* You are free:
* - to Share -- to copy, distribute and transmit the work
* - to Remix -- to adapt the work
*
* Under the following conditions:
* - Attribution -- You must attribute the work in the manner specified by the
* author or licensor (but not in any way that suggests that they endorse
* you or your use of the work).
* - Noncommercial -- You may not use this work for commercial purposes.
* - Share Alike -- If you alter, transform, or build upon this work, you may
* distribute the resulting work only under the same or a similar license to
* this one.
*
* + Detailed license conditions (Germany):
* http://creativecommons.org/licenses/by-nc-sa/3.0/de/
* + Detailed license conditions (unported):
* http://creativecommons.org/licenses/by-nc-sa/3.0/deed.en
*
* This header must be placed in the beginning of any version of this file.
*/
package eas.simulation.brain.neural;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import org.ejml.data.DenseMatrix64F;
import org.ejml.factory.LinearSolver;
import org.ejml.factory.LinearSolverFactory;
import eas.miscellaneous.StaticMethods;
import eas.simulation.brain.neural.functions.ActivationFunction;
import eas.simulation.brain.neural.functions.ActivationFunctionConstant;
import eas.simulation.brain.neural.functions.ActivationFunctionIdentity;
import eas.simulation.brain.neural.functions.ActivationFunctionLug;
import eas.simulation.brain.neural.functions.InversionNotSupportedException;
import eas.simulation.brain.neural.functions.TransitionFunction;
import eas.simulation.brain.neural.functions.TransitionFunctionWeightedSum;
import eas.startSetup.GlobalVariables;
import eas.startSetup.ParCollection;
/**
* Using the normal constructor to create an object of this class, the
* resulting translator is a universal translator:<BR>
* <BR>
* Neuron 0 - Bias (constant 1).<BR>
* Neuron 1 - Input for sequences (identity).<BR>
* Neuron 2 - Output for translator construction (identity).<BR>
* Link (1 -> 2) - weighted 1.
*
* @author lko
*/
public class OldNeuroTranslator extends GeneralNeuralNetwork {
private static final long serialVersionUID = 8242169489373650817L;
public static final int BIAS_NEURON_ID = 0;
public static final int INPUT_NEURON_ID = 1;
public static final int OUTPUT_NEURON_ID = 2;
/**
* A list of initial values for the neurons that the neurons are set to
* at the beginning of a translation.
*/
private ArrayList<Double> neuronStartValuesInputOrOutputOutput = null;
public OldNeuroTranslator() {
this(GlobalVariables.getPrematureParameters());
}
public OldNeuroTranslator(ParCollection params) {
super(params);
this.setStandardActFct(new ActivationFunctionLug());
super.addNeuron(BIAS_NEURON_ID, new ActivationFunctionConstant(1), new TransitionFunctionWeightedSum(), Neuron.HIDDEN_NEURON);
super.addNeuron(INPUT_NEURON_ID, new ActivationFunctionIdentity(), new TransitionFunctionWeightedSum(), Neuron.INPUT_NEURON);
super.addNeuron(OUTPUT_NEURON_ID, new ActivationFunctionIdentity(), new TransitionFunctionWeightedSum(), Neuron.OUTPUT_NEURON);
this.setAllowLinksFromOutputs(true);
this.setAllowLinksToInputs(false);
this.setAllowRecurrentLinks(true);
this.addLink(INPUT_NEURON_ID, OUTPUT_NEURON_ID, 1);
}
@Override
public int addNeuron(int neuronType) {
return super.addNeuron(neuronType);
}
@Override
public void removeNeuron(int neuronID) {
Neuron neu = this.getNeuron(neuronID);
if (neu.isInput() || neu.isOutput()) {
throw new RuntimeException("Cannot remove input or output neurons from translator (id: " + neuronID + ").");
}
super.removeNeuron(neuronID);
}
@Override
public int addNeuron(int neuronID, ActivationFunction actFctPhi, TransitionFunction trnFctSigma, int neuronType) {
if (neuronType == Neuron.INPUT_NEURON || neuronType == Neuron.INPUT_OUTPUT_NEURON || neuronType == Neuron.OUTPUT_NEURON) {
throw new RuntimeException("Cannot insert additional input or output neurons to translator (id: " + neuronID + ").");
}
return super.addNeuron(neuronID, actFctPhi, trnFctSigma, neuronType);
}
/**
* All neurons' net output values are set to the given (input or output) values.
* The input and ouput values are reset to 0.
* If the values are null or do not match the neuron count, all is set to 0.
*/
private void setNeuronsToStartValues() {
this.resetAllNeuronInputsAndOutputs();
if (this.neuronStartValuesInputOrOutputOutput != null
&& this.neuronStartValuesInputOrOutputOutput.size() == this.getNeuronCount()) {
this.setAllNeuronOutputsTo(this.neuronStartValuesInputOrOutputOutput);
}
}
private void storeCurrentNeuronValuesAsStartValues() {
this.neuronStartValuesInputOrOutputOutput = new ArrayList<Double>(this.getNeuronCount());
for (int i = 0; i < this.getNeuronCount(); i++) {
this.neuronStartValuesInputOrOutputOutput.add(this.getNeuron(i).getNetOutput());
}
}
private int maxNumOfNeurons = 10;
private int interpretLink(List<Double> sequence, OldNeuroTranslator transNeu, int i) {
double source;
double target;
double weight;
try { // This is just in case the genome is missing values at the end.
this.setInput(INPUT_NEURON_ID, sequence.get(i));
this.propagate();
// if (i < 10) if (this.getNeuronCount() > 3) StaticMethods.showImage(this.generateNeuroImage(700), "(" + this.getNeuronCount() + " neurons)");
source = this.getNeuron(OUTPUT_NEURON_ID).getNetOutput() * maxNumOfNeurons;
this.setInput(INPUT_NEURON_ID, sequence.get(i + 1));
this.propagate();
// if (i < 10) if (this.getNeuronCount() > 3) StaticMethods.showImage(this.generateNeuroImage(700), "(" + this.getNeuronCount() + " neurons)");
target = this.getNeuron(OUTPUT_NEURON_ID).getNetOutput() * maxNumOfNeurons;
this.setInput(INPUT_NEURON_ID, sequence.get(i + 2));
this.propagate();
// if (i < 10) if (this.getNeuronCount() > 3) StaticMethods.showImage(this.generateNeuroImage(700), "(" + this.getNeuronCount() + " neurons)");
weight = this.getNeuron(OUTPUT_NEURON_ID).getNetOutput();
if (Double.isNaN(source)) {
source = 0;
}
if (Double.isNaN(target)) {
target = 0;
}
if (Double.isNaN(weight)) {
weight = 0;
}
NeuralLinkDummyDouble link = new NeuralLinkDummyDouble(source, target, weight, Integer.MAX_VALUE);
transNeu.applyNeuralLinkSoft(link);
// if (i < 10) if (this.getNeuronCount() > 3) StaticMethods.showImage(transNeu.generateNeuroImage(700), "(" + transNeu.getNeuronCount() + " neurons)");
} catch (Exception e) {
this.getPars().logInfo("Link not inserted (this means probably that the sequence length is not divisible by 3): " + e.getMessage());
throw e;
}
return i;
}
public OldNeuroTranslator translateGenomeToTranslator(List<Double> sequence) {
// Preparations of neural network.
this.setForwardModeAndResetNetOutput(0, true);
OldNeuroTranslator transNeu = new OldNeuroTranslator(this.getPars());
this.setNeuronsToStartValues();
StaticMethods.showImage(this.generateNeuroImage(1000), "");
for (int i = 0; i < sequence.size(); i += 3) {
i = interpretLink(sequence, transNeu, i);
}
// Propagate until nothing changes anymore.
// double epsilon = 0.001;
// this.getInputNeuron().setInput(0);
// HashSet<Double> valuesOccurred = new HashSet<Double>();
// while (Math.abs(this.getOutputNeuron().getOutput()) > epsilon) {
// this.propagate();
// int size = valuesOccurred.size();
// valuesOccurred.add(this.getOutputNeuron().getOutput());
// if (size == valuesOccurred.size()) {
// break;
// }
// }
return transNeu;
}
@SuppressWarnings("unused")
private void findAndSetAppropriateStartNetOutputs() {
final int neuroCount = this.getNeuronCount();
double[][] freeMembers = new double[neuroCount - 2][1];
double[][] matrix = new double[neuroCount - 2][neuroCount - 2];
// Set net outputs of output neurons.
this.getNeuron(OUTPUT_NEURON_ID).setNetOutput(this.getNeuron(OUTPUT_NEURON_ID).getOutput());
// Set net output of bias neuron.
this.getNeuron(BIAS_NEURON_ID).setNetOutput(this.getNeuron(BIAS_NEURON_ID).getActFct().activationPhi(0));
for (int i = 2; i < neuroCount; i++) {
freeMembers[i - 2][0] = this.getNeuron(i).getNetOutput();
try {
freeMembers[i - 2][0] = this.getNeuron(i).getActFct().inverseActivationPhi(freeMembers[i - 2][0]);
} catch (InversionNotSupportedException e) {
throw new RuntimeException("All non-bias neurons should be invertible.");
}
}
for (int i = 0; i < neuroCount - 2; i++) {
freeMembers[i][0] -= this.getNeuron(BIAS_NEURON_ID).getNetOutput() * this.getWeight(BIAS_NEURON_ID, i + 2);
freeMembers[i][0] -= this.getNeuron(OUTPUT_NEURON_ID).getNetOutput() * this.getWeight(OUTPUT_NEURON_ID, i + 2);
matrix[i][0] = this.getWeight(1, i + 2);
for (int j = 3; j < neuroCount; j++) {
matrix[i][j - 2] = this.getWeight(j, i + 2);
}
}
DenseMatrix64F mx = this.solveLinearEquationSystem(matrix, freeMembers);
this.getNeuron(INPUT_NEURON_ID).setNetOutput(this.getNeuron(INPUT_NEURON_ID).getActFct().activationPhi(mx.get(0, 0)));
for (int i = 1; i < neuroCount - 2; i++) {
this.getNeuron(i + 2).setNetOutput(this.getNeuron(i + 2).getActFct().activationPhi(mx.get(i, 0)));
}
// StaticMethods.showImage(this.generateNeuroImage(600), "");
// try {Thread.sleep(1000000000);} catch (InterruptedException e) {}
}
public LinkedList<Double> translateReverseTranslatorToGenome() {
LinkedList<Double> genomeInput = new LinkedList<Double>();
LinkedList<NeuralLinkDummyDouble> givenOutput = this.getConstructionSoftLinkHistory();
ArrayList<Double> givenOutputSequence = new ArrayList<Double>(givenOutput.size());
// Generate sequence of doubles.
for (NeuralLinkDummyDouble n : givenOutput) {
givenOutputSequence.add(0, (double) n.getSourceID());
givenOutputSequence.add(0, (double) n.getTargetID());
givenOutputSequence.add(0, n.getWeight());
}
// Preparations of neural network.
this.setForwardModeAndResetNetOutput(0, false);
this.setOutput(OldNeuroTranslator.OUTPUT_NEURON_ID, givenOutputSequence.get(0));
// this.findAndSetAppropriateStartNetOutputs();
/**
* Traverse sequence and retranslate to sequence.
*/
for (int i = 0; i < givenOutputSequence.size(); i++) {
double value = givenOutputSequence.get(i);
this.setOutput(OldNeuroTranslator.OUTPUT_NEURON_ID, value);
this.propagateBackwards();
genomeInput.add(this.getNeuron(INPUT_NEURON_ID).getInput());
}
this.storeCurrentNeuronValuesAsStartValues();
return genomeInput;
}
public Neuron getOutputNeuron() {
return this.getNeuron(2);
}
public Neuron getInputNeuron() {
return this.getNeuron(1);
}
private double[][] freeMembers; // Only one column!
private double[][] matrix;
/**
* Propagates the net one step backwards by solving a system of linear
* equations. For now, only the version with neuron0 = bias, neuron1
* =input (no other incoming signals) and neuron2=output is implemented.
*
* @return The value of the input neuron.
*/
public double propagateBackwards() {
final int neuroCount = this.getNeuronCount();
this.generateLinearEquationSystem();
DenseMatrix64F mx = this.solveLinearEquationSystem(this.matrix, this.freeMembers);
// StaticMethods.showImage(this.generateNeuroImage(700), "(" + this.getNeuronCount() + " neurons)");
// try {Thread.sleep(1000000000);} catch (InterruptedException e) {}
// Set neurons at accurate values.
this.getNeuron(INPUT_NEURON_ID).setNetOutput(mx.get(2, 0));
try {
this.getNeuron(INPUT_NEURON_ID).setInput(this.getNeuron(INPUT_NEURON_ID).getActFct().inverseActivationPhi(mx.get(2, 0)));
} catch (InversionNotSupportedException e) {
throw new RuntimeException("All non-bias neurons should be invertible.");
}
for (int j = 0; j < neuroCount; j++) {
this.getNeuron(j).setNetOutput(mx.get(j, 0));
}
return this.getNeuron(INPUT_NEURON_ID).getInput();
}
/**
* Generates the system of linear equations required to set the current
* state of the neural network one step backwards in time.<BR>
* <BR>
* The system is stored in class variables that should never be touched
* from outside this method.
*/
private void generateLinearEquationSystem() {
final int neuroCount = this.getNeuronCount();
this.freeMembers = new double[neuroCount - 2][1];
this.matrix = new double[neuroCount - 2][neuroCount];
// Set net outputs of output neurons.
this.getNeuron(OUTPUT_NEURON_ID).setNetOutput(this.getNeuron(OUTPUT_NEURON_ID).getOutput());
// Set net output of bias neuron.
this.getNeuron(BIAS_NEURON_ID).setNetOutput(this.getNeuron(BIAS_NEURON_ID).getActFct().activationPhi(0));
for (int i = 2; i < neuroCount; i++) {
freeMembers[i - 2][0] = this.getNeuron(i).getNetOutput();
try {
freeMembers[i - 2][0] = this.getNeuron(i).getActFct().inverseActivationPhi(freeMembers[i - 2][0]);
} catch (InversionNotSupportedException e) {
throw new RuntimeException("All non-bias neurons should be invertible.");
}
}
for (int i = 0; i < neuroCount - 2; i++) {
for (int j = 0; j < i + 2; j++) {
freeMembers[i][0] -= this.getNeuron(j).getNetOutput() * this.getWeight(j, i + 2);
}
for (int j = i + 2; j < neuroCount; j++) {
matrix[i][j] = this.getWeight(j, i + 2);
}
}
}
private DenseMatrix64F solveLinearEquationSystem(double[][] matrix, double[][] freeMembers) {
DenseMatrix64F mA = new DenseMatrix64F(matrix);
DenseMatrix64F mb = new DenseMatrix64F(freeMembers);
DenseMatrix64F mx = new DenseMatrix64F(mA.numCols, 1);
LinearSolver<DenseMatrix64F> solver = LinearSolverFactory.leastSquaresQrPivot(true, false);
solver.setA(mA);
solver.solve(mb, mx);
// System.out.println(mA);
// System.out.println(mb);
// System.out.println(mx);
return mx;
}
}