Проксимальная политика оптимизации с использованием PyTorch и Gymnasium

Проксимальная оптимизация стратегии (PPO) – один из предпочтительных алгоритмов для решения проблем Обучения с подкреплением (RL). Он был разработан в 2017 году Джоном Шуманом, соучредителем OpenAI. 

PPO широко используется в OpenAI для обучения моделей эмулировать человекоподобное поведение. Он улучшает ранее использовавшиеся методы, такие как Trust Region Policy Optimization (TRPO), и стал популярным из-за своей надежности и эффективности. 

В этом уроке мы подробно рассмотрим PPO. Мы рассмотрим теорию и продемонстрируем, как его реализовать с использованием PyTorch.

Понимание проксимальной оптимизации стратегии (PPO)

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

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

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

Обновления политик в доверительной области (TRPO)

Алгоритм обновлений политик в доверительной области (TRPO) был предложен в 2015 году Джоном Шульманом (который также предложил PPO в 2017 году). Для измерения разницы между старой политикой и обновленной политикой TRPO использует дивергенцию Кульбака-Лейблера (KL). Дивергенция KL используется для измерения разницы между двумя распределениями вероятностей. TRPO оказался эффективным в реализации доверительных областей.

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

Вот почему PPO стал предпочтительным по сравнению с TRPO в решении задач RL. Благодаря более эффективному методу оценки доверительных областей, PPO эффективно балансирует производительность и стабильность.

Приближенное управление политикой (PPO)

PPO часто рассматривается как подкласс методов актер-критик, которые обновляют градиенты политики на основе функции ценности. Методы Advantage Actor-critic (A2C) используют параметр, называемый преимуществом. Это измеряет разницу между предсказанными критиком доходами и реализованными доходами при реализации политики.

Чтобы понять PPO, вам нужно знать его компоненты:

  1. Актер выполняет политику. Он реализован в виде нейронной сети. Учитывая состояние в качестве входных данных, он выдает действие, которое нужно предпринять.
  2. Критик – это еще одна нейронная сеть. Он берет состояние на вход и выводит ожидаемое значение этого состояния. Таким образом, критик выражает функцию значения состояния.
  3. Методы на основе градиента политики могут выбирать разные целевые функции. В частности, PPO использует функцию преимущества. Функция преимущества измеряет величину, на которую кумулятивная награда (на основе политики, реализованной актером) превышает ожидаемую базовую награду (как предсказано критиком). Цель PPO – увеличить вероятность выбора действий с высоким преимуществом. Оптимизационная цель PPO использует функции потерь, основанные на этой функции преимущества.
  4. Отсеченная целевая функция является основным новшеством в PPO. Она предотвращает большие обновления политики в одной обучающей итерации. Она ограничивает, насколько политика обновляется в одной итерации. Для измерения инкрементальных обновлений политики методы, основанные на политике, используют вероятностное отношение новой политики к старой политике. 
  5. Замещающая потеря является целевой функцией в PPO и учитывает упомянутые ранее новшества. Она вычисляется следующим образом:
    1. Вычислите фактическое отношение (как объяснено ранее) и умножьте его на преимущество. 
    2. Обрежьте соотношение так, чтобы оно находилось в желаемом диапазоне. Умножьте обрезанное отношение на преимущество.
    3. Возьмите минимальное значение из вышеуказанных двух величин.
  6. На практике к замене функции потерь также добавляется энтропийный член. Это называется бонусом энтропии. Он основан на математическом распределении вероятностей действий. Идея бонуса энтропии заключается в том, чтобы вводить дополнительную случайность контролируемым образом. Это побуждает процесс оптимизации исследовать пространство действий. Высокий бонус энтропии поощряет исследование перед эксплуатацией.

Понимание механизма обрезки.

Предположим, что по старой политике πстарая, вероятность выполнения действия a в состоянии s равна πстарая(a|s). При новой политике вероятность выполнения того же действия a из того же состояния s обновляется до πновая(a|s). Отношение этих вероятностей как функция параметров политики θ, это r(θ). Когда новая политика делает действие более вероятным (в том же состоянии), отношение больше 1, и наоборот. 

Механизм обрезки ограничивает это соотношение вероятностей таким образом, что новые вероятности действий должны находиться в пределах определенного процента от старых вероятностей действий. Например, r(θ) может быть ограничен в пределах от 0.8 до 1.2. Это предотвращает большие скачки, что в свою очередь обеспечивает стабильный процесс обучения.

В остальной части этой статьи вы узнаете, как собрать компоненты для простой реализации PPO с использованием PyTorch.

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

