Вынес методы инициализации из хромосомы

This commit is contained in:
2025-10-21 18:14:20 +03:00
parent afd7a700ca
commit 83be98e923
3 changed files with 84 additions and 73 deletions

View File

@@ -1,10 +1,10 @@
import random import random
from typing import Literal, Sequence from typing import Callable, Sequence
from .operation import Operation from .operation import Operation
from .terminal import Terminal from .terminal import Terminal
type InitMethod = Literal["full", "grow"] type InitFunc = Callable[["Chromosome"], "Chromosome.Node"]
class Chromosome: class Chromosome:
@@ -12,76 +12,21 @@ class Chromosome:
self, self,
operations: Sequence[Operation], operations: Sequence[Operation],
terminals: Sequence[Terminal], terminals: Sequence[Terminal],
max_depth: int, init_func: InitFunc,
init_method: InitMethod,
terminal_probability: float = 0.5,
): ):
self.operations = operations self.operations = operations
self.terminals = terminals self.terminals = terminals
self.root = ( self.root = init_func(self)
self._init_full(max_depth)
if init_method == "full"
else self._init_grow(max_depth, terminal_probability)
)
self.depth = self._compute_depth(self.root)
def _compute_depth(self, node: "Chromosome.Node") -> int: def get_depth(self) -> int:
"""Вычисляет глубину дерева. Дерево из одного только корня имеет глубину 1.""" """Вычисляет глубину дерева. Дерево из одного только корня имеет глубину 1."""
return ( return self.root.get_depth() if self.root is not None else 0
max(self._compute_depth(child) for child in node.children) + 1
if node.children
else 1
)
def _random_terminal(self) -> Terminal:
return random.choice(self.terminals)
def _init_full(self, max_depth: int) -> "Chromosome.Node":
"""Полная инициализация.
В полном методе при генерации дерева, пока не достигнута максимальная глубина,
допускается выбор только функциональных символов, а на последнем уровне
(максимальной глубины) выбираются только терминальные символы.
"""
def build(level: int) -> Chromosome.Node:
# Если достигнута максимальная глубина — выбираем терминал
if level == max_depth:
return Chromosome.Node(self._random_terminal(), [])
# Иначе выбираем операцию и создаём потомков
op = random.choice(self.operations)
node = Chromosome.Node(op, [build(level + 1) for _ in range(op.arity)])
return node
return build(1)
def _init_grow(
self, max_depth: int, terminal_probability: float
) -> "Chromosome.Node":
"""Растущая инициализация.
В растущей инициализации генерируются нерегулярные деревья с различной глубиной
листьев вследствие случайного на каждом шаге выбора функционального
или терминального символа. Здесь при выборе терминального символа рост дерева
прекращается по текущей ветви и поэтому дерево имеет нерегулярную структуру.
"""
def build(level: int) -> Chromosome.Node:
# Если достигнута максимальная глубина, либо сыграла заданная вероятность
# — выбираем терминал
if level == max_depth or random.random() < terminal_probability:
return Chromosome.Node(self._random_terminal(), [])
# Иначе выбираем случайную операцию и создаём потомков
op = random.choice(self.operations)
children = [build(level + 1) for _ in range(op.arity)]
return Chromosome.Node(op, children)
return build(1)
def eval(self, values: list[float]) -> float: def eval(self, values: list[float]) -> float:
"""Вычисляет значение хромосомы для заданных значений терминалов.""" """Вычисляет значение хромосомы для заданных значений терминалов."""
if self.root is None:
raise ValueError("Chromosome is not initialized")
for terminal, value in zip(self.terminals, values): for terminal, value in zip(self.terminals, values):
terminal._value = value terminal._value = value
@@ -104,6 +49,9 @@ class Chromosome:
def str_tree(self) -> str: def str_tree(self) -> str:
"""Строковое представление древовидной структуры формулы.""" """Строковое представление древовидной структуры формулы."""
if self.root is None:
return ""
lines = [self.root.value.name] lines = [self.root.value.name]
for i, child in enumerate(self.root.children): for i, child in enumerate(self.root.children):
last = i == len(self.root.children) - 1 last = i == len(self.root.children) - 1
@@ -117,6 +65,14 @@ class Chromosome:
self.value = value self.value = value
self.children = children self.children = children
def get_depth(self) -> int:
"""Вычисляет глубину поддерева."""
return (
max(child.get_depth() for child in self.children) + 1
if self.children
else 1
)
def __str__(self) -> str: def __str__(self) -> str:
"""Рекурсивный перевод древовидного вида формулы в строку в инфиксной форме.""" """Рекурсивный перевод древовидного вида формулы в строку в инфиксной форме."""
if isinstance(self.value, Terminal): if isinstance(self.value, Terminal):
@@ -136,3 +92,53 @@ class Chromosome:
return self.value._value # type: ignore return self.value._value # type: ignore
return self.value._eval([child._eval() for child in self.children]) return self.value._eval([child._eval() for child in self.children])
def _random_terminal(terminals: Sequence[Terminal]) -> Terminal:
return random.choice(terminals)
def init_full(chromosome: Chromosome, max_depth: int) -> Chromosome.Node:
"""Полная инициализация.
В полном методе при генерации дерева, пока не достигнута максимальная глубина,
допускается выбор только функциональных символов, а на последнем уровне
(максимальной глубины) выбираются только терминальные символы.
"""
def build(level: int) -> Chromosome.Node:
# Если достигнута максимальная глубина — выбираем терминал
if level == max_depth:
return Chromosome.Node(_random_terminal(chromosome.terminals), [])
# Иначе выбираем операцию и создаём потомков
op = random.choice(chromosome.operations)
node = Chromosome.Node(op, [build(level + 1) for _ in range(op.arity)])
return node
return build(1)
def init_grow(
chromosome: Chromosome, max_depth: int, terminal_probability: float = 0.5
) -> Chromosome.Node:
"""Растущая инициализация.
В растущей инициализации генерируются нерегулярные деревья с различной глубиной
листьев вследствие случайного на каждом шаге выбора функционального
или терминального символа. Здесь при выборе терминального символа рост дерева
прекращается по текущей ветви и поэтому дерево имеет нерегулярную структуру.
"""
def build(level: int) -> Chromosome.Node:
# Если достигнута максимальная глубина, либо сыграла заданная вероятность
# — выбираем терминал
if level == max_depth or random.random() < terminal_probability:
return Chromosome.Node(_random_terminal(chromosome.terminals), [])
# Иначе выбираем случайную операцию и создаём потомков
op = random.choice(chromosome.operations)
children = [build(level + 1) for _ in range(op.arity)]
return Chromosome.Node(op, children)
return build(1)

