feedForwardNetwork.FeedForwardNetwork.java Source code

Java tutorial

Introduction

Here is the source code for feedForwardNetwork.FeedForwardNetwork.java

Source

/*
 * Copyright (C) 2009 Istvan Fehervari, Wilfried Elmenreich
 * Original project page: http://www.frevotool.tk
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License Version 3 as published
 * by the Free Software Foundation http://www.gnu.org/licenses/gpl-3.0.txt
 *
 * There is no warranty for this free software. The GPL requires that 
 * modified versions be marked as changed, so that their problems will
 * not be attributed erroneously to authors of previous versions.
 */
package feedForwardNetwork;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.util.ArrayList;
import java.util.Hashtable;

import net.jodk.lang.FastMath;

import org.dom4j.Element;
import org.dom4j.Node;

import utils.NESRandom;
import core.AbstractRepresentation;
import core.FileType;
import core.XMLFieldEntry;

/**
 * A not recursive N-layered neural network
 * 
 * @author Istvan
 */
public class FeedForwardNetwork extends AbstractRepresentation {

    public static final ActivationFunction DEFAULT_ACTIVATION_FUNCTION = ActivationFunction.LINEAR;

    protected NESRandom generator;
    protected float[][] weight;
    protected float[] bias;
    protected float[] output;
    protected float[] activation;
    protected int rank;

    /**
     * Number of total nodes
     */
    protected int nodes;

    protected int input_nodes;
    protected int output_nodes;

    /**
     * Number of hidden layers
     */
    protected int layers;

    /**
     * Hidden nodes per layer
     */
    protected int hidden_nodes;

    protected float weight_range;
    protected float bias_range;
    protected int stepnumber;

    protected ActivationFunction activationFunction;

    protected ArrayList<FileType> exportList = new ArrayList<FileType>();

    public FeedForwardNetwork(int inputnumber, int outputnumber, NESRandom random,
            Hashtable<String, XMLFieldEntry> properties) {
        super(inputnumber, outputnumber, random, properties);
        this.setProperties(properties);

        generator = random;
        input_nodes = inputnumber;
        output_nodes = outputnumber;

        // load properties
        XMLFieldEntry snumber = getProperties().get("stepNumber");
        stepnumber = Integer.parseInt(snumber.getValue());

        XMLFieldEntry wr = getProperties().get("weight_range");
        this.weight_range = Float.parseFloat(wr.getValue());
        XMLFieldEntry br = getProperties().get("bias_range");
        this.bias_range = Float.parseFloat(br.getValue());
        XMLFieldEntry hn = getProperties().get("hiddenNodes");
        hidden_nodes = Integer.parseInt(hn.getValue());
        XMLFieldEntry ln = getProperties().get("layers");
        layers = Integer.parseInt(ln.getValue());

        // load activation function
        XMLFieldEntry afprop = getProperties().get("activationFunction");
        if (afprop == null) {
            activationFunction = DEFAULT_ACTIVATION_FUNCTION;
        } else {
            activationFunction = ActivationFunction.valueOf(afprop.getValue());
        }

        // number of nodes = all nodes
        nodes = input_nodes + output_nodes + hidden_nodes * layers;

        // generate structure
        this.weight = new float[this.nodes][this.nodes]; // weight matrix
        this.bias = new float[this.nodes];
        this.output = new float[this.nodes];
        this.activation = new float[this.nodes];
        if (random != null)
            randomizeWB(); // this step is not needed for cloning, otherwise
                           // random is not null
    }

    public String getHash() {
        double sum = 0;
        // add bias
        for (int i = 0; i < bias.length; i++) {
            sum += bias[i];
        }
        // add weights
        for (int i = input_nodes; i < nodes; i++) {
            for (int j = 0; j < nodes; j++) {
                sum += this.weight[j][i];
            }
        }
        String res = Double.toString(sum);
        int resint = res.hashCode();
        return Integer.toHexString(resint & 0xFFFFF);
    }

