Autonomous Learning Library - это библиотека на основе PyTorch, предназначенная для быстрого внедрения новых агентов обучения с подкреплением. В этой статье описывается базовая философия проектирования и архитектура библиотеки, чтобы вы могли начать создание собственных агентов.

Если эта статья вам знакома, возможно, вы читали документацию. Это также может быть связано с тем, что вы видели сообщение Синхронизировано, в котором использовались изображения из документации. Спасибо Synced за привет! Без дальнейших церемоний…

Агент-ориентированный дизайн

Одна из основных философий autonomous-learning-library заключается в том, что RL должен быть основан на агентах, а не на алгоритмах. Чтобы понять, что мы имеем в виду, ознакомьтесь с реализацией DQN в OpenAI Baselines. Есть гигантская функция learn, которая принимает окружение и большое количество гиперпараметров. В основе этой функции лежит большой контур управления, который вызывает множество различных функций. Какая часть этой learn функции является агентом? Какая часть окружающей среды? Какая часть что-то еще? Мы называем эту реализацию алгоритмической, потому что центральной абстракцией является функция с именем learn, которая обеспечивает полную спецификацию алгоритма.

Как тогда будет выглядеть реализация на основе агентов? Нам не нужно смотреть дальше следующей известной диаграммы:

Определение Agent простое. Он принимает состояние и награду и возвращает действие. Вот и все. Все остальное - детали реализации. Вот интерфейс Agent в библиотеке автономного обучения:

class Agent(ABC):
    @abstractmethod
    def act(self, state, reward):
        pass

Вот и все. Когда и как он тренируется, никого не касается, кроме самого Agent. Когда Agent разрешено действовать, определяется контуром управления. Как может выглядеть реализация этого? Вот функция act из нашей реализации DQN:

def act(self, state, reward):
    self._store_transition(self._state, self._action, reward, state)
    self._train()
    self._state = state
    self._action = self.policy(state)
    return self.action

Вот и все. _store_transition() и _train() - частные вспомогательные методы. У контура управления нет причин знать что-либо об этих деталях. Такой подход упрощает как нашу Agent реализацию, так и сам контур управления.

Отделение логики контура управления от логики Agent обеспечивает большую гибкость в использовании агентов. Фактически, Agent полностью отделен от интерфейса Environment. Это означает, что наши агенты могут использоваться вне стандартных исследовательских сред, например, будучи частью REST API, многоагентной системы и т. Д. Любой код, передающий State и вознаграждение, совместим с нашими агентами.

Аппроксимация функций

Почти все, что делает агент глубокого обучения с подкреплением, основано на аппроксимации функции.

По этой причине одной из центральных абстракций в autonomous-learning-library является Approximation. Создавая агенты, которые полагаются на Approximation абстракцию, а не напрямую взаимодействуют с объектами PyTorch Module и Optimizer, мы можем добавлять или изменять функциональность Agent без изменения его исходного кода (это известно как принцип открытости-закрытости). Объект Approximation по умолчанию позволяет нам достичь высокого уровня повторного использования кода, инкапсулируя общие функции, такие как ведение журнала, контрольные точки модели, целевые сети, графики скорости обучения и отсечение градиента. Объект Approximation, в свою очередь, полагается на набор абстракций, которые позволяют пользователям изменять его поведение. Давайте посмотрим на простое использование Approximation для решения очень простой задачи контролируемого обучения:

import torch
from torch import nn, optim
from all.approximation import Approximation
# create a pytorch module
model = nn.Linear(16, 1)
# create an associated pytorch optimizer
optimizer = optim.Adam(model.parameters(), lr=1e-2)
# create the function approximator
f = Approximation(model, optimizer)
for _ in range(200):
    # Generate some arbitrary data.
    # We'll approximate a very simple function:
    # the sum of the input features.
    x = torch.randn((16, 16))
    y = x.sum(1, keepdim=True)
    # forward pass
    y_hat = f(x)
    # compute loss
    loss = nn.functional.mse_loss(y_hat, y)
    # backward pass
    f.reinforce(loss)

Легкий! Теперь давайте посмотрим на функцию _train () для нашего агента DQN:

def _train(self):
    if self._should_train():
        (states, actions, rewards, next_states, _) = self.replay_buffer.sample(self.minibatch_size)
        # forward pass
        values = self.q(states, actions)
        targets = rewards + self.discount_factor * torch.max(self.q.target(next_states), dim=1)[0]
        # compute loss
        loss = mse_loss(values, targets)
        # backward pass
        self.q.reinforce(loss)

Так же просто! Агенту не нужно ничего знать о сетевой архитектуре, журналировании, регуляризации и т. Д. Все это обрабатывается с помощью соответствующей конфигурации Approximation. Вместо этого реализация Agent может сосредоточиться исключительно на своей единственной цели: определении самого алгоритма RL. Инкапсулируя эти детали в Approximation, мы можем следовать принципу единой ответственности.

Еще несколько замечаний: f.eval(x) выполняет прямой проход в torch.no_grad(). f.target(x) вызывает целевую сеть (расширенная концепция, используемая в алгоритмах, таких как DQN), связанная с Approximation. . autonomous-learning-library также предоставляет несколько тонких оболочек над Approximation для определенных целей, таких как QNetwork, VNetwork, FeatureNetwork и несколько Policy реализации.

Среды

Само собой разумеется, что важность Environment в обучении с подкреплением. В autonomous-learning-library предварительно упакованные среды представляют собой просто оболочки для OpenAI Gym, стандартной библиотеки де-факто для сред RL.

Мы добавляем несколько дополнительных функций:

  1. gym в основном использует numpy.array для представления состояний и действий. Мы автоматически конвертируем в torch.Tensor объекты и обратно, так что реализация агента не должна учитывать разницу.
  2. Мы добавляем свойства в среду для state, reward и т. Д. Это упрощает цикл управления и обычно полезно.
  3. Мы применяем общие препроцессоры, такие как несколько стандартных оболочек Atari. Однако, где это возможно, мы предпочитаем выполнять предварительную обработку с использованием Body объектов, чтобы максимизировать гибкость агентов.

Ниже мы покажем, как можно создать несколько различных типов сред:

from all.environments import AtariEnvironment, GymEnvironment
# create an Atari environment on the gpu
env = AtariEnvironment('Breakout', device='cuda')
# create a classic control environment on the compute
env = GymEnvironment('CartPole-v0')
# create a PyBullet environment on the cpu
import pybullet_envs
env = GymEnvironment('HalfCheetahBulletEnv-v0')

Теперь мы можем написать наш первый цикл управления:

# initialize the environment
env.reset()
# Loop for some arbitrary number of timesteps.
for timesteps in range(1000000):
    env.render()
    action = agent.act(env.state, env.reward)
    env.step(action)
    if env.done:
        # terminal update
        agent.act(env.state, env.reward)
        # reset the environment
        env.reset()

Конечно, этот контур управления не совсем многофункциональный. Как правило, лучше использовать Experiment API, описанный ниже.

Пресеты

В autonomous-learning-library агенты являются композиционными, что означает, что поведение данного Agent зависит от поведения нескольких других объектов. Пользователи могут составлять агентов с определенным поведением, передавая соответствующие объекты в конструкторы высокоуровневых алгоритмов, содержащихся в all.agents. Библиотека предоставляет ряд функций, которые создают эти объекты определенным образом, чтобы они подходили для данного набора среды. Мы называем такую ​​функцию preset, и несколько таких предустановок содержатся в пакете all.presets. (Это пример более общего паттерна фабричного метода).

Например, all.agents.vqn содержит высокоуровневое описание стандартного алгоритма Q-обучения. Чтобы фактически применить этот агент к проблеме, например, классической задаче управления, мы могли бы определить следующую предустановку:

# The outer function signature contains the set of hyperparameters
def vqn(
    # Common settings
    device="cpu",
    # Hyperparameters
    discount_factor=0.99,
    lr=1e-2,
    exploration=0.1,
):
    # The inner function creates a closure over the hyperparameters passed into the outer function.
    # It accepts an "env" object which is passed right before the Experiment begins, as well as
    # the writer created by the Experiment which defines the logging parameters.
    def _vqn(env, writer=DummyWriter()):
        # create a pytorch model
        model = nn.Sequential(
            nn.Linear(env.state_space.shape[0], 64),
            nn.ReLU(),
            nn.Linear(64, env.action_space.n),
        ).to(device)
        # create a pytorch optimizer for the model
        optimizer = Adam(model.parameters(), lr=lr)
        # create an Approximation of the Q-function
        q = QNetwork(model, optimizer, writer=writer)
        # create a Policy object derived from the Q-function
        policy = GreedyPolicy(q, env.action_space.n, epsilon=exploration)
        # instansiate the agent
        return VQN(q, policy, discount_factor=discount_factor)
    # return the inner function
    return _vqn

Обратите внимание на то, что есть «внешняя функция» и «внутренняя» функция. Этот подход позволяет разделить конфигурацию и создание экземпляра. Хотя это может показаться излишним, иногда это может быть полезно. Например, предположим, что мы хотим запустить один и тот же агент в нескольких средах. Это можно сделать следующим образом:

agent = vqn()
some_custom_runner(agent(), GymEnvironment('CartPole-v0'))
some_custom_runner(agent(), GymEnvironment('MountainCar-v0'))

Теперь каждый вызов some_custom_runner получает уникальный экземпляр агента. Иногда это достигается в других библиотеках с помощью функции «сброса». Мы считаем, что наш подход позволяет нам поддерживать Agent интерфейс в чистоте и в целом является более элегантным и менее подверженным ошибкам.

Эксперимент

Наконец, у нас есть все компоненты, необходимые для представления объекта Experiment. Experiment - это встроенный контур управления для проведения эксперимента по обучению с подкреплением. Он создает собственный объект Writer, который затем передается каждому из агентов, и запускает каждого агента в каждой переданной ему среде в течение некоторого количества временных шагов (кадров) или эпизодов. Вот краткий пример:

from gym import envs
from all.experiments import Experiment
from all.presets import atari
from all.environments import AtariEnvironment
agents = [
    atari.dqn(),
    atari.ddqn(),
    atari.c51(),
    atari.rainbow(),
    atari.a2c(),
    atari.ppo(),
]
envs = [AtariEnvironment(env, device='cuda') for env in ['BeamRider', 'Breakout', 'Pong', 'Qbert', 'SpaceInvaders']]
Experiment(agents, envs, frames=10e6)

Вышеупомянутый блок выполняет каждый запуск последовательно. Это может занять очень много времени даже на быстром GPU! Если у вас есть доступ к кластеру, в котором запущен Slurm, вы можете заменить Experiment на SlurmExperiment, чтобы существенно ускорить процесс (магия отправки заданий осуществляется за кулисами).

По умолчанию Experiment записывает результаты в ./runs. Вы можете просмотреть результаты в tensorboard, выполнив следующую команду:

tensorboard --logdir runs

Помимо tensorboard журналов, каждые 100 эпизодов в runs/[agent]/[env]/returns100.csv записываются среднее значение и стандартное отклонение предыдущих 100 эпизодов. Этот формат намного быстрее читается и строится, чем собственный формат Tensorboard. Библиотека содержит утилиту автоматического построения графиков, которая генерирует соответствующие графики для всего runs каталога следующим образом:

from all.experiments import plot_returns_100
plot_returns_100('./runs')

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

Вы также можете передать Experiment необязательные параметры, чтобы изменить его поведение. Вместо указания frames вы можете указать максимальное количество episodes. Вы можете настроить render=True для наблюдения за агентом во время обучения (обычно не рекомендуется: это значительно замедляет работу агента!). Вы можете настроить quiet=True на отключение вывода командной строки. Наконец, вы можете установить write_loss=False, чтобы отключить запись отладочной информации в tensorboard. Эти файлы могут становиться большими, поэтому рекомендуется, если у вас ограниченное хранилище!

Структура проекта

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

  1. Файл agent.py, содержащий высокоуровневую реализацию Agent.
  2. model.py файл, содержащий модели PyTorch, соответствующие выбранному вами домену.
  3. Файл preset.py, составляющий ваш Agent с использованием соответствующей модели и других объектов.
  4. main.py или аналогичный файл, который запускает ваш агент и любые autonomous-learning-library пресеты, с которыми вы хотите сравнить.

Хотя нет необходимости следовать этой структуре, мы считаем, что в целом она поможет вам использовать autonomous-learning-library по назначению и обеспечит понимание вашего кода другими пользователями библиотеки.

Резюме

В центре autonomous-learning-library находится Agent. Agent состоит из Approximation и других объектов и может быть настроен и настроен для конкретного Environment путем определения preset. Experiment API позволяет обучать preset агента на Environment и автоматически генерирует всевозможные журналы и другую информацию.

Вот и все! А теперь займись чем-нибудь крутым!