/*
* File name: Neuron.java (package eas.simulation.brain.neural)
* Author(s): Lukas König
* Java version: 6.0
* Generation date: 29.07.2011 (15:04:04)
*
* (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.awt.image.BufferedImage;
import java.io.Serializable;
import java.util.LinkedList;
import eas.math.geometry.Vector2D;
import eas.simulation.brain.neural.functions.ActivationFunction;
import eas.simulation.brain.neural.functions.ActivationFunctionConstant;
import eas.simulation.brain.neural.functions.TransitionFunction;
import eas.simulation.brain.neural.functions.TransitionFunctionWeightedSum;
/**
* @author Lukas König
*
*/
public class Neuron implements Serializable {
private static final long serialVersionUID = 224664994130046667L;
public static final int INPUT_NEURON = 0;
public static final int HIDDEN_NEURON = 1;
public static final int OUTPUT_NEURON = 2;
public static final int INPUT_OUTPUT_NEURON = 3;
private int nrnType;
public void setPaintBlueBorder(boolean paintBlueBorder) {
this.getActFct().setPaintBlueBorder(paintBlueBorder);
}
/**
* The neuron's activation function. Activation functions are constrained
* to return values from [0, 1]. [TODO: ???]
*/
private ActivationFunction actFct;
/**
* The neuron's transition function - standard: weighted sum.
*/
private TransitionFunction trnFct;
private int id;
/**
* The output generated by the neuron as input for the next neurons
* (or in backwards mode for the previous neurons).
*/
private double netOutput = 0.0;
/**
* Only available for input neurons.
*/
private double input = 0.0;
/**
* Only available for output neurons.
*/
private double output = 0.0;
/**
* List of all incoming links. Kept fresh all the time.
*/
private LinkedList<NeuralLink> incomingLinks;
// /**
// * List of all outgoing links. Only needed in backward mode and NOT
// * refreshed automatically. Use calculateOutgoingLinks() to refresh this
// * list.
// */
// private LinkedList<NeuralLink> outgoingLinks = null;
private GeneralNeuralNetwork network;
/**
* Constructs a new Neuron with standard transition function, i.e.,
* weighted sum.
*
* @param activFct The activation function.
*/
protected Neuron(int neuronID, ActivationFunction activFct, int neuronType, GeneralNeuralNetwork net) {
this(neuronID, activFct, new TransitionFunctionWeightedSum(), neuronType, net);
}
public Neuron(int neuronID, ActivationFunction activFct, TransitionFunction transFct, int neuronType, GeneralNeuralNetwork net) {
this.actFct = activFct;
this.trnFct = transFct;
this.nrnType = neuronType;
this.id = neuronID;
this.network = net;
this.incomingLinks = new LinkedList<NeuralLink>();
}
private boolean forwardMode = true;
/**
* Sets the forward mode and resets the net output to value.
*
* @param forwardMode The forward mode.
* @param value The value to set to.
*/
public void setForwardModeAndResetNetOutput(double value, boolean forwardMode) {
this.forwardMode = forwardMode;
this.netOutput = value;
}
public void computeAndStoreNetOutput() {
if (!forwardMode) {
throw new RuntimeException("Neuron is not in forward mode, no forward calculations allowed.");
}
LinkedList<Vector2D> inputSignals = new LinkedList<Vector2D>();
if (this.isInput()) {
inputSignals.add(new Vector2D(input, 1));
}
for (NeuralLink link : incomingLinks) {
Vector2D tuple = new Vector2D(
this.network.getNeuron(link.getSourceNeuronID()).netOutput,
link.getWeight());
inputSignals.add(tuple);
}
double netInput = this.trnFct.inputSigma(inputSignals);
double netOutput = this.actFct.activationPhi(netInput);
this.netOutput = netOutput;
}
// /**
// * Note that the NeuralLink is used in reverse here, meaning that the
// * stored "sourceID" is in fact the target ID (source in the backwards
// * sense).
// *
// * @return
// */
// public LinkedList<NeuralLink> calculateOutgoingLinks() {
// if (this.forwardMode) {
// this.network.getPars().logWarning("Why do you need an outgoing link in forward mode?");
// }
//
// this.outgoingLinks = new LinkedList<NeuralLink>();
//
// HashMap<Integer, Neuron> neurons = this.network.getNeurons();
//
// for (Neuron n : neurons.values()) {
// LinkedList<NeuralLink> incomingForN = n.incomingLinks;
// for (NeuralLink link : incomingForN) {
// if (link.getSourceNeuronID() == this.getId()) {
// outgoingLinks.add(new NeuralLink(n.getId(), link.getWeight()));
// }
// }
// }
//
// return outgoingLinks;
// }
// /**
// * This is the inverse method to computeAndStoreNetOutput(). It calculates
// * based on the backwards incoming (or better outgoing) values of the
// * outgoing links the net input for the former neurons with ids smaller than
// * this.getId(). This input is stored in this.output meaning that
// * the neuron can only be used in one mode at a time and the two methods
// * should not be mixed!
// */
// public void computeAndStoreInverseNetOutput() {
// if (forwardMode) {
// throw new RuntimeException("Neuron is in forward mode, no backward calculations allowed.");
// }
//
// this.calculateOutgoingLinks();
//
// LinkedList<Vector2D> outputSignals = new LinkedList<Vector2D>();
//
// if (this.isOutput()) {
// outputSignals.add(new Vector2D(output, 1));
// }
//
// for (NeuralLink link : outgoingLinks) {
// Vector2D tuple = new Vector2D(
// this.network.getNeuron(link.getSourceNeuronID()).getNetOutput(),
// link.getWeight());
//
//
//
// outputSignals.add(tuple);
// }
//
// double netInput = this.trnFct.inputSigma(outputSignals);
// double netOutput = this.actFct.activationPhi(netInput);
// this.netOutput = netOutput;
// }
public boolean isInput() {
return this.nrnType == INPUT_NEURON || this.nrnType == INPUT_OUTPUT_NEURON;
}
public boolean isHidden() {
return this.nrnType == HIDDEN_NEURON;
}
public boolean isOutput() {
return this.nrnType == OUTPUT_NEURON || this.nrnType == INPUT_OUTPUT_NEURON;
}
/**
* @return Returns the id.
*/
public int getId() {
return this.id;
}
/**
* Inserts a new incoming link if no link from the according source neuron
* existed before. If it did, the weight is changed to the new link's
* weight.
*
* @param link The link to add.
*/
public void addIncomingLink(NeuralLink link) {
NeuralLink alreadyThere = this.containsLink(link.getSourceNeuronID());
if (alreadyThere != null) {
alreadyThere.setWeight(link.getWeight());
} else {
this.incomingLinks.add(link);
}
}
public boolean removeIncomingLink(int fromNeuronID) {
for (NeuralLink link : this.incomingLinks) {
if (link.getSourceNeuronID() == fromNeuronID) {
this.incomingLinks.remove(link);
return true;
}
}
return false;
}
/**
* @param sourceID The id of the source node.
*
* @return The Link associated to the source node if one exists,
* <code>null</code> otherwise.
*/
public NeuralLink containsLink(int sourceID) {
for (NeuralLink link : this.incomingLinks) {
if (link.getSourceNeuronID() == sourceID) {
return link;
}
}
return null;
}
public void setTransitionFunctionSigma(final TransitionFunction sigma) {
this.trnFct = sigma;
}
public void setActivationFunctionPhi(final ActivationFunction phi) {
this.actFct = phi;
}
public BufferedImage generateNeuronView(int width, int height) {
return this.actFct.getFunctionView(width, height);
}
public LinkedList<NeuralLink> getIncomingLinks() {
return this.incomingLinks;
}
/**
* @return Returns the output. The net output is calculated temporarily
* to its theoretical future value and set back internally afterwards.
* The values of the other neurons are left untouched.
*/
public double getNetOutput() {
double netOutputOld = this.netOutput;
this.computeAndStoreNetOutput();
double netOutput = this.netOutput;
this.netOutput = netOutputOld;
return netOutput;
}
/**
* Sets the net output to a new value. Do this only if you know what you're
* doing - at least from outside this class.
*
* @param setToValue
*/
protected void setNetOutput(double setToValue) {
this.netOutput = setToValue;
}
public double getInput() {
if (!this.isInput()) {
throw new RuntimeException("The neuron has no input as it is not an input neuron.");
}
return this.input;
}
public void setInput(double input) {
if (!this.isInput()) {
throw new RuntimeException("The neuron has no input as it is not an input neuron.");
}
this.input = input;
}
public double getOutput() {
if (!this.isOutput()) {
throw new RuntimeException("The neuron has no output as it is not an output neuron.");
}
return this.output;
}
public void setOutput(double output) {
if (!this.isOutput()) {
throw new RuntimeException("The neuron has no output as it is not an output neuron.");
}
this.output = output;
}
public void setId(int id) {
this.id = id;
}
@Override
public String toString() {
String inOut = "";
inOut += "(";
try {inOut += this.getInput();} catch (Exception e) {inOut += "X";}
inOut += " -> ";
try {inOut += this.getNetOutput();} catch (Exception e) {inOut += "X";}
inOut += ")";
String s = "Neuron " + this.getId() + ": " + inOut;
return s;
}
public static Neuron getDummyNeuron() {
return new Neuron(0, new ActivationFunctionConstant(0), Neuron.HIDDEN_NEURON, null) {
/**
*
*/
private static final long serialVersionUID = 1256051857833946920L;
@Override
public BufferedImage generateNeuronView(int width, int height) {
return new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
}
};
}
/**
* Resets input, output and netOutput to 0;
*/
protected void reset() {
this.input = 0;
this.output = 0;
this.netOutput = 0;
}
/**
* Two neurons are equal if they have the same incoming links and the same
* transition and activation functions as well as neuron type (hidden,
* input or output). Note that the current states of the neurons are not
* compared (i.e., their current input, output and net output values).
*/
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((this.actFct == null) ? 0 : this.actFct.hashCode());
result = prime * result + this.id;
result = prime
* result
+ ((this.incomingLinks == null) ? 0 : this.incomingLinks
.hashCode());
result = prime * result + this.nrnType;
result = prime * result
+ ((this.trnFct == null) ? 0 : this.trnFct.hashCode());
return result;
}
/**
* Two neurons are equal if they have the same incoming links and the same
* transition and activation functions as well as neuron type (hidden,
* input or output). Note that the current states of the neurons are not
* compared (i.e., their current input and output values).
*/
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Neuron other = (Neuron) obj;
if (this.actFct == null) {
if (other.actFct != null)
return false;
} else if (!this.actFct.equals(other.actFct))
return false;
if (this.id != other.id)
return false;
if (this.incomingLinks == null) {
if (other.incomingLinks != null)
return false;
} else if (!this.incomingLinks.equals(other.incomingLinks))
return false;
if (this.nrnType != other.nrnType)
return false;
if (this.trnFct == null) {
if (other.trnFct != null)
return false;
} else if (!this.trnFct.equals(other.trnFct))
return false;
return true;
}
public ActivationFunction getActFct() {
return this.actFct;
}
}