前言
深度学习模型的训练前面的文章已经记录过,深度学习-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预测未来值。