Files
genetic-algorithms/lab4/main.py
2025-11-06 22:50:10 +03:00

186 lines
6.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import random
from math import log
import numpy as np
from numpy.typing import NDArray
from gp import Chromosome
from gp.crossovers import crossover_subtree
from gp.fitness import (
HuberFitness,
MAEFitness,
MSEFitness,
NRMSEFitness,
PenalizedFitness,
RMSEFitness,
)
from gp.ga import GARunConfig, genetic_algorithm
from gp.mutations import grow_mutation, shrink_mutation
from gp.ops import ADD, COS, DIV, EXP, MUL, NEG, POW, SIN, SQUARE, SUB
from gp.population import ramped_initialization
from gp.primitive import Const, Var
from gp.selection import roulette_selection
NUM_VARS = 9
TEST_POINTS = 10000
MAX_DEPTH = 13
MAX_GENERATIONS = 500
np.random.seed(17)
random.seed(17)
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)]
# X = np.array(np.meshgrid(*axes)).T.reshape(-1, NUM_VARS)
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)]
# def target_function(x: NDArray[np.float64]) -> NDArray[np.float64]:
# """
# f(x) = x1 + x2 + sin(x1)
# x имеет форму (n_samples, n_vars)
# """
# x1 = x[:, 0]
# x2 = x[:, 1]
# return x1 + x2 + np.sin(x1) + np.sin(x2) + np.exp(-x1) + np.cos(x2)
# def target_function(x: NDArray[np.float64]) -> NDArray[np.float64]:
# """
# Простая тестовая функция: сумма косинусов всех переменных.
# f(x) = sum_i cos(x_i)
# x имеет форму (n_samples, n_vars)
# """
# return np.sum(x, axis=1)
def target_function(x: NDArray[np.float64]) -> NDArray[np.float64]:
"""
Векторизованная версия функции:
f(x) = sum_{i=1}^n sum_{j=1}^i x_j^2
x имеет форму (n_samples, n_vars)
"""
# Префиксные суммы квадратов по оси переменных
x_sq = x**2
prefix_sums = np.cumsum(x_sq, axis=1)
# Суммируем по i (ось 1)
return np.sum(prefix_sums, axis=1)
# def target_function(x: NDArray[np.float64]) -> NDArray[np.float64]:
# """
# Rastrigin function.
# f(x) = 10 * n + sum(x_i^2 - 10 * cos(2πx_i))
# x: shape (n_samples, n_vars)
# """
# n = x.shape[1]
# return 10 * n + np.sum(x**2 - 10 * np.cos(2 * np.pi * x), axis=1)
# fitness_function = MSEFitness(target_function, lambda: X)
# fitness = HuberFitness(target_function, lambda: X, delta=1.0)
# fitness_function = PenalizedFitness(
# target_function, lambda: X, base_fitness=fitness, lambda_=0.003
# )
fitness_function = HuberFitness(target_function, lambda: X)
# fitness_function = PenalizedFitness(
# target_function, lambda: X, base_fitness=fitness, lambda_=0.003
# )
def adaptive_mutation(
chromosome: Chromosome,
generation: int,
max_generations: int,
max_depth: int,
) -> Chromosome:
"""Адаптивная мутация.
Меняет вероятность типов мутации по ходу эволюции:
- Ранняя фаза (<30%): 70% grow, 30% shrink
- Средняя фаза (3070%): 40% grow, 60% shrink
- Поздняя фаза (>=70%): 20% grow, 80% shrink
"""
r = random.random()
# 50% grow, 50% shrink
if r < 0.5:
return grow_mutation(chromosome, max_depth=max_depth)
return shrink_mutation(chromosome)
# def adaptive_mutation(
# chromosome: Chromosome,
# generation: int,
# max_generations: int,
# max_depth: int,
# ) -> Chromosome:
# """Адаптивная мутация.
# Меняет вероятность типов мутации по ходу эволюции:
# - Ранняя фаза (<30%): 70% grow, 30% shrink
# - Средняя фаза (3070%): 40% grow, 60% shrink
# - Поздняя фаза (>=70%): 20% grow, 80% shrink
# """
# # Вычисляем прогресс в диапазоне [0, 1]
# if max_generations <= 0:
# progress = 0.0
# else:
# progress = min(1.0, max(0.0, generation / max_generations))
# r = random.random()
# # Определяем тип мутации
# if progress < 0.3:
# do_grow = r < 0.7
# elif progress < 0.7:
# do_grow = r < 0.4
# else:
# do_grow = r < 0.2
# # Выполняем выбранную мутацию
# if do_grow:
# return grow_mutation(chromosome, max_depth=max_depth)
# return shrink_mutation(chromosome)
config = GARunConfig(
fitness_func=fitness_function,
crossover_fn=lambda p1, p2: crossover_subtree(p1, p2, max_depth=8),
mutation_fn=lambda chrom, gen_num: adaptive_mutation(
chrom, gen_num, MAX_GENERATIONS, MAX_DEPTH
),
selection_fn=roulette_selection,
init_population=ramped_initialization(
15, [4, 5, 6, 6, 7, 7, 8, 9, 10, 11], terminals, operations
),
seed=17,
pc=0.9,
pm=0.3,
elitism=30,
max_generations=MAX_GENERATIONS,
log_every_generation=True,
)
result = genetic_algorithm(config)
# Выводим результаты
print(f"Лучшая особь: {result.best_generation.best}")
print(result.best_generation.best.root.to_str_tree())
print(f"Лучшее значение фитнеса: {result.best_generation.best_fitness:.6f}")
print(f"Количество поколений: {result.generations_count}")
print(f"Время выполнения: {result.time_ms:.2f} мс")
mse_fitness = MSEFitness(target_function, lambda: X)
print(f"MSE: {mse_fitness(result.best_generation.best):.6f}")
rmse_fitness = RMSEFitness(target_function, lambda: X)
print(f"RMSE: {rmse_fitness(result.best_generation.best):.6f}")
mae_fitness = MAEFitness(target_function, lambda: X)
print(f"MAE: {mae_fitness(result.best_generation.best):.6f}")
huber_fitness = HuberFitness(target_function, lambda: X, delta=1.0)
print(f"Huber: {huber_fitness(result.best_generation.best):.6f}")
nrmse_fitness = NRMSEFitness(target_function, lambda: X)
print(f"NRMSE: {nrmse_fitness(result.best_generation.best):.6f}")