@@ -168,6 +168,7 @@ bool netkit::genome::random_mutate() { |
168 | 168 | std::bernoulli_distribution add_neuron_true(m_neat->params.mutation_probs[ADD_NEURON]); |
169 | 169 | std::bernoulli_distribution remove_neuron_true(m_neat->params.mutation_probs[REMOVE_NEURON]); |
170 | 170 | std::bernoulli_distribution add_link_true(m_neat->params.mutation_probs[ADD_LINK]); |
| 171 | + std::bernoulli_distribution add_cascade_true(m_neat->params.mutation_probs[ADD_CASCADE]); |
171 | 172 | if (add_neuron_true(m_neat->rand_engine)) { |
172 | 173 | // let's add a new neuron! |
173 | 174 | return mutate_add_neuron(); |
@@ -177,10 +178,12 @@ bool netkit::genome::random_mutate() { |
177 | 178 | } else if (add_link_true(m_neat->rand_engine)) { |
178 | 179 | // We do not add a new neuron AND a new link at the same time. |
179 | 180 | return mutate_add_link(); |
| 181 | + } else if (add_cascade_true(m_neat->rand_engine)) { |
| 182 | + return mutate_add_cascade(); |
180 | 183 | } else { // We can do any other mutation if there was no structural mutation. |
181 | 184 | // The two first values are the add neuron and add link mutations. |
182 | 185 | std::vector<mutation_t> mutations_to_perform; |
183 | | - mutations_to_perform.reserve(NUMBER_OF_MUTATIONS - 3); |
| 186 | + mutations_to_perform.reserve(NUMBER_OF_MUTATIONS - 4); |
184 | 187 | for (size_t mut_id = 2; mut_id < NUMBER_OF_MUTATIONS; ++mut_id) { |
185 | 188 | std::bernoulli_distribution select_mutation(m_neat->params.mutation_probs[mut_id]); |
186 | 189 | if (select_mutation(m_neat->rand_engine)) { |
@@ -263,7 +266,7 @@ bool netkit::genome::mutate_add_neuron() { |
263 | 266 |
|
264 | 267 | int sel_idx = -1; // selected gene index |
265 | 268 | for (int idx : candidates_idx) { |
266 | | - if (m_genes[idx].enabled) { |
| 269 | + if (m_genes[idx].enabled && !m_genes[idx].frozen) { |
267 | 270 | sel_idx = idx; |
268 | 271 | break; |
269 | 272 | } |
@@ -316,6 +319,43 @@ bool netkit::genome::mutate_add_neuron() { |
316 | 319 | return true; |
317 | 320 | } |
318 | 321 |
|
| 322 | +bool netkit::genome::mutate_add_cascade() { |
| 323 | + for (gene& g : m_genes) { // freeze all existing genes. |
| 324 | + g.frozen = true; |
| 325 | + } |
| 326 | + |
| 327 | + neuron_id_t new_neuron_id = m_neat->innov_pool.next_hidden_neuron_id(); |
| 328 | + |
| 329 | + std::uniform_real_distribution<netkit::neuron_value_t> perturbator(-m_neat->params.initial_weight_perturbation, |
| 330 | + m_neat->params.initial_weight_perturbation); |
| 331 | + |
| 332 | + // connect all hidden neurons to this neuron. |
| 333 | + for (size_t i = m_number_of_outputs + m_number_of_inputs + 1; i < m_known_neuron_ids.size(); ++i) { |
| 334 | + gene new_gene(m_neat->innov_pool.next_innovation(), |
| 335 | + m_known_neuron_ids[i], new_neuron_id, perturbator(m_neat->rand_engine)); |
| 336 | + m_neat->innov_pool.register_gene(new_gene); |
| 337 | + add_gene(new_gene); |
| 338 | + } |
| 339 | + |
| 340 | + // connect all inputs (and the bias) to the new neuron. |
| 341 | + for (size_t i = 0; i < m_number_of_inputs + 1; ++i) { |
| 342 | + gene new_gene(m_neat->innov_pool.next_innovation(), |
| 343 | + m_known_neuron_ids[i], new_neuron_id, perturbator(m_neat->rand_engine)); |
| 344 | + m_neat->innov_pool.register_gene(new_gene); |
| 345 | + add_gene(new_gene); |
| 346 | + } |
| 347 | + |
| 348 | + // connect the new neuron to all outputs. |
| 349 | + for (size_t i = m_number_of_inputs + 1; i < m_number_of_outputs + m_number_of_inputs + 1; ++i) { |
| 350 | + gene new_gene(m_neat->innov_pool.next_innovation(), |
| 351 | + new_neuron_id, m_known_neuron_ids[i], perturbator(m_neat->rand_engine)); |
| 352 | + m_neat->innov_pool.register_gene(new_gene); |
| 353 | + add_gene(new_gene); |
| 354 | + } |
| 355 | + |
| 356 | + return true; |
| 357 | +} |
| 358 | + |
319 | 359 | bool netkit::genome::mutate_remove_neuron() { |
320 | 360 | if (m_known_neuron_ids.size() == m_number_of_inputs + m_number_of_outputs + 1) { |
321 | 361 | return false; |
@@ -336,7 +376,7 @@ bool netkit::genome::mutate_remove_neuron() { |
336 | 376 | bool netkit::genome::mutate_reenable_gene() { |
337 | 377 | std::vector<gene*> candidates; |
338 | 378 | for (gene& g : m_genes) { |
339 | | - if (!g.enabled) { |
| 379 | + if (!g.enabled && !g.frozen) { |
340 | 380 | candidates.push_back(&g); |
341 | 381 | } |
342 | 382 | } |
@@ -351,33 +391,34 @@ bool netkit::genome::mutate_reenable_gene() { |
351 | 391 | } |
352 | 392 |
|
353 | 393 | bool netkit::genome::mutate_toggle_enable() { |
354 | | - if (m_genes.empty()) { |
| 394 | + std::vector<size_t> candidates_idx = helper_generate_candidate_idx(); |
| 395 | + if (candidates_idx.empty()) { |
355 | 396 | return false; |
356 | 397 | } |
357 | 398 |
|
358 | | - std::uniform_int_distribution<size_t> gene_selector(0, m_genes.size() - 1); |
| 399 | + std::uniform_int_distribution<size_t> gene_selector(0, candidates_idx.size() - 1); |
359 | 400 | size_t rnd_val = gene_selector(m_neat->rand_engine); |
360 | | - m_genes[rnd_val].enabled = !m_genes[rnd_val].enabled; |
| 401 | + m_genes[candidates_idx[rnd_val]].enabled = !m_genes[candidates_idx[rnd_val]].enabled; |
361 | 402 |
|
362 | 403 | return true; |
363 | 404 | } |
364 | 405 |
|
365 | 406 | bool netkit::genome::mutate_weights() { |
366 | | - if (m_genes.empty()) { |
| 407 | + std::vector<size_t> candidates_idx = helper_generate_candidate_idx(); |
| 408 | + if (candidates_idx.empty()) { |
367 | 409 | return false; |
368 | 410 | } |
369 | 411 |
|
370 | 412 | std::uniform_real_distribution<netkit::neuron_value_t> perturbator(-m_neat->params.weight_mutation_power, |
371 | 413 | m_neat->params.weight_mutation_power); |
372 | | - std::vector<size_t> candidates_idx(m_genes.size()); |
373 | | - std::iota(candidates_idx.begin(), candidates_idx.end(), 0); |
374 | 414 | std::shuffle(candidates_idx.begin(), candidates_idx.end(), m_neat->rand_engine); |
375 | 415 |
|
376 | 416 | size_t nb_genes = 1; |
377 | | - if (m_genes.size() > 1) { |
378 | | - std::uniform_int_distribution<size_t> nb_genes_selector(0, m_genes.size() - 1); |
| 417 | + if (candidates_idx.size() > 1) { |
| 418 | + std::uniform_int_distribution<size_t> nb_genes_selector(0, candidates_idx.size() - 1); |
379 | 419 | nb_genes = nb_genes_selector(m_neat->rand_engine); |
380 | 420 | } |
| 421 | + |
381 | 422 | for (size_t i = 0; i < nb_genes; ++i) { |
382 | 423 | m_genes[candidates_idx[i]].weight += perturbator(m_neat->rand_engine); |
383 | 424 | } |
@@ -389,19 +430,22 @@ bool netkit::genome::mutate_reset_weights() { |
389 | 430 | std::uniform_real_distribution<netkit::neuron_value_t> perturbator(-m_neat->params.initial_weight_perturbation, |
390 | 431 | m_neat->params.initial_weight_perturbation); |
391 | 432 | for (gene& g : m_genes) { |
392 | | - g.weight = perturbator(m_neat->rand_engine); |
| 433 | + if (!g.frozen) { |
| 434 | + g.weight = perturbator(m_neat->rand_engine); |
| 435 | + } |
393 | 436 | } |
394 | 437 |
|
395 | 438 | return true; |
396 | 439 | } |
397 | 440 |
|
398 | 441 | bool netkit::genome::mutate_remove_gene() { |
399 | | - if (m_genes.empty()) { |
| 442 | + std::vector<size_t> candidates_idx = helper_generate_candidate_idx(); |
| 443 | + if (candidates_idx.empty()) { |
400 | 444 | return false; |
401 | 445 | } |
402 | 446 |
|
403 | | - std::uniform_int_distribution<size_t> gene_selector(0, m_genes.size() - 1); |
404 | | - m_genes.erase(m_genes.begin() + gene_selector(m_neat->rand_engine)); |
| 447 | + std::uniform_int_distribution<size_t> gene_selector(0, candidates_idx.size() - 1); |
| 448 | + m_genes.erase(m_genes.begin() + candidates_idx[gene_selector(m_neat->rand_engine)]); |
405 | 449 | // TODO: check if a neuron goes unknown afterward. /!\ do not remove bias, input and output neurons. |
406 | 450 |
|
407 | 451 | return true; |
@@ -491,6 +535,18 @@ netkit::network netkit::genome::generate_network() const { |
491 | 535 | return std::move(net); |
492 | 536 | } |
493 | 537 |
|
| 538 | +std::vector<size_t> netkit::genome::helper_generate_candidate_idx() { |
| 539 | + std::vector<size_t> candidates_idx; |
| 540 | + candidates_idx.reserve(m_genes.size()); |
| 541 | + for (size_t i = 0; i < m_genes.size(); ++i) { |
| 542 | + if (!m_genes[i].frozen) { |
| 543 | + candidates_idx.emplace_back(i); |
| 544 | + } |
| 545 | + } |
| 546 | + |
| 547 | + return candidates_idx; |
| 548 | +} |
| 549 | + |
494 | 550 | bool netkit::genome::reenable_gene_ok() const { |
495 | 551 | std::bernoulli_distribution distrib(0.25); // TODO: externalize in parameters |
496 | 552 | return distrib(m_neat->rand_engine); |
|