Last active
January 5, 2017 01:42
-
-
Save naru-jpn/6a41cf4920407f4b244997c4dc87cbe3 to your computer and use it in GitHub Desktop.
Neural Network in Swift with perceptron example (not yet: back propagation)
This file contains hidden or 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 Cocoa | |
/** | |
* Neural Network | |
* | |
* …[Cell]━[Axon]┳[Dendrite]━[Cell]━[Axon]┳… | |
* ┣[Dendrite]━[Cell]━[Axon]┳… | |
* ⋮ | |
* ┗[Dendrite]━[Cell]━[Axon]┳… | |
**/ | |
/// 細胞体 | |
class Cell: Equatable { | |
/// 出力時に用いる活性化関数 | |
enum ActivationFunctionType { | |
/// 恒等関数 | |
case identity | |
/// ステップ関数 | |
case step | |
/// ソフトマックス関数 | |
case softmax | |
} | |
init(bias: Double, initialConnectionWeight: Double = 1.0, activationFunctionType: ActivationFunctionType) { | |
self.bias = bias | |
self.initialConnectionWeight = initialConnectionWeight | |
self.activationFunctionType = activationFunctionType | |
} | |
// MARK: elements | |
/// 軸索 | |
final let axon: Axon = Axon() | |
/// セルに接続されている樹状突起 | |
final var dendrites: [Dendrite] = [] | |
/// バイアス | |
final let bias: Double | |
/// 樹状突起に設定されるウェイトの初期値 | |
final let initialConnectionWeight: Double | |
/// セルに接続されている樹状突起から送られてくる信号の合計 | |
final internal var pool: Double = 0.0 | |
/// セルの識別子 | |
final let identifier: String = NSUUID().uuidString | |
private(set) var activationFunctionType: ActivationFunctionType | |
private var allSources: [Double] = [] | |
/// 活性化関数 | |
func activate() -> Double { | |
switch self.activationFunctionType { | |
case .identity: | |
return self.source | |
case .step: | |
return self.source <= 0 ? 0.0 : 1.0 | |
case .softmax: | |
let max: Double = self.allSources.max()! | |
let total: Double = self.allSources.reduce(0.0) { | |
$0 + exp($1-max) | |
} | |
return exp(self.source-max)/total | |
} | |
} | |
func activate(with allSources: [Double]) -> Double { | |
self.allSources = allSources | |
return self.activate() | |
} | |
// MARK: control source value | |
/// 活性化関数に渡される値 | |
final var source: Double { | |
return bias + self.pool | |
} | |
/// 活性化関数を通った値 | |
internal var activatedSource: Double { | |
return self.activate(with: self.allSources) | |
} | |
/// 信号の蓄積 | |
/// - parameter value: 蓄積される信号 | |
final func accumulate(value: Double) { | |
self.pool += value | |
} | |
/// 蓄積された信号のクリア | |
final func clearPool() { | |
self.pool = 0.0 | |
} | |
// MARK: connection | |
/// 他のセルと接続する | |
/// - parameter cell: 接続するセル | |
/// - parameter weight: 軸索のWeight | |
/// - returns: 接続できたかどうか | |
@discardableResult | |
final func connect(to cell: Cell, with weight: Double) -> Bool { | |
guard cell != self else { | |
debugPrint("\(self): Connected cell is the same instance with this cell.") | |
return false | |
} | |
if let dendrite = self.axon.connect(to: cell, with: weight) { | |
cell.dendrites.append(dendrite) | |
return true | |
} else { | |
return false | |
} | |
} | |
/// 他のセルと接続する | |
/// - parameter cell: 接続するセル | |
/// - returns: 接続できたかどうか | |
@discardableResult | |
final func connect(to cell: Cell) -> Bool { | |
return self.connect(to: cell, with: self.initialConnectionWeight) | |
} | |
// MARK: fire | |
/// 蓄積されている信号を次の層へ伝える | |
final func fire() { | |
self.axon.propagate(value: self.activatedSource) | |
} | |
} | |
func ==(lhs: Cell, rhs: Cell) -> Bool { | |
return lhs.identifier == rhs.identifier | |
} | |
/// 軸索 | |
class Axon { | |
// MARK: elements | |
/// 樹状突起 | |
private var dendrites: [Dendrite] = [] | |
// MARK: connection | |
/// 指定されたセルに既に接続しているかどうか | |
/// - parameter cell: セル | |
final func isConnected(to cell: Cell) -> Bool { | |
for dendrite in dendrites { | |
if dendrite.connectedCell == cell { | |
return true | |
} | |
} | |
return false | |
} | |
@discardableResult | |
final func connect(to cell: Cell, with weight: Double) -> Dendrite? { | |
guard !self.isConnected(to: cell) else { | |
debugPrint("\(self): This cell is already connected to this cell.") | |
return nil | |
} | |
let dendrite: Dendrite = Dendrite(weight: weight, connectedCell: cell) | |
self.dendrites.append(dendrite) | |
return dendrite | |
} | |
// MARK: propagation | |
final func propagate(value: Double) { | |
for dendrite in self.dendrites { | |
dendrite.propagate(value: value) | |
} | |
} | |
} | |
/// 樹状突起 | |
class Dendrite { | |
init(weight: Double, connectedCell: Cell) { | |
self.weight = weight | |
self.connectedCell = connectedCell | |
} | |
// MARK: elements | |
final var weight: Double | |
final let connectedCell: Cell | |
// MARK: propagation | |
final func propagate(value: Double) { | |
self.connectedCell.accumulate(value: value * self.weight) | |
} | |
} | |
// ----------------------------------------------------------------------------------- | |
/// 入力信号用 | |
class InputCell: Cell { | |
init(source: Double = 0.0, initialConnectionWeight: Double) { | |
super.init(bias: 0.0, initialConnectionWeight: initialConnectionWeight, activationFunctionType: .identity) | |
self.accumulate(value: source) | |
} | |
// MARK: input value | |
func set(inputValue: Double) { | |
self.pool = inputValue | |
} | |
} | |
/// パーセプトロン | |
class Perceptron: Cell { | |
init(bias: Double, initialConnectionWeight: Double) { | |
super.init(bias: bias, initialConnectionWeight: initialConnectionWeight, activationFunctionType: .step) | |
} | |
} | |
//let input1: InputCell = InputCell(source: 1.0, initialConnectionWeight: 0.5) | |
//let input2: InputCell = InputCell(source: 1.0, initialConnectionWeight: 0.5) | |
//let and: Perceptron = Perceptron(bias: -0.7) | |
// | |
//input1.connect(to: and) | |
//input2.connect(to: and) | |
// | |
//input1.fire() | |
//input2.fire() | |
// | |
//_ = and.source | |
//_ = and.activatedSource | |
// ---------------------------------------------------------------------- | |
class OutputCell: Cell { | |
} | |
/// Brain | |
class Brain: CustomDebugStringConvertible { | |
init(hiddenLayerNames: [String]) { | |
self.hiddenLayerNames = hiddenLayerNames | |
for name in self.hiddenLayerNames { | |
self.hiddenLayers[name] = [] | |
} | |
} | |
// MARK: elements | |
private var inputCells: [InputCell] = [] | |
private var hiddenLayerNames: [String] = [] | |
private var hiddenLayers: [String: [Cell]] = [:] | |
private var outputCells: [OutputCell] = [] | |
private(set) var outputs: [Double]? | |
// MARK: append cells | |
/// Append input cell. | |
/// - parameter inputCell: input cell to append | |
func append(inputCell: InputCell) { | |
// check duplication | |
guard !self.inputCells.contains(inputCell) else { | |
debugPrint("\(self): Same input cell is already appended.") | |
return | |
} | |
self.inputCells.append(inputCell) | |
} | |
/// Append input cells. | |
/// - parameter inputCells: array of input cell to append | |
func append(inputCells: [InputCell]) { | |
// call append function for each input cell to check duplication | |
for inputCell in inputCells { | |
self.append(inputCell: inputCell) | |
} | |
} | |
/// Append cell contained specific hidden layer. | |
/// - parameter cells: cell to append | |
/// - parameter name: name for hidden layer | |
func append(cell: Cell, for name: String) { | |
// check if hidden layer exist | |
guard self.hiddenLayerNames.contains(name) else { | |
debugPrint("\(self): No hidden layer named \(name).") | |
return | |
} | |
// check duplication | |
guard !self.hiddenLayers[name]!.contains(cell) else { | |
debugPrint("\(self): Same cell is already appended.") | |
return | |
} | |
self.hiddenLayers[name]!.append(cell) | |
} | |
/// Append cells contained specific hidden layer. | |
/// - parameter cells: cells to append | |
/// - parameter name: name for hidden layer | |
func append(cells: [Cell], for name: String) { | |
// call append function for each cell to check duplication | |
for cell in cells { | |
self.append(cell: cell, for: name) | |
} | |
} | |
/// Append output cell. | |
/// - parameter outputCell: output cell to append | |
func append(outputCell: OutputCell) { | |
// check duplication | |
guard !self.outputCells.contains(outputCell) else { | |
debugPrint("\(self): Same output cell is already appended.") | |
return | |
} | |
self.outputCells.append(outputCell) | |
} | |
/// Append output cells. | |
/// - parameter outputCell: array of output cell to append | |
func append(outputCells: [OutputCell]) { | |
// call append function for each output cell to check duplication | |
for outputCell in outputCells { | |
self.append(outputCell: outputCell) | |
} | |
} | |
// MARK: configuration | |
/// Connect all cells with cells contained in nearest layer. | |
func connectAllCells() { | |
guard self.inputCells.count > 0 else { | |
debugPrint("\(self): No input cells appended.") | |
return | |
} | |
guard self.outputCells.count > 0 else { | |
debugPrint("\(self): No output cells appended.") | |
return | |
} | |
for name in self.hiddenLayerNames { | |
guard let cells = self.hiddenLayers[name] else { | |
debugPrint("\(self): No hidden layer named \(name)") | |
return | |
} | |
guard cells.count > 0 else { | |
debugPrint("\(self): No cells appended for layer named \(name)") | |
return | |
} | |
} | |
let countOfHiddenLayers: Int = self.hiddenLayerNames.count | |
// configure for each input cells | |
if countOfHiddenLayers == 0 { | |
// connect to each output cells | |
for inputCell in self.inputCells { | |
for outputCell in self.outputCells { | |
inputCell.connect(to: outputCell) | |
} | |
} | |
// no more unconnected cells and return here | |
return | |
} else { | |
// connect to each first hidden layer cells | |
let layerName: String = self.hiddenLayerNames.first! | |
for inputCell in self.inputCells { | |
for cell in self.hiddenLayers[layerName]! { | |
inputCell.connect(to: cell) | |
} | |
} | |
} | |
// configure for each cells contained hidden layer | |
for (index, name) in self.hiddenLayerNames.enumerated() { | |
let isLastHiddenLayer: Bool = index == (countOfHiddenLayers - 1) | |
if isLastHiddenLayer { | |
// connect to each output cells | |
for cell in self.hiddenLayers[name]! { | |
for outputCell in self.outputCells { | |
cell.connect(to: outputCell) | |
} | |
} | |
// no more unconnected cells and return here | |
return | |
} else { | |
let nextLayerName: String = self.hiddenLayerNames[index+1] | |
// connect to each cells contained next layer | |
for cell in self.hiddenLayers[name]! { | |
for nextLayerCell in self.hiddenLayers[nextLayerName]! { | |
cell.connect(to: nextLayerCell) | |
} | |
} | |
} | |
} | |
} | |
// MARK: input | |
/// Clear current pool for all cells and input values. | |
/// - parameter values: input values | |
func input(values: [Double]) { | |
guard values.count == self.inputCells.count else { | |
debugPrint("\(self): Number of applied input values is illigal. (\(self.inputCells.count) input cells are containing in brain.)") | |
self.outputs = nil | |
return | |
} | |
// clear all pool value | |
for inputCell in self.inputCells { | |
inputCell.clearPool() | |
} | |
for name in self.hiddenLayerNames { | |
for cell in self.hiddenLayers[name]! { | |
cell.clearPool() | |
} | |
} | |
for outputCell in self.outputCells { | |
outputCell.clearPool() | |
} | |
// set each input values | |
for (index, value) in values.enumerated() { | |
self.inputCells[index].set(inputValue: value) | |
} | |
} | |
/// Calculate. | |
func calculate() { | |
// fire input cells | |
for inputCell in self.inputCells { | |
inputCell.fire() | |
} | |
// fire cells contained in hidden layer | |
for name in self.hiddenLayerNames { | |
for cell in self.hiddenLayers[name]! { | |
cell.fire() | |
} | |
} | |
let sources: [Double] = self.outputCells.map { (outputCell: OutputCell) -> Double in | |
return outputCell.source | |
} | |
self.outputs = self.outputCells.map { (outputCell: OutputCell) -> Double in | |
return outputCell.activate(with: sources) | |
} | |
} | |
/// Return calculated result. | |
var result: [Double]? { | |
return self.outputs | |
} | |
// MARK: CustomDebugStringConvertible | |
var debugDescription: String { | |
let pointer: String = String(Unmanaged.passUnretained(self).toOpaque().hashValue, radix: 16) | |
let hiddenLayerInformation: String = self.hiddenLayerNames.map { (name: String) -> String in | |
guard let cells = self.hiddenLayers[name] else { | |
return "'\(name)' (undefined)" | |
} | |
return "'\(name)' (\(cells.count))" | |
}.joined(separator: ", ") | |
return "<Brain: 0x\(pointer), Inputs (\(self.inputCells.count)), [\(hiddenLayerInformation)], Outputs (\(self.outputCells.count))>" | |
} | |
} | |
let inputCell1: InputCell = InputCell(initialConnectionWeight: -0.5) | |
let inputCell2: InputCell = InputCell(initialConnectionWeight: -0.5) | |
let hiddenCell1: Perceptron = Perceptron(bias: 0.7, initialConnectionWeight: 0.5) | |
inputCell1.connect(to: hiddenCell1) | |
inputCell2.connect(to: hiddenCell1) | |
let inputCell3: InputCell = InputCell(initialConnectionWeight: 0.5) | |
let inputCell4: InputCell = InputCell(initialConnectionWeight: 0.5) | |
let hiddenCell2: Perceptron = Perceptron(bias: -0.3, initialConnectionWeight: 0.5) | |
inputCell3.connect(to: hiddenCell2) | |
inputCell4.connect(to: hiddenCell2) | |
let outputCell: OutputCell = OutputCell(bias: -0.7, activationFunctionType: .step) | |
hiddenCell1.connect(to: outputCell) | |
hiddenCell2.connect(to: outputCell) | |
let brain: Brain = Brain(hiddenLayerNames: ["hiddenLayer"]) | |
brain.append(inputCells: [inputCell1, inputCell2, inputCell3, inputCell4]) | |
brain.append(cells: [hiddenCell1, hiddenCell2], for: "hiddenLayer") | |
brain.append(outputCells: [outputCell]) | |
brain.input(values: [0.0, 1.0, 0.0, 1.0]) | |
brain.calculate() | |
_ = brain.result | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment