利用深度学习模型基于遗传算法(GA)寻求最优解


前言

深度学习模型的训练前面的文章已经记录过,深度学习-LSTM预测未来值,训练好后的模型如何使用呢?其中一个用途就是用来寻求最优解,优化算法的种类也有很多,本文选用比较经典的遗传算法。


一、各种优化算法的优缺点

遗传算法(GA):遗传算法具有良好的全局搜索能力,可以快速地将解空间中的全体解搜索出,而不会陷入局部最优解的快速下降陷阱;并且利用它的内在并行性,可以方便地进行分布式计算,加快求解速度。但是遗传算法的局部搜索能力较差,导致单纯的遗传算法比较费时,在进化后期搜索效率较低。在实际应用中,遗传算法容易产生早熟收敛的问题。采用何种选择方法既要使优良个体得以保留,又要维持群体的多样性,一直是遗传算法中较难解决的问题。

粒子群算法(PSO):具有相当快的逼近最优解的速度,可以有效的对系统的参数进行优化,PSO算法的优势在于求解一些连续函数的优化问题。最主要问题的是它容易产生早熟收敛(尤其是在处理复杂的多峰搜索问题中)、局部寻优能力较差等。PSO算法陷入局部最小,主要归咎于种群在搜索空间中多样性的丢失。

蚁群算法(ACO):在求解性能上,具有很强的鲁棒性(对基本蚁群算法模型稍加修改,便可以应用于其他问题)和搜索较好解的能力。蚁群算法收敛速度慢、易陷入局部最优。蚁群算法中初始信息素匮乏。蚁群算法一般需要较长的搜索时间,其复杂度可以反映这一点;而且该方法容易出现停滞现象,即搜索进行到一定程度后,所有个体发现的解完全一致,不能对解空间进一步进行搜索,不利于发现更好的解。

还有模拟退火算法、鱼群算法、鲸鱼优化算法等,可以参考这篇粒子群、遗传、蚁群、模拟退火和鲸群算法优缺点比较

二、深度学习模型+遗传算法

1.思路

整体的思路比较简单,就是将训练好的深度学习模型作为遗传算法的目标函数,关于如何训练存储深度学习模型,可以参考深度学习-LSTM预测未来值

2.代码部分

一、遗传算法的主要实现过程如下:
在这里插入图片描述
二、首先还是导入所需要的库,并定义一些人为设定的关键参数:

import numpy as np
import pandas as pd
from tensorflow.keras.models import load_model
import os


DNA_SIZE = 24   #编码的位数,越大越精确,但计算量越大
POP_SIZE = 200  #初始化种群数量
CROSSOVER_RATE = 0.8  #交叉率
MUTATION_RATE = 0.005  #变异率
N_GENERATIONS = 2   #迭代次数
X_BOUND = [10, 20]   #变量x的取值范围   阳极流速
Y_BOUND = [100, 500]  #变量y的取值范围   阴极流速

三、导入训练好的模型,我这里由于没有模型和代码不在同一个文件夹,修改了一下工作路径:

folder = "D:\Desktop\First"
os.chdir(folder)
global model
model = load_model('D:\Desktop\First\model.h5')

四、这里使用的方法中,没有编码部分,之所以要编码是因为要与十进制相对应,但实际上不编码依旧可以进行对应。例如,以十位二进制数来表示【-1,1】之间的数,十位二进制数对应十进制可以表示0-1023,那么可以把【-1,1】这个区间切割成1023份,一份表示2/1023,就可以精确表示【-1,1】之间的数了。举个例子,0000000011对应十进制为3,占3份,则它在【-1,1】中表示3*([1-(1)]/1023)=0.005865。即基因0000000011表示0.005865。参考:遗传算法详解 附python代码实现。依上述,则有如下代码:

'''解码'''
def translateDNA(pop):  # pop表示种群矩阵,一行表示一个二进制编码表示的DNA,矩阵的行数为种群数目
    x_pop = pop[:, 1::2]  # 奇数列表示X  [::2]表示隔一个取一个  [::-1]可视为翻转操作
    y_pop = pop[:, ::2]  # 偶数列表示y

    # pop:(POP_SIZE,DNA_SIZE)*(DNA_SIZE,1) --> (POP_SIZE,1)
    x = x_pop.dot(2 ** np.arange(DNA_SIZE)[::-1]) / float(2 ** DNA_SIZE - 1) * (X_BOUND[1] - X_BOUND[0]) + X_BOUND[0]
    y = y_pop.dot(2 ** np.arange(DNA_SIZE)[::-1]) / float(2 ** DNA_SIZE - 1) * (Y_BOUND[1] - Y_BOUND[0]) + Y_BOUND[0]
    return x, y

五、交叉变异部分就是模仿生物学中的父代母代基因对下一代的遗传,以及可能出现的变异,可以参考:遗传算法关于多目标优化python(详解)
在这里插入图片描述
代码:

'''交叉变异'''
def crossover_and_mutation(pop, CROSSOVER_RATE=0.8):
    new_pop = []
    for father in pop:  # 遍历种群中的每一个个体,将该个体作为父亲
        child = father  # 孩子先得到父亲的全部基因(这里我把一串二进制串的那些0,1称为基因)
        if np.random.rand() < CROSSOVER_RATE:  # 产生子代时不是必然发生交叉,而是以一定的概率发生交叉
            mother = pop[np.random.randint(POP_SIZE)]  # 再种群中选择另一个个体,并将该个体作为母亲
            cross_points = np.random.randint(low=0, high=DNA_SIZE * 2)  # 随机产生交叉的点
            child[cross_points:] = mother[cross_points:]  # 孩子得到位于交叉点后的母亲的基因
        mutation(child)  # 每个后代有一定的机率发生变异
        new_pop.append(child)

    return new_pop

def mutation(child, MUTATION_RATE=0.003):
    if np.random.rand() < MUTATION_RATE:  # 以MUTATION_RATE的概率进行变异
        mutate_point = np.random.randint(0, DNA_SIZE*2)  # 随机产生一个实数,代表要变异基因的位置
        child[mutate_point] = child[mutate_point] ^ 1  # 将变异点的二进制为反转

六、 **最重要的部分:将适应度函数替换成深度学习模型。**小tips:因为根据适者生存规则在求最小值问题上,函数值越小的可能解对应的适应度应该越大,同时适应度也不能为负值,先将适应度减去最大预测值,将适应度可能取值区间压缩为[ n p . m i n ( p r e d ) − n p . m a x ( p r e d ) , 0 ] [np.min(pred)-np.max(pred), 0][np.min(pred)−np.max(pred),0],然后添加个负号将适应度变为正数,同理为了不出现0,最后在加上一个很小的正数。

'''适应度函数'''
def F(x, y):
    num = x.shape[0]
    print(num)
    X2 = []
    for i in range(num):
        X = [x[i], y[i], 2200, 1000, 1000, 1000, 1000]
        X2.append(X)
    Xtest = np.array(X2)
    X_test = Xtest.reshape(-1, 7)
    Y_pre = model.predict(X_test)

    return Y_pre[:, 0]

'''计算适应度'''
def get_fitness(pop):
    x, y = translateDNA(pop)
    pred = F(x, y)
    return (pred - np.min(pred)) + 1e-3  #计算最大值 减去最小的适应度是为了防止适应度出现负数,通过这一步fitness的范围为[0, np.max(pred)-np.min(pred)],最后在加上一个很小的数防止出现为0的适应度
    #return -(pred - np.max(pred)) + 1e-3 #计算最小值
'''

根据适应度进行选择:

'''根据适应度进行选择'''
def select(pop, fitness):  # nature selection wrt pop's fitness
    idx = np.random.choice(np.arange(POP_SIZE), size=POP_SIZE, replace=True, p=(fitness) / (fitness.sum()))
    return pop[idx]

输出结果:

'''输出结果'''
def print_info(pop):
    fitness = get_fitness(pop)
    max_fitness_index = np.argmax(fitness)
    print("max_fitness:", fitness[max_fitness_index])
    x, y = translateDNA(pop)
    print("最优的基因型:", pop[max_fitness_index])
    print("(x, y):", (x[max_fitness_index], y[max_fitness_index]))

    X = [x[max_fitness_index], y[max_fitness_index], 2200.0, 1000.0, 1000.0, 1000.0, 1000.0]
    X = np.array(X)
    XMAX = X.reshape(-1, 7)
    Y_PRE = model.predict(XMAX)
    Y_MAX = Y_PRE[:, 0]
    print("最优化结果为:", Y_MAX)

三、完整代码

'''
autor: wsw
Time: 2021.4.24
'''
from tensorflow_core import keras
import numpy as np
import pandas as pd
from tensorflow.keras.models import load_model
import os


DNA_SIZE = 24   #编码的位数,越大越精确,但计算量越大
POP_SIZE = 200  #初始化种群数量
CROSSOVER_RATE = 0.8  #交叉率
MUTATION_RATE = 0.005  #变异率
N_GENERATIONS = 2   #迭代次数
X_BOUND = [10, 20]   #变量x的取值范围   阳极流速
Y_BOUND = [100, 500]  #变量y的取值范围   阴极流速


folder = "D:\Desktop\First"
os.chdir(folder)
global model
model = load_model('D:\Desktop\First\model.h5')

'''适应度函数'''
def F(x, y):
    num = x.shape[0]
    print(num)
    X2 = []
    for i in range(num):
        X = [x[i], y[i], 2200, 1000, 1000, 1000, 1000]
        X2.append(X)
    Xtest = np.array(X2)
    X_test = Xtest.reshape(-1, 7)
    Y_pre = model.predict(X_test)

    return Y_pre[:, 0]

'''计算适应度'''
def get_fitness(pop):
    x, y = translateDNA(pop)
    pred = F(x, y)
    return (pred - np.min(pred)) + 1e-3  #计算最大值 减去最小的适应度是为了防止适应度出现负数,通过这一步fitness的范围为[0, np.max(pred)-np.min(pred)],最后在加上一个很小的数防止出现为0的适应度
    #return -(pred - np.max(pred)) + 1e-3 #计算最小值
'''
因为根据适者生存规则在求最小值问题上,函数值越小的可能解对应的适应度应该越大,同时适应度也不能为负值,先将适应度减去最大预测值,
将适应度可能取值区间压缩为[ n p . m i n ( p r e d ) − n p . m a x ( p r e d ) , 0 ] [np.min(pred)-np.max(pred), 0][np.min(pred)−np.max(pred),0],
然后添加个负号将适应度变为正数,同理为了不出现0,最后在加上一个很小的正数。
'''

'''解码'''
def translateDNA(pop):  # pop表示种群矩阵,一行表示一个二进制编码表示的DNA,矩阵的行数为种群数目
    x_pop = pop[:, 1::2]  # 奇数列表示X  [::2]表示隔一个取一个  [::-1]可视为翻转操作
    y_pop = pop[:, ::2]  # 偶数列表示y

    # pop:(POP_SIZE,DNA_SIZE)*(DNA_SIZE,1) --> (POP_SIZE,1)
    x = x_pop.dot(2 ** np.arange(DNA_SIZE)[::-1]) / float(2 ** DNA_SIZE - 1) * (X_BOUND[1] - X_BOUND[0]) + X_BOUND[0]
    y = y_pop.dot(2 ** np.arange(DNA_SIZE)[::-1]) / float(2 ** DNA_SIZE - 1) * (Y_BOUND[1] - Y_BOUND[0]) + Y_BOUND[0]
    return x, y

'''交叉变异'''
def crossover_and_mutation(pop, CROSSOVER_RATE=0.8):
    new_pop = []
    for father in pop:  # 遍历种群中的每一个个体,将该个体作为父亲
        child = father  # 孩子先得到父亲的全部基因(这里我把一串二进制串的那些0,1称为基因)
        if np.random.rand() < CROSSOVER_RATE:  # 产生子代时不是必然发生交叉,而是以一定的概率发生交叉
            mother = pop[np.random.randint(POP_SIZE)]  # 再种群中选择另一个个体,并将该个体作为母亲
            cross_points = np.random.randint(low=0, high=DNA_SIZE * 2)  # 随机产生交叉的点
            child[cross_points:] = mother[cross_points:]  # 孩子得到位于交叉点后的母亲的基因
        mutation(child)  # 每个后代有一定的机率发生变异
        new_pop.append(child)

    return new_pop

def mutation(child, MUTATION_RATE=0.003):
    if np.random.rand() < MUTATION_RATE:  # 以MUTATION_RATE的概率进行变异
        mutate_point = np.random.randint(0, DNA_SIZE*2)  # 随机产生一个实数,代表要变异基因的位置
        child[mutate_point] = child[mutate_point] ^ 1  # 将变异点的二进制为反转

'''根据适应度进行选择'''
def select(pop, fitness):  # nature selection wrt pop's fitness
    idx = np.random.choice(np.arange(POP_SIZE), size=POP_SIZE, replace=True, p=(fitness) / (fitness.sum()))
    return pop[idx]

'''输出结果'''
def print_info(pop):
    fitness = get_fitness(pop)
    max_fitness_index = np.argmax(fitness)
    print("max_fitness:", fitness[max_fitness_index])
    x, y = translateDNA(pop)
    print("最优的基因型:", pop[max_fitness_index])
    print("(x, y):", (x[max_fitness_index], y[max_fitness_index]))

    X = [x[max_fitness_index], y[max_fitness_index], 2200.0, 1000.0, 1000.0, 1000.0, 1000.0]
    X = np.array(X)
    XMAX = X.reshape(-1, 7)
    Y_PRE = model.predict(XMAX)
    Y_MAX = Y_PRE[:, 0]
    print("最优化结果为:", Y_MAX)

if __name__ == "__main__":
    pop = np.random.randint(2, size=(POP_SIZE, DNA_SIZE * 2))  # matrix (POP_SIZE, DNA_SIZE)
    for _ in range(N_GENERATIONS):  # 迭代N代
        x, y = translateDNA(pop)   #解码
        pop = np.array(crossover_and_mutation(pop, CROSSOVER_RATE))   #交叉变异
        # F_values = F(translateDNA(pop)[0], translateDNA(pop)[1])#x, y --> Z matrix
        fitness = get_fitness(pop)   #计算适应度
        pop = select(pop, fitness)  # 选择生成新的种群


    print_info(pop)

四、总结

用遗传算法对训练好的深度学习的模型进行最优化求解,对工程上的最佳参数选择有很重要的意义,本文的核心部分是将遗传算法的目标函数替换成了深度学习模型。关于模型如何存储导入,tensorflow2.0版本以上的可以参考:tensorflow2.0。如果是tensorflow2.1版本遇到无法用model.save存储的问题,可能是因为版本文件的错误,可以参考:keras存储报错。关于如何训练模型可以参考:深度学习-LSTM预测未来值

微信扫码订阅
UP更新不错过~
关注
  • 6
    点赞
  • 70
    收藏
  • 打赏
    打赏
  • 22
    评论
©️2022 CSDN 皮肤主题:数字20 设计师:CSDN官方博客 返回首页
评论 22

打赏作者

w^s^w

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值