Нейросети — это просто (Часть 26): Обучение с подкреплением

Dmitriy Gizlyk | 24 августа, 2022

Содержание


Введение

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


1. Основы обучения с подкреплением

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

Итак, мы живем в мире, который постоянно меняется. И его изменения в какой-то мере зависят от нас и наших действий. Но, наверное, в большей степени его изменения не зависят от нас. Так как в нем живут ещё и миллионы других людей, которые также совершают некие действия. А ещё есть и множество факторов, которые происходят под действием других сил, не зависящих от жителей этого мира.

Точно так же в обучении с подкреплением выделяется Среда (Environment), которая является олицетворением нашего мира. С ней взаимодействует некий Агент (Agent). Данный агент можно сопоставить с человеком, живущим в этой среде. Как и наш мир, среда постоянно меняется.

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

Аналогично, Среда (Environment) генерирует свое Состояние (State), которое оценивается Агентом (Agent).

Точно так же, как и мы действуем в соответствии со своим мировоззрением, Агент (Agent) совершает Действие (Action), продиктованное его Стратегией (Policy — Политикой).

Под влиянием оказанного воздействия среда изменяется с определенной долей вероятности. При этом за каждое действие Агент (Agent) получает от Среды (Environment) некое Вознаграждение (Rewards). При этом Вознаграждение (Rewards) может быть как положительное, так и отрицательное. Именно по размеру вознаграждение Агент (Agent) может оценить полезность совершенного действия.

Обучение с подкреплением

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

Аналогично, открытая в рынке позиция в момент времени может быть как в плюсе, так и в минусе. Но результат торговой операции можно оценить только после её закрытия.

Подобные случаи демонстрируют, что довольно часто Вознаграждение (Rewards) зависит не от одного отдельного Действия (Action). А от целого ряда последовательных Действий (Action).

Отсюда и следует цель обучения модели. Так же, как и мы в трейдинге стремимся к максимальной прибыли, модель обучается на получение максимального Вознаграждения (Rewards) за определенный конечный интервал. Это может быть как игра, так и некая определенная сессия. Или просто временной отрезок.

И здесь надо обратить внимание на 2 вытекающих требования к применению методов обучения с подкреплением.

Во-первых, изучаемый процесс должен удовлетворять требованием так называемого марковского процесса принятия решений. А попросту, каждое предпринимаемое Агентом (Agent) Действие (Action) зависит только от текущего Состояние (State). И не одно из совершенных ранее Действий (Action) или наблюдаемых Состояний (State) более не оказывает влияние на Действия (Action) Агента (Agent) и изменения Среды (Environment). Все их влияние уже учтено в текущем Состоянии (State).

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

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

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

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

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

Очень часто довольно сложно правильно распределить конечную награду между всеми совершенными действиями на пути достижения финальной цели. К примеру, как разделить прибыль или убыток от сделки между операциями открытия позиции и её закрытия. Первое, что приходит в голову, это разделить поровну между обеими операциями. Но, к сожалению, это не всегда верно. Вы можете открыть позицию в нужный момент и цена начнет идти в вашу сторону. Как определить момент выхода? Мы можете закрыть позицию раньше времени и упустить часть возможной прибыли. А можете передержать позицию и закрыться, когда цена начнет откатывать против вас. И при этом закрыться упустив часть возможной прибыли. А в особо неудачных ситуациях даже с убытком. И в таком случае, казалось бы, хорошая операция открытия позиции получит отрицательное вознаграждение из-за неправильной операции закрытия позиции. Не кажется ли вам это не справедливо? Более того, получив отрицательное вознаграждение вероятно в следующий раз модель сочтет подобную ситуацию для входа не подходящей. И вы упустите ещё больше прибыли.

Конечно, возможна и обратная ситуация. Вы открываете неудачную позицию. Мы не будем сейчас обсуждать причины её открытия. Но вам повезло. Через некоторое время цена разворачивается и идет в вашем направлении. Вам удалось закрыть сделку с прибылью. И модель, получив положительное вознаграждение, считает удачным такой вход и при возникновении подобного паттерна открывает новую сделку. Но в этот раз вам уже так не везёт. И цена не разворачивается.

