#!/usr/bin/env python
# Created by "Thieu" at 12:48, 18/03/2020 ----------%
# Email: nguyenthieu2102@gmail.com %
# Github: https://github.com/thieu1995 %
# --------------------------------------------------%
import numpy as np
from copy import deepcopy
from mealpy.optimizer import Optimizer
[docs]class BaseSBO(Optimizer):
"""
My changed version of: Satin Bowerbird Optimizer (SBO)
Links:
1. https://doi.org/10.1016/j.engappai.2017.01.006
Notes
~~~~~
The original version is not good enough, I remove all third loop for faster training, remove equation (1, 2) in the paper,
calculate probability by roulette-wheel. My version can also handle negative value
Hyper-parameters should fine tuned in approximate range to get faster convergen toward the global optimum:
+ alpha (float): [0.5, 0.99], the greatest step size
+ pm (float): [0.01, 0.2], mutation probability
+ psw (float): [0.01, 0.1], proportion of space width (z in the paper)
Examples
~~~~~~~~
>>> import numpy as np
>>> from mealpy.bio_based.SBO import BaseSBO
>>>
>>> def fitness_function(solution):
>>> return np.sum(solution**2)
>>>
>>> problem_dict1 = {
>>> "fit_func": fitness_function,
>>> "lb": [-10, -15, -4, -2, -8],
>>> "ub": [10, 15, 12, 8, 20],
>>> "minmax": "min",
>>> "verbose": True,
>>> }
>>>
>>> epoch = 1000
>>> pop_size = 50
>>> alpha = 0.9
>>> pm=0.05
>>> psw = 0.02
>>> model = BaseSBO(problem_dict1, epoch, pop_size, alpha, pm, psw)
>>> best_position, best_fitness = model.solve()
>>> print(f"Solution: {best_position}, Fitness: {best_fitness}")
"""
def __init__(self, problem, epoch=10000, pop_size=100, alpha=0.94, pm=0.05, psw=0.02, **kwargs):
"""
Args:
problem (dict): The problem dictionary
epoch (int): maximum number of iterations, default = 10000
pop_size (int): number of population size, default = 100
alpha (float): the greatest step size, default=0.94
pm (float): mutation probability, default=0.05
psw (float): proportion of space width (z in the paper), default=0.02
"""
super().__init__(problem, kwargs)
self.nfe_per_epoch = pop_size
self.sort_flag = False
self.epoch = epoch
self.pop_size = pop_size
self.alpha = alpha
self.p_m = pm
self.psw = psw
# (percent of the difference between the upper and lower limit (Eq. 7))
self.sigma = self.psw * (self.problem.ub - self.problem.lb)
[docs] def evolve(self, epoch):
"""
The main operations (equations) of algorithm. Inherit from Optimizer class
Args:
epoch (int): The current iteration
"""
## Calculate the probability of bowers using my equation
fit_list = np.array([item[self.ID_TAR][self.ID_FIT] for item in self.pop])
pop_new = []
for i in range(0, self.pop_size):
### Select a bower using roulette wheel
idx = self.get_index_roulette_wheel_selection(fit_list)
### Calculating Step Size
lamda = self.alpha * np.random.uniform()
pos_new = self.pop[i][self.ID_POS] + lamda * ((self.pop[idx][self.ID_POS] + self.g_best[self.ID_POS]) / 2 - self.pop[i][self.ID_POS])
### Mutation
temp = self.pop[i][self.ID_POS] + np.random.normal(0, 1, self.problem.n_dims) * self.sigma
pos_new = np.where(np.random.uniform(0, 1, self.problem.n_dims) < self.p_m, temp, pos_new)
### In-bound position
pos_new = self.amend_position(pos_new)
pop_new.append([pos_new, None])
self.pop = self.update_fitness_population(pop_new)
[docs]class OriginalSBO(BaseSBO):
"""
The original version of: Satin Bowerbird Optimizer (SBO)
Links:
1. https://doi.org/10.1016/j.engappai.2017.01.006
2. https://www.mathworks.com/matlabcentral/fileexchange/62009-satin-bowerbird-optimizer-sbo-2017
Hyper-parameters should fine tuned in approximate range to get faster convergen toward the global optimum:
+ alpha (float): [0.5, 0.99], the greatest step size
+ pm (float): [0.01, 0.2], mutation probability
+ psw (float): [0.01, 0.1], proportion of space width (z in the paper)
Examples
~~~~~~~~
>>> import numpy as np
>>> from mealpy.bio_based.SBO import OriginalSBO
>>>
>>> def fitness_function(solution):
>>> return np.sum(solution**2)
>>>
>>> problem_dict1 = {
>>> "fit_func": fitness_function,
>>> "lb": [-10, -15, -4, -2, -8],
>>> "ub": [10, 15, 12, 8, 20],
>>> "minmax": "min",
>>> "verbose": True,
>>> }
>>>
>>> epoch = 1000
>>> pop_size = 50
>>> alpha = 0.9
>>> pm=0.05
>>> psw = 0.02
>>> model = OriginalSBO(problem_dict1, epoch, pop_size, alpha, pm, psw)
>>> best_position, best_fitness = model.solve()
>>> print(f"Solution: {best_position}, Fitness: {best_fitness}")
References
~~~~~~~~~~
[1] Moosavi, S.H.S. and Bardsiri, V.K., 2017. Satin bowerbird optimizer: A new optimization algorithm
to optimize ANFIS for software development effort estimation. Engineering Applications of Artificial Intelligence, 60, pp.1-15.
"""
def __init__(self, problem, epoch=10000, pop_size=100, alpha=0.94, pm=0.05, psw=0.02, **kwargs):
"""
Args:
problem (dict): The problem dictionary
epoch (int): maximum number of iterations, default = 10000
pop_size (int): number of population size, default = 100
alpha (float): the greatest step size, default=0.94
pm (float): mutation probability, default=0.05
psw (float): proportion of space width (z in the paper), default=0.02
"""
super().__init__(problem, epoch, pop_size, alpha, pm, psw, **kwargs)
def _roulette_wheel_selection__(self, fitness_list=None) -> int:
"""
Roulette Wheel Selection in the original version, this version can't handle the negative fitness values
Args:
fitness_list (list): Fitness of population
Returns:
f (int): The index of selected solution
"""
r = np.random.uniform()
c = np.cumsum(fitness_list)
f = np.where(r < c)[0][0]
return f
[docs] def evolve(self, epoch):
"""
The main operations (equations) of algorithm. Inherit from Optimizer class
Args:
epoch (int): The current iteration
"""
## Calculate the probability of bowers using Eqs. (1) and (2)
fx_list = np.array([agent[self.ID_TAR][self.ID_FIT] for agent in self.pop])
fit_list = deepcopy(fx_list)
for i in range(0, self.pop_size):
if fx_list[i] < 0:
fit_list[i] = 1.0 + np.abs(fx_list[i])
else:
fit_list[i] = 1.0 / (1.0 + np.abs(fx_list[i]))
fit_sum = np.sum(fit_list)
## Calculating the probability of each bower
prob_list = fit_list / fit_sum
pop_new = []
for i in range(0, self.pop_size):
pos_new = deepcopy(self.pop[i][self.ID_POS])
for j in range(0, self.problem.n_dims):
### Select a bower using roulette wheel
idx = self._roulette_wheel_selection__(prob_list)
### Calculating Step Size
lamda = self.alpha / (1 + prob_list[idx])
pos_new[j] = self.pop[i][self.ID_POS][j] + lamda * ((self.pop[idx][self.ID_POS][j] +
self.g_best[self.ID_POS][j]) / 2 - self.pop[i][self.ID_POS][j])
### Mutation
if np.random.uniform() < self.p_m:
pos_new[j] = self.pop[i][self.ID_POS][j] + np.random.normal(0, 1) * self.sigma[j]
pos_new = self.amend_position(pos_new)
pop_new.append([pos_new, None])
self.pop = self.update_fitness_population(pop_new)