Visualization of Artificial Neural Network with WebGL


Facebook Twitter More...

By Markus SprunckRevision: 1.3; Status: final; Last Content Change: Apr 8, 2013;

This WebGL experiment shows an Artificial Neural Network which learns to detect the frequency of the input signal independent from the phase. The implementation is based on Three.js (r56), Guava and Google Web Toolkit. 

Here GWT helps to translate the Java code for the Artificial Neural Network into JavaScript, which is then executed in the browser. The experiment has been developed with GWT 2.5 and App Engine SDK 1.7.6. It runs best with Chrome Browser. 

Open the WebGL experiment with WebGL compatible Browser like Chrome 25 for Win32 or Firefox for Android 17 and you should see something like this:

Figure 1: Not trained Neural Network (press button Reset)

Use the buttons in the right bottom corner to reset the network and train it again. You may use the Arrow- and Page-UP/Down Keys to move the graphic.

Figure 2: Trained Neural Network (press button Train)

If your browser does not support WebGL, it will try to render directly to the canvas. This fallback solution is significantly slower than WebGL. In Figure 3 you see Firefox 11.0 (Windows)

Figure 3: Open in browser without WebGL support

Project Structure

To compile and test all the sources you will need a Google AppEngine project. If you are not familiar with Google AppEngine projects, you may read chapter 'Preparations to get a Web Applications Starter Project' from the article How to get User Information with OAuth2 in a GWT and Google AppEngine Java Application?'

Figure 4: Project structure in Eclipse Juno

Eclipse Juno (JEE SR1) has been used to develop the project. At the end of this article you find a link to download the entire project.

Code for Artificial Neural Network

The following code implements a simple multi-layer perceptron neural network with backpropagation algorithm as training method. If you are not familiar with Artificial Neural Networks you may start reading with Wikipedia - Neural Network and then Neural Networks - A Systematic Introduction.

// File #1: Neuron.java
/** 
 * Copyright 2013 Markus Sprunck 
 */
package com.sw_engineering_candies.sample.shared;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import com.google.common.base.Preconditions;

public class Neuron {

    public enum Type {
        INPUT, OUTPUT, INNER
    }

    private final List<Link> links = new ArrayList<Link>();

    private double output = 0.0;

    private double outputExpected = 0.0;

    private double outputError = 0.0;

    private double input = 0.0;

    private double outputDerived = 1.0;

    private final Type type;

    public Neuron() {
        this(Type.INNER);
    }

    public Neuron(final Type type) {
        this.type = type;
    }

    public void createLink(final Neuron source, final double weight) {
        Link link = null;

        Preconditions.checkNotNull(source);
        Preconditions.checkArgument(!source.equals(this));

        link = new Link();
        link.setSource(source);
        link.setWeight(weight);
        links.add(link);
    }

    private double functionFermi(final double x) {
        if (x > Double.MAX_EXPONENT) {
            return 1.0;
        } else if (x < Double.MIN_EXPONENT) {
            return 0.0;
        } else {
            final double y = 1.0 / (1.0 + Math.exp(-x));
            return y;
        }
    }

    private double functionFermiDerive(final double x) {
        final double z = functionFermi(x);
        final double y = z * (1 - z);
        return y;
    }

    public double getInput() {
        return input;
    }

    public List<Link> getLinks() {
        return Collections.unmodifiableList(links);
    }

    public double getOutput() {
        return output;
    }

    public double getOutputDerived() {
        return outputDerived;
    }

    public double getOutputExpected() {
        return outputExpected;
    }

    public boolean isInputNeuron() {
        return Type.INPUT.equals(type);
    }

    public boolean isInnerNeuron() {
        return Type.INNER.equals(type);
    }

    public boolean isOutputNeuron() {
        return Type.OUTPUT.equals(type);
    }

    public void setInput(final double input) {
        Preconditions.checkState(Type.INPUT == type);
        this.input = input;
    }

