Введение
Стремительное развитие глубокого обучения с подкреплением (deep reinforcement learning – DRL), представляющего собой комбинацию глубокого обучения (DL) и обучения с подкреплением (RL), привлекает все больше исследователей из самых разных сфер науки к применению DRL для решения задач в своих областях исследований. Благодаря способности глубокого обучения работать с непрерывным или сложным пространством состояний и способности обучения с подкреплением учиться методом проб и ошибок в сложной среде, DRL особенно хорошо подходит для решения задач, для которых не хватает хороших точных или эвристических методов в сложных средах. Поскольку решение большинства задач обучения с подкреплением требует чрезвычайно большого количества данных, большинство DRL(или RL)-агентов обучаются в симулированной среде. Основным выбором для обучения DRL-моделей, благодаря своей внушительной библиотеке инструментов машинного обучения, стал Python. Однако использовать Python как язык программирования для создания крупномасштабных симуляций, имитирующих сложные среды, довольно сложно.
AnyLogic — идеальная платформа для создания симуляционных моделей для обучения DRL-агентов в сложных средах. Недавно разработанная библиотека Alpyne — это библиотека Python, которая позволяет пользователям обучать DRL-агентов на Python, взаимодействуя с моделью AnyLogic. К сожалению, она все еще недостаточно стабильна для работы со сложными симуляционными моделями.
В этой статье мы представляем новый способ взаимодействия DRL с симуляционными моделям в AnyLogic с помощью библиотеки Pypeline. Этот метод также может быть использован для (не глубокого) обучения с подкреплением, но благодаря своей простоте большинство сред, для которых хватает простого RL, могут быть смоделированы непосредственно в самих языках программирования, таких как Python.
Стандартным способом обучения DRL-агента является взаимодействие с симуляционными моделями из Python. В рамках этого метода DRL-агент вызывается из симуляционной модели, чтобы наблюдать и взаимодействовать с моделью через определенные временные интервалы, и сохраняет все свои критические компоненты, например, буфер воспроизведения и нейронные сети, в локальной памяти в конце каждого эпизода. Такой подход обеспечивает стабильный способ реализации DRL в моделях AnyLogic.
Начнем же мы с краткого обзора основных компонентов, задействованных в этом методе. В частности, для демонстрации мы здесь используем реализацию Deep Q-Learning, но этот метод может быть применен к различным RL-алгоритмам. Затем мы рассмотрим простой пример (упрощенный OpenAI Gym Taxi-v3), чтобы продемонстрировать реализацию этого метода.
Краткий обзор основных компонентов
Компоненты на стороне AnyLogic (среда)
Чтобы взаимодействовать с Python, сначала нужно установить в модель AnyLogic библиотеку Pypeline. Поскольку в данной статье речь идет не о библиотеке Pypeline, детальные инструкции по установке и использованию библиотеки вы можете найти на сайте.
После установки библиотеки Pypeline нам нужно импортировать модуль Python для обучения DRL и создать инстанс класса обучения DRL в секции On Startup главного агента. В дальнейшем этот инстанс класса обучения DRL будет вызываться во время работы симуляции на каждом шаге действия для получения информации о состоянии, совершения какого-либо действия на ее основе и получения вознаграждения от симуляционной среды.
Для обучения RL-агента симуляционная среда должна обладать четырьмя важными возможностями:
-
выводить информацию о состоянии из среды (1),
-
выдавать вознаграждение от среды (2),
-
получать и выполнять действия от RL-агента (3), и
-
сообщать RL-агенту о завершении эпизода (4).
Таким образом, в симуляции должны быть предусмотрены функции для всех этих четырех возможностей. Специально для нашей реализации были созданы функция под пункты (1) и (2), и еще одна функция для пунктов (3) и (4). Функция для (1) просто возвращает информацию о текущем состоянии в виде вещественного или целочисленного списка. Функция для (2) просто возвращает текущее вознаграждение в вещественном или целочисленном виде. Функция для (3) и (4) принимает от RL-агента входные данные о действиях в среде и возвращает, будет ли симуляция закончена после выполнения действия.
Наконец, функция, коммуницирующая с действием RL, должна использовать все четыре вышеупомянутые возможности для взаимодейстия с RL-агентом на каждом шаге действия.
Компоненты на стороне Python (RL-агент)
Как говорилось выше, в начале каждого эпизода инициализируется новый инстанс RL-агента. Поскольку в каждом эпизоде инициализируется новый RL-агент, очень важно реализовать какой-нибудь способ локальной записи важной информации об обучении, чтобы эта информация не была потеряна в конце каждого эпизода обучения. Здесь мы используем JSON и функции сохранения из библиотек наподобие PyTorch для сохранения информации в конце каждого эпизода обучения и загрузки ее при инициализации. На примере Deep Q-Learning, важная информация включает, но не ограничивается буфером воспроизведения, сетью политик, целевой сетью, количеством пройденных шагов, буфером вознаграждения, историей потерь и оптимизатором (если используется оптимизатор импульса, например ADAM). Более подробно об алгоритме Deep Q-Learning можно узнать из [1].
Логирование важной информации позволяет нам непрерывно обучать RL-агента между эпизодами. Однако необходимо решить еще одну задачу: симуляционная модель выводит только текущее состояние, вознаграждение и информацию о том, закончен ли эпизод (в дальнейшем мы будем говорить об этом, как о флаге DONE), но RL-агенту необходимо предыдущее состояние, чтобы сформировать переход для отправки в буфер воспроизведения. Эта проблема решается путем инициализации предыдущего состояния и значений действий в null. После получения информации о состоянии, вознаграждении и DONE от симуляции состояние станет новым предыдущим состоянием, а действие на основе состояния — новым предыдущим действием. Если значения предыдущего состояния и предыдущего действия не равны null, то в буфер воспроизведения будет добавлен новый переход, состоящий из предыдущего состояния, предыдущего действия, текущего состояния, вознаграждения и DONE.
Простая демонстрация – Упрощенное Taxi-v3
Давайте перейдем непосредственно к реализации. Модель AnyLogic с файлами Python, созданными для этой демонстрации, доступны в этом репозитории.
Среда
В демонстрационных целях мы показываем наш метод на примере упрощенной среды OpenAI Gym Taxi-v3, реплицированной в AnyLogic. Тем не менее, этот метод достаточно стабилен, чтобы его можно было применять к крупномасштабным и гораздо более сложным средам. Возможно, он даже лучше подходит для более сложных сред, потому что в более сложных средах накладными расходами на коммуникацию между AnyLogic и Python по сути можно пренебречь.
Эта среда состоит из сетки 4*4, такси, управляемого RL, и пассажира. Визуализация сетки показана на рисунке 1. Зеленые линии обозначают стены, через которые такси не может проехать. Начальное местоположение пассажира — G, а пункт назначения — Y. Такси будет инициализировано в произвольном месте, отличном от местоположения пассажира. Задача такси — сначала забрать пассажира, а затем высадить его в пункте назначения. Как только пассажир будет высажен или будет сделано более 200 шагов, эпизод завершится. Пространство действий в этой среде: 0 – движение вверх, 1 – движение вниз, 2 – движение влево, 3 – движение вправо, 4 – подобрать пассажира и 5 – высадить. Пространство состояний — это положение такси по оси X, положение такси по оси Y, а также был ли подобран пассажир (0 или 1). Если такси неудачно подобрало или высадило пассажира, оно получает вознаграждение -10. Когда такси успешно высаживает пассажира, оно получает награду +20. Такси получает награду -1, если не срабатывает одно из вышеупомянутых вознаграждений.
Реализация в AnyLogic
В этой модели есть несколько важных функций, которые позволяют обучать RL-агента. Функция f_State возвращает целочисленный список, представляющий текущее состояние. Функция f_Reward возвращает вознаграждение, полученное в результате выполнения действия. Функция f_TaxiAction реализует действие от RL-агента и возвращает, завершился ли эпизод после выполнения этого действия. Если параметр модели deploy установлен в true, функция f_TaxiAction изменит визуализацию в соответствии с действием. Функция f_RLAction вызывает RL-агента для выбора действия в соответствии с текущим состоянием и предоставляет RL-агенту необходимую для обучения информацию с помощью вышеупомянутых трех функций. Во время выполнения симуляции функция вызывается в цикле f_RLAction с интервалом в 0.1 секунду.
Реализация на Python
Для реализации Deep Q-Learning на Python мы используем библиотеку глубокого обучения PyTorch. За исключением нескольких дополнительных строк кода для сохранения и загрузки важной информации для обучения, эта реализация ничем не отличается от других стандартных реализаций Deep Q-Learning. Поскольку основной темой этой статьи не являются алгоритмы обучения с подкреплением, и чтобы не утомлять вас техническими подробностями, в этом разделе будут рассмотрены только те части кода, которые имеют отношение к применению RL в AnyLogic. В данной реализации для обучения с подкреплением выделено два файла Python: Train.py и DQNModel.py. Поскольку задача DQNModel.py заключается только в построении нейронной сети, он не рассматривается в этом блоге.
Здесь следует отметить, что, учитывая, что мы создаем связь между AnyLogic и Python, код Python лучше модулировать, чтобы сделать эту связь проще и чище. Здесь мы создали класс для обучающего агента Deep Q-Learning под названием DQN_Main.
Чтобы инициализировать инстанс класса DQN_Main (это происходит в начале каждого эпизода), нам нужно сначала загрузить необходимую информацию с локального диска с помощью JSON и функции load из PyTorch, затем установить значение предыдущего состояния и предыдущего действия в null, а вознаграждение эпизода — в 0. Информация, необходимая для этого инстанса, выделена красным на рисунке 2.
Затем на каждом шаге действий определяется функция act, которая вызывается из AnyLogic, чтобы поместить опыт в буфер воспроизведения, вызвать функцию train для обучения нейронной сети и, если эпизод завершен, сохранить важную информацию на локальном диске.
После вызова функции act функция train обучает нейронную сеть в течение одной эпохи и сохраняет на локальном диске важную информацию, которая изменилась в процессе обучения, если эпизод завершен.
При желании вы можете добавить функции для генерации графиков вознаграждения и потерь на локальный диск, чтобы вы могли наблюдать за прогрессом вашего RL-агента. Сгенерированные графики для данного инстанса отмечены желтым цветом на рисунке 2.
Ниже прилагается полный код Train.py:
import os.path import os import torch import json from DQNModel import DQN import random import numpy as np import torch.nn.functional as F import matplotlib.pyplot as plt class DQN_Main: def __init__(self): self.BUFFER_SIZE = 200000 self.MIN_REPLAY_SIZE = 50000 self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu") self.GAMMA = 0.99 self.BATCH_SIZE = 128 self.EPSILON_START = 0.99 self.EPSILON_END = 0.1 self.EPSILON_DECAY = 0.000025 self.TARGET_UPDATE_FREQ = 10000 self.LR = 0.00025 os.environ["KMP_DUPLICATE_LIB_OK"] = "TRUE" if os.path.exists('replay_buffer.json'): with open("replay_buffer.json", "r") as read_content: self.replay_buffer = json.load(read_content) else: self.replay_buffer = [] if os.path.exists('reward_buffer.json'): with open("reward_buffer.json", "r") as read_content: self.reward_buffer = json.load(read_content) else: self.reward_buffer = [] if os.path.exists('step.json'): with open("step.json", "r") as read_content: self.step = json.load(read_content) else: self.step = 0 self.policy_net = DQN(device=self.device).to(self.device) self.target_net = DQN(device=self.device).to(self.device) if os.path.exists('policy_net.pth'): self.policy_net.load_state_dict(torch.load('policy_net.pth')) self.target_net.load_state_dict(torch.load('target_net.pth')) else: self.target_net.load_state_dict(self.policy_net.state_dict()) self.optimizer = torch.optim.Adam(self.policy_net.parameters(), lr=self.LR) if os.path.exists('policy_net_adam.pth'): self.optimizer.load_state_dict(torch.load('policy_net_adam.pth')) if os.path.exists('loss_hist.json'): with open("loss_hist.json", "r") as read_content: self.loss_hist = json.load(read_content) else: self.loss_hist = [] if os.path.exists('loss_hist_show.json'): with open("loss_hist_show.json", "r") as read_content: self.loss_hist_show = json.load(read_content) else: self.loss_hist_show = [] self.episode_reward = 0 self.prev_state = None self.prev_action = None def save_hyperparams(self): hyperparams_dict = { 'BUFFER SIZE': self.BUFFER_SIZE, 'MIN REPLAY SIZE': self.MIN_REPLAY_SIZE, 'GAMMA': self.GAMMA, 'BATCH SIZE': self.BATCH_SIZE, 'EPSILON START': self.EPSILON_START, 'EPSILON END': self.EPSILON_END, 'EPSILON DECAY': self.EPSILON_DECAY, 'TARGET UPDATE FREQ': self.TARGET_UPDATE_FREQ, 'LR': self.LR, } with open("hyperparameters.json", "w") as write: json.dump(hyperparams_dict, write) def train(self, done): # добавьте сюда шаг обучения transitions = random.sample(self.replay_buffer, self.BATCH_SIZE) states = np.asarray([t[0] for t in transitions]) actions = np.asarray([t[1] for t in transitions]) rewards = np.asarray([t[2] for t in transitions]) dones = np.asarray([t[3] for t in transitions]) next_states = np.asarray([t[4] for t in transitions]) states_t = torch.as_tensor(states, dtype=torch.float32).to(self.device) actions_t = torch.as_tensor(actions, dtype=torch.int64).unsqueeze(-1).to(self.device) rewards_t = torch.as_tensor(rewards, dtype=torch.float32).unsqueeze(-1).to(self.device) dones_t = torch.as_tensor(dones, dtype=torch.float32).unsqueeze(-1).to(self.device) next_states_t = torch.as_tensor(next_states, dtype=torch.float32).to(self.device) # вычисление цели _, actions_target = self.policy_net(next_states_t).max(dim=1, keepdim=True) target_q_values_1 = self.target_net(next_states_t).gather(dim=1, index=actions_target) targets_1 = rewards_t + self.GAMMA * (1 - dones_t) * target_q_values_1 # вычисление потери q_values = self.policy_net(states_t) action_q_values = torch.gather(input=q_values, dim=1, index=actions_t) # градиентный спуск loss = F.mse_loss(action_q_values, targets_1) self.loss_hist.append(loss.item()) self.optimizer.zero_grad() loss.backward() self.optimizer.step() if self.step % 200 == 0: self.loss_hist_show.append(sum(self.loss_hist[-300:])/300) self.plot_loss_hist() # обновление целевой сети if self.step % self.TARGET_UPDATE_FREQ == 0: self.target_net.load_state_dict(self.policy_net.state_dict()) # нам нужен параметр done, так как нам нужно сохранить нейросети, если эпизод завершен if done: torch.save(self.policy_net.state_dict(), 'policy_net.pth') torch.save(self.target_net.state_dict(), 'target_net.pth') torch.save(self.optimizer.state_dict(), 'policy_net_adam.pth') with open("loss_hist.json", "w") as write: json.dump(self.loss_hist, write) with open("loss_hist_show.json", "w") as write: json.dump(self.loss_hist_show, write) def random_action(self): return random.choice(self.policy_net.action_space) def act(self, state, reward, done, deploy): if deploy: with torch.no_grad(): state_t = torch.tensor(state) action = self.policy_net.act(state_t) return action if len(self.replay_buffer) >= self.MIN_REPLAY_SIZE: rnd = random.random() epsilon = self.EPSILON_START - self.EPSILON_DECAY * self.step self.step += 1 if epsilon < self.EPSILON_END: epsilon = self.EPSILON_END if rnd <= epsilon: action = self.random_action() else: with torch.no_grad(): state_t = torch.tensor(state) action = self.policy_net.act(state_t) else: # заполнение буфера воспроизведения action = self.random_action() if self.prev_state is None: # начало эпизода, мы просто берем действие, ничего не добавляем в буфер воспроизведения self.prev_state = state.copy() self.prev_action = action # здесь нам все еще нужно обучить нашу нейронную сеть if len(self.replay_buffer) >= self.MIN_REPLAY_SIZE: # if done neural nets will be saved in the train function self.train(done) return action else: # здесь мы добавляем переходы в буфер воспроизведения self.episode_reward += reward transition = (self.prev_state, self.prev_action, reward, done, state) self.replay_buffer.append(transition) if len(self.replay_buffer) > self.BUFFER_SIZE: self.replay_buffer.pop(0) # корректировка предыдущего состояния и действия self.prev_state = state.copy() self.prev_action = action if done: self.reward_buffer.append(self.episode_reward) # поскольку мы подключаемся к AnyLogic, нам приходится сохранять все в каждом эпизоде with open("reward_buffer.json", "w") as write: json.dump(self.reward_buffer, write) with open("replay_buffer.json", "w") as write: json.dump(self.replay_buffer, write) with open("step.json", "w") as write: json.dump(self.step, write) if len(self.reward_buffer)%100 == 0: self.plot_reward_buffer() if len(self.replay_buffer) >= self.MIN_REPLAY_SIZE: # если done, нейронные сети будут сохранены в функции train self.train(done) return action def plot_reward_buffer(self): plt.plot(self.reward_buffer) plt.xlabel('Episodes') plt.ylabel('Rewards') plt.savefig('reward buffer.jpg') plt.close() def plot_loss_hist(self): plt.plot(self.loss_hist_show[10:]) plt.xlabel('100 Epoch') plt.ylabel('Loss') plt.savefig('Loss History.jpg') plt.close()
Обучение с помощью эксперимента AnyLogic
Обучение RL-агента происходит с помощью эксперимента Монте-Карло от AnyLogic. В эксперименте Монте-Карло мы задаем количество эпизодов, на которых мы хотим обучить RL-агента, в разделе Replications, после чего нам остается только сидеть и наблюдать, как RL-агент развивается, получая опыт от симуляции!
Если вы хотите прервать текущее обучение и заново обучить нового RL-агента, вы можете сделать это, удалив все файлы, отмеченные желтым или красным цветом на рисунке 2.
Результат обучения
Результат обучения подтверждает, что наш метод работает не хуже любого другого метода обучения RL-моделей. На рисунке 3 показано, что вознаграждение постоянно улучшается, и модель успешно сходится.
Успех обучения подтверждается визуализацией эксперимента AnyLogic (желтый: такси, красный: пассажир, зеленый: пункт назначения):
Спасибо за внимание! Если у вас возникнут дополнительные вопросы, не стесняйтесь посетить мою страницу на GitHub и оставить свои вопросы в разделе обсуждений! 🙂
Ссылки:
-
Mnih, V., Kavukcuoglu, K., Silver, D. et al. Human-level control through deep reinforcement learning. Nature 518, 529–533 (2015).
Научиться создавать имитационные модели в ПО AnyLogic и применять их для анализа проектов можно под руководством экспертов области на онлайн-курсе «Имитационное моделирование на базе AnyLogic».
ссылка на оригинал статьи https://habr.com/ru/articles/823508/
Добавить комментарий