Установка PyTorch и необходимых библиотек

Нам нужно установить следующее программное обеспечение: 

  • PyTorch и другие библиотеки, такие как numpy (для математических и статистических функций) и matplotlib (для построения графиков).
  • Открытый исходный код Gym программного обеспечения от OpenAI, библиотека Python, которая моделирует различные среды и игры, которые могут быть решены с использованием обучения с подкреплением. Вы можете использовать API Gym для взаимодействия вашего алгоритма с окружающей средой. Поскольку функциональность gym иногда меняется в процессе обновления, в этом примере мы фиксируем его версию на 0.25.2.

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

$ pip install torch numpy matplotlib gym==0.25.2

Для установки с использованием блокнота, например Google Colab или DataLab, используйте: 

!pip install torch numpy matplotlib gym==0.25.2

Создайте среду CartPole

Используйте OpenAI Gym для создания двух экземпляров (одного для обучения и другого для тестирования) среды CartPole:

env_train = gym.make('CartPole-v1') env_test = gym.make('CartPole-v1')

Теперь давайте реализуем PPO с использованием PyTorch.

Определение сети политики

Как уже объяснено, PPO реализуется как модель актер-критик. Актер реализует политику, а критик предсказывает ее оценочное значение. Обе нейронные сети актера и критика принимают одинаковый вход – состояние на каждом временном шаге. Таким образом, модели актера и критика могут использовать общую нейронную сеть, которая называется архитектурой основы. Актер и критик могут расширить архитектуру основы дополнительными слоями.

Определите основную сеть

Следующие шаги описывают основную сеть:

  • Реализуйте сеть с 3 слоями – входным, скрытым и выходным слоями. 
  • После входного и скрытого слоев используем функцию активации. В этом учебнике мы выбираем ReLU, поскольку она вычислительно эффективна.
  • Мы также применяем функцию отсева после входного и скрытых слоев для получения надежной сети. Функция отсева случайным образом обнуляет некоторые нейроны. Это снижает зависимость от конкретных нейронов и предотвращает переобучение, делая сеть более надежной.

Ниже приведен код, реализующий основу:

class BackboneNetwork(nn.Module): def __init__(self, in_features, hidden_dimensions, out_features, dropout): super().__init__() self.layer1 = nn.Linear(in_features, hidden_dimensions) self.layer2 = nn.Linear(hidden_dimensions, hidden_dimensions) self.layer3 = nn.Linear(hidden_dimensions, out_features) self.dropout = nn.Dropout(dropout) def forward(self, x): x = self.layer1(x) x = f.relu(x) x = self.dropout(x) x = self.layer2(x) x = f.relu(x) x = self.dropout(x) x = self.layer3(x) return x

Определите сеть актера-критика

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

class ActorCritic(nn.Module): def __init__(self, actor, critic): super().__init__() self.actor = actor self.critic = critic def forward(self, state): action_pred = self.actor(state) value_pred = self.critic(state) return action_pred, value_pred

Создайте экземпляры сетей актера и критика

Мы будем использовать определенные выше сети для создания актера и критика. Затем мы создадим агента, включая актера и критика.

Перед созданием агента инициализируйте параметры сети:

  • Размеры скрытого слоя, H, который является настраиваемым параметром. Размер и количество скрытых слоев зависят от сложности проблемы. Мы будем использовать скрытый слой с размерами 64 x 64.
  • Входные признаки, N, где N – размер массива состояний. Входной слой имеет размеры N x H. В среде CartPole состояние представляет собой массив из 4 элементов. Поэтому N равно 4.
  • Выходные признаки сети актера, O, где O – количество действий в среде. Выходной слой актера имеет размеры H x O. В среде CartPole 2 действия.
  • Выходные признаки сети критика. Поскольку сеть критика предсказывает только ожидаемое значение (при заданном входном состоянии), количество выходных признаков равно 1.
  • Выпадение как дробь.

Следующий код показывает, как объявить сети актера и критика на основе основной сети:

def create_agent(hidden_dimensions, dropout): INPUT_FEATURES = env_train.observation_space.shape[0] HIDDEN_DIMENSIONS = hidden_dimensions ACTOR_OUTPUT_FEATURES = env_train.action_space.n CRITIC_OUTPUT_FEATURES = 1 DROPOUT = dropout actor = BackboneNetwork( INPUT_FEATURES, HIDDEN_DIMENSIONS, ACTOR_OUTPUT_FEATURES, DROPOUT) critic = BackboneNetwork( INPUT_FEATURES, HIDDEN_DIMENSIONS, CRITIC_OUTPUT_FEATURES, DROPOUT) agent = ActorCritic(actor, critic) return agent