    public void recall() {

        if (Type.INPUT.equals(type)) {
            output = input;
            outputDerived = 1.0;
        } else {
            double sum = 0.0;
            for (final Link link : links) {
                sum += link.getSource().getOutput() * link.getWeight();
            }
            output = functionFermi(sum);
            outputDerived = functionFermiDerive(sum);
        }
    }

    public void calculateEvaluateOutputError() {
        Preconditions.checkArgument(Type.OUTPUT == type);
        outputError = output - outputExpected;
    };

    public void calculateEvaluateOutputErrorHiddenNeurons(final double m_FlatSpot) {
        Preconditions.checkArgument(Type.INPUT != type);

        for (final Link link : links) {
            final double derivation = outputDerived + m_FlatSpot;
            final double oldError = link.getSource().getOutputError();
            link.getSource().setOutputError(derivation * outputError * link.getWeight() + oldError);
        }
    };

    public void setOutputError(final double outputError) {
        this.outputError = outputError;
    }

    public double getOutputError() {
        return outputError;
    }

    public void setOutputExpected(final double outputExpected) {
        this.outputExpected = outputExpected;
    }

    @Override
    public String toString() {
        return "{\"y\":" + output + ",\"y_ex\":" + outputExpected + "}";
    }

}

// File #2: Layer.java
/** 
 * Copyright 2013 Markus Sprunck 
 */
package com.sw_engineering_candies.sample.shared;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Layer {

    private final List<Neuron> neurons = new ArrayList<Neuron>();

    public void addNeuron(final Neuron neuron) {
        neurons.add(neuron);
    }

    public List<Neuron> getNeurons() {
        return Collections.unmodifiableList(neurons);
    }

    public void recallNeurons() {
        for (final Neuron neuron : neurons) {
            neuron.recall();
        }
    }

    public void resetLinks() {
        for (final Neuron neuron : neurons) {
            for (final Link link : neuron.getLinks()) {
                link.setWeight(Math.random() - 0.5);
            }
        }
    }

    @Override
    public String toString() {
        final StringBuilder result = new StringBuilder("{\"nodes\":[");
        final int neuronNumber = neurons.size();
        for (int index = 0; index < neuronNumber; index++) {
            result.append(neurons.get(index).toString());
            result.append(index == neuronNumber - 1 ? "" : ",");
        }
        result.append("]}");
        return result.toString();
    }

}

// File #3: Network.java
/** 
 * Copyright 2013 Markus Sprunck 
 */
