Skip to content

Commit 7ec0861

Browse files
author
Guled
committed
Genetic Algorithm complete. Flappy Bird AI still in testing, but is functional. Flappy bird was able to get 2 points in 30 minutes with 30 generations
1 parent 09f1c69 commit 7ec0861

File tree

16 files changed

+565
-248
lines changed

16 files changed

+565
-248
lines changed

Example/MLKit/GameScene.swift

Lines changed: 325 additions & 73 deletions
Large diffs are not rendered by default.

Example/MLKit/GameViewController.swift

Lines changed: 31 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,21 @@
99
import UIKit
1010
import SpriteKit
1111
import MachineLearningKit
12+
import Upsurge
1213

1314
extension SKNode {
14-
class func unarchiveFromFile(_ file : String) -> SKNode? {
15-
15+
class func unarchiveFromFile(_ file: String) -> SKNode? {
16+
1617
let path = Bundle.main.path(forResource: file, ofType: "sks")
17-
18+
1819
let sceneData: Data?
1920
do {
2021
sceneData = try Data(contentsOf: URL(fileURLWithPath: path!), options: .mappedIfSafe)
2122
} catch _ {
2223
sceneData = nil
2324
}
2425
let archiver = NSKeyedUnarchiver(forReadingWith: sceneData!)
25-
26+
2627
archiver.setClass(self.classForKeyedUnarchiver(), forClassName: "SKScene")
2728
let scene = archiver.decodeObject(forKey: NSKeyedArchiveRootObjectKey) as! GameScene
2829
archiver.finishDecoding()
@@ -31,26 +32,27 @@ extension SKNode {
3132
}
3233

3334

34-
public struct FlappyGenome: GenomeProtocol {
35+
// ADDITIONS
36+
37+
// Genome that represents a Flappy Bird
38+
public class FlappyGenome: Genome {
3539

36-
public var network: NeuralNet?
40+
public var brain: NeuralNet?
3741

38-
public init(genotype: [Float], network: NeuralNet){
42+
public init(genotype: [Float], network: NeuralNet) {
43+
44+
super.init(genotype: genotype)
3945

4046
self.genotypeRepresentation = genotype
41-
self.network = network
47+
self.brain = network
4248
}
4349

44-
/// Genotype representation of the genome.
45-
public var genotypeRepresentation: [Float]
46-
47-
/// Fitness of a particular genome.
48-
public var fitness: Float?
49-
50+
public func generateFitness(score: Int, time: Float) {
51+
self.fitness = Float(Float(score) + time)
52+
}
5053
}
5154

52-
53-
55+
// END of ADDITIONS
5456

5557
class GameViewController: UIViewController {
5658

@@ -60,14 +62,13 @@ class GameViewController: UIViewController {
6062

6163

6264
// Create First Generation of Flappy Birds
65+
var generation1: [FlappyGenome] = []
6366

64-
var generation1:[FlappyGenome] = []
65-
66-
for _ in 1...10 {
67+
for _ in 1...20 {
6768

6869
let brain = NeuralNet()
6970

70-
brain.initializeNet(numberOfInputNeurons: 4, numberOfHiddenLayers: 1, numberOfNeuronsInHiddenLayer: 4, numberOfOutputNeurons: 1)
71+
brain.initializeNet(numberOfInputNeurons: 3, numberOfHiddenLayers: 1, numberOfNeuronsInHiddenLayer: 4, numberOfOutputNeurons: 1)
7172

7273
brain.activationFuncType = .SIGLOG
7374

@@ -81,29 +82,30 @@ class GameViewController: UIViewController {
8182

8283
if let scene = GameScene.unarchiveFromFile("GameScene") as? GameScene {
8384

84-
// Set the first generation of Flappy Birds
85-
scene.firstGenerationOfFlappyBirds = generation1
85+
// Set the first generation of Flappy Birds
86+
scene.flappyBirdGenerationContainer = generation1
8687

8788
// Configure the view.
8889
let skView = self.view as! SKView
89-
skView.showsFPS = true
90-
skView.showsNodeCount = true
90+
skView.showsFPS = false
91+
skView.showsPhysics = true
92+
skView.showsNodeCount = false
9193

9294
/* Sprite Kit applies additional optimizations to improve rendering performance */
9395
skView.ignoresSiblingOrder = true
94-
96+
9597
/* Set the scale mode to scale to fit the window */
9698
scene.scaleMode = .aspectFill
97-
99+
98100
skView.presentScene(scene)
99101
}
100102
}
101103

102-
override var shouldAutorotate : Bool {
104+
override var shouldAutorotate: Bool {
103105
return true
104106
}
105107

106-
override var supportedInterfaceOrientations : UIInterfaceOrientationMask {
108+
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
107109
if UIDevice.current.userInterfaceIdiom == .phone {
108110
return UIInterfaceOrientationMask.allButUpsideDown
109111
} else {
@@ -116,5 +118,5 @@ class GameViewController: UIViewController {
116118
// Release any cached data, images, etc that aren't in use.
117119
}
118120

119-
121+
120122
}

Example/MLKit/GeneticOperations.swift

Lines changed: 78 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,24 @@
88

99
import Foundation
1010
import MachineLearningKit
11+
import Upsurge
1112

13+
14+
/// The GeneticOperations class manages encoding genes into weights for the neural network and decoding neural network weights into genes. These methods are not provided in the framework itself, rather it was for the game example.
1215
final class GeneticOperations {
1316

17+
18+
/**
19+
The encode method converts a NueralNet object to an array of floats by taking the weights of each layer and placing them into an array.
20+
21+
- parameter network: A NeuralNet Object.
22+
23+
- returns: An array of Float values.
24+
*/
1425
public static func encode(network: NeuralNet) -> [Float] {
26+
1527
let inputLayerNuerons = network.inputLayer.listOfNeurons
16-
let hiddenLayerNeurons = network.hiddenLayer.listOfNeurons
28+
let hiddenLayerNeurons = network.listOfHiddenLayers[0].listOfNeurons
1729
let outputLayerNeurons = network.outputLayer.listOfNeurons
1830

1931
var genotypeRepresentation: [Float] = []
@@ -23,7 +35,11 @@ final class GeneticOperations {
2335
}
2436

2537
for neuron in hiddenLayerNeurons {
26-
genotypeRepresentation = genotypeRepresentation + neuron.weightsComingIn + neuron.weightsGoingOut
38+
genotypeRepresentation = genotypeRepresentation + neuron.weightsComingIn
39+
}
40+
41+
for neuron in hiddenLayerNeurons {
42+
genotypeRepresentation = genotypeRepresentation + neuron.weightsGoingOut
2743
}
2844

2945
for neuron in outputLayerNeurons {
@@ -33,4 +49,64 @@ final class GeneticOperations {
3349
return genotypeRepresentation
3450
}
3551

52+
53+
/**
54+
The decode method converts a genotype back to a NeuralNet object by taking each value from the genotype and mapping them to a neuron in a particular layer.
55+
56+
- parameter network: A NeuralNet Object.
57+
58+
- returns: An array of Float values.
59+
*/
60+
public static func decode(genotype: [Float]) -> NeuralNet {
61+
62+
// Create a new NueralNet
63+
let brain = NeuralNet()
64+
65+
brain.initializeNet(numberOfInputNeurons: 3, numberOfHiddenLayers: 1, numberOfNeuronsInHiddenLayer: 4, numberOfOutputNeurons: 1)
66+
67+
brain.activationFuncType = .SIGLOG
68+
69+
brain.activationFuncTypeOutputLayer = .SIGLOG
70+
71+
// Convert genotype back to weights for each layer
72+
let inputLayerWeights: [Float] = Array<Float>(genotype[0...3])
73+
74+
// First is bias neuron
75+
let hiddenLayerWeightsComingInForNueron1: [Float] = Array<Float>(genotype[4...7])
76+
let hiddenLayerWeightsComingInForNueron2: [Float] = Array<Float>(genotype[8...11])
77+
let hiddenLayerWeightsComingInForNueron3: [Float] = Array<Float>(genotype[12...15])
78+
let hiddenLayerWeightsComingInForNueron4: [Float] = Array<Float>(genotype[16...19])
79+
let hiddenLayerWeightsGoingOut: [Float] = Array<Float>(genotype[20...24])
80+
let outputLayerWeightGoingOut: Float = genotype[25]
81+
82+
for (var i, var neuron) in brain.inputLayer.listOfNeurons.enumerated() {
83+
84+
neuron.weightsComingIn = ValueArray<Float>([inputLayerWeights[i]])
85+
}
86+
87+
for (var i, var neuron) in brain.listOfHiddenLayers[0].listOfNeurons.enumerated() {
88+
89+
if i == 0 {
90+
continue
91+
} else if i == 1 {
92+
neuron.weightsComingIn = ValueArray<Float>(hiddenLayerWeightsComingInForNueron1)
93+
} else if i == 2 {
94+
neuron.weightsComingIn = ValueArray<Float>(hiddenLayerWeightsComingInForNueron2)
95+
} else if i == 3 {
96+
neuron.weightsComingIn = ValueArray<Float>(hiddenLayerWeightsComingInForNueron3)
97+
} else if i == 4 {
98+
neuron.weightsComingIn = ValueArray<Float>(hiddenLayerWeightsComingInForNueron4)
99+
}
100+
}
101+
102+
for (var i, var neuron) in brain.listOfHiddenLayers[0].listOfNeurons.enumerated() {
103+
104+
neuron.weightsGoingOut = ValueArray<Float>([hiddenLayerWeightsGoingOut[i]])
105+
106+
}
107+
108+
brain.outputLayer.listOfNeurons[0].weightsGoingOut = ValueArray<Float>([outputLayerWeightGoingOut])
109+
110+
return brain
111+
}
36112
}

Example/Tests/GeneticSpec.swift

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,15 @@ import Nimble
1515

1616
class GeneticSpec: QuickSpec {
1717

18-
19-
2018
override func spec() {
2119

2220
it("Should be able to produce a unique genotype after swap mutation process.") {
2321

24-
var fakeGenome:Genome = Genome(genotype: [1.0,2.0,3.0])
22+
let fakeGenome: Genome = Genome(genotype: [1.0, 2.0, 3.0])
2523

2624
let oldGenotype = fakeGenome.genotypeRepresentation
2725

28-
BiologicalProcessManager.swapMutation(genotype: &fakeGenome.genotypeRepresentation)
26+
BiologicalProcessManager.swapMutation(mutationRate: 1.0, genotype: &fakeGenome.genotypeRepresentation )
2927

3028

3129
expect(oldGenotype).toNot(equal(fakeGenome.genotypeRepresentation))
@@ -34,39 +32,40 @@ class GeneticSpec: QuickSpec {
3432

3533
it("Should be able to produce a unique genotype after insert mutation process. ") {
3634

37-
var fakeGenome:Genome = Genome(genotype: [1.0,2.0,3.0,4.0,5.0,6.0,7.0])
35+
let fakeGenome: Genome = Genome(genotype: [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0])
3836

3937
let oldGenotype = fakeGenome.genotypeRepresentation
4038

41-
BiologicalProcessManager.insertMutation(genotype: &fakeGenome.genotypeRepresentation)
39+
BiologicalProcessManager.insertMutation(mutationRate: 1.0, genotype: &fakeGenome.genotypeRepresentation )
4240

4341
expect(oldGenotype).toNot(equal(fakeGenome.genotypeRepresentation))
4442
}
4543

4644

4745
it("Should be able to produce a unique genotype after inverse mutation process. ") {
4846

49-
var fakeGenome:Genome = Genome(genotype: [1.0,2.0,3.0])
47+
let fakeGenome: Genome = Genome(genotype: [1.0, 2.0, 3.0])
5048

5149
let oldGenotype = fakeGenome.genotypeRepresentation
5250

53-
BiologicalProcessManager.inverseMutation(genotype: &fakeGenome.genotypeRepresentation)
51+
BiologicalProcessManager.inverseMutation(mutationRate: 1.0, genotype: &fakeGenome.genotypeRepresentation )
5452

5553
expect(oldGenotype).toNot(equal(fakeGenome.genotypeRepresentation))
5654
}
5755

5856
it("Should be able to produce a unique genotype after scramble mutation process. ") {
5957

60-
var fakeGenome:Genome = Genome(genotype: [1.0,2.0,3.0,4.0,5.0,6.0,7.0])
58+
let fakeGenome: Genome = Genome(genotype: [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0])
6159

6260
let oldGenotype = fakeGenome.genotypeRepresentation
6361

64-
BiologicalProcessManager.scrambleMutation(genotype: &fakeGenome.genotypeRepresentation)
62+
BiologicalProcessManager.scrambleMutation(mutationRate: 1.0, genotype: &fakeGenome.genotypeRepresentation)
6563

6664
expect(oldGenotype).toNot(equal(fakeGenome.genotypeRepresentation))
6765
}
6866

6967

68+
7069
}
71-
70+
7271
}

MLKit-PlayGround.playground/Contents.swift

Lines changed: 3 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -3,45 +3,8 @@
33
import UIKit
44
import MachineLearningKit
55

6-
// Slicing
7-
struct test {
8-
var testArray:[Int] = [1,2,3,4,5,6,7,8]
9-
}
6+
// Slicing
107

11-
var t = test()
8+
var testArray: [Int] = [1, 2,3, 4,5, 6,7, 8]
129

13-
func swapMutation( genotype:inout [Int]) {
14-
genotype[0] = genotype[1]
15-
16-
}
17-
18-
19-
extension Collection {
20-
/// Return a copy of `self` with its elements shuffled
21-
func shuffle() -> [Iterator.Element] {
22-
var list = Array(self)
23-
list.shuffle()
24-
return list
25-
}
26-
}
27-
28-
extension MutableCollection where Indices.Iterator.Element == Index {
29-
/// Shuffles the contents of this collection.
30-
mutating func shuffle() {
31-
let c = count
32-
guard c > 1 else { return }
33-
34-
for (firstUnshuffled, unshuffledCount) in zip(indices, stride(from: c, to: 1, by: -1)) {
35-
let d: IndexDistance = numericCast(arc4random_uniform(numericCast(unshuffledCount)))
36-
guard d != 0 else { continue }
37-
let i = index(firstUnshuffled, offsetBy: d)
38-
swap(&self[firstUnshuffled], &self[i])
39-
}
40-
}
41-
}
42-
43-
44-
//t.testArray[0...3].shuffle()
45-
print(t.testArray)
46-
print(t.testArray[0...3].reverse())
47-
print(t.testArray)
10+
var t = testArray[0...3]

MLKit/Classes/ANN/HiddenLayer.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
import Foundation
1313
import Upsurge
1414

15-
15+
/// The HiddenLayer class represents the hidden layer of a NueralNet object.
1616
public class HiddenLayer: Layer {
1717

1818

MLKit/Classes/ANN/InputLayer.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import Foundation
1111
import Upsurge
1212

13+
/// The InputLayer class represents the input layer of a NueralNet object.
1314
public class InputLayer: Layer, InputandOutputLayerMethods {
1415

1516

@@ -29,7 +30,7 @@ public class InputLayer: Layer, InputandOutputLayerMethods {
2930
}
3031

3132
/// Number of neurons in the input layer
32-
public var numberOfNueronsInLayer: Int {
33+
public var numberOfNueronsInLayer: Int {
3334

3435
get {
3536
return _numberOfNueronsInLayer

MLKit/Classes/ANN/LayerProtocol.swift .swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import Foundation
1212
import Upsurge
1313

1414

15+
/// The Layer Protocol defines what attributes and methods a Layer Object must have.
1516
public protocol Layer {
1617

1718
/// List of Neuron Object in a particular layer.
@@ -22,7 +23,6 @@ public protocol Layer {
2223

2324
}
2425

25-
2626
public protocol InputandOutputLayerMethods {
2727

2828
/**

0 commit comments

Comments
 (0)