10
\$\begingroup\$

I am designing a very basic layered neural network in Swift as an exercise. I currently got the network evaluating the response for a given stimulus by propagating the stimulus forwards through the layers and collecting the response at the output layer, which is assumed to be a single neuron.

My next step will be to provide back propagation, however I don't believe that the remaining coding will introduce many new linguistic constructs, so now is a good time to evaluate my coding style.

I have a network containing a list of layers, and each layer contains a list of neurons. I've made Net and Layer classes, so as to allow layers to form a doubly linked list, which should ease traversal. I've made Neuron a struct, as I learned each class instance requires its own individual heap allocation.

There are two places I think I can make an improvement, marked by ???1 and ???2. However I can't figure out how to implement.

Also, maybe zooming out there is some improvement I could make to the overall network design.

Working code is on SwiftStub.

func random_01() -> Double {
    return Double(arc4random()) / Double(UInt32.max)
}

struct Neuron {
    var weights = [Double] ()
    var bias    = random_01()

    unowned let net   : Net
    unowned let layer : Layer
            let index : Int

    var sum = 0.0

    init( net:Net, layer:Layer, index:Int )
    {
        (self.net, self.layer, self.index) = (net, layer, index)

        guard let prev = layer.prev else { return }

        weights = prev.neurons.map { _ in random_01() }
        /* Could also do:
            for _ in 0 ..< prev.neurons.count { weights.append( random_01() ) }
            weights = prev.neurons.map { (_:AnyObject) -> Double in return random_01() }
            weights = (0..<prev.neurons.count).map { _ in random_01() }
        */
    }

    var output = 0.0

    mutating func calc()
    {
        sum = bias

        // input-layer neurons have no inputs, just bias
        if let prev = layer.prev {
            for (i,N) in prev.neurons.enumerate() {
                sum += N.output * weights[i] // reduce ???1
            }
        }

        func sigmoid(x:Double)->Double {  return 1.0 / ( 1.0 + exp(-x) )  }

        output = sigmoid(sum)
    }
}

final class Layer
{
    unowned let net: Net

    weak var prev: Layer? = nil
    weak var next: Layer? = nil

    let index: Int

    var neurons = [Neuron] ()

    init( net:Net, index:Int )
    {
        (self.net, self.index) = (net, index)
    }

    // first initialise all layers, then populate all layers
    func populate()
    {
        if index > 0                  { prev = net.layers[index-1] }
        if index < net.layers.count-1 { next = net.layers[index+1] }

        for i in 0 ..< net.neuronsInLayer[index] {
            neurons.append(  Neuron( net:net, layer:self, index:i )  )
        }
    }

    func forward_propagate()
    {
        // output layer?
        guard let next = next else { return }

        // update next layer from this layer's output
        for i in 0 ..< next.neurons.count {
            next.neurons[i].calc()
        }
        //next.neurons.map { $0.calc() } ???2

        next.forward_propagate()
    }
}


public final class Net
{
    var layers = [Layer] ()
    var neuronsInLayer = [Int] ()

    public init( layerSizes: [Int] )
    {
        neuronsInLayer = layerSizes

        // create layers
        for i in 0 ..< layerSizes.count {
            layers.append( Layer(net:self,  index:i) )
        }

        // link
        for l in layers { l.populate() }
        //or: layers.map { $0.populate() }
    }

    func input( x: [Double] ) -> Double
    {
        guard let  inputLayer = layers.first else { return -2.0 }
        guard let outputLayer = layers.last  else { return -3.0 }

        assert(  inputLayer.neurons.count == x.count
             && outputLayer.neurons.count == 1  )

        for i in 0 ..< x.count {
            inputLayer.neurons[i].bias = x[i]
        }

        inputLayer.forward_propagate()

        return outputLayer.neurons.first!.output
    }


    public func train( inputs: [Double], _ expected: Double )
    {
        let actualAnswer = input(inputs)
        let error = actualAnswer - expected

        print( error )
    }
}


var net = Net( layerSizes: [3,2,1] )