    private void randomizeWB() {
        // randomize connections between hidden nodes and input nodes
        for (int i = input_nodes; i < nodes - output_nodes; i++) // all hidden nodes
        {
            for (int j = 0; j < input_nodes; j++) // all input nodes
            {
                this.weight[j][i] = rand_range(weight_range);
            }
            this.bias[i] = rand_range(bias_range);
        }

        // randomize connections between hidden nodes and output nodes
        for (int i = nodes - output_nodes; i < nodes; i++) // all output nodes
        {
            for (int j = input_nodes; j < nodes - output_nodes; j++) // all hidden nodes
            {
                this.weight[j][i] = rand_range(weight_range);
            }
            this.bias[i] = rand_range(bias_range);
        }

        // no bias for input nodes
    }

    // *f
    private float rand_range(float border) {
        double val;
        val = generator.nextDouble() * 2 * border - border; // *f

        return (float) val;
    }

    @Override
    public int getNumberofMutationFunctions() {
        return 2;
    }

    @Override
    public int getNumberOfRecombinationFunctions() {
        return 1;
    }

    @Override
    protected void mutationFunction(float severity, float probability, int method) {
        this.mutateBias(severity, probability, method);
        this.mutateWeight(severity, probability, method);
    }

    public void mutateBias(float severity, float p, int method) {
        float rate = severity * bias_range;

        for (int i = input_nodes; i < bias.length; i++) {
            if (generator.nextFloat() < p) {
                switch (method) {
                case 1:
                    this.bias[i] += rand_range(rate);
                    break;
                case 2: // smart method, facilitates the mutation to fine-tune
                        // small parameters
                    this.bias[i] += rand_range((int) Math.max(this.bias[i] / 5.0, 10)); // 20% mutation
                    break;
                default:
                    this.bias[i] += rand_range(rate);
                    break;
                }
            }

        }
    }

    public void mutateWeight(float severity, float p, int method) {
        float rate = severity * weight_range;

        for (int i = 0; i < bias.length; i++) {
            for (int j = input_nodes; j < weight.length; j++) {
                if (generator.nextFloat() < p)
                    switch (method) {
                    case 1:
                        this.weight[i][j] += rand_range(rate);
                        break;
                    case 2: // smart method, facilitates the mutation to
                            // fine-tune small parameters
                        this.weight[i][j] += rand_range((int) Math.max(this.weight[i][j] / 5.0, 10)); // 20% mutation
                        break;
                    default:
                        this.weight[i][j] += rand_range(rate);
                        break;
                    }

            }
        }
    }

    private int rndIndex(int range) {
        if (range < 1)
            return 0;

        return generator.nextInt(Integer.MAX_VALUE) % range;
    }

    @Override
    public void recombinationFunction(AbstractRepresentation other, int method) {
        if (!(other instanceof FeedForwardNetwork))
            throw new IllegalArgumentException("Xover between different network classes not possible!");

        int noninodes = nodes - input_nodes;
        int count = rndIndex(noninodes - 1) + 1; // minimum 1, maximum all
        // but 1
        int start = input_nodes + rndIndex(noninodes);

        FeedForwardNetwork offspring = this;
        FeedForwardNetwork father = (FeedForwardNetwork) other;

        for (int i = start; i < start + count; i++) {
            if (i >= nodes)
                break;

            for (int j = 0; j < nodes; j++)
                offspring.weight[j][i] = father.weight[j][i];
            offspring.bias[i] = father.bias[i];
        }

    }

    public float activate(float x) {
        switch (activationFunction) {
        case LINEAR:
            return linearActivate(x);
        case SIGMOID:
            return sigmoidActivate(x);
        case RELU:
            return reluActivate(x);
        default:
            return linearActivate(x);
        }
    }

    public static float reluActivate(float x) {
        return Math.max(0, x);
    }

    public static float sigmoidActivate(float x) {
        return (float) (1.0f / (1.0f + FastMath.exp(-x)));
    }

    public static float linearActivate(float x) {
        if (x >= 1)
            return 1;
        else if (x <= 0)
            return 0;
        else
            return x;
    }

    public void reset() {
        for (int i = 0; i < nodes; i++)
            this.output[i] = 0;
    }

