|
1
|
|
|
# encoding=utf8 |
|
2
|
|
|
import logging |
|
3
|
|
|
from numpy import where, apply_along_axis, zeros, append, ndarray, delete, arange, argmin, absolute, int32 |
|
4
|
|
|
from NiaPy.algorithms.algorithm import Algorithm |
|
5
|
|
|
from NiaPy.util import limit_repair |
|
6
|
|
|
|
|
7
|
|
|
__all__ = ['ForestOptimizationAlgorithm'] |
|
8
|
|
|
|
|
9
|
|
|
logging.basicConfig() |
|
10
|
|
|
logger = logging.getLogger('NiaPy.algorithms.basic') |
|
11
|
|
|
logger.setLevel('INFO') |
|
12
|
|
|
|
|
13
|
|
|
class ForestOptimizationAlgorithm(Algorithm): |
|
14
|
|
|
r"""Implementation of Forest Optimization Algorithm. |
|
15
|
|
|
|
|
16
|
|
|
Algorithm: |
|
17
|
|
|
Forest Optimization Algorithm |
|
18
|
|
|
|
|
19
|
|
|
Date: |
|
20
|
|
|
2019 |
|
21
|
|
|
|
|
22
|
|
|
Authors: |
|
23
|
|
|
Luka Pečnik |
|
24
|
|
|
|
|
25
|
|
|
License: |
|
26
|
|
|
MIT |
|
27
|
|
|
|
|
28
|
|
|
Reference paper: |
|
29
|
|
|
Manizheh Ghaemi, Mohammad-Reza Feizi-Derakhshi, Forest Optimization Algorithm, Expert Systems with Applications, Volume 41, Issue 15, 2014, Pages 6676-6687, ISSN 0957-4174, https://doi.org/10.1016/j.eswa.2014.05.009. |
|
30
|
|
|
|
|
31
|
|
|
References URL: |
|
32
|
|
|
Implementation is based on the following MATLAB code: https://github.com/cominsys/FOA |
|
33
|
|
|
|
|
34
|
|
|
Attributes: |
|
35
|
|
|
Name (List[str]): List of strings representing algorithm name. |
|
36
|
|
|
lt (int): Life time of trees parameter. |
|
37
|
|
|
al (int): Area limit parameter. |
|
38
|
|
|
lsc (int): Local seeding changes parameter. |
|
39
|
|
|
gsc (int): Global seeding changes parameter. |
|
40
|
|
|
tr (float): Transfer rate parameter. |
|
41
|
|
|
|
|
42
|
|
|
See Also: |
|
43
|
|
|
* :class:`NiaPy.algorithms.Algorithm` |
|
44
|
|
|
""" |
|
45
|
|
|
Name = ['ForestOptimizationAlgorithm', 'FOA'] |
|
46
|
|
|
|
|
47
|
|
|
@staticmethod |
|
48
|
|
|
def algorithmInfo(): |
|
49
|
|
|
r"""Get algorithms information. |
|
50
|
|
|
|
|
51
|
|
|
Returns: |
|
52
|
|
|
str: Algorithm information. |
|
53
|
|
|
|
|
54
|
|
|
See Also: |
|
55
|
|
|
* :func:`NiaPy.algorithms.Algorithm.algorithmInfo` |
|
56
|
|
|
""" |
|
57
|
|
|
return r"""Manizheh Ghaemi, Mohammad-Reza Feizi-Derakhshi, Forest Optimization Algorithm, Expert Systems with Applications, Volume 41, Issue 15, 2014, Pages 6676-6687, ISSN 0957-4174, https://doi.org/10.1016/j.eswa.2014.05.009.""" |
|
58
|
|
|
|
|
59
|
|
View Code Duplication |
@staticmethod |
|
|
|
|
|
|
60
|
|
|
def typeParameters(): |
|
61
|
|
|
r"""Get dictionary with functions for checking values of parameters. |
|
62
|
|
|
|
|
63
|
|
|
Returns: |
|
64
|
|
|
Dict[str, Callable]: |
|
65
|
|
|
* lt (Callable[[int], bool]): Checks if life time parameter has a proper value. |
|
66
|
|
|
* al (Callable[[int], bool]): Checks if area limit parameter has a proper value. |
|
67
|
|
|
* lsc (Callable[[int], bool]): Checks if local seeding changes parameter has a proper value. |
|
68
|
|
|
* gsc (Callable[[int], bool]): Checks if global seeding changes parameter has a proper value. |
|
69
|
|
|
* tr (Callable[[float], bool]): Checks if transfer rate parameter has a proper value. |
|
70
|
|
|
|
|
71
|
|
|
See Also: |
|
72
|
|
|
* :func:`NiaPy.algorithms.algorithm.Algorithm.typeParameters` |
|
73
|
|
|
""" |
|
74
|
|
|
d = Algorithm.typeParameters() |
|
75
|
|
|
d.update({ |
|
76
|
|
|
'lt': lambda x: isinstance(x, int) and x > 0, |
|
77
|
|
|
'al': lambda x: isinstance(x, int) and x > 0, |
|
78
|
|
|
'lsc': lambda x: isinstance(x, int) and x > 0, |
|
79
|
|
|
'gsc': lambda x: isinstance(x, int) and x > 0, |
|
80
|
|
|
'tr': lambda x: isinstance(x, float) and 0 <= x <= 1, |
|
81
|
|
|
}) |
|
82
|
|
|
return d |
|
83
|
|
|
|
|
84
|
|
|
def setParameters(self, NP=10, lt=3, al=10, lsc=1, gsc=1, tr=0.3, **ukwargs): |
|
85
|
|
|
r"""Set the parameters of the algorithm. |
|
86
|
|
|
|
|
87
|
|
|
Args: |
|
88
|
|
|
NP (Optional[int]): Population size. |
|
89
|
|
|
lt (Optional[int]): Life time parameter. |
|
90
|
|
|
al (Optional[int]): Area limit parameter. |
|
91
|
|
|
lsc (Optional[int]): Local seeding changes parameter. |
|
92
|
|
|
gsc (Optional[int]): Global seeding changes parameter. |
|
93
|
|
|
tr (Optional[float]): Transfer rate parameter. |
|
94
|
|
|
ukwargs (Dict[str, Any]): Additional arguments. |
|
95
|
|
|
|
|
96
|
|
|
See Also: |
|
97
|
|
|
* :func:`NiaPy.algorithms.Algorithm.setParameters` |
|
98
|
|
|
""" |
|
99
|
|
|
Algorithm.setParameters(self, NP=NP, **ukwargs) |
|
100
|
|
|
self.lt, self.al, self.lsc, self.gsc, self.tr = lt, al, lsc, gsc, tr |
|
101
|
|
|
|
|
102
|
|
|
def getParameters(self): |
|
103
|
|
|
r"""Get parameters values of the algorithm. |
|
104
|
|
|
|
|
105
|
|
|
Returns: |
|
106
|
|
|
Dict[str, Any]: TODO. |
|
107
|
|
|
""" |
|
108
|
|
|
d = Algorithm.getParameters(self) |
|
109
|
|
|
d.update({ |
|
110
|
|
|
'lt': self.lt, |
|
111
|
|
|
'al': self.al, |
|
112
|
|
|
'lsc': self.lsc, |
|
113
|
|
|
'gsc': self.gsc, |
|
114
|
|
|
'tr': self.tr |
|
115
|
|
|
}) |
|
116
|
|
|
return d |
|
117
|
|
|
|
|
118
|
|
|
def localSeeding(self, task, trees): |
|
119
|
|
|
r"""Local optimum search stage. |
|
120
|
|
|
|
|
121
|
|
|
Args: |
|
122
|
|
|
task (Task): Optimization task. |
|
123
|
|
|
trees (numpy.ndarray): Zero age trees for local seeding. |
|
124
|
|
|
|
|
125
|
|
|
Returns: |
|
126
|
|
|
numpy.ndarray: Resulting zero age trees. |
|
127
|
|
|
""" |
|
128
|
|
|
n = trees.shape[0] |
|
129
|
|
|
deltas = self.uniform(-self.dx, self.dx, (n, self.lsc)) |
|
130
|
|
|
deltas = append(deltas, zeros((n, task.D - self.lsc)), axis=1) |
|
131
|
|
|
perms = self.rand([deltas.shape[0], deltas.shape[1]]).argsort(1) |
|
132
|
|
|
deltas = deltas[arange(deltas.shape[0])[:, None], perms] |
|
133
|
|
|
trees += deltas |
|
134
|
|
|
trees = apply_along_axis(limit_repair, 1, trees, task.Lower, task.Upper) |
|
135
|
|
|
return trees |
|
136
|
|
|
|
|
137
|
|
|
def globalSeeding(self, task, candidates, size): |
|
138
|
|
|
r"""Global optimum search stage that should prevent getting stuck in a local optimum. |
|
139
|
|
|
|
|
140
|
|
|
Args: |
|
141
|
|
|
task (Task): Optimization task. |
|
142
|
|
|
candidates (numpy.ndarray): Candidate population for global seeding. |
|
143
|
|
|
size (int): Number of trees to produce. |
|
144
|
|
|
|
|
145
|
|
|
Returns: |
|
146
|
|
|
numpy.ndarray: Resulting trees. |
|
147
|
|
|
""" |
|
148
|
|
|
seeds = candidates[self.randint(len(candidates), D=size)] |
|
149
|
|
|
deltas = self.uniform(task.benchmark.Lower, task.benchmark.Upper, (size, self.gsc)) |
|
150
|
|
|
deltas = append(deltas, zeros((size, task.D - self.gsc)), axis=1) |
|
151
|
|
|
perms = self.rand([deltas.shape[0], deltas.shape[1]]).argsort(1) |
|
152
|
|
|
deltas = deltas[arange(deltas.shape[0])[:, None], perms] |
|
153
|
|
|
deltas = deltas.flatten() |
|
154
|
|
|
seeds = seeds.flatten() |
|
155
|
|
|
seeds[deltas != 0] = deltas[deltas != 0] |
|
156
|
|
|
|
|
157
|
|
|
return seeds.reshape(size, task.D) |
|
158
|
|
|
|
|
159
|
|
|
def removeLifeTimeExceeded(self, trees, candidates, age): |
|
160
|
|
|
r"""Remove dead trees. |
|
161
|
|
|
|
|
162
|
|
|
Args: |
|
163
|
|
|
trees (numpy.ndarray): Population to test. |
|
164
|
|
|
candidates (numpy.ndarray): Candidate population array to be updated. |
|
165
|
|
|
age (numpy.ndarray[int32]): Age of trees. |
|
166
|
|
|
|
|
167
|
|
|
Returns: |
|
168
|
|
|
Tuple[numpy.ndarray, numpy.ndarray, numpy.ndarray[int32]]: |
|
169
|
|
|
1. Alive trees. |
|
170
|
|
|
2. New candidate population. |
|
171
|
|
|
3. Age of trees. |
|
172
|
|
|
""" |
|
173
|
|
|
lifeTimeExceeded = where(age > self.lt) |
|
174
|
|
|
candidates = trees[lifeTimeExceeded] |
|
175
|
|
|
trees = delete(trees, lifeTimeExceeded, axis=0) |
|
176
|
|
|
age = delete(age, lifeTimeExceeded, axis=0) |
|
177
|
|
|
return trees, candidates, age |
|
178
|
|
|
|
|
179
|
|
|
def survivalOfTheFittest(self, task, trees, candidates, age): |
|
180
|
|
|
r"""Evaluate and filter current population. |
|
181
|
|
|
|
|
182
|
|
|
Args: |
|
183
|
|
|
task (Task): Optimization task. |
|
184
|
|
|
trees (numpy.ndarray): Population to evaluate. |
|
185
|
|
|
candidates (numpy.ndarray): Candidate population array to be updated. |
|
186
|
|
|
age (numpy.ndarray[int32]): Age of trees. |
|
187
|
|
|
|
|
188
|
|
|
Returns: |
|
189
|
|
|
Tuple[numpy.ndarray, numpy.ndarray, numpy.ndarray[float], numpy.ndarray[int32]]: |
|
190
|
|
|
1. Trees sorted by fitness value. |
|
191
|
|
|
2. Updated candidate population. |
|
192
|
|
|
3. Population fitness values. |
|
193
|
|
|
4. Age of trees |
|
194
|
|
|
""" |
|
195
|
|
|
evaluations = apply_along_axis(task.eval, 1, trees) |
|
196
|
|
|
ei = evaluations.argsort() |
|
197
|
|
|
candidates = append(candidates, trees[ei[self.al:]], axis=0) |
|
198
|
|
|
trees = trees[ei[:self.al]] |
|
199
|
|
|
age = age[ei[:self.al]] |
|
200
|
|
|
evaluations = evaluations[ei[:self.al]] |
|
201
|
|
|
return trees, candidates, evaluations, age |
|
202
|
|
|
|
|
203
|
|
|
def initPopulation(self, task): |
|
204
|
|
|
r"""Initialize the starting population. |
|
205
|
|
|
|
|
206
|
|
|
Args: |
|
207
|
|
|
task (Task): Optimization task |
|
208
|
|
|
|
|
209
|
|
|
Returns: |
|
210
|
|
|
Tuple[numpy.ndarray, numpy.ndarray[float], Dict[str, Any]]: |
|
211
|
|
|
1. New population. |
|
212
|
|
|
2. New population fitness/function values. |
|
213
|
|
|
3. Additional arguments: |
|
214
|
|
|
* age (numpy.ndarray[int32]): Age of trees. |
|
215
|
|
|
|
|
216
|
|
|
See Also: |
|
217
|
|
|
* :func:`NiaPy.algorithms.Algorithm.initPopulation` |
|
218
|
|
|
""" |
|
219
|
|
|
Trees, Evaluations, _ = Algorithm.initPopulation(self, task) |
|
220
|
|
|
age = zeros(self.NP, dtype=int32) |
|
221
|
|
|
self.dx = absolute(task.benchmark.Upper) / 5 |
|
222
|
|
|
return Trees, Evaluations, {'age': age} |
|
223
|
|
|
|
|
224
|
|
|
def runIteration(self, task, Trees, Evaluations, xb, fxb, age, **dparams): |
|
225
|
|
|
r"""Core function of Forest Optimization Algorithm. |
|
226
|
|
|
|
|
227
|
|
|
Args: |
|
228
|
|
|
task (Task): Optimization task. |
|
229
|
|
|
Trees (numpy.ndarray): Current population. |
|
230
|
|
|
Evaluations (numpy.ndarray[float]): Current population function/fitness values. |
|
231
|
|
|
xb (numpy.ndarray): Global best individual. |
|
232
|
|
|
fxb (float): Global best individual fitness/function value. |
|
233
|
|
|
age (numpy.ndarray[int32]): Age of trees. |
|
234
|
|
|
**dparams (Dict[str, Any]): Additional arguments. |
|
235
|
|
|
|
|
236
|
|
|
Returns: |
|
237
|
|
|
Tuple[numpy.ndarray, numpy.ndarray[float], Dict[str, Any]]: |
|
238
|
|
|
1. New population. |
|
239
|
|
|
2. New population fitness/function values. |
|
240
|
|
|
3. Additional arguments: |
|
241
|
|
|
* age (numpy.ndarray[int32]): Age of trees. |
|
242
|
|
|
""" |
|
243
|
|
|
candidatePopulation = ndarray((0, task.D + 1)) |
|
244
|
|
|
zeroAgeTrees = Trees[age == 0] |
|
245
|
|
|
localSeeds = self.localSeeding(task, zeroAgeTrees) |
|
246
|
|
|
age += 1 |
|
247
|
|
|
Trees, candidatePopulation, age = self.removeLifeTimeExceeded(Trees, candidatePopulation, age) |
|
248
|
|
|
Trees = append(Trees, localSeeds, axis=0) |
|
249
|
|
|
age = append(age, zeros(len(localSeeds), dtype=int32)) |
|
250
|
|
|
Trees, candidatePopulation, Evaluations, age = self.survivalOfTheFittest(task, Trees, candidatePopulation, age) |
|
251
|
|
|
gsn = int(self.tr * len(candidatePopulation)) |
|
252
|
|
|
if gsn > 0: |
|
253
|
|
|
globalSeeds = self.globalSeeding(task, candidatePopulation, gsn) |
|
254
|
|
|
Trees = append(Trees, globalSeeds, axis=0) |
|
255
|
|
|
age = append(age, zeros(len(globalSeeds), dtype=int32)) |
|
256
|
|
|
gste = apply_along_axis(task.eval, 1, globalSeeds) |
|
257
|
|
|
Evaluations = append(Evaluations, gste) |
|
258
|
|
|
ib = argmin(Evaluations) |
|
259
|
|
|
age[ib] = 0 |
|
260
|
|
|
if Evaluations[ib] < fxb: xb, fxb = Trees[ib].copy(), Evaluations[ib] |
|
261
|
|
|
return Trees, Evaluations, xb, fxb, {'age': age} |
|
262
|
|
|
|