package com.sw_engineering_candies.sample.shared;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Network {

    private static final double GAMMA = 3.0;

    private static final double ALPHA = 0.10;

    private static final double WEIGHT_DECAY = 6.0;

    private static final double FLAT_SPOT = 0.01;

    private final List<Layer> layers = new ArrayList<Layer>();

    public void addLayer(final Layer layer) {
        layers.add(layer);
    }

    public List<Layer> getLayers() {
        return Collections.unmodifiableList(layers);
    }

    public void recallNetwork() {
        for (final Layer layer : layers) {
            layer.recallNeurons();
        }
    }

    public void meshAllNeurons() {
        int connections = 0;
        Layer firstLayer = null;
        Layer secondLayer = null;
        final int numberOfLayers = layers.size();
        for (int index = 1; index < numberOfLayers; index++) {
            firstLayer = layers.get(index - 1);
            secondLayer = layers.get(index);
            for (final Neuron target : secondLayer.getNeurons()) {
                for (final Neuron source : firstLayer.getNeurons()) {
                    target.createLink(source, -1 + 0.1 * connections++);
                }
            }
        }
    }

    public void trainBackpropagation(final Pattern pattern, final int itterations, final int steps) {

        final int maxLayerIndex = layers.size() - 1;

        for (int step = 0; step < steps; step++) {
            for (int i = 0; i < itterations; i++) {

                // Activate a random pattern
                pattern.activatePatternRandom();

                // Forward propagation of input values
                recallNetwork();

                // Calculate errors of output neurons
                for (final Neuron neuron : layers.get(maxLayerIndex).getNeurons()) {
                    neuron.calculateEvaluateOutputError();
                }

                // Calculate errors of hidden neurons
                for (int k = maxLayerIndex; k > 0; k--) {
                    for (final Neuron neuron : layers.get(k).getNeurons()) {
                        neuron.calculateEvaluateOutputErrorHiddenNeurons(FLAT_SPOT);
                    }
                }

                // Calculate delta weights
                for (int k = maxLayerIndex; k > 0; k--) {
                    for (final Neuron neuron : layers.get(k).getNeurons()) {
                        for (final Link link : neuron.getLinks()) {
                            final double weightDecayTerm = 
                                    Math.pow(10, -WEIGHT_DECAY) * link.getWeight();
                            final double momentumTerm = ALPHA * link.getDeltaWeigthOld();
                            link.setDeltaWeigth(link.getDeltaWeigth() 
                                    - GAMMA * link.getSource().getOutput()
                                    * neuron.getOutputDerived() * neuron.getOutputError() 
                                    + momentumTerm
                                    - weightDecayTerm);
                        }
                    }
                }

                // calculate new weights of links
                for (int k = maxLayerIndex; k > 0; k--) {
                    for (final Neuron neuron : layers.get(k).getNeurons()) {
                        for (final Link link : neuron.getLinks()) {
                            link.setWeight(link.getWeight() + link.getDeltaWeigth());
                            link.setDeltaWeigthOld(link.getDeltaWeigth());
                            link.setDeltaWeigth(0.0);
                        }
                    }
                }

                // Reset error of all neurons
                for (int k = maxLayerIndex; k > 0; k--) {
                    for (final Neuron neuron : layers.get(k).getNeurons()) {
                        neuron.setOutputError(0.0);
                    }
                }
            }
        }

    }

    public void resetLinks() {
        for (final Layer layer : layers) {
            layer.resetLinks();
        }
    }

    public double rms(final Pattern patterns) {

        double result = 0;
        final int patternNumber = patterns.getNumberOfPattern();
        for (int i = 0; i < patternNumber; i++) {
            // Activate a random pattern
            patterns.activatePattern(i);
            // Forward propagation of input values
            recallNetwork();
            // Calculate errors of output neurons
            for (final Neuron neuron : layers.get(layers.size() - 1).getNeurons()) {
                neuron.calculateEvaluateOutputError();
                result = +Math.pow(neuron.getOutputError(), 2.0);
            }
        }

        return result / patternNumber;
    }

    @Override
    public String toString() {
        final StringBuilder result = new StringBuilder("{\"layers\":[");
        final int layerNumber = layers.size();
        for (int index = 0; index < layerNumber; index++) {
            result.append(layers.get(index).toString());
            result.append(index == layerNumber - 1 ? "" : ",");
        }
        result.append("]}");
        return result.toString();
    }

}

// File #4: Link.java
/** 
 * Copyright 2013 Markus Sprunck 
 */
package com.sw_engineering_candies.sample.shared;

public class Link {

    private Neuron source;

    private double weight;

    private double deltaWeigth;

    private double deltaWeigthOld;

    public double getDeltaWeigth() {
        return deltaWeigth;
    }

    public void setDeltaWeigth(final double deltaWeigth) {
        this.deltaWeigth = deltaWeigth;
    }

    public void setDeltaWeigthOld(final double deltaWeigthOld) {
        this.deltaWeigthOld = deltaWeigthOld;
    }

    public double getDeltaWeigthOld() {
        return deltaWeigthOld;
    }

    public Neuron getSource() {
        return source;
    }

    public double getWeight() {
        return weight;
    }

    public void setSource(final Neuron source) {
        this.source = source;
    }

    public void setWeight(final double weight) {
        this.weight = weight;
    }
}

// File #5: Pattern.java
/** 
 * Copyright 2013 Markus Sprunck 
 */
package com.sw_engineering_candies.sample.shared;

import java.util.List;

import com.google.common.base.Preconditions;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.ImmutableTable;
import com.google.common.collect.Table;

public class Pattern {

    protected int numberOfPattern = 0;

    protected List<Neuron> inputNeurons;

    protected List<Neuron> outputNeurons;

    protected Table<Integer, Neuron, Double> value;

    public void bind(final Network network, final Double[][] table) {
        Preconditions.checkNotNull(network);
        Preconditions.checkArgument(null != table);
        Preconditions.checkArgument(0 < table.length);

        inputNeurons = network.getLayers().get(0).getNeurons();
        outputNeurons = network.getLayers().get(network.getLayers().size() - 1).getNeurons();

        final int inputNeuronNumber = inputNeurons.size();
        final int outputNeuronNumber = outputNeurons.size();
        Preconditions.checkArgument(inputNeuronNumber + outputNeuronNumber == table[0].length);
        numberOfPattern = table.length;

        value = HashBasedTable.create();

        for (int col = 0; col < inputNeuronNumber; col++) {
            for (int row = 0; row < numberOfPattern; row++) {
                value.put(row, inputNeurons.get(col), table[row][col]);
            }
        }
        for (int col = 0; col < outputNeuronNumber; col++) {
            for (int row = 0; row < numberOfPattern; row++) {
                value.put(row, outputNeurons.get(col), table[row][col + inputNeuronNumber]);
            }
        }
    }

    public ImmutableTable<Integer, Neuron, Double> getPatterns() {
        return ImmutableTable.copyOf(value);
    }

    public void activatePatternRandom() {
        activatePattern((int) (Math.random() * numberOfPattern % numberOfPattern));
    }

    public void activatePattern(final int index) {
        Preconditions.checkArgument(index >= 0);
        Preconditions.checkArgument(index < numberOfPattern);

        for (final Neuron inputNeuron : inputNeurons) {
            inputNeuron.setInput(value.get(index, inputNeuron));
        }
        for (final Neuron outputNeuron : outputNeurons) {
            outputNeuron.setOutputExpected(value.get(index, outputNeuron));
        }
    }

    public int getNumberOfPattern() {
        return numberOfPattern;
    }

}

// File #6: ModelFactory.java
/** 
 * Copyright 2013 Markus Sprunck 
 */
package com.sw_engineering_candies.sample.shared;

import com.google.common.collect.HashBasedTable;
import com.sw_engineering_candies.sample.shared.Neuron.Type;

public class ModelFactory extends Pattern {

    private static final double ALPHA_INCRMENT = Math.PI / 180.0 * 8.0;

    private static final double PHASE_INCRMENT = Math.PI / 180.0 * 5.0;

    public Network createBindTestPattern() {

        final Network network = new Network();

        final int inputChanels = 20;

        final int hiddenNeurons = 15;

        final int outputChanels = 10;

        final Layer layer10 = new Layer();
        for (int i = 0; i < inputChanels; i++) {
            layer10.addNeuron(new Neuron(Type.INPUT));
        }
        network.addLayer(layer10);

        final Layer layer20b = new Layer();
        for (int i = 0; i < hiddenNeurons; i++) {
            layer20b.addNeuron(new Neuron());
        }
        network.addLayer(layer20b);

        final Layer layer30 = new Layer();
        for (int i = 0; i < outputChanels; i++) {
            layer30.addNeuron(new Neuron(Type.OUTPUT));
        }
        network.addLayer(layer30);

        network.meshAllNeurons();

        inputNeurons = network.getLayers().get(0).getNeurons();
        outputNeurons = network.getLayers().get(network.getLayers().size() - 1).getNeurons();
        value = HashBasedTable.create();

        final int maxFrequenceFactor = outputChanels;
        numberOfPattern = 0;
        double phase = 0;
        for (int frequenceFactor = 1; frequenceFactor <= maxFrequenceFactor; frequenceFactor++) {
            for (int indexPhase = 0; indexPhase < inputChanels; indexPhase++) {
                phase += PHASE_INCRMENT * frequenceFactor;
                for (int index = 0; index < inputChanels; index++) {
                    final double alpha = ALPHA_INCRMENT * index;
                    final double input = 0.6 + 0.5 * Math.cos(frequenceFactor * alpha + phase);
                    value.put(numberOfPattern, inputNeurons.get(index), input);
                }
                for (int output = 1; output <= outputChanels; output++) {
                    if (output == frequenceFactor) {
                        value.put(numberOfPattern, outputNeurons.get(output - 1), 0.9);
                    } else {
                        value.put(numberOfPattern, outputNeurons.get(output - 1), 0.1);
                    }
                }
                numberOfPattern++;
            }
        }

        network.resetLinks();
        return network;
    }
}

Code for WebGL User Interface

In this code the library three.js has been used. This helps to reduce the boilerplate code needed for native WebGL. You may read more about three.js here.

// File #7: Webgl_ann_sample.js
//
// Constants for drawing the network
var CUBE_SIZE = 40;
var DISTANCE_CUBE = CUBE_SIZE * 1.2;
var DISTANCE_LAYER = CUBE_SIZE * 4;
var TEXT_LENGTH = 190;
var TEXT_HEIGHT = 34;

// Artificial neural network model
var model;

// Make initialization just once
var initReady = false;

// Helps to draw the graphic
var maxCols;
var maxRows;
var sizeres;
var halfsizeres;
var deltaX = -50;
var deltaY = 110;
var deltaZ = 320;

// WebGL
var grid = [];
var scene, camera, renderer, geometry, projector, ray;

// Draws the model and does the initialization if needed
//
function renderData(modelText) {
    model = createObjects(modelText);
    if (initReady) {
        update();
    } else {
        calulateModelDimensions()
        init();
    }
}

// Update the graphic with new values form model
//
function update() {
    // Change size and color of all neurons
    for (var layerId = 0; layerId < model.layers.length; layerId++) {
        var numberOfNodes = model.layers[layerId].nodes.length;
        for (var nodeId = 0; nodeId < numberOfNodes; nodeId++) {
            var elementId = nodeId + layerId * maxRows;
            var cubeHeight = model.layers[layerId].nodes[nodeId].y * 2;
            grid[elementId].scale.y = 0.01 + cubeHeight;
            var color = getColorHex(0.01 + cubeHeight);
            grid[elementId].material.color.setHex( color ); 
        }
    }
    // Change size and color of expected output values
    var layerId = model.layers.length - 1;
     var numberOfNodes = model.layers[layerId].nodes.length;       
    for (var nodeId = 0; nodeId < numberOfNodes; nodeId++) {
        var elementId = nodeId + (layerId + 1) * maxRows;
        var cubeHeight = model.layers[layerId].nodes[nodeId].y_ex * 2;
        grid[elementId].scale.y = 0.01 + cubeHeight;
        var color = getColorHex(0.01 + cubeHeight);
        grid[elementId].material.color.setHex( color );
    }
    renderer.render(scene, camera);
}