    /** Provides the networks output for the given input */
    public ArrayList<Float> getOutput(ArrayList<Float> input) {
        for (int i = 0; i < stepnumber - 1; i++) {
            getStep(input);
        }
        return getStep(input);
    }

    /**
     * Calculate an output vector for the given input vector
     * @param input given input vector
     * @return calculated output vector
     */
    public ArrayList<Float> getStep(ArrayList<Float> input) {
        if (input.size() != this.input_nodes)
            throw new IllegalArgumentException("Input vector size inappropriate!");

        for (int i = 0; i < input.size(); i++) {
            this.output[i] = input.get(i);
        }

        // propagate input to first hidden layer
        for (int hiddenNodeIndex = input_nodes; hiddenNodeIndex < input_nodes + hidden_nodes; hiddenNodeIndex++) { // all hidden                                          // nodes
            float sum = 0;
            for (int inputNodeIndex = 0; inputNodeIndex < input_nodes; inputNodeIndex++) // all input nodes
            {
                sum += this.weight[inputNodeIndex][hiddenNodeIndex] * this.output[inputNodeIndex];
            }
            this.activation[hiddenNodeIndex] = this.bias[hiddenNodeIndex] + sum;
            this.output[hiddenNodeIndex] = activate(this.activation[hiddenNodeIndex]);
        }

        // propagate to all subsequent hidden layers
        for (int layerIndex = 1; layerIndex < this.layers; layerIndex++) {
            int hiddenLayerIndex = input_nodes + (hidden_nodes * layerIndex); // index to the current hidden layer
            int prevHiddenLayerIndex = hiddenLayerIndex - hidden_nodes; // index to the previous hidden layer
            for (int hiddenNodeIndex = hiddenLayerIndex; hiddenNodeIndex < hiddenLayerIndex
                    + hidden_nodes; hiddenNodeIndex++) {
                float sum = 0;
                for (int inputNodeIndex = prevHiddenLayerIndex; inputNodeIndex < prevHiddenLayerIndex
                        + hidden_nodes; inputNodeIndex++) // all input nodes
                {
                    sum += this.weight[inputNodeIndex][hiddenNodeIndex] * this.output[inputNodeIndex];
                }
                this.activation[hiddenNodeIndex] = this.bias[hiddenNodeIndex] + sum;
                this.output[hiddenNodeIndex] = activate(this.activation[hiddenNodeIndex]);
            }
        }

        // propagate information to output layer
        for (int i = nodes - output_nodes; i < nodes; i++) // all output nodes
        {
            float sum = 0;
            for (int j = nodes - output_nodes - hidden_nodes; j < nodes - output_nodes; j++) // last hidden layer nodes
            {
                sum += this.weight[j][i] * this.output[j];
            }
            this.activation[i] = this.bias[i] + sum;
            this.output[i] = activate(this.activation[i]);
        }

        ArrayList<Float> outputVector = new ArrayList<Float>();

        for (int i = nodes - output_nodes; i < nodes; i++) {
            outputVector.add(this.output[i]);
        }
        return outputVector;
    }

    @Override
    public double diffTo(AbstractRepresentation o) {
        double diff = 0;
        int n = 0;

        if (!(o instanceof FeedForwardNetwork))
            throw new IllegalArgumentException("diffTo between different network classes not possible!");

        FeedForwardNetwork network = (FeedForwardNetwork) o;

        // add differences from input to hidden layer
        for (int i = input_nodes; i < nodes - output_nodes; i++) // all hidden
        // nodes
        {
            for (int j = 0; j < input_nodes; j++) // all input nodes
            {
                diff += Math.abs(network.weight[j][i] - this.weight[j][i]) / weight_range;
            }
        }
        n += (nodes - output_nodes - input_nodes) * input_nodes;

        // add differences from hidden to output layer
        for (int i = nodes - output_nodes; i < nodes; i++) // all output nodes
        {
            for (int j = input_nodes; j < nodes - output_nodes; j++) // all hidden
            {
                diff += Math.abs(network.weight[j][i] - this.weight[j][i]) / weight_range;
            }
        }
        n += output_nodes * (nodes - output_nodes - input_nodes);

        // bias of all hidden and output nodes 
        for (int i = input_nodes; i < this.bias.length; i++)
            diff += Math.abs(network.bias[i] - this.bias[i]) / bias_range;
        n += this.bias.length - input_nodes;

        return diff / n;
    }

