теории добавил
This commit is contained in:
@@ -187,43 +187,352 @@
|
||||
\newpage
|
||||
\section{Теоретические сведения}
|
||||
|
||||
Эволюционные стратегии (ЭС) представляют собой семейство эволюционных алгоритмов, ориентированных на работу в пространстве фенотипов. Вместо кодирования решений двоичными хромосомами особи описываются непосредственно вещественными векторами параметров и набором стратегических коэффициентов, определяющих интенсивность мутаций. Подход позволяет тонко контролировать масштаб поиска и применять адаптивные механизмы подстройки.
|
||||
\subsection{Общие сведения}
|
||||
|
||||
Общая форма особи записывается как $v = (\mathbf{x}, \boldsymbol{\sigma})$, где $\mathbf{x} = (x_1, \ldots, x_n)$ -- точка в пространстве решений, а $\boldsymbol{\sigma} = (\sigma_1, \ldots, \sigma_n)$ -- вектор стандартных отклонений, управляющий величиной мутаций по координатам. Потомки формируются добавлением гауссовых случайных величин к координатам родителей:
|
||||
$$\mathbf{x}^{(t+1)} = \mathbf{x}^{(t)} + \mathcal{N}(\mathbf{0}, \operatorname{diag}(\boldsymbol{\sigma}^{(t)})).$$
|
||||
Эволюционные стратегии (ЭС), также как и генетические алгоритмы, основаны на эволюции популяции потенциальных решений, но, в отличие от них, здесь используются генетические операторы на уровне фенотипа, а не генотипа. Разница в том, что ГА работают в пространстве генотипа --- кодов решений, в то время как ЭС производят поиск в пространстве фенотипа --- векторном пространстве вещественных чисел.
|
||||
|
||||
\subsection{(1+1)-эволюционная стратегия}
|
||||
В ЭС учитываются свойства хромосомы <<в целом>>, в отличие от ГА, где при поиске решений исследуются отдельные гены. В природе один ген может одновременно влиять на несколько свойств организма. С другой стороны, одно свойство особи может определяться несколькими генами. Естественная эволюция основана на исследовании совокупности генов, а не отдельного (изолированного) гена.
|
||||
|
||||
Базовый вариант ЭС использует единственного родителя и одного потомка. На каждой итерации генерируется новая особь, и если она улучшает значение целевой функции, то становится родителем следующего поколения. Иначе родитель сохраняется без изменений. Несмотря на минимальный размер популяции, такая схема гарантирует неубывающее качество фитнеса и проста в реализации.
|
||||
В эволюционных стратегиях целью является движение особей популяции по направлению к лучшей области ландшафта фитнесс-функции. ЭС изначально разработаны для решения многомерных оптимизационных задач, где пространство поиска --- многомерное пространство вещественных чисел.
|
||||
|
||||
Ранние эволюционные стратегии основывались на популяции, состоящей из одной особи, и в них использовался только один генетический оператор --- мутация. Здесь для представления особи (потенциального решения) была использована идея, которая заключается в следующем.
|
||||
|
||||
Особь представляется парой действительных векторов:
|
||||
$$v = (\mathbf{x}, \boldsymbol{\sigma}),$$
|
||||
где $\mathbf{x}$ --- точка в пространстве решений и $\boldsymbol{\sigma}$ --- вектор стандартных отклонений (вариабельность) от решения. В общем случае особь популяции определяется вектором потенциального решения и вектором <<стратегических параметров>> эволюции. Обычно это вектор стандартных отклонений (дисперсия), хотя допускаются и другие статистики.
|
||||
|
||||
Единственным генетическим оператором в классической ЭС является оператор мутации, который выполняется путём сложения координат вектора-родителя со случайными числами, подчиняющимися закону нормального распределения, следующим образом:
|
||||
$$\mathbf{x}^{(t+1)} = \mathbf{x}^{(t)} + \mathcal{N}(\mathbf{0}, \boldsymbol{\sigma}),$$
|
||||
где $\mathcal{N}(\mathbf{0}, \boldsymbol{\sigma})$ --- вектор независимых случайных чисел, генерируемых согласно распределению Гаусса с нулевым средним значением и стандартным отклонением $\boldsymbol{\sigma}$. Как видно из приведённой формулы, величина мутации управляется нетрадиционным способом. Иногда эволюционный процесс используется для изменения и самих стратегических параметров $\boldsymbol{\sigma}$, в этом случае величина мутации эволюционирует вместе с искомым потенциальным решением.
|
||||
|
||||
Интуитивно ясно, что увеличение отклонения подобно увеличению шага поиска на поверхности ландшафта. Высокая вариабельность способствует расширению пространства поиска и эффективна при нахождении потенциальных зон (суб)оптимальных решений и соответствует высоким значениям коэффициента мутации. В то же время малые значения вариабельности позволяют сфокусироваться на поиске решения в перспективной области. Стратегические параметры стохастически определяют величину шага поиска: большая вариабельность ведёт к большим шагам.
|
||||
|
||||
\subsection{Двукратная эволюционная (1+1)-стратегия}
|
||||
|
||||
Здесь потомок принимается в качестве нового члена популяции (он заменяет своего родителя), если значение фитнесс-функции (целевой функции) на нём лучше, чем у его родителя и выполняются все ограничения. Иначе (если значение фитнесс-функции на нём хуже, чем у родителя), потомок уничтожается и популяция остаётся неизменной.
|
||||
|
||||
Алгоритм процесса эволюции двукратной (1+1)-эволюционной стратегии можно сформулировать следующим образом:
|
||||
|
||||
\begin{enumerate}
|
||||
\item Выбрать множество параметров $\mathbf{X}$, необходимых для представления решения данной проблемы, и определить диапазон допустимых изменений каждого параметра: $\{x_1^{min}, x_1^{max}\}, \{x_2^{min}, x_2^{max}\}, \ldots, \{x_P^{min}, x_P^{max}\}$. Установить номер поколения $t=0$; задать стандартное отклонение $\sigma_i$ для каждого параметра, функцию $f$, для которой необходимо найти оптимум, и максимальное число поколений $k$.
|
||||
|
||||
\item Для каждого параметра случайным образом выбрать начальное значение из допустимого диапазона: множество этих значений составляет начальную популяцию (из одной особи) $\mathbf{X}^{(t)} = (x_1, x_2, \ldots, x_P)$.
|
||||
|
||||
\item Вычислить значение оптимизируемой функции $f$ для родительской особи $F_p = f(\mathbf{X}^{(t)})$.
|
||||
|
||||
\item Создать новую особь-потомка: $\mathbf{X}^* = \mathbf{X}^{(t)} + \mathcal{N}(\mathbf{0}, \boldsymbol{\sigma})$.
|
||||
|
||||
\item Вычислить значение $f$ для особи-потомка $F_o = f(\mathbf{X}^*)$.
|
||||
|
||||
\item Сравнить значения функций $f$ для родителя и потомка; если значение потомка $F_o$ лучше, чем у родительской особи, то заменить родителя на потомка $\mathbf{X}^{(t)} = \mathbf{X}^*$, иначе оставить в популяции родителя.
|
||||
|
||||
\item Увеличить номер поколения $t = t + 1$.
|
||||
|
||||
\item Если не достигнуто максимальное число поколений $t < k$, то переход на шаг 4, иначе выдать найденное решение $\mathbf{X}^{(t)}$.
|
||||
\end{enumerate}
|
||||
|
||||
Несмотря на то, что фактически здесь популяция состоит из одной особи, рассмотренная стратегия называется двукратной ЭС. Причина в том, что здесь фактически происходит конкуренция потомка и родителя.
|
||||
|
||||
\subsection{Правило успеха $1/5$}
|
||||
|
||||
Для ускорения сходимости И. Решенберг предложил адаптивное изменение дисперсии мутации. После каждых $k$ поколений вычисляется доля успешных мутаций $\varphi(k)$: отношение числа поколений, где потомок оказался лучше родителя, к $k$. Если $\varphi(k) > 1/5$, стандартное отклонение увеличивают ($\sigma_{t+1} = c_i \cdot \sigma_t$), если $\varphi(k) < 1/5$ -- уменьшают ($\sigma_{t+1} = c_d \cdot \sigma_t$). Обычно выбирают $c_i = 1/0.82$ и $c_d = 0.82$. Таким образом, алгоритм автоматически подстраивает шаг поиска под текущий рельеф функции.
|
||||
Обычно вектор стандартных отклонений $\boldsymbol{\sigma}$ остаётся неизменным в течение всего процесса эволюции. Чтобы оптимизировать скорость сходимости этого процесса, И. Решенберг (основоположник ЭС) предложил правило успеха <<$1/5$>>.
|
||||
|
||||
\subsection{Многократные эволюционные стратегии}
|
||||
Смысл его заключается в следующем --- правило применяется после каждых $k$ поколений процесса (где $k$ --- параметр этого метода):
|
||||
$$\sigma^{(t+1)}_i = \begin{cases}
|
||||
c_i \cdot \sigma^{(t)}_i, & \text{если } \varphi(k) > 1/5, \\
|
||||
\sigma^{(t)}_i, & \text{если } \varphi(k) = 1/5, \\
|
||||
c_d \cdot \sigma^{(t)}_i, & \text{если } \varphi(k) < 1/5,
|
||||
\end{cases}$$
|
||||
где $\varphi(k)$ --- отношение числа успешных мутаций к общему числу произведённых мутаций $k$ (число успехов, делённое на $k$), которое называется коэффициентом успеха для оператора мутации в течение $k$ последних поколений; величина $c_i > 1$, $c_d < 1$ --- регулирует увеличение/уменьшение отклонения мутации.
|
||||
|
||||
Для повышения устойчивости к локальным минимумам используются популяционные варианты: $(\mu+1)$, $(\mu+\lambda)$ и $(\mu, \lambda)$-стратегии. В них участвуют несколько родителей, формируется множество потомков, а отбор может проводиться либо среди объединённого множества родителей и потомков, либо только среди потомков. Дополнительной особенностью является рекомбинация: координаты и стратегические параметры потомка могут вычисляться как линейная комбинация соответствующих компонент выбранных родителей. Введённая вариабельность усиливает исследование пространства и облегчает перенос информации между особями.
|
||||
Обычно на практике оптимальные значения полагают равными следующим величинам: $c_d = 0.82$; $c_i = 1/0.82 = 1.22$. Смысл этого правила в следующем:
|
||||
\begin{itemize}
|
||||
\item если коэффициент успеха $\varphi(k) > 1/5$, то отклонение $\sigma^{(t+1)}$ увеличивается (мы идём более крупными шагами);
|
||||
\item если коэффициент успеха $\varphi(k) < 1/5$, то отклонение $\sigma^{(t+1)}$ уменьшается (шаг поиска уменьшается).
|
||||
\end{itemize}
|
||||
|
||||
Таким образом, алгоритм автоматически подстраивает шаг поиска под текущий рельеф функции.
|
||||
|
||||
\subsection{Многократная эволюционная стратегия}
|
||||
|
||||
По сравнению с двукратной многократная эволюция отличается не только размером популяции ($N > 2$), но и имеет некоторые дополнительные отличия:
|
||||
\begin{itemize}
|
||||
\item все особи в поколении имеют одинаковую вероятность выбора для мутации;
|
||||
\item имеется возможность введения оператора рекомбинации, где два случайно выбранных родителя производят потомка по следующей схеме:
|
||||
$$x_i^{\text{потомок}} = x_i^{q_i}, \quad i = 1, \ldots, n,$$
|
||||
где $q_i = 1$ или $q_i = 2$ (т.е. каждая компонента потомка копируется из первого или второго родителя).
|
||||
\end{itemize}
|
||||
|
||||
В современной литературе используются следующие обозначения:
|
||||
\begin{itemize}
|
||||
\item $(1+1)$-ЭС --- двукратная стратегия (1 родитель производит 1 потомка);
|
||||
\item $(\mu+1)$-ЭС --- многократная стратегия ($\mu$ родителей производят 1 потомка);
|
||||
\item $(\mu+\lambda)$-ЭС --- $\mu$ родителей производят $\lambda$ потомков и отбор $\mu$ лучших представителей производится среди объединённого множества ($\mu + \lambda$ особей) родителей и потомков;
|
||||
\item $(\mu, \lambda)$-ЭС --- $\mu$ особей родителей порождает $\lambda$ потомков, причём $\lambda > \mu$ и процесс выбора $\mu$ лучших производится только на множестве потомков.
|
||||
\end{itemize}
|
||||
|
||||
Следует подчеркнуть, что в обоих последних видах ЭС обычно число потомков существенно больше числа родителей $\lambda > \mu$ (иногда полагают $\lambda/\mu = 7$).
|
||||
|
||||
Многочисленные исследования доказывают, что ЭС не менее эффективно, а часто гораздо лучше справляются с задачами оптимизации в многомерных пространствах, при этом более просты в реализации из-за отсутствия процедур кодирования и декодирования хромосом.
|
||||
|
||||
\newpage
|
||||
\section{Особенности реализации}
|
||||
|
||||
Реализация лабораторной работы расположена в каталоге \texttt{lab5}. Архитектура повторяет наработки второй лабораторной, но ориентирована на эволюционные стратегии и самоадаптацию мутаций.
|
||||
\subsection{Структура модулей}
|
||||
|
||||
\begin{itemize}
|
||||
\item \textbf{Модуль \texttt{functions.py}}: содержит реализацию тестовой функции axis parallel hyper-ellipsoid и вспомогательные генераторы диапазонов. Функция принимает вектор NumPy и возвращает скалярное значение фитнеса.
|
||||
\item \textbf{Модуль \texttt{es.py}}: ядро эволюционной стратегии. Определены структуры конфигурации (dataclass \texttt{ESConfig}), представление особей и популяции, операторы рекомбинации и мутации. Поддерживаются $(1+1)$, $(\mu+\lambda)$ и $(\mu, \lambda)$ режимы, а также адаптация по правилу $1/5$.
|
||||
\item \textbf{Модуль \texttt{experiments.py}}: сценарии серийных экспериментов. Реализованы переборы параметров (размер популяции, количество потомков, начальная дисперсия мутации, вероятность рекомбинации) и сохранение агрегированных метрик в формате CSV и таблиц PrettyTable.
|
||||
\item \textbf{Модуль \texttt{main.py}}: точка входа для интерактивных запусков. Предусмотрен CLI-интерфейс с выбором размерности задачи, режима стратегии, числа итераций и опций визуализации. Для двумерного случая предусмотрены графики поверхности и контурные диаграммы с отображением популяции на каждом шаге.
|
||||
\item \textbf{Модуль \texttt{functions.py}}: содержит реализацию тестовой функции axis parallel hyper-ellipsoid и вспомогательные генераторы диапазонов.
|
||||
\item \textbf{Модуль \texttt{es.py}}: ядро эволюционной стратегии. Определены структуры конфигурации, представление особей и популяции, операторы рекомбинации и мутации.
|
||||
\item \textbf{Модуль \texttt{experiments.py}}: сценарии серийных экспериментов с переборами параметров и сохранением метрик.
|
||||
\item \textbf{Модуль \texttt{main.py}}: точка входа для интерактивных запусков с визуализацией.
|
||||
\end{itemize}
|
||||
|
||||
Для удобства экспериментов в коде определены следующие ключевые сущности.
|
||||
\subsection{Модуль functions.py}
|
||||
|
||||
\begin{itemize}
|
||||
\item \textbf{Самоадаптивная мутация}: функция \texttt{self\_adaptive\_mutation} обновляет как координаты, так и стратегические параметры особи. Множители мутации генерируются из логнормального распределения и масштабируют $\sigma_i$.
|
||||
\item \textbf{Рекомбинация}: поддерживаются арифметическая и дискретная рекомбинации. Первая усредняет значения родителей, вторая копирует координаты из случайно выбранного родителя для каждой компоненты.
|
||||
\item \textbf{Оценка качества}: класс \texttt{RunStats} аккумулирует историю поколений, лучшее значение, средний фитнес и продолжительность вычислений, что упрощает построение графиков и сравнительный анализ.
|
||||
\item \textbf{Визуализация}: модуль \texttt{main.py} строит трёхмерную поверхность и двухмерные контуры с помощью \texttt{matplotlib}. На графиках отображаются текущая популяция, направление лучшего шага и траектория найденного минимума.
|
||||
\end{itemize}
|
||||
Модуль содержит реализацию тестовой функции axis parallel hyper-ellipsoid:
|
||||
|
||||
\begin{lstlisting}[language=Python]
|
||||
def axis_parallel_hyperellipsoid(x: Array) -> float:
|
||||
"""Axis-parallel hyper-ellipsoid benchmark function.
|
||||
|
||||
Parameters:
|
||||
x: Point in R^n
|
||||
|
||||
Returns:
|
||||
The value of the hyper-ellipsoid function
|
||||
"""
|
||||
indices = np.arange(1, x.shape[0] + 1, dtype=np.float64)
|
||||
return float(np.sum(indices * np.square(x)))
|
||||
\end{lstlisting}
|
||||
|
||||
Функция принимает вектор NumPy произвольной размерности и возвращает скалярное значение фитнеса. Для двумерного случая формула принимает вид $f(x_1, x_2) = x_1^2 + 2x_2^2$, для трёхмерного $f(x_1, x_2, x_3) = x_1^2 + 2x_2^2 + 3x_3^2$.
|
||||
|
||||
Также определена вспомогательная функция для генерации симметричных границ:
|
||||
|
||||
\begin{lstlisting}[language=Python]
|
||||
def default_bounds(dimension: int,
|
||||
lower: float = -5.12,
|
||||
upper: float = 5.12) -> tuple[Array, Array]:
|
||||
"""Construct symmetric bounds for each dimension."""
|
||||
x_min = np.full(dimension, lower, dtype=np.float64)
|
||||
x_max = np.full(dimension, upper, dtype=np.float64)
|
||||
return x_min, x_max
|
||||
\end{lstlisting}
|
||||
|
||||
\subsection{Модуль es.py}
|
||||
|
||||
\subsubsection{Структуры данных}
|
||||
|
||||
Особь представлена классом \texttt{Individual}, содержащим координаты решения, стратегические параметры и фитнес:
|
||||
|
||||
\begin{lstlisting}[language=Python]
|
||||
@dataclass
|
||||
class Individual:
|
||||
"""Single individual of the evolution strategy population."""
|
||||
x: Array # Coordinates in solution space
|
||||
sigma: Array # Standard deviations for mutation
|
||||
fitness: float # Fitness value
|
||||
|
||||
def copy(self) -> "Individual":
|
||||
return Individual(self.x.copy(),
|
||||
self.sigma.copy(),
|
||||
float(self.fitness))
|
||||
\end{lstlisting}
|
||||
|
||||
Конфигурация эволюционной стратегии задаётся через \texttt{EvolutionStrategyConfig}:
|
||||
|
||||
\begin{lstlisting}[language=Python]
|
||||
@dataclass
|
||||
class EvolutionStrategyConfig:
|
||||
fitness_func: FitnessFn
|
||||
dimension: int
|
||||
x_min: Array
|
||||
x_max: Array
|
||||
mu: int # Number of parents
|
||||
lambda_: int # Number of offspring
|
||||
mutation_probability: float
|
||||
initial_sigma: Array | float
|
||||
max_generations: int
|
||||
selection: Literal["plus", "comma"] = "comma"
|
||||
recombination: Literal["intermediate", "discrete",
|
||||
"none"] = "intermediate"
|
||||
success_rule_window: int = 10
|
||||
success_rule_target: float = 0.2
|
||||
sigma_increase: float = 1.22
|
||||
sigma_decrease: float = 0.82
|
||||
# ... other parameters
|
||||
\end{lstlisting}
|
||||
|
||||
\subsubsection{Рекомбинация}
|
||||
|
||||
Функция \texttt{recombine} реализует выбор родителей и создание базового вектора для потомка:
|
||||
|
||||
\begin{lstlisting}[language=Python]
|
||||
def recombine(parents: Sequence[Individual],
|
||||
config: EvolutionStrategyConfig) -> tuple[Array, Array, float]:
|
||||
"""Recombine parent individuals before mutation.
|
||||
|
||||
Returns:
|
||||
Base vector, sigma and the best parent fitness
|
||||
"""
|
||||
if config.recombination == "none":
|
||||
parent = random.choice(parents)
|
||||
return parent.x.copy(), parent.sigma.copy(), parent.fitness
|
||||
|
||||
selected = random.choices(parents,
|
||||
k=config.parents_per_offspring)
|
||||
|
||||
if config.recombination == "intermediate":
|
||||
x = np.mean([p.x for p in selected], axis=0)
|
||||
sigma = np.mean([p.sigma for p in selected], axis=0)
|
||||
elif config.recombination == "discrete":
|
||||
mask = np.random.randint(0, len(selected),
|
||||
size=config.dimension)
|
||||
x = np.array([selected[mask[i]].x[i]
|
||||
for i in range(config.dimension)])
|
||||
sigma = np.array([selected[mask[i]].sigma[i]
|
||||
for i in range(config.dimension)])
|
||||
|
||||
parent_fitness = min(p.fitness for p in selected)
|
||||
return x, sigma, parent_fitness
|
||||
\end{lstlisting}
|
||||
|
||||
Промежуточная рекомбинация усредняет координаты родителей, дискретная копирует каждую координату из случайно выбранного родителя.
|
||||
|
||||
\subsubsection{Мутация}
|
||||
|
||||
Оператор мутации использует логнормальное распределение для адаптации стратегических параметров:
|
||||
|
||||
\begin{lstlisting}[language=Python]
|
||||
def mutate(x: Array, sigma: Array,
|
||||
config: EvolutionStrategyConfig,
|
||||
sigma_scale: float) -> tuple[Array, Array]:
|
||||
"""Apply log-normal mutation with optional
|
||||
per-coordinate masking."""
|
||||
global_noise = np.random.normal()
|
||||
coordinate_noise = np.random.normal(size=config.dimension)
|
||||
|
||||
# Adapt sigma using log-normal distribution
|
||||
sigma_new = sigma * np.exp(config.tau_prime * global_noise +
|
||||
config.tau * coordinate_noise)
|
||||
sigma_new = np.clip(sigma_new * sigma_scale,
|
||||
config.sigma_min, config.sigma_max)
|
||||
|
||||
# Apply mutation steps
|
||||
steps = np.random.normal(size=config.dimension) * sigma_new
|
||||
|
||||
# Optional per-coordinate mutation probability
|
||||
if config.mutation_probability < 1.0:
|
||||
mask = np.random.random(config.dimension) < \
|
||||
config.mutation_probability
|
||||
if not np.any(mask):
|
||||
mask[np.random.randint(0, config.dimension)] = True
|
||||
steps = steps * mask
|
||||
sigma_new = np.where(mask, sigma_new, sigma)
|
||||
|
||||
x_new = np.clip(x + steps, config.x_min, config.x_max)
|
||||
return x_new, sigma_new
|
||||
\end{lstlisting}
|
||||
|
||||
Параметры $\tau$ и $\tau'$ вычисляются как $\tau = 1/\sqrt{2\sqrt{n}}$ и $\tau' = 1/\sqrt{2n}$, где $n$ --- размерность задачи.
|
||||
|
||||
\subsubsection{Создание потомков}
|
||||
|
||||
Функция \texttt{create\_offspring} генерирует $\lambda$ потомков и отслеживает успешные мутации:
|
||||
|
||||
\begin{lstlisting}[language=Python]
|
||||
def create_offspring(parents: Sequence[Individual],
|
||||
config: EvolutionStrategyConfig,
|
||||
sigma_scale: float) -> tuple[list[Individual],
|
||||
list[bool]]:
|
||||
"""Create offspring and track successful mutations."""
|
||||
offspring: list[Individual] = []
|
||||
successes: list[bool] = []
|
||||
|
||||
for _ in range(config.lambda_):
|
||||
base_x, base_sigma, best_parent_fitness = \
|
||||
recombine(parents, config)
|
||||
mutated_x, mutated_sigma = \
|
||||
mutate(base_x, base_sigma, config, sigma_scale)
|
||||
fitness = float(config.fitness_func(mutated_x))
|
||||
child = Individual(mutated_x, mutated_sigma, fitness)
|
||||
offspring.append(child)
|
||||
successes.append(fitness < best_parent_fitness)
|
||||
|
||||
return offspring, successes
|
||||
\end{lstlisting}
|
||||
|
||||
\subsubsection{Селекция}
|
||||
|
||||
Отбор следующего поколения производится согласно выбранной стратегии:
|
||||
|
||||
\begin{lstlisting}[language=Python]
|
||||
def select_next_generation(parents: list[Individual],
|
||||
offspring: list[Individual],
|
||||
config: EvolutionStrategyConfig) -> list[Individual]:
|
||||
"""Select next generation according to the strategy."""
|
||||
if config.selection == "plus":
|
||||
pool = parents + offspring # (mu + lambda)-strategy
|
||||
else:
|
||||
pool = offspring # (mu, lambda)-strategy
|
||||
|
||||
pool.sort(key=lambda ind: ind.fitness)
|
||||
next_generation = [ind.copy() for ind in pool[:config.mu]]
|
||||
return next_generation
|
||||
\end{lstlisting}
|
||||
|
||||
\subsection{Главная функция алгоритма}
|
||||
|
||||
Функция \texttt{run\_evolution\_strategy} реализует основной цикл эволюционной стратегии с адаптацией по правилу успеха $1/5$:
|
||||
|
||||
\begin{lstlisting}[language=Python]
|
||||
def run_evolution_strategy(config: EvolutionStrategyConfig) -> EvolutionStrategyResult:
|
||||
"""Main evolution strategy loop with 1/5 success rule."""
|
||||
# Initialize random seed
|
||||
if config.seed is not None:
|
||||
random.seed(config.seed)
|
||||
np.random.seed(config.seed)
|
||||
|
||||
# Initialize population
|
||||
parents = [Individual(
|
||||
np.random.uniform(config.x_min, config.x_max),
|
||||
config.make_initial_sigma(),
|
||||
0.0
|
||||
) for _ in range(config.mu)]
|
||||
evaluate_population(parents, config.fitness_func)
|
||||
|
||||
sigma_scale = 1.0
|
||||
success_window: deque[float] = deque()
|
||||
|
||||
for generation_number in range(1, config.max_generations + 1):
|
||||
# Create offspring and track successes
|
||||
offspring, successes = create_offspring(parents, config,
|
||||
sigma_scale)
|
||||
success_ratio = sum(successes) / len(successes)
|
||||
success_window.append(success_ratio)
|
||||
|
||||
# Apply 1/5 success rule
|
||||
if len(success_window) == config.success_rule_window:
|
||||
average_success = sum(success_window) / \
|
||||
len(success_window)
|
||||
if average_success > config.success_rule_target:
|
||||
sigma_scale = min(sigma_scale * config.sigma_increase,
|
||||
config.sigma_scale_max)
|
||||
elif average_success < config.success_rule_target:
|
||||
sigma_scale = max(sigma_scale * config.sigma_decrease,
|
||||
config.sigma_scale_min)
|
||||
success_window.clear()
|
||||
|
||||
# Select next generation
|
||||
parents = select_next_generation(parents, offspring, config)
|
||||
|
||||
# Check stopping criteria
|
||||
# ...
|
||||
|
||||
return EvolutionStrategyResult(...)
|
||||
\end{lstlisting}
|
||||
|
||||
Правило успеха $1/5$ применяется каждые $k$ поколений (по умолчанию $k=5$): если доля успешных мутаций выше $1/5$, масштаб $\sigma$ увеличивается в $1.22$ раза, если ниже --- уменьшается в $0.82$ раза.
|
||||
|
||||
\newpage
|
||||
\section{Результаты работы}
|
||||
|
||||
Reference in New Issue
Block a user