lab1 предварительная версия
This commit is contained in:
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
*
|
||||
|
||||
!**/
|
||||
!*.gitignore
|
||||
!*.py
|
||||
310
lab1/gen.py
Normal file
310
lab1/gen.py
Normal file
@@ -0,0 +1,310 @@
|
||||
import math
|
||||
import os
|
||||
import random
|
||||
import shutil
|
||||
import time
|
||||
from dataclasses import dataclass
|
||||
from typing import Callable, List, Tuple
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
|
||||
|
||||
def target_function(x: float) -> float:
|
||||
"""f(x) = sin(x)/x^2"""
|
||||
return math.sin(x) / (x * x)
|
||||
|
||||
|
||||
def bits_for_precision(x_min: float, x_max: float, digits_after_decimal: int) -> int:
|
||||
"""
|
||||
Подбор числа бит L так, чтобы шаг сетки был ≤ 10^{-digits_after_decimal}.
|
||||
Идея как в методичке: 2^L >= (x_max - x_min)*10^{digits_after_decimal}.
|
||||
"""
|
||||
required_levels = int(math.ceil((x_max - x_min) * (10**digits_after_decimal)))
|
||||
L = 1
|
||||
while (1 << L) < required_levels:
|
||||
L += 1
|
||||
return L
|
||||
|
||||
|
||||
def decode_bits_to_x(bits: List[int], x_min: float, x_max: float) -> float:
|
||||
"""Линейное отображение битовой строки в x ∈ [x_min, x_max]."""
|
||||
v = 0
|
||||
for b in bits:
|
||||
v = (v << 1) | int(b)
|
||||
L = len(bits)
|
||||
levels = (1 << L) - 1
|
||||
return x_min + (x_max - x_min) * (v / levels)
|
||||
|
||||
|
||||
def random_bits(L: int) -> List[int]:
|
||||
return [random.randint(0, 1) for _ in range(L)]
|
||||
|
||||
|
||||
def eval_population(
|
||||
population: List[List[int]],
|
||||
x_min: float,
|
||||
x_max: float,
|
||||
fitness_func: Callable[[float], float],
|
||||
) -> Tuple[List[float], List[float]]:
|
||||
"""Оценка популяции: преобразование в x значения и вычисление фитнес функции."""
|
||||
xs = [decode_bits_to_x(ch, x_min, x_max) for ch in population]
|
||||
fits = [fitness_func(x) for x in xs]
|
||||
return xs, fits
|
||||
|
||||
|
||||
def clear_results_directory(results_dir: str) -> None:
|
||||
"""Очищает папку с результатами перед началом эксперимента."""
|
||||
if os.path.exists(results_dir):
|
||||
shutil.rmtree(results_dir)
|
||||
os.makedirs(results_dir, exist_ok=True)
|
||||
|
||||
|
||||
def reproduction(
|
||||
population: List[List[int]], fitnesses: List[float]
|
||||
) -> List[List[int]]:
|
||||
"""Репродукция (селекция) методом рулетки."""
|
||||
# Чтобы работать с отрицательными f, сдвигаем значения фитнес функции на минимальное
|
||||
# значение в популяции. Вычитаем min_fit, т. к. min_fit может быть отрицательным.
|
||||
min_fit = min(fitnesses)
|
||||
shifted_fitnesses = [f - min_fit + 1e-12 for f in fitnesses]
|
||||
s = sum(shifted_fitnesses)
|
||||
|
||||
probs = [sf / s for sf in shifted_fitnesses]
|
||||
cum = np.cumsum(probs)
|
||||
selected = []
|
||||
for _ in population:
|
||||
r = random.random()
|
||||
idx = int(np.searchsorted(cum, r, side="left"))
|
||||
selected.append(population[idx][:])
|
||||
return selected
|
||||
|
||||
|
||||
def crossover_pair(
|
||||
p1: List[int], p2: List[int], pc: float
|
||||
) -> Tuple[List[int], List[int]]:
|
||||
"""Кроссинговер между двумя хромосомами с вероятностью pc."""
|
||||
if random.random() <= pc:
|
||||
k = random.randint(1, len(p1) - 1)
|
||||
return p1[:k] + p2[k:], p2[:k] + p1[k:]
|
||||
else:
|
||||
return p1[:], p2[:]
|
||||
|
||||
|
||||
def crossover(population: List[List[int]], pc: float) -> List[List[int]]:
|
||||
"""Оператор кроссинговера (скрещивания) выполняется с заданной вероятностью pc.
|
||||
1. Две хромосомы (родители) выбираются случайно из промежуточной популяции.
|
||||
2. Случайно выбирается точка скрещивания - число k из диапазона [1,2,…,n-1],
|
||||
где n – длина хромосомы (число бит в двоичном коде).
|
||||
3. Две новых хромосомы A', B' (потомки) формируются из A и B путем обмена подстрок
|
||||
после точки скрещивания с вероятностью pc. Иначе родители добавляются в новую
|
||||
популяцию без изменений.
|
||||
|
||||
Если популяция нечетного размера, то последняя хромосома скрещивается со случайной
|
||||
другой хромосомой из популяции. В таком случае одна из хромосом может поучаствовать
|
||||
в кроссовере дважды.
|
||||
"""
|
||||
# Создаем копию популяции и перемешиваем её для случайного выбора пар
|
||||
shuffled_population = population[:]
|
||||
random.shuffle(shuffled_population)
|
||||
|
||||
next_population = []
|
||||
pop_size = len(shuffled_population)
|
||||
|
||||
for i in range(0, pop_size, 2):
|
||||
p1 = shuffled_population[i]
|
||||
p2 = shuffled_population[(i + 1) % pop_size]
|
||||
c1, c2 = crossover_pair(p1, p2, pc)
|
||||
next_population.append(c1)
|
||||
next_population.append(c2)
|
||||
|
||||
return next_population[:pop_size]
|
||||
|
||||
|
||||
def mutation(chrom: List[int], pm: float) -> None:
|
||||
"""Мутация происходит с вероятностью pm.
|
||||
1. В хромосоме случайно выбирается k-ая позиция (бит) мутации.
|
||||
2. Производится инверсия значения гена в k-й позиции.
|
||||
"""
|
||||
if random.random() <= pm:
|
||||
k = random.randint(0, len(chrom) - 1)
|
||||
chrom[k] = 1 - chrom[k]
|
||||
|
||||
|
||||
@dataclass
|
||||
class GARunConfig:
|
||||
x_min: float = 3.1
|
||||
x_max: float = 20.0
|
||||
precision_digits: int = 3 # точность сетки ~0.001
|
||||
pop_size: int = 100 # размер популяции
|
||||
pc: float = 0.7 # вероятность кроссинговера
|
||||
pm: float = 0.01 # вероятность мутации
|
||||
max_generations: int = 200 # максимальное количество поколений
|
||||
seed: int | None = None # seed для генератора случайных чисел
|
||||
save_generations: list[int] | None = (
|
||||
None # индексы поколений для сохранения графиков
|
||||
)
|
||||
results_dir: str = "results" # папка для сохранения графиков
|
||||
|
||||
|
||||
@dataclass
|
||||
class GARunResult:
|
||||
best_x: float
|
||||
best_f: float
|
||||
generations: int
|
||||
history_best_x: List[float]
|
||||
history_best_f: List[float]
|
||||
history_populations_x: List[List[float]] # история всех популяций (x значения)
|
||||
history_populations_f: List[List[float]] # история всех популяций (f значения)
|
||||
time_ms: float
|
||||
L: int # число бит
|
||||
|
||||
|
||||
def genetic_algorithm(
|
||||
config: GARunConfig,
|
||||
fitness_func: Callable[[float], float] = target_function,
|
||||
) -> GARunResult:
|
||||
if config.seed is not None:
|
||||
random.seed(config.seed)
|
||||
np.random.seed(config.seed)
|
||||
|
||||
# Очищаем папку результатов если нужно сохранять графики
|
||||
if config.save_generations:
|
||||
clear_results_directory(config.results_dir)
|
||||
|
||||
L = bits_for_precision(config.x_min, config.x_max, config.precision_digits)
|
||||
population = [random_bits(L) for _ in range(config.pop_size)]
|
||||
|
||||
start = time.perf_counter()
|
||||
history_best_x, history_best_f = [], []
|
||||
history_populations_x, history_populations_f = [], []
|
||||
best_x, best_f = 0, -float("inf")
|
||||
|
||||
for generation in range(config.max_generations):
|
||||
xs, fits = eval_population(population, config.x_min, config.x_max, fitness_func)
|
||||
|
||||
# лучший в поколении + глобально лучший
|
||||
gi = int(np.argmax(fits))
|
||||
gen_best_x, gen_best_f = xs[gi], fits[gi]
|
||||
if gen_best_f > best_f:
|
||||
best_x, best_f = gen_best_x, gen_best_f
|
||||
|
||||
history_best_x.append(gen_best_x)
|
||||
history_best_f.append(gen_best_f)
|
||||
history_populations_x.append(xs[:])
|
||||
history_populations_f.append(fits[:])
|
||||
|
||||
# Сохранение графика для указанных поколений
|
||||
if config.save_generations and generation in config.save_generations:
|
||||
plot_generation_snapshot(
|
||||
history_best_x,
|
||||
history_best_f,
|
||||
history_populations_x,
|
||||
history_populations_f,
|
||||
generation,
|
||||
config.x_min,
|
||||
config.x_max,
|
||||
config.results_dir,
|
||||
)
|
||||
|
||||
# селекция
|
||||
parents = reproduction(population, fits)
|
||||
|
||||
# кроссинговер попарно
|
||||
next_population = crossover(parents, config.pc)
|
||||
|
||||
# мутация
|
||||
for ch in next_population:
|
||||
mutation(ch, config.pm)
|
||||
|
||||
population = next_population[: config.pop_size]
|
||||
|
||||
end = time.perf_counter()
|
||||
|
||||
return GARunResult(
|
||||
best_x,
|
||||
best_f,
|
||||
config.max_generations,
|
||||
history_best_x,
|
||||
history_best_f,
|
||||
history_populations_x,
|
||||
history_populations_f,
|
||||
(end - start) * 1000.0,
|
||||
L,
|
||||
)
|
||||
|
||||
|
||||
def plot_generation_snapshot(
|
||||
history_best_x: List[float],
|
||||
history_best_f: List[float],
|
||||
history_populations_x: List[List[float]],
|
||||
history_populations_f: List[List[float]],
|
||||
generation: int,
|
||||
x_min: float,
|
||||
x_max: float,
|
||||
results_dir: str,
|
||||
) -> str:
|
||||
"""
|
||||
График для конкретного поколения с отображением всей популяции.
|
||||
"""
|
||||
os.makedirs(results_dir, exist_ok=True)
|
||||
|
||||
xs = np.linspace(x_min, x_max, 1500)
|
||||
ys = np.sin(xs) / (xs * xs)
|
||||
|
||||
fig = plt.figure(figsize=(10, 6))
|
||||
plt.plot(xs, ys, label="f(x)=sin(x)/x^2", alpha=0.7, color="blue")
|
||||
|
||||
# Отображаем всю популяцию текущего поколения
|
||||
if generation < len(history_populations_x):
|
||||
current_pop_x = history_populations_x[generation]
|
||||
current_pop_f = history_populations_f[generation]
|
||||
|
||||
# Вся популяция серыми точками
|
||||
plt.scatter(
|
||||
current_pop_x,
|
||||
current_pop_f,
|
||||
s=20,
|
||||
alpha=0.9,
|
||||
color="gray",
|
||||
label=f"Популяция поколения {generation}",
|
||||
)
|
||||
|
||||
# Лучшая особь красной точкой
|
||||
best_idx = np.argmax(current_pop_f)
|
||||
plt.scatter(
|
||||
[current_pop_x[best_idx]],
|
||||
[current_pop_f[best_idx]],
|
||||
s=60,
|
||||
color="red",
|
||||
marker="o",
|
||||
label=f"Лучшая особь поколения {generation}",
|
||||
edgecolors="darkred",
|
||||
linewidth=1,
|
||||
)
|
||||
|
||||
# История лучших по поколениям (до текущего включительно)
|
||||
if generation > 0:
|
||||
history_x_until_now = history_best_x[:generation]
|
||||
history_f_until_now = history_best_f[:generation]
|
||||
plt.scatter(
|
||||
history_x_until_now,
|
||||
history_f_until_now,
|
||||
s=15,
|
||||
alpha=0.9,
|
||||
color="orange",
|
||||
label="Лучшие предыдущих поколений",
|
||||
)
|
||||
|
||||
plt.xlabel("x")
|
||||
plt.ylabel("f(x)")
|
||||
plt.title(f"Популяция поколения {generation}")
|
||||
plt.legend(loc="best")
|
||||
plt.grid(True, alpha=0.3)
|
||||
plt.tight_layout()
|
||||
|
||||
filename = f"generation_{generation:03d}.png"
|
||||
path_png = os.path.join(results_dir, filename)
|
||||
plt.savefig(path_png, dpi=150, bbox_inches="tight")
|
||||
plt.close(fig)
|
||||
return path_png
|
||||
49
lab1/main.py
Normal file
49
lab1/main.py
Normal file
@@ -0,0 +1,49 @@
|
||||
import os
|
||||
|
||||
from gen import GARunConfig, genetic_algorithm
|
||||
|
||||
# Запуск эксперимента с генетическим алгоритмом
|
||||
config = GARunConfig(
|
||||
x_min=3.1,
|
||||
x_max=20.0,
|
||||
precision_digits=3,
|
||||
pop_size=5,
|
||||
pc=0.7,
|
||||
pm=0.01,
|
||||
max_generations=200,
|
||||
seed=17,
|
||||
save_generations=[
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
10,
|
||||
25,
|
||||
49,
|
||||
99,
|
||||
150,
|
||||
199,
|
||||
], # поколения для сохранения графиков
|
||||
results_dir="results",
|
||||
)
|
||||
|
||||
# Запускаем генетический алгоритм
|
||||
result = genetic_algorithm(config)
|
||||
|
||||
# Выводим результаты
|
||||
print(f"Лучшее x: {result.best_x:.4f}")
|
||||
print(f"Лучшее f(x): {result.best_f:.6f}")
|
||||
print(f"Количество поколений: {result.generations}")
|
||||
print(f"Время выполнения: {result.time_ms:.2f} мс")
|
||||
print(f"Количество бит: {result.L}")
|
||||
|
||||
# Выводим информацию о сохраненных графиках поколений
|
||||
if config.save_generations:
|
||||
print(
|
||||
f"\nГрафики для поколений {config.save_generations} сохранены в папку '{config.results_dir}/'"
|
||||
)
|
||||
for gen in config.save_generations:
|
||||
if gen < result.generations:
|
||||
filename = f"generation_{gen:03d}.png"
|
||||
filepath = os.path.join(config.results_dir, filename)
|
||||
print(f" - Поколение {gen}: {filepath}")
|
||||
Reference in New Issue
Block a user