浏览代码

Fix few issues in crossbreed
Add best snake showtime

Alexey Edelev 5 年之前
父节点
当前提交
ecb547dbe4
共有 4 个文件被更改,包括 238 次插入224 次删除
  1. 37 21
      neuralnetwork/genetic/genetic.go
  2. 4 4
      neuralnetwork/genetic/interface.go
  3. 1 1
      neuralnetwork/main.go
  4. 196 198
      neuralnetwork/snakesimulator/snakesimulator.go

+ 37 - 21
neuralnetwork/genetic/genetic.go

@@ -20,6 +20,7 @@ type Population struct {
 	Networks         []*neuralnetwork.NeuralNetwork
 	verifier         PopulationVerifier
 	mutagen          Mutagen
+	etalonsCount     int
 }
 
 func NewPopulation(verifier PopulationVerifier, mutagen Mutagen, populationConfig PopulationConfig, sizes []int) (p *Population) {
@@ -32,6 +33,11 @@ func NewPopulation(verifier PopulationVerifier, mutagen Mutagen, populationConfi
 		Networks:         make([]*neuralnetwork.NeuralNetwork, populationConfig.PopulationSize),
 		verifier:         verifier,
 		mutagen:          mutagen,
+		etalonsCount:     int(float64(populationConfig.PopulationSize) * populationConfig.SelectionSize),
+	}
+
+	if p.etalonsCount%2 != 0 {
+		p.etalonsCount -= 1
 	}
 
 	for i := 0; i < populationConfig.PopulationSize; i++ {
@@ -51,30 +57,40 @@ func (p *Population) NaturalSelection(generationCount int) {
 	}
 }
 
-func (p *Population) crossbreedPopulation(results []*IndividalResult) {
-	sort.SliceStable(results, func(i, j int) bool {
-		return results[i].Result > results[j].Result //Descent order best will be on top, worst in the bottom
+func (p *Population) crossbreedPopulation(fitnesses []*IndividalFitness) {
+	sort.Slice(fitnesses, func(i, j int) bool {
+		return fitnesses[i].Fitness > fitnesses[j].Fitness //Descent order best will be on top, worst in the bottom
 	})
 
-	etalons := int(float64(p.populationConfig.PopulationSize) * p.populationConfig.SelectionSize)
-	if etalons%2 != 0 {
-		etalons -= 1
-	}
-	etalonNetworks := make([]*neuralnetwork.NeuralNetwork, etalons)
-	for i := 1; i < etalons; i += 2 {
-		firstParentBase := results[i-1].Index
-		secondParentBase := results[i].Index
-		etalonNetworks[i-1] = p.Networks[firstParentBase].Copy()
-		etalonNetworks[i] = p.Networks[secondParentBase].Copy()
+	//Collect etalons from upper part of neural network list and crossbreed/mutate them
+	etalonNetworks := make([]*neuralnetwork.NeuralNetwork, p.etalonsCount)
+	for i := 1; i < p.etalonsCount; i += 2 {
+		firstParent := fitnesses[i-1].Index
+		secondParent := fitnesses[i].Index
+		fmt.Printf("Result i %v firstParent %v secondParent %v firstFitness %v secondFitness %v\n", i, firstParent, secondParent, fitnesses[i-1].Fitness, fitnesses[i].Fitness)
+
+		etalonNetworks[i-1] = p.Networks[firstParent].Copy()
+		etalonNetworks[i] = p.Networks[secondParent].Copy()
+
+		crossbreed(p.Networks[firstParent], p.Networks[secondParent], p.populationConfig.CrossbreedPart)
+		p.mutagen.Mutate(p.Networks[firstParent])
+		p.mutagen.Mutate(p.Networks[secondParent])
 	}
-	for i := 1; i < p.populationConfig.PopulationSize; i += 2 {
-		firstParentBase := etalonNetworks[(i-1)%etalons]
-		secondParentBase := etalonNetworks[i%etalons]
-		firstParent := results[i-1].Index
-		secondParent := results[i].Index
-		fmt.Printf("Result value %v i %v firstParent %v secondParent %v\n", results[i].Result, i, firstParent, secondParent)
-		p.Networks[firstParent] = firstParentBase.Copy()
-		p.Networks[secondParent] = secondParentBase.Copy()
+
+	//Rest of networks are based on collected etalons but crossbreed/mutate own way
+	for i := p.etalonsCount + 1; i < p.populationConfig.PopulationSize; i += 2 {
+		firstParent := fitnesses[i-1].Index
+		secondParent := fitnesses[i].Index
+		fmt.Printf("Result i %v firstParent %v secondParent %v firstFitness %v secondFitness %v firstEtalon %v secondEtalon %v\n",
+			i, firstParent, secondParent,
+			fitnesses[i-1].Fitness, fitnesses[i].Fitness,
+			fitnesses[(i-1)%p.etalonsCount].Index, fitnesses[(i)%p.etalonsCount].Index)
+
+		firstParentEtalon := etalonNetworks[(i-1)%p.etalonsCount]
+		secondParenEtalon := etalonNetworks[(i)%p.etalonsCount]
+
+		p.Networks[firstParent] = firstParentEtalon.Copy()
+		p.Networks[secondParent] = secondParenEtalon.Copy()
 		crossbreed(p.Networks[firstParent], p.Networks[secondParent], p.populationConfig.CrossbreedPart)
 		p.mutagen.Mutate(p.Networks[firstParent])
 		p.mutagen.Mutate(p.Networks[secondParent])

+ 4 - 4
neuralnetwork/genetic/interface.go

@@ -2,13 +2,13 @@ package genetic
 
 import neuralnetwork "../neuralnetwork"
 
-type IndividalResult struct {
-	Result float64
-	Index  int
+type IndividalFitness struct {
+	Fitness float64
+	Index   int
 }
 
 type PopulationVerifier interface {
-	Verify(*Population) []*IndividalResult
+	Verify(*Population) []*IndividalFitness
 }
 
 type Mutagen interface {

+ 1 - 1
neuralnetwork/main.go

@@ -12,7 +12,7 @@ func main() {
 	go rc.Run()
 	s := snakesimulator.NewSnakeSimulator()
 	s.StartServer()
-	p := genetic.NewPopulation(s, mutagen.NewDummyMutagen(50), genetic.PopulationConfig{PopulationSize: 500, SelectionSize: 0.05, CrossbreedPart: 0.2}, []int{8, 18, 18, 4})
+	p := genetic.NewPopulation(s, mutagen.NewDummyMutagen(50), genetic.PopulationConfig{PopulationSize: 2000, SelectionSize: 0.01, CrossbreedPart: 0.2}, []int{24, 18, 18, 4})
 	for _, net := range p.Networks {
 		net.SetStateWatcher(rc)
 	}

+ 196 - 198
neuralnetwork/snakesimulator/snakesimulator.go

@@ -6,11 +6,13 @@ import (
 	math "math"
 	"math/rand"
 	"net"
+	"sort"
 	"time"
 
 	"gonum.org/v1/gonum/mat"
 
 	genetic "../genetic"
+	neuralnetwork "../neuralnetwork"
 	grpc "google.golang.org/grpc"
 )
 
@@ -49,14 +51,74 @@ func NewSnakeSimulator() (s *SnakeSimulator) {
 	return
 }
 
-func (s *SnakeSimulator) Verify(population *genetic.Population) (results []*genetic.IndividalResult) {
+func (s *SnakeSimulator) Verify(population *genetic.Population) (fitnesses []*genetic.IndividalFitness) {
 	s.stats.Generation++
 	s.statsUpdateQueue <- true
 	s.field.GenerateNextFood()
-	results = make([]*genetic.IndividalResult, len(population.Networks))
+	fitnesses = make([]*genetic.IndividalFitness, len(population.Networks))
 	for index, inidividual := range population.Networks {
 		s.stats.Individual = uint32(index)
 		s.statsUpdateQueue <- true
+
+		s.runSnake(inidividual, false)
+		fitnesses[index] = &genetic.IndividalFitness{
+			// Fitness: float64(len(s.snake.Points)-2) * float64(s.stats.Move),
+			Fitness: float64(s.stats.Move),
+			Index:   index,
+		}
+	}
+
+	//This is duplication of crossbreedPopulation functionality.
+	sort.Slice(fitnesses, func(i, j int) bool {
+		return fitnesses[i].Fitness > fitnesses[j].Fitness //Descent order best will be on top, worst in the bottom
+	})
+
+	//Best snake showtime!
+	prevSpeed := s.speed
+	s.speed = 2
+	s.runSnake(population.Networks[fitnesses[0].Index], false)
+	s.speed = prevSpeed
+	return
+}
+
+func (s *SnakeSimulator) runSnake(inidividual *neuralnetwork.NeuralNetwork, randomStart bool) {
+	if randomStart {
+		rand.Seed(time.Now().UnixNano())
+		switch rand.Uint32() % 4 {
+		case 1:
+			s.snake = &Snake{
+				Points: []*Point{
+					&Point{X: 20, Y: 20},
+					&Point{X: 21, Y: 20},
+					&Point{X: 22, Y: 20},
+				},
+			}
+		case 2:
+			s.snake = &Snake{
+				Points: []*Point{
+					&Point{X: 22, Y: 20},
+					&Point{X: 21, Y: 20},
+					&Point{X: 20, Y: 20},
+				},
+			}
+		case 3:
+			s.snake = &Snake{
+				Points: []*Point{
+					&Point{X: 20, Y: 20},
+					&Point{X: 20, Y: 21},
+					&Point{X: 20, Y: 22},
+				},
+			}
+		default:
+			s.snake = &Snake{
+				Points: []*Point{
+					&Point{X: 20, Y: 22},
+					&Point{X: 20, Y: 21},
+					&Point{X: 20, Y: 20},
+				},
+			}
+		}
+	} else {
 		s.snake = &Snake{
 			Points: []*Point{
 				&Point{X: 19, Y: 20},
@@ -64,122 +126,79 @@ func (s *SnakeSimulator) Verify(population *genetic.Population) (results []*gene
 				&Point{X: 21, Y: 20},
 			},
 		}
-		// rand.Seed(time.Now().UnixNano())
-		// switch rand.Uint32() % 4 {
-		// case 1:
-		// 	s.snake = &Snake{
-		// 		Points: []*Point{
-		// 			&Point{X: 20, Y: 20},
-		// 			&Point{X: 21, Y: 20},
-		// 			&Point{X: 22, Y: 20},
-		// 		},
-		// 	}
-		// case 2:
-		// 	s.snake = &Snake{
-		// 		Points: []*Point{
-		// 			&Point{X: 22, Y: 20},
-		// 			&Point{X: 21, Y: 20},
-		// 			&Point{X: 20, Y: 20},
-		// 		},
-		// 	}
-		// case 3:
-		// 	s.snake = &Snake{
-		// 		Points: []*Point{
-		// 			&Point{X: 20, Y: 20},
-		// 			&Point{X: 20, Y: 21},
-		// 			&Point{X: 20, Y: 22},
-		// 		},
-		// 	}
-		// default:
-		// 	s.snake = &Snake{
-		// 		Points: []*Point{
-		// 			&Point{X: 20, Y: 22},
-		// 			&Point{X: 20, Y: 21},
-		// 			&Point{X: 20, Y: 20},
-		// 		},
-		// 	}
-		// }
-
-		i := 0
-		s.stats.Move = 0
-		for i < 300 {
-			if s.speed > 0 {
-				s.statsUpdateQueue <- true
-			}
+	}
 
-			//Read speed from client
-			select {
-			case newSpeed := <-s.speedQueue:
-				fmt.Printf("Apply new speed: %v\n", newSpeed)
-				if newSpeed < 10 {
-					if newSpeed > 0 {
-						s.speed = newSpeed
-					} else {
-						s.speed = 0
-					}
-				}
-			default:
-			}
-			i++
-			if s.speed > 0 {
-				s.snakeUpdateQueue <- true
-			}
-			direction, _ := inidividual.Predict(mat.NewDense(inidividual.Sizes[0], 1, s.GetHeadState()))
-			newHead := s.snake.NewHead(Direction(direction + 1))
-
-			if selfCollisionIndex := s.snake.SelfCollision(newHead); selfCollisionIndex > 0 {
-				if selfCollisionIndex == 1 {
-					// switch Direction(direction + 1) {
-					// case Direction_Up:
-					// 	newHead = s.snake.NewHead(Direction_Down)
-					// case Direction_Down:
-					// 	newHead = s.snake.NewHead(Direction_Up)
-					// case Direction_Left:
-					// 	newHead = s.snake.NewHead(Direction_Right)
-					// default:
-					// 	newHead = s.snake.NewHead(Direction_Left)
-					// }
-					continue
+	i := 0
+	s.stats.Move = 0
+	for i < 300 {
+		if s.speed > 0 {
+			s.statsUpdateQueue <- true
+		}
+
+		//Read speed from client
+		select {
+		case newSpeed := <-s.speedQueue:
+			fmt.Printf("Apply new speed: %v\n", newSpeed)
+			if newSpeed < 10 {
+				if newSpeed > 0 {
+					s.speed = newSpeed
 				} else {
-					fmt.Printf("Game over self collision\n")
-					break
+					s.speed = 0
 				}
 			}
+		default:
+		}
+		i++
+		if s.speed > 0 {
+			s.snakeUpdateQueue <- true
+		}
+		direction, _ := inidividual.Predict(mat.NewDense(inidividual.Sizes[0], 1, s.GetHeadState()))
+		newHead := s.snake.NewHead(Direction(direction + 1))
 
-			if newHead.X == s.field.Food.X && newHead.Y == s.field.Food.Y {
-				s.snake.Feed(newHead)
-				s.field.GenerateNextFood()
-				if s.speed > 0 {
-					s.fieldUpdateQueue <- true
-				}
-			} else if newHead.X >= s.field.Width || newHead.Y >= s.field.Height {
-				// fmt.Printf("Game over\n")
-				// time.Sleep(1000 * time.Millisecond)
-				break
+		if selfCollisionIndex := s.snake.SelfCollision(newHead); selfCollisionIndex > 0 {
+			if selfCollisionIndex == 1 {
+				// switch Direction(direction + 1) {
+				// case Direction_Up:
+				// 	newHead = s.snake.NewHead(Direction_Down)
+				// case Direction_Down:
+				// 	newHead = s.snake.NewHead(Direction_Up)
+				// case Direction_Left:
+				// 	newHead = s.snake.NewHead(Direction_Right)
+				// default:
+				// 	newHead = s.snake.NewHead(Direction_Left)
+				// }
+				continue
 			} else {
-				s.snake.Move(newHead)
+				fmt.Printf("Game over self collision\n")
+				break
 			}
+		}
+
+		if newHead.X == s.field.Food.X && newHead.Y == s.field.Food.Y {
+			s.snake.Feed(newHead)
+			s.field.GenerateNextFood()
 			if s.speed > 0 {
-				time.Sleep(100 / time.Duration(s.speed) * time.Millisecond)
+				s.fieldUpdateQueue <- true
 			}
-			s.stats.Move++
+		} else if newHead.X >= s.field.Width || newHead.Y >= s.field.Height {
+			// fmt.Printf("Game over\n")
+			// time.Sleep(1000 * time.Millisecond)
+			break
+		} else {
+			s.snake.Move(newHead)
 		}
-
-		results[index] = &genetic.IndividalResult{
-			// Result: float64(len(s.snake.Points)-2) * float64(s.stats.Move),
-			Result: float64(s.stats.Move),
-			Index:  index,
+		if s.speed > 0 {
+			time.Sleep(100 / time.Duration(s.speed) * time.Millisecond)
 		}
+		s.stats.Move++
 	}
-
-	return
 }
 
 func (s *SnakeSimulator) GetHeadState() []float64 {
 	headX := float64(s.snake.Points[0].X)
 	headY := float64(s.snake.Points[0].Y)
-	// tailX := float64(s.snake.Points[len(s.snake.Points)-1].X)
-	// tailY := float64(s.snake.Points[len(s.snake.Points)-1].Y)
+	tailX := float64(s.snake.Points[len(s.snake.Points)-1].X)
+	tailY := float64(s.snake.Points[len(s.snake.Points)-1].Y)
 	// prevX := float64(s.snake.Points[1].X)
 	// prevY := float64(s.snake.Points[1].Y)
 	foodX := float64(s.field.Food.X)
@@ -188,44 +207,15 @@ func (s *SnakeSimulator) GetHeadState() []float64 {
 	height := float64(s.field.Height)
 	diag := float64(width) * math.Sqrt2
 
-	// tBack := float64(0.0)
-	// bBack := float64(0.0)
-	// lBack := float64(0.0)
-	// rBack := float64(0.0)
-	// if prevX == headX {
-	// 	if prevY > headY {
-	// 		bBack = 1.0
-	// 	} else {
-	// 		tBack = 1.0
-	// 	}
-	// }
-
-	// if prevY == headY {
-	// 	if prevX > headX {
-	// 		lBack = 1.0
-	// 	} else {
-	// 		rBack = 1.0
-	// 	}
-	// }
-
 	lWall := headX
 	rWall := (width - headX)
 	tWall := headY
 	bWall := (height - headY)
+
 	lFood := float64(width)
 	rFood := float64(width)
 	tFood := float64(height)
 	bFood := float64(height)
-
-	// tlFood := float64(diag)
-	// trFood := float64(diag)
-	// blFood := float64(diag)
-	// brFood := float64(diag)
-	tlWall := float64(0)
-	trWall := float64(0)
-	blWall := float64(0)
-	brWall := float64(0)
-
 	if headX == foodX {
 		tFood = headY - foodY
 		if tFood < 0 {
@@ -248,22 +238,30 @@ func (s *SnakeSimulator) GetHeadState() []float64 {
 		}
 	}
 
-	// if math.Abs(foodY-headY) == math.Abs(foodX-headX) {
-	// 	if foodX > headX {
-	// 		if foodY > headY {
-	// 			trFood = math.Abs(foodX-headX) * math.Sqrt2
-	// 		} else {
-	// 			brFood = math.Abs(foodX-headX) * math.Sqrt2
-	// 		}
-	// 	} else {
-	// 		if foodY > headY {
-	// 			tlFood = math.Abs(foodX-headX) * math.Sqrt2
-	// 		} else {
-	// 			blFood = math.Abs(foodX-headX) * math.Sqrt2
-	// 		}
-	// 	}
-	// }
+	tlFood := float64(diag)
+	trFood := float64(diag)
+	blFood := float64(diag)
+	brFood := float64(diag)
+	if math.Abs(foodY-headY) == math.Abs(foodX-headX) {
+		if foodX > headX {
+			if foodY > headY {
+				trFood = math.Abs(foodX-headX) * math.Sqrt2
+			} else {
+				brFood = math.Abs(foodX-headX) * math.Sqrt2
+			}
+		} else {
+			if foodY > headY {
+				tlFood = math.Abs(foodX-headX) * math.Sqrt2
+			} else {
+				blFood = math.Abs(foodX-headX) * math.Sqrt2
+			}
+		}
+	}
 
+	tlWall := float64(0)
+	trWall := float64(0)
+	blWall := float64(0)
+	brWall := float64(0)
 	if lWall > tWall {
 		tlWall = float64(tWall) * math.Sqrt2
 	} else {
@@ -312,68 +310,68 @@ func (s *SnakeSimulator) GetHeadState() []float64 {
 		brWall = float64(rWall) * math.Sqrt2
 	}
 
-	// tTail := (headY - tailY)
-	// if tTail < 0 {
-	// 	tTail = height
-	// }
-	// bTail := (tailY - headY)
-	// if bTail < 0 {
-	// 	bTail = height
-	// }
-	// lTail := (headX - tailX)
-	// if lTail < 0 {
-	// 	tTail = width
-	// }
-	// rTail := (tailX - headX)
-	// if lTail < 0 {
-	// 	tTail = width
-	// }
-
-	// tlTail := float64(diag)
-	// trTail := float64(diag)
-	// blTail := float64(diag)
-	// brTail := float64(diag)
-	// if math.Abs(headY-tailY) == math.Abs(headX-tailX) {
-	// 	if tailY > headY {
-	// 		if tailX > headX {
-	// 			trTail = math.Abs(tailX-headX) * math.Sqrt2
-	// 		} else {
-	// 			tlTail = math.Abs(tailX-headX) * math.Sqrt2
-	// 		}
-	// 	} else {
-	// 		if tailX > headX {
-	// 			brTail = math.Abs(tailX-headX) * math.Sqrt2
-	// 		} else {
-	// 			blTail = math.Abs(tailX-headX) * math.Sqrt2
-	// 		}
-	// 	}
-	// }
+	tTail := (headY - tailY)
+	if tTail < 0 {
+		tTail = height
+	}
+	bTail := (tailY - headY)
+	if bTail < 0 {
+		bTail = height
+	}
+	lTail := (headX - tailX)
+	if lTail < 0 {
+		tTail = width
+	}
+	rTail := (tailX - headX)
+	if lTail < 0 {
+		tTail = width
+	}
+
+	tlTail := float64(diag)
+	trTail := float64(diag)
+	blTail := float64(diag)
+	brTail := float64(diag)
+	if math.Abs(headY-tailY) == math.Abs(headX-tailX) {
+		if tailY > headY {
+			if tailX > headX {
+				trTail = math.Abs(tailX-headX) * math.Sqrt2
+			} else {
+				tlTail = math.Abs(tailX-headX) * math.Sqrt2
+			}
+		} else {
+			if tailX > headX {
+				brTail = math.Abs(tailX-headX) * math.Sqrt2
+			} else {
+				blTail = math.Abs(tailX-headX) * math.Sqrt2
+			}
+		}
+	}
 
 	return []float64{
 		lWall / width,
 		rWall / width,
 		tWall / height,
 		bWall / height,
-		// (1.0 - lFood/width),
-		// (1.0 - rFood/width),
-		// (1.0 - tFood/height),
-		// (1.0 - bFood/height),
+		(1.0 - lFood/width),
+		(1.0 - rFood/width),
+		(1.0 - tFood/height),
+		(1.0 - bFood/height),
 		tlWall / diag,
 		trWall / diag,
 		blWall / diag,
 		brWall / diag,
-		// (1.0 - tlFood/diag),
-		// (1.0 - trFood/diag),
-		// (1.0 - blFood/diag),
-		// (1.0 - brFood/diag),
-		// tTail / height,
-		// bTail / height,
-		// lTail / width,
-		// rTail / width,
-		// tlTail / diag,
-		// trTail / diag,
-		// blTail / diag,
-		// brTail / diag,
+		(1.0 - tlFood/diag),
+		(1.0 - trFood/diag),
+		(1.0 - blFood/diag),
+		(1.0 - brFood/diag),
+		tTail / height,
+		bTail / height,
+		lTail / width,
+		rTail / width,
+		tlTail / diag,
+		trTail / diag,
+		blTail / diag,
+		brTail / diag,
 	}
 }