// Initialization of the WebGL stuff
//
function init() {

    // Create camera
    camera = new THREE.PerspectiveCamera(50, 
            window.innerWidth / window.innerHeight, 1, 2000);
    camera.position.x = halfsizeres + deltaX;
    camera.position.y = halfsizeres + deltaY;
    camera.position.z = sizeres * 0.85 + deltaZ;
    camera.lookAt(new THREE.Vector3(halfsizeres + deltaX, 
            halfsizeres / 2 + deltaY, halfsizeres + deltaZ));

    // Create scene
    scene = new THREE.Scene();
    scene.add(camera);
 
    // Create renderer
    if (Detector.webgl) {
        renderer = new THREE.WebGLRenderer({
            antialias: true
        });
    } else {
        renderer = new THREE.CanvasRenderer();
        var infoLabel = document.getElementById('infoLabelContainer3');
        infoLabel.innerHTML = "WebGL is not available (canvas renderer active)";
    }

    // Create light    
    var light = new THREE.SpotLight(0xffffff, 1.25);
    light.position.set(-500, 900, 1600);
    light.target.position.set(halfsizeres, 0, halfsizeres);
    light.castShadow = true;
    scene.add(light);
    
    scene.add(new THREE.AmbientLight(0xF0F0F0));
    
    // Create cubes (for each neuron)
    geometry = new THREE.CubeGeometry(CUBE_SIZE, CUBE_SIZE, CUBE_SIZE);
    var matrix = new THREE.Matrix4();
    matrix.makeTranslation( 0,  CUBE_SIZE / 2, 0 );
    geometry.applyMatrix( matrix );    
    for (var layerId = 0; layerId <= maxCols; layerId++) {
        for (var nodeId = 0; nodeId < maxRows; nodeId++) {
            var material = new THREE.MeshLambertMaterial({ color: 0xffffff, 
                    ambient: 0x3f3f3f, reflectivity: 0.75 } );
            cube = new THREE.Mesh(geometry, material);            
            cube.position.x = CUBE_SIZE + (nodeId * DISTANCE_CUBE);
            cube.position.z = CUBE_SIZE + (layerId * DISTANCE_LAYER);
            cube.receiveShadow = false;
            cube.scale.y = 0.01;
            scene.add(cube);
            grid.push(cube);
        }
    }

    // Create labeling (for each layer)
    var z = DISTANCE_CUBE;
    scene.add(createText("Input", -TEXT_LENGTH / 2, 0, z));
    z = DISTANCE_CUBE + (maxCols - 1) * DISTANCE_LAYER;
    scene.add(createText("Output", -TEXT_LENGTH / 2, 0, z));
    z = DISTANCE_CUBE + maxCols * DISTANCE_LAYER;
    scene.add(createText("Expected", -TEXT_LENGTH / 2, 0,z ));
    for (var layerId = 1; layerId < maxCols - 1; layerId++) {
        z = DISTANCE_CUBE + layerId * DISTANCE_LAYER;
        scene.add(createText("Hidden", -TEXT_LENGTH / 2, 0, z));
    }

    var container = document.getElementById('drawingArea');
    container.appendChild(renderer.domElement);

    // Support move with keyboard
    window.addEventListener("keydown", doKeyDown, true);

    // Support window resize
    var resizeCallback = function () {
        var offsetHeight = document.getElementById('header').clientHeight + 90;
        var devicePixelRatio = window.devicePixelRatio || 1;
        var width = window.innerWidth * devicePixelRatio;
        var height = (window.innerHeight - offsetHeight)* devicePixelRatio;
        renderer.setSize(width, height);
        renderer.domElement.style.width = window.innerWidth + 'px';
        renderer.domElement.style.height = (window.innerHeight - offsetHeight) + 'px';
        camera.updateProjectionMatrix();
    }
    window.addEventListener('resize', resizeCallback, false);
    resizeCallback();

    // Do all this just once
    initReady = true;
}

// Determines the maximal number of layers and nodes
//
function calulateModelDimensions() {
    maxCols = model.layers.length;
    maxRows = 0;
    for (var layerId = 0; layerId < model.layers.length; layerId++) {
        maxRows = Math.max(model.layers[layerId].nodes.length, maxRows);
    }
    sizeres = DISTANCE_CUBE * (Math.max(maxRows, maxCols));
    halfsizeres = sizeres / 2;
}

// Helper for coloring the nodes (part 1)
//
function getColorHex(value) {
    frequency = 2.0;
    red = Math.sin(2 - frequency * value) * 127 + 128;
    green = Math.sin(1 - frequency * value) * 127 + 128;
    blue = Math.sin(4 - frequency * value) * 127 + 128;
    return '0x' + integerToHex(red) + integerToHex(green) + integerToHex(blue);
}

