Skip to content

Commit a39d082

Browse files
author
Guled
authored
Merge pull request #3 from Somnibyte/neural-network-revision
Neural network revision
2 parents 13da306 + 573a3c3 commit a39d082

25 files changed

+1063
-1681
lines changed

Example/MLKit/GameScene.swift

+79-33
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import SpriteKit
1010
import MachineLearningKit
11+
import Upsurge
1112

1213
class GameScene: SKScene, SKPhysicsContactDelegate {
1314

@@ -26,7 +27,7 @@ class GameScene: SKScene, SKPhysicsContactDelegate {
2627
var generationCounter = 1
2728

2829
/// Variable to keep track of the current time (this is used to determine fitness later)
29-
var currentTime = NSDate()
30+
var currentTimeForFlappyBird = NSDate()
3031

3132
/// The red square (our goal area)
3233
var goalArea: SKShapeNode!
@@ -43,6 +44,18 @@ class GameScene: SKScene, SKPhysicsContactDelegate {
4344
/// The best birds from the previous generation
4445
var lastBestGen: [FlappyGenome] = []
4546

47+
/// SKLabel
48+
var generationLabel: SKLabelNode!
49+
var fitnessLabel: SKLabelNode!
50+
51+
52+
/// Best score (regardless of generation)
53+
var bestScore: Int = 0
54+
55+
/// Label that displays the best score (bestScore attribute)
56+
var bestScoreLabel: SKLabelNode!
57+
58+
4659
// END of ADDITIONS
4760

4861
let verticalPipeGap = 150.0
@@ -173,6 +186,26 @@ class GameScene: SKScene, SKPhysicsContactDelegate {
173186
scoreLabelNode.text = String(score)
174187
self.addChild(scoreLabelNode)
175188

189+
190+
bestScore = 0
191+
bestScoreLabel = SKLabelNode(fontNamed:"MarkerFelt-Wide")
192+
bestScoreLabel.position = CGPoint( x: self.frame.midX - 120.0, y: 3 * self.frame.size.height / 4 + 110.0 )
193+
bestScoreLabel.zPosition = 100
194+
bestScoreLabel.text = "bestScore: \(self.bestScore)"
195+
self.addChild(bestScoreLabel)
196+
197+
generationLabel = SKLabelNode(fontNamed:"MarkerFelt-Wide")
198+
generationLabel.position = CGPoint( x: self.frame.midX - 150.0, y: 3 * self.frame.size.height / 4 + 140.0 )
199+
generationLabel.zPosition = 100
200+
generationLabel.text = "Gen: 1"
201+
self.addChild(generationLabel)
202+
203+
fitnessLabel = SKLabelNode(fontNamed:"MarkerFelt-Wide")
204+
fitnessLabel.position = CGPoint( x: self.frame.midX + 110.0, y: 3 * self.frame.size.height / 4 + 140.0 )
205+
fitnessLabel.zPosition = 100
206+
fitnessLabel.text = "Fitness: 0"
207+
self.addChild(fitnessLabel)
208+
176209
// Set the current bird
177210
currentBird = flappyBirdGenerationContainer?[currentFlappy]
178211

@@ -256,8 +289,8 @@ class GameScene: SKScene, SKPhysicsContactDelegate {
256289

257290
// Calculate the amount of time it took until the AI lost.
258291
let endDate: NSDate = NSDate()
259-
let timeInterval: Double = endDate.timeIntervalSince(currentTime as Date)
260-
currentTime = NSDate()
292+
let timeInterval: Double = endDate.timeIntervalSince(currentTimeForFlappyBird as Date)
293+
currentTimeForFlappyBird = NSDate()
261294

262295
// Evaluate the current birds fitness
263296
currentBird?.generateFitness(score: score, time: Float(timeInterval))
@@ -267,36 +300,39 @@ class GameScene: SKScene, SKPhysicsContactDelegate {
267300
print("GENERATION: \(generationCounter)")
268301
print("BIRD COUNT: \(currentFlappy)")
269302
print("FITNESS: \(currentBird?.fitness)")
303+
self.generationLabel.text = "Gen: \(self.generationCounter)"
270304
print("--------------------------- \n")
271305

272-
/* DEBUGGING
273-
if (currentBird?.fitness)! >= Float(7.0) {
274-
print("-----BEST BIRD FOUND------")
275-
276-
currentBird?.brain?.inputLayer.printLayer(layer: (currentBird?.brain?.inputLayer)!)
277-
currentBird?.brain?.hiddenLayer.printLayer(listOfHiddenLayers: (currentBird?.brain?.listOfHiddenLayers)!)
278-
currentBird?.brain?.outputLayer.printLayer(layer: (currentBird?.brain?.outputLayer)!)
279-
280-
}
281-
*/
282-
283306
// Go to next flappy bird
284307
currentFlappy += 1
285308

286309
// Filter out the worst birds after gen 6 (can be adjusted)
310+
311+
if let bird = currentBird {
312+
if bird.fitness >= 9.0 {
313+
print("FOUND RARE BIRD")
314+
print(bird.brain?.layers[0].weights)
315+
print(bird.brain?.layers[1].weights)
316+
print(bird.brain?.layers[0].bias)
317+
print(bird.brain?.layers[1].bias)
318+
}
319+
}
320+
287321
if generationCounter >= 3 {
288322

289323
// Experiment: Keep some of the last best birds and put them back into the population
290-
lastBestGen = (flappyBirdGenerationContainer?.filter({$0.fitness >= 4}))!
324+
lastBestGen = (flappyBirdGenerationContainer?.filter({$0.fitness >= 7.0}))!
291325
}
292326

293-
if (currentBird?.fitness)! > maxFitness {
294-
maxFitness = (currentBird?.fitness)!
295-
maxBird = currentBird
327+
if let bird = currentBird {
328+
if bird.fitness > maxFitness {
329+
maxFitness = bird.fitness
330+
maxBird = bird
331+
}
296332
}
297333

298334
// If we have hit the 20th bird, we need to move on to the next generation
299-
if currentFlappy == 10 {
335+
if currentFlappy == 20 {
300336

301337
print("GENERATING NEW GEN!")
302338

@@ -308,7 +344,7 @@ class GameScene: SKScene, SKPhysicsContactDelegate {
308344
newGen += lastBestGen
309345
newGen.append(maxBird!)
310346

311-
while newGen.count < 10 {
347+
while newGen.count < 20 {
312348

313349
// Select the best parents
314350
let parents = PopulationManager.selectParents(genomes: flappyBirdGenerationContainer!)
@@ -320,10 +356,10 @@ class GameScene: SKScene, SKPhysicsContactDelegate {
320356
var offspring = BiologicalProcessManager.onePointCrossover(crossoverRate: 0.5, parentOneGenotype: parents.0.genotypeRepresentation, parentTwoGenotype: parents.1.genotypeRepresentation)
321357

322358
// Mutate their genes
323-
324359
BiologicalProcessManager.inverseMutation(mutationRate: 0.7, genotype: &offspring.0)
325360
BiologicalProcessManager.inverseMutation(mutationRate: 0.7, genotype: &offspring.1)
326361

362+
327363
// Create a separate neural network for the birds based on their genes
328364
let brainofOffspring1 = GeneticOperations.decode(genotype: offspring.0)
329365

@@ -346,12 +382,19 @@ class GameScene: SKScene, SKPhysicsContactDelegate {
346382
flappyBirdGenerationContainer = newGen
347383

348384
// Set the current bird
349-
currentBird = flappyBirdGenerationContainer?[currentFlappy]
385+
if (flappyBirdGenerationContainer?.count)! > currentFlappy {
386+
currentBird = flappyBirdGenerationContainer?[currentFlappy]
387+
} else {
388+
currentBird = maxBird
389+
}
350390

351391
} else {
352392

353393
// Set the current bird
354-
currentBird = flappyBirdGenerationContainer?[currentFlappy]
394+
if (flappyBirdGenerationContainer?.count)! > currentFlappy {
395+
currentBird = flappyBirdGenerationContainer?[currentFlappy]
396+
}
397+
355398
}
356399

357400
}
@@ -385,6 +428,10 @@ class GameScene: SKScene, SKPhysicsContactDelegate {
385428

386429
override func update(_ currentTime: TimeInterval) {
387430

431+
let endDate: NSDate = NSDate()
432+
let timeInterval: Double = endDate.timeIntervalSince(currentTimeForFlappyBird as Date)
433+
self.fitnessLabel.text = "Fitness: \(NSString(format: "%.2f", timeInterval))"
434+
388435
checkIfOutOfBounds(bird.position.y)
389436

390437
/* Called before each frame is rendered */
@@ -418,21 +465,14 @@ class GameScene: SKScene, SKPhysicsContactDelegate {
418465

419466
let normalizedPosToGap = (posToGap - (-439))/(279 - (-439))
420467

421-
/*
422-
print("======================== \n")
423-
print(" DIST: \((finalDistanceOfNextPipe))")
424-
print("PIPE POSITION: \(finalPosToGap)")
425-
print("Bird POS Y: \(birdPos)")
426-
print("======================== \n")
427-
*/
428-
429468
// Decision AI makes
430-
let decision = (currentBird?.brain?.forward(input: [Float(1), Float(normalizedDistanceOfNextPipe), Float(normalizedPosToGap), Float(birdYPos), Float(normalizedDistanceFromBottomPipe)]))!
469+
let input = Matrix<Float>(rows: 4, columns: 1, elements: [Float(normalizedDistanceOfNextPipe), Float(normalizedPosToGap), Float(birdYPos), Float(normalizedDistanceFromBottomPipe)])
470+
let decision = currentBird?.brain?.feedforward(input: input)
431471

432472
print("FLAPPY BIRD DECISION: \(decision)")
433473

434474
// 0.95 was arbitrary, tweaking is recommended
435-
if decision[0] >= Float(0.89) {
475+
if (decision?.elements[0])! >= Float(0.5) {
436476

437477
if moving.speed > 0 {
438478

@@ -491,6 +531,12 @@ class GameScene: SKScene, SKPhysicsContactDelegate {
491531
score += 1
492532
scoreLabelNode.text = String(score)
493533

534+
// Update best score label
535+
if score > bestScore {
536+
bestScore = score
537+
bestScoreLabel.text = "Best Score: \(self.bestScore)"
538+
}
539+
494540
// Add a little visual feedback for the score increment
495541
scoreLabelNode.run(SKAction.sequence([SKAction.scale(to: 1.5, duration:TimeInterval(0.1)), SKAction.scale(to: 1.0, duration:TimeInterval(0.1))]))
496542
} else {

Example/MLKit/GameViewController.swift

+6-8
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,9 @@ public class FlappyGenome: Genome {
4141

4242
public var fitness: Float = 0
4343

44-
public var brain: NeuralNet?
44+
public var brain: NeuralNetwork?
4545

46-
public init(genotype: [Float], network: NeuralNet) {
46+
public init(genotype: [Float], network: NeuralNetwork) {
4747

4848
self.genotypeRepresentation = genotype
4949
self.brain = network
@@ -64,14 +64,12 @@ class GameViewController: UIViewController {
6464
// Create First Generation of Flappy Birds
6565
var generation1: [FlappyGenome] = []
6666

67-
for _ in 1...10 {
67+
for _ in 1...20 {
6868

6969
// Bias already included
70-
let brain = NeuralNet(numberOfInputNeurons: 4, hiddenLayers: [4], numberOfOutputNeurons: 1)
71-
72-
brain.activationFuncType = .siglog
73-
74-
brain.activationFuncTypeOfOutputLayer = .siglog
70+
let brain = NeuralNetwork(size: (4, 1))
71+
brain.addLayer(layer: Layer(size: (4, 4), activationType: .siglog))
72+
brain.addLayer(layer: Layer(size: (4, 1), activationType: .siglog))
7573

7674
let newBird = FlappyGenome(genotype: GeneticOperations.encode(network: brain), network: brain)
7775

Example/MLKit/GeneticOperations.swift

+17-68
Original file line numberDiff line numberDiff line change
@@ -11,40 +11,29 @@ import MachineLearningKit
1111
import Upsurge
1212

1313
/// 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.
14-
final class GeneticOperations {
1514

15+
final class GeneticOperations {
1616
/**
17-
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.
17+
The encode method converts a NueralNetwork object to an array of floats by taking the weights of each layer and placing them into an array.
1818

1919
- parameter network: A NeuralNet Object.
2020

2121
- returns: An array of Float values.
2222
*/
23-
public static func encode(network: NeuralNet) -> [Float] {
24-
25-
let inputLayerNeurons = network.inputLayer.listOfNeurons
26-
let outputLayerNeurons = network.outputLayer.listOfNeurons
23+
public static func encode(network: NeuralNetwork) -> [Float] {
2724

2825
var genotypeRepresentation: [Float] = []
2926

30-
for neuron in inputLayerNeurons {
31-
genotypeRepresentation += neuron.weightsComingIn
32-
}
33-
34-
for hiddenLayer in network.listOfHiddenLayers {
35-
for neuron in hiddenLayer.listOfNeurons {
36-
genotypeRepresentation += neuron.weightsComingIn
37-
}
27+
for layer in network.layers {
3828

39-
for neuron in hiddenLayer.listOfNeurons {
40-
genotypeRepresentation += neuron.weightsGoingOut
41-
}
29+
genotypeRepresentation += Array<Float>(layer.weights!.elements)
4230
}
4331

44-
for neuron in outputLayerNeurons {
45-
genotypeRepresentation += neuron.weightsGoingOut
32+
for layer in network.layers {
33+
genotypeRepresentation += Array<Float>(layer.bias!.elements)
4634
}
4735

36+
4837
return genotypeRepresentation
4938
}
5039

@@ -55,57 +44,17 @@ final class GeneticOperations {
5544

5645
- returns: An array of Float values.
5746
*/
58-
public static func decode(genotype: [Float]) -> NeuralNet {
47+
public static func decode(genotype: [Float]) -> NeuralNetwork {
5948

6049
// Create a new NueralNet
61-
let brain = NeuralNet(numberOfInputNeurons: 4, hiddenLayers: [4], numberOfOutputNeurons: 1)
62-
63-
brain.activationFuncType = .siglog
64-
65-
brain.activationFuncTypeOfOutputLayer = .siglog
66-
67-
// Convert genotype back to weights for each layer
68-
let inputLayerWeights: [Float] = Array<Float>(genotype[0...4])
69-
70-
// First is bias neuron
71-
let hiddenLayerWeightsComingInForNeuron1: [Float] = Array<Float>(genotype[5...8])
72-
let hiddenLayerWeightsComingInForNeuron2: [Float] = Array<Float>(genotype[9...12])
73-
let hiddenLayerWeightsComingInForNeuron3: [Float] = Array<Float>(genotype[13...16])
74-
let hiddenLayerWeightsComingInForNeuron4: [Float] = Array<Float>(genotype[17...20])
75-
let hiddenLayerWeightsComingInForNeuron5: [Float] = Array<Float>(genotype[21...24])
76-
let hiddenLayerWeightsGoingOut: [Float] = Array<Float>(genotype[25...29])
77-
let outputLayerWeightGoingOut: Float = genotype[30]
78-
79-
for (i, neuron) in brain.inputLayer.listOfNeurons.enumerated() {
80-
81-
neuron.weightsComingIn = ValueArray<Float>([inputLayerWeights[i]])
82-
}
83-
84-
for hiddenLayer in brain.listOfHiddenLayers {
85-
for (i, neuron) in hiddenLayer.listOfNeurons.enumerated() {
86-
87-
if i == 0 {
88-
neuron.weightsComingIn = ValueArray<Float>(hiddenLayerWeightsComingInForNeuron1)
89-
} else if i == 1 {
90-
neuron.weightsComingIn = ValueArray<Float>(hiddenLayerWeightsComingInForNeuron2)
91-
} else if i == 2 {
92-
neuron.weightsComingIn = ValueArray<Float>(hiddenLayerWeightsComingInForNeuron3)
93-
} else if i == 3 {
94-
neuron.weightsComingIn = ValueArray<Float>(hiddenLayerWeightsComingInForNeuron4)
95-
} else if i == 4 {
96-
neuron.weightsComingIn = ValueArray<Float>(hiddenLayerWeightsComingInForNeuron5)
97-
}
98-
}
99-
100-
for (i, neuron) in hiddenLayer.listOfNeurons.enumerated() {
101-
102-
neuron.weightsGoingOut = ValueArray<Float>([hiddenLayerWeightsGoingOut[i]])
103-
104-
}
105-
106-
}
107-
108-
brain.outputLayer.listOfNeurons[0].weightsGoingOut = ValueArray<Float>([outputLayerWeightGoingOut])
50+
let brain = NeuralNetwork(size: (4, 1))
51+
brain.addLayer(layer: Layer(size: (4, 4), activationType: .siglog))
52+
brain.addLayer(layer: Layer(size: (4, 1), activationType: .siglog))
53+
54+
brain.layers[0].weights = Matrix<Float>(rows: 4, columns: 4, elements: ValueArray<Float>(Array<Float>(genotype[0...15])))
55+
brain.layers[0].bias = Matrix<Float>(rows: 4, columns: 1, elements: ValueArray<Float>(Array<Float>(genotype[20...23])))
56+
brain.layers[1].weights = Matrix<Float>(rows: 1, columns: 4, elements: ValueArray<Float>(Array<Float>(genotype[16...19])))
57+
brain.layers[1].bias = Matrix<Float>(rows: 1, columns: 1, elements: ValueArray<Float>([genotype[24]]))
10958

11059
return brain
11160
}

0 commit comments

Comments
 (0)