基于遗传算法计算函数最大值--JavaScript实现

背景知识

  • 进化算法(evolutionary algorithms,EA)是基于自然选择和自然遗传等生物进化机制的一种搜索算法。
  • 生物进化是通过繁殖、变异、竞争和选择实现的;而进化算法则主要通过选择、重组和变异这三种操作实现优化问题的求解。
  • 进化算法是一个“算法簇”,包括遗传算法(GA)、遗传规划、进化策略和进化规划等。

基本思想

基于遗传算法计算函数最大值--JavaScript实现_第1张图片

基于遗传算法计算函数最大值--JavaScript实现_第2张图片

实际问题

下面我将结合下图中的问题并结合代码来解决这个问题。
这里写图片描述

一、初始化染色体种群

首先,我们定义一下染色体的结构。可以利用JS中的对象,使其有两个属性,一个属性是基因位,一个是该基因对应的适应度函数值。

        //定义染色体的结构
        var gene = {
            "geneBit": undefined, //基因
            "fitValue": undefined //该基因对应的适应度函数值
        }

接着,初始化染色体。根据题目要求,[0,30]之间的整数,则用5位二进制编码即可表示。

       var bit = 5; //基因的位数
      // 初始化染色体
       function createSingle() {
           var str = "";
           for (var i = 0; i < bit - 1; i++) {
               if (Math.random() < 0.5) {
                   str += "1";
               } else {
                   str += "0";
               }
           }

           //保证产生的数在[0,30]内
           if (str == "1111") {
               str += 0;
           } else {
               if (Math.random() < 0.5) {
                   str += "1";
               } else {
                   str += "0";
               }
           }
           return str;
       }

接着,初始化种群。

        var Popnum = 100; //染色体个数
        var Pop = new Array(); //种群
        // 初始化种群
        function createPop() {
            for (var j = 0; j < Popnum; j++) {
                Pop[j] = new Object();
                Pop[j].geneBit = createSingle();
            }
        }

二、计算每个个体的适应度函数值

计算适应度之前,要选择合适的适应度函数。适应度函数具有最大性和非负性两个特点。即适应度函数值越大,其被选择的可能性越大。
我利用了R语言中curve(x*x*x-60*x*x+900*x+100,0,30)画出了该函数图像。
基于遗传算法计算函数最大值--JavaScript实现_第3张图片
发现其满足以上条件,所以我们选择适应度函数为:x * x * x - 60 * x * x + 900 * x + 100
当然,在计算适应度函数值之前还有进行译码工作,即二进制转化为十进制。

        // 计算适应度函数值
        function calFitValue(x) {
            return x * x * x - 60 * x * x + 900 * x + 100;
        }

        // 二进制转化成十进制(译码)
        function toTen(x) {
            return x[0] * 16 + x[1] * 8 + x[2] * 4 + x[3] * 2 + x[4] * 1;
        }

        //二进制转换为十进制并且计算适应度函数值
        function toTenAndCalFitValue() {
            for (var j = 0; j < Popnum; j++) {
                var tenValue = toTen(Pop[j].geneBit);
                Pop[j].fitValue = calFitValue(tenValue);
            }
        }

三、根据适应度函数值进行选择交叉

体现自然中的优胜劣汰情况。
采用轮盘赌方法进行选择。

  • 按个体的选择概率产生一个轮盘,轮盘每个区的角度与个体的选择概率成比例。
  • 产生一个随机数,它落入转盘的哪个区域就选择相应的个体交叉。 遇到第一个比他大的数即可落到相应区域。
        //轮盘赌选择法
        function selection() {
            var totallFitValue = 0; //总的适应度
            var choicePro = new Array(Popnum); //每个染色体的适应度值对应的概率
            var sumChoicePro = new Array(Popnum); //累计概率
            for (var j = 0; j < Popnum; j++) {
                totallFitValue += Pop[j].fitValue;
            }
            for (var j = 0; j < Popnum; j++) {
                choicePro[j] = Pop[j].fitValue / totallFitValue;
                if (j != 0) {
                    sumChoicePro[j] = sumChoicePro[j - 1] + choicePro[j];
                } else {
                    sumChoicePro[j] = choicePro[j];
                }
            }

            for (var j = 0; j < Popnum - 1; j++) {
                var rand = Math.random();
                for (var k = 0; k < Popnum - 1; k++) {
                    if (rand <= sumChoicePro[k]) {
                        crossover(k, k + 1); //选择出来的染色体与他下一个进行交叉
                        break;
                    }
                }
            }
        }

四、交叉

  • 一点交叉:在个体串中随机设定一个交叉点,实行交叉时,该点前或后的两个个体的部分结构进行互换,并生成两个新的个体。
  • 二点交叉:随机设置两个交叉点,将两个交叉点之间的码串相互交换。
  • 均匀交叉:按照均匀概率抽取一些位,每一位是否被选取都是随机的,并且独立于其他位。然后将两个个体被抽取位互换组成两个新个体。
    在进行二点交叉时,要注意交叉方法的修正。
        // 交叉(单点)
        function crossover(x, y) {
            if (Math.random() < 0.6) { //变异概率为0.6
                var str1, str2, tem1 = 0,
                    tem2 = 0;
                var pos = Math.floor(Math.random() * bit);
                str1 = Pop[x].geneBit.substring(pos);
                str2 = Pop[y].geneBit.substring(pos);
                Pop[x].geneBit = Pop[x].geneBit.substring(0, pos) + str2;
                Pop[y].geneBit = Pop[y].geneBit.substring(0, pos) + str1;

                // 为交叉后得到的个体计算适应度值
                tem1 = toTen(Pop[x].geneBit);
                Pop[x].fitValue = calFitValue(tem1);
                tem2 = toTen(Pop[y].geneBit);
                Pop[y].fitValue = calFitValue(tem2);
            }
        }

五、变异

模拟遗传中突变的情况。

  • 位点变异:群体中的个体码串,随机挑选一个或多个基因座,并对这些基因座的基因值以变异概率作变动。
        // 变异
        function mutation() {
            for (var j = 0; j < Popnum; j++) {
                if (Math.random() < 0.01) { //变异概率为0.01
                    var pos = Math.floor(Math.random() * bit);
                    var tem = 0;
                    if (Pop[j].geneBit[pos] == "1") {
                        Pop[j].geneBit[pos] = "0";
                    } else {
                        Pop[j].geneBit[pos] = "1";
                    }

                    // 为变异后得到的个体计算适应度值
                    tem = toTen(Pop[j].geneBit);
                    Pop[j].fitValue = calFitValue(tem);
                }
            }
        }

当然,还需要记录每次迭代后的最大值。

        // 记录最大值
        function bestGene() {
            bestValue = 0, bestGeneTenValue = 0;
            for (var j = 0; j < Popnum; j++) {
                if (Pop[j].fitValue > bestValue) {
                    bestValue = Pop[j].fitValue;
                    bestGeneTenValue = toTen(Pop[j].geneBit);
                }
            }
        }

Demo可以戳这里看啦

以上也是个人学习中的一些理解与实践,有任何错误,望大家批评指正。

你可能感兴趣的:(生活随手记,algorithm)