From 74e02df2059326559cee32aebffe5a65db9ec885 Mon Sep 17 00:00:00 2001 From: Arity-T Date: Fri, 7 Nov 2025 01:44:59 +0300 Subject: [PATCH] i think i've done this shit RMSE: 0.64 !!!! --- lab4/gp/fitness.py | 36 ++++---------- lab4/gp/mutations.py | 18 +++---- lab4/main.py | 109 +++++++++---------------------------------- 3 files changed, 38 insertions(+), 125 deletions(-) diff --git a/lab4/gp/fitness.py b/lab4/gp/fitness.py index 7c58632..2dd82a1 100644 --- a/lab4/gp/fitness.py +++ b/lab4/gp/fitness.py @@ -77,32 +77,6 @@ class MAEFitness(BaseFitness): return float(np.mean(np.abs(predicted - true_values))) -class HuberFitness(BaseFitness): - """Huber Loss (компромисс между MSE и MAE)""" - - def __init__( - self, - target_fn: TargetFunction, - test_points_fn: TestPointsFn, - delta: float = 1.0, - ): - super().__init__(target_fn, test_points_fn) - self.delta = delta - - def fitness_fn( - self, - chromosome: Chromosome, - predicted: NDArray[np.float64], - true_values: NDArray[np.float64], - ) -> float: - error = predicted - true_values - mask = np.abs(error) <= self.delta - squared = 0.5 * (error[mask] ** 2) - linear = self.delta * (np.abs(error[~mask]) - 0.5 * self.delta) - huber = np.concatenate([squared, linear]) - return float(np.mean(huber)) - - class NRMSEFitness(BaseFitness): """Нормализованный RMSE (масштаб-инвариантен)""" @@ -128,12 +102,18 @@ class PenalizedFitness(BaseFitness): base_fitness: BaseFitness, lambda_: float = 0.001, depth_weight: float = 0.2, + scale_penalty: bool | None = None, ): super().__init__(target_fn, test_points_fn) self.base_fitness = base_fitness self.lambda_ = lambda_ self.depth_weight = depth_weight + # Масштабировать штраф необязательно, если функция фитнеса нормализована + if scale_penalty is None: + scale_penalty = not isinstance(base_fitness, NRMSEFitness) + self.scale_penalty = scale_penalty + def fitness_fn( self, chromosome: Chromosome, @@ -146,4 +126,8 @@ class PenalizedFitness(BaseFitness): depth = chromosome.root.get_depth() penalty = self.lambda_ * (size + self.depth_weight * depth) + + if self.scale_penalty: + penalty *= base + return float(base + penalty) diff --git a/lab4/gp/mutations.py b/lab4/gp/mutations.py index cd125bc..6b47342 100644 --- a/lab4/gp/mutations.py +++ b/lab4/gp/mutations.py @@ -42,29 +42,25 @@ def grow_mutation(chromosome: Chromosome, max_depth: int) -> Chromosome: def node_replacement_mutation(chromosome: Chromosome) -> Chromosome: """Мутация замены операции (Node Replacement Mutation). - Выбирает случайный узел с операцией (arity > 0) и заменяет его - на случайную другую операцию той же арности, сохраняя поддеревья. + Выбирает случайный узел и заменяет его + на случайную другую операцию той же арности или терминал, сохраняя поддеревья. Если подходящей альтернативы нет — возвращает копию без изменений. """ chromosome = chromosome.copy() - 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 = random.choice(chromosome.root.list_nodes()) current_arity = target_node.value.arity - same_arity_ops = [ + same_arity = [ op - for op in chromosome.operations + for op in list(chromosome.operations) + list(chromosome.terminals) if op.arity == current_arity and op != target_node.value ] - if not same_arity_ops: + if not same_arity: return chromosome - new_operation = random.choice(same_arity_ops) + new_operation = random.choice(same_arity) target_node.value = new_operation diff --git a/lab4/main.py b/lab4/main.py index 1dd82e1..69c724b 100644 --- a/lab4/main.py +++ b/lab4/main.py @@ -7,7 +7,6 @@ from numpy.typing import NDArray from gp import Chromosome from gp.crossovers import crossover_subtree from gp.fitness import ( - HuberFitness, MAEFitness, MSEFitness, NRMSEFitness, @@ -26,12 +25,13 @@ from gp.population import ramped_initialization from gp.primitive import Const, Var from gp.selection import roulette_selection, tournament_selection -NUM_VARS = 9 +NUM_VARS = 8 TEST_POINTS = 10000 -MAX_DEPTH = 15 +MAX_DEPTH = 10 MAX_GENERATIONS = 200 -np.random.seed(17) -random.seed(17) +SEED = 17 +np.random.seed(SEED) +random.seed(SEED) 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) @@ -40,25 +40,6 @@ operations = [SQUARE, SIN, COS, EXP, ADD, SUB, MUL, DIV, POW] 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]: """ Векторизованная версия функции: @@ -72,23 +53,16 @@ def target_function(x: NDArray[np.float64]) -> NDArray[np.float64]: 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 = HuberFitness(target_function, lambda: X, delta=0.5) # fitness_function = PenalizedFitness( -# target_function, lambda: X, base_fitness=fitness, lambda_=0.003 +# 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 = PenalizedFitness( -# target_function, lambda: X, base_fitness=fitness, lambda_=0.003 +# target_function, lambda: X, base_fitness=fitness_function, lambda_=0.0001 # ) @@ -98,14 +72,6 @@ def adaptive_mutation( max_generations: int, max_depth: int, ) -> Chromosome: - """Адаптивная мутация. - - Меняет вероятность типов мутации по ходу эволюции: - - Ранняя фаза (<30%): 70% grow, 30% shrink - - Средняя фаза (30–70%): 40% grow, 60% shrink - - Поздняя фаза (>=70%): 20% grow, 80% shrink - """ - r = random.random() if r < 0.4: @@ -118,57 +84,25 @@ def adaptive_mutation( return shrink_mutation(chromosome) -# def adaptive_mutation( -# chromosome: Chromosome, -# generation: int, -# max_generations: int, -# max_depth: int, -# ) -> Chromosome: -# """Адаптивная мутация. - -# Меняет вероятность типов мутации по ходу эволюции: -# - Ранняя фаза (<30%): 70% grow, 30% shrink -# - Средняя фаза (30–70%): 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) +init_population = ramped_initialization( + 20, [i for i in range(MAX_DEPTH - 9, MAX_DEPTH + 1)], terminals, operations +) +print("Population size:", len(init_population)) config = GARunConfig( fitness_func=fitness_function, - crossover_fn=lambda p1, p2: crossover_subtree(p1, p2, max_depth=8), + crossover_fn=lambda p1, p2: crossover_subtree(p1, p2, max_depth=MAX_DEPTH), mutation_fn=lambda chrom, gen_num: adaptive_mutation( chrom, gen_num, MAX_GENERATIONS, MAX_DEPTH ), # selection_fn=roulette_selection, selection_fn=lambda p, f: tournament_selection(p, f, k=3), - init_population=ramped_initialization( - 10, [4, 5, 6, 6, 7, 7, 8, 9, 10, 11], terminals, operations - ), - seed=17, - pc=0.9, - pm=0.3, - elitism=10, + init_population=init_population, + seed=SEED, + pc=0.85, + pm=0.15, + elitism=15, max_generations=MAX_GENERATIONS, log_every_generation=True, ) @@ -182,6 +116,7 @@ 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} мс") +print("Population size:", len(init_population)) mse_fitness = MSEFitness(target_function, lambda: X) print(f"MSE: {mse_fitness(result.best_generation.best):.6f}") @@ -189,7 +124,5 @@ 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}")