    public AbstractRepresentation cloneFunction() {
        FeedForwardNetwork res = new FeedForwardNetwork(input_nodes, output_nodes, generator, getProperties());

        for (int i = 0; i < weight.length; i++) {
            System.arraycopy(weight[i], 0, res.weight[i], 0, weight[i].length);
        }

        res.bias = this.bias.clone();
        res.output = this.output.clone();
        res.activation = this.activation.clone();
        res.rank = this.rank;
        return res;
    }

    public float getWeight(int nr, int from) {
        if (nr >= nodes)
            throw new IllegalArgumentException("Id " + nr + " is not a node!");
        {
            if (from >= nodes)
                throw new IllegalArgumentException("Id " + from + " is not a node!");

            return this.weight[from][nr];
        }
    }

    /** Method used for saving the properties of this component */
    public void exportToXmlElement(Element element) {
        Element nn = element.addElement("ThreeLayerNetwork");
        nn.addAttribute("input_nodes", String.valueOf(this.input_nodes));
        nn.addAttribute("output_nodes", String.valueOf(this.output_nodes));
        nn.addAttribute("nodes", String.valueOf(this.nodes));
        nn.addAttribute("layers", String.valueOf(this.layers));
        nn.addAttribute("weight_range", String.valueOf(this.weight_range));
        nn.addAttribute("bias_range", String.valueOf(this.bias_range));
        if (this.isEvaluated()) {
            nn.addAttribute("fitness", String.valueOf(this.getFitness()));
        }

        Element dnodes = nn.addElement("nodes");

        for (int i = 0; i < this.nodes; i++) {
            Element node;
            if (i < input_nodes) {
                node = dnodes.addElement("node");
                node.addAttribute("nr", String.valueOf(i));
                node.addAttribute("type", "inputNode");
            } else {
                node = dnodes.addElement("node");
                node.addAttribute("nr", String.valueOf(i));
                node.addAttribute("type", "node");
                Element bias = node.addElement("bias");
                bias.addText(String.valueOf(this.bias[i]));

                Element weights = node.addElement("weights");
                for (int j = 0; j < this.nodes; j++) {
                    Element weight = weights.addElement("weight");
                    weight.addAttribute("from", String.valueOf(j));
                    weight.addText(String.valueOf(this.getWeight(i, j)));
                }
            }
        }
    }

    /**
     * Sets the generator of random numbers for the current representation. 
     */
    @Override
    public void setGenerator(NESRandom generator) {
        this.generator = generator;
    }

    @Override
    public AbstractRepresentation loadFromXML(Node nd) {
        try {
            String fitnessString = nd.valueOf("./@fitness");
            if (!fitnessString.isEmpty()) {
                this.setFitness(Double.parseDouble(fitnessString));
            }

            // load activation function
            XMLFieldEntry afprop = getProperties().get("activationFunction");
            if (afprop == null) {
                activationFunction = DEFAULT_ACTIVATION_FUNCTION;
            } else {
                activationFunction = ActivationFunction.valueOf(afprop.getValue());
            }

            this.input_nodes = Integer.parseInt(nd.valueOf("./@input_nodes"));
            this.output_nodes = Integer.parseInt(nd.valueOf("./@output_nodes"));
            this.nodes = Integer.parseInt(nd.valueOf("./@nodes"));
            this.weight_range = Float.parseFloat(nd.valueOf("./@weight_range"));
            this.bias_range = Float.parseFloat(nd.valueOf("./@bias_range"));
            this.layers = Integer.parseInt(nd.valueOf("./@layers"));
            // this.iteration =
            // Integer.parseInt(nd.valueOf("./simulation/@iteration"));
            // this.score = Integer.parseInt(nd.valueOf("./simulation/@score"));
            this.activation = new float[this.nodes];
            this.output = new float[this.nodes];
            this.bias = new float[this.nodes];
            this.weight = new float[this.nodes][this.nodes];
            Node dnodes = nd.selectSingleNode("./nodes");
            for (int nr = this.input_nodes; nr < this.nodes; nr++) {
                Node curnode = dnodes.selectSingleNode("./node[@nr='" + nr + "']");
                if (curnode == null)
                    throw new IllegalArgumentException("ThreeLayerNetwork: node tags inconsistent!"
                            + "\ncheck 'nr' attributes and nodes count in nnetwork!");
                this.bias[nr] = Float.parseFloat(curnode.valueOf("./bias"));
                Node dweights = curnode.selectSingleNode("./weights");
                for (int from = 0; from < this.nodes; from++) {
                    String ws = dweights.valueOf("./weight[@from='" + from + "']");
                    if (ws.length() == 0)
                        throw new IllegalArgumentException("ThreeLayerNetwork: weight tags inconsistent!"
                                + "\ncheck 'from' attributes and nodes count in nnetwork!");
                    float val = Float.parseFloat(ws);
                    this.weight[from][nr] = val;
                }
            }
            // this.gahist = new GAHistory(this.config, nd);
        } catch (NumberFormatException e) {
            throw new IllegalArgumentException("ThreeLayerNetwork: NumberFormatException! Check XML File");
        }
        return this;
    }

