Haskell’s benevolent influence

without even a mention of the ‘M’ word

One of the things that attracted me to SPA 2013 was the Neural Net workshop on the Sunday. It turned out to be even better than I hoped; although we didn’t get quite as far as we could have done.

The first thing we wanted to do was implement a Neuron. In this particular case, a neuron took two inputs (let’s say they are doubles, for the time being) and then performs a computation on those inputs to determine a single output.

In this case, our neuron’s computation was as follows:

output = inputOne * weightOne + inputTwo * weightTwo > threshold ? 1.0 : 0.0

My pair and I quickly put together an implementation of this in Java that looked a bit like this:

package net.digihippo;

public class NeuronImpl implements Neuron {
    private final double threshold;
    private final double[] weights;

    public NeuronImpl(double threshold, double[] weights) {
        this.threshold = threshold;
        this.weights = weights;
    }

    @Override
    public double run(double[] inputs) {
        double sum = 0.0;
        for (int i = 0; i < weights.length; ++i) {
            sum += (weights[i] * inputs[i]);
        }
        return sum > threshold ? 1.0 : 0.0;
    }
}

You could obviously implement this as a static function, but suitable hints were given that the threshold and weights were properties of a given neuron, and so fields it was.

Many connected neurons – a neural network

To cut a long story short; we eventually want to plumb several neurons together to form a network. A single neuron is limited in the functions it can express, for reasons I won’t explore in this post; networks of them do not suffer in the same way.

Handily, once the weight and threshold of a given neuron are fields, the call to a neuron matches that to a network of neurons. But we needed a way to plumb a tree of neurons together, like so:

inputOne ->|          |
           | neuron 1 |_  
inputTwo ->|          | \_|          |
                          | neuron 3 |---> output
inputOne ->|          |  _|          |
           | neuron 2 |_/ 
inputTwo ->|          |

N.B Each neuron on the far left of the network is passed the same pair of inputs.

We can see that each Neuron either has precisely two ancestors or none at all (proof by terrible ASCII picture + some induction). A light, well, actually it was more a series of small, excited explosions, went off in my head. Now I just needed to remember that Node a Tree a Tree a magic and I would be laughing. My pair was working in Java, however, and somewhere between the magic and the .java files being edited in Eclipse, something wasn’t working. I needed to get it onto the screen before I could translate it across. Ten minutes or so later, I had this Haskell version of Neuron (which incorporates the concept of a network):

data Neuron = Neuron Double Double Double
            | NeuralNetwork Neuron Neuron Neuron
              deriving (Show)

runNet :: Double -> Double -> Neuron -> Double
runNet i1 i2 (Neuron t w1 w2) = bool2doub $ (i1 * w1) + (i2 * w2) > t
runNet i1 i2 (NeuralNetwork n n1 n2) = runNet n (runNet i1 i2 n1) (runNet i1 i2 n2)

bool2doub :: Bool -> Double
bool2doub True = 1.0
bool2doub False = 0.0

Side note: Feedback on any Haskell naïveté within this post would be greatly appreciated!

As you can see we have a neat, recursively defined, data type, and we defer any actual neuron run until we reach a “leaf” (which might have been a better name for the first constructor) at the far left of the ASCII diagram from before. It should be possible to translate this to Java without too much difficulty; we can map the data type Neuron to an interface, and the two constructors to two implementations of that interface.

Compiling Haskell to Java via human.

The sharper eyed readers will have noticed that we were on the way to doing this already in the Java source; NeuronImpl implements Neuron. An accidental bit of foreshadowing there, apologies. So, our NeuronImpl doesn’t have to change, but what does NeuralNetwork look like?

N.B Apologies for the use of above and below – they are extremely poor names for the two parent neurons.

package net.digihippo;

public class NeuralNetwork implements Neuron {
    private final Neuron neuron;
    private final Neuron above;
    private final Neuron below;

    public NeuralNetwork(Neuron neuron, Neuron above, Neuron below) {
        this.neuron = neuron;
        this.above = above;
        this.below = below;
    }

    @Override
    public double run(double[] inputs) {
        return neuron.run(new double[]{above.run(inputs), below.run(inputs)});
    }
}

Remarkably similar, it turns out! The Haskell version is somewhat terser, but the idea appears to have survived translation. It’s quite pleasing to know that lessons from Haskell can (albeit in this simple example) be applied in less fun languages. One wonders if there are places where we cannot translate Haskell data types and their constructors into interfaces and their implementations.

A new requirement arrives

Two inputs? Bah! We laugh in the face of two inputs. We want to be able to deal with n inputs. N.B This means that each level of the network has n times more neurons than the next. Perhaps this might be where our Haskell -> Java translation starts to creak? Let’s add the idea of multiple inputs to our Haskell implementation.

data Neuron = Neuron Double [Double]
            | NeuralNetwork Neuron [Neuron]
              deriving (Show)

runNet :: [Double] -> Neuron -> Double
runNet inputs (Neuron t weights) = bool2doub $ (sum (zipWith (*) inputs weights)) > t
runNet inputs (NeuralNetwork n ns) = runNet (map (runNet inputs) ns) n

Nice and simple – if anything, the variable input version is actually simpler than the fixed, two input version. The use of sum (zipWith ... is particularly pleasing, to my eye. Let’s run it through the mangler and see what we get out in Java…

package net.digihippo;

public class NeuralNetwork implements Neuron {
    private final Neuron neuron;
    private final Neuron[] parents;

    public NeuralNetwork(Neuron neuron, Neuron[] parents) {
        this.neuron = neuron;
        this.parents = parents;
    }

    @Override
    public double run(double[] inputs) {
        final double[] results = new double[inputs.length];
        for (int i = 0; i < inputs.length; ++i) results[i] = parents[i].run(inputs);
        return neuron.run(results);
    }
}

Not bad

Conclusion

Translating a well meaning Haskell implementation into Java:

  • can work
  • might even be a good idea.
  • involves writing some Haskell, and is therefore good for you.

A horrible warning

There is recursion here. Mind how you go.

Leave a Reply