遺傳算法自我複習之高維單目標優化問題

面試過程是一個很好的查漏補缺的機會,要好好梳理,覆盤面試過程。

比如最近面試就發現,我已經把一年前課堂上學的遺傳算法和優化方法,全數還給老師了,渣都不剩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個函數的收斂曲線如下:

 

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