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 )