Введение

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

Этот урок разделен на 2 части. В первой части мы собираемся создать простую игру, используя Python и PyGLET. Затем мы собираемся адаптировать интерфейс OpenAI Gym, чтобы стандартизировать нашу среду, чтобы при разработке алгоритма обучения нам не нужно было понимать, как он работает внутри.

Игра

Мы собираемся построить игру-лабиринт, в которой есть 2 простых правила:

  • Агент может перемещаться на 1 шаг в 4 направлениях: вверх, вниз, влево и вправо.
  • Игра заканчивается, когда агент достигает цели.

На рисунке ниже показано, как выглядит игра.

Разработка игр

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

Бизнес-логика

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

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

Уровень представления

Как только логика игры завершена, следующим шагом будет разработка уровня представления. Библиотека, которую мы собираемся использовать для этого слоя, — это библиотека разработки игр Python под названием PyGLET.

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

На рисунке ниже показано, как все компоненты подходят друг к другу.

Включить тренажерный зал OpenAI

Хотя игра готова, есть небольшая проблема, которую необходимо решить в первую очередь. Чтобы разработать модель, пользователям все еще нужно понимать механизм нашей игры, чтобы они могли заставить алгоритм обучения взаимодействовать с ней. Это проблематично, потому что мы хотим, чтобы наши пользователи сосредоточились на решении проблемы, а не на изучении того, как работает система. Более того, если в какой-то момент мы внесем изменения в нашу игру, это может испортить все их произведения. Звучит ужасно, правда?

Чтобы предотвратить это, мы собираемся стандартизировать интерфейс нашей игры, чтобы независимо от того, как она работает внутри, она выглядела одинаково для пользователей, а стандарт, который мы собираемся применить, разработан OpenAI Gym.

Что такое тренажерный зал OpenAI?

OpenAI Gym — это набор инструментов для разработки алгоритмов обучения с подкреплением. Библиотека поставляется с коллекцией сред для решения известных задач обучения с подкреплением, таких как CartPole и MountainCar. Наличие этих готовых решений позволяет разработчикам сосредоточиться исключительно на алгоритмах обучения и моделях.

В дополнение к встроенным средам OpenAI Gym также позволяет создавать пользовательскую среду, просто расширяя предоставленную абстракцию класса Env.

Интерфейс тренажерного зала OpenAI

Чтобы создать пользовательскую среду OpenAI Gym Environment, вам необходимо расширить класс Env, предоставляемый библиотекой, следующим образом:

import gym
class ImageMazeEnv(gym.Env):
     def __init__(self):
         ...
     def step(self, action):
         ...
     def reset(self):
         ...
     def render(self):
         ...
     def close(self):
         ...

Затем вам нужно переопределить 2 атрибута и 4 метода, которые работают следующим образом:

  • Атрибуты
    - action_space: все доступные действия, которые может выполнять агент.
    - observation_space: структура наблюдения.
  • Методы
    - step: выполнить действие в среде, затем вернуть состояние окружения, награду за действие и завершение эпизода.
    - reset: сбросить состояние среды, затем вернуть исходное состояние.
    - render(необязательно): визуализация среды для визуализации.
    - close(необязательно): выполнение очистки.

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

Пространство действия и наблюдения

Пространство действия простое. Доступны 4 действия: Влево, Вправо, Вверх и Вниз. Мы можем определить его, используя класс Discrete, предоставленный для дискретного пространства.

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

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

import gym
from gym.spaces import Discrete, Box
class ImageMazeEnv(gym.Env):
     def __init__(self):
        self.maze = .... # Create a maze object
        ....
        self.action_space = Discrete(4)
        self.observation_space = Box(low=0,high=255,shape=[500,500])

Ступенчатая функция

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

