diff --git a/lab4/report/.gitignore b/lab4/report/.gitignore new file mode 100644 index 0000000..38887f8 --- /dev/null +++ b/lab4/report/.gitignore @@ -0,0 +1,6 @@ +* + +!**/ +!.gitignore +!report.tex +!img/**/*.png \ No newline at end of file diff --git a/lab4/report/img/best_tree.png b/lab4/report/img/best_tree.png new file mode 100644 index 0000000..374021b Binary files /dev/null and b/lab4/report/img/best_tree.png differ diff --git a/lab4/report/img/original_tree.png b/lab4/report/img/original_tree.png new file mode 100644 index 0000000..76a3663 Binary files /dev/null and b/lab4/report/img/original_tree.png differ diff --git a/lab4/report/img/results/fitness_avg.png b/lab4/report/img/results/fitness_avg.png new file mode 100644 index 0000000..f66ac0e Binary files /dev/null and b/lab4/report/img/results/fitness_avg.png differ diff --git a/lab4/report/img/results/fitness_best.png b/lab4/report/img/results/fitness_best.png new file mode 100644 index 0000000..e8145c7 Binary files /dev/null and b/lab4/report/img/results/fitness_best.png differ diff --git a/lab4/report/img/results/generation_001.png b/lab4/report/img/results/generation_001.png new file mode 100644 index 0000000..a92f2ad Binary files /dev/null and b/lab4/report/img/results/generation_001.png differ diff --git a/lab4/report/img/results/generation_010.png b/lab4/report/img/results/generation_010.png new file mode 100644 index 0000000..d5d1bac Binary files /dev/null and b/lab4/report/img/results/generation_010.png differ diff --git a/lab4/report/img/results/generation_020.png b/lab4/report/img/results/generation_020.png new file mode 100644 index 0000000..a1310b9 Binary files /dev/null and b/lab4/report/img/results/generation_020.png differ diff --git a/lab4/report/img/results/generation_030.png b/lab4/report/img/results/generation_030.png new file mode 100644 index 0000000..c08df1b Binary files /dev/null and b/lab4/report/img/results/generation_030.png differ diff --git a/lab4/report/img/results/generation_040.png b/lab4/report/img/results/generation_040.png new file mode 100644 index 0000000..46bcb0c Binary files /dev/null and b/lab4/report/img/results/generation_040.png differ diff --git a/lab4/report/img/results/generation_050.png b/lab4/report/img/results/generation_050.png new file mode 100644 index 0000000..3e66a71 Binary files /dev/null and b/lab4/report/img/results/generation_050.png differ diff --git a/lab4/report/img/results/generation_100.png b/lab4/report/img/results/generation_100.png new file mode 100644 index 0000000..0b75df2 Binary files /dev/null and b/lab4/report/img/results/generation_100.png differ diff --git a/lab4/report/img/results/generation_150.png b/lab4/report/img/results/generation_150.png new file mode 100644 index 0000000..9fe044c Binary files /dev/null and b/lab4/report/img/results/generation_150.png differ diff --git a/lab4/report/img/results/generation_200.png b/lab4/report/img/results/generation_200.png new file mode 100644 index 0000000..374021b Binary files /dev/null and b/lab4/report/img/results/generation_200.png differ diff --git a/lab4/report/report.tex b/lab4/report/report.tex new file mode 100644 index 0000000..516bd0f --- /dev/null +++ b/lab4/report/report.tex @@ -0,0 +1,705 @@ +\documentclass[a4paper, final]{article} +%\usepackage{literat} % Нормальные шрифты +\usepackage[14pt]{extsizes} % для того чтобы задать нестандартный 14-ый размер шрифта +\usepackage{tabularx} +\usepackage{booktabs} +\usepackage[T2A]{fontenc} +\usepackage[utf8]{inputenc} +\usepackage[russian]{babel} +\usepackage{amsmath} +\usepackage[left=25mm, top=20mm, right=20mm, bottom=20mm, footskip=10mm]{geometry} +\usepackage{ragged2e} %для растягивания по ширине +\usepackage{setspace} %для межстрочно го интервала +\usepackage{moreverb} %для работы с листингами +\usepackage{indentfirst} % для абзацного отступа +\usepackage{moreverb} %для печати в листинге исходного кода программ +\usepackage{pdfpages} %для вставки других pdf файлов +\usepackage{tikz} +\usepackage{graphicx} +\usepackage{afterpage} +\usepackage{longtable} +\usepackage{float} +\usepackage{xcolor} + + + + +% \usepackage[paper=A4,DIV=12]{typearea} +\usepackage{pdflscape} +% \usepackage{lscape} + +\usepackage{array} +\usepackage{multirow} + +\renewcommand\verbatimtabsize{4\relax} +\renewcommand\listingoffset{0.2em} %отступ от номеров строк в листинге +\renewcommand{\arraystretch}{1.4} % изменяю высоту строки в таблице +\usepackage[font=small, singlelinecheck=false, justification=centering, format=plain, labelsep=period]{caption} %для настройки заголовка таблицы +\usepackage{listings} %листинги +\usepackage{xcolor} % цвета +\usepackage{hyperref}% для гиперссылок +\usepackage{enumitem} %для перечислений + +\newcommand{\specialcell}[2][l]{\begin{tabular}[#1]{@{}l@{}}#2\end{tabular}} + + +\setlist[enumerate,itemize]{leftmargin=1.2cm} %отступ в перечислениях + +\hypersetup{colorlinks, + allcolors=[RGB]{010 090 200}} %красивые гиперссылки (не красные) + +% подгружаемые языки — подробнее в документации listings (это всё для листингов) +% включаем кириллицу и добавляем кое−какие опции +\lstset{tabsize=2, + breaklines, + basicstyle=\footnotesize, + columns=fullflexible, + flexiblecolumns, + numbers=left, + numberstyle={\footnotesize}, + keywordstyle=\color{blue}, + inputencoding=cp1251, + extendedchars=true +} + +\textheight=24cm % высота текста +\textwidth=16cm % ширина текста +\oddsidemargin=0pt % отступ от левого края +\topmargin=-1.5cm % отступ от верхнего края +\parindent=24pt % абзацный отступ +\parskip=5pt % интервал между абзацами +\tolerance=2000 % терпимость к "жидким" строкам +\flushbottom % выравнивание высоты страниц + + +% Настройка листингов +\lstset{ + language=python, + extendedchars=\true, + inputencoding=utf8, + keepspaces=true, + % captionpos=b, % подписи листингов снизу +} + +\begin{document} % начало документа + + + + % НАЧАЛО ТИТУЛЬНОГО ЛИСТА + \begin{center} + \hfill \break + \hfill \break + \normalsize{МИНИСТЕРСТВО НАУКИ И ВЫСШЕГО ОБРАЗОВАНИЯ РОССИЙСКОЙ ФЕДЕРАЦИИ\\ + федеральное государственное автономное образовательное учреждение высшего образования «Санкт-Петербургский политехнический университет Петра Великого»\\[10pt]} + \normalsize{Институт компьютерных наук и кибербезопасности}\\[10pt] + \normalsize{Высшая школа технологий искусственного интеллекта}\\[10pt] + \normalsize{Направление: 02.03.01 <<Математика и компьютерные науки>>}\\ + + \hfill \break + \hfill \break + \hfill \break + \hfill \break + \large{Лабораторная работа №4}\\ + \large{по дисциплине}\\ + \large{<<Генетические алгоритмы>>}\\ + \large{Вариант 18}\\ + + % \hfill \break + \hfill \break + \end{center} + + \small{ + \begin{tabular}{lrrl} + \!\!\!Студент, & \hspace{2cm} & & \\ + \!\!\!группы 5130201/20101 & \hspace{2cm} & \underline{\hspace{3cm}} &Тищенко А. А. \\\\ + \!\!\!Преподаватель & \hspace{2cm} & \underline{\hspace{3cm}} & Большаков А. А. \\\\ + &&\hspace{4cm} + \end{tabular} + \begin{flushright} + <<\underline{\hspace{1cm}}>>\underline{\hspace{2.5cm}} 2025г. + \end{flushright} + } + + \hfill \break + % \hfill \break + \begin{center} \small{Санкт-Петербург, 2025} \end{center} + \thispagestyle{empty} % выключаем отображение номера для этой страницы + + % КОНЕЦ ТИТУЛЬНОГО ЛИСТА + \newpage + + \tableofcontents + + \newpage + \section {Постановка задачи} + В данной работе были поставлены следующие задачи: + + \begin{itemize} + \item Разработать эволюционный алгоритм, реализующий ГП для нахождения заданной по варианту функции. + \begin{itemize} + \item Структура для представления программы – древовидное представление. + \item Терминальное множество: переменные $x_1, x_2, x_3, \ldots, x_n$, и константы в соответствии с заданием по варианту. + \item Функциональное множество: $+$, $-$, $*$, $/$, $abs()$, $sin()$, $cos()$, $exp()$, возведение в степень. + \item Фитнесс-функция – мера близости между реальными значениями выхода и требуемыми. + \end{itemize} + \item Представить графически найденное решение на каждой итерации. + \item Сравнить найденное решение с представленным в условии задачи. + \end{itemize} + + \textbf{Индивидуальное задание вариант 18:} + + \textbf{Дано:} Функция + + $$f(x) = \sum_{i=1}^{n} \sum_{j=1}^{i} x_j^2, \text{ где } x_j \in [-5.536, 5.536] \text{ для всех } j = 1, \ldots, n, \text{ а } n = 8.$$ + + + \newpage + \section{Теоретические сведения} + + \subsection{Генетическое программирование} + + \textbf{Генетическое программирование} (ГП) — разновидность эволюционных алгоритмов, в которых особь представляет собой программу, автоматически создаваемую для решения задачи. В отличие от генетических алгоритмов с фиксированной структурой хромосом, в ГП особи имеют переменную длину, что требует специальных методов кодирования, инициализации и генетических операторов. Ключевая идея ГП — представление программы на высоком уровне абстракции с учётом структуры компьютерных программ. + + Оценка программ выполняется с помощью фитнесс-функции, отражающей степень соответствия решения требованиям задачи. Обычно используются метрики ошибки: среднеквадратичная ошибка, абсолютная ошибка или другие функции рассогласования между вычисленным и ожидаемым значением. Чем ниже ошибка, тем выше приспособленность особи. + + \subsection{Терминальное и функциональное множества} + + Программы формируются из \textbf{переменных}, \textbf{констант} и \textbf{функций}, связанных синтаксическими правилами. Для их описания необходимо определить два базовых множества: + \begin{itemize} + \item \textbf{Терминальное множество}, включающее константы и переменные. + \item \textbf{Функциональное множество}, состоящее из операторов и элементарных функций, таких как \( \exp(x) \), \( \sin(x) \) и других. + \end{itemize} + + \subsubsection{Терминальное множество} + Терминальное множество включает: + \begin{enumerate} + \item Внешние входы программы. + \item Константы, используемые в программе. + \item Функции без аргументов. + \end{enumerate} + + Термин «терминал» используется потому, что эти элементы соответствуют концевым (висячим) узлам в древовидных структурах и терминалам формальных грамматик. Терминал предоставляет численное значение, не требуя входных аргументов, то есть имеет нулевую арность. В классическом ГП на основе деревьев множество числовых констант выбирается для всей популяции и остается неизменным. + + \subsubsection{Функциональное множество} + + Функциональное множество состоит из операторов и различных функций. Оно может быть очень широким и включать типичные конструкции языков программирования, такие как: + + \begin{itemize} + \item Логические функции: AND, OR, NOT; + \item Арифметические операции: $+$, $-$, $\times$, $\div$; + \item Трансцендентные функции: $\sin$, $\cos$, $\tan$, $\log$; + \item Операции присваивания: $a := 2$; + \item Условные операторы: if-then-else, switch/case; + \item Операторы переходов: go to, jump, call; + \item Операторы циклов: while, repeat-until, for; + \item Подпрограммы и пользовательские функции. + \end{itemize} + + \subsection{Виды представления программ. Древовидное представление} + + Среди наиболее распространённых структур для представления особей (потенциальных решений) в современном генетическом программировании можно выделить: + + \begin{enumerate} + \item \textbf{Древовидное представление} — классический подход, где программы представляются в виде деревьев с операторами в узлах и терминалами в листьях + + \item \textbf{Линейная структура} — программы записываются как последовательности инструкций, аналогично ассемблерному коду + + \item \textbf{Графоподобная структура} — расширенное представление, допускающее множественные связи и переиспользование компонентов + \end{enumerate} + + Древовидная форма представления является классической для ГП. Программа представляется в виде дерева, где внутренние узлы — это функции из функционального множества, а листья (терминальные узлы) — это переменные и константы из терминального + множества. Такая структура позволяет гибко работать с выражениями различной длины + и сложности + + \subsection{Инициализация древовидных структур} + + Сложность древовидных структур оценивается через максимальную глубину дерева $D_m$ или общее количество узлов. Процесс инициализации древовидных структур основан на случайном выборе функциональных и терминальных символов при заданном ограничении максимальной глубины. Рассмотрим пример с терминальным множеством: + + Существуют два основных метода инициализации: + + \subsubsection*{Полный метод (full)} + + На всех уровнях, кроме последнего, выбираются только функциональные символы. Терминальные символы размещаются исключительно на уровне максимальной глубины $D_m$. Это гарантирует создание сбалансированных деревьев регулярной структуры. + + \subsubsection*{Растущий метод (grow)} + На каждом шаге случайным образом выбирается либо функциональный, либо терминальный символ. Выбор терминала прекращает рост ветви, что приводит к формированию нерегулярных деревьев с различной глубиной листьев. + + \subsection{Оператор кроссинговера на древовидных структурах} + + Для древовидной формы представления программ в генетическом программировании применяются три основных типа операторов кроссинговера: + + \begin{enumerate}[label=\alph*)] + \item Узловой ОК + \item Кроссинговер поддеревьев + \item Смешанный + \end{enumerate} + + \subsubsection{Узловой оператор кроссинговера} + + В узловом операторе кроссинговера выбираются два родителя (два дерева) и внутри них — узлы. Первый родитель называется доминантом, второй — рецессивом. Узлы могут различаться по типу, поэтому сначала необходимо проверить, что выбранные узлы взаимозаменяемы. Если типы не совпадают, выбирается другой узел во втором родителе, и проверка повторяется. После этого осуществляется обмен выбранных узлов между деревьями. + + \subsubsection{Кроссинговер поддеревьев} + + В кроссинговере поддеревьев не происходит обмен отдельными узлами, а определяется обмен поддеревьями. Он осуществляется следующим образом: + + \begin{enumerate} + \item Выбираются два родителя (\textit{один — доминантный, другой — рецессивный}). Необходимо убедиться, что выбранные узлы взаимозаменяемы, то есть принадлежат одному типу. В противном случае выбирается другой узел в рецессивном дереве. + \item Производится обмен соответствующими поддеревьями. + \item Далее вычисляется предполагаемый размер потомков. Если он не превышает установленный порог, то обмен ветвями запоминается. + \end{enumerate} + + При смешанном операторе кроссинговера для некоторых узлов выполняется узловой ОК, а для других - кроссинговер поддеревьев. В целом ОК выполняется следующим образом: + + \begin{enumerate} + \item Выбор точек скрещивания \( P_1, P_2 \) в обоих родителях + \item Выбор типа кроссинговера с заданной вероятностью: + \begin{itemize} + \item Первый тип (обмен подграфами) с вероятностью \( P_G \) + \item Второй тип (линейный обмен) с вероятностью \( 1 - P_G \) + \end{itemize} + \item Если выбран первый тип и размер потомка не превышает порог, выполняется кроссинговер подграфами + \item Если выбран второй тип и размер потомка не превышает порог, выполняется линейный кроссинговер + \end{enumerate} + + \subsection{Мутационные операторы для древовидных структур} + + В контексте древовидного представления программ применяются следующие мутационные операторы: + + \begin{enumerate}[label=\alph*)] + \item Мутация узлов (узловая) + \item Мутация с усечением (усекающая) + \item Мутация с ростом (растущая) + \item Hoist-мутация + \end{enumerate} + + \textbf{Процедура узловой мутации} включает следующие шаги: + + \begin{enumerate} + \item Случайный выбор целевого узла в дереве программы и идентификация его типа + \item Случайный выбор заменяющего узла того же типа из соответствующего множества (функционального или терминального) + \item Замена исходного узла на выбранный вариант + \end{enumerate} + + \textbf{Алгоритм усекающей мутации} реализуется следующим образом: + + \begin{enumerate} + \item Выбор узла, который будет подвергнут мутации + \item Случайный выбор терминального символа из допустимого множества + \item Удаление поддерева, корнем которого является выбранный узел + \item Замена удаленного поддерева терминальным символом + \end{enumerate} + + \textbf{Алгоритм растущей мутации} реализуется следующим образом: + + \begin{enumerate} + \item Определение узла, подвергаемого мутации + \item Если узел является терминальным, выбирается другой узел; для нетерминального узла производится удаление всех исходящих ветвей + \item Вычисление размера и сложности оставшейся части дерева + \item Генерация нового случайного поддерева, размер которого не превышает заданного порогового значения, и его размещение вместо удалённой части + \end{enumerate} + + \textbf{Алгоритм Hoist-мутации} предназначен для борьбы с избыточным ростом деревьев (bloat) и реализуется следующим образом: + + \begin{enumerate} + \item Случайный выбор поддерева с функциональным узлом в корне + \item Выбор случайного узла внутри этого поддерева (исключая корень выбранного поддерева) + \item Замена исходного поддерева на поддерево, начинающееся с выбранного внутреннего узла + \item В результате дерево становится короче, сохраняя при этом часть исходной структуры + \end{enumerate} + + Данная мутация всегда уменьшает размер дерева, что помогает контролировать сложность программ и предотвращает неконтролируемый рост деревьев в процессе эволюции. + + \textbf{Комбинированная мутация.} В реализованном алгоритме используется стратегия комбинированной мутации, которая на каждом шаге случайно выбирает один из четырёх описанных операторов с заданными вероятностями: + + \begin{itemize} + \item Растущая мутация: $p = 0.40$ + \item Узловая мутация: $p = 0.30$ + \item Hoist-мутация: $p = 0.15$ + \item Усекающая мутация: $p = 0.15$ + \end{itemize} + + Такой подход обеспечивает баланс между увеличением разнообразия популяции (растущая мутация), локальными изменениями (узловая мутация) и контролем размера деревьев (Hoist-мутация и усекающая мутация). + + \subsection{Фитнес-функции в генетическом программировании} + + В отличие от генетических алгоритмов, где фитнес-функция часто совпадает с исходной целевой функцией, в генетическом программировании фитнес-функция обычно измеряет степень соответствия между фактическими выходными значениями $y_i$ и целевыми значениями $d_i$. В качестве фитнес-функций часто используются метрики ошибок, такие как абсолютное отклонение или среднеквадратичная ошибка. + + \newpage + \section{Особенности реализации} + + В рамках работы создана библиотека \texttt{gp} для генетического программирования с древовидным представлением программ. Реализация выполнена на языке Python с использованием NumPy для векторизованных вычислений. + + \subsection{Примитивы и операции (primitive.py, ops.py)} + + Базовый класс \texttt{Primitive} представляет атомарные элементы дерева программы: + +\begin{lstlisting} +@dataclass(frozen=True) +class Primitive: + name: str + arity: int # арность: 0 для терминалов, >0 для операций + operation_fn: OperationFn | None +\end{lstlisting} + + Реализованы конструкторы для создания терминалов и операций: \texttt{Var(name: str)}, \texttt{Const(name: str, val: Value)}, \texttt{Operation(name: str, arity: int, fn)}. + + Модуль \texttt{ops.py} содержит набор безопасных векторизованных операций. Функция \texttt{make\_safe} оборачивает операции для обработки некорректных значений: + +\begin{lstlisting} +def make_safe(fn: Callable) -> Callable: + def wrapped(args: Sequence[Value]) -> Value: + with np.errstate(over="ignore", invalid="ignore", + divide="ignore", under="ignore"): + res = fn(args) + res = np.nan_to_num(res, nan=0.0, posinf=1e6, neginf=-1e6) + return np.clip(res, -1e6, 1e6) + return wrapped +\end{lstlisting} + + Реализованы унарные операции (\texttt{NEG, SIN, COS, SQUARE, EXP}) и бинарные (\texttt{ADD, SUB, MUL, DIV, POW}). Для деления используется защита от деления на ноль, для возведения в степень -- ограничение показателя. + + \subsection{Узлы дерева (node.py)} + + Класс \texttt{Node} представляет узел дерева программы: + +\begin{lstlisting} +class Node: + value: Primitive + parent: Node | None + children: list[Node] +\end{lstlisting} + + Реализованы методы для манипуляций с деревом: \texttt{add\_child}, \texttt{replace\_child}, \texttt{copy\_subtree}. Метод \texttt{list\_nodes} возвращает список всех узлов поддерева (обход в глубину). Для контроля размера реализован метод \texttt{prune}, который усекает дерево до заданной глубины, заменяя операции на случайные терминалы. + + Вычисление программы выполняется методом \texttt{eval}, который рекурсивно вычисляет значения поддеревьев и применяет операцию узла: + +\begin{lstlisting} +def eval(self, context: Context) -> Value: + return self.value.eval( + [child.eval(context) for child in self.children], + context + ) +\end{lstlisting} + + Для кроссовера реализована функция \texttt{swap\_subtrees(a: Node, b: Node)}, которая обменивает два поддерева, корректно обновляя ссылки на родителей. + + \subsection{Хромосомы (chromosome.py)} + + Класс \texttt{Chromosome} инкапсулирует дерево программы вместе с множествами терминалов и операций: + +\begin{lstlisting} +class Chromosome: + terminals: Sequence[Primitive] + operations: Sequence[Primitive] + root: Node +\end{lstlisting} + + Реализованы два метода инициализации случайных деревьев: + + \begin{itemize} + \item \texttt{full\_init(terminals, operations, max\_depth)} -- полная инициализация, где на каждом уровне до максимальной глубины выбираются только операции, а на последнем -- только терминалы. + \item \texttt{grow\_init(terminals, operations, max\_depth, terminal\_probability)} -- растущая инициализация с вероятностным выбором терминалов на каждом уровне, что создаёт деревья различной формы. + \end{itemize} + + Комбинация этих методов (\textit{ramped half-and-half}) реализована в функции \texttt{ramped\_initialization}, которая создаёт начальную популяцию из деревьев различных глубин, используя оба метода поровну. + + \subsection{Кроссовер (crossovers.py)} + + Реализован оператор кроссовера поддеревьев: + +\begin{lstlisting} +def crossover_subtree(parent1: Chromosome, parent2: Chromosome, + max_depth: int) -> tuple[Chromosome, Chromosome]: +\end{lstlisting} + + Алгоритм выбирает случайные узлы в каждом родителе (кроме корня) и обменивает соответствующие поддеревья. Если глубина потомков превышает \texttt{max\_depth}, деревья усекаются методом \texttt{prune}. + + \subsection{Мутации (mutations.py)} + + Все мутации наследуются от базового класса \texttt{BaseMutation} с методом \texttt{mutate}. Реализованы четыре типа мутаций: + + \begin{itemize} + \item \texttt{NodeReplacementMutation} -- заменяет узел на другой той же арности + \item \texttt{ShrinkMutation} -- заменяет случайную операцию на терминал (усечение) + \item \texttt{GrowMutation} -- заменяет узел на случайное поддерево с контролем глубины + \item \texttt{HoistMutation} -- заменяет поддерево на его случайную внутреннюю часть (уменьшает размер) + \end{itemize} + + Класс \texttt{CombinedMutation} позволяет комбинировать мутации с заданными вероятностями, случайно выбирая одну из них на каждом шаге. + + \subsection{Фитнес-функции (fitness.py)} + + Базовый класс \texttt{BaseFitness} определяет интерфейс для вычисления ошибки: + +\begin{lstlisting} +class BaseFitness(ABC): + def __call__(self, chromosome: Chromosome) -> float: + test_points = self.test_points_fn() + context = {t: test_points[:, i] + for i, t in enumerate(chromosome.terminals)} + predicted = chromosome.root.eval(context) + true_values = self.target_function(test_points) + return self.fitness_fn(chromosome, predicted, true_values) +\end{lstlisting} + + Реализованы метрики ошибок: \texttt{MSEFitness} (среднеквадратичная), \texttt{RMSEFitness} (корень из MSE), \texttt{MAEFitness} (средняя абсолютная), \texttt{NRMSEFitness} (нормализованная RMSE). Класс \texttt{PenalizedFitness} добавляет штраф за размер и глубину дерева для борьбы с bloat. + + \subsection{Селекция (selection.py)} + + Реализованы три метода селекции: + + \begin{itemize} + \item \texttt{roulette\_selection} -- селекция рулеткой со сдвигом для обработки отрицательных значений + \item \texttt{tournament\_selection(k)} -- турнирная селекция размера $k$ + \item \texttt{stochastic\_tournament\_selection(k, p\_best)} -- стохастическая турнирная с вероятностью выбора лучшего + \end{itemize} + + Для минимизации фитнес-функции используется инверсия знака при передаче фитнесов в селекцию. + + \subsection{Генетический алгоритм (ga.py)} + + Основная функция \texttt{genetic\_algorithm(config: GARunConfig)} реализует классический цикл ГА: + + \begin{enumerate} + \item Вычисление фитнеса: \texttt{eval\_population(population, fitness\_func)} + \item Сохранение элиты (если \texttt{config.elitism > 0}) + \item Селекция родителей: \texttt{config.selection\_fn(population, fitnesses)} + \item Кроссовер с вероятностью $p_c$: попарный обмен поддеревьями + \item Мутация с вероятностью $p_m$ + \item Замещение популяции с восстановлением элиты + \end{enumerate} + + Поддерживаются критерии остановки: по числу поколений, повторению лучшего результата, достижению порогового значения. История поколений сохраняется в виде списка объектов \texttt{Generation}. + + Функция \texttt{save\_generation} использует библиотеку Graphviz для визуализации лучшего дерева поколения. Функция \texttt{plot\_fitness\_history} строит графики динамики лучших и средних значений фитнеса по поколениям и сохраняет их отдельно в \texttt{fitness\_best.png} и \texttt{fitness\_avg.png}. + + \newpage + \section{Результаты работы} + + На Рис.~\ref{fig:gen1}--\ref{fig:lastgen} представлены результаты работы генетического алгоритма со следующими параметрами: + \begin{itemize} + \item $N = 400$ -- размер популяции. + \item $10$ -- максимальная глубина дерева. + \item $p_c = 0.85$ -- вероятность кроссинговера поддеревьев. + \item $p_m = 0.15$ -- вероятность мутации, при этом использовалась комбинация различных вариантов: + \begin{itemize} + \item Растущая мутация: $p = 0.40$ + \item Узловая мутация: $p = 0.30$ + \item Hoist-мутация: $p = 0.15$ + \item Усекающая мутация: $p = 0.15$ + \end{itemize} + \item $200$ -- максимальное количество поколений. + \item $15$ -- количество "элитных" особей, переносимых без изменения в следующее поколение. + \item $3$ -- размер турнира для селекции. + \end{itemize} + + На Рис.~\ref{fig:fitness_avg} и Рис.~\ref{fig:fitness_best} показаны графики изменения среднего и лучшего значения фитнеса по поколениям. + + \begin{figure}[h!] + \centering + \includegraphics[width=0.95\linewidth]{img/results/fitness_avg.png} + \caption{График среднего значения фитнеса по поколениям} + \label{fig:fitness_avg} + \end{figure} + + \begin{figure}[h!] + \centering + \includegraphics[width=0.95\linewidth]{img/results/fitness_best.png} + \caption{График лучшего значения фитнеса по поколениям} + \label{fig:fitness_best} + \end{figure} + + \begin{figure}[h!] + \centering + \includegraphics[width=0.25\linewidth]{img/results/generation_001.png} + \caption{Лучшая особь поколения №1} + \label{fig:gen1} + \end{figure} + + \begin{figure}[h!] + \centering + \includegraphics[width=0.25\linewidth]{img/results/generation_010.png} + \caption{Лучшая особь поколения №10} + \label{fig:gen10} + \end{figure} + + \begin{figure}[h!] + \centering + \includegraphics[width=0.25\linewidth]{img/results/generation_020.png} + \caption{Лучшая особь поколения №20} + \label{fig:gen20} + \end{figure} + + \begin{figure}[h!] + \centering + \includegraphics[width=0.25\linewidth]{img/results/generation_030.png} + \caption{Лучшая особь поколения №30} + \label{fig:gen30} + \end{figure} + + \begin{figure}[h!] + \centering + \includegraphics[width=0.5\linewidth]{img/results/generation_040.png} + \caption{Лучшая особь поколения №40} + \label{fig:gen40} + \end{figure} + + \begin{figure}[h!] + \centering + \includegraphics[width=1\linewidth]{img/results/generation_050.png} + \caption{Лучшая особь поколения №50} + \label{fig:gen50} + \end{figure} + + + \begin{figure}[h!] + \centering + \includegraphics[width=1\linewidth]{img/results/generation_100.png} + \caption{Лучшая особь поколения №100} + \label{fig:gen100} + \end{figure} + + \begin{figure}[h!] + \centering + \includegraphics[width=1\linewidth]{img/results/generation_150.png} + \caption{Лучшая особь поколения №150} + \label{fig:gen300} + \end{figure} + + \begin{figure}[h!] + \centering + \includegraphics[width=1\linewidth]{img/results/generation_200.png} + \caption{Лучшая особь поколения №200} + \label{fig:lastgen} + \end{figure} + + \newpage + \phantom{text} + \newpage + \phantom{text} + \newpage + \phantom{text} + \newpage + \phantom{text} + \newpage + \phantom{text} + \newpage + \phantom{text} + \subsection{Анализ результатов} + + \subsubsection*{Сравнение полученных деревьев} + + На Рис.~\ref{fig:original_tree} представлено исходное дерево, на Рис.~\ref{fig:best_tree} представлено лучшее дерево, найденное алгоритмом. + + \begin{figure}[h!] + \centering + \includegraphics[width=0.9\linewidth]{img/original_tree.png} + \caption{Дерево целевой функции} + \label{fig:original_tree} + \end{figure} + + \begin{figure}[h!] + \centering + \includegraphics[width=0.9\linewidth]{img/best_tree.png} + \caption{Лучшая особь, найденная алгоритмом} + \label{fig:best_tree} + \end{figure} + + \subsubsection*{Сравнение полученных формул} + + Перед сравнением, упростим исходную формулу, раскрыв знаки суммирования и перегруппировав слагаемые. + + $$f(x) = \sum_{i=1}^{n} \sum_{j=1}^{i} x_j^2, \text{ для всех } j = 1, \ldots, n, \text{ при этом n }= 8.$$ + + $$ + f(x) = \underbrace{(x_1^2) + + (x_1^2 + x_2^2) + + \ldots + + (x_1^2 + x_2^2 + x_3^2 + x_4^2 + x_5^2 + x_6^2 + x_7^2 + x_8^2)}_{\text{ всего } n = 8 \text{ слагаемых}} + $$ + + $$ + f(x) = 8 x_1^2 + 7 x_2^2 + 6 x_3^2 + 5 x_4^2 + 4 x_5^2 + 3 x_6^2 + 2 x_7^2 + x_8^2 + $$ + + В программе реализован метод преобразования особи (дереве) в строковую формулу. Вывод программы для лучшей особи представлен ниже: + +\begin{lstlisting}[label={lst:}] +(((((pow2(x3) + ((pow2(x1) + pow2(x2)) + pow2(x1))) + pow2(x6)) + +((pow2(x2) + pow2(x2)) + ((sin(((x6 + x2) + sin(x6))) + ((pow2(x4) + +pow2(x2)) + pow2(x4))) + (((pow2(x3) + pow2(x4)) + pow2(x7)) + (pow2(x6) + +pow2(x4)))))) + (((pow2(x2) + ((pow2(x8) + pow2((x5 + x5))) + pow2(x3))) + +pow2(x1)) + (pow2(x6) + pow2(x4)))) + (((((pow2(x3) + pow2(x3)) ++ ((pow2(x7) + pow2(x2)) + pow2(x1))) + pow2(x1)) + (pow2(x2) + ((pow2(x3) + +pow2(x1)) + pow2(x1)))) + (sin(x2) + pow2(x1)))) +\end{lstlisting} + + Программный метод автоматически обрамляет функции и переменные в скобки, чтобы правильно расставить приоритеты операций. Однако в данном случае они избыточны, поэтому их можно убрать: + +\begin{lstlisting}[label={lst:}] +pow2(x3) + pow2(x1) + pow2(x2) + pow2(x1) + pow2(x6) + pow2(x2) + pow2(x2) + +sin(x6 + x2) + sin(x6) + pow2(x4) + pow2(x2) + pow2(x4) + pow2(x3) + pow2(x4) + +pow2(x7) + pow2(x6) + pow2(x4) + pow2(x2) + pow2(x8) + pow2(x5 + x5) + pow2(x3) + +pow2(x1) + pow2(x6) + pow2(x4) + pow2(x3) + pow2(x3) + pow2(x7) + pow2(x2) + pow2(x1) + +pow2(x1) + pow2(x2) + pow2(x3) + pow2(x1) + pow2(x1) + sin(x2) + pow2(x1) +\end{lstlisting} + + Переставим слагаемые: + +\begin{lstlisting}[label={lst:}] +pow2(x1) + pow2(x1) + pow2(x1) + pow2(x1) + pow2(x1) + pow2(x1) + pow2(x1) + pow2(x1) + +pow2(x2) + pow2(x2) + pow2(x2) + pow2(x2) + pow2(x2) + pow2(x2) + pow2(x2) + +pow2(x3) + pow2(x3) + pow2(x3) + pow2(x3) + pow2(x3) + pow2(x3) + +pow2(x4) + pow2(x4) + pow2(x4) + pow2(x4) + pow2(x4) + +pow2(x5 + x5) + +pow2(x6) + pow2(x6) + pow2(x6) + +pow2(x7) + pow2(x7) + +pow2(x8) + +sin(x6 + x2) + sin(x6) + sin(x2) +\end{lstlisting} + + Заметим, что $(x_5 + x_5)^2 = (2x_5)^2 = 4x_5^2$, а также сгруппируем слагаемые, чтобы получить финальный вид формулы, найденной алгоритмом: + + $$ + \hat{f}(x) = \textcolor{green!70!black}{8x_1^2 + 7x_2^2 + 6x_3^2 + 5x_4^2 + 4x_5^2 + 3x_6^2 + 2x_7^2 + x_8^2} + \textcolor{red!90!black}{sin(x_6 + x_2) + sin(x_6) + sin(x_2)} + $$ + + Найденная формула полностью включает в себя целевую и содержит лишь несколько лишних слагаемых. + + \newpage + \section{Ответ на контрольный вопрос} + + \textbf{Вопрос}: Опишите древовидное представление. + + \textbf{Ответ}: + + Древовидное представление — классический подход в генетическом программировании, где программы представляются в виде синтаксических деревьев. Внутренние узлы содержат функции из функционального множества (арифметические операции, математические функции), а листья — терминалы из терминального множества (переменные и константы). Вычисление происходит рекурсивно от листьев к корню. Сложность дерева оценивается через максимальную глубину $D_m$ (расстояние от корня до самого дальнего листа) или общее количество узлов. + + Основные преимущества: естественное отображение синтаксической структуры математических выражений, гибкость в работе с выражениями различной длины и сложности, простота реализации генетических операторов (кроссовер поддеревьев, узловая мутация, растущая и усекающая мутации), автоматическое соблюдение синтаксической корректности при генерации и модификации программ. Инициализация выполняется полным методом (full) или растущим методом (grow), либо их комбинацией (ramped half-and-half). + + + \newpage + \section*{Заключение} + \addcontentsline{toc}{section}{Заключение} + + В ходе четвёртой лабораторной работы была успешно решена задача нахождения формулы целевой функции вида $f(x) = \sum_{i=1}^{n} \sum_{j=1}^{i} x_j^2$ с использованием генетического программирования: + + \begin{enumerate} + \item Изучен теоретический материал о представлениях программ в генетическом программировании (древовидное, линейное, графовое) и специализированных операторах кроссинговера и мутации для древовидных структур; + \item Создана программная библиотека \texttt{gp} на языке Python с реализацией древовидного представления хромосом, кроссовера поддеревьев, четырёх типов мутаций (узловая, усекающая, растущая, Hoist-мутация), турнирной селекции и безопасных векторизованных операций; + \item Реализованы методы инициализации популяции (full, grow, ramped half-and-half), фитнес-функции на основе метрик ошибок (MSE, RMSE, MAE, NRMSE), механизм элитизма и визуализация деревьев с помощью Graphviz; + \item Проведён эксперимент с популяцией из 400 особей на 10000 тестовых точках для 8 переменных. За 200 поколений (~5.9 минут) получено решение с MSE = 0.412 и RMSE = 0.642, полностью включающее целевую функцию с небольшими дополнительными слагаемыми. + \end{enumerate} + + +\newpage +\section*{Список литературы} +\addcontentsline{toc}{section}{Список литературы} + +\vspace{-1.5cm} +\begin{thebibliography}{0} + \bibitem{vostrov} + Методические указания по выполнению лабораторных работ к курсу «Генетические алгоритмы», 119 стр. +\end{thebibliography} + +\end{document} \ No newline at end of file