View File

@@ -1,7 +1,7 @@
import random import random
from typing import Callable from typing import Callable
from .chromosome import Chromosome, InitMethod from .chromosome import Chromosome, InitFunc, init_full, init_grow
type Population = list[Chromosome] type Population = list[Chromosome]
@@ -9,7 +9,7 @@ type Population = list[Chromosome]
def ramped_initialization( def ramped_initialization(
population_size: int, population_size: int,
depths: list[int], depths: list[int],
make_chromosome: Callable[[int, InitMethod], Chromosome], # (max_depth, method) make_chromosome: Callable[[InitFunc], Chromosome],
) -> Population: ) -> Population:
"""Комбинация методов grow и full инициализации хромосом для инициализации начальной """Комбинация методов grow и full инициализации хромосом для инициализации начальной
популяции. популяции.
@@ -25,14 +25,18 @@ def ramped_initialization(
n_full = int(per_depth / 2) n_full = int(per_depth / 2)
n_grow = int(per_depth / 2) n_grow = int(per_depth / 2)
population.extend(make_chromosome(depth, "full") for _ in range(n_full)) population.extend(
population.extend(make_chromosome(depth, "grow") for _ in range(n_grow)) make_chromosome(lambda c: init_full(c, depth)) for _ in range(n_full)
)
population.extend(
make_chromosome(lambda c: init_grow(c, depth)) for _ in range(n_grow)
)
# Из-за округления хромосом может оказаться меньше заданного количества, # Из-за округления хромосом может оказаться меньше заданного количества,
# поэтому дозаполняем остаток популяции случайными хромосомами # поэтому дозаполняем остаток популяции случайными хромосомами
while len(population) < population_size: while len(population) < population_size:
depth = random.choice(depths) depth = random.choice(depths)
method = "full" if random.random() < 0.5 else "grow" init_func = init_full if random.random() < 0.5 else init_grow
population.append(make_chromosome(depth, method)) population.append(make_chromosome(lambda c: init_func(c, depth)))
return population return population

View File

@@ -1,12 +1,13 @@
from gp import Chromosome, Terminal, ops from gp import Chromosome, Terminal, ops
from gp.chromosome import init_grow
from gp.population import ramped_initialization from gp.population import ramped_initialization
operations = ops.ALL operations = ops.ALL
terminals = [Terminal(f"x{i}") for i in range(1, 9)] terminals = [Terminal(f"x{i}") for i in range(1, 9)]
chrom = Chromosome(operations, terminals, 8, "grow") chrom = Chromosome(operations, terminals, init_func=lambda c: init_grow(c, 8))
print("Depth:", chrom.depth) print("Depth:", chrom.get_depth())
print("Formula:", chrom) print("Formula:", chrom)
print("Tree:\n", chrom.str_tree()) print("Tree:\n", chrom.str_tree())
@@ -16,7 +17,7 @@ print("Value for ", values, ":", chrom.eval(values))
population = ramped_initialization( population = ramped_initialization(
100, 100,
[3, 4, 5, 6, 7, 8], [3, 4, 5, 6, 7, 8],
lambda depth, method: Chromosome(operations, terminals, depth, method), lambda init_func: Chromosome(operations, terminals, init_func),
) )
print("Population size:", len(population)) print("Population size:", len(population))
print("Population:") print("Population:")