pyerualjetwork 4.0.5__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- pyerualjetwork/__init__.py +71 -0
- pyerualjetwork/activation_functions.py +367 -0
- pyerualjetwork/activation_functions_cuda.py +363 -0
- pyerualjetwork/data_operations.py +439 -0
- pyerualjetwork/data_operations_cuda.py +463 -0
- pyerualjetwork/help.py +16 -0
- pyerualjetwork/loss_functions.py +21 -0
- pyerualjetwork/loss_functions_cuda.py +21 -0
- pyerualjetwork/metrics.py +190 -0
- pyerualjetwork/metrics_cuda.py +165 -0
- pyerualjetwork/model_operations.py +408 -0
- pyerualjetwork/model_operations_cuda.py +414 -0
- pyerualjetwork/plan.py +681 -0
- pyerualjetwork/plan_cuda.py +677 -0
- pyerualjetwork/planeat.py +734 -0
- pyerualjetwork/planeat_cuda.py +736 -0
- pyerualjetwork/ui.py +22 -0
- pyerualjetwork/visualizations.py +799 -0
- pyerualjetwork/visualizations_cuda.py +799 -0
- pyerualjetwork-4.0.5.dist-info/METADATA +95 -0
- pyerualjetwork-4.0.5.dist-info/RECORD +23 -0
- pyerualjetwork-4.0.5.dist-info/WHEEL +5 -0
- pyerualjetwork-4.0.5.dist-info/top_level.txt +1 -0
@@ -0,0 +1,734 @@
|
|
1
|
+
"""
|
2
|
+
MAIN MODULE FOR PLANEAT
|
3
|
+
|
4
|
+
ANAPLAN document: https://github.com/HCB06/Anaplan/blob/main/Welcome_to_Anaplan/ANAPLAN_USER_MANUEL_AND_LEGAL_INFORMATION(EN).pdf
|
5
|
+
|
6
|
+
@author: Hasan Can Beydili
|
7
|
+
@YouTube: https://www.youtube.com/@HasanCanBeydili
|
8
|
+
@Linkedin: https://www.linkedin.com/in/hasan-can-beydili-77a1b9270/
|
9
|
+
@Instagram: https://www.instagram.com/canbeydili.06/
|
10
|
+
@contact: tchasancan@gmail.com
|
11
|
+
"""
|
12
|
+
|
13
|
+
import numpy as np
|
14
|
+
import random
|
15
|
+
from tqdm import tqdm
|
16
|
+
|
17
|
+
### LIBRARY IMPORTS ###
|
18
|
+
from plan import feed_forward
|
19
|
+
from data_operations import normalization
|
20
|
+
from ui import loading_bars
|
21
|
+
from activation_functions import apply_activation, all_activations
|
22
|
+
|
23
|
+
def define_genomes(input_shape, output_shape, population_size, dtype=np.float32):
|
24
|
+
"""
|
25
|
+
Initializes a population of genomes, where each genome is represented by a set of weights
|
26
|
+
and an associated activation function. Each genome is created with random weights and activation
|
27
|
+
functions are applied and normalized. (Max abs normalization.)
|
28
|
+
|
29
|
+
Args:
|
30
|
+
|
31
|
+
input_shape (int): The number of input features for the neural network.
|
32
|
+
|
33
|
+
output_shape (int): The number of output features for the neural network.
|
34
|
+
|
35
|
+
population_size (int): The number of genomes (individuals) in the population.
|
36
|
+
|
37
|
+
dtype (numpy.dtype): Data type for the arrays. np.float32 by default. Example: np.float64 or np.float16. [fp32 for balanced devices, fp64 for strong devices, fp16 for weak devices: not reccomended!] (optional)
|
38
|
+
|
39
|
+
Returns:
|
40
|
+
tuple: A tuple containing:
|
41
|
+
- population_weights (numpy.ndarray): A 2D numpy array of shape (population_size, output_shape, input_shape) representing the
|
42
|
+
weight matrices for each genome.
|
43
|
+
- population_activations (list): A list of activation functions applied to each genome.
|
44
|
+
|
45
|
+
Notes:
|
46
|
+
The weights are initialized randomly within the range [-1, 1].
|
47
|
+
Activation functions are selected randomly from a predefined list `all_activations()`.
|
48
|
+
The weights for each genome are then modified by applying the corresponding activation function
|
49
|
+
and normalized using the `normalization()` function. (Max abs normalization.)
|
50
|
+
"""
|
51
|
+
population_weights = [0] * population_size
|
52
|
+
population_activations = [0] * population_size
|
53
|
+
|
54
|
+
except_this = ['spiral', 'circular']
|
55
|
+
activations = [item for item in all_activations() if item not in except_this] # SPIRAL AND CIRCULAR ACTIVATION DISCARDED
|
56
|
+
|
57
|
+
for i in range(len(population_weights)):
|
58
|
+
|
59
|
+
population_weights[i] = np.random.uniform(-1, 1, (output_shape, input_shape)).astype(dtype)
|
60
|
+
population_activations[i] = activations[int(random.uniform(0, len(activations)-1))]
|
61
|
+
|
62
|
+
# ACTIVATIONS APPLYING IN WEIGHTS SPECIFIC OUTPUT CONNECTIONS (MORE PLAN LIKE FEATURES(FOR NON-LINEARITY)):
|
63
|
+
|
64
|
+
for j in range(population_weights[i].shape[0]):
|
65
|
+
|
66
|
+
population_weights[i][j,:] = apply_activation(population_weights[i][j,:], population_activations[i])
|
67
|
+
population_weights[i][j,:] = normalization(population_weights[i][j,:], dtype=dtype)
|
68
|
+
|
69
|
+
return np.array(population_weights), population_activations
|
70
|
+
|
71
|
+
|
72
|
+
def evolve(weights, activation_potentiations, what_gen, y_reward, show_info=False, strategy='cross_over', policy='normal_selective', mutations=True, bad_genoms_mutation_prob=None, activation_mutate_prob=0.5, save_best_genom=True, cross_over_mode='tpm', activation_add_prob=0.5, activation_delete_prob=0.5, activation_change_prob=0.5, weight_mutate_prob=1, weight_mutate_rate=32, activation_selection_add_prob=0.5, activation_selection_change_prob=0.5, activation_selection_rate=2, dtype=np.float32):
|
73
|
+
"""
|
74
|
+
Applies the evolving process of a population of genomes using selection, crossover, mutation, and activation function potentiation.
|
75
|
+
The function modifies the population's weights and activation functions based on a specified policy, mutation probabilities, and strategy.
|
76
|
+
|
77
|
+
Args:
|
78
|
+
weights (numpy.ndarray): Array of weights for each genome.
|
79
|
+
(first returned value of define_genomes function)
|
80
|
+
|
81
|
+
activation_potentiations (list): A list of activation functions for each genome.
|
82
|
+
(second returned value of define_genomes function)
|
83
|
+
|
84
|
+
what_gen (int): The current generation number, used for informational purposes or logging.
|
85
|
+
|
86
|
+
y_reward (numpy.ndarray): A 1D array containing the fitness or reward values of each genome.
|
87
|
+
The array is used to rank the genomes based on their performance. PLANEAT maximizes the reward.
|
88
|
+
|
89
|
+
show_info (bool, optional): If True, prints information about the current generation and the
|
90
|
+
maximum reward obtained. Also shows the current configuration. Default is False.
|
91
|
+
|
92
|
+
strategy (str, optional): The strategy for combining the best and bad genomes. Options:
|
93
|
+
- 'cross_over': Perform crossover between the best genomes and replace bad genomes.
|
94
|
+
(Classic NEAT crossover)
|
95
|
+
- 'potentiate': Cumulate the weight of the best genomes and replace bad genomes.
|
96
|
+
(PLAN feature, similar to arithmetic crossover but different.)
|
97
|
+
Default is 'cross_over'.
|
98
|
+
|
99
|
+
policy (str, optional): The selection policy that governs how genomes are selected for reproduction. Options:
|
100
|
+
- 'normal_selective': Normal selection based on reward, where a portion of the bad genes are discarded.
|
101
|
+
- 'more_selective': A more selective policy, where fewer bad genes survive.
|
102
|
+
- 'less_selective': A less selective policy, where more bad genes survive.
|
103
|
+
Default is 'normal_selective'.
|
104
|
+
|
105
|
+
mutations (bool, optional): If True, mutations are applied to the bad genomes and potentially
|
106
|
+
to the best genomes as well. Default is True.
|
107
|
+
|
108
|
+
bad_genoms_mutation_prob (float, optional): The probability of applying mutation to the bad genomes.
|
109
|
+
Must be in the range [0, 1]. Also affects the mutation probability of the best genomes inversely.
|
110
|
+
For example, a value of 0.7 for bad genomes implies 0.3 for best genomes. Default is None,
|
111
|
+
which means it is determined by the `policy` argument.
|
112
|
+
|
113
|
+
activation_mutate_prob (float, optional): The probability of applying mutation to the activation functions.
|
114
|
+
Must be in the range [0, 1]. Default is 0.5 (50%).
|
115
|
+
|
116
|
+
save_best_genom (bool, optional): If True, ensures that the best genomes are saved and not mutated
|
117
|
+
or altered during reproduction. Default is True.
|
118
|
+
|
119
|
+
cross_over_mode (str, optional): Specifies the crossover method to use. Options:
|
120
|
+
- 'tpm': Two-Point Matrix Crossover
|
121
|
+
- 'plantic': plantic Crossover
|
122
|
+
Default is 'tpm'.
|
123
|
+
|
124
|
+
activation_add_prob (float, optional): The probability of adding a new activation function to the genome for mutation.
|
125
|
+
Must be in the range [0, 1]. Default is 0.5.
|
126
|
+
|
127
|
+
activation_delete_prob (float, optional): The probability of deleting an existing activation function
|
128
|
+
from the genome for mutation. Must be in the range [0, 1]. Default is 0.5.
|
129
|
+
|
130
|
+
activation_change_prob (float, optional): The probability of changing an activation function in the genome for mutation.
|
131
|
+
Must be in the range [0, 1]. Default is 0.5.
|
132
|
+
|
133
|
+
weight_mutate_prob (float, optional): The probability of mutating a weight in the genome.
|
134
|
+
Must be in the range [0, 1]. Default is 1.
|
135
|
+
|
136
|
+
weight_mutate_rate (int, optional): If the value you enter here is equal to the result of input layer * output layer,
|
137
|
+
only a single weight will be mutated during each mutation process. If the value you enter here is half
|
138
|
+
of the result of input layer * output layer, two weights in the weight matrix will be mutated.
|
139
|
+
WARNING: if you don't understand do NOT change this value. Default is 32.
|
140
|
+
|
141
|
+
activation_selection_add_prob (float, optional): The probability of adding an existing activation function for cross over.
|
142
|
+
from the genome. Must be in the range [0, 1]. Default is 0.5.
|
143
|
+
|
144
|
+
activation_selection_change_prob (float, optional): The probability of changing an activation function in the genome for cross over.
|
145
|
+
Must be in the range [0, 1]. Default is 0.5.
|
146
|
+
|
147
|
+
activation_selection_rate (int, optional): If the activation list of a good genome is smaller than the value entered here, only one activation will undergo a crossover operation. In other words, this parameter controls the model complexity. Default is 2.
|
148
|
+
|
149
|
+
dtype (numpy.dtype): Data type for the arrays. np.float32 by default. Example: np.float64 or np.float16. [fp32 for balanced devices, fp64 for strong devices, fp16 for weak devices: not reccomended!] (optional)
|
150
|
+
|
151
|
+
Raises:
|
152
|
+
ValueError:
|
153
|
+
- If `policy` is not one of the specified values ('normal_selective', 'more_selective', 'less_selective').
|
154
|
+
- If `cross_over_mode` is not one of the specified values ('tpm', 'plantic').
|
155
|
+
- If `bad_genoms_mutation_prob`, `activation_mutate_prob`, or other probability parameters are not in the range [0, 1].
|
156
|
+
- If the population size is odd (ensuring an even number of genomes is required for proper selection).
|
157
|
+
|
158
|
+
Returns:
|
159
|
+
tuple: A tuple containing:
|
160
|
+
- weights (numpy.ndarray): The updated weights for the population after selection, crossover, and mutation.
|
161
|
+
The shape is (population_size, output_shape, input_shape).
|
162
|
+
- activation_potentiations (list): The updated list of activation functions for the population.
|
163
|
+
|
164
|
+
Notes:
|
165
|
+
- **Selection Process**:
|
166
|
+
- The genomes are sorted by their fitness (based on `y_reward`), and then split into "best" and "bad" halves.
|
167
|
+
- The best genomes are retained, and the bad genomes are modified based on the selected strategy.
|
168
|
+
|
169
|
+
- **Crossover and Potentiation Strategies**:
|
170
|
+
- The **'cross_over'** strategy performs crossover, where parts of the best genomes' weights are combined with the other good genomes to create new weight matrices.
|
171
|
+
- The **'potentiate'** strategy strengthens the best genomes by potentiating their weights towards the other good genomes.
|
172
|
+
|
173
|
+
- **Mutation**:
|
174
|
+
- Mutation is applied to both the best and bad genomes, depending on the mutation probability and the `policy`.
|
175
|
+
- `bad_genoms_mutation_prob` determines the probability of applying mutations to the bad genomes.
|
176
|
+
- If `activation_mutate_prob` is provided, activation function mutations are applied to the genomes based on this probability.
|
177
|
+
|
178
|
+
- **Population Size**: The population size must be an even number to properly split the best and bad genomes. If `y_reward` has an odd length, an error is raised.
|
179
|
+
|
180
|
+
- **Logging**: If `show_info=True`, the current generation and the maximum reward from the population are printed for tracking the learning progress.
|
181
|
+
|
182
|
+
Example:
|
183
|
+
```python
|
184
|
+
weights, activation_potentiations = learner(weights, activation_potentiations, 1, y_reward, info=True, strategy='cross_over', policy='normal_selective')
|
185
|
+
```
|
186
|
+
|
187
|
+
- The function returns the updated weights and activations after processing based on the chosen strategy, policy, and mutation parameters.
|
188
|
+
"""
|
189
|
+
|
190
|
+
### ERROR AND CONFIGURATION CHECKS:
|
191
|
+
|
192
|
+
if policy == 'normal_selective':
|
193
|
+
if bad_genoms_mutation_prob == None:
|
194
|
+
bad_genoms_mutation_prob = 0.7
|
195
|
+
|
196
|
+
elif policy == 'more_selective':
|
197
|
+
if bad_genoms_mutation_prob == None:
|
198
|
+
bad_genoms_mutation_prob = 0.85
|
199
|
+
|
200
|
+
elif policy == 'less_selective':
|
201
|
+
if bad_genoms_mutation_prob == None:
|
202
|
+
bad_genoms_mutation_prob = 0.6
|
203
|
+
|
204
|
+
else:
|
205
|
+
raise ValueError("policy parameter must be: 'normal_selective' or 'more_selective' or 'less_selective'")
|
206
|
+
|
207
|
+
|
208
|
+
if (activation_add_prob < 0 or activation_add_prob > 1) or (activation_change_prob < 0 or activation_change_prob > 1) or (activation_delete_prob < 0 or activation_delete_prob > 1) or (weight_mutate_prob < 0 or weight_mutate_prob > 1) or (activation_selection_add_prob < 0 or activation_selection_add_prob > 1) or (activation_selection_change_prob < 0 or activation_selection_change_prob > 1):
|
209
|
+
raise ValueError("All hyperparameters ending with 'prob' must be a number between 0 and 1.")
|
210
|
+
|
211
|
+
if cross_over_mode != 'tpm' and cross_over_mode != 'plantic':
|
212
|
+
raise ValueError("cross_over_mode parameter must be 'tpm' or 'plantic'")
|
213
|
+
|
214
|
+
if bad_genoms_mutation_prob is not None:
|
215
|
+
if not isinstance(bad_genoms_mutation_prob, float) or bad_genoms_mutation_prob < 0 or bad_genoms_mutation_prob > 1:
|
216
|
+
raise ValueError("bad_genoms_mutation_prob parameter must be float and 0-1 range")
|
217
|
+
|
218
|
+
if activation_mutate_prob is not None:
|
219
|
+
if not isinstance(activation_mutate_prob, float) or activation_mutate_prob < 0 or activation_mutate_prob > 1:
|
220
|
+
raise ValueError("activation_mutate_prob parameter must be float and 0-1 range")
|
221
|
+
|
222
|
+
if len(y_reward) % 2 == 0:
|
223
|
+
slice_center = int(len(y_reward) / 2)
|
224
|
+
|
225
|
+
else:
|
226
|
+
raise ValueError("genome population size must be even number. for example: not 99, make 100 or 98.")
|
227
|
+
|
228
|
+
sort_indices = np.argsort(y_reward)
|
229
|
+
|
230
|
+
### REWARD LIST IS SORTED IN ASCENDING ORDER, AND THE WEIGHT AND ACTIVATIONS OF EACH GENOME ARE SORTED ACCORDING TO THIS ORDER:
|
231
|
+
|
232
|
+
y_reward = y_reward[sort_indices]
|
233
|
+
weights = weights[sort_indices]
|
234
|
+
|
235
|
+
activation_potentiations = [activation_potentiations[i] for i in sort_indices]
|
236
|
+
|
237
|
+
### GENOMES ARE DIVIDED INTO TWO GROUPS: GOOD GENOMES AND BAD GENOMES:
|
238
|
+
|
239
|
+
best_weights = weights[slice_center:]
|
240
|
+
bad_weights = weights[:slice_center]
|
241
|
+
best_weight = best_weights[len(best_weights)-1]
|
242
|
+
|
243
|
+
best_activations = list(activation_potentiations[slice_center:])
|
244
|
+
bad_activations = list(activation_potentiations[:slice_center])
|
245
|
+
best_activation = best_activations[len(best_activations) - 1]
|
246
|
+
|
247
|
+
|
248
|
+
### NEAT IS APPLIED ACCORDING TO THE SPECIFIED POLICY, STRATEGY, AND PROBABILITY CONFIGURATION:
|
249
|
+
|
250
|
+
bar_format = loading_bars()[0]
|
251
|
+
|
252
|
+
for i in tqdm(range(len(bad_weights)), desc="GENERATION: " + str(what_gen), bar_format=bar_format, ncols=50, ascii="▱▰"):
|
253
|
+
|
254
|
+
if policy == 'normal_selective':
|
255
|
+
|
256
|
+
if strategy == 'cross_over':
|
257
|
+
bad_weights[i], bad_activations[i] = cross_over(best_weight, best_weights[i], best_activations=best_activation, good_activations=best_activations[i], cross_over_mode=cross_over_mode, activation_selection_add_prob=activation_selection_add_prob, activation_selection_change_prob=activation_selection_change_prob, activation_selection_rate=activation_selection_rate)
|
258
|
+
|
259
|
+
|
260
|
+
elif strategy == 'potentiate':
|
261
|
+
bad_weights[i], bad_activations[i] = potentiate(best_weight, best_weights[i], best_activations=best_activation, good_activations=best_activations[i], dtype=dtype)
|
262
|
+
|
263
|
+
|
264
|
+
if mutations is True:
|
265
|
+
|
266
|
+
mutation_prob = random.uniform(0, 1)
|
267
|
+
|
268
|
+
if mutation_prob > bad_genoms_mutation_prob:
|
269
|
+
if (save_best_genom == True and not np.array_equal(best_weights[i], best_weight)) or save_best_genom == False:
|
270
|
+
best_weights[i], best_activations[i] = mutation(best_weights[i], best_activations[i], activation_mutate_prob=activation_mutate_prob, activation_add_prob=activation_add_prob, activation_delete_prob=activation_delete_prob, activation_change_prob=activation_change_prob, weight_mutate_prob=weight_mutate_prob, threshold=weight_mutate_rate, dtype=dtype)
|
271
|
+
|
272
|
+
elif mutation_prob < bad_genoms_mutation_prob:
|
273
|
+
bad_weights[i], bad_activations[i] = mutation(bad_weights[i], bad_activations[i], activation_mutate_prob=activation_mutate_prob, activation_add_prob=activation_add_prob, activation_delete_prob=activation_delete_prob, activation_change_prob=activation_change_prob, weight_mutate_prob=weight_mutate_prob, threshold=weight_mutate_rate, dtype=dtype)
|
274
|
+
|
275
|
+
if policy == 'more_selective':
|
276
|
+
|
277
|
+
if strategy == 'cross_over':
|
278
|
+
bad_weights[i], bad_activations[i] = cross_over(best_weight, best_weights[i], best_activations=best_activation, good_activations=best_activations[i], cross_over_mode=cross_over_mode, activation_selection_add_prob=activation_selection_add_prob, activation_selection_change_prob=activation_selection_change_prob, activation_selection_rate=activation_selection_rate)
|
279
|
+
|
280
|
+
elif strategy == 'potentiate':
|
281
|
+
bad_weights[i], bad_activations[i] = potentiate(best_weight, best_weights[i], best_activations=best_activation, good_activations=best_activations[i], dtype=dtype)
|
282
|
+
|
283
|
+
if mutations is True:
|
284
|
+
|
285
|
+
mutation_prob = random.uniform(0, 1)
|
286
|
+
|
287
|
+
if mutation_prob > bad_genoms_mutation_prob:
|
288
|
+
if (save_best_genom == True and not np.array_equal(best_weights[i], best_weight)) or save_best_genom == False:
|
289
|
+
best_weights[i], best_activations[i] = mutation(best_weights[i], best_activations[i], activation_mutate_prob=activation_mutate_prob, activation_add_prob=activation_add_prob, activation_delete_prob=activation_delete_prob, activation_change_prob=activation_change_prob, weight_mutate_prob=weight_mutate_prob, threshold=weight_mutate_rate, dtype=dtype)
|
290
|
+
|
291
|
+
elif mutation_prob < bad_genoms_mutation_prob:
|
292
|
+
bad_weights[i], bad_activations[i] = mutation(bad_weights[i], bad_activations[i], activation_mutate_prob=activation_mutate_prob, activation_add_prob=activation_add_prob, activation_delete_prob=activation_delete_prob, activation_change_prob=activation_change_prob, weight_mutate_prob=weight_mutate_prob, threshold=weight_mutate_rate, dtype=dtype)
|
293
|
+
|
294
|
+
|
295
|
+
|
296
|
+
if policy == 'less_selective':
|
297
|
+
|
298
|
+
random_index = int(random.uniform(0, len(best_weights) - 1))
|
299
|
+
|
300
|
+
if strategy == 'cross_over':
|
301
|
+
bad_weights[i], bad_activations[i] = cross_over(best_weights[random_index], best_weights[i], best_activations=best_activations[random_index], good_activations=best_activations[i], cross_over_mode=cross_over_mode, activation_selection_add_prob=activation_selection_add_prob, activation_selection_change_prob=activation_selection_change_prob, activation_selection_rate=activation_selection_rate)
|
302
|
+
|
303
|
+
elif strategy == 'potentiate':
|
304
|
+
bad_weights[i], bad_activations[i] = potentiate(best_weights[random_index], best_weights[i], best_activations=best_activations[random_index], good_activations=best_activations[i])
|
305
|
+
|
306
|
+
if mutations is True:
|
307
|
+
|
308
|
+
mutation_prob = random.uniform(0, 1)
|
309
|
+
|
310
|
+
if mutation_prob > bad_genoms_mutation_prob:
|
311
|
+
if (save_best_genom == True and not np.array_equal(best_weights[i], best_weight)) or save_best_genom == False:
|
312
|
+
best_weights[i], best_activations[i] = mutation(best_weights[i], best_activations[i], activation_mutate_prob=activation_mutate_prob, activation_add_prob=activation_add_prob, activation_delete_prob=activation_delete_prob, activation_change_prob=activation_change_prob, weight_mutate_prob=weight_mutate_prob, threshold=weight_mutate_rate, dtype=dtype)
|
313
|
+
|
314
|
+
elif mutation_prob < bad_genoms_mutation_prob:
|
315
|
+
bad_weights[i], bad_activations[i] = mutation(bad_weights[i], bad_activations[i], activation_mutate_prob=activation_mutate_prob, activation_add_prob=activation_add_prob, activation_delete_prob=activation_delete_prob, activation_change_prob=activation_change_prob, weight_mutate_prob=weight_mutate_prob, threshold=weight_mutate_rate, dtype=dtype)
|
316
|
+
|
317
|
+
|
318
|
+
weights = np.vstack((bad_weights, best_weights))
|
319
|
+
activation_potentiations = bad_activations + best_activations
|
320
|
+
|
321
|
+
### INFO PRINTING CONSOLE
|
322
|
+
|
323
|
+
if show_info == True:
|
324
|
+
print("\nGENERATION:", str(what_gen) + ' FINISHED \n')
|
325
|
+
print("*** Configuration Settings ***")
|
326
|
+
print(" POPULATION SIZE: ", str(len(weights)))
|
327
|
+
print(" STRATEGY: ", strategy)
|
328
|
+
|
329
|
+
if strategy == 'cross_over':
|
330
|
+
print(" CROSS OVER MODE: ", cross_over_mode)
|
331
|
+
|
332
|
+
print(" POLICY: ", policy)
|
333
|
+
print(" MUTATIONS: ", str(mutations))
|
334
|
+
print(" BAD GENOMES MUTATION PROB: ", str(bad_genoms_mutation_prob))
|
335
|
+
print(" GOOD GENOMES MUTATION PROB: ", str(round(1 - bad_genoms_mutation_prob, 2)))
|
336
|
+
print(" WEIGHT MUTATE PROB: ", str(weight_mutate_prob))
|
337
|
+
print(" WEIGHT MUTATE RATE (THRESHOLD VALUE FOR SINGLE MUTATION): ", str(weight_mutate_rate))
|
338
|
+
print(" ACTIVATION MUTATE PROB: ", str(activation_mutate_prob))
|
339
|
+
print(" ACTIVATION ADD PROB: ", str(activation_add_prob))
|
340
|
+
print(" ACTIVATION DELETE PROB: ", str(activation_delete_prob))
|
341
|
+
print(" ACTIVATION CHANGE PROB: ", str(activation_change_prob))
|
342
|
+
print(" ACTIVATION SELECTION ADD PROB: ", str(activation_selection_add_prob))
|
343
|
+
print(" ACTIVATION SELECTION CHANGE PROB: ", str(activation_selection_change_prob))
|
344
|
+
print(" ACTIVATION SELECTION RATE (THRESHOLD VALUE FOR SINGLE CROSS OVER):", str(activation_selection_rate) + '\n')
|
345
|
+
|
346
|
+
print("*** Performance ***")
|
347
|
+
print(" MAX REWARD: ", str(round(max(y_reward), 2)))
|
348
|
+
print(" MEAN REWARD: ", str(round(np.mean(y_reward), 2)))
|
349
|
+
print(" MIN REWARD: ", str(round(min(y_reward), 2)) + '\n')
|
350
|
+
|
351
|
+
print(" BEST GENOME INDEX: ", str(len(weights)-1))
|
352
|
+
print(" NOTE: Genomes are always sorted from the least successful to the most successful according to their performance ranking. Therefore, the genome at the last index is the king of the previous generation. " + '\n')
|
353
|
+
|
354
|
+
|
355
|
+
return weights, activation_potentiations
|
356
|
+
|
357
|
+
|
358
|
+
def evaluate(x_population, weights, activation_potentiations, rl_mode=False, dtype=np.float32):
|
359
|
+
"""
|
360
|
+
Evaluates the performance of a population of genomes, applying different activation functions
|
361
|
+
and weights depending on whether reinforcement learning mode is enabled or not.
|
362
|
+
|
363
|
+
Args:
|
364
|
+
x_population (list or numpy.ndarray): A list or 2D numpy array where each element represents
|
365
|
+
a genome (A list of input features for each genome, or a single set of input features for one genome (only in rl_mode)).
|
366
|
+
weights (list or numpy.ndarray): A list or 2D numpy array of weights corresponding to each genome
|
367
|
+
in `x_population`. This determines the strength of connections.
|
368
|
+
activation_potentiations (list or str): A list where each entry represents an activation function
|
369
|
+
or a potentiation strategy applied to each genome. If only one
|
370
|
+
activation function is used, this can be a single string.
|
371
|
+
rl_mode (bool, optional): If True, reinforcement learning mode is activated, this accepts x_population is a single genom. (Also weights and activation_potentations a single genomes part.)
|
372
|
+
Default is False.
|
373
|
+
|
374
|
+
dtype (numpy.dtype): Data type for the arrays. np.float32 by default. Example: np.float64 or np.float16. [fp32 for balanced devices, fp64 for strong devices, fp16 for weak devices: not reccomended!] (optional)
|
375
|
+
|
376
|
+
Returns:
|
377
|
+
list: A list of outputs corresponding to each genome in the population after applying the respective
|
378
|
+
activation function and weights.
|
379
|
+
|
380
|
+
Notes:
|
381
|
+
- If `rl_mode` is True:
|
382
|
+
- Accepts x_population is a single genom
|
383
|
+
- The inputs are flattened, and the activation function is applied across the single genom.
|
384
|
+
|
385
|
+
- If `rl_mode` is False:
|
386
|
+
- Accepts x_population is a list of genomes
|
387
|
+
- Each genome is processed individually, and the results are stored in the `outputs` list.
|
388
|
+
|
389
|
+
- `feed_forward()` function is the core function that processes the input with the given weights and activation function.
|
390
|
+
|
391
|
+
Example:
|
392
|
+
```python
|
393
|
+
outputs = evaluate(x_population, weights, activation_potentiations, rl_mode=False)
|
394
|
+
```
|
395
|
+
|
396
|
+
- The function returns a list of outputs after processing the population, where each element corresponds to
|
397
|
+
the output for each genome in `x_population`.
|
398
|
+
"""
|
399
|
+
|
400
|
+
### IF RL_MODE IS TRUE, A SINGLE GENOME IS ASSUMED AS INPUT, A FEEDFORWARD PREDICTION IS MADE, AND THE OUTPUT(NPARRAY) IS RETURNED:
|
401
|
+
|
402
|
+
### IF RL_MODE IS FALSE, PREDICTIONS ARE MADE FOR ALL GENOMES IN THE GROUP USING THEIR CORRESPONDING INDEXED INPUTS AND DATA.
|
403
|
+
### THE OUTPUTS ARE RETURNED AS A PYTHON LIST, WHERE EACH GENOME'S OUTPUT MATCHES ITS INDEX:
|
404
|
+
|
405
|
+
if rl_mode == True:
|
406
|
+
Input = np.array(x_population, copy=False, dtype=dtype)
|
407
|
+
Input = Input.ravel()
|
408
|
+
|
409
|
+
if isinstance(activation_potentiations, str):
|
410
|
+
activation_potentiations = [activation_potentiations]
|
411
|
+
|
412
|
+
outputs = feed_forward(Input=Input, is_training=False, activation_potentiation=activation_potentiations, w=weights)
|
413
|
+
|
414
|
+
else:
|
415
|
+
outputs = [0] * len(x_population)
|
416
|
+
for i, genome in enumerate(x_population):
|
417
|
+
|
418
|
+
Input = np.array(genome, copy=False)
|
419
|
+
Input = Input.ravel()
|
420
|
+
|
421
|
+
if isinstance(activation_potentiations[i], str):
|
422
|
+
activation_potentiations[i] = [activation_potentiations[i]]
|
423
|
+
|
424
|
+
outputs[i] = feed_forward(Input=Input, is_training=False, activation_potentiation=activation_potentiations[i], w=weights[i])
|
425
|
+
|
426
|
+
return outputs
|
427
|
+
|
428
|
+
|
429
|
+
def cross_over(best_weight, good_weight, best_activations, good_activations, cross_over_mode, activation_selection_add_prob, activation_selection_change_prob, activation_selection_rate):
|
430
|
+
"""
|
431
|
+
Performs a selected Crossover operation on two sets of weights and activation functions.
|
432
|
+
This function combines two individuals (represented by their weights and activation functions)
|
433
|
+
to create a new individual by exchanging parts of their weight matrices and activation functions.
|
434
|
+
|
435
|
+
Args:
|
436
|
+
best_weight (numpy.ndarray): The weight matrix of the first individual (parent).
|
437
|
+
good_weight (numpy.ndarray): The weight matrix of the second individual (parent).
|
438
|
+
best_activations (str or list): The activation function(s) of the first individual.
|
439
|
+
good_activations (str or list): The activation function(s) of the second individual.
|
440
|
+
cross_over_mode (str): Determines the crossover method to be used. Options:
|
441
|
+
- 'tpm': Two-Point Matrix Crossover, where sub-matrices of weights
|
442
|
+
are swapped between parents.
|
443
|
+
- 'plan': Output Connections Crossover, where specific connections
|
444
|
+
in the weight matrix are crossed over. Default is 'tpm'.
|
445
|
+
|
446
|
+
Returns:
|
447
|
+
tuple: A tuple containing:
|
448
|
+
- new_weight (numpy.ndarray): The weight matrix of the new individual created by crossover.
|
449
|
+
- new_activations (list): The list of activation functions of the new individual created by crossover.
|
450
|
+
|
451
|
+
Notes:
|
452
|
+
- The crossover is performed based on the selected `cross_over_mode`.
|
453
|
+
- In 'tpm', random sub-matrices from the parent weight matrices are swapped.
|
454
|
+
- In 'plantic', specific connections in the weight matrix are swapped between parents.
|
455
|
+
- The crossover operation combines the activation functions of both parents:
|
456
|
+
- If the activation functions are passed as strings, they are converted to lists for uniform handling.
|
457
|
+
- The resulting activation functions depend on the crossover method and the parent's configuration.
|
458
|
+
|
459
|
+
Example:
|
460
|
+
```python
|
461
|
+
new_weights, new_activations = cross_over(best_weight, good_weight, best_activations, good_activations, cross_over_mode='tpm')
|
462
|
+
```
|
463
|
+
"""
|
464
|
+
|
465
|
+
### THE GIVEN GENOMES' WEIGHTS ARE RANDOMLY SELECTED AND COMBINED OVER A RANDOM RANGE. SIMILARLY, THEIR ACTIVATIONS ARE COMBINED. A NEW GENOME IS RETURNED WITH THE COMBINED WEIGHTS FIRST, FOLLOWED BY THE ACTIVATIONS:
|
466
|
+
|
467
|
+
start = 0
|
468
|
+
|
469
|
+
row_end = best_weight.shape[0]
|
470
|
+
col_end = best_weight.shape[1]
|
471
|
+
|
472
|
+
while True:
|
473
|
+
|
474
|
+
row_cut_start = int(random.uniform(start, row_end))
|
475
|
+
col_cut_start = int(random.uniform(start, col_end))
|
476
|
+
|
477
|
+
row_cut_end = int(random.uniform(start, row_end))
|
478
|
+
col_cut_end = int(random.uniform(start, col_end))
|
479
|
+
|
480
|
+
if (row_cut_end > row_cut_start) and (col_cut_end > col_cut_start):
|
481
|
+
break
|
482
|
+
|
483
|
+
new_weight = np.copy(best_weight)
|
484
|
+
best_w2 = np.copy(good_weight)
|
485
|
+
|
486
|
+
if cross_over_mode == 'tpm':
|
487
|
+
new_weight[row_cut_start:row_cut_end, col_cut_start:col_cut_end] = best_w2[row_cut_start:row_cut_end, col_cut_start:col_cut_end]
|
488
|
+
|
489
|
+
elif cross_over_mode == 'plantic':
|
490
|
+
new_weight[row_cut_start:row_cut_end,:] = best_w2[row_cut_start:row_cut_end,:]
|
491
|
+
|
492
|
+
|
493
|
+
if isinstance(best_activations, str):
|
494
|
+
best = [best_activations]
|
495
|
+
|
496
|
+
if isinstance(good_activations, str):
|
497
|
+
good = [good_activations]
|
498
|
+
|
499
|
+
if isinstance(best_activations, list):
|
500
|
+
best = best_activations
|
501
|
+
|
502
|
+
if isinstance(good_activations, list):
|
503
|
+
good = good_activations
|
504
|
+
|
505
|
+
new_activations = list(np.copy(best))
|
506
|
+
|
507
|
+
activation_selection_add_prob = 1 - activation_selection_add_prob # if prob 0.8 (%80) then 1 - 0.8. Because 0-1 random number probably greater than 0.2
|
508
|
+
potential_activation_selection_add = random.uniform(0, 1)
|
509
|
+
|
510
|
+
if potential_activation_selection_add > activation_selection_add_prob:
|
511
|
+
|
512
|
+
new_threshold = activation_selection_rate
|
513
|
+
|
514
|
+
while True:
|
515
|
+
|
516
|
+
random_index_good = int(random.uniform(0, len(good)-1))
|
517
|
+
random_good_activation = good[random_index_good]
|
518
|
+
|
519
|
+
new_activations.append(random_good_activation)
|
520
|
+
|
521
|
+
if len(best) > new_threshold:
|
522
|
+
new_threshold += activation_selection_rate
|
523
|
+
pass
|
524
|
+
|
525
|
+
else:
|
526
|
+
break
|
527
|
+
|
528
|
+
activation_selection_change_prob = 1 - activation_selection_change_prob
|
529
|
+
potential_activation_selection_change_prob = random.uniform(0, 1)
|
530
|
+
|
531
|
+
if potential_activation_selection_change_prob > activation_selection_change_prob:
|
532
|
+
|
533
|
+
new_threshold = activation_selection_rate
|
534
|
+
|
535
|
+
while True:
|
536
|
+
|
537
|
+
random_index_good = int(random.uniform(0, len(good)-1))
|
538
|
+
random_index_best = int(random.uniform(0, len(best)-1))
|
539
|
+
random_good_activation = good[random_index_good]
|
540
|
+
|
541
|
+
new_activations[random_index_best] = good[random_index_good]
|
542
|
+
|
543
|
+
if len(best) > new_threshold:
|
544
|
+
new_threshold += activation_selection_rate
|
545
|
+
pass
|
546
|
+
|
547
|
+
else:
|
548
|
+
break
|
549
|
+
|
550
|
+
return new_weight, new_activations
|
551
|
+
|
552
|
+
def potentiate(best_weight, good_weight, best_activations, good_activations, dtype=np.float32):
|
553
|
+
"""
|
554
|
+
Combines two sets of weights and activation functions by adding the weight matrices and
|
555
|
+
concatenating the activation functions. The resulting weight matrix is normalized. (Max abs normalization.)
|
556
|
+
|
557
|
+
Args:
|
558
|
+
best_weight (numpy.ndarray): The weight matrix of the first individual (parent).
|
559
|
+
good_weight (numpy.ndarray): The weight matrix of the second individual (parent).
|
560
|
+
best_activations (str or list): The activation function(s) of the first individual.
|
561
|
+
good_activations (str or list): The activation function(s) of the second individual.
|
562
|
+
dtype (numpy.dtype): Data type for the arrays. np.float32 by default. Example: np.float64 or np.float16. [fp32 for balanced devices, fp64 for strong devices, fp16 for weak devices: not reccomended!] (optional)
|
563
|
+
|
564
|
+
Returns:
|
565
|
+
tuple: A tuple containing:
|
566
|
+
- new_weight (numpy.ndarray): The new weight matrix after potentiation and normalization. (Max abs normalization.)
|
567
|
+
- new_activations (list): The new activation functions after concatenation.
|
568
|
+
|
569
|
+
Notes:
|
570
|
+
- The weight matrices are element-wise added and then normalized using the `normalization` function. (Max abs normalization.)
|
571
|
+
- The activation functions from both parents are concatenated to form the new activation functions list.
|
572
|
+
- If the activation functions are passed as strings, they are converted to lists for uniform handling.
|
573
|
+
"""
|
574
|
+
|
575
|
+
new_weight = best_weight + good_weight
|
576
|
+
new_weight = normalization(new_weight, dtype=dtype)
|
577
|
+
|
578
|
+
if isinstance(best_activations, str):
|
579
|
+
best = [best_activations]
|
580
|
+
|
581
|
+
if isinstance(good_activations, str):
|
582
|
+
good = [good_activations]
|
583
|
+
|
584
|
+
if isinstance(best_activations, list):
|
585
|
+
best = best_activations
|
586
|
+
|
587
|
+
if isinstance(good_activations, list):
|
588
|
+
good = good_activations
|
589
|
+
|
590
|
+
new_activations = best + good
|
591
|
+
|
592
|
+
return new_weight, new_activations
|
593
|
+
|
594
|
+
def mutation(weight, activations, activation_mutate_prob, activation_add_prob, activation_delete_prob, activation_change_prob, weight_mutate_prob, threshold, dtype=np.float32):
|
595
|
+
"""
|
596
|
+
Performs mutation on the given weight matrix and activation functions.
|
597
|
+
- The weight matrix is mutated by randomly changing its values based on the mutation probability.
|
598
|
+
- The activation functions are mutated by adding, removing, or replacing them with predefined probabilities.
|
599
|
+
|
600
|
+
Args:
|
601
|
+
weight (numpy.ndarray): The weight matrix to mutate.
|
602
|
+
activations (list): The list of activation functions to mutate.
|
603
|
+
activation_mutate_prob (float): The overall probability of mutating activation functions.
|
604
|
+
activation_add_prob (float): Probability of adding a new activation function.
|
605
|
+
activation_delete_prob (float): Probability of removing an existing activation function.
|
606
|
+
activation_change_prob (float): Probability of replacing an existing activation function with a new one.
|
607
|
+
weight_mutate_prob (float): The probability of mutating weight matrix.
|
608
|
+
threshold (float): If the value you enter here is equal to the result of input layer * output layer, only a single weight will be mutated during each mutation process. If the value you enter here is half of the result of input layer * output layer, two weights in the weight matrix will be mutated.
|
609
|
+
dtype (numpy.dtype): Data type for the arrays. np.float32 by default. Example: np.float64 or np.float16. [fp32 for balanced devices, fp64 for strong devices, fp16 for weak devices: not reccomended!] (optional)
|
610
|
+
|
611
|
+
Returns:
|
612
|
+
tuple: A tuple containing:
|
613
|
+
- mutated_weight (numpy.ndarray): The weight matrix after mutation.
|
614
|
+
- mutated_activations (list): The list of activation functions after mutation.
|
615
|
+
|
616
|
+
Notes:
|
617
|
+
- Weight mutation:
|
618
|
+
- Each weight has a chance defined by `weight_mutate_prob` to be altered by adding a random value
|
619
|
+
within the range of [0, 1].
|
620
|
+
- Activation mutation:
|
621
|
+
- If `activation_mutate_prob` is triggered, one or more activation functions can be added, removed,
|
622
|
+
or replaced based on their respective probabilities (`activation_add_prob`, `activation_delete_prob`,
|
623
|
+
`activation_change_prob`).
|
624
|
+
- The mutation probabilities should be chosen carefully to balance exploration and exploitation during
|
625
|
+
the optimization process.
|
626
|
+
"""
|
627
|
+
|
628
|
+
if isinstance(activations, str):
|
629
|
+
activations = [activations]
|
630
|
+
|
631
|
+
weight_mutate_prob = 1 - weight_mutate_prob # if prob 0.8 (%80) then 1 - 0.8. Because 0-1 random number probably greater than 0.2
|
632
|
+
potential_weight_mutation = random.uniform(0, 1)
|
633
|
+
|
634
|
+
if potential_weight_mutation > weight_mutate_prob:
|
635
|
+
|
636
|
+
start = 0
|
637
|
+
row_end = weight.shape[0]
|
638
|
+
col_end = weight.shape[1]
|
639
|
+
new_threshold = threshold
|
640
|
+
|
641
|
+
while True:
|
642
|
+
|
643
|
+
selected_row = int(random.uniform(start, row_end))
|
644
|
+
selected_col = int(random.uniform(start, col_end))
|
645
|
+
|
646
|
+
weight[selected_row, selected_col] = random.uniform(-1, 1)
|
647
|
+
|
648
|
+
if int(row_end * col_end) > new_threshold:
|
649
|
+
new_threshold += threshold
|
650
|
+
pass
|
651
|
+
|
652
|
+
else:
|
653
|
+
break
|
654
|
+
|
655
|
+
|
656
|
+
activation_mutate_prob = 1 - activation_mutate_prob
|
657
|
+
potential_activation_mutation = random.uniform(0, 1)
|
658
|
+
|
659
|
+
if potential_activation_mutation > activation_mutate_prob:
|
660
|
+
|
661
|
+
except_this = ['spiral', 'circular']
|
662
|
+
all_acts = [item for item in all_activations() if item not in except_this] # SPIRAL AND CIRCULAR ACTIVATION DISCARDED
|
663
|
+
|
664
|
+
activation_add_prob = 1 - activation_add_prob
|
665
|
+
activation_delete_prob = 1 - activation_delete_prob
|
666
|
+
activation_change_prob = 1 - activation_change_prob
|
667
|
+
|
668
|
+
potential_activation_add_prob = random.uniform(0, 1)
|
669
|
+
potential_activation_delete_prob = random.uniform(0, 1)
|
670
|
+
potential_activation_change_prob = random.uniform(0, 1)
|
671
|
+
|
672
|
+
if potential_activation_add_prob > activation_add_prob:
|
673
|
+
|
674
|
+
try:
|
675
|
+
|
676
|
+
random_index_all_act = int(random.uniform(0, len(all_acts)-1))
|
677
|
+
activations.append(all_acts[random_index_all_act])
|
678
|
+
|
679
|
+
for i in range(weight.shape[0]):
|
680
|
+
|
681
|
+
weight[i,:] = apply_activation(weight[i,:], activations[-1])
|
682
|
+
|
683
|
+
weight = normalization(weight, dtype=dtype)
|
684
|
+
|
685
|
+
except:
|
686
|
+
|
687
|
+
activation = activations
|
688
|
+
activations = []
|
689
|
+
|
690
|
+
activations.append(activation)
|
691
|
+
activations.append(all_acts[int(random.uniform(0, len(all_acts)-1))])
|
692
|
+
|
693
|
+
for i in range(weight.shape[0]):
|
694
|
+
|
695
|
+
weight[i,:] = apply_activation(weight[i,:], activations[-1])
|
696
|
+
|
697
|
+
weight = normalization(weight, dtype=dtype)
|
698
|
+
|
699
|
+
if potential_activation_delete_prob > activation_delete_prob and len(activations) > 1:
|
700
|
+
|
701
|
+
random_index = random.randint(0, len(activations) - 1)
|
702
|
+
|
703
|
+
wc = np.copy(weight)
|
704
|
+
for i in range(weight.shape[0]):
|
705
|
+
|
706
|
+
wc[i,:] = apply_activation(wc[i,:], activations[random_index])
|
707
|
+
weight[i,:] -= wc[i,:]
|
708
|
+
|
709
|
+
activations.pop(random_index)
|
710
|
+
weight = normalization(weight, dtype=dtype)
|
711
|
+
|
712
|
+
|
713
|
+
if potential_activation_change_prob > activation_change_prob:
|
714
|
+
|
715
|
+
random_index_all_act = int(random.uniform(0, len(all_acts)-1))
|
716
|
+
random_index_genom_act = int(random.uniform(0, len(activations)-1))
|
717
|
+
|
718
|
+
activations[random_index_genom_act] = all_acts[random_index_all_act]
|
719
|
+
|
720
|
+
wc = np.copy(weight)
|
721
|
+
for i in range(weight.shape[0]):
|
722
|
+
|
723
|
+
wc[i,:] = apply_activation(wc[i,:], activations[random_index_genom_act])
|
724
|
+
weight[i,:] -= wc[i,:]
|
725
|
+
|
726
|
+
weight = normalization(weight, dtype=dtype)
|
727
|
+
|
728
|
+
for i in range(weight.shape[0]):
|
729
|
+
|
730
|
+
weight[i,:] = apply_activation(weight[i,:], activations[random_index_genom_act])
|
731
|
+
|
732
|
+
weight = normalization(weight, dtype=dtype)
|
733
|
+
|
734
|
+
return weight, activations
|