Первые два тривиальны, так как мы уже это сделали. В классе Maze уже есть метод move_robot, поэтому нам просто нужно передать ему действие. Для наблюдения мы реализовали MazeDrawer, который может создавать изображение, представляющее текущее состояние объекта лабиринта. Оставшаяся работа заключается в разработке и реализации функции вознаграждения, которая является важной частью разработки алгоритма обучения.

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

  • Если агент приближается к цели (сравните с предыдущим ходом), он получает награду +1.
  • Если агент удаляется от цели, он получает штраф -1.
  • Если агент ударяется о стену, он получает штраф -10.
  • Если агент достигает цели, он получает награду +100.
  • Расстояние между агентом и целью вычисляется по евклидову расстоянию без учета стены.
  • Если агент не достиг цели за определенное количество ходов, игра окончена.

Ниже показано, как выглядит ступенчатая функция:

def step(self, action):
   self.timestep += 1
   current_dist_to_goal = self.maze.dist_to_goal()
   self.maze.move_robot(action)
   new_dist_to_goal = self.maze.dist_to_goal()
   is_collide = current_dist_to_goal == new_dist_to_goal
   self.done = self.maze.is_robot_reach_goal() or self.timestep ==     self.time_limit
   reward = 0.
   if self.maze.is_robot_reach_goal():
      reward = 100
   elif is_collide:
      reward = -10
   elif new_dist_to_goal < current_dist_to_goal:
      reward = 1
   else:
      reward = -1
   return MazeDrawer.draw_maze(self.maze, 500, 500), reward.value, self.done, {}

Сброс, визуализация и закрытие

Функция reset должна повторно инициализировать игру до начального состояния и вернуть наблюдение. Для простоты мы сохраним начальное состояние в файле JSON и создадим из него новый экземпляр Maze. Подобно ступенчатой ​​функции, MazeDrawer используется для создания наблюдения.

Функция render визуализирует окружение, чтобы мы могли его визуализировать. Мы сделаем это, используя классы MazeDrawer и Renderer. Метод close предназначен для очистки. Единственное, что нам нужно сделать, это просто уничтожить объект Renderer.

Реализация этих 3 методов выглядит следующим образом:

def reset(self):
   self.maze = Maze.from_config(self.config_file)
   self.done = False
   self.timestep = 0
   
   if self.visualize:
     self.renderer.render(MazeDrawer.draw_maze(self.maze, 500, 500))
   return MazeDrawer.draw_maze(self.maze, 500, 500)
def render(self, mode='human'):
   self.renderer.render(MazeDrawer.draw_maze(self.maze, 500, 500))
def close(self):
   if self.visualize:
      self.renderer.close()

Зарегистрируйтесь и используйте среду

Теперь, когда наша среда готова, последнее, что нужно сделать, это зарегистрировать ее в реестре среды OpenAI Gym. Вот как это сделать:

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

class ImageMazeV0(ImageMazeEnv):
     def __init__(self):
         super().__init__(time_limit=200)

Затем зарегистрируйте его в спортзале register, поместив следующий код в __init__.py каталога вашего проекта.

from gym.envs.registration import register
register(
   id='ImageMaze-v0',
   entry_point='gym_image_maze.envs:ImageMazeV0'
)

Наконец, добавьте оператор импорта в __init__.py в вашем каталоге envs.

from gym_image_maze.envs.image_maze_env import ImageMazeV0

Новое окружение готово!!! Давайте проверим это с помощью следующего кода:

import gym
if __name__=='__main__':
   env = gym.make('ImageMaze-v0')
   env.reset()
   for i in range(500):
       env.render()
       observation, reward, done, _ = env.step(env.action_space.sample())
       print('Observation : ' + str(observation.shape))
       print('Reward      : ' + str(reward))
       print('Done        : ' + str(done))
       print('---------------------')
env.close()

Вы должны увидеть что-то подобное на экране и в терминале.

---------------------
Observation : (500, 500)
Reward      : -10
Done        : False
---------------------
Observation : (500, 500)
Reward      : 1
Done        : False
---------------------
Observation : (500, 500)
Reward      : 1
Done        : False
---------------------

Вот и все. Были сделаны. Теперь наша среда готова к использованию.
Если вы хотите увидеть код этого проекта, он доступен здесь.

Заключение

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

Спасибо за прочтение. Если вам понравилась эта статья, вы можете подписаться на меня на Medium, чтобы узнать больше. Увидимся в следующем посте.