// Helper for coloring the nodes (part 2)
//
function integerToHex(n) {
    n = Math.max(0, Math.min(parseInt(n, 10), 255));
    charFirst = "0123456789ABCDEF".charAt((n - n % 16) / 16);
    charSecond = "0123456789ABCDEF".charAt(n % 16);
    return charFirst + charSecond;
}

// Move the graphic and camera with arrow keys
//
function doKeyDown(e) {
    delta = 10;
    if (e.keyIdentifier == "Right") {
        deltaX -= delta;
        camera.position.x = camera.position.x - delta;
    } else if (e.keyIdentifier == "Left") {
        deltaX += delta;
        camera.position.x = camera.position.x + delta;
    } else if (e.keyIdentifier == "Down") {
        deltaZ -= delta;
        camera.position.z = camera.position.z - delta;
    } else if (e.keyIdentifier == "Up") {
        deltaZ += delta;
        camera.position.z = camera.position.z + delta;
    } else if (e.keyIdentifier == "PageDown") {
        deltaY += delta;
        camera.position.y = camera.position.y + delta;
    } else if (e.keyIdentifier == "PageUp") {
        deltaY -= delta;
        camera.position.y = camera.position.y - delta;
    }
    camera.lookAt(new THREE.Vector3(halfsizeres + deltaX, 
            halfsizeres / 2 + deltaY, halfsizeres + deltaZ));
}

// Helper for drawing the layer labeling 
// 
function createText(text, x, y, z) {

    var textHolder = document.createElement('canvas');
    var ctext = textHolder.getContext('2d');
    textHolder.width = TEXT_LENGTH;
    textHolder.height = TEXT_HEIGHT;
    ctext.fillStyle = 'white';
    ctext.font = '28px Arial';
    ctext.textAlign = 'right';
    ctext.fillText(text, TEXT_LENGTH - 2, TEXT_HEIGHT - 6);

    var tex = new THREE.Texture(textHolder);
    var mat = new THREE.MeshBasicMaterial({
        map: tex,
        overdraw: true
    });
    mat.transparent = true;
    mat.map.needsUpdate = true;
    mat.depthTest = true;

    var textBoard = new THREE.Mesh(
            new THREE.PlaneGeometry(textHolder.width, textHolder.height), 
            mat);
    textBoard.position.x = x;
    textBoard.position.y = y;
    textBoard.position.z = z;
    textBoard.rotation.x = -Math.PI / 4;
    textBoard.dynamic = true;
    textBoard.doubleSided = true;

    return textBoard;
}

// File #8: Jsonhelper.js
function createObjects(JSONText) {
   // Start public domain parseJSON block
   (function (s) {
      // This prototype has been released into the Public Domain, 2007-03-20; 
      // Original Authorship: Douglas Crockford, Originating Website:
      // http://www.JSON.org, Originating URL : http://www.JSON.org/JSON.js
      var m = {
         '\b': '\\b',
         '\t': '\\t',
         '\n': '\\n',
         '\f': '\\f',
         '\r': '\\r',
         '"': '\\"',
         '\\': '\\\\'
      };
      s.parseJSON = function (filter) {
         try {
            if (/^("(\\.|[^"\\\n\r])*?"|[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t])+?$/.test(this)) {
               var j = eval('(' + this + ')');
               if (typeof filter === 'function') {
                  function walk(k, v) {
                     if (v && typeof v === 'object') {
                        for (var i in v) {
                           if (v.hasOwnProperty(i)) {
                              v[i] = walk(i, v[i]);
                           }
                        }
                     }
                     return filter(k, v);
                  }
                  j = walk('', j);
               }
               return j;
            }
         }
         catch (e) {}
         throw new SyntaxError("parseJSON");
      };
   })(String.prototype);
   // End public domain parseJSON block

   return JSONText.parseJSON();
};

