遗传算法自我复习之高维单目标优化问题

面试过程是一个很好的查漏补缺的机会,要好好梳理,覆盘面试过程。

比如最近面试就发现,我已经把一年前课堂上学的遗传算法和优化方法,全数还给老师了,渣都不剩O_O。。。

这对得起辛辛苦苦上的课和呕心沥血写的大作业吗?

于是抓紧复习,真的多亏当年呕心沥血写的大作业,很快勾起了我的回忆~


遗传算法思路很简单,关键在于在一定概率下选择比较好的样本留下,而结果改进的关键在于交叉和变异

当年做的单目标优化问题主要用于找到高维函数的极值,代码中用的是20维。简单来说就是求一组自变量(100个),找到类似于

f(x_{1},x_{2},\cdots ,x_{20}) =k_{1}x_{1}+k_{2}x_{2} +\cdots +k_{20}x_{20}

这样的函数的极值(最小值或者最大值)。

当然实际中函数的样子更加复杂,代码中有当时的6道题目,可以感受一下。

遗传算法解题步骤归结下来分为几步:

  1. 初始化种群样本(比如100个个体构成种群,每个个体有20个自变量的初始值);
  2. 计算适应度(把自变量代入要求解的函数中,有些函数要求求最小值,那么适应度要计算为函数值的倒数,保证越接近正确的答案适应度越大);
  3. 轮盘赌选择样本;
  4. 交叉:以一定概率将两个个体交换一部分自变量;
  5. 变异:以一定概率将个体中某些值变异成其他值;
  6. 重复迭代2~5步,直到得到最优个体和最优解。

严肃一点就是

其中,选择算子轮盘赌选择样本的理论如下:

首先来一个饼图,不对是轮盘

三个颜色代表三个个体,饼的大小代表每个个体的适应度大小。

理论上来说,饼越大,被飞刀戳中的概率越大,这就是通过适应度选择较好个体的过程。

具体写成代码怎么做呢?这里用一个累积概率的方法,每个个体排排站,将它们的概率也拼在一起。

想象一条直线,两端代表0和1,中间每个个体凭实力(概率)占据一段,实力越强占据的线段越长。这样我们随机生成一个0~1之间的数,落在长线段区间的机会就比较大。把直线圈成一个圆形,就是轮盘赌的原理。

具体代码如下,做一个参考,建议先从主函数开始看:

六个题目分别为:

咦,怎么才5个?不知道为啥代码里面有6个,代码中对应的是第5个题目。

#!/usr/bin/env python 
# -*- coding: utf-8 -*- 
# @Time : 18-11-1 上午10:06 
# @Author : Xier
# @File : high_d.py 

# 高维单目标优化问题

import numpy as np
import copy
import math
import random
import matplotlib.pyplot as plt
from pylab import mpl
mpl.rcParams['font.sans-serif'] = 'NSimSun, Times New Roman'
mpl.rcParams['axes.unicode_minus']=False

def init(range_ab,val_num,popusize):
    init_gen = np.random.uniform(range_ab[0], range_ab[1], (popusize,val_num))
    return init_gen

def cal_fitness(dec_gen):
    f = np.zeros((dec_gen.shape[0],1))
    f1 = np.zeros((dec_gen.shape[0], 1))
    f2 = np.zeros((dec_gen.shape[0], 1))

    # 题目一
    # for i in range(dec_gen.shape[0]):
    #     for j in range(dec_gen.shape[1]):
    #         if j == 0:
    #             f[i] = dec_gen[i][j]*dec_gen[i][j]
    #         else:
    #             f[i] += dec_gen[i][j]*dec_gen[i][j]

    # 题目二
    # for i in range(dec_gen.shape[0]):
    #     for j in range(dec_gen.shape[1]):
    #         if j == 0:
    #             f[i] = math.ceil(dec_gen[i][j]+0.5)**2
    #         else:
    #             f[i] += math.ceil(dec_gen[i][j]+0.5)**2

    # # 题目三
    # for i in range(dec_gen.shape[0]):
    #     for j in range(dec_gen.shape[1]):
    #         for p in range(j+1):
    #             if p == 0:
    #                 x = dec_gen[i][p]
    #             else:
    #                 x += dec_gen[i][p]
    #         if j == 0:
    #             f[i] = x**2
    #         else:
    #             f[i] += x**2

    # 题目四
    # for i in range(dec_gen.shape[0]):
    #     for j in range(dec_gen.shape[1]):
    #         if j == 0:
    #             f1[i] = abs(dec_gen[i][j])
    #         else:
    #             f1[i] += abs(dec_gen[i][j])
    #     for j in range(dec_gen.shape[1]):
    #         if j == 0:
    #             f2[i] = abs(dec_gen[i][j])
    #         else:
    #             f2[i] *= abs(dec_gen[i][j])
    #
    #     f[i]=f1[i]+f2[i]

    # 题目五
    # for i in range(dec_gen.shape[0]):
    #     for j in range(dec_gen.shape[1]-1):
    #         if j == 0:
    #             f[i] = 100*(dec_gen[i][j+1]-dec_gen[i][j]**2)**2+(1-dec_gen[i][j])**2
    #         else:
    #             f[i] += 100*(dec_gen[i][j+1]-dec_gen[i][j]**2)**2+(1-dec_gen[i][j])**2

    # 题目六
    A = 1
    for i in range(dec_gen.shape[0]):
        for j in range(dec_gen.shape[1]):
            if j == 0:
                f[i] = dec_gen[i][j]**2-A*math.cos(2*math.pi*dec_gen[i][j])
            else:
                f[i] += dec_gen[i][j]**2-A*math.cos(2*math.pi*dec_gen[i][j])
        f[i] = dec_gen.shape[1]*A + f[i]
    return f


