hebbNet.HebbNet.java Source code

Java tutorial

Introduction

Here is the source code for hebbNet.HebbNet.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 hebbNet;

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

import main.FrevoMain;

import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.XMLWriter;

import core.FileType;
import core.AbstractRepresentation;
import core.XMLFieldEntry;

import utils.NESRandom;

public class HebbNet extends AbstractRepresentation {

    protected NESRandom generator;
    protected float[][] weight;
    protected float[][] plasticity;
    protected float[][] currentweight;
    protected float[] bias;
    protected float[] output;
    protected float[] activation;
    protected float learningrate;
    protected int nodes;
    protected int input_nodes;
    protected int output_nodes;
    protected int hidden_nodes;
    protected float weight_range;
    protected float bias_range;
    protected int stepnumber;

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

    public HebbNet(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 from properties
        loadProperties();
        // generate structure
        this.weight = new float[this.nodes][this.nodes];
        this.currentweight = new float[this.nodes][this.nodes];
        this.plasticity = new float[this.nodes][this.nodes];
        this.bias = new float[this.nodes];
        this.output = new float[this.nodes];
        this.activation = new float[this.nodes];
        learningrate = 0.0f;
        if (random != null)
            randomizeWB(); // this step is not needed for cloning, otherwise
                           // random is not null
    }

    private void loadProperties() {
        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());