Вычисление возвратов

Среда дает вознаграждение с каждого шага на следующий, в зависимости от действия агента. Вознаграждение, R, выражается как:

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

В этом руководстве (и многих других источниках) возврат означает дисконтированный возврат.

Для вычисления возврата:

  • Начните с ожидаемых вознаграждений от всех будущих состояний.
  • Умножьте каждое будущее вознаграждение на экспонент дисконтного фактора, . Например, ожидаемое вознаграждение после 2 шагов (от настоящего) умножается на 2.
  • Сложите все дисконтированные будущие вознаграждения, чтобы вычислить возврат.
  • Нормализуйте значение возврата.

Функция calculate_returns() выполняет эти вычисления, как показано ниже:

def calculate_returns(rewards, discount_factor): returns = [] cumulative_reward = 0 for r in reversed(rewards): cumulative_reward = r + cumulative_reward * discount_factor returns.insert(0, cumulative_reward) returns = torch.tensor(returns) # нормализовать возврат returns = (returns - returns.mean()) / returns.std() return returns

Реализация функции преимущества

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

В оригинальной статье PPO (уравнение 10) преимущество, просматриваемое до момента времени T, выражается следующим образом:

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

Следовательно, упрощенная формула ниже выражает преимущество выбора: 

  • конкретного действия 
  • в данном состоянии 
  • при конкретной стратегии 
  • в конкретный момент времени 

Это выражается как: 

OpenAI также использует эту формулу для реализации RL. Функция calculate_advantages() показанная ниже, вычисляет преимущество:

def calculate_advantages(returns, values): advantages = returns - values # Нормализация преимущества advantages = (advantages - advantages.mean()) / advantages.std() return advantages

Замещающая потеря и механизм обрезки

Потеря политики будет стандартной потерей градиента политики без особых техник, таких как PPO. Стандартная потеря градиента политики рассчитывается как произведение:

  • Вероятностей действий политики
  • Функции преимущества, которая рассчитывается как разница между:
    • Возвратом политики
    • Ожидаемым значением

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

  • Произведение:
    • Политика отношения. Это отношение выражает разницу между старыми и новыми вероятностями действий.
    • Функция преимущества
  • Произведение:
    • Зажатое значение политики отношения. Это отношение усекается таким образом, чтобы обновленная политика находилась в определенном процентном соотношении с прежней политикой.
    • Функция преимущества

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

Механизм обрезки

Отношение политики, R, представляет собой разницу между новыми и старыми политиками и выражается в виде отношения логарифмов вероятностей политики при новых и старых параметрах:

Обрезанное отношение политики, R’, ограничено таким образом, что: 

Учитывая преимущество, Ат, как показано в предыдущем разделе, и коэффициент политики, как показано выше, замещающий убыток рассчитывается следующим образом:

Приведенный ниже код показывает, как реализовать механизм обрезки и замещающий убыток.

def calculate_surrogate_loss( actions_log_probability_old, actions_log_probability_new, epsilon, advantages): advantages = advantages.detach() policy_ratio = ( actions_log_probability_new - actions_log_probability_old ).exp() surrogate_loss_1 = policy_ratio * advantages surrogate_loss_2 = torch.clamp( policy_ratio, min=1.0-epsilon, max=1.0+epsilon ) * advantages surrogate_loss = torch.min(surrogate_loss_1, surrogate_loss_2) return surrogate_loss

Теперь давайте обучим агента.

Расчет потерь политики и значения

Мы готовы рассчитать потери политики и значения:

  • Политический ущерб – это сумма замещающего ущерба и бонуса энтропии.
  • Ущерб ценности основан на разнице между значением, предсказанным критиком, и возвращениями (накопленная награда), генерируемыми политикой. Вычисление убытков ценности использует функцию Smooth L1 Loss. Это помогает сгладить функцию потерь и делает ее менее чувствительной к выбросам.

Оба ущерба, рассчитанные выше, являются тензорами. Градиентный спуск основан на скалярных значениях. Чтобы получить одно скалярное значение, представляющее ущерб, используйте функцию .sum() для суммирования элементов тензора. Ниже приведена функция, показывающая, как это сделать:

def calculate_losses( surrogate_loss, entropy, entropy_coefficient, returns, value_pred): entropy_bonus = entropy_coefficient * entropy policy_loss = -(surrogate_loss + entropy_bonus).sum() value_loss = f.smooth_l1_loss(returns, value_pred).sum() return policy_loss, value_loss

