Go语言实现遗传算法的实例代码

2019-11-10 11:24:30丽君

我立马定义了一组设置,以便在稍后启动的算法中用到。

第二部分的GeneticAlgorithmRunner这个看起来有点奇怪。GeneticAlgorithmRunner是一个接口,询问如何生成初始种群,执行corssovers和mutataions,并对答案进行排序,以便在Population中保持最好的个体,这样下一代才会更加优秀。我认为这看起来很奇怪,因为“接口”通常用于面向对象的语言,通常会要求对象实现某些特性和方法。这里没有什么差别。这一小段代码实际上是在说,它正在请求一些东西来定义这些方法的细节。我是这样做的:

type QuadraticGA struct {}
func (l QuadraticGA) GenerateInitialPopulation(populationSize int) []interface{}{
 initialPopulation := make([]interface{}, 0, populationSize)
 for i:= 0; i < populationSize; i++ {
  initialPopulation = append(initialPopulation, makeNewEntry())
 }
 return initialPopulation
}
func (l QuadraticGA) PerformCrossover(result1, result2 interface{}, _ int) interface{}{
 return (result1.(float64) + result2.(float64)) / 2
}
func (l QuadraticGA) PerformMutation(_ interface{}, _ int) interface{}{
 return makeNewEntry()
}
func (l QuadraticGA) Sort(population []interface{}){
 sort.Slice(population, func(i, j int) bool {
  return calculate(population[i].(float64)) > calculate(population[j].(float64))
 })
}

更奇怪的是,我从来没有提到过这些方法的接口。请记住,因为没有对象,也没有继承。QuadraticGA结构体是一个空白对象,隐式地作为GeneticAlgorithmRunner。每个必需的方法都在括号中绑定到该结构体,就像Java中的“@ override”。现在,结构体和设置需要传递给运行该算法的模块。

settings := ga.GeneticAlgorithmSettings{
  PopulationSize: 5,
  MutationRate: 10,
  CrossoverRate: 100,
  NumGenerations: 20,
  KeepBestAcrossPopulation: true,
}
best, err := ga.Run(QuadraticGA{}, settings)
if err != nil {
  println(err)
}else{
  fmt.Printf("Best: x: %f y: %fn", best, calculate(best.(float64)))
}

很简单,对吧?“QuadraticGA {}”只是简单地创建了该结构的一个新实例,其余的则由Run()方法完成。该方法返回搜索结果和发生的任何错误,因为Go不相信try / catch——另一场战争作者采取了严格的设计立场。

现在来计算每个项的性能,以求二次函数求出的二次函数来求出一个新的X值的方法:

func makeNewEntry() float64 {
  return highRange * rand.Float64()
}
func calculate(x float64) float64 {
  return math.Pow(x, 2) - 6*x + 2 // minimum should be at x=3
}

既然已经为二次实现创建了接口,那么GA本身需要完成:

func Run(geneticAlgoRunner GeneticAlgorithmRunner, settings GeneticAlgorithmSettings) (interface{}, error){
  population := geneticAlgoRunner.GenerateInitialPopulation(settings.PopulationSize)
  geneticAlgoRunner.Sort(population)
  bestSoFar := population[len(population) - 1]
  for i:= 0; i < settings.NumGenerations; i++ {
   newPopulation := make([]interface{}, 0, settings.PopulationSize)
   if settings.KeepBestAcrossPopulation {
     newPopulation = append(newPopulation, bestSoFar)
   }
   // perform crossovers with random selection
   probabilisticListOfPerformers := createStochasticProbableListOfIndividuals(population)
   newPopIndex := 0
   if settings.KeepBestAcrossPopulation{
     newPopIndex = 1
   }
   for ; newPopIndex < settings.PopulationSize; newPopIndex++ {
     indexSelection1 := rand.Int() % len(probabilisticListOfPerformers)
     indexSelection2 := rand.Int() % len(probabilisticListOfPerformers)
     // crossover
     newIndividual := geneticAlgoRunner.PerformCrossover(
      probabilisticListOfPerformers[indexSelection1],
      probabilisticListOfPerformers[indexSelection2], settings.CrossoverRate)
     // mutate
     if rand.Intn(101) < settings.MutationRate {
      newIndividual = geneticAlgoRunner.PerformMutation(newIndividual)
     }
     newPopulation = append(newPopulation, newIndividual)
   }
   population = newPopulation
   // sort by performance
   geneticAlgoRunner.Sort(population)
   // keep the best so far
   bestSoFar = population[len(population) - 1]
  }
  return bestSoFar, nil
}
func createStochasticProbableListOfIndividuals(population []interface{}) []interface{} {
  totalCount, populationLength:= 0, len(population)
  for j:= 0; j < populationLength; j++ {
   totalCount += j
  }
  probableIndividuals := make([]interface{}, 0, totalCount)
  for index, individual := range population {
   for i:= 0; i < index; i++{
     probableIndividuals = append(probableIndividuals, individual)
   }
  }
  return probableIndividuals
}