net.train( [1,0,0], 1 )
net.train( [0,1,0], 1 )
net.train( [0,0,1], 1 )

net.train( [1,1,0], 0 )
net.train( [0,1,1], 0 )
net.train( [1,0,1], 0 )
\$\endgroup\$
  • 5
    \$\begingroup\$ Review answers are allowed to review any or all aspects of your code. About the whitespace - as someone new to swift it is probably a good thing to be introduced to what industry standards (such as they are) are for code style in Swift. I have deleted all other comments on this post because they were obsolete/redundant. \$\endgroup\$ – rolfl Jun 30 '15 at 13:56
14
\$\begingroup\$

assert(false, "Don't use assert like this!")

func input( x: [Double] ) -> Double
{
    guard let  inputLayer = layers.first else { return -2.0 }
    guard let outputLayer = layers.last  else { return -3.0 }

    assert(  inputLayer.neurons.count == x.count
         && outputLayer.neurons.count == 1  )

The logic for this method is all over the place.

First of all, you will never return -3.0 from this method. If last were going to return nil, then first already returned nil. For an array with just one thing in it, first and last return the same thing.

I don't understand (because it's not documented) why we're somehow okay returning some magic numbers if our layers array is empty, but if the last layer has more than 1 neuron or if the user has not guessed the number of neurons in the first array and sent in a properly sized array, we're throwing an assertion!?!?

Asserts should be reserved for things we can guarantee at compile time. Moreover, they should be reserved for things that we've already tested for and (hopefully) guaranteed to be true.

The exactly correct way to handle this (there are many options) depends on a great number of things (the behavior should be documented either way) (I can't make recommendations without more information), but in no way is this assert going to be correct, given that your code makes no effort to first verify this to be true.

Moreover, when you do use assert, you should endeavor to include a message with it explaining the assertion failure so that any user has some clue where to begin in resolving the issue.


Spacing & Formatting

Things like this can make your code easier to read:

var weights = [Double] ()
var bias    = random_01()

unowned let net   : Net
unowned let layer : Layer
        let index : Int

But it quickly becomes difficult to maintain. Each time we add a new variable, if its name is longer than any of the existing variables, we have to go increase the spacing for all of the other declarations. And no IDE that I know of (certainly not Swift) is going to put any effort into helping you align these quite the way you want.

Ultimately, the most appropriate thing to do to make these variable declarations more readable (without hampering maintainability drastically like this) is to set your IDE up with good syntax coloring. With most syntax coloring schemes, nothing else will be the same color as the variable name (even Stack Exchange catches everything but unowned and your function name).

The extra spaces you've scattered throughout your code don't do anything to improve the readability. If anything, for me, they hurt it, as they are distracting. Instead of thinking about the code, I am stuck thinking "Why are these spaces randomly scattered throughout?"

Here are some examples:

var weights = [Double] ()

Should just be:

var weights = [Double]()

init( net:Net, layer:Layer, index:Int )

Should be spaced more like this:

init(net: Net, layer: Layer, index: Int)

The space between the : and the type is optional (I prefer it personally), but the space after the , should definitely be there, and the spaces for the parenthesis definitely should not (it's distracting).


layers.append( Layer(net:self,  index:i) )

These extra spaces are unnecessary. This should just be:

layers.append(Layer(net:self, index:i))

If the double parenthesis are confusing at the end, the right way to fix that is by breaking this onto multiple lines:

let layer = Layer(net:self, index:i)
layers.append(layer)

guard let  inputLayer = layers.first else { return -2.0 }
guard let outputLayer = layers.last  else { return -3.0 }

The spacing here is all over the place, and it's going to suffer the same problem as the first section I talked about. Adding another line in this group or making other sorts of modifications means we have to go fix up spacing of lots of lines potentially. I don't mind keeping the else part all on the same line since it's so simple, but let's not play this game with the spaces:

guard let inputLayer = layers.first else { return -2.0 }
guard let outputLayer = layers.last else { return -3.0 }
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.