Возможности Мастера MQL5, которые вам нужно знать (Часть 59): Обучение с подкреплением (DDPG) совместно с паттернами скользящей средней и стохастика
Введение
В предыдущей статье мы представили алгоритм обучения с подкреплением DDPG и рассмотрели 3 его ключевых класса, реализованных на Python. Класс буфера воспроизведения, класс сети актеров и класс сети критиков. В статье не рассматривались класс DDPG-agent; импорт ценовых данных MetaTrader 5 в Python; функции для скользящей средней и стохастического осциллятора; функция get-pattern для объединения данных из двух индикаторов в бинарный входной вектор для сети контролируемого обучения (реализованная в предыдущей статье об обучении с учителем с помощью MQL5); и, наконец, цикл моделирования среды для обучения сетей актеров и критиков.
Все это является частью обучения с подкреплением (Reinforcement-Learning, RL), которое мы рассматриваем как переход от обучения с учителем (Supervised-Learning, SL) к обучению на основе вывода (Inference-Learning, IL) (или обучению без учителя). Любой из этих режимов можно использовать отдельно для обучения и применения модели, однако в этих статьях мы пытаемся доказать, что их можно использовать вместе для создания чего-то более интересного. Итак, мы продолжаем наше изучение обучения с подкреплением, рассматривая очень важный класс DDPG-Agent.
DDPG-Agent
Основная архитектура и инициализация этого класса могут быть определены следующим образом:
def __init__(self, state_dim, action_dim): # Actor networks self.actor = Actor(state_dim, action_dim, HIDDEN_DIM).to(device) self.actor_target = Actor(state_dim, action_dim, HIDDEN_DIM).to(device) self.actor_target.load_state_dict(self.actor.state_dict()) self.actor_optimizer = optim.Adam(self.actor.parameters(), lr=LR_ACTOR) # Critic networks self.critic = Critic(state_dim, action_dim, HIDDEN_DIM).to(device) self.critic_target = Critic(state_dim, action_dim, HIDDEN_DIM).to(device) self.critic_target.load_state_dict(self.critic.state_dict()) self.critic_optimizer = optim.Adam(self.critic.parameters(), lr=LR_CRITIC) self.replay_buffer = ReplayBuffer(BUFFER_SIZE)
Ключевыми компонентами здесь являются двухсетевая архитектура, настройка оптимизатора и управление пользовательским опытом. Двойная архитектура поддерживает отдельную политику (сеть акторов) и отдельную ценность (сеть критиков), отличающиеся от двух основных сетей политик и ценностей. Эти модели реализуют целевые сети для обоих типов сетей, что важно для обеспечения стабильности при обучении. Инициализация соответствующих целевых нейронных сетей осуществляется с использованием тех же весов, что и в их основных сетях.
В конфигурации оптимизатора используются отдельные оптимизаторы Adam для сетей актеров и критиков. Кроме того, как это обычно и бывает, мы используем разные темпы обучения для сетей политик и ценностей. Наконец, для управления опытом мы обеспечиваем хранение переходов в буфере воспроизведения для обучения вне политики, а фиксированный размер буфера предотвращает неограниченное использование памяти. Мы выбираем действия, используя следующий подход, основанный на исследовании:
def select_action(self, state, noise_scale=0.1): state = torch.FloatTensor(state).unsqueeze(0).to(device) action = self.actor(state).cpu().data.numpy().flatten() action += noise_scale * np.random.randn(self.action_dim) return np.clip(action, -1, 1)
Ключевыми механизмами здесь являются обработка состояний, стратегия исследования и управление устройствами. Обработка состояний контролирует преобразование массива NumPy в правильный тензорный формат, добавление размерности пакета (путем распаковки) и, наконец, обеспечение выполнения вычислений на правильном устройстве.
В стратегии исследования к детерминированному результату политики добавляется гауссовский шум. Шкала шума контролирует величину шума при исследовании, а ограничение частоты событий поддерживает допустимый диапазон действий. Система управления устройствами обеспечивает эффективное переключение между графическим и центральным процессорами, если это применимо. Кроме того, для обеспечения совместимости с различными средами функция возвращает итоговый результат в виде массива NumPy. Механизм обновления обучения выглядит так:
def update(self): if len(self.replay_buffer) < BATCH_SIZE: return
Условный оператор служит своего рода «воротами обновления», пропуская обновления до тех пор, пока не будет собрано достаточное количество данных, равное размеру пакета. Это обеспечивает получение достоверной пакетной статистики. Обновление информации в двух сетях критиков выглядит так:
# Sample batch states, actions, rewards, next_states, dones = self.replay_buffer.sample(BATCH_SIZE) # Target Q calculation next_actions = self.actor_target(next_states) target_q = self.critic_target(next_states, next_actions) target_q = rewards + (1 - dones) * GAMMA * target_q # Current Q estimation current_q = self.critic(states, actions) # Loss computation and backpropagation critic_loss = nn.MSELoss()(current_q, target_q.detach()) self.critic_optimizer.zero_grad() critic_loss.backward() self.critic_optimizer.step()
В данном коде рассматриваются ключевые аспекты, такие как расчет целевого значения, вычисление функции потерь и управление градиентом. Расчет целевого значения использует целевые сети для получения стабильных целевых значений Q. В нем реализовано уравнение Беллмана с обработкой завершения, задаваемой параметром опыта dones. Коэффициент дисконтирования GAMMA определяет важность будущих вознаграждений.
Для вычисления потерь определяется среднеквадратичная ошибка между текущим и целевым Q-значениями (Q-значения). Метод detach() предотвращает передачу целевых градиентов (или их перенос тензором для обеспечения возможности переноса). И применяется стандартный метод обучения с временной разностью. Управление градиентами просто гарантирует обнуление всех градиентов, а оптимизация сети критиков является отдельным этапом. Обновления сети актеров также выполняются следующим образом:
actor_loss = -self.critic(states, self.actor(states)).mean() self.actor_optimizer.zero_grad() actor_loss.backward() self.actor_optimizer.step()
В данном случае рассматриваются следующие специфические аспекты градиентного анализа политики: максимизация значений Q путем минимизации отрицательных значений Q, дифференциация как по сетям актеров, так и по сетям критиков, а также применение чистого градиентного анализа политики без логарифмических вероятностей (детерминированный подход). Обновления целевой сети следующие:
for target, param in zip(self.actor_target.parameters(), self.actor.parameters()): target.data.copy_(TAU * param.data + (1 - TAU) * target.data) for target, param in zip(self.critic_target.parameters(), self.critic.parameters()): target.data.copy_(TAU * param.data + (1 - TAU) * target.data)
Этот механизм мягкого обновления использует усреднение Поляка (polyak-averaging) со значением TAU, которое обычно меньше 1. Отслеживание весовых коэффициентов сети происходит медленно, поскольку это является альтернативой периодическим жестким обновлениям. В целом, этот процесс обеспечивает стабильность и одновременно способствует обучению. Наша модель должна быть устойчивой. Она должна уметь загружать ранее сохраненные веса нейронной сети, а также сохранять их после обучения. Мы достигаем этого следующим образом:
def save(self, filename): torch.save({ 'actor': self.actor.state_dict(), 'critic': self.critic.state_dict(), 'actor_target': self.actor_target.state_dict(), 'critic_target': self.critic_target.state_dict(), }, filename) def load(self, filename): checkpoint = torch.load(filename) self.actor.load_state_dict(checkpoint['actor']) self.critic.load_state_dict(checkpoint['critic']) self.actor_target.load_state_dict(checkpoint['actor_target']) self.critic_target.load_state_dict(checkpoint['critic_target'])
Ключевые особенности нашего приложения, представленного выше, заключаются в следующем: мы сохраняем/загружаем все состояния сети; поддерживаем согласованность целевой сети; обеспечиваем возможность продолжения обучения; и поддерживаем оценку модели. Подводя итог, следует отметить, что при реализации агента DDPG необходимо принять несколько важных проектных решений. В целом, их можно разделить на три категории: выбор компонентов, специфичных для DDPG, использование сильных сторон реализации и внесение потенциальных улучшений.
В состав DDPG-компонентов входят в основном целевые сети, детерминированная политика и отдельные скорости обучения. Целевые нейронные сети играют очень важную роль в стабильном обучении на основе вознаграждения за совершенные действия (Q-обучение) при работе с непрерывными пространствами действий. Использование непрерывных пространств делает это крайне важным. В таком случае, такая детерминированная политика требует проведения внешнего исследования с использованием шума для обеспечения устойчивости. Использование отдельных скоростей обучения также является типичным примером применения, когда политика (сеть актеров) имеет более низкую скорость обучения, чем сеть значений.
К факторам, обеспечивающим относительно надежную реализацию, относится четкое "разделение ответственности", при котором у нас есть четко определенные методы для выбора действий, а также для обновлений. Кроме того, предусмотрена функция распознавания устройств, обеспечивающая согласованную обработку переходов между графическим и центральным процессорами. Для повышения эффективности тензорных операций также используется пакетная обработка, и, наконец, для обеспечения согласованной размерности тензоров в нескольких точках проверяется безопасность формы.
Возможные улучшения могут включать: обрезку градиента для предотвращения взрывных градиентов; использование графика скорости обучения для уточнения и лучшего контроля процесса обучения; использование приоритетного воспроизведения для эффективной выборки, хотя это связано с уже упомянутым буфером воспроизведения в предыдущей статье; и, наконец, параллельное исследование, где можно использовать несколько экземпляров актера для более быстрого сбора данных.
Также следует отметить несколько особенностей динамики обучения, связанных с последовательностью обновления и учетом гиперпараметров. В последовательности обновления сначала обновляются сети критиков. Это объясняется тем, что более точные значения Q действительно помогают улучшить политику. Для обеспечения дополнительной стабильности можно также ввести отсрочку обновления политики. Наконец, для замедления изменения параметров (весов сети), которые были изучены, выполняются частые обновления целевых значений.
При выборе гиперпараметров следует уделить особое внимание параметру TAU, поскольку он контролирует целевую скорость сети и, следовательно, является ключевым фактором стабильности всего процесса обучения. Следует использовать шкалу шума, учитывающую затухание со временем. Размер буфера также имеет решающее значение, поскольку он влияет на эффективность обучения, а размер пакета данных влияет на дисперсию обновлений.
Функции скользящей средней и стохастика
В отличие от обучения с учителем в статье, где функции реализовывались в MQL5, а входные данные нейронной сети экспортировались в Python для обучения, в случае с обучением с подкреплением мы реализуем эти две функции в Python. В нашей реализации мы используем модуль Python MetaTrader 5 для подключения к запущенному экземпляру терминала и последующего получения данных о ценах. В документации есть необходимые инструкции. Приведенные ниже индикаторные функции преобразуют исходные данные о ценах в данные технических индикаторов, которые после преобразования/нормализации в бинарный вектор паттернов служат входными данными для нашей модели обучения с учителем.
Результаты модели обучения с учителем мы рассматриваем как состояния, поскольку по сути они прогнозируют изменения в динамике цен. Затем эти состояния используются в качестве входных данных для агента DDPG RL. Наша функция скользящей средней принимает на вход фрейм данных pandas с ценами из модуля Python MetaTrader 5. Этот набор данных необходимо проверить и подготовить следующим образом:
p = np.asarray(p).flatten() # Convert to 1D array if not already if len(p) < window: raise ValueError("Window size cannot be larger than the number of prices.")
Здесь мы стандартизируем массив, чтобы обеспечить согласованный одномерный формат входных данных независимо от их формы. У нас также предусмотрена обработка ошибок, которая предотвращает использование недопустимых размеров окна, что могло бы привести к ошибкам вычислений. Обеспечение целостности данных также гарантирует бесперебойный поток данных через весь конвейер обработки. Механизм вычислений следующий:
return np.convolve(p, np.ones(window), 'valid') / window
В данной реализации для эффективного вычисления скользящего среднего используется свертка. Использование входного параметра valid гарантирует возврат только полностью вычисленных окон. Нормализация также выполняется с помощью размера окна для получения истинного среднего значения. Вся операция векторизована для достижения оптимальной производительности. Финансовое значение заключается в том, что это сглаживает данные о ценах, помогая выявлять тенденции, а размер используемого окна (так называемый период усреднения) определяет чувствительность к изменениям цен. Функция стохастического осциллятора проверяет свои входные данные следующим образом:
p = np.asarray(p).flatten() if len(p) < k_window: raise ValueError("Window size for %K cannot be larger than the number of prices.")
В данном случае, при проектировании, необходимо обеспечить единообразное форматирование входных данных с функцией скользящей средней. Необходимо ввести отдельную проверку для расчетного окна %K, при этом в случае недопустимых параметров должна генерироваться ошибка в качестве раннего предупреждения. Расчет %K производится так:
for i in range(k_window - 1, len(p)): current_close = p[i] lowest_low = min(p[i - k_window + 1:i + 1]) highest_high = max(p[i - k_window + 1:i + 1]) K = ((current_close - lowest_low) / (highest_high - lowest_low)) * 100 K_values.append(K)
Важными компонентами здесь являются анализ с использованием скользящего окна, рыночный контекст и общая реализация. Анализ с использованием скользящего окна требует изучения ценового диапазона за определенный период времени. Это помогает определить относительное положение текущей цены закрытия, при этом применяется стандартная шкала от 0 до 100. Анализ рыночной конъюнктуры помогает нам оценить состояние перекупленности/перепроданности. Значения, близкие к 100, указывают на потенциальный нисходящий тренд, тогда как значения, близкие к 0, указывают на восходящий тренд. В целом, для большей ясности используется явный цикл, применяется правильная индексация окон для обработки граничных случаев, а также сохраняется временной порядок результатов. Расчет %D производится так:
D_values = MA(K_values, d_window)
По сути, это уточнение сигнала, при котором используется сглаженная версия скользящего среднего %K. Обычно для этого периода усреднения используется значение 3, и именно его мы и применяем. Этот дополнительный буфер обеспечивает подтверждение колебаний в %K и, таким образом, помогает уменьшить количество ложных сигналов от исходного %K.
Функция получения паттерна
Функция используется для интеграции данных из двух указанных выше буферов индикаторов в конвейер обучения. Она выполняет роль проектирования функционала. В частности, она способствует уменьшению размерности, поскольку преобразует исходные цены в более осмысленные сигналы; способствует улучшению стационарности, поскольку индикаторы часто более стабильны, чем исходные цены; и, наконец, позволяет учитывать временной контекст, поскольку оконные вычисления с временными зависимостями сохраняют временную зависимость (сгенерированный входной вектор, например, [1,0,0,1], может быть связан со временем его генерации, подобно любому значению индикатора, или исходные цены также помечаются временем их генерации).
В основном, однако, она используется для подготовки к обучению с учителем. Полученные данные в виде бинарного вектора из 0 и 1 используются для обучения модели прогнозированию следующих изменений цен. Скользящая средняя предоставляет информацию о тренде, а функция STO — информацию об импульсе и развороте. В статье 57 мы рассмотрели комбинированные взаимодополняющие модели, полученные на основе обоих показателей. Данные, полученные в результате прогнозирования изменения цен, служат в качестве представления состояния RL.
Это означает, что прогнозы нашей модели обучения с учителем становятся входными данными для состояния DDPG. Таким образом, используемые нами индикаторы скользящей средней и STO помогают агенту DDPG, предоставляя рыночный контекст, способствующий пониманию конкретного рыночного режима. Это снижает необходимость использования исходных исторических данных о ценах при определении состояния.
К преимуществам реализации относятся надежность, обеспечиваемая проверкой входных данных для предотвращения скрытых сбоев, обработка размерностей для обеспечения согласованности форм массивов, а также сообщения об ошибках для четкого информирования в случае неправильного использования. Кроме того, следует учитывать соображения повышения производительности, используя векторизованные операции там, где это возможно, явные циклы и эффективность использования памяти за счет проектирования, ориентированного на потоковую обработку данных. Эти преимущества остаются актуальными для трейдеров. Для формирования состояний используются стандартные индикаторы. Эти показатели дополняют друг друга, поскольку объединяют метрики тренда и импульса, а выходные данные по состоянию находятся в нормализованном диапазоне, что важно для обеспечения согласованности.
Возможные улучшения включают оптимизацию вычислений за счет векторизованной реализации расчета %K, использование ускорения Numba (импортированного из JIT) для ускорения циклов в функции STO, а также кэширование промежуточных вычислений. Расширенная функциональность может быть добавлена за счет дополнительной проверки значений NaN/inf. Этот код, реализующий обучение с подкреплением с использованием DDPG, довольно объемный, и хотя было бы уместно прокомментировать его ключевые разделы, я просто прикреплю неописанные части в конце статьи. Главной из них будет функция получения паттерна (get pattern).
Тестирование
Из 10 моделей, протестированных нами в статье о контролируемом обучении (# 57), только 7 смогли прибыльно развиваться в течение года, будучи обученными годом ранее. Поскольку каждый паттерн представляет собой отдельную сеть, нам необходимо создавать сети и среды обучения с подкреплением также для каждого паттерна. В статье 57 мы используем аналогичную методологию, обучая модель на валютной паре EURUSD за 2023 год на дневном таймфрейме. В данном случае мы обучаем наши нейронные сети с подкреплением, имитируя 2023 год как реальную рыночную среду. Как утверждалось в двух предыдущих статьях, обучение с подкреплением представляет собой систему, предназначенную для поддержки и защиты уже созданной и обученной модели, которой в нашем случае является сеть, обученная нами с помощью контролируемого обучения в статье 57.
Это достигается за счет обратного распространения ошибки в эксплуатационной или рабочей среде, а не на основе исторических данных. Поскольку обратное распространение ошибки в сети ONNX из MQL5 не представляется возможным, мы «имитируем» реальную среду, которой в нашем случае остается 2023 год.
Вместо вопроса «Что дальше будет с ценой?», который мы задавали в обучении с учителем, мы задаем вопрос: «Учитывая эти грядущие изменения цены, какие действия должен предпринять трейдер?» Таким образом, мы проводим имитационные тренировки, как описано выше, в 2023 году, а затем осуществляем прогноз на 2024 год, в ходе которого наши условия входа немного изменяются.
Вместо того чтобы основывать свои длинные или короткие позиции исключительно на том, как изменится цена в будущем, мы также учитываем, какие действия нам действительно необходимо предпринять с учетом дальнейшего развития ценовой динамики. Мы также учитываем, будут ли вознаграждения прибыльными. Из 7 паттернов, описанных в статье 57, только 3 демонстрируют осмысленное продвижение вперед при использовании обучения с подкреплением. Используя нашу систему индексации числа 10 (от 0 до 9), получаем следующие последовательности: 1, 2 и 5. Их отчеты представлены ниже:
Для паттерна 1:


Для паттерна 2:


Для паттерна 5:


Как всегда, протестированный советник построен с использованием пользовательского класса сигналов, код которого прилагается ниже. Мы вносим изменения в файл класса сигнала, который был описан в статье 57, переименовывая функцию IsPattern в Supervise. Кроме того, мы вводим новую функцию Reinforce. Код обоих приложений приведен ниже:
//+------------------------------------------------------------------+ //| Supervised Learning Model Forward Pass. | //+------------------------------------------------------------------+ double CSignal_DDPG::Supervise(int Index, ENUM_POSITION_TYPE T) { vectorf _x = Get(Index, m_time.GetData(X()), m_close, m_ma, m_ma_lag, m_sto); vectorf _y(1); _y.Fill(0.0); int _i=Index; if(_i==8) { _i -= 2; } ResetLastError(); if(!OnnxRun(m_handles[_i], ONNX_NO_CONVERSION, _x, _y)) { printf(__FUNCSIG__ + " failed to get y forecast, err: %i", GetLastError()); return(double(_y[0])); } if(T == POSITION_TYPE_BUY && _y[0] > 0.5f) { _y[0] = 2.0f * (_y[0] - 0.5f); } else if(T == POSITION_TYPE_SELL && _y[0] < 0.5f) { _y[0] = 2.0f * (0.5f - _y[0]); } return(double(_y[0])); } //+------------------------------------------------------------------+ //| Reinforcement Learning Model Forward Pass. | //+------------------------------------------------------------------+ double CSignal_DDPG::Reinforce(int Index, ENUM_POSITION_TYPE T, double State) { vectorf _x(1); _x.Fill(float(State)); vectorf _y(1); _y.Fill(0.0); vectorf _y_state(1); _y_state.Fill(float(State)); vectorf _y_action(1); _y_action.Fill(0.0); vectorf _z(1); _z.Fill(0.0); int _i=Index; if(_i==8) { _i -= 2; } ResetLastError(); if(!OnnxRun(m_handles_a[_i], ONNX_NO_CONVERSION, _x, _y)) { printf(__FUNCSIG__ + " failed to get y action forecast, err: %i", GetLastError()); } _y_action[0] = _y[0]; ResetLastError(); if(!OnnxRun(m_handles_c[_i], ONNX_NO_CONVERSION, _y_state, _y_action, _z)) { printf(__FUNCSIG__ + " failed to get z reward forecast, err: %i", GetLastError()); } //normalize action output & check for state-action alignment if(T == POSITION_TYPE_BUY && _y[0] > 0.5f) { _y[0] = 2.0f * (_y[0] - 0.5f); } else if(T == POSITION_TYPE_SELL && _y[0] < 0.5f) { _y[0] = 2.0f * (0.5f - _y[0]); } else { _y[0] = 0.0f; } return(double(_y[0]*_z[0])); }
Этот файл класса пользовательского сигнала предназначен для сборки в советника с помощью Мастера MQL5. Новички могут найти подробную информацию здесь и здесь.
Заключение
Мы рассмотрели возможность применения обучения с подкреплением, когда модели находятся на стадии развертывания. В нашей модели обучения с подкреплением использовался алгоритм глубокого детерминированного градиента политики, и эта реализация включала классы: буфера воспроизведения, актера, критика и агента, как описано в этой и предыдущей статье. Обучение с подкреплением в развертывании/производстве служит средством для поддержания фокуса модели на том, что было изучено на этапе контролируемого обучения (эксплуатация), а также для отслеживания новых неизвестных изменений в окружающей среде/рыночных условиях, которые следует учитывать при принятии решений в будущем (эксплуатация). Для правильного выполнения этой задачи нам необходимо выполнить обратное распространение ошибки и обучить модель в процессе ее использования.
Однако, поскольку обучение модели ONNX на языке MQL5 не поддерживается, мы выбрали моделирование реальных торговых условий на основе исторических данных. После моделирования мы протестировали обученные модели с подкреплением на следующем году после года обучения, и только 3 из 7 смогли продолжить движение вперед, хотя результаты сделок были искажены, поскольку позиции в основном удерживались только в длинных или только в коротких позициях. Как мы утверждали в статье 57, это, скорее всего, связано с небольшим тестовым окном, а это значит, что обширное обучение и тестирование на большем объеме данных должны исправить ситуацию. В следующий раз рассмотрим вывод.
| Тип | Описание |
|---|---|
| Файлы *.*onnx | Файлы модели ONNX находятся в подпапке Python, расположенной в том же месте, что и файл класса пользовательского сигнала |
| Файлы *.*mqh | Файл пользовательского класса сигналов и файл с функцией обработки входных сетевых данных (57_X) |
| Файлы *.*mq5 | Созданный Мастером советник, в заголовке которого отображаются использованные файлы |
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/17684
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Особенности написания Пользовательских Индикаторов
Нейросети в трейдинге: Асинхронная обработка событий в потоковых моделях (Окончание)
Нейросети в трейдинге: Асинхронная обработка событий в потоковых моделях (Основные компоненты)
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования