参考链接:http://www.alimirjalili.com/SMA.html
https://tinyurl.com/Slime-mould-algorithm.
这个优化算法是2020年提出来的,还算比较新的算法,上面两个链接都有源码。
本着能用的代码就是好代码的原则,我把人家的代码搬了过来组合了一下,测试后可以使用...
直接上代码
粘液霉菌算法(SMA)
这一段是定义优化算法一般的功能
from numpy import where, clip, logical_and, ones, array, ceil
from numpy.random import uniform, choice
from copy import deepcopy
from numpy import abs, zeros, log10, where, arctanh, tanh
class Root:
""" This is root of all Algorithms """
ID_MIN_PROB = 0 # min problem
ID_MAX_PROB = -1 # max problem
ID_POS = 0 # Position
ID_FIT = 1 # Fitness
EPSILON = 10E-10
def __init__(self, obj_func=None, lb=None, ub=None, problem_size=50, verbose=True):
"""
Parameters
----------
obj_func : function
lb : list
ub : list
problem_size : int, optional
batch_size: int, optional
verbose : bool, optional
"""
self.obj_func = obj_func
if (lb is None) or (ub is None):
if problem_size is None:
print("Problem size must be an int number")
exit(0)
elif problem_size <= 0:
print("Problem size must > 0")
exit(0)
else:
self.problem_size = int(ceil(problem_size))
self.lb = -1 * ones(problem_size)
self.ub = 1 * ones(problem_size)
else:
if isinstance(lb, list) and isinstance(ub, list) and not (problem_size is None):
if (len(lb) == len(ub)) and (problem_size > 0):
if len(lb) == 1:
self.problem_size = problem_size
self.lb = lb[0] * ones(problem_size)
self.ub = ub[0] * ones(problem_size)
else:
self.problem_size = len(lb)
self.lb = array(lb)
self.ub = array(ub)
else:
print("Lower bound and Upper bound need to be same length. Problem size must > 0")
exit(0)
else:
print("Lower bound and Upper bound need to be a list. Problem size is an int number")
exit(0)
self.verbose = verbose
self.epoch, self.pop_size = None, None
self.solution, self.loss_train = None, []
def create_solution(self, minmax=0):
""" Return the position position with 2 element: position of position and fitness of position
Parameters
----------
minmax
0 - minimum problem, else - maximum problem
"""
position = uniform(self.lb, self.ub)
fitness = self.get_fitness_position(position=position, minmax=minmax)
return [position, fitness]
def get_fitness_position(self, position=None, minmax=0):
""" Assumption that objective function always return the original value
:param position: 1-D numpy array
:param minmax: 0- min problem, 1 - max problem
:return:
"""
return self.obj_func(position) if minmax == 0 else 1.0 / (self.obj_func(position) + self.EPSILON)
def get_sorted_pop_and_global_best_solution(self, pop=None, id_fit=None, id_best=None):
""" Sort population and return the sorted population and the best position """
sorted_pop = sorted(pop, key=lambda temp: temp[id_fit])
return sorted_pop, deepcopy(sorted_pop[id_best])
def amend_position(self, position=None):
return clip(position, self.lb, self.ub)
def amend_position_random(self, position=None):
return where(logical_and(self.lb <= position, position <= self.ub), position, uniform(self.lb, self.ub))
def update_sorted_population_and_global_best_solution(self, pop=None, id_best=None, g_best=None):
""" Sort the population and update the current best position. Return the sorted population and the new current best position """
sorted_pop = sorted(pop, key=lambda temp: temp[self.ID_FIT])
current_best = sorted_pop[id_best]
g_best = deepcopy(current_best) if current_best[self.ID_FIT] < g_best[self.ID_FIT] else deepcopy(g_best)
return sorted_pop, g_best
下面是两种不同的SMA算法的类
class BaseSMA(Root):
"""
Modified version of: Slime Mould Algorithm (SMA)
(Slime Mould Algorithm: A New Method for Stochastic Optimization)
Notes:
+ Selected 2 unique and random solution to create new solution (not to create variable) --> remove third loop in original version
+ Check bound and update fitness after each individual move instead of after the whole population move in the original version
"""
ID_WEI = 2
def __init__(self, obj_func=None, lb=None, ub=None, problem_size=50, verbose=True, epoch=750, pop_size=100, z=0.03):
Root.__init__(self, obj_func, lb, ub, problem_size, verbose)
self.epoch = epoch
self.pop_size = pop_size
self.z = z
def create_solution(self, minmax=0):
pos = uniform(self.lb, self.ub)
fit = self.get_fitness_position(pos)
weight = zeros(self.problem_size)
return [pos, fit, weight]
def train(self):
pop = [self.create_solution() for _ in range(self.pop_size)]
pop, g_best = self.get_sorted_pop_and_global_best_solution(pop, self.ID_FIT, self.ID_MIN_PROB) # Eq.(2.6)
for epoch in range(self.epoch):
s = pop[0][self.ID_FIT] - pop[-1][self.ID_FIT] + self.EPSILON # plus eps to avoid denominator zero
# calculate the fitness weight of each slime mold
for i in range(0, self.pop_size):
# Eq.(2.5)
if i <= int(self.pop_size / 2):
pop[i][self.ID_WEI] = 1 + uniform(0, 1, self.problem_size) * log10((pop[0][self.ID_FIT] - pop[i][self.ID_FIT]) / s + 1)
else:
pop[i][self.ID_WEI] = 1 - uniform(0, 1, self.problem_size) * log10((pop[0][self.ID_FIT] - pop[i][self.ID_FIT]) / s + 1)
a = arctanh(-((epoch + 1) / self.epoch) + 1) # Eq.(2.4)
b = 1 - (epoch + 1) / self.epoch
# Update the Position of search agents
for i in range(0, self.pop_size):
if uniform() < self.z: # Eq.(2.7)
pos_new = uniform(self.lb, self.ub)
else:
p = tanh(abs(pop[i][self.ID_FIT] - g_best[self.ID_FIT])) # Eq.(2.2)
vb = uniform(-a, a, self.problem_size) # Eq.(2.3)
vc = uniform(-b, b, self.problem_size)
# two positions randomly selected from population, apply for the whole problem size instead of 1 variable
id_a, id_b = choice(list(set(range(0, self.pop_size)) - {i}), 2, replace=False)
pos_1 = g_best[self.ID_POS] + vb * (pop[i][self.ID_WEI] * pop[id_a][self.ID_POS] - pop[id_b][self.ID_POS])
pos_2 = vc * pop[i][self.ID_POS]
pos_new = where(uniform(0, 1, self.problem_size) < p, pos_1, pos_2)
# Check bound and re-calculate fitness after each individual move
pos_new = self.amend_position(pos_new)
fit_new = self.get_fitness_position(pos_new)
pop[i][self.ID_POS] = pos_new
pop[i][self.ID_FIT] = fit_new
# Sorted population and update the global best
pop, g_best = self.update_sorted_population_and_global_best_solution(pop, self.ID_MIN_PROB, g_best)
self.loss_train.append(g_best[self.ID_FIT])
if self.verbose:
print("> Epoch: {}, Best fit: {}".format(epoch + 1, g_best[self.ID_FIT]))
self.solution = g_best
return g_best[self.ID_POS], g_best[self.ID_FIT], self.loss_train
class OriginalSMA(Root):
"""
The original version of: Slime Mould Algorithm (SMA)
(Slime Mould Algorithm: A New Method for Stochastic Optimization)
Link:
https://doi.org/10.1016/j.future.2020.03.055
"""
ID_WEI = 2
def __init__(self, obj_func=None, lb=None, ub=None, problem_size=50, verbose=True, epoch=750, pop_size=100, z=0.03):
Root.__init__(self, obj_func, lb, ub, problem_size, verbose)
self.epoch = epoch
self.pop_size = pop_size
self.z = z
def create_solution(self, minmax=0):
pos = uniform(self.lb, self.ub)
fit = self.get_fitness_position(pos)
weight = zeros(self.problem_size)
return [pos, fit, weight]
def train(self):
pop = [self.create_solution() for _ in range(self.pop_size)]
pop, g_best = self.get_sorted_pop_and_global_best_solution(pop, self.ID_FIT, self.ID_MIN_PROB) # Eq.(2.6)
for epoch in range(self.epoch):
s = pop[0][self.ID_FIT] - pop[-1][self.ID_FIT] + self.EPSILON # plus eps to avoid denominator zero
# calculate the fitness weight of each slime mold
for i in range(0, self.pop_size):
# Eq.(2.5)
if i <= int(self.pop_size / 2):
pop[i][self.ID_WEI] = 1 + uniform(0, 1, self.problem_size) * log10((pop[0][self.ID_FIT] - pop[i][self.ID_FIT]) / s + 1)
else:
pop[i][self.ID_WEI] = 1 - uniform(0, 1, self.problem_size) * log10((pop[0][self.ID_FIT] - pop[i][self.ID_FIT]) / s + 1)
a = arctanh(-((epoch + 1) / self.epoch) + 1) # Eq.(2.4)
b = 1 - (epoch + 1) / self.epoch
# Update the Position of search agents
for i in range(0, self.pop_size):
if uniform() < self.z: # Eq.(2.7)
pop[i][self.ID_POS] = uniform(self.lb, self.ub)
else:
p = tanh(abs(pop[i][self.ID_FIT] - g_best[self.ID_FIT])) # Eq.(2.2)
vb = uniform(-a, a, self.problem_size) # Eq.(2.3)
vc = uniform(-b, b, self.problem_size)
for j in range(0, self.problem_size):
# two positions randomly selected from population
id_a, id_b = choice(list(set(range(0, self.pop_size)) - {i}), 2, replace=False)
if uniform() < p: # Eq.(2.1)
pop[i][self.ID_POS][j] = g_best[self.ID_POS][j] + vb[j] * (
pop[i][self.ID_WEI][j] * pop[id_a][self.ID_POS][j] - pop[id_b][self.ID_POS][j])
else:
pop[i][self.ID_POS][j] = vc[j] * pop[i][self.ID_POS][j]
# Check bound and re-calculate fitness after the whole population move
for i in range(0, self.pop_size):
pos_new = self.amend_position(pop[i][self.ID_POS])
fit_new = self.get_fitness_position(pos_new)
pop[i][self.ID_POS] = pos_new
pop[i][self.ID_FIT] = fit_new
# Sorted population and update the global best
pop, g_best = self.update_sorted_population_and_global_best_solution(pop, self.ID_MIN_PROB, g_best)
self.loss_train.append(g_best[self.ID_FIT])
if self.verbose:
print("> Epoch: {}, Best fit: {}".format(epoch + 1, g_best[self.ID_FIT]))
self.solution = g_best
return g_best[self.ID_POS], g_best[self.ID_FIT], self.loss_train
定义问题参数,可以定义任意自己想优化的问题,寻找最小值
#from SMA import BaseSMA, OriginalSMA
#from numpy import sum, pi, exp, sqrt, cos
import numpy as np
## You can create whatever function you want here
def func_sum(solution):
return np.sum(solution ** 2)
def func_ackley(solution):
a, b, c = 20, 0.2, 2 * np.pi
d = len(solution)
sum_1 = -a * np.exp(-b * np.sqrt(np.sum(solution ** 2) / d))
sum_2 = np.exp(np.sum(np.cos(c * solution)) / d)
return sum_1 - sum_2 + a + np.exp(1)
## You can create different bound for each dimension like this
# lb = [-15, -10, -3, -15, -10, -3, -15, -10, -3, -15, -10, -3, -15, -10, -3, -100, -40, -50]
# ub = [15, 10, 3, 15, 10, 3, 15, 10, 3, 15, 10, 3, 15, 10, 3, 20, 200, 1000]
# problem_size = 18
## if you choose this way, the problem_size need to be same length as lb and ub
## Or bound is the same for all dimension like this
lb = [-100]
ub = [100]
problem_size = 100
## if you choose this way, the problem_size can be anything you want
## Setting parameters
obj_func = func_ackley
verbose = True
epoch = 100
pop_size = 50
开始使用两种SMA进行测试
第一种基础的SMA
md1 = BaseSMA(obj_func, lb, ub, problem_size, verbose, epoch, pop_size)
best_pos1, best_fit1, list_loss1 = md1.train()
# return : the global best solution, the fitness of global best solution and the loss of training process in each epoch/iteration
print(md1.solution[0])
print(md1.solution[1])
#print(md1.loss_train)
verbose开着的话会显示每轮迭代的损失的大小,md1.solution[0]打印最优的X,md1.solution[1]打印最优的y值。
md1.loss_train可以返回每轮迭代的损失y的变化,是一个数组,画图什么很方便。
第二种SMA
md2 = OriginalSMA(obj_func, lb, ub, problem_size, verbose, epoch, pop_size)
best_pos2, best_fit2, list_loss2 = md2.train()
# return : the global best solution, the fitness of global best solution and the loss of training process in each epoch/iteration
print(best_pos2)
print(best_fit2)
#print(list_loss2)
返回值都差不多,但是这种SMA 的运行时间有点略微的长,想来是更加复杂点。
如有兴趣研究原理的同学可以进下面的链接,是这篇算法的论文的链接。