Да, модель учится не на одной сделке. Собирает и усредняет статистику операций. Но некорректная система вознаграждений может испортить ситуацию. И направить обучение модели не в том направлении.

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


2. Отличие от ранее рассмотренных методов

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

В случае же обучения с подкреплением у нас отсутствует обучающая выборка в привычном понимании. У нас есть некая Среда (Environment), которая генерирует текущее Состояние (State). Да, мы можем сделать выборки различных состояний среды для обучающей выборки. Но есть и другая взаимосвязь среды с агентом. После оценки состояния системы агентом совершается некое действие. Которое оказывает влияние на среду и, в какой-то мере изменяет её. При этом среда должна ещё вернуть ответ на действие в виде Вознаграждения (Rewards).

Мы можем сравнить полученное от Среды Вознаграждением с "эталонным ответом учителя" в методах обучения с учителем. Но здесь есть кардинальное отличие. При обучении с учителем мы имеем единственно верный ответ для каждой ситуации и на нем обучаемся. В случае же обучения с подкреплением мы лишь получаем реакцию на действие нашего Агента. И не понимаем каким образом было сформировано это вознаграждение. Более того, мы не знаем оно максимальное или минимальное. И насколько оно далеко от экстремальных значений. Иными словами, мы знаем действие агента и его оценку. Но мы не знаем каким должно было быть "эталонное" действие. Чтобы это узнать нам нужно совершить все возможные действия из конкретного состояния. И в этом случае мы получим оценку действий в одном состоянии.

Теперь давайте вспомним, что на следующем временном отрезке мы попадаем в новое Состояние Среды, которое зависит от предпринятого Агентом Действия на предшествующем шаге.

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

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

Подведем итоги.

Обучение с учителем Обучение без учителя Обучение с подкреплением
 Обучается до аппроксимации эталонных значений  Изучает структуру данных  Обучается методом проб и ошибок до получения максимальной награды
 Требуются эталонные значения  Не требуются эталонные значения  Требуются реакция среды на действия агента
 Модель не влияет на исходные данные  Модель не влияет на исходные данные  Агент может влиять на Среду


3. Метод кросс-энтропии

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

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

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

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

Из всех проходов мы выбираем от 20% до 50% лучших проходов по суммарной награде и по их результатам обновляем политику нашего агента. Формула обновления политики представлена ниже.

Обновление политики

После обновления политики мы повторяем проходы по изучаемой среде. Отбираем лучшие проходы. И обновляем политику агента.

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

3.1. Реализация средствами MQL5

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

Но тут мы можем вспомнить о задачах обучения без учителя. И в частности, задачи кластеризации. При изучении метода k-средних мы делили все возможные состояния на 500 кластеров. По-моему, это вполне приемлемое решение задачи конечности числа возможных состояний системы.

Для демонстрации алгоритма этого вполне достаточно. И мы не будем сейчас углубляться в детализацию влияния действий агента на состояние системы.

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

#include "..\Unsupervised\K-means\kmeans.mqh"
#include <Trade\SymbolInfo.mqh>
#include <Indicators\Oscilators.mqh>

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

input int                  StudyPeriod =  15;            //Study period, years
input uint                 HistoryBars =  20;            //Depth of history
input int                  Clusters    =  500;           //Clusters
ENUM_TIMEFRAMES            TimeFrame   =  PERIOD_CURRENT;
//---
input int                  Samples     =  100;
input int                  Percentile  =  70;
      int                  Actions     =  3;
//---
input group                "---- RSI ----"
input int                  RSIPeriod   =  14;            //Period
input ENUM_APPLIED_PRICE   RSIPrice    =  PRICE_CLOSE;   //Applied price
//---
input group                "---- CCI ----"
input int                  CCIPeriod   =  14;            //Period
input ENUM_APPLIED_PRICE   CCIPrice    =  PRICE_TYPICAL; //Applied price
//---
input group                "---- ATR ----"
input int                  ATRPeriod   =  14;            //Period
//---
input group                "---- MACD ----"
input int                  FastPeriod  =  12;            //Fast
input int                  SlowPeriod  =  26;            //Slow
input int                  SignalPeriod =  9;            //Signal
input ENUM_APPLIED_PRICE   MACDPrice   =  PRICE_CLOSE;   //Applied price

Для целей реализации метода кросс-энтропии было добавлено 3 переменных:

Количество возможных состояний системы определяется количеством кластеров, создаваемых методом k-средних.

В методе инициализации советника мы инициализируем объекты работы с индикаторами и объект для кластеризации графических паттернов.

int OnInit()
  {
//---
   Symb = new CSymbolInfo();
   if(CheckPointer(Symb) == POINTER_INVALID || !Symb.Name(_Symbol))
      return INIT_FAILED;
   Symb.Refresh();
//---
   RSI = new CiRSI();
   if(CheckPointer(RSI) == POINTER_INVALID || !RSI.Create(Symb.Name(), TimeFrame, RSIPeriod, RSIPrice))
      return INIT_FAILED;
//---
   CCI = new CiCCI();
   if(CheckPointer(CCI) == POINTER_INVALID || !CCI.Create(Symb.Name(), TimeFrame, CCIPeriod, CCIPrice))
      return INIT_FAILED;
//---
   ATR = new CiATR();
   if(CheckPointer(ATR) == POINTER_INVALID || !ATR.Create(Symb.Name(), TimeFrame, ATRPeriod))
      return INIT_FAILED;
//---
   MACD = new CiMACD();
   if(CheckPointer(MACD) == POINTER_INVALID || !MACD.Create(Symb.Name(), TimeFrame, FastPeriod, SlowPeriod, SignalPeriod, MACDPrice))
      return INIT_FAILED;
//---
   Kmeans = new CKmeans();
   if(CheckPointer(Kmeans) == POINTER_INVALID)
      return INIT_FAILED;
//---
   bool    bEventStudy = EventChartCustom(ChartID(), 1, 0, 0, "Init");
//---
   return(INIT_SUCCEEDED);
  }

И сразу напишем метод деинициализации советника, в котором удалим все выше созданные объекты. Напомню, что метод деинициализации вызывается при закрытии программы. И организация в нем очистки памяти является хорошей практикой.

void OnDeinit(const int reason)
  {
//---
   if(CheckPointer(Symb) != POINTER_INVALID)
      delete Symb;
//---
   if(CheckPointer(RSI) != POINTER_INVALID)
      delete RSI;
//---
   if(CheckPointer(CCI) != POINTER_INVALID)
      delete CCI;
//---
   if(CheckPointer(ATR) != POINTER_INVALID)
      delete ATR;
//---
   if(CheckPointer(MACD) != POINTER_INVALID)
      delete MACD;
//---
   if(CheckPointer(Kmeans) != POINTER_INVALID)
      delete Kmeans;
//---
  }

Непосредственно реализация алгоритма и обучение модели осуществляется в функции Train. В начале функции мы проведем подготовительную работу. Которая начинается с создания объекта работы с OpenCL устройством. Мы используем данную технологию для реализации алгоритма k-средних. Подробнее с его реализацией можно познакомиться в статье "Нейросети — это просто (Часть 15): Кластеризации данных средствами MQL5".

