Эволюционные вычисления

Генетическое программирование

Разбить на страницы
Показывать лекцию целиком

Ниже вводятся различные способы кодирования (представления) программ: древовидное, линейное, графоподобное. Для них представлены соответствующие генетические операторы кроссинговера и мутации и виды фитнесс-функции. Описан метод символьной регрессии.

Эксперименты по компьютерному синтезу программ проводились с конца 50-х годов [1] и являлись одной из важнейших компонент машинного обучения, которое относится к одному из самых перспективных направлений искусственного интеллекта. В процессе обучения хромосомы или некоторые структуры автоматически генерируются с помощью генетических операторов и представляют компьютерные программы различной сложности. Первые эксперименты проводились с использованием двоичных кодов программ и показали скромные результаты, обусловленные, в первую очередь, состоянием вычислительной техники и программного обеспечения (ПО) того времени. И только в 80-х годах с развитием достаточно мощной компьютерной техники и ПО сформировалось генетическое программирование (ГП), в первую очередь на основе работ Koza [2].

В генетическом программировании (ГП) в качестве особи выступает программа, представленная в определенном формате, которая решает некоторую задачу. Часто это выполняется с использованием обучающих данных и индуктивного вывода. ГП очень близко к машинному обучению и поэтому в качестве фитнесс-функции достаточно часто выступают функции ошибки (рассогласования, невязки в различных метриках). Следует отметить, что ГП работает с генетическим материалом переменной длины, что требует нестандартной формы представления генома и соответствующих генетических операторов [3].

6.1. Функциональное и терминальное множество

Программы составляются из переменных, констант и функций, которые связаны некоторыми синтаксическими правилами. Поэтому необходимо определить терминальное множество, содержащее константы и переменные, и функциональное множество, которое состоит, прежде всего, из операторов и необходимых элементарных функций ($$\exp(x),\sin(x)$$ и т.п.). Следует отметить, что терминалы и функции играют различную роль. Терминалы обеспечивают входные значения в систему (программу), в то время как функции используются при обработке значений внутри системы. Термины "функции" и "терминалы" взяты из древовидного, наиболее часто применяемого, представления программ, которое широко используется в теории формальных языков и грамматик. Терминалы и функции соответствуют узлам древовидных (или графоподобных) структур.

6.1.1. Терминальное множество

Терминальное множество включает в себя: 1) внешние входы в программу; 2) используемые в программе константы; 3) функции, которые не имеют аргументов. Слово "терминал" используется, так как перечисленные выше объекты соответствуют терминальным (конечным, висячим) вершинам в древовидных структурах и соответствуют терминалам в формальных грамматиках. Терминал дает некоторое (численное) значение, не подвергаясь никаким входным значениям. У него нет входных аргументов, и он имеет нулевую "арность".

Следует отметить тесную связь внешних входов с обучающими выборками, которые часто используются в ГП. При этом каждая переменная (признак, фактор и т.п.) обучающей выборки соответствует своему терминалу. В этом смысле ГП отличается от других методов машинного обучения. Здесь переменная выборки не привязана прямо к входным данным – связь производится через терминалы. При необходимости терминал можно связать с другой внешней переменной.

Терминальное множество включает в себя также константы. В классическом ГП, основанном на древовидном представлении, множество числовых констант выбирается для всей популяции и не меняется в процессе эволюции. Но в линейном ГП (использующем линейное представление программы) случайно генерированные (обычно из заданного диапазона) константы могут мутировать при поиске решения.

6.1.2. Функциональное множество

Функциональное множество состоит из операторов (взятых у языков программирования) и различных функций. Оно может быть достаточно широким и включать типовые конструкции различных языков программирования, такие как:

  • булевы функции И, ИЛИ, НЕ и т.п.;
  • арифметические функции сложения, вычитания, умножения, деления;
  • трансцендентные функции (тригонометрические, логарифмические);
  • функции присваивания значения переменным ($$a:=2$$);
  • условные операторы (if then, else: case или switch операторы ветвления);
  • операторы переходов (go to, jump, call- вызов функции);
  • операторы цикла (while do, repeat until, for do);
  • подпрограммы и функции.
  • С одной стороны, терминальное и функциональное множества должны быть достаточно большими для представления потенциального решения. Например, вряд ли функциональное множество из операторов сложения и вычитания может быть эффективно использовано при решении достаточно сложных проблем. С другой стороны не следует сильно без необходимости расширять функциональное множество, поскольку при этом резко возрастает пространство поиска решений. Конечно, набор функций существенно зависит от решаемой задачи. Можно начинать с простейшего множества, состоящего из арифметических операторов сложения, вычитания, умножения, деления и логических - И, ИЛИ, НЕ, ИСКЛЮЧАЮЩЕЕ ИЛИ.

    Это также относится к константам. Во многих реализациях используется 256 узлов для представления функций и терминалов. Например, 56 используются для кодирования функций и 200 для констант. Важным свойством функционального множества является его замкнутость относительно принимаемых значений. То есть каждая функция должна принимать значения, которые могут принимать ее аргументы. Самым известным контрпримером является обычное деление, в котором второй аргумент (делитель) не может принимать нулевое значение. В этом случае может быть аварийный останов. Поэтому иногда используют "защищенное" деление, которое обрабатывает указанную ситуацию, возвращая в этом случае, например, некоторое большое число или нуль. Желательно, чтобы все функции (корень квадратный, логарифм и т.п.) имели подобную "защиту".

    6.2. Структуры для представления программ

    Терминалы и функции должны быть объединены по определенным правилам в некоторые структуры, которые могут представлять программы и использоваться при их выполнении. Выбор структуры существенно влияет на порядок выполнения программы, распределение и использование локальной или глобальной памяти.

    В настоящее время наиболее распространенными структурами для представления особей (потенциальных решений проблемы) являются:

  • древовидное представление;
  • линейная структура;
  • графоподобная структура.
  • 6.2.1. Древовидное представление

    Значительная часть работ в области ГП, в которых были получены положительные результаты, выполнялась на языке программирования задач искусственного интеллекта LISP, где программу удобно представлять в виде дерева. Поэтому в ГП была предложена древовидная форма генома. Мы в качестве примера для удобства будем использовать арифметические формулы, которые также удобно представлять деревом. Рассмотрим арифметическую формулу $$\frac{d}{e}-a*(b+c)$$(в обычном представлении). Этой формуле соответствует $$S$$-выражение $$(-(/de)(*a(+bc)))$$, которое по сути является префиксной (польской) записью формулы, где знак операции стоит перед двумя аргументами. Следует отметить, что скобки здесь при желании можно убрать. Часто используется также и постфиксная запись формулы, где знак операции стоит после аргументов – $$((de/)(a(bc+)*)-)$$. Легко заметить, что дерево (генотип) рис.6.1 также представляет эту формулу (фенотип) $$\frac{d}{e}-a*(b+c)$$.

    (рис 6.1) Древовидное представление формулы d/e-a*(b+c).

    При этом листья дерева соответствуют терминалам, а внутренние узлы – функциям. Заметим, что префиксная и постфиксная запись может быть получена из дерева путем различного обхода дерева. Например, постфиксная запись строится из дерева по Кнуту [4] с помощью следующего обхода:

  • обход левого дерева снизу;
  • обход правого дерева снизу;
  • посещение корня.
  • Для нашего примера вершины дерева посещаются в следующем порядке:$$d\to e\to/ \to a\to b\to c\to +\to *\to-$$

    Древовидная форма представления генотипа оказывается для данного класса задач более эффективной, и позволяет работать с программами или выражениями различной длины. Важным аспектом является также использование памяти при выполнении программы. Древовидная структура позволяет использовать только локальную память в процессе выполнения. Локальность памяти встроена в саму древовидную структуру. Значения переменных доступны для функции только в дереве, корню которого соответствует функция. Например, значения переменных $$d$$, $$e$$ являются локальными относительно узла"/".

    6.2.2. Линейные структуры

    Древовидное представление особей первоначально было ориентировано на программы, написанные на LISP, и менее подходит, например, для программ, написанных на Си. Далее будет рассмотрен один из возможных вариантов линейной структуры ГП, ориентированного на подмножество Си [3,5]. При этом каждая особь (программа) представлена последовательностью переменной длины операторов Си. На рис.6.2 представлен пример такой программы [6].

    (рис 6.2) Линейное представление программы.

    Здесь функциональное множество ("instruction set" или "functional set") состоит из арифметических операций, условных операторов if и вызовов функций. Общая нотация для операторов каждого типа представляется в таблице 6.1.

    Тип оператора Общая нотация
    Арифметический $$v_i:=v_j\ op\ v_k|c,\ op\in\{+,-,/,*\}$$
    Условный $$if(v_i\ cmp\ v_k|c)\ cmp\in \{>,\le\}$$
    Вызов функции $$v_j=f(v_k),\ f\in\{[\sin,\cos,sqrt,\log,\exp\dots]\}$$

    Из таблице 6.1 видно, что за исключением условных операторов, все операторы имеют явную операцию присваивания переменной $$v_i$$. Это позволяет использовать программы с "кратными выходами", которые в процессе выполнения изменяют и передают на выход значение нескольких переменных в отличие от программ, построенных на древовидных структурах, где на выход передается, как правило, значение только одной переменной, связанной с корнем дерева. Все операторы выполняются либо над двумя переменными, либо над переменной и константой. До начала работы программы переменным должны быть присвоены соответствующие значения.

    Переменные и константы вместе образуют множество терминальных символов. При этом подходе любой оператор кодируется 4-х мерным вектором, компонентами которого является тип оператора (одна компонента) и адреса (указатели) переменных и констант.

    Например, оператор $$v_i=v_j+c$$ представляется вектором $$(+,i,j,c)$$. Каждая компонента использует 1 байт памяти, следовательно, число переменных (и констант) ограниченно сверху 256.

    Такое представление позволяет эффективно выполнять рекомбинацию программы и их интерпретацию. Для частично определенных операторов и их функций, в случае неопределенных значений на выходе возвращается "-1". Последовательности условных операторов $$if$$ интерпретируются как вложенные условные операторы (как это трактуется в Си). В случае ложного значения условия оператора пропускается один следующий по порядку оператор. Такая интерпретация условных операторов дает, с одной стороны, достаточно выразительную мощность, и, с другой стороны, упрощает в дальнейшем работу по обнаружению и устранению интронов (ненужных участков кода, описанных в разделе 6.7), что само по себе представляет в ГП значительную проблему. Следует отметить, что в линейном представлении, в отличие от древовидного, для функции нет очевидного способа определения значений аргументов (в древовидном представлении они однозначно определяются узлами, находящимися ниже соответствующего функционального узла). Существенным отличием является также глобальное использование памяти при выполнении программы в отличие от локального в древовидных структурах. Здесь значения переменных доступны для всех функций.

    Следует отметить, что данный подход является более выразительным (позволяющим генерировать более сложные программы), чем "регистровая машина" с линейной последовательностью команд, которая часто используется для представления линейных структур [5].

    В эволюционном алгоритме при этом подходе обычно используется метод турнирного отбора родителей в промежуточную популяцию, описанный в разделе 4. Мощность популяции обычно поддерживается постоянной.

    6.2.3. Графоподобные структуры

    Эта форма представления разработана для задач, которые не могут быть решены методами, которые используют рассмотренные выше модели программ. Данный подход использует более выразительную модель для представления программы [7]. Поэтому данный подход позволяет эффективно решать многие задачи, которые нельзя было решить с помощью предыдущих методов.

    Сначала рассмотрим типичную блок-схему программы написанной вручную, которая представлена в качестве примера на рис.6.3.

    Здесь каждая особь – программа представляется в виде графа. Вершина графа представляет линейный участок программы. Она имеет две части – собственно линейную программу и узел ветвления. Из рисунка видно, что блок-схема линейного графа более естественна, чем линейная (древовидная) форма представления программы.

    Это объясняется тем, что многие программы содержат операторы ветвления, после которых может быть вызов другой части кода программы, не входящей в линейную последовательность операторов. Линейная программа выполняется в том случае, когда данный узел графа достигается при интерпретации программы на конкретных данных. После выполнения линейной части выбирается узел-последователь в соответствии с функцией ветвления этого узла. В течение интерпретации узлы только одного пути графа от входов до выхода будут выполняться в зависимости от текущих значений переменных условий операторов ветвления.

    (рис 6.3) Представление программы в виде графа
    Оператор Ветвления Описание оператора
    $$A<0$$ (аккумулятор меньше 0) Если значение аккумулятора-сумматора меньше нуля, то выбирается левый (верхний) преемник, иначе правый (нижний)
    $$А>0$$ Если значение аккумулятора-сумматора больше нуля, то выбирается левый (верхний) преемник, иначе правый (нижний)
    $$A<op$$ Если значение аккумулятора-сумматора меньше значения операнда, то выбирается левый (верхний) преемник, иначе правый (нижний)
    $$A>op$$ Если значение аккумулятора-сумматора больше значения оператора, то выбирается левый (верхний) преемник, иначе правый (нижний)
    $$RD<0$$ (регистр данных) Если значение регистра данных меньше нуля, то выбирается левый (верхний) преемник, иначе правый (нижний)
    $$RD>0$$ Если значение регистра данных больше нуля, то выбирается левый (верхний) преемник, иначе правый (нижний)
    $$RD<op$$ Если значение регистра данных меньше значения оператора, то выбирается левый (верхний) преемник, иначе правый (нижний)
    $$RD>op$$ Если значение регистра данных больше значения оператора, то выбирается левый (верхний) преемник, иначе правый (нижний)

    Реализация линейных подструктур использует список операторов Си (или другого языка программирования, в том числе и ассемблера) переменной длины, которые оперируют с переменными или константами и полученные значения присваиваются переменным (например, $$a = b + 1.33$$). После выполнения программы вычисленное значение запоминается в "выходные" переменные. Функция ветвления также является оператором Си, который оперирует с теми же переменными, что и линейная программа, но этот оператор только читает значения этих переменных.Таблица 6.2 содержит множество операторов ветвления, которые могут быть использованы в данной модели.

    На рис.6.4 представлен детально узел графа, соответствующий некоторой части линейной программы.

    (рис 6.4) Подпрограмма, соответствующая узлу графа.

    Данная модель, как и предыдущая, использует при выполнении программы глобальную память. Графоподобные структуры позволяют расширить класс задач, которые могут быть решены с помощью ГП, так как являются более общими по сравнению с первыми двумя и допускают эффективную реализацию [7].

    Известен другой способ применения структур графов в ГП, называемый PADO [5]. Здесь каждая программа представляется ориентированным графом, содержащим узлы, индексированную память для входных переменных и стек, как показано на рис.6.5. В произвольном ориентированном графе каждый узел может иметь N исходящих дуг.

    (рис 6.5) Представление программы ориентированным графом.

    Эта структура не просто показывает потоки информации и управление ходом выполнения программы. Узел в графе соответствует сегменту программы и имеет две части: "действие" и "ветвление". Часть "действие" содержит константы и функцию, которая выполняется при достижении данного узла в процессе интерпретации программы. Данные передаются между узлами через стек. Часть "действие" получает данные из (верхушки) стека и после выполнения соответствующей функции передает преобразованные данные снова в стек. После того выполняется "ветвление", которое определяет ветвь следующего выполняемого узла на основании данных стека, памяти или специальных констант ветвления. Очевидно, что в процессе интерпретации необязательно посещаются все вершины графа. Каждая программа имеет две специальных вершины "старт" и "конец" и кроме этого может содержать некоторые специальные узлы типа "вызов подпрограммы". Поскольку граф может содержать обратные связи, то вершина "конец" при интерпретации на некоторых входных данных может быть недостижимой вследствие "зацикливания". Поэтому необходимо контролировать и ограничивать время выполнения программы.

    6.2.4. Другие формы представления программ.

    Кроме рассмотренных трех форм представления, в настоящее время предложены многочисленные модификации и обобщения ГП, в которых разработаны различные формы представления особей и определены на них соответствующие генетические операторы кроссинговера и мутации. В табл.6.3 приведены основные структуры, используемые в настоящее время в ГП [8].

    В работе [9] для представления программ введены более сложные структуры в узлы дерева. Этот подход основан на хорошо известном методе группового учета аргументов (МГУА) Ивахненко, который широко используется при решении многих задач. При этом внутренние узлы дерева соответствуют полиномам второго порядка, которые используются в МГУА. Далее на указанных древовидных структурах определяются генетические операторы кроссинговера и мутации. При этом обычно используется локальный поиск и замена функций – процедура "переразметки", которая применяется для нахождения оптимальных значений параметров при заданной структуре. При определении фитнесс-функции здесь часто используют кратный (множественный) регрессионный анализ. Иногда используются и другие меры, например, сложность особи (к примеру, минимальная длина) или их комбинация.

    Клеточное кодирование [10] также использует структуру графов, которые широко используются в компьютерных науках (для описания электрических цепей, нейронных сетей, конечных автоматов и т.п.), и поэтому область применения такого представления достаточно широкая (в частности, структурный синтез). Основная идея заключается в том, что фенотип отделяется от генотипа, который основан на древовидной структуре. С другой стороны фенотип представляется структурой (возможно циклического) графа. Генотип представляется грамматическим деревом (разбора), использующим правила грамматического вывода. При этом используются древовидные грамматики, в которых при грамматическом выводе нетерминальный символ заменяется соответствующим деревом. Эти операции выполняются параллельно подобно $$L$$-системам [11]. Клеточное кодирование позволяет определить модули, которые последовательно могут применяться в различных участках грамматического дерева.

    Структура Описание
    $$S$$-выражение Древовидная структура
    Основана на методе группового учета аргументов (МГУА) Древовидная структура
    Язык регистрового уровня (TB) Линейная постфиксная запись
    Язык регистрового уровня (JB) Линейная постфиксная запись
    Битовая Линейные геномы
    Битовая Машинные команды
    Абстрактные типы данных Списки, очереди, стеки
    Правила продукций Грамматики
    Клеточное кодирование Древовидные грамматики
    Правила продукций Графы
    Параллельные алгоритмы (PADO) Графы

    Генетическое программирование на основе формальных грамматик предложено в работе [12]. Здесь используется очень общая форма ГП, которая основана на использовании теории формальных языков и грамматик. В процессе эволюции особей применяются контекстно-свободные грамматики, что позволяет преодолеть некоторые ограничения в теоретическом плане, характерные для классического ГП. Применение КС-грамматик гарантирует синтаксическую корректность потомков в процессе эволюции и позволяет определить простые и эффективные генетические операторы. Кроссинговер выполняется следующим образом. Случайным образом выбирается нетерминальный символ в грамматическом дереве первого родителя и затем производится поиск этого же нетерминала во втором родителе. Если во втором родителе этого нетерминала нет, то оператор не выполняется. Иначе производится обмен поддеревьев, соответствующих нетерминальным символам. Мутация выполняется путем "выращивания" грамматического дерева из случайным образом выбранного нетерминального символа. В дальнейших работах авторы допускают изменение продукций КС-грамматики в процессе эволюции для вывода лучших правил. Известны также работы, в которых используются и более общие контекстно-зависимые грамматики.

    В работах [8,11] определено понятие $$L$$-систем, которые первоначально были разработаны для моделирования структур биологических формаций или, в более общем контексте, развивающихся процессов. В этом подходе замена нетерминальных символов при "выращивании деревьев" выполняется параллельно. Это позволяет развиваться различным ветвям независимо друг от друга, как это имеет место в реальных сложных процессах. При этом целью эволюции является генерация $$L$$-системы, множество продукций которой позволяет решать поставленную задачу. Таким образом, здесь в качестве особи выступает вся $$L$$-система. Каждая $$L$$-система оценивается после вывода грамматического дерева. Следует отметить, что такие особи не могут изменяться произвольным образом, а развиваются согласно правилам мета-грамматики.

    Интересным является применение в ГП методов нечеткой (fuzzy) логики [9]. В частности, нечеткая логика может использоваться в адаптивном ГП для подстройки таких параметров как вероятности кроссинговера и мутации в процессе эволюции, что позволяет повысить эффективность. При этом используется коэволюция правил нечеткого контроллера, который управляет параметрами ГП.

    Поиск решения на основе генетического программирования может быть реализован согласно алгоритму, блок-схема которого представлена на рис.1.1. Но, учитывая специфику кодирования особи, имеющую переменную длину и сложную структуру, реализация каждого этапа этого алгоритма имеет свои особенности, которые мы рассмотрим ниже.

    6.3. Инициализация начальной популяции

    Данный этап в ГП является не таким простым, как в классическом ГА, где генерация случайных двоичных строк не представляет особых проблем. Это связано с различной сложностью особей и их структурой. Естественно методы инициализации начальной популяции различны для разных форм представления программ. Одним из важнейших параметров в ГП является максимально возможный размер (сложность) программы.

    6.3.1. Инициализация древовидных структур

    Для деревьев в качестве меры сложности используется максимальная глубина (иногда называется высота) дерева или общее число узлов в дереве. Глубиной узла называется минимальное число узлов, которые необходимо пройти от корня дерева к этому узлу. Максимальной глубиной дерева $$D_m$$ называется максимально возможная глубина в дереве для терминального символа (листа). Если арность каждого узла равна двум, то общее число узлов не превышает $$2^{D_m}$$, которое также используется в качестве меры сложности.

    Инициализация древовидных структур выполняется путем случайного выбора функциональных и терминальных символов при заданной максимальной глубине дерева. Пусть для определенности выбраны следующее терминальное $$T=\{a,b,c,d,e\}$$ и функциональное $$F=\{+,-,*,\% \}$$ множества, где $$\%$$ означает деление нацело. Применяются два основных метода: 1) полная (full) и 2) растущая (grow) инициализация [2].

    (рис 6.6) Деревья, генерируемые при инициализации разными методами: а) полная (левые 3 дерева); б) растущая (правые 3 дерева).

    В полном методе при генерации дерева, пока не достигнута максимальная глубина, допускается выбор только функциональных символов, а на последнем уровне (максимальной глубины) выбираются только терминальные символы. Например, на рис.6.6 а) представлено дерево с $$D_m=3$$.

    В растущей инициализации генерируются нерегулярные деревья с различной глубиной листьев вследствие случайного на каждом шаге выбора функционального или терминального символа. Здесь при выборе терминального символа рост дерева прекращается по текущей ветви и поэтому дерево имеет нерегулярную структуру.

    Например, для $$D_m=3$$ при растущей инициализации для указанных функциональных и терминальных символов могут быть построены деревья, приведенные на рис.6.6 б).

    При использовании первого метода начальная популяция содержит однородное множество структур, что способствует вырождению генетического материала (и преждевременной сходимости к локальным экстремумам). Поэтому на практике часто эти два метода используют одновременно следующим образом. Начальная популяция генерируется так, чтобы в нее входили деревья с разной максимальной длиной примерно поровну (для нашего примера $$D_m=1, D_m=2, D_m=3, D_m=4$$). Для каждой глубины первая половина деревьев генерируется полным методом, а вторая – растущей инициализацией.

    6.3.2. Инициализация линейных структур

    В этом случае процесс инициализации выполняется совершенно по-другому. Прежде всего, особь-программа разбивается на следующие четыре части:

  • заголовок;
  • тело;
  • "подвал";
  • выход (возврат).
  • Из них только тело программы генерируется с помощью эволюции, остальные части программы являются стандартными и заготавливаются заранее. Алгоритм инициализации можно сформулировать следующим образом:

  • Выбор случайной длины из заданного диапазона;
  • Копирование заготовленного заголовка;
  • Инициализация и пополнение собственно операторов в программу пока не достигнута длина, определенная в пункте 1. Операторы выбираются случайным образом, сначала тип, затем переменная или константа из заданного диапазона;
  • Копирование в конец программы заготовленного "подвала";
  • Копирование в конец программы заготовленных операторов выхода.
  • 6.4. Кроссинговер в генетическом программировании

    В начальной популяции особи (потенциальные решения), как правило, имеют низкие (плохие) значения фитнесс-функции. В процессе эволюции с помощью генетических операторов популяция развивается и значения фитнесс-функции улучшаются. В ГП используются те же, что и в ГА, генетические операторы кроссинговера, мутации и репродукции. Отметим, что в терминах машинного обучения они соответствуют операторам поиска решения. Далее мы рассмотрим выполнение генетических операторов на ранее определенных структурах.

    6.4.1. Выполнение кроссинговера на древовидных структурах

    Для древообразной формы представления используются следующие три основные операторы кроссинговера (ОК):

  • узловой ОК;
  • кроссинговер поддеревьев;
  • смешанный.
  • В узловом операторе кроссинговера выбираются два родителя (два дерева) и узлы в этих деревьях. Первый родитель называется доминантом, второй – рецессивом. Узлы в деревьях могут быть разного типа. Поэтому сначала необходимо убедиться, что выбранные узлы у родителей являются взаимозаменяемыми. Если узел во втором родителе не соответствует типу узла первого родителя, то случайным образом выбирается другой узел во втором родителе, который опять подлежит проверке на предмет совместимости. Далее производится обмен узлов.

    Рассмотрим ОК на следующем примере, представленном на рис.6.7 для родительских особей:$$\frac{3}{4}*x^2+x*y,\ \frac{x}{2}+y+z*y$$

    В кроссинговере поддеревьев родители обмениваются не узлами, а определяемыми ими поддеревьями. Оператор выполняется следующим образом:

  • Выбираются родители (один – доминантный, другой – рецессивный). Далее необходимо убедиться, что выбранные узлы взаимозаменяемы, т.е. принадлежат одному типу. Иначе, как и в предыдущем случае, в рецессивном дереве выбирается другой узел с последующей проверкой.
  • Затем производится обмен поддеревьев, которые определены этими узлами.
  • Вычисляется размер ожидаемых потомков. Если ожидаемый размер (сложность потомка) не превышает заданный порог, такой обмен ветвями запоминается. На рис.6.8показан пример выполнения этого ОК.
  • Этот тип ОК является основным. При этом под размером (под)дерева понимается, как и ранее, либо его высота, либо число его вершин.

    При смешанном операторе кроссинговера для некоторых узлов выполняется узловой ОК, а для других - кроссинговер поддеревьев.

    Отметим, что выполнение кроссинговера на древовидных структурах выполняется достаточно просто с помощью $$S$$-выражений, что показано в таблице 6.4. Например, кроссинговер поддеревьев сводится к обмену "скобками", которые здесь соответствуют поддеревьям.

    В таблице 6.5 приведены типовые операторы кроссинговера для древовидных структур[3,8].

    (рис 6.7) Узловой кроссинговер.
    Родители Потомки
    $$( - ( * a b ) c )$$ $$( - (- ( + d e) f) c )$$
    $$( * (- ( + d e) f ) g)$$ $$( * (* a b) g)$$
    Наименование Описание производимых действий
    Кроссинговер обмена поддеревьев Обмен поддеревьями родителей
    Само-кроссинговер Обмен поддеревьями в одном родителе
    Модульный кроссинговер Обмен модулями (фрагментами) родителей
    Контекстно-сохраняющий кроссинговер Полный обмен поддеревьями, если он согласуется с контекстом, иначе частичный обмен
    (рис 6.8) Кроссинговер поддеревьев.

    6.4.2. Кроссинговер на линейных структурах

    Скрещивание на линейных структурах выполняется достаточно просто. Здесь у родителей выполняется обмен линейными сегментами, как это показано на рис.6.9.

    При этом в каждом из двух родителей выбирается сегмент, начиная со случайной позиции и имеющий случайную длину. Далее производится обмен выбранных сегментов программ. Если размер хотя бы одного из потомков превышает некоторый порог, то результаты ОК аннулируются. При этом точки скрещивания выбираются только между операторами.

    6.4.3. Выполнение кроссинговера для графоподобных структур

    Кроссинговер комбинирует генетический материал из двух родительских программ путем обмена некоторых частей программ. ОК для этой модели может быть реализован двумя способами.

    Первый способ похож на кроссинговер поддеревьев, который определен на древообразных структурах путем обмена поддеревьев. Здесь обмен производится подграфами, как это показано на рис.6.10.

    (рис 6.9) Кроссинговер на линейных структурах.

    При этом в каждой особи-родителе выбирается случайным образом множество смежных узлов и производится обмен двух подграфов между этими подграфами.

    (рис 6.10) Кроссинговер на графоподобных структурах.

    Второй тип кроссинговера выполняет линейный ОК. Здесь для каждого родителя выбирается линейный сегмент (в одном узле графа), начинающийся со случайной позиции данного сегмента и имеющий случайную длину. Далее, как обычно, производится обмен этими линейными сегментами. Если размер хотя бы одного потомка превышает некоторый порог, то результаты ОК аннулируются и выполняется обмен равными сегментами меньшей длины.

    Обычно линейный ОК выполняется в вероятностью $$P_i=0,1$$. Пример выполнения этого ОК представлен на рис.6.11. Как правило, в процессе эволюции используются ОК обоих типов. В целом ОК выполняется следующим образом:

  • Выбор точки скрещивания $$P_1,P_2$$ в обоих родителях
  • Выбор с заданной вероятностью типа ОК ( для 1-го типа с вероятностью $$P_G$$, для 2-го с вероятностью $$1-P_G$$).Если выбран 1-й тип то переход на п.3, иначе на п.4.
  • Если размер потомка не превышает порог, то выполнить ОК 1-го типа, переход на п.5.
  • Если размер потомка не превышает порог, то выполнить ОК 2-го типа.
  • Конец.
  • (рис 6.11) Линейный кроссинговер на графах.

    6.5. Мутация в генетическом программировании

    После выполнения кроссинговера с заданной малой вероятностью $$P_c$$ выполняется мутация для выбранной одной особи-программы.

    6.5.1. Выполнение мутации на древовидных структурах

    Для деревьев используются следующие операторы мутации (ОМ):

  • узловая;
  • усекающая;
  • растущая.
  • Узловая мутация выполняется следующим образом:

  • выбрать случайным образом узел, подлежащий мутации, определить его тип;
  • случайным образом выбрать из соответствующего множества вариантов узлов, отличный от рассматриваемого узла;
  • поменять исходный узел на выбранный.
  • Усекающая мутация производится так:

  • определяется или выбирается узел;
  • случайным образом выбирается терминальный символ из заданного множества;
  • обрезается ветвь узла мутации;
  • вместо обрезанной ветви помещается выбранный терминальный символ.
  • На рис.6.12 показан пример выполнения усекающей мутации.

    (рис 6.12) Усекающая мутация.

    Растущая мутация выполняется следующим образом:

  • определяется узел мутации;
  • если узел нетерминальный, то необходимо отсечь ветви, исходящие из него, иначе выбрать другой узел;
  • вычислить размер (сложность) остатка дерева;
  • вместо отсеченного дерева вырастить случайным образом новое дерево так, чтобы размер нового построенного дерева не превышал заданный порог.
  • Это очень мощный оператор, который обладает большими возможностями. На рис.6.13 представлен пример выполнения этого оператора. В некоторых работах растущая мутация выполняется с использованием формальных грамматик. При этом нетерминальный символ заменяется в соответствии с правилами грамматики, а не случайным образом.

    В настоящее время для древовидных структур в ГП разработаны различные типы мутаций, которые производят различные изменения и представлены в табл.6.6 [5].

    Для примера на рис.6.14 представлен результат выполнения мутации - перестановки (аргументов) для дерева, представляющего выражение $$\frac{1}{2}*(d+e)$$.

    (рис 6.13) Растущая мутация.
    Наименование Описание производимых действий
    Точечная мутация Случайное изменение типа одного узла из того же класса
    Перестановка Перестановка аргументов одного узла
    "Подъем" Случайная генерация новой особи из поддерева
    Растущая мутация Замена терминального символа случайным поддеревом
    Секущая мутация Замена поддерева случайным терминальным символом
    Мутация поддерева Замена поддерева случайным поддеревом
    (рис 6.14) Пример мутации – перестановки аргументов.

    6.5.2. Выполнение мутации на линейных структурах

    Здесь мутация выполняется совершенно другим способом. Прежде всего, из линейного сегмента (тела программы) случайно выбирается оператор (команда) и в нем производятся изменения одного из следующих видов:

  • имя переменной (или регистра) заменяется на другое случайным образом выбранное из заданного множества;
  • оператор может быть также изменен случайным образом на некоторый другой из функционального множества;
  • может быть случайно изменено значение константы на некоторое другое значение из заданного диапазона.
  • Мутация констант выполняется путем стандартного отклонения от текущего значения $$P=P+\delta\cdot h_m$$, где $$P$$-текущее значение, $$1\le\delta\le 1$$-случайное число и $$h_m$$–шаг мутации. При этом вероятность мутации обычно убывает по мере удаления от начала процесса.

    6.5.3. Выполнение мутации на графоподобных структурах

    В этом случае оператор мутации случайным образом выбирает множество узлов и изменяет либо узел линейной программы, либо узел ветвления, либо число исходящих из условного оператора ветвей. То есть ОМ не строит новые линейные последовательности. Измененная ОМ программа помещается назад в популяцию.

    6.6. Фитнесс-функция в генетическом программировании

    В отличие от генетических алгоритмов, где часто при поиске экстремумов в качестве фитнесс-функции используется исходная целевая функция, в ГП фитнесс-функция обычно определяет меру близости между реальными $$y_i$$ и требуемыми $$d_i$$ выходными значениями (например, при использовании ГП в имвольной регрессии ). Поэтому в качестве фитнесс-функции часто используется абсолютная или квадратичная ошибка.

    В этом случае фитнесс-функция использует обучающее множество данных, на котором выполняется обучение системы. С помощью фитнесс-функции в процессе обучения реализуется обратная связь, которая показывает насколько хорошо данная особь-программа реализует необходимую функцию на обучающем множестве. Для этого можно использовать различные виды фитнес-функций, некоторые из которых мы рассмотрим ниже.

    "Непрерывной" (continuous) фитнесс-функцией называют [5] функцию вычисления фитнесс-значений, в которой малое улучшение в обучении программы вызывает малые улучшения измеряемых фитнесс-значений и большие улучшения в обучении связаны соответственно с большими изменениями (в сторону улучшения) фитнесс-значений. В [5] отмечается, что такая "непрерывность" является важным свойством, так как позволяет ГП итеративно улучшать программы в процессе эволюции.

    "Стандартизованой" [5] фитнесс-функцией называют преобразованную фитнесс-функцию, которая лучшим особям приписывает нулевое значение.

    "Нормализованной" [5] фитнесс-функцией называют преобразованную фитнесс-функцию, которая для всех особей дает значения в интервале (0,1).

    Рассмотрим следующий пример [5] с обучающей выборкой, представленной табл.6.7. Каждая строка таблицы определяет один элемент $$(x,y)$$ обучающей выборки. Необходимо в процессе эволюции построить программу (или формулу в случае символьной регрессии), которая для каждого входного значения x вычисляет необходимое (в соответствии с табл.6.5) значение y (фактически нам необходимо реализовать функцию $$f(x)=x^2+x$$).

    Вход $$x$$ Выход $$d$$
    1 1 2
    2 2 6
    3 4 20
    4 7 56
    5 9 90

    Рассмотрим в качестве фитнесс-функции ошибку в метрике абсолютных значений $$f_a=\sum_{i=1}^n |y_i-d_i|$$, где суммирование выполняется по обучающей выборке. Эта фитнесс-функция соответствует первому определению "непрерывной", поскольку чем ближе значения $$y_i$$ к $$d_i$$, тем меньше значение фитнесс-функции. Приведенная фитнесс-функция является также стандартизованной, так как в случае идеального решения дает нулевое значение.

    Часто в качестве фитнесс-функции также используют квадратичную ошибку $$f_s=\sum_{i=1}^n (y_i-d_i)^2$$.Таблица 6.8 показывает различие для этих двух фитнесс-функций в том случае, если на некотором (промежуточном) этапе в качестве особи оценивается (плохо обученная) программа, реализующая функцию $$f(x)=x^2$$.

    Мы рассмотрели использование в качестве фитнесс-функции ошибки в двух метриках, которые характерны для применения ГП в качестве символьной регрессии, что будет детальнее рассмотрено в разделе 6.8.

    Вход $$x$$ Выход $$d$$ Выход $$y$$ Ошибка $$f_a$$ Ошибка $$f_s$$
    1 1 2 1 1 2
    2 2 6 4 2 4
    3 4 20 16 4 16
    4 7 56 49 7 49
    5 9 90 81 9 81
    Общая ошибка 23 151

    Подобный подход применяется также во многих других задачах, где используются следующие фитнесс-функции:

  • число правильно отображаемых пикселов в задачах обработки изображений;
  • число столкновений робота со стенами при обучении обхода препятствия;
  • число правильно классифицируемых примеров в задачах классификации;
  • ошибка между реальными и ожидаемыми значениями в задачах прогнозирования;
  • приз победителя в игровых задачах;
  • количество "пищи", найденной агентом, в многоагентных системах ("искусственная жизнь" и т.п.)
  • Естественно разработано множество фитнесс-функций других типов, их вид существенно зависит от исследуемой проблемы, а применение ГП не ограничивается задачами символьной регрессии. Кроме приведенных стандартных разработано много других методов определения фитнесс-функции, в частности теоретико-игровых и основанных на идее коэволюции, где особи соревнуются между собой без явного вычисления значений. В некоторых случаях, кроме близости решений, учитываются и другие критерии, например, длина или время выполнения программы. В этом случае говорят о многокритериальных фитнесс-функциях.

    6.7. Интроны

    Программы, построенные с помощью методов ГП, имеют тенденцию к накоплению интронов – ненужных и непригодных участков кода

    Например:

    (NOT(NOT x)) ,
    (AND (ORXX)),
    (+… (-XX)),
    (+X0),
    (*X1),
    (*(DIV XX)),
    (MOVE_LEFT MOVE_RIGHT),
    (IF (2=1) . . . ),
    A:=A.
    

    Таких фрагментов в программе возникает достаточно много (их количество может достигать 60%), и обнаружение и удаление интронов представляет серьезную проблему в ГП. Разработаны специальные методы для их устранения. Интересно отметить, что в живой природе интронов также достаточно много (в частности, на генном уровне существуют "лишние" участки ДНК).

    6.8. Общий алгоритм генетического программирования

    Таким образом, для решения задачи с помощью ГП необходимо выполнить описанные выше предварительные этапы:

  • Определить терминальное множество;
  • Определить функциональное множество;
  • Определить фитнесс-функцию;
  • Определить значения параметров, такие как мощность популяции, максимальный размер особи, вероятности кроссинговера и мутации, способ отбора родителей, критерий окончания эволюции (например, максимальное число поколений) и т.п
  • После этого можно разрабатывать непосредственно сам эволюционный алгоритм, реализующий ГП для конкретной задачи. Как и в случае ГА здесь также возможны различные подходы, которые рассмотрены в разделах 4 и 5.

    Например, решение задачи на основе ГП можно представить следующей последовательностью действий:

  • установка параметров эволюции;
  • инициализация начальной популяции;
  • $$t:=0$$
  • оценка особей, входящих в популяцию;
  • $$t:=t+1$$
  • отбор родителей;
  • создание потомков выбранных пар родителей – выполнение оператора кроссинговера;
  • мутация новых особей;
  • расширение популяции новыми порожденными особями;
  • сокращение расширенной популяции до исходного размера;
  • если критерий останова алгоритма выполнен, то выбор лучшей особи в конечной популяции – результат работы алгоритма. Иначе переход на шаг 4.
  • Следует отметить, что в ГП достаточно часто применяется асинхронный ГА, рассмотренный в разделе 4.6.

    6.9. Символьная регрессия

    Этот раздел является одним из важнейших приложений ГП. Данный термин подчеркивает то, что здесь объектом поиска является символьное описание модели, в отличие от множества коэффициентов в стандартных методах. Этот подход существенно отличается от других методов регрессии и использования нейросетей прямого распространения, где структура (и сложность) модели предполагается известной и фактически необходимо найти только ее коэффициенты. В случае символьной регрессии вид и сложность функции заранее неизвестны и могут изменяться в процессе поиска.

    Задача регрессии может быть определена на основе множества значений входных независимых переменных $$x$$ и зависимой выходной переменной $$y$$. Целью поиска является аппроксимация $$y$$ с помощью переменных $$x$$ и коэффициентов $$w$$ следующим образом $$y=f(x,w)+\varepsilon$$, где $$\varepsilon$$ представляет шум (ошибку).

    В стандартних методах регресии вид функции $$f$$ предполагается известным, например, в линейной регресии - $$f(x,w)=w_0+w_1x_1+\dots +w_nx_n$$. Здесь коэффициенты $$w_i$$ обычно находятся методом наименьших квадратов. В нелинейных методах, например с использованием нейронных сетей прямого распространения, функция имеет вид $$f(x,w)=w_0\cdot g(w_hx)$$. Здесь коэффициенты $$w_0$$ и $$w_h$$ представляют синаптические веса нейронной сети выходного и скрытых слоев соответственно.

    Как уже отмечалось, символьная регрессия на основе ГП не использует некоторую заранее предопределенную форму функции $$f(x,w)$$. Здесь функция $$f(x,w)$$ представляется древовидной структурой и строится эволюционным методом с использованием определенного функционального и терминального множеств. В качестве фитнесс-функции обычно используется квадратичная ошибка, которая оценивает качество решения и обеспечивает обратную связь при поиске решения. Для определенности обозначим функции множества, зависящие от одной переменной через $$h_1,\dots,h_k$$ и функции от двух переменных как $$g_1,\dots,g_l$$. В этой нотации функция $$f(x,w)$$ представляется в виде суперпозиции функций $$h_i,g_j$$, и например, может иметь следующий вид: $$f(x,w)=h_1(g_2(g_1(x_3,w_1),h_2(x_1)))$$.

    Заметим, что в символьной регрессии при поиске решения не используются численные методы, например, градиентные или стохастические.

    Далее рассмотрим детально пример использования символьной регрессии из [5] для аппроксимации данных, представленных в табл.6.9. Необходимо найти функцию $$f(x)$$, которая аппроксимирует с заданной точностью эти "экспериментальные" данные. Для решения задачи определим в соответствии с вышесказанным:

    Терминальное множество: переменная x и константы в диапазоне [-5,5];

    Функциональное множество: арифметические функции $$+, - , * , \%$$ (защищенное деление).

    Koza [2] ввел следующую удобную и "прозрачную" форму для перечисления параметров, которая представлена в табл.6.10.

    Далее приведем некоторые полученные экспериментальные данные результатов эволюции для различных запусков программ из [5]. В начальной популяции после инициализации лучшая особь представлена деревом рис.6.15, которая реализует (не минимальным образом !) функцию $$f_0(x)=\frac{x}{3}$$.В последующих рисунках рис.6.16,рис.6.17,рис.6.18,рис.6.19,рис.6.20 представлены лучшие особи последующих поколений. Здесь в первом поколении лучшая особь реализует $$f_1(x)=\frac{x}{6-3x}$$.В последующих рисунках рис.6.16,рис.6.17,рис.6.18,рис.6.19,рис.6.20 представлены лучшие особи последующих поколений. Здесь в первом поколении лучшая особь реализует $$f_1(x)=\frac{x}{6-3x}$$ и соответствующее дерево рис.6.16 сильно избыточно.

    Вход $$x$$ Выход $$y$$
    1 0.000 0.000
    2 0.100 0.005
    3 0.200 0.020
    4 0.300 0.045
    5 0.400 0.080
    6 0.500 0.125
    7 0.600 0.180
    8 0.700 0.245
    9 0.800 0.320
    10 0.900 0.405

    Аналогично во втором поколении лучшая особь реализует функцию $$f_2(x)=\frac{x}{x(x-4)-1+\frac{4}{x}-\frac{\frac{9(x+1)}{5x}+x}{6-3x}}$$ и дерево тоже сильно избыточно. Наконец в третьем поколении получена лучшая особь $$f_3(x)=\frac{x^2}{2}$$, которая дает оптимальное решение в простейшей форме.

    (рис 6.15) Лучшая особь в поколении 0. (рис 6.16) Лучшая особь поколения 1
    Параметры Значения
    Цель: Эволюция функции, аппроксимирующей данные Табл.6.6
    Терминальное множество Переменная $$x$$, Целые от –5 до +5
    Функциональное множество ADD, SUB, MUL, DIV
    Мощность популяции: 600
    Вероятн. кроссинговера: 0.90
    Вероятность мутации: 0.05
    Отбор родителей: турнирный, с мощностью тура 4
    Максимальное число поколений: 100
    Максимальная глубина после кроссинговера: 200
    Максимальная глубина мутации: 4
    Метод инициализации: Растущая
    (рис 6.17) Лучшая особь поколения 2. (рис 6.18) Лучшая особь поколения 3.

    В табл.6.11 для сравнения представлены значения функций лучших особей первых поколений (0-3).Отметим, что была выполнена еще одна итерация (4-е поколение). На рис.6.19 представлена лучшая особь четвертого поколения, которая также реализует функцию $$f_4(x)=\frac{x^2}{2}$$, но избыточность соответствующего дерева выросла. Конечно, для данного примера можно было использовать и классические методы регрессии, но он носит чисто иллюстративный характер.

    (рис 6.19) Лучшая особь поколения 4.

    Интересным примером является машинный вывод третьего закона Кеплера на основе символьной регрессии. Итак, есть экспериментальные данные, которые связывают радиус и период обращения планет солнечной системы, которые представлены в табл.6.12 (в астрономических единицах). Напомним, что согласно закону Кеплера, период обращения планеты пропорционален корню квадратному из третьей степени радиуса орбиты $$P=\sqrt{(r)^3}$$, где $$r$$ – радиус орбиты планеты (чаще используется формулировка $$\frac{r^3}{P^2}=c$$). Для решения этой задачи с помощью символьной регрессии возможные параметры представлены в табл.6.13. На рис.6.20 представлено полученное с помощью символьной регрессии дерево, которое представляет закон Кеплера.

    Во многих работах [2,5] приведены результаты для более сложных зависимостей, но они требуют для представления результатов большого объема.

    $$y$$ $$f_0$$ $$f_1$$ $$f_2$$ $$f_3$$
    1 0.000000 0.000000 0.000000 0.000000 0.000000
    2 0.005000 0.033333 0.017544 0.002375 0.005000
    3 0.020000 0.066667 0.037037 0.009863 0.020000
    4 0.045000 0.100000 0.058824 0.023416 0.045000
    5 0.080000 0.133333 0.083333 0.044664 0.080000
    6 0.125000 0.166667 0.111111 0.076207 0.125000
    7 0.180000 0.200000 0.142857 0.1222140 0.180000
    8 0.245000 0.233333 0.179487 0.188952 0.245000
    9 0.320000 0.266667 0.222222 0.287024 0.320000
    10 0.405000 0.300000 0.272727 0.432966 0.405000
    Планета Радиус - $$r$$ Период - $$p$$
    Меркурий 0,387 0,24
    Венера 0,723 0,62
    Земля 1,000 1,000
    Марс 1,524 1,88
    Юпитер 5,203 11,86
    Сатурн 9,569 29,46
    Уран 19,309 84,01
    Нептун 30,284 164,79
    Плутон 39,781 247,69

    Следует отметить, что мощность популяций используемых в ГП на порядок выше, чем в классических ГА, а число поколений, как правило, меньше. Применение ГП в качестве символьной регрессии является важным для многих технических задач (например, в гидро- и аэродинамике), но далеко не единственным.

    (рис 6.20) Дерево, представляющее третий закон Кеплера

    Как показано выше, символьная регрессия может использоваться для получения эмпирических зависимостей на основе экспериментальных данных. Она широко применяется в эконометрике при решении задач моделирования и прогнозирования. Кроме этого, символьная регрессия с успехом применяется в символьных вычислениях, включая символьное дифференцирование и интегрирование, решение дифференциальных и интегральных уравнений в символьном виде и т.п.[2].

    6.10 Модульное построение программ в генетическом программировани

    Эволюционный подход в представленном выше виде порождает слабо структурированные программы, что подтверждают приведенные примеры. Напротив, программист (человек) при проектировании программ, как правило, применяет модульный принцип построения, где решение разбивается на отдельные небольшие (часто используемые) участки кода, которые затем используются в разработке программы. Для этого широко используется аппарат функций и подпрограмм, что позволяет существенно упростить как процесс проектирования, так и сами разрабатываемые программы. Этот подход является типичным примером применения стратегии "разделяй и властвуй", которая широко используется при проектировании программ.

    Параметры Значения
    Цель: Вывод третьего закона Кеплера, аппроксимируюшей данные табл.6.12
    Терминальное множество Переменная $$r$$(расстояние от солнца в астрономических единицах)
    Функциональное множество $$+, - , * ,^ , sqrt, \sin, \cos$$
    Фитнесс-функция Сумма абсолютных значений разности между реальным периодом и даваемым текущей формулой
    Мощность популяции: 500
    Вероятность кроссинговера: 0.80
    Вероятность мутации: 0.05
    Максимальное число поколений: 50

    Одним из самых перспективных направлений повышения эффективности ГП является использование модульного принципа, в котором идентифицируются некоторые функциональные единицы программы, которые оформляются по определенным стандартам и сохраняются для дальнейшего использования.

    Модулем называют последовательность (соседних) операторов программы, ограниченную некоторыми окаймляющими элементами, который имеет общий идентификатор. Модуль должен обладать, по крайней мере, следующими свойствами:

  • логическая замкнутость;
  • реализация по принципу "черного ящика";
  • должен иметь интерфейс для взаимодействия с другими модулями (множество операций и типов данных).
  • Модульный принцип является средством инкапсуляции блоков кода. Такие блоки оформляются в виде подпрограмм, которые затем могут многократно использоваться в главной программе или включаться в другие подпрограммы. Это позволяет существенно уменьшить сложность программы за счет оформления часто используемых идентичных участков кода в виде подпрограмм. Отметим, что меньшая сложность (длина) программ способствуют их "выживанию" в процессе эволюционного поиска. В ГП используется различная техника для реализации модульного принципа. Самыми известными методами являются автоматически определяемые функции (АОФ) (automatically defined functions –ADF), которые введены в [2] и детально исследованы во многих работах [2,5].

    ADF построены по аналогии с функциями языка LISP. Поэтому программа, содержащая ADF, также представляется древовидной структурой, но при этом дерево состоит из двух частей (поддеревьев):

  • главной программы, которая, прежде всего, оценивается с помощью фитнесс-функции;
  • определение функций. Пример такой структуры приведен на рис.6.21.
  • Эти два поддерева соответствуют структуре программы на языке программирования высокого уровня, подобном С++, Паскаль и т.п., где главная программа (тело) сопровождается "объявлениями", где описаны соответствующие функции. Отметим, что обе указанные компоненты должны участвовать в эволюции, которая в этом случае производит программу, построенную в соответствии с модульным принципом.

    На рис.6.21 корень дерева"программа" соответствует программе в целом и объединяет ее различные части. Узел "определение" является корневым для поддерева, содержащего описание функции ADF0. В случае необходимости использования нескольких функций каждой из них необходимо выделить соответствующее поддерево ADF для описания функции.

    (рис 6.21) Дерево, содержащее ADF

    Левая ветвь, исходящая из узла "определение", содержит имя функции (ADF0), которое включается в функциональное множество главной программы и используется для вызова соответствующей функции. Справа от узла имени расположен узел, представляющий список аргументов данной функции. Этот список определяет имена входных переменных аргументов функции. Эти имена входят в терминальное множество тела соответствующей функции. Этот подход аналогичен определению функции в С++ или Паскале, где необходимо дать имя функции и определить входные переменные вместе с описанием тела функции. Следует отметить, что использование рекурсии, как правило, не допускается вследствие значительных трудностей реализации.

    Очевидно, что данный подход требует применения специального, синтаксически ограниченного, кроссинговера. Использование описанных выше видов кроссинговера для древовидных структур в этом случае невозможно. Отметим, что, различные поддеревья (главная программа и описание функций) имеют свои (существенно отличающиеся) функциональные и терминальные множества. Очевидно, что при кроссинговере нельзя производить обмен некоторыми частями между различными типами поддеревьев. Поэтому, прежде всего, при выполнении кроссинговера производится выбор поддерева в одном из поддеревьев первого родителя (главной программе или функции). Соответственно точка кроссинговера (или поддерево) во втором родителе должна быть выбрана в поддереве того же типа, только в этом случае возможен обмен фрагментами деревьев.

    До запуска программы поиска с использованием данного подхода необходимо определить число ADF функций и допустимое число аргументов для каждой функции. То есть мы должны в каком-то смысле задать структуру дерева. Естественно, при инициализации мы должны придерживаться этой структуры. При этом различные поддеревья (главная программа и функции) генерируются независимо (случайными методами).

    При использовании данного подхода необходимо дополнительно выполнить следующие действия:

  • выбрать число функций ADF;
  • фиксировать число аргументов для каждой ADF;
  • определить возможные связи между разными ADF, если используется больше одной функции;
  • определить терминальные функциональные множества для главной программы и ADF;
  • определить фитнесс- функцию для оценки особей;
  • определить параметры эволюционного алгоритма.
  • Основным недостатком данного подхода является необходимость определения заранее структуры программы, которая включает число ADF функций, число аргументов и т.п.

    Кроме ADF в ГП применяются и другие методы модульного построения программ [5]. К ним относится использование автоматически определяемых макросов (automatically defined macros), автоматическое определение циклов do while, until и рекурсии, применение сложных и абстрактных типов данных и т.д. С одной стороны они позволяют получать более компактные программы, но с другой их применение существенно осложняет сам процесс их построения.

    Контрольные вопросы

  • Чем отличаются терминальное и функциональное множества?
  • Какие структуры используются для представления программ в ГП?
  • Опишите древовидное представление программы.
  • Какой тип памяти используется в древовидном представлении?
  • Опишите линейное представление программы.
  • Опишите представление программы в виде графа.
  • Какие два метода используются в инициализации древовидных структур?
  • Как производится инициализация линейных структур?
  • Какие виды кроссинговера вы знаете для древовидных структур?
  • Как выполняется кроссинговер на линейных структурах?
  • Какие виды кроссинговера вы знаете для графоподобных структур?
  • Какие виды мутации вы знаете для древовидных структур?
  • Как производится мутация на линейных структурах?
  • Как можно определить фитнесс-функцию в ГП?
  • Что такое интроны?
  • Приведите общий алгоритм ГП.
  • Чем отличается символьная регрессия от обычной?
  • Как можно в ГП использовать принцип модульного построения программ?
  • Упражнения

  • Разработать эволюционный алгоритм, реализующий ГП для нахождения заданной по варианту функции (таб. 6.14).
  • Структура для представления программы – древовидное представление.
  • Терминальное множество: переменные $$x_1, x_2, x_3, \dots,x_n,$$, и константы в соответствии с заданием по варианту.
  • Функциональное множество:$$+, -, *, /, abs(), \sin(), \cos(), \exp()$$, возведение в степень,
  • Фитнесс-функция – мера близости между реальными значениями выхода и требуемыми.
  • Представить графически найденное решение на каждой итерации.
  • Сравнить найденное решение с представленным в условии задачи.
  • № вв. Вид функции Кол-во первых N Промежуток исследования
    1 $$f_1(x)=\sum_{i=1}^n x_i^2$$ 10 $$-5,12\le x_i\le 5,12$$
    2 $$f_{la}(x)=\sum_{i=1}^n i\cdot x_i^2$$ 9 $$-5,12\le x_i\le 5,12$$
    3 $$f_{1b}(x)=\sum_{i=1}^n\left(\sum_{j=1}^i x_j\right)^2$$ 8 $$-5,536\le x_i\le 65,536$$
    4 $$f_2(x)=\sum_{i=1}^{n-1} 100\cdot (x_{i+1}-x_i^2)^2+(1-x_i)^2)$$ 7 $$-2,048\le x_i\le 2,048$$
    5 $$f_6(x)=10\cdot n+\sum_{i=1}^n(x_i^2-10\cdot \cos(2\cdot\pi\cdot x_i))$$ 9 $$-5,12\le x_i\le 5,12$$
    6 $$f_7(x)=\sum_{i=1}^n -x_i\cdot \sin(\sqrt{|x_i|})$$ 10 $$-500\le x_i\le 500$$
    7 $$f_8(x)=\sum_{i=1}^n \frac{x_i^2}{4000}-\prod_{i=1}^n\cos\left(\frac{x_i}{\sqrt{i}\right)+10$$ 5 $$-600\le x_i\le 600$$
    8 $$f_9(x)=\sum_{i=1}^n|x_i|^{(i+1)}$$ 8 $$-1\le x_i\le 1$$
    9 $$f_{10}(x)=-a\cdot e^{-b\cdot \sqrt{\frac{\sum_{i=1}^n x_i^2}{n}}}-e^{\frac{\sum_{i=1}^n cos(c\cdot x_i)}{n}}+a+e^1;\\a=20;b=0,2;c=2\cdot \pi$$ 4 $$-2.768\le x_i\le 32.768$$
    10 $$f_{11}(x)=-\sum_{i=1}^m c_i\cdot(e^{-\frac{\|x-A(i)\|^2}{\pi}}\cdot\cos\left(\pi\cdot\|\bar x-A(i)\|^2)),\\m=5;A_i,C_i\ne 0$$ 4 $$0\le x_i\le 10$$
    11 $$f_{12}(x)=-\sum_{i=1}^n \sin(x_i)\cdot\left(\sin\left(\frac{i\cdot x_i^2}{\pi} \right ) \right)^{2\cdot m},\\ m=10$$ 5 $$0\le x_i\le \pi$$
    12 $$f_{Bran}(x_1,x_2)=a\cdot(x_2-b\cdot x_1^2+c\cdotx_1-d)^2+e\cdot(1-f)\cdot(x_1)+e,\\a=1,b=\frac{5,1}{4\cdot \pi^2},c=\frac{5}{\pi},d=6,e=10,f=\frac{1}{8\cdot\pi}$$ 2 $$-5\le x_1\le 10,\ 0\le x_2\le \15$$
    13 $$f_{Easo}(x_1,x_2)=-\cos(x_1)\cdot\cos(x_2)\cdot e^{-((x_1-\pi)^2+(x_2-\pi)^2)}$$ 2 $$-100\le x_i\le 100$$
    14 $$f_{Gold}(x_1,x_2)=(1+(x_1+x_2+1)^2\cdot(19-14x_1+3x_1^2-14x_2+6x_1x_2+3x_2^2))\cdot(30+(2x_1-3x_2)^2\cdot(18-32x_1+12x_1^2+48x_2-36x_1x_2+27x_2^2))$$ 2 $$-2\le x_i\le 2$$
    15 $$f_{Sixh}(x_1,x_2)=(4-2.1\cdot x_1^2+x_1^{4/3})\cdot x_1^2+x1\cdot x_2+(-4+4\cdot x_2^2)\cdot x_2^2;$$ 2 $$-3\le x_1\le 3,-2\le x_2\le2$$

    Краткие итоги:

  • изложены основы ГП, основной алгоритм ГП и его параметры;
  • описаны основные формы представления ГП: древовидное, линейное, графоподобное;
  • рассмотрены различные методы инициализации начальной популяции в ГП;
  • описаны генетические операторы кроссинговера и мутации на различных формах представления ГП;
  • изложен на основе ГП метод символьной регрессии;
  • рассмотрено использование модульного построения в ГП.
  • Страницы:

    Ниже вводятся различные способы кодирования (представления) программ: древовидное, линейное, графоподобное. Для них представлены соответствующие генетические операторы кроссинговера и мутации и виды фитнесс-функции. Описан метод символьной регрессии.

    Эксперименты по компьютерному синтезу программ проводились с конца 50-х годов [1] и являлись одной из важнейших компонент машинного обучения, которое относится к одному из самых перспективных направлений искусственного интеллекта. В процессе обучения хромосомы или некоторые структуры автоматически генерируются с помощью генетических операторов и представляют компьютерные программы различной сложности. Первые эксперименты проводились с использованием двоичных кодов программ и показали скромные результаты, обусловленные, в первую очередь, состоянием вычислительной техники и программного обеспечения (ПО) того времени. И только в 80-х годах с развитием достаточно мощной компьютерной техники и ПО сформировалось генетическое программирование (ГП), в первую очередь на основе работ Koza [2].

    В генетическом программировании (ГП) в качестве особи выступает программа, представленная в определенном формате, которая решает некоторую задачу. Часто это выполняется с использованием обучающих данных и индуктивного вывода. ГП очень близко к машинному обучению и поэтому в качестве фитнесс-функции достаточно часто выступают функции ошибки (рассогласования, невязки в различных метриках). Следует отметить, что ГП работает с генетическим материалом переменной длины, что требует нестандартной формы представления генома и соответствующих генетических операторов [3].

    6.1. Функциональное и терминальное множество

    Программы составляются из переменных, констант и функций, которые связаны некоторыми синтаксическими правилами. Поэтому необходимо определить терминальное множество, содержащее константы и переменные, и функциональное множество, которое состоит, прежде всего, из операторов и необходимых элементарных функций ($$\exp(x),\sin(x)$$ и т.п.). Следует отметить, что терминалы и функции играют различную роль. Терминалы обеспечивают входные значения в систему (программу), в то время как функции используются при обработке значений внутри системы. Термины "функции" и "терминалы" взяты из древовидного, наиболее часто применяемого, представления программ, которое широко используется в теории формальных языков и грамматик. Терминалы и функции соответствуют узлам древовидных (или графоподобных) структур.

    6.1.1. Терминальное множество

    Терминальное множество включает в себя: 1) внешние входы в программу; 2) используемые в программе константы; 3) функции, которые не имеют аргументов. Слово "терминал" используется, так как перечисленные выше объекты соответствуют терминальным (конечным, висячим) вершинам в древовидных структурах и соответствуют терминалам в формальных грамматиках. Терминал дает некоторое (численное) значение, не подвергаясь никаким входным значениям. У него нет входных аргументов, и он имеет нулевую "арность".

    Следует отметить тесную связь внешних входов с обучающими выборками, которые часто используются в ГП. При этом каждая переменная (признак, фактор и т.п.) обучающей выборки соответствует своему терминалу. В этом смысле ГП отличается от других методов машинного обучения. Здесь переменная выборки не привязана прямо к входным данным – связь производится через терминалы. При необходимости терминал можно связать с другой внешней переменной.

    Терминальное множество включает в себя также константы. В классическом ГП, основанном на древовидном представлении, множество числовых констант выбирается для всей популяции и не меняется в процессе эволюции. Но в линейном ГП (использующем линейное представление программы) случайно генерированные (обычно из заданного диапазона) константы могут мутировать при поиске решения.

    6.1.2. Функциональное множество

    Функциональное множество состоит из операторов (взятых у языков программирования) и различных функций. Оно может быть достаточно широким и включать типовые конструкции различных языков программирования, такие как:

  • булевы функции И, ИЛИ, НЕ и т.п.;
  • арифметические функции сложения, вычитания, умножения, деления;
  • трансцендентные функции (тригонометрические, логарифмические);
  • функции присваивания значения переменным ($$a:=2$$);
  • условные операторы (if then, else: case или switch операторы ветвления);
  • операторы переходов (go to, jump, call- вызов функции);
  • операторы цикла (while do, repeat until, for do);
  • подпрограммы и функции.
  • С одной стороны, терминальное и функциональное множества должны быть достаточно большими для представления потенциального решения. Например, вряд ли функциональное множество из операторов сложения и вычитания может быть эффективно использовано при решении достаточно сложных проблем. С другой стороны не следует сильно без необходимости расширять функциональное множество, поскольку при этом резко возрастает пространство поиска решений. Конечно, набор функций существенно зависит от решаемой задачи. Можно начинать с простейшего множества, состоящего из арифметических операторов сложения, вычитания, умножения, деления и логических - И, ИЛИ, НЕ, ИСКЛЮЧАЮЩЕЕ ИЛИ.

    Это также относится к константам. Во многих реализациях используется 256 узлов для представления функций и терминалов. Например, 56 используются для кодирования функций и 200 для констант. Важным свойством функционального множества является его замкнутость относительно принимаемых значений. То есть каждая функция должна принимать значения, которые могут принимать ее аргументы. Самым известным контрпримером является обычное деление, в котором второй аргумент (делитель) не может принимать нулевое значение. В этом случае может быть аварийный останов. Поэтому иногда используют "защищенное" деление, которое обрабатывает указанную ситуацию, возвращая в этом случае, например, некоторое большое число или нуль. Желательно, чтобы все функции (корень квадратный, логарифм и т.п.) имели подобную "защиту".

    6.2. Структуры для представления программ

    Терминалы и функции должны быть объединены по определенным правилам в некоторые структуры, которые могут представлять программы и использоваться при их выполнении. Выбор структуры существенно влияет на порядок выполнения программы, распределение и использование локальной или глобальной памяти.

    В настоящее время наиболее распространенными структурами для представления особей (потенциальных решений проблемы) являются:

  • древовидное представление;
  • линейная структура;
  • графоподобная структура.
  • 6.2.1. Древовидное представление

    Значительная часть работ в области ГП, в которых были получены положительные результаты, выполнялась на языке программирования задач искусственного интеллекта LISP, где программу удобно представлять в виде дерева. Поэтому в ГП была предложена древовидная форма генома. Мы в качестве примера для удобства будем использовать арифметические формулы, которые также удобно представлять деревом. Рассмотрим арифметическую формулу $$\frac{d}{e}-a*(b+c)$$(в обычном представлении). Этой формуле соответствует $$S$$-выражение $$(-(/de)(*a(+bc)))$$, которое по сути является префиксной (польской) записью формулы, где знак операции стоит перед двумя аргументами. Следует отметить, что скобки здесь при желании можно убрать. Часто используется также и постфиксная запись формулы, где знак операции стоит после аргументов – $$((de/)(a(bc+)*)-)$$. Легко заметить, что дерево (генотип) рис.6.1 также представляет эту формулу (фенотип) $$\frac{d}{e}-a*(b+c)$$.

    (рис 6.1) Древовидное представление формулы d/e-a*(b+c).

    При этом листья дерева соответствуют терминалам, а внутренние узлы – функциям. Заметим, что префиксная и постфиксная запись может быть получена из дерева путем различного обхода дерева. Например, постфиксная запись строится из дерева по Кнуту [4] с помощью следующего обхода:

  • обход левого дерева снизу;
  • обход правого дерева снизу;
  • посещение корня.
  • Для нашего примера вершины дерева посещаются в следующем порядке:$$d\to e\to/ \to a\to b\to c\to +\to *\to-$$

    Древовидная форма представления генотипа оказывается для данного класса задач более эффективной, и позволяет работать с программами или выражениями различной длины. Важным аспектом является также использование памяти при выполнении программы. Древовидная структура позволяет использовать только локальную память в процессе выполнения. Локальность памяти встроена в саму древовидную структуру. Значения переменных доступны для функции только в дереве, корню которого соответствует функция. Например, значения переменных $$d$$, $$e$$ являются локальными относительно узла"/".

    6.2.2. Линейные структуры

    Древовидное представление особей первоначально было ориентировано на программы, написанные на LISP, и менее подходит, например, для программ, написанных на Си. Далее будет рассмотрен один из возможных вариантов линейной структуры ГП, ориентированного на подмножество Си [3,5]. При этом каждая особь (программа) представлена последовательностью переменной длины операторов Си. На рис.6.2 представлен пример такой программы [6].

    (рис 6.2) Линейное представление программы.

    Здесь функциональное множество ("instruction set" или "functional set") состоит из арифметических операций, условных операторов if и вызовов функций. Общая нотация для операторов каждого типа представляется в таблице 6.1.

    Тип оператора Общая нотация
    Арифметический $$v_i:=v_j\ op\ v_k|c,\ op\in\{+,-,/,*\}$$
    Условный $$if(v_i\ cmp\ v_k|c)\ cmp\in \{>,\le\}$$
    Вызов функции $$v_j=f(v_k),\ f\in\{[\sin,\cos,sqrt,\log,\exp\dots]\}$$

    Из таблице 6.1 видно, что за исключением условных операторов, все операторы имеют явную операцию присваивания переменной $$v_i$$. Это позволяет использовать программы с "кратными выходами", которые в процессе выполнения изменяют и передают на выход значение нескольких переменных в отличие от программ, построенных на древовидных структурах, где на выход передается, как правило, значение только одной переменной, связанной с корнем дерева. Все операторы выполняются либо над двумя переменными, либо над переменной и константой. До начала работы программы переменным должны быть присвоены соответствующие значения.

    Переменные и константы вместе образуют множество терминальных символов. При этом подходе любой оператор кодируется 4-х мерным вектором, компонентами которого является тип оператора (одна компонента) и адреса (указатели) переменных и констант.

    Например, оператор $$v_i=v_j+c$$ представляется вектором $$(+,i,j,c)$$. Каждая компонента использует 1 байт памяти, следовательно, число переменных (и констант) ограниченно сверху 256.

    Такое представление позволяет эффективно выполнять рекомбинацию программы и их интерпретацию. Для частично определенных операторов и их функций, в случае неопределенных значений на выходе возвращается "-1". Последовательности условных операторов $$if$$ интерпретируются как вложенные условные операторы (как это трактуется в Си). В случае ложного значения условия оператора пропускается один следующий по порядку оператор. Такая интерпретация условных операторов дает, с одной стороны, достаточно выразительную мощность, и, с другой стороны, упрощает в дальнейшем работу по обнаружению и устранению интронов (ненужных участков кода, описанных в разделе 6.7), что само по себе представляет в ГП значительную проблему. Следует отметить, что в линейном представлении, в отличие от древовидного, для функции нет очевидного способа определения значений аргументов (в древовидном представлении они однозначно определяются узлами, находящимися ниже соответствующего функционального узла). Существенным отличием является также глобальное использование памяти при выполнении программы в отличие от локального в древовидных структурах. Здесь значения переменных доступны для всех функций.

    Следует отметить, что данный подход является более выразительным (позволяющим генерировать более сложные программы), чем "регистровая машина" с линейной последовательностью команд, которая часто используется для представления линейных структур [5].

    В эволюционном алгоритме при этом подходе обычно используется метод турнирного отбора родителей в промежуточную популяцию, описанный в разделе 4. Мощность популяции обычно поддерживается постоянной.

    6.2.3. Графоподобные структуры

    Эта форма представления разработана для задач, которые не могут быть решены методами, которые используют рассмотренные выше модели программ. Данный подход использует более выразительную модель для представления программы [7]. Поэтому данный подход позволяет эффективно решать многие задачи, которые нельзя было решить с помощью предыдущих методов.

    Сначала рассмотрим типичную блок-схему программы написанной вручную, которая представлена в качестве примера на рис.6.3.

    Здесь каждая особь – программа представляется в виде графа. Вершина графа представляет линейный участок программы. Она имеет две части – собственно линейную программу и узел ветвления. Из рисунка видно, что блок-схема линейного графа более естественна, чем линейная (древовидная) форма представления программы.

    Это объясняется тем, что многие программы содержат операторы ветвления, после которых может быть вызов другой части кода программы, не входящей в линейную последовательность операторов. Линейная программа выполняется в том случае, когда данный узел графа достигается при интерпретации программы на конкретных данных. После выполнения линейной части выбирается узел-последователь в соответствии с функцией ветвления этого узла. В течение интерпретации узлы только одного пути графа от входов до выхода будут выполняться в зависимости от текущих значений переменных условий операторов ветвления.

    (рис 6.3) Представление программы в виде графа
    Оператор Ветвления Описание оператора
    $$A<0$$ (аккумулятор меньше 0) Если значение аккумулятора-сумматора меньше нуля, то выбирается левый (верхний) преемник, иначе правый (нижний)
    $$А>0$$ Если значение аккумулятора-сумматора больше нуля, то выбирается левый (верхний) преемник, иначе правый (нижний)
    $$A<op$$ Если значение аккумулятора-сумматора меньше значения операнда, то выбирается левый (верхний) преемник, иначе правый (нижний)
    $$A>op$$ Если значение аккумулятора-сумматора больше значения оператора, то выбирается левый (верхний) преемник, иначе правый (нижний)
    $$RD<0$$ (регистр данных) Если значение регистра данных меньше нуля, то выбирается левый (верхний) преемник, иначе правый (нижний)
    $$RD>0$$ Если значение регистра данных больше нуля, то выбирается левый (верхний) преемник, иначе правый (нижний)
    $$RD<op$$ Если значение регистра данных меньше значения оператора, то выбирается левый (верхний) преемник, иначе правый (нижний)
    $$RD>op$$ Если значение регистра данных больше значения оператора, то выбирается левый (верхний) преемник, иначе правый (нижний)

    Реализация линейных подструктур использует список операторов Си (или другого языка программирования, в том числе и ассемблера) переменной длины, которые оперируют с переменными или константами и полученные значения присваиваются переменным (например, $$a = b + 1.33$$). После выполнения программы вычисленное значение запоминается в "выходные" переменные. Функция ветвления также является оператором Си, который оперирует с теми же переменными, что и линейная программа, но этот оператор только читает значения этих переменных.Таблица 6.2 содержит множество операторов ветвления, которые могут быть использованы в данной модели.

    На рис.6.4 представлен детально узел графа, соответствующий некоторой части линейной программы.

    (рис 6.4) Подпрограмма, соответствующая узлу графа.

    Данная модель, как и предыдущая, использует при выполнении программы глобальную память. Графоподобные структуры позволяют расширить класс задач, которые могут быть решены с помощью ГП, так как являются более общими по сравнению с первыми двумя и допускают эффективную реализацию [7].

    Известен другой способ применения структур графов в ГП, называемый PADO [5]. Здесь каждая программа представляется ориентированным графом, содержащим узлы, индексированную память для входных переменных и стек, как показано на рис.6.5. В произвольном ориентированном графе каждый узел может иметь N исходящих дуг.

    (рис 6.5) Представление программы ориентированным графом.

    Эта структура не просто показывает потоки информации и управление ходом выполнения программы. Узел в графе соответствует сегменту программы и имеет две части: "действие" и "ветвление". Часть "действие" содержит константы и функцию, которая выполняется при достижении данного узла в процессе интерпретации программы. Данные передаются между узлами через стек. Часть "действие" получает данные из (верхушки) стека и после выполнения соответствующей функции передает преобразованные данные снова в стек. После того выполняется "ветвление", которое определяет ветвь следующего выполняемого узла на основании данных стека, памяти или специальных констант ветвления. Очевидно, что в процессе интерпретации необязательно посещаются все вершины графа. Каждая программа имеет две специальных вершины "старт" и "конец" и кроме этого может содержать некоторые специальные узлы типа "вызов подпрограммы". Поскольку граф может содержать обратные связи, то вершина "конец" при интерпретации на некоторых входных данных может быть недостижимой вследствие "зацикливания". Поэтому необходимо контролировать и ограничивать время выполнения программы.

    6.2.4. Другие формы представления программ.

    Кроме рассмотренных трех форм представления, в настоящее время предложены многочисленные модификации и обобщения ГП, в которых разработаны различные формы представления особей и определены на них соответствующие генетические операторы кроссинговера и мутации. В табл.6.3 приведены основные структуры, используемые в настоящее время в ГП [8].

    В работе [9] для представления программ введены более сложные структуры в узлы дерева. Этот подход основан на хорошо известном методе группового учета аргументов (МГУА) Ивахненко, который широко используется при решении многих задач. При этом внутренние узлы дерева соответствуют полиномам второго порядка, которые используются в МГУА. Далее на указанных древовидных структурах определяются генетические операторы кроссинговера и мутации. При этом обычно используется локальный поиск и замена функций – процедура "переразметки", которая применяется для нахождения оптимальных значений параметров при заданной структуре. При определении фитнесс-функции здесь часто используют кратный (множественный) регрессионный анализ. Иногда используются и другие меры, например, сложность особи (к примеру, минимальная длина) или их комбинация.

    Клеточное кодирование [10] также использует структуру графов, которые широко используются в компьютерных науках (для описания электрических цепей, нейронных сетей, конечных автоматов и т.п.), и поэтому область применения такого представления достаточно широкая (в частности, структурный синтез). Основная идея заключается в том, что фенотип отделяется от генотипа, который основан на древовидной структуре. С другой стороны фенотип представляется структурой (возможно циклического) графа. Генотип представляется грамматическим деревом (разбора), использующим правила грамматического вывода. При этом используются древовидные грамматики, в которых при грамматическом выводе нетерминальный символ заменяется соответствующим деревом. Эти операции выполняются параллельно подобно $$L$$-системам [11]. Клеточное кодирование позволяет определить модули, которые последовательно могут применяться в различных участках грамматического дерева.

    Структура Описание
    $$S$$-выражение Древовидная структура
    Основана на методе группового учета аргументов (МГУА) Древовидная структура
    Язык регистрового уровня (TB) Линейная постфиксная запись
    Язык регистрового уровня (JB) Линейная постфиксная запись
    Битовая Линейные геномы
    Битовая Машинные команды
    Абстрактные типы данных Списки, очереди, стеки
    Правила продукций Грамматики
    Клеточное кодирование Древовидные грамматики
    Правила продукций Графы
    Параллельные алгоритмы (PADO) Графы

    Генетическое программирование на основе формальных грамматик предложено в работе [12]. Здесь используется очень общая форма ГП, которая основана на использовании теории формальных языков и грамматик. В процессе эволюции особей применяются контекстно-свободные грамматики, что позволяет преодолеть некоторые ограничения в теоретическом плане, характерные для классического ГП. Применение КС-грамматик гарантирует синтаксическую корректность потомков в процессе эволюции и позволяет определить простые и эффективные генетические операторы. Кроссинговер выполняется следующим образом. Случайным образом выбирается нетерминальный символ в грамматическом дереве первого родителя и затем производится поиск этого же нетерминала во втором родителе. Если во втором родителе этого нетерминала нет, то оператор не выполняется. Иначе производится обмен поддеревьев, соответствующих нетерминальным символам. Мутация выполняется путем "выращивания" грамматического дерева из случайным образом выбранного нетерминального символа. В дальнейших работах авторы допускают изменение продукций КС-грамматики в процессе эволюции для вывода лучших правил. Известны также работы, в которых используются и более общие контекстно-зависимые грамматики.

    В работах [8,11] определено понятие $$L$$-систем, которые первоначально были разработаны для моделирования структур биологических формаций или, в более общем контексте, развивающихся процессов. В этом подходе замена нетерминальных символов при "выращивании деревьев" выполняется параллельно. Это позволяет развиваться различным ветвям независимо друг от друга, как это имеет место в реальных сложных процессах. При этом целью эволюции является генерация $$L$$-системы, множество продукций которой позволяет решать поставленную задачу. Таким образом, здесь в качестве особи выступает вся $$L$$-система. Каждая $$L$$-система оценивается после вывода грамматического дерева. Следует отметить, что такие особи не могут изменяться произвольным образом, а развиваются согласно правилам мета-грамматики.

    Интересным является применение в ГП методов нечеткой (fuzzy) логики [9]. В частности, нечеткая логика может использоваться в адаптивном ГП для подстройки таких параметров как вероятности кроссинговера и мутации в процессе эволюции, что позволяет повысить эффективность. При этом используется коэволюция правил нечеткого контроллера, который управляет параметрами ГП.

    Поиск решения на основе генетического программирования может быть реализован согласно алгоритму, блок-схема которого представлена на рис.1.1. Но, учитывая специфику кодирования особи, имеющую переменную длину и сложную структуру, реализация каждого этапа этого алгоритма имеет свои особенности, которые мы рассмотрим ниже.

    6.3. Инициализация начальной популяции

    Данный этап в ГП является не таким простым, как в классическом ГА, где генерация случайных двоичных строк не представляет особых проблем. Это связано с различной сложностью особей и их структурой. Естественно методы инициализации начальной популяции различны для разных форм представления программ. Одним из важнейших параметров в ГП является максимально возможный размер (сложность) программы.

    6.3.1. Инициализация древовидных структур

    Для деревьев в качестве меры сложности используется максимальная глубина (иногда называется высота) дерева или общее число узлов в дереве. Глубиной узла называется минимальное число узлов, которые необходимо пройти от корня дерева к этому узлу. Максимальной глубиной дерева $$D_m$$ называется максимально возможная глубина в дереве для терминального символа (листа). Если арность каждого узла равна двум, то общее число узлов не превышает $$2^{D_m}$$, которое также используется в качестве меры сложности.

    Инициализация древовидных структур выполняется путем случайного выбора функциональных и терминальных символов при заданной максимальной глубине дерева. Пусть для определенности выбраны следующее терминальное $$T=\{a,b,c,d,e\}$$ и функциональное $$F=\{+,-,*,\% \}$$ множества, где $$\%$$ означает деление нацело. Применяются два основных метода: 1) полная (full) и 2) растущая (grow) инициализация [2].

    (рис 6.6) Деревья, генерируемые при инициализации разными методами: а) полная (левые 3 дерева); б) растущая (правые 3 дерева).

    В полном методе при генерации дерева, пока не достигнута максимальная глубина, допускается выбор только функциональных символов, а на последнем уровне (максимальной глубины) выбираются только терминальные символы. Например, на рис.6.6 а) представлено дерево с $$D_m=3$$.

    В растущей инициализации генерируются нерегулярные деревья с различной глубиной листьев вследствие случайного на каждом шаге выбора функционального или терминального символа. Здесь при выборе терминального символа рост дерева прекращается по текущей ветви и поэтому дерево имеет нерегулярную структуру.

    Например, для $$D_m=3$$ при растущей инициализации для указанных функциональных и терминальных символов могут быть построены деревья, приведенные на рис.6.6 б).

    При использовании первого метода начальная популяция содержит однородное множество структур, что способствует вырождению генетического материала (и преждевременной сходимости к локальным экстремумам). Поэтому на практике часто эти два метода используют одновременно следующим образом. Начальная популяция генерируется так, чтобы в нее входили деревья с разной максимальной длиной примерно поровну (для нашего примера $$D_m=1, D_m=2, D_m=3, D_m=4$$). Для каждой глубины первая половина деревьев генерируется полным методом, а вторая – растущей инициализацией.

    6.3.2. Инициализация линейных структур

    В этом случае процесс инициализации выполняется совершенно по-другому. Прежде всего, особь-программа разбивается на следующие четыре части:

  • заголовок;
  • тело;
  • "подвал";
  • выход (возврат).
  • Из них только тело программы генерируется с помощью эволюции, остальные части программы являются стандартными и заготавливаются заранее. Алгоритм инициализации можно сформулировать следующим образом:

  • Выбор случайной длины из заданного диапазона;
  • Копирование заготовленного заголовка;
  • Инициализация и пополнение собственно операторов в программу пока не достигнута длина, определенная в пункте 1. Операторы выбираются случайным образом, сначала тип, затем переменная или константа из заданного диапазона;
  • Копирование в конец программы заготовленного "подвала";
  • Копирование в конец программы заготовленных операторов выхода.
  • 6.4. Кроссинговер в генетическом программировании

    В начальной популяции особи (потенциальные решения), как правило, имеют низкие (плохие) значения фитнесс-функции. В процессе эволюции с помощью генетических операторов популяция развивается и значения фитнесс-функции улучшаются. В ГП используются те же, что и в ГА, генетические операторы кроссинговера, мутации и репродукции. Отметим, что в терминах машинного обучения они соответствуют операторам поиска решения. Далее мы рассмотрим выполнение генетических операторов на ранее определенных структурах.

    6.4.1. Выполнение кроссинговера на древовидных структурах

    Для древообразной формы представления используются следующие три основные операторы кроссинговера (ОК):

  • узловой ОК;
  • кроссинговер поддеревьев;
  • смешанный.
  • В узловом операторе кроссинговера выбираются два родителя (два дерева) и узлы в этих деревьях. Первый родитель называется доминантом, второй – рецессивом. Узлы в деревьях могут быть разного типа. Поэтому сначала необходимо убедиться, что выбранные узлы у родителей являются взаимозаменяемыми. Если узел во втором родителе не соответствует типу узла первого родителя, то случайным образом выбирается другой узел во втором родителе, который опять подлежит проверке на предмет совместимости. Далее производится обмен узлов.

    Рассмотрим ОК на следующем примере, представленном на рис.6.7 для родительских особей:$$\frac{3}{4}*x^2+x*y,\ \frac{x}{2}+y+z*y$$

    В кроссинговере поддеревьев родители обмениваются не узлами, а определяемыми ими поддеревьями. Оператор выполняется следующим образом:

  • Выбираются родители (один – доминантный, другой – рецессивный). Далее необходимо убедиться, что выбранные узлы взаимозаменяемы, т.е. принадлежат одному типу. Иначе, как и в предыдущем случае, в рецессивном дереве выбирается другой узел с последующей проверкой.
  • Затем производится обмен поддеревьев, которые определены этими узлами.
  • Вычисляется размер ожидаемых потомков. Если ожидаемый размер (сложность потомка) не превышает заданный порог, такой обмен ветвями запоминается. На рис.6.8показан пример выполнения этого ОК.
  • Этот тип ОК является основным. При этом под размером (под)дерева понимается, как и ранее, либо его высота, либо число его вершин.

    При смешанном операторе кроссинговера для некоторых узлов выполняется узловой ОК, а для других - кроссинговер поддеревьев.

    Отметим, что выполнение кроссинговера на древовидных структурах выполняется достаточно просто с помощью $$S$$-выражений, что показано в таблице 6.4. Например, кроссинговер поддеревьев сводится к обмену "скобками", которые здесь соответствуют поддеревьям.

    В таблице 6.5 приведены типовые операторы кроссинговера для древовидных структур[3,8].

    (рис 6.7) Узловой кроссинговер.
    Родители Потомки
    $$( - ( * a b ) c )$$ $$( - (- ( + d e) f) c )$$
    $$( * (- ( + d e) f ) g)$$ $$( * (* a b) g)$$
    Наименование Описание производимых действий
    Кроссинговер обмена поддеревьев Обмен поддеревьями родителей
    Само-кроссинговер Обмен поддеревьями в одном родителе
    Модульный кроссинговер Обмен модулями (фрагментами) родителей
    Контекстно-сохраняющий кроссинговер Полный обмен поддеревьями, если он согласуется с контекстом, иначе частичный обмен
    (рис 6.8) Кроссинговер поддеревьев.

    6.4.2. Кроссинговер на линейных структурах

    Скрещивание на линейных структурах выполняется достаточно просто. Здесь у родителей выполняется обмен линейными сегментами, как это показано на рис.6.9.

    При этом в каждом из двух родителей выбирается сегмент, начиная со случайной позиции и имеющий случайную длину. Далее производится обмен выбранных сегментов программ. Если размер хотя бы одного из потомков превышает некоторый порог, то результаты ОК аннулируются. При этом точки скрещивания выбираются только между операторами.

    6.4.3. Выполнение кроссинговера для графоподобных структур

    Кроссинговер комбинирует генетический материал из двух родительских программ путем обмена некоторых частей программ. ОК для этой модели может быть реализован двумя способами.

    Первый способ похож на кроссинговер поддеревьев, который определен на древообразных структурах путем обмена поддеревьев. Здесь обмен производится подграфами, как это показано на рис.6.10.

    (рис 6.9) Кроссинговер на линейных структурах.

    При этом в каждой особи-родителе выбирается случайным образом множество смежных узлов и производится обмен двух подграфов между этими подграфами.

    (рис 6.10) Кроссинговер на графоподобных структурах.

    Второй тип кроссинговера выполняет линейный ОК. Здесь для каждого родителя выбирается линейный сегмент (в одном узле графа), начинающийся со случайной позиции данного сегмента и имеющий случайную длину. Далее, как обычно, производится обмен этими линейными сегментами. Если размер хотя бы одного потомка превышает некоторый порог, то результаты ОК аннулируются и выполняется обмен равными сегментами меньшей длины.

    Обычно линейный ОК выполняется в вероятностью $$P_i=0,1$$. Пример выполнения этого ОК представлен на рис.6.11. Как правило, в процессе эволюции используются ОК обоих типов. В целом ОК выполняется следующим образом:

  • Выбор точки скрещивания $$P_1,P_2$$ в обоих родителях
  • Выбор с заданной вероятностью типа ОК ( для 1-го типа с вероятностью $$P_G$$, для 2-го с вероятностью $$1-P_G$$).Если выбран 1-й тип то переход на п.3, иначе на п.4.
  • Если размер потомка не превышает порог, то выполнить ОК 1-го типа, переход на п.5.
  • Если размер потомка не превышает порог, то выполнить ОК 2-го типа.
  • Конец.
  • (рис 6.11) Линейный кроссинговер на графах.

    6.5. Мутация в генетическом программировании

    После выполнения кроссинговера с заданной малой вероятностью $$P_c$$ выполняется мутация для выбранной одной особи-программы.

    6.5.1. Выполнение мутации на древовидных структурах

    Для деревьев используются следующие операторы мутации (ОМ):

  • узловая;
  • усекающая;
  • растущая.
  • Узловая мутация выполняется следующим образом:

  • выбрать случайным образом узел, подлежащий мутации, определить его тип;
  • случайным образом выбрать из соответствующего множества вариантов узлов, отличный от рассматриваемого узла;
  • поменять исходный узел на выбранный.
  • Усекающая мутация производится так:

  • определяется или выбирается узел;
  • случайным образом выбирается терминальный символ из заданного множества;
  • обрезается ветвь узла мутации;
  • вместо обрезанной ветви помещается выбранный терминальный символ.
  • На рис.6.12 показан пример выполнения усекающей мутации.

    (рис 6.12) Усекающая мутация.

    Растущая мутация выполняется следующим образом:

  • определяется узел мутации;
  • если узел нетерминальный, то необходимо отсечь ветви, исходящие из него, иначе выбрать другой узел;
  • вычислить размер (сложность) остатка дерева;
  • вместо отсеченного дерева вырастить случайным образом новое дерево так, чтобы размер нового построенного дерева не превышал заданный порог.
  • Это очень мощный оператор, который обладает большими возможностями. На рис.6.13 представлен пример выполнения этого оператора. В некоторых работах растущая мутация выполняется с использованием формальных грамматик. При этом нетерминальный символ заменяется в соответствии с правилами грамматики, а не случайным образом.

    В настоящее время для древовидных структур в ГП разработаны различные типы мутаций, которые производят различные изменения и представлены в табл.6.6 [5].

    Для примера на рис.6.14 представлен результат выполнения мутации - перестановки (аргументов) для дерева, представляющего выражение $$\frac{1}{2}*(d+e)$$.

    (рис 6.13) Растущая мутация.
    Наименование Описание производимых действий
    Точечная мутация Случайное изменение типа одного узла из того же класса
    Перестановка Перестановка аргументов одного узла
    "Подъем" Случайная генерация новой особи из поддерева
    Растущая мутация Замена терминального символа случайным поддеревом
    Секущая мутация Замена поддерева случайным терминальным символом
    Мутация поддерева Замена поддерева случайным поддеревом
    (рис 6.14) Пример мутации – перестановки аргументов.

    6.5.2. Выполнение мутации на линейных структурах

    Здесь мутация выполняется совершенно другим способом. Прежде всего, из линейного сегмента (тела программы) случайно выбирается оператор (команда) и в нем производятся изменения одного из следующих видов:

  • имя переменной (или регистра) заменяется на другое случайным образом выбранное из заданного множества;
  • оператор может быть также изменен случайным образом на некоторый другой из функционального множества;
  • может быть случайно изменено значение константы на некоторое другое значение из заданного диапазона.
  • Мутация констант выполняется путем стандартного отклонения от текущего значения $$P=P+\delta\cdot h_m$$, где $$P$$-текущее значение, $$1\le\delta\le 1$$-случайное число и $$h_m$$–шаг мутации. При этом вероятность мутации обычно убывает по мере удаления от начала процесса.

    6.5.3. Выполнение мутации на графоподобных структурах

    В этом случае оператор мутации случайным образом выбирает множество узлов и изменяет либо узел линейной программы, либо узел ветвления, либо число исходящих из условного оператора ветвей. То есть ОМ не строит новые линейные последовательности. Измененная ОМ программа помещается назад в популяцию.

    6.6. Фитнесс-функция в генетическом программировании

    В отличие от генетических алгоритмов, где часто при поиске экстремумов в качестве фитнесс-функции используется исходная целевая функция, в ГП фитнесс-функция обычно определяет меру близости между реальными $$y_i$$ и требуемыми $$d_i$$ выходными значениями (например, при использовании ГП в имвольной регрессии ). Поэтому в качестве фитнесс-функции часто используется абсолютная или квадратичная ошибка.

    В этом случае фитнесс-функция использует обучающее множество данных, на котором выполняется обучение системы. С помощью фитнесс-функции в процессе обучения реализуется обратная связь, которая показывает насколько хорошо данная особь-программа реализует необходимую функцию на обучающем множестве. Для этого можно использовать различные виды фитнес-функций, некоторые из которых мы рассмотрим ниже.

    "Непрерывной" (continuous) фитнесс-функцией называют [5] функцию вычисления фитнесс-значений, в которой малое улучшение в обучении программы вызывает малые улучшения измеряемых фитнесс-значений и большие улучшения в обучении связаны соответственно с большими изменениями (в сторону улучшения) фитнесс-значений. В [5] отмечается, что такая "непрерывность" является важным свойством, так как позволяет ГП итеративно улучшать программы в процессе эволюции.

    "Стандартизованой" [5] фитнесс-функцией называют преобразованную фитнесс-функцию, которая лучшим особям приписывает нулевое значение.

    "Нормализованной" [5] фитнесс-функцией называют преобразованную фитнесс-функцию, которая для всех особей дает значения в интервале (0,1).

    Рассмотрим следующий пример [5] с обучающей выборкой, представленной табл.6.7. Каждая строка таблицы определяет один элемент $$(x,y)$$ обучающей выборки. Необходимо в процессе эволюции построить программу (или формулу в случае символьной регрессии), которая для каждого входного значения x вычисляет необходимое (в соответствии с табл.6.5) значение y (фактически нам необходимо реализовать функцию $$f(x)=x^2+x$$).

    Вход $$x$$ Выход $$d$$
    1 1 2
    2 2 6
    3 4 20
    4 7 56
    5 9 90

    Рассмотрим в качестве фитнесс-функции ошибку в метрике абсолютных значений $$f_a=\sum_{i=1}^n |y_i-d_i|$$, где суммирование выполняется по обучающей выборке. Эта фитнесс-функция соответствует первому определению "непрерывной", поскольку чем ближе значения $$y_i$$ к $$d_i$$, тем меньше значение фитнесс-функции. Приведенная фитнесс-функция является также стандартизованной, так как в случае идеального решения дает нулевое значение.

    Часто в качестве фитнесс-функции также используют квадратичную ошибку $$f_s=\sum_{i=1}^n (y_i-d_i)^2$$.Таблица 6.8 показывает различие для этих двух фитнесс-функций в том случае, если на некотором (промежуточном) этапе в качестве особи оценивается (плохо обученная) программа, реализующая функцию $$f(x)=x^2$$.

    Мы рассмотрели использование в качестве фитнесс-функции ошибки в двух метриках, которые характерны для применения ГП в качестве символьной регрессии, что будет детальнее рассмотрено в разделе 6.8.

    Вход $$x$$ Выход $$d$$ Выход $$y$$ Ошибка $$f_a$$ Ошибка $$f_s$$
    1 1 2 1 1 2
    2 2 6 4 2 4
    3 4 20 16 4 16
    4 7 56 49 7 49
    5 9 90 81 9 81
    Общая ошибка 23 151

    Подобный подход применяется также во многих других задачах, где используются следующие фитнесс-функции:

  • число правильно отображаемых пикселов в задачах обработки изображений;
  • число столкновений робота со стенами при обучении обхода препятствия;
  • число правильно классифицируемых примеров в задачах классификации;
  • ошибка между реальными и ожидаемыми значениями в задачах прогнозирования;
  • приз победителя в игровых задачах;
  • количество "пищи", найденной агентом, в многоагентных системах ("искусственная жизнь" и т.п.)
  • Естественно разработано множество фитнесс-функций других типов, их вид существенно зависит от исследуемой проблемы, а применение ГП не ограничивается задачами символьной регрессии. Кроме приведенных стандартных разработано много других методов определения фитнесс-функции, в частности теоретико-игровых и основанных на идее коэволюции, где особи соревнуются между собой без явного вычисления значений. В некоторых случаях, кроме близости решений, учитываются и другие критерии, например, длина или время выполнения программы. В этом случае говорят о многокритериальных фитнесс-функциях.

    6.7. Интроны

    Программы, построенные с помощью методов ГП, имеют тенденцию к накоплению интронов – ненужных и непригодных участков кода

    Например:

    (NOT(NOT x)) ,
    (AND (ORXX)),
    (+… (-XX)),
    (+X0),
    (*X1),
    (*(DIV XX)),
    (MOVE_LEFT MOVE_RIGHT),
    (IF (2=1) . . . ),
    A:=A.
    

    Таких фрагментов в программе возникает достаточно много (их количество может достигать 60%), и обнаружение и удаление интронов представляет серьезную проблему в ГП. Разработаны специальные методы для их устранения. Интересно отметить, что в живой природе интронов также достаточно много (в частности, на генном уровне существуют "лишние" участки ДНК).

    6.8. Общий алгоритм генетического программирования

    Таким образом, для решения задачи с помощью ГП необходимо выполнить описанные выше предварительные этапы:

  • Определить терминальное множество;
  • Определить функциональное множество;
  • Определить фитнесс-функцию;
  • Определить значения параметров, такие как мощность популяции, максимальный размер особи, вероятности кроссинговера и мутации, способ отбора родителей, критерий окончания эволюции (например, максимальное число поколений) и т.п
  • После этого можно разрабатывать непосредственно сам эволюционный алгоритм, реализующий ГП для конкретной задачи. Как и в случае ГА здесь также возможны различные подходы, которые рассмотрены в разделах 4 и 5.

    Например, решение задачи на основе ГП можно представить следующей последовательностью действий:

  • установка параметров эволюции;
  • инициализация начальной популяции;
  • $$t:=0$$
  • оценка особей, входящих в популяцию;
  • $$t:=t+1$$
  • отбор родителей;
  • создание потомков выбранных пар родителей – выполнение оператора кроссинговера;
  • мутация новых особей;
  • расширение популяции новыми порожденными особями;
  • сокращение расширенной популяции до исходного размера;
  • если критерий останова алгоритма выполнен, то выбор лучшей особи в конечной популяции – результат работы алгоритма. Иначе переход на шаг 4.
  • Следует отметить, что в ГП достаточно часто применяется асинхронный ГА, рассмотренный в разделе 4.6.

    6.9. Символьная регрессия

    Этот раздел является одним из важнейших приложений ГП. Данный термин подчеркивает то, что здесь объектом поиска является символьное описание модели, в отличие от множества коэффициентов в стандартных методах. Этот подход существенно отличается от других методов регрессии и использования нейросетей прямого распространения, где структура (и сложность) модели предполагается известной и фактически необходимо найти только ее коэффициенты. В случае символьной регрессии вид и сложность функции заранее неизвестны и могут изменяться в процессе поиска.

    Задача регрессии может быть определена на основе множества значений входных независимых переменных $$x$$ и зависимой выходной переменной $$y$$. Целью поиска является аппроксимация $$y$$ с помощью переменных $$x$$ и коэффициентов $$w$$ следующим образом $$y=f(x,w)+\varepsilon$$, где $$\varepsilon$$ представляет шум (ошибку).

    В стандартних методах регресии вид функции $$f$$ предполагается известным, например, в линейной регресии - $$f(x,w)=w_0+w_1x_1+\dots +w_nx_n$$. Здесь коэффициенты $$w_i$$ обычно находятся методом наименьших квадратов. В нелинейных методах, например с использованием нейронных сетей прямого распространения, функция имеет вид $$f(x,w)=w_0\cdot g(w_hx)$$. Здесь коэффициенты $$w_0$$ и $$w_h$$ представляют синаптические веса нейронной сети выходного и скрытых слоев соответственно.

    Как уже отмечалось, символьная регрессия на основе ГП не использует некоторую заранее предопределенную форму функции $$f(x,w)$$. Здесь функция $$f(x,w)$$ представляется древовидной структурой и строится эволюционным методом с использованием определенного функционального и терминального множеств. В качестве фитнесс-функции обычно используется квадратичная ошибка, которая оценивает качество решения и обеспечивает обратную связь при поиске решения. Для определенности обозначим функции множества, зависящие от одной переменной через $$h_1,\dots,h_k$$ и функции от двух переменных как $$g_1,\dots,g_l$$. В этой нотации функция $$f(x,w)$$ представляется в виде суперпозиции функций $$h_i,g_j$$, и например, может иметь следующий вид: $$f(x,w)=h_1(g_2(g_1(x_3,w_1),h_2(x_1)))$$.

    Заметим, что в символьной регрессии при поиске решения не используются численные методы, например, градиентные или стохастические.

    Далее рассмотрим детально пример использования символьной регрессии из [5] для аппроксимации данных, представленных в табл.6.9. Необходимо найти функцию $$f(x)$$, которая аппроксимирует с заданной точностью эти "экспериментальные" данные. Для решения задачи определим в соответствии с вышесказанным:

    Терминальное множество: переменная x и константы в диапазоне [-5,5];

    Функциональное множество: арифметические функции $$+, - , * , \%$$ (защищенное деление).

    Koza [2] ввел следующую удобную и "прозрачную" форму для перечисления параметров, которая представлена в табл.6.10.

    Далее приведем некоторые полученные экспериментальные данные результатов эволюции для различных запусков программ из [5]. В начальной популяции после инициализации лучшая особь представлена деревом рис.6.15, которая реализует (не минимальным образом !) функцию $$f_0(x)=\frac{x}{3}$$.В последующих рисунках рис.6.16,рис.6.17,рис.6.18,рис.6.19,рис.6.20 представлены лучшие особи последующих поколений. Здесь в первом поколении лучшая особь реализует $$f_1(x)=\frac{x}{6-3x}$$.В последующих рисунках рис.6.16,рис.6.17,рис.6.18,рис.6.19,рис.6.20 представлены лучшие особи последующих поколений. Здесь в первом поколении лучшая особь реализует $$f_1(x)=\frac{x}{6-3x}$$ и соответствующее дерево рис.6.16 сильно избыточно.

    Вход $$x$$ Выход $$y$$
    1 0.000 0.000
    2 0.100 0.005
    3 0.200 0.020
    4 0.300 0.045
    5 0.400 0.080
    6 0.500 0.125
    7 0.600 0.180
    8 0.700 0.245
    9 0.800 0.320
    10 0.900 0.405

    Аналогично во втором поколении лучшая особь реализует функцию $$f_2(x)=\frac{x}{x(x-4)-1+\frac{4}{x}-\frac{\frac{9(x+1)}{5x}+x}{6-3x}}$$ и дерево тоже сильно избыточно. Наконец в третьем поколении получена лучшая особь $$f_3(x)=\frac{x^2}{2}$$, которая дает оптимальное решение в простейшей форме.

    (рис 6.15) Лучшая особь в поколении 0. (рис 6.16) Лучшая особь поколения 1
    Параметры Значения
    Цель: Эволюция функции, аппроксимирующей данные Табл.6.6
    Терминальное множество Переменная $$x$$, Целые от –5 до +5
    Функциональное множество ADD, SUB, MUL, DIV
    Мощность популяции: 600
    Вероятн. кроссинговера: 0.90
    Вероятность мутации: 0.05
    Отбор родителей: турнирный, с мощностью тура 4
    Максимальное число поколений: 100
    Максимальная глубина после кроссинговера: 200
    Максимальная глубина мутации: 4
    Метод инициализации: Растущая
    (рис 6.17) Лучшая особь поколения 2. (рис 6.18) Лучшая особь поколения 3.

    В табл.6.11 для сравнения представлены значения функций лучших особей первых поколений (0-3).Отметим, что была выполнена еще одна итерация (4-е поколение). На рис.6.19 представлена лучшая особь четвертого поколения, которая также реализует функцию $$f_4(x)=\frac{x^2}{2}$$, но избыточность соответствующего дерева выросла. Конечно, для данного примера можно было использовать и классические методы регрессии, но он носит чисто иллюстративный характер.

    (рис 6.19) Лучшая особь поколения 4.

    Интересным примером является машинный вывод третьего закона Кеплера на основе символьной регрессии. Итак, есть экспериментальные данные, которые связывают радиус и период обращения планет солнечной системы, которые представлены в табл.6.12 (в астрономических единицах). Напомним, что согласно закону Кеплера, период обращения планеты пропорционален корню квадратному из третьей степени радиуса орбиты $$P=\sqrt{(r)^3}$$, где $$r$$ – радиус орбиты планеты (чаще используется формулировка $$\frac{r^3}{P^2}=c$$). Для решения этой задачи с помощью символьной регрессии возможные параметры представлены в табл.6.13. На рис.6.20 представлено полученное с помощью символьной регрессии дерево, которое представляет закон Кеплера.

    Во многих работах [2,5] приведены результаты для более сложных зависимостей, но они требуют для представления результатов большого объема.

    $$y$$ $$f_0$$ $$f_1$$ $$f_2$$ $$f_3$$
    1 0.000000 0.000000 0.000000 0.000000 0.000000
    2 0.005000 0.033333 0.017544 0.002375 0.005000
    3 0.020000 0.066667 0.037037 0.009863 0.020000
    4 0.045000 0.100000 0.058824 0.023416 0.045000
    5 0.080000 0.133333 0.083333 0.044664 0.080000
    6 0.125000 0.166667 0.111111 0.076207 0.125000
    7 0.180000 0.200000 0.142857 0.1222140 0.180000
    8 0.245000 0.233333 0.179487 0.188952 0.245000
    9 0.320000 0.266667 0.222222 0.287024 0.320000
    10 0.405000 0.300000 0.272727 0.432966 0.405000
    Планета Радиус - $$r$$ Период - $$p$$
    Меркурий 0,387 0,24
    Венера 0,723 0,62
    Земля 1,000 1,000
    Марс 1,524 1,88
    Юпитер 5,203 11,86
    Сатурн 9,569 29,46
    Уран 19,309 84,01
    Нептун 30,284 164,79
    Плутон 39,781 247,69

    Следует отметить, что мощность популяций используемых в ГП на порядок выше, чем в классических ГА, а число поколений, как правило, меньше. Применение ГП в качестве символьной регрессии является важным для многих технических задач (например, в гидро- и аэродинамике), но далеко не единственным.

    (рис 6.20) Дерево, представляющее третий закон Кеплера

    Как показано выше, символьная регрессия может использоваться для получения эмпирических зависимостей на основе экспериментальных данных. Она широко применяется в эконометрике при решении задач моделирования и прогнозирования. Кроме этого, символьная регрессия с успехом применяется в символьных вычислениях, включая символьное дифференцирование и интегрирование, решение дифференциальных и интегральных уравнений в символьном виде и т.п.[2].

    6.10 Модульное построение программ в генетическом программировани

    Эволюционный подход в представленном выше виде порождает слабо структурированные программы, что подтверждают приведенные примеры. Напротив, программист (человек) при проектировании программ, как правило, применяет модульный принцип построения, где решение разбивается на отдельные небольшие (часто используемые) участки кода, которые затем используются в разработке программы. Для этого широко используется аппарат функций и подпрограмм, что позволяет существенно упростить как процесс проектирования, так и сами разрабатываемые программы. Этот подход является типичным примером применения стратегии "разделяй и властвуй", которая широко используется при проектировании программ.

    Параметры Значения
    Цель: Вывод третьего закона Кеплера, аппроксимируюшей данные табл.6.12
    Терминальное множество Переменная $$r$$(расстояние от солнца в астрономических единицах)
    Функциональное множество $$+, - , * ,^ , sqrt, \sin, \cos$$
    Фитнесс-функция Сумма абсолютных значений разности между реальным периодом и даваемым текущей формулой
    Мощность популяции: 500
    Вероятность кроссинговера: 0.80
    Вероятность мутации: 0.05
    Максимальное число поколений: 50

    Одним из самых перспективных направлений повышения эффективности ГП является использование модульного принципа, в котором идентифицируются некоторые функциональные единицы программы, которые оформляются по определенным стандартам и сохраняются для дальнейшего использования.

    Модулем называют последовательность (соседних) операторов программы, ограниченную некоторыми окаймляющими элементами, который имеет общий идентификатор. Модуль должен обладать, по крайней мере, следующими свойствами:

  • логическая замкнутость;
  • реализация по принципу "черного ящика";
  • должен иметь интерфейс для взаимодействия с другими модулями (множество операций и типов данных).
  • Модульный принцип является средством инкапсуляции блоков кода. Такие блоки оформляются в виде подпрограмм, которые затем могут многократно использоваться в главной программе или включаться в другие подпрограммы. Это позволяет существенно уменьшить сложность программы за счет оформления часто используемых идентичных участков кода в виде подпрограмм. Отметим, что меньшая сложность (длина) программ способствуют их "выживанию" в процессе эволюционного поиска. В ГП используется различная техника для реализации модульного принципа. Самыми известными методами являются автоматически определяемые функции (АОФ) (automatically defined functions –ADF), которые введены в [2] и детально исследованы во многих работах [2,5].

    ADF построены по аналогии с функциями языка LISP. Поэтому программа, содержащая ADF, также представляется древовидной структурой, но при этом дерево состоит из двух частей (поддеревьев):

  • главной программы, которая, прежде всего, оценивается с помощью фитнесс-функции;
  • определение функций. Пример такой структуры приведен на рис.6.21.
  • Эти два поддерева соответствуют структуре программы на языке программирования высокого уровня, подобном С++, Паскаль и т.п., где главная программа (тело) сопровождается "объявлениями", где описаны соответствующие функции. Отметим, что обе указанные компоненты должны участвовать в эволюции, которая в этом случае производит программу, построенную в соответствии с модульным принципом.

    На рис.6.21 корень дерева"программа" соответствует программе в целом и объединяет ее различные части. Узел "определение" является корневым для поддерева, содержащего описание функции ADF0. В случае необходимости использования нескольких функций каждой из них необходимо выделить соответствующее поддерево ADF для описания функции.

    (рис 6.21) Дерево, содержащее ADF

    Левая ветвь, исходящая из узла "определение", содержит имя функции (ADF0), которое включается в функциональное множество главной программы и используется для вызова соответствующей функции. Справа от узла имени расположен узел, представляющий список аргументов данной функции. Этот список определяет имена входных переменных аргументов функции. Эти имена входят в терминальное множество тела соответствующей функции. Этот подход аналогичен определению функции в С++ или Паскале, где необходимо дать имя функции и определить входные переменные вместе с описанием тела функции. Следует отметить, что использование рекурсии, как правило, не допускается вследствие значительных трудностей реализации.

    Очевидно, что данный подход требует применения специального, синтаксически ограниченного, кроссинговера. Использование описанных выше видов кроссинговера для древовидных структур в этом случае невозможно. Отметим, что, различные поддеревья (главная программа и описание функций) имеют свои (существенно отличающиеся) функциональные и терминальные множества. Очевидно, что при кроссинговере нельзя производить обмен некоторыми частями между различными типами поддеревьев. Поэтому, прежде всего, при выполнении кроссинговера производится выбор поддерева в одном из поддеревьев первого родителя (главной программе или функции). Соответственно точка кроссинговера (или поддерево) во втором родителе должна быть выбрана в поддереве того же типа, только в этом случае возможен обмен фрагментами деревьев.

    До запуска программы поиска с использованием данного подхода необходимо определить число ADF функций и допустимое число аргументов для каждой функции. То есть мы должны в каком-то смысле задать структуру дерева. Естественно, при инициализации мы должны придерживаться этой структуры. При этом различные поддеревья (главная программа и функции) генерируются независимо (случайными методами).

    При использовании данного подхода необходимо дополнительно выполнить следующие действия:

  • выбрать число функций ADF;
  • фиксировать число аргументов для каждой ADF;
  • определить возможные связи между разными ADF, если используется больше одной функции;
  • определить терминальные функциональные множества для главной программы и ADF;
  • определить фитнесс- функцию для оценки особей;
  • определить параметры эволюционного алгоритма.
  • Основным недостатком данного подхода является необходимость определения заранее структуры программы, которая включает число ADF функций, число аргументов и т.п.

    Кроме ADF в ГП применяются и другие методы модульного построения программ [5]. К ним относится использование автоматически определяемых макросов (automatically defined macros), автоматическое определение циклов do while, until и рекурсии, применение сложных и абстрактных типов данных и т.д. С одной стороны они позволяют получать более компактные программы, но с другой их применение существенно осложняет сам процесс их построения.

    Контрольные вопросы

  • Чем отличаются терминальное и функциональное множества?
  • Какие структуры используются для представления программ в ГП?
  • Опишите древовидное представление программы.
  • Какой тип памяти используется в древовидном представлении?
  • Опишите линейное представление программы.
  • Опишите представление программы в виде графа.
  • Какие два метода используются в инициализации древовидных структур?
  • Как производится инициализация линейных структур?
  • Какие виды кроссинговера вы знаете для древовидных структур?
  • Как выполняется кроссинговер на линейных структурах?
  • Какие виды кроссинговера вы знаете для графоподобных структур?
  • Какие виды мутации вы знаете для древовидных структур?
  • Как производится мутация на линейных структурах?
  • Как можно определить фитнесс-функцию в ГП?
  • Что такое интроны?
  • Приведите общий алгоритм ГП.
  • Чем отличается символьная регрессия от обычной?
  • Как можно в ГП использовать принцип модульного построения программ?
  • Упражнения

  • Разработать эволюционный алгоритм, реализующий ГП для нахождения заданной по варианту функции (таб. 6.14).
  • Структура для представления программы – древовидное представление.
  • Терминальное множество: переменные $$x_1, x_2, x_3, \dots,x_n,$$, и константы в соответствии с заданием по варианту.
  • Функциональное множество:$$+, -, *, /, abs(), \sin(), \cos(), \exp()$$, возведение в степень,
  • Фитнесс-функция – мера близости между реальными значениями выхода и требуемыми.
  • Представить графически найденное решение на каждой итерации.
  • Сравнить найденное решение с представленным в условии задачи.
  • № вв. Вид функции Кол-во первых N Промежуток исследования
    1 $$f_1(x)=\sum_{i=1}^n x_i^2$$ 10 $$-5,12\le x_i\le 5,12$$
    2 $$f_{la}(x)=\sum_{i=1}^n i\cdot x_i^2$$ 9 $$-5,12\le x_i\le 5,12$$
    3 $$f_{1b}(x)=\sum_{i=1}^n\left(\sum_{j=1}^i x_j\right)^2$$ 8 $$-5,536\le x_i\le 65,536$$
    4 $$f_2(x)=\sum_{i=1}^{n-1} 100\cdot (x_{i+1}-x_i^2)^2+(1-x_i)^2)$$ 7 $$-2,048\le x_i\le 2,048$$
    5 $$f_6(x)=10\cdot n+\sum_{i=1}^n(x_i^2-10\cdot \cos(2\cdot\pi\cdot x_i))$$ 9 $$-5,12\le x_i\le 5,12$$
    6 $$f_7(x)=\sum_{i=1}^n -x_i\cdot \sin(\sqrt{|x_i|})$$ 10 $$-500\le x_i\le 500$$
    7 $$f_8(x)=\sum_{i=1}^n \frac{x_i^2}{4000}-\prod_{i=1}^n\cos\left(\frac{x_i}{\sqrt{i}\right)+10$$ 5 $$-600\le x_i\le 600$$
    8 $$f_9(x)=\sum_{i=1}^n|x_i|^{(i+1)}$$ 8 $$-1\le x_i\le 1$$
    9 $$f_{10}(x)=-a\cdot e^{-b\cdot \sqrt{\frac{\sum_{i=1}^n x_i^2}{n}}}-e^{\frac{\sum_{i=1}^n cos(c\cdot x_i)}{n}}+a+e^1;\\a=20;b=0,2;c=2\cdot \pi$$ 4 $$-2.768\le x_i\le 32.768$$
    10 $$f_{11}(x)=-\sum_{i=1}^m c_i\cdot(e^{-\frac{\|x-A(i)\|^2}{\pi}}\cdot\cos\left(\pi\cdot\|\bar x-A(i)\|^2)),\\m=5;A_i,C_i\ne 0$$ 4 $$0\le x_i\le 10$$
    11 $$f_{12}(x)=-\sum_{i=1}^n \sin(x_i)\cdot\left(\sin\left(\frac{i\cdot x_i^2}{\pi} \right ) \right)^{2\cdot m},\\ m=10$$ 5 $$0\le x_i\le \pi$$
    12 $$f_{Bran}(x_1,x_2)=a\cdot(x_2-b\cdot x_1^2+c\cdotx_1-d)^2+e\cdot(1-f)\cdot(x_1)+e,\\a=1,b=\frac{5,1}{4\cdot \pi^2},c=\frac{5}{\pi},d=6,e=10,f=\frac{1}{8\cdot\pi}$$ 2 $$-5\le x_1\le 10,\ 0\le x_2\le \15$$
    13 $$f_{Easo}(x_1,x_2)=-\cos(x_1)\cdot\cos(x_2)\cdot e^{-((x_1-\pi)^2+(x_2-\pi)^2)}$$ 2 $$-100\le x_i\le 100$$
    14 $$f_{Gold}(x_1,x_2)=(1+(x_1+x_2+1)^2\cdot(19-14x_1+3x_1^2-14x_2+6x_1x_2+3x_2^2))\cdot(30+(2x_1-3x_2)^2\cdot(18-32x_1+12x_1^2+48x_2-36x_1x_2+27x_2^2))$$ 2 $$-2\le x_i\le 2$$
    15 $$f_{Sixh}(x_1,x_2)=(4-2.1\cdot x_1^2+x_1^{4/3})\cdot x_1^2+x1\cdot x_2+(-4+4\cdot x_2^2)\cdot x_2^2;$$ 2 $$-3\le x_1\le 3,-2\le x_2\le2$$

    Краткие итоги:

  • изложены основы ГП, основной алгоритм ГП и его параметры;
  • описаны основные формы представления ГП: древовидное, линейное, графоподобное;
  • рассмотрены различные методы инициализации начальной популяции в ГП;
  • описаны генетические операторы кроссинговера и мутации на различных формах представления ГП;
  • изложен на основе ГП метод символьной регрессии;
  • рассмотрено использование модульного построения в ГП.
  • Вернуться к учебному плану