Last active
May 17, 2020 21:47
-
-
Save blorsch/37c610dfe86a719593070edd73a66d44 to your computer and use it in GitHub Desktop.
A single neuron neural network (perceptron) written entirely in Swift with only Foundation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
//Swift version of https://github.com/gitshanks/simpleneuralnet/blob/master/neuralnet.py | |
//In Swift you can't easily add, multiply arrays/matrices etc, so I had write it manually and some of them are kinda ugly | |
//Used for my 2020 Marin Academy Math Night Presentation: "Neural Networks and Their Applications in Text Generation" | |
//Pattern to the data: output = column 2 of input | |
var inputs = [[0,0,1], [1,1,1], [1,0,1], [0,1,1]] as [[Double]] | |
var outputs = [[0], [1], [0], [1]] as [[Double]] | |
//3x1 matrix of weights randomly initialized to be between -1 and 1 | |
var weights = ((0..<3).map( {_ in Double.random(in: -1...1)})).map({[$0]}) | |
func trainNeuron(trainingInputs: [[Double]], trainingOutputs: [[Double]], i:Int) { | |
for _ in 0..<i { | |
//get output - sigmoid(inputs • weights) | |
let outputs = getOutput(inputs: trainingInputs, weights: weights) | |
//calculate error (correct output - actual outputs) | |
let errors: [[Double]] = (0..<trainingOutputs.count).map { i in | |
let error = trainingOutputs[i][0] - outputs[i][0] | |
return [error] | |
} | |
//multiply errors and siggrad of outputs | |
let grad = sigGrad(input: outputs) | |
let multiplied: [[Double]] = (0..<grad.count).map { i in | |
let x = (grad[i][0])*(errors[i][0]) | |
return [x] | |
} | |
//calculate how much to adjust each weight by | |
let adjustments = dot(m1: transpose(input: trainingInputs), m2: multiplied) | |
//adjust each weight by that | |
let newWeights: [[Double]] = (0..<weights.count).map { i in | |
let x = weights[i][0] + adjustments[i][0] | |
return [x] | |
} | |
weights = newWeights | |
} | |
} | |
//sigmoid(inputs • weights) | |
func getOutput(inputs: [[Double]], weights: [[Double]]) -> [[Double]] { | |
return sigmoidArray(input: dot(m1: inputs, m2: weights)) | |
} | |
//swift doesnt really have a dot product function so lets write our own. m1 = AxN, m2 = Nx1 | |
func dot(m1: [[Double]], m2: [[Double]]) -> [[Double]] { | |
var dotted = [[Double]]() | |
for i in 0..<m1.count { //for each frow of m1 multiply each value by the weights | |
var sum = 0.0 | |
for i2 in 0..<m1[0].count { | |
sum += m1[i][i2] * m2[i2][0] | |
} | |
dotted.append([sum]) | |
} | |
return dotted | |
} | |
//sigmoid 1/(1+e^x) | |
func sigmoid(input: Double) -> Double { | |
return 1 / (1 + pow(Darwin.M_E, -1*input)) | |
} | |
//derivative of sigmoid (x*(1-x) | |
func sigGrad(input: [[Double]]) -> [[Double]] { | |
return input.map({[($0[0]) * (1-($0[0]))]}) | |
} | |
//applys sigmoid to every element in Nx1 array | |
func sigmoidArray(input: [[Double]]) -> [[Double]] { | |
return input.map({[sigmoid(input: $0[0])]}) | |
} | |
//transpose array - https://stackoverflow.com/a/32922962/4777497 | |
func transpose<T>(input: [[T]]) -> [[T]] { | |
if input.isEmpty{ | |
return [[T]]() | |
} | |
let count = input[0].count | |
var out = [[T]](repeating: [T](), count: count) | |
for outer in input { | |
for (index, inner) in outer.enumerated() { | |
out[index].append(inner) | |
} | |
} | |
return out | |
} | |
//starting weights | |
print("Starting weights: \(weights)") | |
//train | |
trainNeuron(trainingInputs: inputs, trainingOutputs: outputs, i: 10000) | |
//trained weights | |
print("Trained weights: \(weights)") | |
//validate | |
let validatingOutput = getOutput(inputs: [[1,1,0]], weights: weights) | |
print(validatingOutput) //should be 1 (column 2 is 1 and that was the pattern |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment