mutation
This commit is contained in:
@@ -17,7 +17,7 @@ type FitnessFn = Callable[[Chromosome], float]
|
|||||||
|
|
||||||
type InitializePopulationFn = Callable[[int], Population]
|
type InitializePopulationFn = Callable[[int], Population]
|
||||||
type CrossoverFn = Callable[[Chromosome, Chromosome], tuple[Chromosome, Chromosome]]
|
type CrossoverFn = Callable[[Chromosome, Chromosome], tuple[Chromosome, Chromosome]]
|
||||||
type MutationFn = Callable[[Chromosome, int], Chromosome]
|
type MutationFn = Callable[[Chromosome], Chromosome]
|
||||||
type SelectionFn = Callable[[Population, Fitnesses], Population]
|
type SelectionFn = Callable[[Population, Fitnesses], Population]
|
||||||
|
|
||||||
|
|
||||||
@@ -132,7 +132,7 @@ def mutation(
|
|||||||
next_population = []
|
next_population = []
|
||||||
for chrom in population:
|
for chrom in population:
|
||||||
next_population.append(
|
next_population.append(
|
||||||
mutation_fn(chrom, gen_num) if np.random.random() <= pm else chrom
|
mutation_fn(chrom) if np.random.random() <= pm else chrom
|
||||||
)
|
)
|
||||||
return next_population
|
return next_population
|
||||||
|
|
||||||
|
|||||||
@@ -1,45 +1,58 @@
|
|||||||
import random
|
import random
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from typing import Sequence
|
||||||
|
|
||||||
from .chromosome import Chromosome
|
from .chromosome import Chromosome
|
||||||
|
|
||||||
|
|
||||||
def shrink_mutation(chromosome: Chromosome) -> Chromosome:
|
class BaseMutation(ABC):
|
||||||
|
@abstractmethod
|
||||||
|
def mutate(self, chromosome: Chromosome) -> Chromosome: ...
|
||||||
|
def __call__(self, chromosome: Chromosome) -> Chromosome:
|
||||||
|
chromosome = chromosome.copy()
|
||||||
|
return self.mutate(chromosome)
|
||||||
|
|
||||||
|
|
||||||
|
class ShrinkMutation(BaseMutation):
|
||||||
"""Усекающая мутация. Заменяет случайно выбранную операцию на случайный терминал."""
|
"""Усекающая мутация. Заменяет случайно выбранную операцию на случайный терминал."""
|
||||||
chromosome = chromosome.copy()
|
|
||||||
|
|
||||||
operation_nodes = [n for n in chromosome.root.list_nodes() if n.value.arity > 0]
|
def mutate(self, chromosome: Chromosome) -> Chromosome:
|
||||||
|
operation_nodes = [n for n in chromosome.root.list_nodes() if n.value.arity > 0]
|
||||||
|
|
||||||
|
if not operation_nodes:
|
||||||
|
return chromosome
|
||||||
|
|
||||||
|
target_node = random.choice(operation_nodes)
|
||||||
|
|
||||||
|
target_node.prune(chromosome.terminals, max_depth=1)
|
||||||
|
|
||||||
if not operation_nodes:
|
|
||||||
return chromosome
|
return chromosome
|
||||||
|
|
||||||
target_node = random.choice(operation_nodes)
|
|
||||||
|
|
||||||
target_node.prune(chromosome.terminals, max_depth=1)
|
class GrowMutation(BaseMutation):
|
||||||
|
|
||||||
return chromosome
|
|
||||||
|
|
||||||
|
|
||||||
def grow_mutation(chromosome: Chromosome, max_depth: int) -> Chromosome:
|
|
||||||
"""Растущая мутация. Заменяет случайно выбранный узел на случайное поддерево."""
|
"""Растущая мутация. Заменяет случайно выбранный узел на случайное поддерево."""
|
||||||
chromosome = chromosome.copy()
|
|
||||||
|
|
||||||
target_node = random.choice(chromosome.root.list_nodes())
|
def __init__(self, max_depth: int):
|
||||||
|
self.max_depth = max_depth
|
||||||
|
|
||||||
max_subtree_depth = max_depth - target_node.get_level() + 1
|
def mutate(self, chromosome: Chromosome) -> Chromosome:
|
||||||
|
target_node = random.choice(chromosome.root.list_nodes())
|
||||||
|
|
||||||
subtree = Chromosome.grow_init(
|
max_subtree_depth = self.max_depth - target_node.get_level() + 1
|
||||||
chromosome.terminals, chromosome.operations, max_subtree_depth
|
|
||||||
).root
|
|
||||||
|
|
||||||
if target_node.parent:
|
subtree = Chromosome.grow_init(
|
||||||
target_node.parent.replace_child(target_node, subtree)
|
chromosome.terminals, chromosome.operations, max_subtree_depth
|
||||||
else:
|
).root
|
||||||
chromosome.root = subtree
|
|
||||||
|
|
||||||
return chromosome
|
if target_node.parent:
|
||||||
|
target_node.parent.replace_child(target_node, subtree)
|
||||||
|
else:
|
||||||
|
chromosome.root = subtree
|
||||||
|
|
||||||
|
return chromosome
|
||||||
|
|
||||||
|
|
||||||
def node_replacement_mutation(chromosome: Chromosome) -> Chromosome:
|
class NodeReplacementMutation(BaseMutation):
|
||||||
"""Мутация замены операции (Node Replacement Mutation).
|
"""Мутация замены операции (Node Replacement Mutation).
|
||||||
|
|
||||||
Выбирает случайный узел и заменяет его
|
Выбирает случайный узел и заменяет его
|
||||||
@@ -47,48 +60,72 @@ def node_replacement_mutation(chromosome: Chromosome) -> Chromosome:
|
|||||||
|
|
||||||
Если подходящей альтернативы нет — возвращает копию без изменений.
|
Если подходящей альтернативы нет — возвращает копию без изменений.
|
||||||
"""
|
"""
|
||||||
chromosome = chromosome.copy()
|
|
||||||
|
|
||||||
target_node = random.choice(chromosome.root.list_nodes())
|
def mutate(self, chromosome: Chromosome) -> Chromosome:
|
||||||
current_arity = target_node.value.arity
|
target_node = random.choice(chromosome.root.list_nodes())
|
||||||
|
current_arity = target_node.value.arity
|
||||||
|
|
||||||
|
same_arity = [
|
||||||
|
op
|
||||||
|
for op in list(chromosome.operations) + list(chromosome.terminals)
|
||||||
|
if op.arity == current_arity and op != target_node.value
|
||||||
|
]
|
||||||
|
if not same_arity:
|
||||||
|
return chromosome
|
||||||
|
|
||||||
|
new_operation = random.choice(same_arity)
|
||||||
|
|
||||||
|
target_node.value = new_operation
|
||||||
|
|
||||||
same_arity = [
|
|
||||||
op
|
|
||||||
for op in list(chromosome.operations) + list(chromosome.terminals)
|
|
||||||
if op.arity == current_arity and op != target_node.value
|
|
||||||
]
|
|
||||||
if not same_arity:
|
|
||||||
return chromosome
|
return chromosome
|
||||||
|
|
||||||
new_operation = random.choice(same_arity)
|
|
||||||
|
|
||||||
target_node.value = new_operation
|
class HoistMutation(BaseMutation):
|
||||||
|
def mutate(self, chromosome: Chromosome) -> Chromosome:
|
||||||
|
"""Hoist-мутация (анти-bloat).
|
||||||
|
|
||||||
return chromosome
|
Выбирает случайное поддерево, затем внутри него — случайное поддерево меньшей
|
||||||
|
глубины, и заменяет исходное поддерево на это внутреннее.
|
||||||
|
|
||||||
|
В результате дерево становится короче, сохраняя часть структуры.
|
||||||
|
"""
|
||||||
|
operation_nodes = [n for n in chromosome.root.list_nodes() if n.value.arity > 0]
|
||||||
|
if not operation_nodes:
|
||||||
|
return chromosome
|
||||||
|
|
||||||
|
outer_subtree = random.choice(operation_nodes)
|
||||||
|
outer_nodes = outer_subtree.list_nodes()[1:] # исключаем корень
|
||||||
|
|
||||||
|
inner_subtree = random.choice(outer_nodes).copy_subtree()
|
||||||
|
|
||||||
|
if outer_subtree.parent:
|
||||||
|
outer_subtree.parent.replace_child(outer_subtree, inner_subtree)
|
||||||
|
else:
|
||||||
|
chromosome.root = inner_subtree
|
||||||
|
|
||||||
|
return chromosome
|
||||||
|
|
||||||
|
|
||||||
def hoist_mutation(chromosome: Chromosome) -> Chromosome:
|
class CombinedMutation(BaseMutation):
|
||||||
"""Hoist-мутация (анти-bloat).
|
"""Комбинированная мутация.
|
||||||
|
|
||||||
Выбирает случайное поддерево, затем внутри него — случайное поддерево меньшей глубины,
|
Принимает список (или словарь) мутаций и случайно выбирает одну из них
|
||||||
и заменяет исходное поддерево на это внутреннее.
|
для применения. Можно задать веса вероятностей.
|
||||||
|
|
||||||
В результате дерево становится короче, сохраняя часть структуры.
|
|
||||||
"""
|
"""
|
||||||
chromosome = chromosome.copy()
|
|
||||||
|
|
||||||
operation_nodes = [n for n in chromosome.root.list_nodes() if n.value.arity > 0]
|
def __init__(
|
||||||
if not operation_nodes:
|
self, mutations: Sequence[BaseMutation], probs: Sequence[float] | None = None
|
||||||
return chromosome
|
):
|
||||||
|
if probs is not None:
|
||||||
|
assert abs(sum(probs) - 1.0) < 1e-8, (
|
||||||
|
"Сумма вероятностей должна быть равна 1"
|
||||||
|
)
|
||||||
|
assert len(probs) == len(mutations), (
|
||||||
|
"Число вероятностей должно совпадать с числом мутаций"
|
||||||
|
)
|
||||||
|
self.mutations = mutations
|
||||||
|
self.probs = probs
|
||||||
|
|
||||||
outer_subtree = random.choice(operation_nodes)
|
def mutate(self, chromosome: Chromosome) -> Chromosome:
|
||||||
outer_nodes = outer_subtree.list_nodes()[1:] # исключаем корень
|
mutation = random.choices(self.mutations, weights=self.probs, k=1)[0]
|
||||||
|
return mutation(chromosome)
|
||||||
inner_subtree = random.choice(outer_nodes).copy_subtree()
|
|
||||||
|
|
||||||
if outer_subtree.parent:
|
|
||||||
outer_subtree.parent.replace_child(outer_subtree, inner_subtree)
|
|
||||||
else:
|
|
||||||
chromosome.root = inner_subtree
|
|
||||||
|
|
||||||
return chromosome
|
|
||||||
|
|||||||
52
lab4/main.py
52
lab4/main.py
@@ -15,10 +15,11 @@ from gp.fitness import (
|
|||||||
)
|
)
|
||||||
from gp.ga import GARunConfig, genetic_algorithm
|
from gp.ga import GARunConfig, genetic_algorithm
|
||||||
from gp.mutations import (
|
from gp.mutations import (
|
||||||
grow_mutation,
|
CombinedMutation,
|
||||||
hoist_mutation,
|
GrowMutation,
|
||||||
node_replacement_mutation,
|
HoistMutation,
|
||||||
shrink_mutation,
|
NodeReplacementMutation,
|
||||||
|
ShrinkMutation,
|
||||||
)
|
)
|
||||||
from gp.ops import ADD, COS, DIV, EXP, MUL, NEG, POW, SIN, SQUARE, SUB
|
from gp.ops import ADD, COS, DIV, EXP, MUL, NEG, POW, SIN, SQUARE, SUB
|
||||||
from gp.population import ramped_initialization
|
from gp.population import ramped_initialization
|
||||||
@@ -36,7 +37,6 @@ X = np.random.uniform(-5.536, 5.536, size=(TEST_POINTS, NUM_VARS))
|
|||||||
# axes = [np.linspace(-5.536, 5.536, TEST_POINTS) for _ in range(NUM_VARS)]
|
# axes = [np.linspace(-5.536, 5.536, TEST_POINTS) for _ in range(NUM_VARS)]
|
||||||
# X = np.array(np.meshgrid(*axes)).T.reshape(-1, NUM_VARS)
|
# X = np.array(np.meshgrid(*axes)).T.reshape(-1, NUM_VARS)
|
||||||
operations = [SQUARE, SIN, COS, EXP, ADD, SUB, MUL, DIV, POW]
|
operations = [SQUARE, SIN, COS, EXP, ADD, SUB, MUL, DIV, POW]
|
||||||
# operations = [SQUARE, ADD, SUB, MUL]
|
|
||||||
terminals = [Var(f"x{i}") for i in range(1, NUM_VARS + 1)]
|
terminals = [Var(f"x{i}") for i in range(1, NUM_VARS + 1)]
|
||||||
|
|
||||||
|
|
||||||
@@ -53,36 +53,16 @@ def target_function(x: NDArray[np.float64]) -> NDArray[np.float64]:
|
|||||||
return np.sum(prefix_sums, axis=1)
|
return np.sum(prefix_sums, axis=1)
|
||||||
|
|
||||||
|
|
||||||
# fitness_function = MSEFitness(target_function, lambda: X)
|
|
||||||
# fitness_function = HuberFitness(target_function, lambda: X, delta=0.5)
|
|
||||||
# fitness_function = PenalizedFitness(
|
|
||||||
# target_function, lambda: X, base_fitness=fitness, lambda_=0.1
|
|
||||||
# )
|
|
||||||
# fitness_function = NRMSEFitness(target_function, lambda: X)
|
|
||||||
fitness_function = RMSEFitness(target_function, lambda: X)
|
fitness_function = RMSEFitness(target_function, lambda: X)
|
||||||
|
combined_mutation = CombinedMutation(
|
||||||
# fitness_function = PenalizedFitness(
|
mutations=[
|
||||||
# target_function, lambda: X, base_fitness=fitness_function, lambda_=0.0001
|
GrowMutation(max_depth=MAX_DEPTH),
|
||||||
# )
|
NodeReplacementMutation(),
|
||||||
|
HoistMutation(),
|
||||||
|
ShrinkMutation(),
|
||||||
def adaptive_mutation(
|
],
|
||||||
chromosome: Chromosome,
|
probs=[0.4, 0.3, 0.15, 0.15],
|
||||||
generation: int,
|
)
|
||||||
max_generations: int,
|
|
||||||
max_depth: int,
|
|
||||||
) -> Chromosome:
|
|
||||||
r = random.random()
|
|
||||||
|
|
||||||
if r < 0.4:
|
|
||||||
return grow_mutation(chromosome, max_depth=max_depth)
|
|
||||||
elif r < 0.7:
|
|
||||||
return node_replacement_mutation(chromosome)
|
|
||||||
elif r < 0.85:
|
|
||||||
return hoist_mutation(chromosome)
|
|
||||||
|
|
||||||
return shrink_mutation(chromosome)
|
|
||||||
|
|
||||||
|
|
||||||
init_population = ramped_initialization(
|
init_population = ramped_initialization(
|
||||||
20, [i for i in range(MAX_DEPTH - 9, MAX_DEPTH + 1)], terminals, operations
|
20, [i for i in range(MAX_DEPTH - 9, MAX_DEPTH + 1)], terminals, operations
|
||||||
@@ -93,9 +73,7 @@ print("Population size:", len(init_population))
|
|||||||
config = GARunConfig(
|
config = GARunConfig(
|
||||||
fitness_func=fitness_function,
|
fitness_func=fitness_function,
|
||||||
crossover_fn=lambda p1, p2: crossover_subtree(p1, p2, max_depth=MAX_DEPTH),
|
crossover_fn=lambda p1, p2: crossover_subtree(p1, p2, max_depth=MAX_DEPTH),
|
||||||
mutation_fn=lambda chrom, gen_num: adaptive_mutation(
|
mutation_fn=combined_mutation,
|
||||||
chrom, gen_num, MAX_GENERATIONS, MAX_DEPTH
|
|
||||||
),
|
|
||||||
# selection_fn=roulette_selection,
|
# selection_fn=roulette_selection,
|
||||||
selection_fn=lambda p, f: tournament_selection(p, f, k=3),
|
selection_fn=lambda p, f: tournament_selection(p, f, k=3),
|
||||||
init_population=init_population,
|
init_population=init_population,
|
||||||
|
|||||||
Reference in New Issue
Block a user