Определение цикла обучения

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

def init_training(): states = [] actions = [] actions_log_probability = [] values = [] rewards = [] done = False episode_reward = 0 return states, actions, actions_log_probability, values, rewards, done, episode_reward

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

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

Перед запуском цикла обучения:

  • Установите модель в режим обучения, используя agent.train().
  • Сбросьте среду в случайное состояние, используя env.reset(). Это начальное состояние для данной итерации обучения.

Следующие шаги объясняют, что происходит на каждом временном шаге в цикле обучения:

  • Передайте состояние агенту.
  • Агент возвращает:
    • Предсказанное действие для данного состояния на основе политики (актора). Передайте этот тензор предсказанного действия через функцию softmax, чтобы получить набор вероятностей действий.
    • Предсказанное значение состояния, основанное на критике.
  • Агент выбирает действие:
    • Используйте вероятности действий для оценки распределения вероятностей.
    • Случайным образом выберите действие, выбрав образец из этого распределения. Функция dist.sample() делает это.
  • Используйте функцию env.step(), чтобы передать это действие в среду и смоделировать ответ среды на этот временной шаг. На основе действия агента среда генерирует:
    • Новое состояние
    • Награда
    • Логическое значение возврата done (указывает, достигло ли окружение терминального состояния)
  • Добавьте в соответствующие буферы значения действий агента, награды, предсказанные значения и новое состояние.

Обучающий эпизод завершается, когда функция env.step() возвращает true для логического значения возврата done.

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

Следующая функция на Python показывает, как реализовать эти шаги:

def forward_pass(env, agent, optimizer, discount_factor): states, actions, actions_log_probability, values, rewards, done, episode_reward = init_training() state = env.reset() agent.train() while not done: state = torch.FloatTensor(state).unsqueeze(0) states.append(state) action_pred, value_pred = agent(state) action_prob = f.softmax(action_pred, dim=-1) dist = distributions.Categorical(action_prob) action = dist.sample() log_prob_action = dist.log_prob(action) state, reward, done, _ = env.step(action.item()) actions.append(action) actions_log_probability.append(log_prob_action) values.append(value_pred) rewards.append(reward) episode_reward += reward states = torch.cat(states) actions = torch.cat(actions) actions_log_probability = torch.cat(actions_log_probability) values = torch.cat(values).squeeze(-1) returns = calculate_returns(rewards, discount_factor) advantages = calculate_advantages(returns, values) return episode_reward, states, actions, actions_log_probability, advantages, returns

Обновление параметров модели

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

Максимальное количество шагов в среде CartPole составляет 500. В более сложных средах количество шагов может быть даже миллионами. В таких случаях набор результатов обучения должен быть разделен на пакеты. Количество шагов в каждом пакете называется размером оптимизационного пакета.

Таким образом, шаги по обновлению параметров модели следующие:

  • Разделите набор результатов обучения на пакеты.
  • Для каждого пакета:
    • Получите действие агента и предсказанное значение для каждого состояния.
    • Используйте эти предсказанные действия для оценки нового распределения вероятностей действий.
    • Используйте эту дистрибуцию для расчета энтропии.
    • Используйте эту дистрибуцию для получения логарифма вероятности действий в наборе данных результатов обучения. Это новый набор логарифмов вероятностей действий в наборе данных результатов обучения. Старый набор логарифмов вероятностей этих же действий был рассчитан в цикле обучения, объясненном в предыдущем разделе.
    • Рассчитайте замещающие потери, используя старое и новое распределения вероятностей действий.
    • Рассчитайте потери политики и потери значения, используя замещающие потери, энтропию и преимущества.
    • Запустите .backward() отдельно для потерь политики и значения. Это обновит градиенты на функциях потерь.
    • Запустите .step() на оптимизаторе, чтобы обновить параметры политики. В этом случае мы используем оптимизатор Adam для балансировки скорости и надежности.
    • Накопите потери политики и значений.
  • Повторите обратный проход (вышеуказанные операции) на каждой партии несколько раз, в зависимости от значения параметра PPO_STEPS. Повторение обратного прохода на каждой партии является вычислительно эффективным, поскольку это эффективно увеличивает размер обучающего набора данных без необходимости запускать дополнительные прямые проходы. Количество шагов среды в каждой альтернации между выборкой и оптимизацией называется размером пакета итерации.
  • Вернуть среднюю потерю политики и потерю значения.

Ниже приведен код, реализующий эти шаги:

def update_policy( agent, states, actions, actions_log_probability_old, advantages, returns, optimizer, ppo_steps, epsilon, entropy_coefficient): BATCH_SIZE = 128 total_policy_loss = 0 total_value_loss = 0 actions_log_probability_old = actions_log_probability_old.detach() actions = actions.detach() training_results_dataset = TensorDataset( states, actions, actions_log_probability_old, advantages, returns) batch_dataset = DataLoader( training_results_dataset, batch_size=BATCH_SIZE, shuffle=False) for _ in range(ppo_steps): for batch_idx, (states, actions, actions_log_probability_old, advantages, returns) in enumerate(batch_dataset): # получить новые логарифмические вероятности действий для всех входных состояний action_pred, value_pred = agent(states) value_pred = value_pred.squeeze(-1) action_prob = f.softmax(action_pred, dim=-1) probability_distribution_new = distributions.Categorical( action_prob) entropy = probability_distribution_new.entropy() # оценить новые логарифмические вероятности, используя старые действия actions_log_probability_new = probability_distribution_new.log_prob(actions) surrogate_loss = calculate_surrogate_loss( actions_log_probability_old, actions_log_probability_new, epsilon, advantages) policy_loss, value_loss = calculate_losses( surrogate_loss, entropy, entropy_coefficient, returns, value_pred) optimizer.zero_grad() policy_loss.backward() value_loss.backward() optimizer.step() total_policy_loss += policy_loss.item() total_value_loss += value_loss.item() return total_policy_loss / ppo_steps, total_value_loss / ppo_steps

Наконец, давайте запустим агента PPO.

Оценка производительности 

Для оценки производительности агента создайте новую среду и рассчитайте накопленные вознаграждения от запуска агента в этой новой среде. Вам нужно установить агент в режим оценки, используя функцию .eval(). Шаги те же, что и для цикла обучения. Ниже приведен фрагмент кода, реализующий функцию оценки: 

def evaluate(env, agent): agent.eval() rewards = [] done = False episode_reward = 0 state = env.reset() while not done: state = torch.FloatTensor(state).unsqueeze(0) with torch.no_grad(): action_pred, _ = agent(state) action_prob = f.softmax(action_pred, dim=-1) action = torch.argmax(action_prob, dim=-1) state, reward, done, _ = env.step(action.item()) episode_reward += reward return episode_reward

Визуализация результатов обучения

Мы будем использовать библиотеку Matplotlib для визуализации прогресса процесса обучения. Ниже приведена функция, показывающая, как построить графики наград из обучающего и тестового циклов:

def plot_train_rewards(train_rewards, reward_threshold): plt.figure(figsize=(12, 8)) plt.plot(train_rewards, label='Training Reward') plt.xlabel('Episode', fontsize=20) plt.ylabel('Training Reward', fontsize=20) plt.hlines(reward_threshold, 0, len(train_rewards), color='y') plt.legend(loc='lower right') plt.grid() plt.show()
def plot_test_rewards(test_rewards, reward_threshold): plt.figure(figsize=(12, 8)) plt.plot(test_rewards, label='Testing Reward') plt.xlabel('Episode', fontsize=20) plt.ylabel('Testing Reward', fontsize=20) plt.hlines(reward_threshold, 0, len(test_rewards), color='y') plt.legend(loc='lower right') plt.grid() plt.show()

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

Обучающие вознаграждения (полученные применением политики в обучающей среде). Изображение Автора.

Тестовые вознаграждения (полученные применением политики в тестовой среде). Изображение Автора.

На выводных графиках, показанных выше, наблюдайте ход обучающего процесса:

  • Награда начинается с низких значений. По мере продвижения обучения награды увеличиваются.
  • Награды случайным образом колеблются, увеличиваясь. Это связано с тем, что агент исследует пространство политики.
  • Обучение завершается, и тестовые награды стабилизировались вокруг порога (475) в течение многих итераций.
  • Награды ограничены 500. Это ограничения, накладываемые средой (Gym CartPole v1).

Аналогично, вы можете построить графики значений потерь и политики в течение итераций:

def plot_losses(policy_losses, value_losses): plt.figure(figsize=(12, 8)) plt.plot(value_losses, label='Value Losses') plt.plot(policy_losses, label='Policy Losses') plt.xlabel('Episode', fontsize=20) plt.ylabel('Loss', fontsize=20) plt.legend(loc='lower right') plt.grid() plt.show()

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

Потери значимости и политики в процессе обучения. Изображение автора

Наблюдайте за графиком и обратите внимание:

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

Запустите алгоритм PPO

Теперь у вас есть все компоненты для обучения агента с использованием PPO. Чтобы всё это объединить, вам нужно:

  • Объявить гиперпараметры, такие как коэффициент дисконтирования, размер пакета, скорость обучения и т. д.
  • Создайте буферы как пустые массивы для хранения вознаграждений и потерь с каждой итерацией.
  • Создайте экземпляр агента, используя функцию create_agent().
  • Итеративно выполняйте прямой и обратный проходы, используя функции forward_pass() и update_policy().
  • Проверьте производительность политики, используя функцию evaluate().
  • Добавьте политику, потери значений и вознаграждения из функций обучения и оценки в соответствующие буферы.
  • Вычислите среднее значение вознаграждений и потерь за последние несколько временных шагов. Приведенный ниже пример усредняет вознаграждения и потери за последние 40 временных шагов.
  • Выводите результаты оценки через каждые несколько шагов. Приведенный ниже пример выводит каждые 10 шагов.
  • Прекратите процесс, когда среднее вознаграждение превысит определенный порог.

Приведенный ниже код показывает, как объявить функцию, выполняющую это в Python:

def run_ppo(): MAX_EPISODES = 500 DISCOUNT_FACTOR = 0.99 REWARD_THRESHOLD = 475 PRINT_INTERVAL = 10 PPO_STEPS = 8 N_TRIALS = 100 EPSILON = 0.2 ENTROPY_COEFFICIENT = 0.01 HIDDEN_DIMENSIONS = 64 DROPOUT = 0.2 LEARNING_RATE = 0.001 train_rewards = [] test_rewards = [] policy_losses = [] value_losses = [] agent = create_agent(HIDDEN_DIMENSIONS, DROPOUT) optimizer = optim.Adam(agent.parameters(), lr=LEARNING_RATE) for episode in range(1, MAX_EPISODES+1): train_reward, states, actions, actions_log_probability, advantages, returns = forward_pass( env_train, agent, optimizer, DISCOUNT_FACTOR) policy_loss, value_loss = update_policy( agent, states, actions, actions_log_probability, advantages, returns, optimizer, PPO_STEPS, EPSILON, ENTROPY_COEFFICIENT) test_reward = evaluate(env_test, agent) policy_losses.append(policy_loss) value_losses.append(value_loss) train_rewards.append(train_reward) test_rewards.append(test_reward) mean_train_rewards = np.mean(train_rewards[-N_TRIALS:]) mean_test_rewards = np.mean(test_rewards[-N_TRIALS:]) mean_abs_policy_loss = np.mean(np.abs(policy_losses[-N_TRIALS:])) mean_abs_value_loss = np.mean(np.abs(value_losses[-N_TRIALS:])) if episode % PRINT_INTERVAL == 0: print(f'Episode: {episode:3} | \ Mean Train Rewards: {mean_train_rewards:3.1f} \ | Mean Test Rewards: {mean_test_rewards:3.1f} \ | Mean Abs Policy Loss: {mean_abs_policy_loss:2.2f} \ | Mean Abs Value Loss: {mean_abs_value_loss:2.2f}') if mean_test_rewards >= REWARD_THRESHOLD: print(f'Reached reward threshold in {episode} episodes') break plot_train_rewards(train_rewards, REWARD_THRESHOLD) plot_test_rewards(test_rewards, REWARD_THRESHOLD) plot_losses(policy_losses, value_losses)

Запустите программу:

run_ppo()

Вывод должен напоминать пример ниже:

Episode: 10 | Mean Train Rewards: 22.3 | Mean Test Rewards: 30.4 | Mean Abs Policy Loss: 0.37 | Mean Abs Value Loss: 0.39 Episode: 20 | Mean Train Rewards: 38.6 | Mean Test Rewards: 69.8 | Mean Abs Policy Loss: 0.46 | Mean Abs Value Loss: 0.37 . . . Episode: 100 | Mean Train Rewards: 289.5 | Mean Test Rewards: 427.3 | Mean Abs Policy Loss: 1.73 | Mean Abs Value Loss: 0.21 Episode: 110 | Mean Train Rewards: 357.7 | Mean Test Rewards: 461.4 | Mean Abs Policy Loss: 1.86 | Mean Abs Value Loss: 0.22 Reached reward threshold in 116 episodes

Вы можете просматривать и запускать рабочую программу на этом блокноте DataLab!

В машинном обучении гиперпараметры управляют процессом обучения. Ниже я объясняю некоторые из важных гиперпараметров, используемых в PPO: 

  • Скорость обучения: Скорость обучения определяет, насколько сильно могут изменяться параметры политики на каждой итерации. В стохастическом градиентном спуске количество, на которое обновляются параметры политики на каждой итерации, определяется произведением скорости обучения и градиента. 
  • Параметр обрезки: Это также называется эпсилон, ε. Он определяет степень обрезки коэффициента политики. Соотношение новой и старой политики может варьироваться в диапазоне [1-ε, 1+ε]. Когда оно выходит за этот диапазон, оно искусственно обрезается до значения в этом диапазоне. 
  • Размер партии: Это относится к количеству шагов, которые рассматриваются для каждого обновления градиента. В PPO размер партии – это количество временных шагов, необходимых для применения политики и вычисления заменяемых потерь для обновления параметров политики. В этой статье мы использовали размер партии 64.
  • Шаги итерации: Это количество раз, которые каждая партия повторно используется для выполнения обратного прохода. Код в этой статье ссылается на это как PPO_STEPS. В сложных средах выполнение прямого прохода много раз является вычислительно дорогостоящим. Более эффективной альтернативой является несколько повторных запусков каждой партии. Обычно рекомендуется использовать значение от 5 до 10.
  • Фактор дисконтирования: Этот фактор также называется гамма, γ. Он выражает степень, в которой немедленные вознаграждения ценнее будущих вознаграждений. Это аналогично концепции процентных ставок при расчете временной стоимости денег. Когда ближе к 0, это означает, что будущие вознаграждения менее ценны, и агент должен придавать приоритет немедленным вознаграждениям. Когда ближе к 1, это означает, что будущие вознаграждения важны.
  • Коэффициент энтропии: Коэффициент энтропии решает бонус энтропии, который рассчитывается как произведение коэффициента энтропии и энтропии распределения. Роль бонуса энтропии заключается во введении большего количества случайности в политику. Это побуждает агента исследовать пространство политики. Однако обучение не сходится к оптимальной политике, когда эта случайность слишком высока.
  • Критерии успеха для обучения: Необходимо установить критерии для определения успешности обучения. Обычным способом является установка условия, что средние вознаграждения за последние N испытаний (эпизодов) превышают определенный порог. В приведенном выше примере это выражается переменной N_TRIALS. Если установить более высокое значение, обучение займет больше времени, потому что стратегия должна достичь порогового вознаграждения за большее количество эпизодов. Это также приведет к созданию более надежной стратегии, но потребует больше вычислительных ресурсов. Обратите внимание, что PPO является стохастической стратегией, и будут эпизоды, когда агент не преодолеет порог. Поэтому, если значение N_TRIALS слишком высоко, ваше обучение может не завершиться.

Стратегии оптимизации производительности PPO

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

  • Фактор дисконтирования: Когда важны отдаленные вознаграждения, как в среде CartPole, где шесту нужно оставаться стабильным со временем, начните с умеренного значения гамма, например, 0,99.
  • Бонус энтропии: В сложных средах агент должен исследовать пространство действий, чтобы найти оптимальную стратегию. Бонус энтропии способствует исследованию. Бонус энтропии добавляется к заместительной потере. Проверьте величину заместительной потери и энтропию распределения перед принятием решения о коэффициенте энтропии. В этой статье мы использовали коэффициент энтропии 0,01.
  • Параметр обрезки: Параметр обрезки решает, насколько отличается обновленная политика от текущей политики. Большое значение параметра обрезки поощряет лучшее исследование окружающей среды, но рискует дестабилизировать обучение. Вам нужен параметр обрезки, который позволяет постепенное исследование, предотвращая дестабилизирующие обновления. В этой статье мы использовали параметр обрезки 0.2.
  • Скорость обучения: Когда скорость обучения слишком высока, политика обновляется большими шагами на каждой итерации, и процесс обучения может стать нестабильным. Когда она слишком низкая, обучение занимает слишком много времени. В этом руководстве использовалась скорость обучения 0.001, которая хорошо работает для данной среды. Во многих случаях рекомендуется использовать скорость обучения 1е-5.

Трудности и лучшие практики в PPO

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

