Kaynağa Gözat

Add customization for crossbreeding in genetic

- Add network Copy function
- Makee crossbreeding customizable
Alexey Edelev 5 yıl önce
ebeveyn
işleme
a592660241

+ 26 - 15
neuralnetwork/genetic/genetic.go

@@ -8,26 +8,32 @@ import (
 	neuralnetwork "../neuralnetwork"
 )
 
+type PopulationConfig struct {
+	PopulationSize int
+	SelectionSize  float64 // 0..1 persentage of success individuals to be used as parents for population
+	CrossbreedPart float64 // 0..1 persentage of weights and biases to be exchanged beetween individuals while Crossbreed
+}
+
 type Population struct {
-	populationSize int
-	Networks       []*neuralnetwork.NeuralNetwork
-	verifier       PopulationVerifier
-	mutagen        Mutagen
+	populationConfig *PopulationConfig
+	Networks         []*neuralnetwork.NeuralNetwork
+	verifier         PopulationVerifier
+	mutagen          Mutagen
 }
 
-func NewPopulation(verifier PopulationVerifier, mutagen Mutagen, populationSize int, sizes []int) (p *Population) {
-	if populationSize%2 != 0 {
+func NewPopulation(verifier PopulationVerifier, mutagen Mutagen, populationConfig PopulationConfig, sizes []int) (p *Population) {
+	if populationConfig.PopulationSize%2 != 0 {
 		return nil
 	}
 
 	p = &Population{
-		populationSize: populationSize,
-		Networks:       make([]*neuralnetwork.NeuralNetwork, populationSize),
-		verifier:       verifier,
-		mutagen:        mutagen,
+		populationConfig: &populationConfig,
+		Networks:         make([]*neuralnetwork.NeuralNetwork, populationConfig.PopulationSize),
+		verifier:         verifier,
+		mutagen:          mutagen,
 	}
 
-	for i := 0; i < populationSize; i++ {
+	for i := 0; i < populationConfig.PopulationSize; i++ {
 		var err error
 		p.Networks[i], err = neuralnetwork.NewNeuralNetwork(sizes, nil)
 		if err != nil {
@@ -49,23 +55,28 @@ func (p *Population) crossbreedPopulation(results []*IndividalResult) {
 		return results[i].Result < results[j].Result
 	})
 
-	for i := 1; i < p.populationSize; i += 2 {
+	etalons := int(float64(p.populationConfig.PopulationSize) * p.populationConfig.SelectionSize)
+	for i := 1; i < p.populationConfig.PopulationSize; i += 2 {
+		firstParentBase := results[i%etalons].Index
+		secondParentBase := results[(i-1)%etalons].Index
 		firstParent := results[i].Index
 		secondParent := results[i-1].Index
-		crossbreed(p.Networks[firstParent], p.Networks[secondParent])
+		p.Networks[firstParent] = p.Networks[firstParentBase].Copy()
+		p.Networks[secondParent] = p.Networks[secondParentBase].Copy()
+		crossbreed(p.Networks[firstParent], p.Networks[secondParent], p.populationConfig.CrossbreedPart)
 		p.mutagen.Mutate(p.Networks[firstParent])
 		p.mutagen.Mutate(p.Networks[secondParent])
 	}
 }
 
-func crossbreed(firstParent, secondParent *neuralnetwork.NeuralNetwork) {
+func crossbreed(firstParent, secondParent *neuralnetwork.NeuralNetwork, crossbreedPart float64) {
 	for l := 1; l < firstParent.LayerCount; l++ {
 		firstParentWeights := firstParent.Weights[l]
 		secondParentWeights := secondParent.Weights[l]
 		firstParentBiases := firstParent.Biases[l]
 		secondParentBiases := secondParent.Biases[l]
 		r, c := firstParentWeights.Dims()
-		for i := 0; i < r/2; i++ {
+		for i := 0; i < int(float64(r)*crossbreedPart); i++ {
 			for j := 0; j < c; j++ {
 				// Swap first half of weights
 				w := firstParentWeights.At(i, j)

+ 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), 400, []int{16, 18, 18, 4})
+	p := genetic.NewPopulation(s, mutagen.NewDummyMutagen(50), genetic.PopulationConfig{PopulationSize: 400, SelectionSize: 0.1, CrossbreedPart: 0.5}, []int{20, 18, 18, 4})
 	for _, net := range p.Networks {
 		net.SetStateWatcher(rc)
 	}

+ 31 - 9
neuralnetwork/neuralnetwork/neuralnetwork.go

@@ -123,15 +123,16 @@ func NewNeuralNetwork(sizes []int, gradientDescentInitializer GradientDescentIni
 		}
 	}
 
-	nn = &NeuralNetwork{}
-	nn.Sizes = sizes
-	nn.LayerCount = len(sizes)
-	nn.Biases = make([]*mat.Dense, nn.LayerCount)
-	nn.Weights = make([]*mat.Dense, nn.LayerCount)
-	nn.BGradient = make([]interface{}, nn.LayerCount)
-	nn.WGradient = make([]interface{}, nn.LayerCount)
-
-	nn.gradientDescentInitializer = gradientDescentInitializer
+	lenSizes := len(sizes)
+	nn = &NeuralNetwork{
+		Sizes:                      sizes,
+		LayerCount:                 len(sizes),
+		Biases:                     make([]*mat.Dense, lenSizes),
+		Weights:                    make([]*mat.Dense, lenSizes),
+		BGradient:                  make([]interface{}, lenSizes),
+		WGradient:                  make([]interface{}, lenSizes),
+		gradientDescentInitializer: gradientDescentInitializer,
+	}
 
 	for l := 1; l < nn.LayerCount; l++ {
 		nn.Biases[l] = generateRandomDense(nn.Sizes[l], 1)
@@ -144,6 +145,27 @@ func NewNeuralNetwork(sizes []int, gradientDescentInitializer GradientDescentIni
 	return
 }
 
+func (nn *NeuralNetwork) Copy() (outNN *NeuralNetwork) {
+	outNN = &NeuralNetwork{
+		Sizes:                      nn.Sizes,
+		LayerCount:                 len(nn.Sizes),
+		Biases:                     make([]*mat.Dense, nn.LayerCount),
+		Weights:                    make([]*mat.Dense, nn.LayerCount),
+		BGradient:                  make([]interface{}, nn.LayerCount),
+		WGradient:                  make([]interface{}, nn.LayerCount),
+		gradientDescentInitializer: nn.gradientDescentInitializer,
+	}
+	for l := 1; l < nn.LayerCount; l++ {
+		outNN.Biases[l] = mat.DenseCopyOf(nn.Biases[l])
+		outNN.Weights[l] = mat.DenseCopyOf(nn.Weights[l])
+		if outNN.gradientDescentInitializer != nil {
+			outNN.BGradient[l] = outNN.gradientDescentInitializer(outNN, l, BiasGradient)
+			outNN.WGradient[l] = outNN.gradientDescentInitializer(outNN, l, WeightGradient)
+		}
+	}
+	return
+}
+
 func (nn *NeuralNetwork) SetStateWatcher(watcher StateWatcher) {
 	nn.watcher = watcher
 	if watcher != nil {

+ 67 - 62
neuralnetwork/snakesimulator/snakesimulator.go

@@ -144,7 +144,7 @@ func (s *SnakeSimulator) Verify(population *genetic.Population) (results []*gene
 		}
 
 		results[index] = &genetic.IndividalResult{
-			Result: float64(len(s.snake.Points)) * float64(s.stats.Move),
+			Result: float64(len(s.snake.Points)-2) * float64(s.stats.Move),
 			Index:  index,
 		}
 	}
@@ -152,22 +152,46 @@ func (s *SnakeSimulator) Verify(population *genetic.Population) (results []*gene
 }
 
 func (s *SnakeSimulator) GetHeadState() []float64 {
-	headX := int32(s.snake.Points[0].X)
-	headY := int32(s.snake.Points[0].Y)
-	foodX := int32(s.field.Food.X)
-	foodY := int32(s.field.Food.Y)
-	width := int32(s.field.Width)
-	height := int32(s.field.Height)
+	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)
+	// prevX := float64(s.snake.Points[1].X)
+	// prevY := float64(s.snake.Points[1].Y)
+	foodX := float64(s.field.Food.X)
+	foodY := float64(s.field.Food.Y)
+	width := float64(s.field.Width)
+	height := float64(s.field.Height)
 	diag := float64(width) * math.Sqrt2
 
-	lWall := headX
-	rWall := width - headX
-	tWall := headY
-	bWall := height - headY
-	lFood := int32(0)
-	rFood := int32(0)
-	tFood := int32(0)
-	bFood := int32(0)
+	tBack := float64(1.0)
+	bBack := float64(1.0)
+	lBack := float64(1.0)
+	rBack := float64(1.0)
+	// if prevX == headX {
+	// 	if prevY > headY {
+	// 		tBack = 0.0
+	// 	} else {
+	// 		bBack = 0.0
+	// 	}
+	// }
+
+	// if prevY == headY {
+	// 	if prevX > headX {
+	// 		rBack = 0.0
+	// 	} else {
+	// 		lBack = 0.0
+	// 	}
+	// }
+
+	lWall := headX * lBack
+	rWall := (width - headX) * rBack
+	tWall := headY * tBack
+	bWall := (height - headY) * bBack
+	lFood := float64(0)
+	rFood := float64(0)
+	tFood := float64(0)
+	bFood := float64(0)
 
 	tlFood := float64(0)
 	trFood := float64(0)
@@ -178,21 +202,11 @@ func (s *SnakeSimulator) GetHeadState() []float64 {
 	blWall := float64(0)
 	brWall := float64(0)
 
-	if foodX == headX {
-		if foodY > headY {
-			bFood = foodY - headY
-		} else {
-			tFood = headY - foodY
-		}
-	}
+	tFood = (1.0 - (headY-foodY)/height) * tBack
+	bFood = (1.0 - (foodY-headY)/height) * bBack
 
-	if foodY == headY {
-		if foodX > headX {
-			rFood = foodX - headX
-		} else {
-			lFood = headX - foodX
-		}
-	}
+	rFood = (1.0 - (foodX-headX)/width) * rBack
+	lFood = (1.0 - (headX-foodX)/width) * lBack
 
 	if lWall > tWall {
 		tlWall = float64(tWall) * math.Sqrt2
@@ -218,41 +232,32 @@ func (s *SnakeSimulator) GetHeadState() []float64 {
 		brWall = float64(rWall) * math.Sqrt2
 	}
 
-	foodDiagXDiff := math.Abs(float64(foodX - headX))
-	foodDiagYDiff := math.Abs(float64(foodY - headY))
-	if foodDiagXDiff == foodDiagYDiff {
-		if math.Signbit(float64(foodX - headX)) {
-			if math.Signbit(float64(foodY - headY)) {
-				trFood = foodDiagXDiff * math.Sqrt2
-			} else {
-				brFood = foodDiagXDiff * math.Sqrt2
-			}
-		} else {
-			if math.Signbit(float64(foodY - headY)) {
-				tlFood = foodDiagXDiff * math.Sqrt2
-			} else {
-				blFood = foodDiagXDiff * math.Sqrt2
-			}
-		}
-	}
+	tTail := (headY - tailY)
+	bTail := (tailY - headY)
+	lTail := (headX - tailX)
+	rTail := (tailX - headX)
 
 	return []float64{
-		float64(lWall) / float64(width),
-		float64(lFood) / float64(lWall),
-		float64(rWall) / float64(width),
-		float64(rFood) / float64(rWall),
-		float64(tWall) / float64(height),
-		float64(tFood) / float64(tWall),
-		float64(bWall) / float64(height),
-		float64(bFood) / float64(bWall),
-		float64(tlWall) / diag,
-		float64(tlFood) / diag,
-		float64(trFood) / diag,
-		float64(trWall) / diag,
-		float64(blFood) / diag,
-		float64(blWall) / diag,
-		float64(brFood) / diag,
-		float64(brWall) / diag,
+		lWall / width,
+		lFood,
+		rWall / width,
+		rFood,
+		tWall / height,
+		tFood,
+		bWall / height,
+		bFood,
+		tlWall / diag,
+		tlFood,
+		trWall / diag,
+		trFood,
+		blWall / diag,
+		blFood,
+		brWall / diag,
+		brFood,
+		tTail / height,
+		bTail / height,
+		lTail / width,
+		rTail / width,
 	}
 }