How to implement genetic algorithm in Go language

Source: Internet
Author: User
Tags pow rand

Original: Go with genetic algorithms
Translation: Diwei

For fun, I decided to learn the go language. I think the best way to learn a new language is to learn in depth and make as many mistakes as possible. While this may be slow, you can ensure that no compilation errors will occur in later procedures.

The go language is different from the other languages I'm used to. Go prefers to implement it alone, while other languages like Java prefer inheritance. In fact, in the go language there is no inheritance of this concept, because it has no object at all. For example, C language, it has a structure, but there is no class. However, it is possible to have common ideas and design patterns such as "constructors" (a way to produce structures in an orderly manner in this case).

The go language strongly advocates the combination (composition), but also is very opposed to the practice of inheritance, on the network caused a strong discussion, but also let people rethink the direction of the language to go forward. So, from this point of view, the difference between the go language and other languages may not be that big.

This article will focus on how to implement genetic algorithm with Go language. If you have not participated in the Golang tour, I also suggest that you take a quick look at the language introduction.

Don't say much, let's start with the code! The first example is similar to what I've done before: find a minimum value of two times.

type GeneticAlgorithmSettings struct {  PopulationSize int  MutationRate int  CrossoverRate int  NumGenerations int  KeepBestAcrossPopulation bool}type GeneticAlgorithmRunner interface {  GenerateInitialPopulation(populationSize int) []interface{}  PerformCrossover(individual1, individual2 interface{}, mutationRate int) interface{}  PerformMutation(individual interface{}) interface{}  Sort([]interface{})}

I immediately defined a set of settings to be used in the algorithm that is started later.

The second part of the Geneticalgorithmrunner looks a bit strange. Geneticalgorithmrunner is an interface that asks how to generate initial populations, execute corssovers and mutataions, and sort the answers to keep the best individuals in the population so that the next generation will be better. I think this looks strange, because "interfaces" are often used in object-oriented languages and often require objects to implement certain features and methods. There's no difference here. This little piece of code is actually saying that it is requesting something to define the details of these methods. That's what I did:

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))  })}

Even more strangely, I never mentioned the interfaces of these methods. Keep in mind that there is no object and no inheritance. The QUADRATICGA structure is a blank object, implicitly as a geneticalgorithmrunner. Each of the required methods is bound to the struct in parentheses, like "@ override" in Java. Now, the struct and the settings need to be passed to the module that runs the algorithm.

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: %f\n", best, calculate(best.(float64)))}

It's simple, isn't it? " QUADRATICGA {} "simply creates a new instance of the struct, and the rest is done by the run () method. The method returns search results and any errors that occur because go does not believe that try/catch--another war author took a strict design stance.

Now to calculate the performance of each item, in order to find a new X-value method for the two functions of the two-time function:

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}

Now that the interface has been created for two implementations, the GA itself needs to be completed:

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) Newpopi Ndex: = 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], Probabil Isticlistofperformers[indexselection2], settings. Crossoverrate)//mutate if Rand. INTN (101) < settings.  mutationrate {newindividual = Geneticalgorunner.performmutation (newindividual)} newpopulation = Append (Newpopulation, newindividual)} population = newpopulation//Sort by performance Geneticalg Orunner.sort (population)//keep the best and far Bestsofar = Population[len (population)-1]} return Bestso Far, 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: = Rang E population {for i:= 0; i < index; i++{probableindividuals =Append (probableindividuals, Individual)}} return Probableindividuals} 

Like before, a new population is created, and members of the population will mate for generations, and their offspring may carry mutations. The better a person behaves, the more likely he is to mate. Over time, the algorithm converges to the best answer, or at least a pretty good answer.

So when it runs, what does it return?

Best: x: 3.072833 y: -6.994695

Not bad! Because the population is only 5, 20 generations, and the input range is limited to [0 100], the search is nailed to the vertex.

Now, you may wonder why I have defined all the interface methods to return "interface {}". It's like go and generics. There is no object, so no object type is returned, but data that does not describe the size can still be passed on the stack. This is essentially the meaning of this return type: it passes some known and similar types of objects. With this "generics", I can move GA into its own package and move the same code to several different types of data.

We have two input 3D two-time equations, not a single input of a two-dimensional two-second equation. The interface method requires only a small change:

Type quad3d struct {x, y float64}func makenewquadentry (newx, newy float64) quad3d {return quad3d{x:newx, Y:newy,}}func calculate3d (entry Quad3d) float64 {return math. Pow (entry.x, 2)-6 * entry.x + math. Pow (ENTRY.Y, 2)-6 * entry.y + 2}type quadratic3dga struct {}func (l QUADRATIC3DGA) generateinitialpopulation (populationSi ze int) []interface{}{initialpopulation: = Make ([]interface{}, 0, populationsize) for i:= 0; i < populationsize; i+ + {initialpopulation = append (Initialpopulation, Makenewquadentry (Makenewentry (), Makenewentry ())} return Initialpopulation} func (L QUADRATIC3DGA) performcrossover (RESULT1, Result2 interface{}, mutationrate int) interface{}{ R1entry, R2entry: = Result1. (QUAD3D), result2. (QUAD3D) return Makenewquadentry ((r1entry.x + r2entry.x)/2, (R1ENTRY.Y + r2entry.y)/2,)} func (l QUADRATIC3DGA) perfo Rmmutation (_ interface{}) interface{}{return Makenewquadentry (Makenewentry (), Makenewentry ())} func (L QUADRATIC3DGA) Sort (Population []interface{}) {sort. Slice (population, func (i, J int) bool {return Calculate3d (Population[i]. ( QUAD3D) > Calculate3d (Population[j]. ( QUAD3D)})}func Quadratic3dmain () {settings: = Ga. geneticalgorithmsettings{populationsize:25, Mutationrate:10, crossoverrate:100, numgenerations:20 , Keepbestacrosspopulation:true,} best, err: = Ga. Run (quadratic3dga{}, Settings) entry: = Best. (QUAD3D) If err! = Nil {println (err)}else{FMT. Printf ("best:x:%f y:%f z:%f\n", entry.x, Entry.y, Calculate3d (Entry))}}

And not everywhere is float64s, anywhere can be through QUAD3D entries; each has an x and a Y value. For each entry created, it is created with Contructor makenewquadentry. The code in the Run () method has not changed.

When it runs, we get this output:

Best: x: 3.891671 y: 4.554884 z: -12.787259

It's close!

Oh, I forgot to say go fast! When you do this in Java, there is a noticeable wait time even with the same settings. Solving two equations in a relatively small range is not complicated, but it is noteworthy to one person.

Go is compiled locally, such as C. When binary executes, it seems to spit out an answer right away. Here's an easy way to measure the execution time of each run:

func main() {   beforeQuadTime := time.Now()   quadraticMain()   afterQuadTime := time.Since(beforeQuadTime)   fmt.Printf("%d\n", afterQuadTime)   before3dQuadTime := time.Now()   quadratic3dMain()   after3dQuatTime := time.Since(before3dQuadTime)   fmt.Printf("%d\n", after3dQuatTime)}

Side note: Can I say that I am glad that we are a developer community to get them out of the mistakes of the past and to build integrated time modules and packages into one language? Java 8 + owns them, Python owns them, and owns them. It makes me happy.

Now the output:

Best: x: 3.072833 y: -6.994695136,876Best: x: 3.891671 y: 4.554884 z: -12.7872594,142,778

That "almost instantaneous" feeling is what I want to convey, and now we have the hard numbers. 136,876 looks great, but it's time to report in nanoseconds.

Again: nanosecond. Not a few milliseconds, we are used to the internet age or other common language such as Python and Java, nanoseconds. 1/1,000,000 milliseconds.

This means that in less than a millisecond we have found an answer to the two-time equation that uses genetic algorithms to search for answers. This sentence, "Damn moment" seems to fit, doesn't it? This includes printing to the terminal.

So, what about more dense stuff? Before I show a way to find a good fantasy football lineups, I use it on FanDuel. This includes reading data from spreadsheets, making and filtering lineups, and making more complex crosses and mutations. Forcing the search for the best solution may take more than 75,000 years (at least using the Python I was using at the time).

I don't need to check all the details, you can see the code yourself, but I'll show the output here:

Best: 121.409960:, $58100QB: Aaron Rodgers - 23.777778RB: Latavius Murray - 15.228571RB: DeMarco Murray - 19.980000WR: Kelvin Benjamin - 11.800000WR: Stefon Diggs - 14.312500WR: Alshon Jeffery - 9.888889TE: Connor Hamlett - 8.200000D: Philadelphia Eagles - 10.777778K: Phil Dawson - 7.44444416,010,182

Oh, yes! It seems to be a good squad now! It takes only 16 milliseconds to find it.

Now, this genetic algorithm can be improved. As with C, when an object is passed to a method, the object is copied (read data) on the stack. As object sizes grow, it's best not to replicate them over and over again, but to create them in the heap and pass pointers around them. At the moment, I will take it as a future job.

Go is also written with coroutines and channel native support, and using multiple cores to solve a problem is much simpler than in the past, which is a huge advantage compared to other languages in the single-core era. I want to enhance this algorithm to use these tools, but this must also be left to future work.

I enjoy the learning process very much. It's hard for me to think of engineering solutions in combination rather than inheritance, because I've been used to more than 8 years and I've learned how to program. But each language and method has its own advantages and disadvantages; Each language is a different tool in my tools. For anyone who's worried about trying, don't. There is a hump (more like a deceleration belt), but you will soon overcome it and embark on the path to success.

There are some things I like, I like other languages, mainly a set of basic functional methods to manipulate the data. I need a lambda function and method to map, reduce, and filter the array or part of the data. The reason designers oppose feature implementations is that the code should always be simple, easy to read and write, and this is achievable with a for loop. I think that mapping, filtering, and reducing are usually easier to read and write, but this is an argument that has been raging in the war.

Although I disagree with some developers and I have to consider different ways to solve the problem, go is really a good language. I encourage you to try again after learning one or two languages. It soon became one of the most popular languages, and there were many reasons why. I look forward to using it more in the future.

How to implement genetic algorithm in Go language

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.