def mate_selection(y, gen, val_num):
    fitness = 1/(y+0.1)  # 找使函数值最小的个体,函数值越小适应度越大
    P = fitness.copy()
    Q = fitness.copy()
    sum_fitness = sum(fitness)
    for i in range(len(P)):
        P[i] = fitness[i]/sum_fitness   # 为每个个体赋一个选中的概率
        Q[i] = sum(P[:i+1])  # 记录至今为止的概率

    mate_np = np.zeros((1, gen.shape[1]))
    ran_select = np.random.random(len(fitness))#-1  # 随机生成0~1的数

    # 轮盘赌,理论上来说蛋糕面积越大,随机戳到的机率越大
    for r in range(len(ran_select)):
        for q in range(len(Q)):
            if ran_select[r] <= Q[q]:
                mate_np = np.vstack((mate_np, gen[q, :]))
                break

    return mate_np[1:,:]


def cross_able(gen,pc):
    # cross_np = gen[0].copy()
    # no_cross_np = gen[0].copy()
    ran_select = np.random.random(gen.shape[0])
    cross_np = gen[np.where(ran_select<pc),:]
    no_cross_np = gen[np.where(ran_select>=pc),:]

    return cross_np[0,:,:], no_cross_np[0,:,:]


def cross(gen):

    for i in range(gen.shape[0]//2):
        idex = random.randint(0, gen.shape[1]-1)
        gen[[i*2,i*2+1],idex:] = gen[[i*2+1,i*2],idex:]

    return gen


def mutation(gen,pm):
    for i in range(gen.shape[0]):
        ran_select = np.random.random(gen.shape[1])
        gen[i,np.where(ran_select<pm)] = np.random.uniform(range_ab[0], range_ab[1],(1, len(np.where(ran_select<pm))))

    return gen


if __name__ == '__main__':

    range_ab = [-5.12, 5.12]
    popusize = 100  # 种群个数
    val_num = 20  # 自变量个数
    epochs = 10000  # 迭代优化次数

    # 初始化浮点数种群
    gen = init(range_ab, val_num, popusize)
    Y = []  # 记录当代最优结果
    min_y = []
    record = []
    for i in range(epochs):
        # 解码后的样本
        # dec_gen = decoding(range_ab, bin_gen, bit_n,val_num)
        # 计算适应度
        y = cal_fitness(gen)
        y_min = np.min(y)
        Y.append(y_min)

        if i == 0:
            record_min = y_min
            record = [i + 1, gen[np.argmin(y)],record_min]  # 记录第几个epoch,最优个体,对应的最优结果
        if y_min < record_min:
            record_min = y_min
            # a = i+1
            record = [i+1, gen[np.argmin(y)],record_min]  # 更新最优结果
        min_y.append(record_min)
        # 选择样本,轮盘赌
        mate_np = mate_selection(y, gen, val_num)
        # 判断能否交配
        cross_np, no_cross_np = cross_able(mate_np, 0.6)
        # 交配
        crossed_gen = cross(cross_np)
        # 变异
        cross_gen = np.vstack((crossed_gen, no_cross_np))
        gen = mutation(cross_gen, 0.01)

    print('[迭代次数,最优个体,最优值] = ', record)
    plt.title('Rastrigin函数')
    # plt.ylim(-100, 3000)
    # yticks= np.linspace(0, 3000, 5)
    # plt.yticks(yticks)
    plt.plot(range(epochs), Y, label='当前代最优解')
    plt.plot(range(epochs), min_y, label='历史最优解')
    plt.legend()
    # 添加最优解在图中的位置
    plt.scatter([record[0]], [record[2]], color='red')
    # 添加座标点信息
    plt.text(record[0]-1000, record[2], (record[0], record[2]))
    plt.show()

运行20次的结果如下:

从表可以看出,对于某些复杂高维问题,遗传算法还是过于简单,效能有效。

遗传算法是比较初级的算法,后续广大学者从很多方面改进了遗传算法,如给适应度函数加一些约束,使最优样本的选择更加合理,函数更容易收敛。或者改变变异和交叉机制,使算法优化过程能从局部极小值点跳出来等等。

附上以上5个函数的收敛曲线如下:

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章