Created
March 17, 2016 03:21
-
-
Save steverichey/592073bcda918b47a4b5 to your computer and use it in GitHub Desktop.
NeuralSwift - A very simple neural network written in Swift.
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
// see http://lumiverse.io/series/neural-networks-demystified | |
import Foundation | |
let startTime = NSDate() | |
defer { | |
let endTime = NSDate() | |
print("Total time \(endTime.timeIntervalSinceDate(startTime))") | |
} | |
extension Array { | |
subscript (safe index: Int) -> Element? { | |
return indices ~= index ? self[index] : nil | |
} | |
} | |
extension _ArrayType where Generator.Element == Double { | |
var sum: Double { | |
return reduce(0) { $0 + $1 } | |
} | |
} | |
func randd() -> Double { | |
return Double(rand()) / Double(RAND_MAX) | |
} | |
func rand(width: Int, _ height: Int) -> Matrix { | |
var result = Matrix(width: width, height: height) | |
result.fill { randd() } | |
return result | |
} | |
typealias Matrix = [[Double]] | |
// We can't do an extension for Matrix any other way | |
extension _ArrayType where Generator.Element == [Double] { | |
init(width: Int, height: Int, value: Double = 0) { | |
let row = [Double](count: width, repeatedValue: value) | |
self.init(count: height, repeatedValue: row) | |
} | |
var width: Int { | |
return self[0].count | |
} | |
var height: Int { | |
return count | |
} | |
var rows: Self { | |
return self | |
} | |
var columns: Matrix { | |
return transpose() | |
} | |
var sum: Double { | |
return reduce(0) { $0 + $1.sum} | |
} | |
mutating func fill(withMethod method: () -> Double) { | |
for x in 0..<width { | |
for y in 0..<height { | |
self[y][x] = method() | |
} | |
} | |
} | |
func transpose() -> Matrix { | |
var result = Matrix(width: height, height: width) | |
for x in 0..<width { | |
for y in 0..<height { | |
result[x][y] = self[y][x] | |
} | |
} | |
return result | |
} | |
mutating func set(index index: Int, toValue value: Double) { | |
var x = index | |
var y = 0 | |
while (x >= width) { | |
x -= width | |
y += 1 | |
} | |
self[y][x] = value | |
} | |
} | |
func exp(value: [Double]) -> [Double] { | |
return value.map { exp($0) } | |
} | |
func exp(value: Matrix) -> Matrix { | |
return value.map { exp($0) } | |
} | |
prefix func - (value: [Double]) -> [Double] { | |
return value.map { -$0 } | |
} | |
prefix func - (value: Matrix) -> Matrix { | |
return value.map { -$0 } | |
} | |
func + (lhs: Double, rhs: [Double]) -> [Double] { | |
return rhs.map { lhs + $0 } | |
} | |
func + (lhs: Double, rhs: Matrix) -> Matrix { | |
return rhs.map { lhs + $0 } | |
} | |
func * (lhs: Double, rhs: [Double]) -> [Double] { | |
return rhs.map { lhs * $0 } | |
} | |
func * (lhs: Double, rhs: Matrix) -> Matrix { | |
return rhs.map { lhs * $0 } | |
} | |
func / (lhs: Double, rhs: [Double]) -> [Double] { | |
return rhs.map { lhs / $0 } | |
} | |
func / (lhs: Double, rhs: Matrix) -> Matrix { | |
return rhs.map { lhs / $0 } | |
} | |
func sigmoid(z: Double) -> Double { | |
return 1.0 / (1.0 + exp(-z)) | |
} | |
func sigmoid(z: [Double]) -> [Double] { | |
return 1.0 / (1.0 + exp(-z)) | |
} | |
func sigmoid(z: Matrix) -> Matrix { | |
return 1.0 / (1.0 + exp(-z)) | |
} | |
/** | |
Requires that # of lhs columns equal # of rhs rows. | |
*/ | |
func * (lhs: Matrix, rhs: Matrix) -> Matrix { | |
assert(lhs.columns.count == rhs.rows.count) | |
let width = lhs.rows.count | |
let height = rhs.columns.count | |
var result = Matrix(width: width, height: height) | |
var index = 0 | |
for row in lhs.rows { | |
for column in rhs.columns { | |
var total = 0.0 | |
for (index, lhsElement) in row.enumerate() { | |
total += lhsElement * column[index] | |
} | |
result.set(index: index, toValue: total) | |
index++ | |
} | |
} | |
return result | |
} | |
/** | |
Creates a (very dumb) neural network with no training, but with forward propagation. | |
*/ | |
class NeuralNetwork { | |
/** | |
The number of neurons in the input layer. | |
*/ | |
let inputLayerSize: Int | |
/** | |
The number of neurons in the hidden layer. | |
Assumes a single hidden layer. | |
*/ | |
let hiddenLayerSize: Int | |
/** | |
The number of neurons in the output layer. | |
*/ | |
let outputLayerSize: Int | |
/** | |
First-layer weights. | |
*/ | |
var w1: Matrix | |
/** | |
Second-layer weights. | |
*/ | |
var w2: Matrix | |
/** | |
The activity of our first layer. | |
*/ | |
var z2: Matrix = [] | |
/** | |
The activity of our second layer. | |
*/ | |
var a2: Matrix = [] | |
/** | |
The activity of our third layer. | |
*/ | |
var z3: Matrix = [] | |
init(inputLayerSize: Int, outputLayerSize: Int, hiddenLayerSize: Int) { | |
self.inputLayerSize = inputLayerSize | |
self.outputLayerSize = outputLayerSize | |
self.hiddenLayerSize = hiddenLayerSize | |
w1 = rand(hiddenLayerSize, inputLayerSize) | |
w2 = rand(outputLayerSize, hiddenLayerSize) | |
} | |
func forward(x: Matrix) -> Matrix { | |
// Propagate inputs through first layer | |
z2 = x * w1 | |
// Apply activation function | |
a2 = sigmoid(z2) | |
// Propagate values through third layer | |
z3 = a2 * w2 | |
// Apply activation function | |
return sigmoid(z3) | |
} | |
} | |
let inputX: Matrix = [[3, 5], [5, 1], [10, 2]] | |
let inputY: Matrix = [[0.75], [0.82], [0.93]] | |
let network = NeuralNetwork(inputLayerSize: 2, outputLayerSize: 1, hiddenLayerSize: 3) | |
let yHat = network.forward(inputX) | |
print("y is \(inputY)") | |
print("yHat is \(yHat)") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
you made an error. it should be in row 153
let height = lhs.rows.count
let width = rhs.columns.count