扫二维码与项目经理沟通
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流
本篇内容介绍了“Go语言中如何实现一个遗传算法”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!
网站建设哪家好,找创新互联建站!专注于网页设计、网站建设、微信开发、成都微信小程序、集团企业网站建设等服务项目。为回馈新老客户创新互联还提供了沙雅免费建站欢迎大家使用!
话不多说,让我们开始从代码说起吧!***个例子与我以前做过的很类似:找到一个二次的最小值。
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{}) }
我立马定义了一组设置,以便在稍后启动的算法中用到。
第二部分的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: %f\n", 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 }
很像以前,一个新的人口被创造出来,人口的成员将会世代交配,而他们的后代可能携带突变。一个人的表现越好,就越有可能交配。随着时间的推移,算法收敛到***的答案,或者至少是一个相当不错的答案。
那么当它运行时,它返回了什么呢?
Best: x: 3.072833 y: -6.994695
不坏!由于人口规模只有5、20代,而且输入的范围被限制在[0 100],这一搜索就钉在了顶点上。
现在,您可能想知道为什么我定义了所有的接口方法来返回“接口{}”。这就像Go和generics一样。没有对象,因此没有对象类型返回,但是没有描述的大小的数据仍然可以在堆栈上传递。这本质上也是这个返回类型的含义:它传递一些已知的和类似的类型的对象。有了这个“泛型”,我就可以将GA移动到它自己的包中,并将相同的代码移到多个不同类型的数据上。
我们有两个输入的3D二次方程,而不是一个二维二次方程的单个输入。接口方法只需要很小的改变:
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(populationSize 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) PerformMutation(_ 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)) } }
而不是到处都是float64s,任何地方都可以通过Quad3D的条目;每一个都有一个X和一个Y值。对于创建的每个条目,都使用contructor makeNewQuadEntry创建。Run()方法中的代码都没有更改。
当它运行时,我们得到这个输出:
Best: x: 3.891671 y: 4.554884 z: -12.787259
很接近了!
哦,我忘了说走快了!在Java中执行此操作时,即使使用相同的设置,也会有明显的等待时间。在一个相对较小的范围内求解二次方程并不是很复杂,但它对一个人来说是值得注意的。
Go是本地编译的,比如C。当二进制执行时,它似乎马上就吐出一个答案。这里有一个简单的方法来度量每次运行的执行时间:
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) }
边注:我能说我很高兴我们是一个开发者社区,让他们从过去的错误中走出来,并把综合的时间模块和包构建成一种语言吗?Java 8 +拥有它们,Python拥有它们,并拥有它们。这使我开心。
现在的输出:
Best: x: 3.072833 y: -6.994695 136,876 Best: x: 3.891671 y: 4.554884 z: -12.787259 4,142,778
那“近乎瞬间”的感觉是我想要传达的,现在我们有了很难的数字。136,876看起来很大,但要在纳秒内报告时间。
重申一遍:纳秒。不是几毫秒,我们都习惯了在互联网时代或者其他像Python和Java这样的通用语言;纳秒。1/1,000,000毫秒。
这意味着我们在不到一毫秒的时间里找到了一个使用遗传算法来搜索答案的二次方程的答案。这句话,“该死的瞬间”似乎很合适,不是吗?这包括打印到终端。
那么,要计算更密集的东西呢?在我展示一种寻找好的梦幻足球lineups的方法之前,我在Fanduel上使用。这包括从电子表格中读取数据,制作和过滤lineups,并进行更复杂的交叉和突变。强制寻找***解决方案可能需要超过75,000年(至少使用我当时使用的Python)。
我不需要再检查所有的细节,你可以自己去看代码,但我会在这里显示输出:
Best: 121.409960:, $58100 QB: Aaron Rodgers - 23.777778 RB: Latavius Murray - 15.228571 RB: DeMarco Murray - 19.980000 WR: Kelvin Benjamin - 11.800000 WR: Stefon Diggs - 14.312500 WR: Alshon Jeffery - 9.888889 TE: Connor Hamlett - 8.200000 D: Philadelphia Eagles - 10.777778 K: Phil Dawson - 7.444444 16,010,182
“Go语言中如何实现一个遗传算法”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注创新互联网站,小编将为大家输出更多高质量的实用文章!
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流