Общие проблемы при обучении PPO

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

  • Медленная сходимость: В сложных средах PPO может быть неэффективным по образцам и требует множества взаимодействий с окружающей средой для сходимости к оптимальной стратегии. Это делает процесс обучения медленным и дорогостоящим.
  • Чувствительность к гиперпараметрам:PPO полагается на эффективное исследование пространства политики. Стабильность процесса обучения и скорость сходимости зависят от значений гиперпараметров. Оптимальные значения этих гиперпараметров часто могут быть определены только методом проб и ошибок.
  • Переобучение:Среды RL обычно инициализируются случайными параметрами. Обучение PPO основано на нахождении оптимальной политики на основе среды агента. Иногда процесс обучения сходится к набору оптимальных параметров только для одной конкретной среды, но не для любой случайной среды. Обычно это решается путем многократных итераций, каждая из которых имеет различную случайную обучающую среду.
  • Динамические среды: Простые среды RL, такие как среда CartPole, статичны – правила остаются неизменными со временем. Многие другие среды, например, робот, обучающийся ходить на нестабильной подвижной поверхности, динамичны – правила среды меняются со временем. Для успешной работы в таких средах PPO часто требуется дополнительная настройка. 
  • Исследование против эксплуатации: Механизм ограничения PPO гарантирует, что обновления политики находятся в доверенной области. Однако это также мешает агенту исследовать пространство действий. Это может привести к сходимости к локальным оптимумам, особенно в сложных средах. С другой стороны, слишком большое разрешение агенту на исследование может помешать ему сойтись к оптимальной политике. 

Лучшие практики для обучения моделей PPO

Для достижения хороших результатов при использовании PPO я рекомендую следовать некоторым bewt практикам, таким как:

  • Нормализуйте входные признаки: Нормализация значений доходов и преимуществ уменьшает изменчивость данных и приводит к стабильным обновлениям градиента. Нормализация данных приводит все значения к однородному числовому диапазону. Это помогает уменьшить влияние выбросов и экстремальных значений, которые в противном случае могут исказить обновления градиента и замедлить сходимость.
  • Используйте подходящие размеры пакетов: Небольшие пакеты позволяют быстрее обновлять и обучать, но могут привести к сходимости к локальным оптимумам и нестабильности в процессе обучения. Более крупные размеры пакетов позволяют агенту изучать надежные политики, что приводит к стабильному процессу обучения. Однако слишком большие размеры пакетов также неоптимальны. Помимо увеличения вычислительных затрат, они делают обновления политики менее отзывчивыми на функцию значения, потому что обновления градиента основаны на оценках средних значений по большим пакетам. Кроме того, это может привести к переобучению обновлений для этого конкретного пакета.
  • Шаги итерации: В целом рекомендуется повторно использовать каждый пакет 5-10 итераций. Это делает процесс обучения более эффективным. Повторное использование одного и того же пакета слишком много раз приводит к переобучению. В коде этот гиперпараметр называется PPO_STEPS.
  • Выполняйте регулярную оценку: Для выявления переобучения важно периодически контролировать эффективность политики. Если политика оказывается неэффективной в определенных сценариях, может потребоваться дальнейшее обучение или настройка.
  • Настройте гиперпараметры: Как было объяснено ранее, обучение PPO чувствительно к значениям гиперпараметров. Экспериментируйте с различными значениями гиперпараметров, чтобы определить правильный набор значений для вашей конкретной проблемы.
  • Общая сеть основы: Как показано в этой статье, использование общей основы предотвращает дисбаланс между сетями актера и критика. Общая сеть основы между актером и критиком помогает с общим извлечением функций и общим пониманием среды. Это делает процесс обучения более эффективным и стабильным. Это также помогает уменьшить вычислительное пространство и временную сложность алгоритма. 
  • Количество и размер скрытых слоев: Увеличьте количество скрытых слоев и размеры для более сложных сред. Простые проблемы, такие как CartPole, могут быть решены с помощью одного скрытого слоя. Скрытый слой, используемый в этой статье, имеет 64 измерения. Создание сети намного больше, чем необходимо, является вычислительно затратным и может сделать ее нестабильной. 
  • Раннее прекращение: Прекращение обучения при достижении оценочных метрик помогает предотвратить переобучение и избежать ресурсных потерь. Одним из распространенных критериев оценки является превышение агентом пороговых наград за последние N событий.

Заключение

В этой статье мы обсудили PPO как способ решения проблем RL. Затем мы подробно рассмотрели шаги по реализации PPO с использованием PyTorch. Наконец, мы представили некоторые советы по производительности и лучшие практики для PPO.

Лучший способ научиться – реализовать код самостоятельно. Вы также можете модифицировать код для работы с другими классическими контрольными средами в Gym. Чтобы узнать, как реализовать агентов RL с использованием Python и Gymnasium от OpenAI, следуйте курсу Обучение с подкреплением с Gymnasium на Python!

Source:
https://www.datacamp.com/tutorial/proximal-policy-optimization