Skip to content

Instantly share code, notes, and snippets.

@naru-jpn
Last active January 5, 2017 01:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save naru-jpn/6a41cf4920407f4b244997c4dc87cbe3 to your computer and use it in GitHub Desktop.
Save naru-jpn/6a41cf4920407f4b244997c4dc87cbe3 to your computer and use it in GitHub Desktop.
Neural Network in Swift with perceptron example (not yet: back propagation)
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