Вынес методы инициализации из хромосомы
This commit is contained in:
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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:")
|
||||||
|
|||||||
Reference in New Issue
Block a user