void Train(void)
  {
   COpenCLMy *opencl = OpenCLCreate(cl_unsupervised);
   if(CheckPointer(opencl) == POINTER_INVALID)
     {
      ExpertRemove();
      return;
     }
   if(!Kmeans.SetOpenCL(opencl))
     {
      delete opencl;
      ExpertRemove();
      return;
     }

Далее мы обновляем исторические данные.

   MqlDateTime start_time;
   TimeCurrent(start_time);
   start_time.year -= StudyPeriod;
   if(start_time.year <= 0)
      start_time.year = 1900;
   datetime st_time = StructToTime(start_time);
//---
   int bars = CopyRates(Symb.Name(), TimeFrame, st_time, TimeCurrent(), Rates);
   if(!RSI.BufferResize(bars) || !CCI.BufferResize(bars) || !ATR.BufferResize(bars) || !MACD.BufferResize(bars))
     {
      ExpertRemove();
      return;
     }
   if(!ArraySetAsSeries(Rates, true))
     {
      ExpertRemove();
      return;
     }
//---
   RSI.Refresh();
   CCI.Refresh();
   ATR.Refresh();
   MACD.Refresh();

И загружаем предварительно обученную модель k-средних. Конечно, не забываем контролировать процесс на каждом шаге.

   int handl = FileOpen(StringFormat("kmeans_%d.net", Clusters), FILE_READ | FILE_BIN);
   if(handl == INVALID_HANDLE)
     {
      ExpertRemove();
      return;
     }
   if(FileReadInteger(handl) != Kmeans.Type())
     {
      ExpertRemove();
      return;
     }
   bool result = Kmeans.Load(handl);
   FileClose(handl);
   if(!result)
     {
      ExpertRemove();
      return;
     }

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

   int total = bars - (int)HistoryBars - 480;
   double data[], fractals[];
   if(ArrayResize(data, total * 8 * HistoryBars) <= 0 ||
      ArrayResize(fractals, total * 3) <= 0)
     {
      ExpertRemove();
      return;
     }

И создаем выборку истории для кластеризации.

   for(int i = 0; (i < total && !IsStopped()); i++)
     {
      Comment(StringFormat("Create data: %d of %d", i, total));
      for(int b = 0; b < (int)HistoryBars; b++)
        {
         int bar = i + b + 480;
         int shift = (i * (int)HistoryBars + b) * 8;
         double open = Rates[bar]
                       .open;
         data[shift] = open - Rates[bar].low;
         data[shift + 1] = Rates[bar].high - open;
         data[shift + 2] = Rates[bar].close - open;
         data[shift + 3] = RSI.GetData(MAIN_LINE, bar);
         data[shift + 4] = CCI.GetData(MAIN_LINE, bar);
         data[shift + 5] = ATR.GetData(MAIN_LINE, bar);
         data[shift + 6] = MACD.GetData(MAIN_LINE, bar);
         data[shift + 7] = MACD.GetData(SIGNAL_LINE, bar);
        }
      int shift = i * 3;
      int bar = i + 480;
      fractals[shift] = (int)(Rates[bar - 1].high <= Rates[bar].high && Rates[bar + 1].high < Rates[bar].high);
      fractals[shift + 1] = (int)(Rates[bar - 1].low >= Rates[bar].low && Rates[bar + 1].low > Rates[bar].low);
      fractals[shift + 2] = (int)((fractals[shift] + fractals[shift]) == 0);
     }
   if(IsStopped())
     {
      ExpertRemove();
      return;
     }
   CBufferFloat *Data = new CBufferFloat();
   if(CheckPointer(Data) == POINTER_INVALID ||
      !Data.AssignArray(data))
      return;
   CBufferFloat *Fractals = new CBufferFloat();
   if(CheckPointer(Fractals) == POINTER_INVALID ||
      !Fractals.AssignArray(fractals))
      return;

И сразу осуществим их кластеризацию.

//---
   ResetLastError();
   Data = Kmeans.SoftMax(Data);

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

В векторе CumRewards мы будем накапливать награду каждого прохода.

А политику нашего агента policy мы инициализируем равными вероятностями для каждого действия.

   vector   env = vector::Zeros(Data.Total() / Clusters);
   vector   target = vector::Zeros(env.Size());
   matrix   states = matrix::Zeros(Samples, env.Size());
   matrix   actions = matrix::Zeros(Samples, env.Size());
   vector   CumRewards = vector::Zeros(Samples);
   double   average = 1.0 / Actions;
   matrix   policy = matrix::Full(Clusters, Actions, average);

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

   for(ulong state = 0; state < env.Size(); state++)
     {
      ulong shift = state * Clusters;
      env[state] = (double)(Data.Maximum((int)shift, Clusters) - shift);
      shift = state * Actions;
      target[state] = Fractals.Maximum((int)shift, Actions) - shift;
     }

На этом мы завершаем подготовительную работу и переходим к непосредственной реализации алгоритма метода кросс-энтропии. Как было описано выше алгоритм будет реализован в системе циклов.

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

   for(int iter = 0; iter < 200; iter++)
     {
      CumRewards.Fill(0);

А затем организуем вложенный цикл проходов по анализируемому процессу.

      for(int sampl = 0; sampl < Samples; sampl++)
        {

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

В данной реализации я назначил награду "1" за правильное действие (соответствует эталонному) и "-1" в остальных случаях.

Сохраняем текущее состояние и действие и переходим на следующий шаг (новая итерация цикла).

         for(ulong state = 0; state < env.Size(); state++)
           {
            ulong a = policy.Row((int)env[state]).ArgMax();
            if(policy[(int)env[state], a] <= average)
               a = (int)(MathRand() / 32768.0 * Actions);
            if(a == target[state])
               CumRewards[sampl] += 1;
            else
               CumRewards[sampl] -= 1;
            actions[sampl, state] = (double)a;
            states[sampl,state]=env[state];
           }

После осуществления всех проходов мы определяем проходной уровень вознаграждения для эталонных проходов.

      double percentile = CumRewards.Percentile(Percentile);

И осуществляем процесс обновления политики агента. Для этого мы организовываем систему циклов перебора всех осуществленных проходов и выбираем из них только эталонные.

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

      policy.Fill(0);
      for(int sampl = 0; sampl < Samples; sampl++)
        {
         if(CumRewards[sampl] < percentile)
            continue;
         for(int state = 0; state < env.Size(); state++)
            policy[(int)states[sampl, state], (int)actions[sampl, state]] += 1;
        }

После подсчета действий эталонных стратегий мы нормализуем политику, чтобы суммарная вероятность совершения действий для каждого состояния равнялась "1". Для этого мы организуем цикл перебора всех строк матрицы политики policy. Напомню, что каждая строка данной матрицы соответствует определенному состоянию системы, а столбец — совершаемому действию. Если же для какого-либо состояния не было сохранено ни одного действия, то считаем все действия равновозможными для такого состояния.

      for(int row = 0; row < Clusters; row++)
        {
         vector temp = policy.Row(row);
         double sum = temp.Sum();
         if(sum > 0)
            temp = temp / sum;
         else
            temp.Fill(average);
         if(!policy.Row(temp, row))
            break;
        }

После завершения итераций цикла мы получаем обновленную политику нашего Агента.

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

      PrintFormat("Iteration %d, Max reward %.0f", iter, CumRewards.Max());
     }

После завершения обучения модели мы удаляем объекты обучающей выборки и вызываем процедуру завершения работы советника.

   if(CheckPointer(Data) == POINTER_DYNAMIC)
      delete Data;
   if(CheckPointer(Fractals) == POINTER_DYNAMIC)
      delete Fractals;
   if(CheckPointer(opencl) == POINTER_DYNAMIC)
      delete opencl;
   Comment("");
//---
   ExpertRemove();
  }

С полным кодом советника и используемых библиотек можно ознакомиться во вложении.


Заключение

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

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

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

Ссылки

  1. Нейросети — это просто (Часть 14): Кластеризация данных
  2. Нейросети — это просто (Часть 15): Кластеризации данных средствами MQL5
  3. Нейросети — это просто (Часть 16): Практическое использование кластеризации

Программы, используемые в статье

# Имя Тип Описание
1 crossenteopy.mq5 Советник Советник для обучения модели 
2 kmeans.mqh  Библиотека класса Библиотека для организации метода k-средних 
3 unsupervised.cl Библиотека
Библиотека кода программы OpenCL для организации метода k-средних