// File #9: Webgl_ann_sample.html
<!doctype html>
<html>
   <head>
      <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
      <meta name="viewport" 
            content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0" />
      <style>
         a { color:#009de9; font-family: Helvetica; font-style: normal; 
             font-size:10pt; text-align:left; }            
         h1 { color:#FFF; font-family: Helvetica; font-size:14pt; 
             text-align:left;  padding-left:5px;}            
         .text { color:#FFF; font-family: Helvetica; font-size:10pt; 
             text-align:left;  padding-left:5px; padding-right:5px;}            
         .warning { color:#F00; font-family: Helvetica; font-size:10pt; 
             text-align:left;  padding-left:5px; padding-right:5px;}            
         .main { background:#252525; padding:0px; }   
         .gwt-ProgressBar-shell { border: 2px solid #faf9f7; 
             border-right: 2px solid #848280; border-bottom: 2px solid #848280; 
             background-color: #AAAAAA; height: 14pt; width: 50%; }
         .gwt-ProgressBar-shell .gwt-ProgressBar-bar { background-color: #67A7E3; }
         .gwt-ProgressBar-shell .gwt-ProgressBar-text { padding: 0px; 
             margin: 0px; color: white; }
      </style>
      <title>WebGL Experiment - ANN</title>
      <script type="text/javascript" language="javascript" 
              src="webgl_ann_sample/webgl_ann_sample.nocache.js"></script>
   </head>
   
   <body class="main">
   
      <noscript>
         <div style="width: 22em; position: absolute; left: 50%; 
                margin-left: -11em; color: red; background-color: white; 
                border: 1px solid red; padding: 4px; font-family: sans-serif">
            Your web browser must have JavaScript enabled
            in order for this application to display correctly.
         </div>
      </noscript>
 
      <script type="text/javascript" src="Detector.js"></script>
      <script type="text/javascript" src="three.min.js"></script>
      <script type="text/javascript" src="Jsonhelper.js"></script>     
      <script type="text/javascript" src="Webgl_ann_sample.js"></script>     
       
      <div id="header" >   
          <h1 class="title">Experimental Visualization of Artificial Neural Network with WebGL</h1>
      
          <table align="right" >
              <tr>
                  <div class="text">
                    <a href="https://plus.google.com/u/0/117292523089281814301?rel=author">
                            by Markus Sprunck</a>
                    <p/>
                    This experiment shows an Artificial Neural Network which learns to detect 
                    the frequency of the input signal independent from the phase. The implementation 
                    is based on Three.js (r56), Guava and Google Web Toolkit. GWT helps to translate the 
                    Java code for the Artificial Neural Network into JavaScript, which is then 
                    executed in the browser. The experiment has been developed with 
                    GWT 2.5 and App Engine SDK 1.7.3. It runs best with Chrome Browser. 
                    You may read more and get the source code 
                    <a href="http://www.sw-engineering-candies.com/blog-1/
                        experimental-visualization-of-artificial-neural-network-with-webgl">here</a>.
                </div>
              </tr>
          </table>
      </div>
         
      <div id="drawingArea" ></div>   
   
      <div id="footer" >   
          <table align="right" width="100%">
              <td>
                  <div id="infoLabelContainer1"></div>
                <div id="infoLabelContainer2"></div>
                <div id="infoLabelContainer3" class="warning"></div>
            </td>
            <td width="90px" id="resetButtonContainer"></td>
            <td width="90px" id="trainButtonContainer"></td>
           </table>
      </div>
     
   </body>
</html>
Please, do not hesitate to contact me if you have any ideas for improvement and/or you find a bug in the sample code.  

Change History

 Revision  Date  Author  Description
 1.0  Feb 25, 2013  Markus Sprunck  first version
 1.1  Feb 28, 2013  Markus Sprunck  minor improvements and added some links
 1.2  Mar 8, 2013  Markus Sprunck  changes to support 'THREE.WebGLRenderer 56'
 1.3  Apr 8, 2013  Markus Sprunck  code now on GitHub and update to appengine-java-sdk-1.7.6

Google+ Comments

You may press the +1 button to share and/or comment