    @Override
    public Hashtable<String, String> getDetails() {
        Hashtable<String, String> result = new Hashtable<String, String>();
        result.put("input neurons", Integer.toString(this.input_nodes));
        result.put("output neurons", Integer.toString(this.output_nodes));
        result.put("layers", Integer.toString(this.layers));
        result.put("hidden neurons", Integer.toString(nodes - input_nodes - output_nodes));
        result.put("weight range", Float.toString(this.weight_range));
        result.put("bias range", Float.toString(this.bias_range));
        for (int n = 0; n < nodes; n++) {
            result.put("bias node " + n, Float.toString(this.bias[n]));
            for (int from = 0; from < this.nodes; from++) {
                result.put("weight from " + from + " to " + n, Float.toString(this.weight[from][n]));
            }
        }

        return result;
    }

    @Override
    public void exportToFile(File saveFile) {
        // TODO: this function is copied straight from 3 layered network so it will not be accurate
        System.out.println("Exporting Pajek network file to " + saveFile.getName());
        try {
            // Create file
            FileWriter fstream = new FileWriter(saveFile);
            BufferedWriter out = new BufferedWriter(fstream);
            out.write("*Vertices " + this.nodes);
            out.newLine(); // use newline instead of \n to avoid system
                           // dependency
                           // input neurons
            for (int i = 1; i < input_nodes + 1; i++) {
                out.write(i + " \"I" + i + "\"");
                out.newLine();
            }
            for (int h = input_nodes + 1; h < nodes - output_nodes + 1; h++) {
                out.write(h + " \"H" + (h - input_nodes) + "\"");
                out.newLine();
            }
            int a = 1;
            for (int o = nodes - output_nodes + 1; o < nodes + 1; o++) {
                out.write(o + " \"O" + a + "\"");
                out.newLine();
                a++;
            }

            // Edges
            out.write("*Edges");
            out.newLine();

            // add edges from hidden neurons to input neurons
            for (int n = input_nodes; n < (nodes - output_nodes); n++) { // take
                // all
                // hidden
                // neurons
                for (int from = 0; from < input_nodes; from++) { // take all
                    // input
                    // neurons
                    out.write((n + 1) + " " + (from + 1) + " " + weight[from][n]);
                    out.newLine();
                }
            }

            // add edges from output neurons to hidden neurons
            for (int n = (nodes - output_nodes); n < nodes; n++) { // take all
                // output
                // neurons
                for (int from = input_nodes; from < (nodes - output_nodes); from++) { // take
                    // all
                    // hidden
                    // neurons
                    out.write((n + 1) + " " + (from + 1) + " " + weight[from][n]);
                    out.newLine();
                }
            }

            // Close the output stream
            out.close();
        } catch (Exception e) {
            System.err.println("Error: " + e.getMessage());
        }
    }

}