        nodes = input_nodes + output_nodes + hidden_nodes;
    }

    private void randomizeWB() {
        for (int i = input_nodes; i < nodes; i++) {
            for (int j = 0; j < nodes; j++) {
                this.weight[j][i] = rand_range(weight_range);
                this.plasticity[j][i] = generator.nextFloat();
            }
            this.output[i] = 0; // *f
            this.bias[i] = rand_range(bias_range);
        }
        learningrate = 0.01f * generator.nextFloat();

        resetWeights();
    }

    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);
        this.learningrate += rand_range((int) Math.max(learningrate / 5.0, 0.01));
        resetWeights();
    }

    public void mutateBias(float severity, float p, int method) {
        float rate = severity * bias_range; // use bias_range as 100%

        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; // use weight_range as 100%

        // System.out.println ("mutating weight rate:"+rate+" prob:"+p);

        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
                            // float
                            // change=rand_range((int)Math.max(this.weight[i][j]/5.0,10));
                            // // 20% mutation
                            // System.out.print("ch("+i+","+j+"):"+change+" ");
                        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;
                    }
                // mutate plasticity values

                if (generator.nextFloat() < p)
                    switch (method) {
                    case 1:
                        this.plasticity[i][j] += rand_range(rate);
                        break;
                    case 2: // smart method, facilitates the mutation to
                            // fine-tune small parameters
                            // float
                            // change=rand_range((int)Math.max(this.weight[i][j]/5.0,10));
                            // // 20% mutation
                            // System.out.print("ch("+i+","+j+"):"+change+" ");
                        this.plasticity[i][j] += rand_range((int) Math.max(this.plasticity[i][j] / 5.0, 10)); // 20%
                        // mutation
                        break;
                    default:
                        this.plasticity[i][j] += rand_range(rate);
                        break;
                    }

            }
        }
    }

    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] + this.plasticity[j][i];
            }
        }
        sum += learningrate;
        String res = Double.toString(sum);
        int resint = res.hashCode();
        return Integer.toHexString(resint & 0xFFFFF);
    }

    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 HebbNet))
            throw new IllegalArgumentException("Xover between different network classes not possible!");

        // modify this representation
        if (method == 1) {
            int noninodes = nodes - input_nodes;
            int count = rndIndex(noninodes - 1) + 1; // minimum 1, maximum all
            // but 1
            int start = input_nodes + rndIndex(noninodes);

            HebbNet offspring = this;
            HebbNet father = (HebbNet) 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.plasticity[j][i] = father.plasticity[j][i];
                }
                offspring.bias[i] = father.bias[i];
            }
            // return offspring;
        }
        /*
         * else if (method == 2) { not yet implemented }
         */
        else
            this.recombinationFunction(other, method);
    }

    public float activate(float x) {
        if (x >= 1)
            return 1;
        else if (x <= 0)
            return 0;
        else
            return x;

    }

    public void reset() // resets also the network weights to default
    {
        for (int i = 0; i < nodes; i++)
            this.output[i] = 0;
        resetWeights();
    }

    private void resetWeights() // resets the network weights to default
    {
        for (int i = 0; i < bias.length; i++)
            for (int j = input_nodes; j < weight.length; j++)
                currentweight[j][i] = weight[j][i];

        for (int i = 0; i < nodes; i++)
            for (int j = 0; j < nodes; j++)
                currentweight[j][i] = weight[j][i];
    }

    public ArrayList<Float> getOutput(ArrayList<Float> input) {
        for (int i = 0; i < stepnumber - 1; i++) {
            getStep(input, false);
        }
        return getStep(input, true);
    }

    /** Provides the networks output for the given input */
    public ArrayList<Float> getStep(ArrayList<Float> input, boolean learn) {
        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);

        for (int i = input_nodes; i < nodes; i++) {
            float sum = 0;
            for (int j = 0; j < nodes; j++) {
                sum += this.currentweight[j][i] * this.output[j];
                // System.out.println("sum="+sum);
            }
            this.activation[i] = this.bias[i] + sum;
        }

        for (int i = input_nodes; i < nodes; i++) {
            this.output[i] = activate(this.activation[i]);
        }

        // Hebbian Learning
        if (learn) {
            float delta[][] = new float[nodes][nodes];
            for (int i = input_nodes; i < nodes; i++) {
                for (int j = 0; j < nodes; j++) {
                    delta[j][i] = (float) (learningrate * (output[i] - 0.5) * (output[j] - 0.5));
                }
            }
            // normalize to an average value of 0.0
            float sum = 0.0f;
            int n = 0;
            for (int i = input_nodes; i < nodes; i++) {
                for (int j = 0; j < nodes; j++) {
                    if (i != j) {
                        sum += delta[j][i];
                        n++;
                    }
                }
            }
            float corr = sum / n;
            // apply learning
            for (int i = input_nodes; i < nodes; i++)
                for (int j = 0; j < nodes; j++)
                    if (i != j)
                        currentweight[j][i] += delta[j][i] - corr;
        }

        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;

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

        HebbNet network = (HebbNet) o;

        for (int i = 0; i < this.weight.length; i++) {
            for (int j = 0; j < this.weight[i].length; j++) {
                diff += Math.abs(network.weight[i][j] - this.weight[i][j]);
                diff += Math.abs(network.plasticity[i][j] - this.plasticity[i][j]);
            }
        }
        for (int i = 0; i < this.bias.length; i++)
            diff += Math.abs(network.bias[i] - this.bias[i]);

        return diff;
    }

    public AbstractRepresentation cloneFunction() {
        HebbNet c = new HebbNet(input_nodes, output_nodes, generator, getProperties());

        for (int i = 0; i < this.weight.length; i++) {
            for (int j = 0; j < this.weight[0].length; j++) {
                c.weight[i][j] = this.weight[i][j];
                c.plasticity[i][j] = this.plasticity[i][j];
                c.currentweight[i][j] = this.currentweight[i][j];
            }
        }

        c.bias = this.bias.clone();
        c.output = this.output.clone();
        c.activation = this.activation.clone();
        c.nodes = this.nodes;
        c.input_nodes = this.input_nodes;
        c.output_nodes = this.output_nodes;
        c.hidden_nodes = this.hidden_nodes;
        c.weight_range = this.weight_range;
        c.bias_range = this.bias_range;
        c.stepnumber = this.stepnumber;
        c.learningrate = this.learningrate;

        String n1 = this.getHash();
        String n2 = c.getHash();

        if (!n1.equals(n2))
            System.out.println(this.getHash() + "cloned to" + c.getHash());

        return c;
    }

    /** Method used for saving the properties of this component */
    public void exportToXmlElement(Element element) {
        Element nn = element.addElement("hebbnet");
        // TODO
        nn.addAttribute("UniqueName", getHash());
        nn.addAttribute("learningrate", String.valueOf(learningrate));
        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("weight_range", String.valueOf(this.weight_range));
        nn.addAttribute("bias_range", String.valueOf(this.bias_range));

        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.weight[j][i]));
                    weight.addAttribute("plasticity", String.valueOf(plasticity[j][i]));
                }
            }
        }
    }

    @Override
    public AbstractRepresentation loadFromXML(Node nd) {
        try {
            // Add properties
            loadProperties();
            this.learningrate = Float.parseFloat(nd.valueOf("./@learningrate"));
            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.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];
            this.plasticity = new float[this.nodes][this.nodes];
            this.currentweight = 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("HebbNet: 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++) {
                    Node thisweight = dweights.selectSingleNode("./weight[@from='" + from + "']");
                    float plast = Float.parseFloat(thisweight.valueOf("./@plasticity"));
                    float weight = Float.parseFloat(thisweight.valueOf("."));
                    this.weight[from][nr] = weight;
                    this.plasticity[from][nr] = plast;
                }
            }
            resetWeights();
        } catch (NumberFormatException e) {
            throw new IllegalArgumentException("HebbNet: 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("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]) + " Plasticity:"
                        + Float.toString(plasticity[from][n]));
            }
        }

        return result;
    }

    /**
     * @return the bias
     */
    public float getBias(int i) {
        return bias[i];
    }

    public int getNumberofNodes() {
        return nodes;
    }

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

    @Override
    public void exportToFile(File saveFile) {
        String extension = FrevoMain.getExtension(saveFile);
        if (extension.equals("net")) {
            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();
                // 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();
                for (int n = 0; n < input_nodes - 1; n++) {
                    for (int p = n + 1; p < input_nodes; p++) {
                        if (n != p) {
                            out.write((n + 1) + " " + (p + 1) + " " + 0);
                            out.newLine();
                        }
                    }
                }

                for (int n = input_nodes; n < nodes; n++) {
                    for (int from = 0; from < nodes; from++) {
                        out.write((n + 1) + " " + (from + 1) + " " + weight[from][n]);
                        // out.write((n+1)+" "+(from+1)+" "+plasticity[from][n]);
                        out.newLine();
                    }
                }

                // Close the output stream
                out.close();
            } catch (Exception e) {
                System.err.println("Error: " + e.getMessage());
            }
        } else if (extension.equals("xml")) {
            System.out.println("Saving to XML");

            Document doc = DocumentHelper.createDocument();
            doc.addDocType("HebbNet", null,
                    System.getProperty("user.dir") + "//Components//Representations//HebbNet//src//HebbNet.dtd");
            Element cnetwork = doc.addElement("HebbNet");
            this.exportToXmlElement(cnetwork);

            OutputFormat format = OutputFormat.createPrettyPrint();
            format.setLineSeparator(System.getProperty("line.separator"));

            try {
                saveFile.createNewFile();
                FileWriter out = new FileWriter(saveFile);
                BufferedWriter bw = new BufferedWriter(out);
                XMLWriter wr = new XMLWriter(bw, format);
                wr.write(doc);
                wr.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }

}