Давайте начнем с быстрого освежения знаний об обучении с подкреплением и алгоритме DQN. Обучение с подкреплением - это область машинного обучения, которая направлена ​​на обучение агентов выполнению определенных действий в определенных состояниях из среды , чтобы получить максимальное вознаграждение. Допустим, я хочу сделать бота для игры в покер (агента). Бот будет играть с другими ботами за покерным столом с фишками и картами (окружение). Этот бот должен иметь возможность сбрасывать карты или делать ставки (действия) на основе карт на столе, карт в руке и ставок (состояний) других ботов. Бот хочет максимизировать количество фишек (вознаграждение), которое он должен иметь, чтобы выиграть игру.

DQN - это алгоритм обучения с подкреплением, в котором построена модель глубокого обучения для поиска действий, которые агент может предпринять в каждом состоянии.

Технические определения

Основные номенклатуры RL включают, но не ограничиваются: текущее состояние (s), состояние на следующем шаге (s ') , действие (a), политика (p) и вознаграждение (r). Функция состояния-действия-значения (Q (s, a)) - это ожидаемое общее вознаграждение для агента, начиная с текущего состояния и его результат известен как значение Q. Как я уже сказал, наша цель - выбрать определенное действие (a) в состоянии (s), чтобы максимизировать вознаграждение или значение Q.

DQN - это комбинация глубокого обучения и обучения с подкреплением. Целью модели является приблизительное значение Q (s, a), и оно обновляется путем обратного распространения. Предполагая, что аппроксимация Q (s, a) - это y (шляпа), а функция потерь - L, мы имеем:

прогноз: y (шляпа) = f (s, θ)

потеря: L (y, y (шляпа)) = L (Q (s, a), f (s, θ))

В процессе обратного распространения мы берем частную производную функции потерь от θ, чтобы найти значение θ, которое минимизирует потери. Поскольку это обучение с учителем, вы можете задаться вопросом, как найти основную истину Q (s, a). Ответ - уравнение Беллмана.

Уравнение Беллмана: Q (s, a) = max (r + Q (s ’, a))

куда

Q (s ', a) = f (s', θ), если s не является конечное состояние (состояние на последнем шаге)

Q (s ’, a) = 0, если s - конечное состояние

Мы видим, что когда s является конечным состоянием, Q (s, a) = r. Поскольку мы используем прогноз модели f (s ', θ) для аппроксимации реального значения Q (s', a), мы называем это полуградиентом. Как вы, возможно, поняли, проблема использования полуградиента заключается в том, что обновления модели могут быть очень нестабильными, поскольку реальная цель будет меняться каждый раз, когда модель обновляется. Решение состоит в том, чтобы создать целевую сеть, которая по сути является копией модели обучения на определенных временных шагах, чтобы целевая модель обновлялась реже.

Еще одна проблема с моделью - переоснащение. Когда мы обновляем модель после окончания каждой игры, мы потенциально уже выполнили сотни шагов, поэтому мы, по сути, выполняем пакетный градиентный спуск. Поскольку каждый пакет всегда содержит шаги из одной полной игры, модель может плохо учиться на нем. Чтобы решить эту проблему, мы создаем буфер воспроизведения опыта, в котором хранятся значения (s, s ', a, r) ​​ нескольких сотен игр, и случайным образом выбираем из него партию. каждый раз обновлять модель.

Что такое CartPole?

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

Чтобы переместить шест, нужно выполнить два действия: влево или вправо. За каждый сделанный шаг (включая шаг завершения) он получает +1 награду. Игра заканчивается, когда шест падает, то есть когда угол вешки превышает ± 12 ° или положение тележки превышает ± 2,4 (центр тележки достигает края дисплея). Более новые версии тренажерного зала также имеют ограничение по длине, которое завершает игру, когда длина эпизода превышает 200.

Реализация

Полный код находится здесь.

1. Создайте класс модели tf.keras

Давайте сначала реализуем модель нейронной сети глубокого обучения f (s, θ) в TensorFlow. В TF2 активное выполнение является режимом по умолчанию, поэтому нам больше не нужно сначала создавать операции, а потом запускать их в сеансах. Вдобавок TF2 дает автограф в tf.function(). Есть два способа создать экземпляр модели. Более простой способ - указать прямой проход модели путем объединения слоев Keras и создать модель из входных и выходных данных. Однако для обучения более сложной и настраиваемой модели нам необходимо создать класс модели, создав подклассы моделей Keras. Подробнее см. Здесь.

В классе MyModel мы определяем все слои в __init__ и реализуем прямой проход модели в call().. Обратите внимание, что форма ввода - [размер пакета, размер состояния (в данном случае 4)], а форма вывода - [размер пакета, количество действий (в данном случае 2)]. По сути, мы загружаем модель состояниями и выводим значения выполнения каждого действия в каждом состоянии. @tf.function аннотация для call() включает автограф и автоматическое управление зависимостями.

2. Создайте основной класс модели DQN.

В основном классе DQN создается, вызывается и обновляется модель Deep Q-net. Модель нейронной сети, которую мы только что построили, является частью модели Deep Q-net.

В __init__() мы определяем количество действий, размер пакета и оптимизатор для градиентного спуска. Гамма коэффициента дисконтирования - это значение от 0 до 1, которое умножается на значение Q на следующем этапе, потому что агенты меньше заботятся о вознаграждениях в отдаленном будущем, чем о вознаграждениях в ближайшем будущем. Мы также инициализируем MyModel как переменную экземпляра self.mode и создаем буфер воспроизведения опыта self.experience. Агент не начнет обучение, если размер буфера не превышает self.min_experience, и как только буфер достигнет максимального размера self.max_experience, он удалит самые старые значения для освободите место для новых ценностей.

Метод экземпляра predict() принимает в качестве входных данных либо одно состояние, либо группу состояний, выполняет прямой проход self.model и возвращает результаты модели (логиты для действий). Обратите внимание, что модель tf.keras по умолчанию распознает ввод как пакет, поэтому мы хотим убедиться, что ввод имеет как минимум 2 измерения, даже если это одно состояние.

Intrain() мы сначала случайным образом выбираем пакет значений (s, s ', a, r) ​​ с логическим значением done, указывающим, находится ли текущее состояние (s) - конечное состояние. Затем мы вызываем predict(), чтобы получить значения в следующем состоянии. Обратите внимание, что здесь мы используем скопированную целевую сеть для стабилизации значений. Затем мы получаем основные истинные значения из функции Беллмана. Как мы обсуждали ранее, если состояние (s) является конечным состоянием, цель Q (s, a) - это просто награда (r). Withintf.GradientTape(), мы вычисляем среднеквадратичные потери реальной цели и прогноза. Поскольку мы не используем встроенную функцию потерь, нам нужно вручную замаскировать логиты с помощью tf.one_hot(). Как только мы получим тензор потерь, мы можем использовать удобные встроенные операции TensorFlow для выполнения обратного распространения ошибки.

Еще одна важная концепция в RL - это эпсилон-жадность. Эпсилон - это значение от 0 до 1, которое со временем уменьшается. Идея состоит в том, чтобы сбалансировать исследование и эксплуатацию. Когда модель вначале менее точна, мы хотим исследовать больше, выбирая случайные действия, поэтому мы выбираем эпсилон большего размера. По мере того, как мы собираем больше данных, играя в игры, мы постепенно разрушаем эпсилон, чтобы лучше использовать модель. Реализация epsilon-greedy находится в get_action().

В add_experience() и copy_weights() мы реализуем методы буфера воспроизведения опыта и целевой сети, как упоминалось ранее. Каждый раз, когда мы собираем новые данные во время игры, мы добавляем данные в буфер, следя за тем, чтобы они не превышали лимит, определенный как self.max_experiences. Мы создадим два экземпляра класса DQN: обучающую сеть и целевую сеть. В то время как обучающая сеть используется для обновления весов, целевая сеть выполняет только две задачи: прогнозирование значения на следующем шаге Q (s ', a) для обучающей сети для обновления в train() и копирование гири из тренировочной сети.

3. Сыграйте в игру.

Наконец-то мы можем сыграть в игру!

Давайте начнем игру с передачи 5 параметров в play_game()function: предварительно заданную среду CartPole в тренажерном зале, тренировочную сетку, целевую сетку, эпсилон и интервальные шаги для копирования веса. Внутри функции мы сначала сбрасываем среду, чтобы получить начальное состояние. Затем мы создаем цикл, чтобы играть в игру, пока она не достигнет конечного состояния. Внутри цикла мы с эпсилон-жадностью выбираем действие, перемещаем шаг, добавляем в буфер пару (s, s ', a, r) ​​ и done, и обучить модель. По умолчанию среда всегда предоставляет награду +1 за каждый временной шаг, но, чтобы наказать модель, мы назначаем -200 награде, когда она достигает конечного состояния перед завершением всего эпизода. iter отслеживает количество пройденных нами шагов в одной игре, поэтому мы можем копировать веса в целевую сеть на каждыеcopy_step шага. По окончании игры мы возвращаем сумму наград.

4. Сделайте видео для тестирования

После обучения модели мы хотели бы увидеть, как она на самом деле работает в игре CartPole. Для этого мы просто оборачиваем среду CartPole в wrappers.Monitor и определяем путь для сохранения видео. Мы играем в игру, полностью используя модель, и видео сохраняется после завершения игры.

5. Определите гиперпараметры и журналы Tensorboard и соберите все вместе

Теперь модель DQN настроена, и все, что нам нужно сделать, это определить наши гиперпараметры, выходные журналы для Tensorboard и обучить модель. Давайте посмотрим, как это делается в функции main().

Сначала мы создаем среду Gym CartPole, тренировочную сетку и целевую сетку. Затем мы определяем гиперпараметры и составитель сводки Tensorflow. Текущие настройки гиперпараметров будут генерировать награду за эпизод в размере 200 после 15000 эпизодов, что является наивысшим вознаграждением при текущей продолжительности эпизода в 200. Однако наша модель довольно нестабильна, и требуется дальнейшая настройка гиперпараметров.

В цикле for мы играем 50000 игр и уменьшаем эпсилон по мере увеличения количества сыгранных игр. Для визуализации Tensorboard мы также отслеживаем награды от каждой игры, а также текущие средние награды с размером окна 100. Наконец, мы делаем видео, вызывая make_video() и закрывая среду.

После завершения тестирования вы сможете увидеть подобное видео в указанной вами папке.

Чтобы запустить Tensorboard, просто введите tensorboard --logdir log_dir(the path of your Tensorflow summary writer).

В вашем терминале (Mac) вы увидите IP-адрес localhost с портом для Tensorflow. Нажмите на нее, и вы сможете просмотреть свои награды на Tensorboard.

Заключение и предложения для будущей работы

В этой статье мы узнали:

  1. Обучение с подкреплением и алгоритм DQN;
  2. Создайте индивидуальную модель, создав подкласс tf.keras.Model в TF 2;
  3. Обучите tf.keras.Model с помощью tf.Gradient.Tape ();
  4. Создать видео в wrappers.Monitor для тестирования модели DQN;
  5. Отобразите награды на Tensorboard.

Однако стоит упомянуть, что DQN не гарантирует сходимость из-за нестабильности, вызванной загрузкой, выборкой и приближением функционального значения. CartPole также является относительно сложной средой для изучения DQN. На самом деле обучение часто терпит неудачу, и результат, показанный выше, был достигнут после многих попыток. Мне посчастливилось достичь конвергенции за 50 тысяч эпох, но я сомневаюсь, что это происходит каждый раз. Чтобы воспроизвести это, я настоятельно рекомендую вам пробежать хотя бы несколько миллионов эпох.

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

Другой важный факт заключается в том, что DQN является алгоритмом вне политики, и значения Q могут сходиться даже с случайными политиками, пока выполняется алгоритм Роббинса-Монро и все состояния и действия посещаются бесконечное количество раз. Следовательно, значения Q могли уже сойтись и быть готовыми к применению для прогнозирования после многих эпох, даже если вознаграждение не выглядит идеальным.

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

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

Надеюсь, вам понравилось читать эту статью. :)