Смещение отклонения для глубокого обучения: как построить бот для Atari с OpenAI Gym

_ Автор выбрал Girls Who Code, чтобы получить пожертвование в рамках программы Write for DOnations ._

Вступление

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

В машинном обучении бот формально известен как agent. В этом руководстве агент - это «игрок» в системе, который действует в соответствии с функцией принятия решений, называемой policy. Основная цель - создать сильных агентов, вооружив их сильной политикой. Другими словами, наша цель - развивать интеллектуальных ботов, вооружая их сильными возможностями для принятия решений.

Вы начнете этот урок с обучения основного обучающего агента по подкреплению, который выполняет случайные действия при игре в Space Invaders, классическую аркадную игру Atari, которая послужит вам основой для сравнения. После этого вы изучите несколько других методов - включая Q-learning, https://en.wikipedia.org/wiki/Q-learning#Deep_Q- learning [deep Q-learning] и least squares - при создании агентов, которые играют в Space Invaders и Frozen Lake - простую игровую среду, включенную в https: // gym. openai.com/[Gym], инструментарий для обучения подкреплению, выпущенный OpenAI. Следуя этому руководству, вы получите представление о фундаментальных концепциях, которые определяют выбор сложности модели в машинном обучении.

Предпосылки

Для завершения этого урока вам понадобится:

  • Сервер под управлением Ubuntu 18.04, с не менее 1 ГБ ОЗУ. На этом сервере должен быть настроен пользователь без полномочий root с привилегиями + sudo +, а также брандмауэр, настроенный с помощью UFW. Вы можете настроить это, следуя этому Initial Руководству по установке сервера для Ubuntu 18.04.

  • Виртуальная среда Python 3, которой вы можете достичь, прочитав наше руководство «https://www.digitalocean.com/community/tutorials/how-to-install-python-3-and-set-up-a-programming-environment- on-an-ubuntu-18-04-server [Как установить Python 3 и настроить среду программирования на сервере Ubuntu 18.04]. »

В качестве альтернативы, если вы используете локальный компьютер, вы можете установить Python 3 и настроить среду локального программирования, прочитав соответствующее руководство для вашей операционной системы через наш https://www.digitalocean.com/community/tutorial_series/how-to -install-and-set-up-a-local-program-environment-for-python-3 [Серия установки и настройки Python].

Шаг 1 - Создание проекта и установка зависимостей

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

Начните с создания рабочей области для этого проекта с именем + AtariBot +:

mkdir ~/AtariBot

Перейдите в новый каталог + AtariBot +:

cd ~/AtariBot

Затем создайте новую виртуальную среду для проекта. Вы можете назвать это виртуальное окружение как угодно; здесь мы назовем это + ataribot +:

python3 -m venv

Активируйте свою среду:

source /bin/activate

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

sudo apt-get install -y cmake libsm6 libxext6 libxrender-dev libz-dev

Затем используйте + pip + для установки пакета + wheel +, эталонной реализации стандарта упаковки wheel. Этот пакет, являющийся библиотекой Python, служит расширением для сборки колес и включает в себя инструмент командной строки для работы с файлами + .whl +:

python -m pip install wheel

В дополнение к + wheel + вам необходимо установить следующие пакеты:

  • Gym, библиотека Python, которая делает доступными для исследования различные игры, а также все зависимости для игр Atari. Разработанный OpenAI, Gym предлагает общедоступные тесты для каждой из игр, чтобы можно было единообразно / оценивать производительность для различных агентов и алгоритмов.

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

  • OpenCV, библиотека компьютерного зрения, упомянутая ранее.

  • SciPy, научная компьютерная библиотека, предлагающая эффективные алгоритмы оптимизации.

  • NumPy, библиотека линейной алгебры.

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

python -m pip install gym==0.9.5 tensorflow==1.5.0 tensorpack==0.8.0 numpy==1.14.0 scipy==1.1.0 opencv-python==3.4.1.15

После этого используйте + pip + еще раз для установки сред Atari в Gym, которые включают в себя различные видеоигры Atari, включая Space Invaders:

python -m pip install gym[atari]

Если ваша установка пакета + gym [atari] + прошла успешно, ваш вывод закончится следующим:

OutputInstalling collected packages: atari-py, Pillow, PyOpenGL
Successfully installed Pillow-5.4.1 PyOpenGL-3.1.0 atari-py-0.1.7

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

Шаг 2 - Создание базового случайного агента с Gym

Теперь, когда необходимое программное обеспечение находится на вашем сервере, вы настроите агента, который будет играть в упрощенную версию классической игры Atari, Space Invaders. Для любого эксперимента необходимо получить базовый уровень, который поможет вам понять, насколько хорошо работает ваша модель. Поскольку этот агент выполняет случайные действия в каждом кадре, мы будем называть его нашим случайным базовым агентом. В этом случае вы будете сравнивать с этим базовым агентом, чтобы понять, насколько хорошо ваши агенты работают на последующих этапах.

С Gym вы поддерживаете свой игровой цикл. Это означает, что вы обрабатываете каждый шаг выполнения игры: на каждом временном шаге вы даете + gym + новое действие и запрашиваете у + gym + состояние game. В этом уроке состояние игры - это внешний вид игры на заданном временном шаге, и это именно то, что вы бы увидели, если бы играли в игру.

Используя предпочитаемый вами текстовый редактор, создайте файл Python с именем + bot_2_random.py +. Здесь мы будем использовать + nano +:

nano bot_2_random.py

Запустите этот скрипт, добавив следующие выделенные строки. Эти строки включают блок комментария, который объясняет, что будет делать этот скрипт, и два оператора + import +, которые будут импортировать пакеты, которые в конечном итоге понадобятся этому скрипту для функционирования:

/AtariBot/bot_2_random.py

Добавьте функцию + main +. В этой функции создайте игровое окружение + SpaceInvaders-v0 + - и затем инициализируйте игру, используя + env.reset +:

/AtariBot/bot_2_random.py

. . .
import gym
import random

Затем добавьте функцию + env.step +. Эта функция может возвращать следующие виды значений:

  • + state +: новое состояние игры после применения предоставленного действия.

  • + награда +: увеличение количества очков, получаемых государством. Например, это может быть, когда пуля уничтожила инопланетянина, и счет увеличивается на 50 очков. Тогда + вознаграждение = 50 +. В любой игре, основанной на счете, цель игрока - максимизировать счет. Это синоним максимизации общего вознаграждения.

  • + done +: закончился ли эпизод или нет, что обычно происходит, когда игрок потерял все жизни.

  • + info +: посторонняя информация, которую вы пока отложите.

Вы будете использовать + reward + для подсчета общей суммы вознаграждения. Вы также будете использовать + done +, чтобы определить, когда игрок умрет, что произойдет, когда + done + вернет + True +.

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

/AtariBot/bot_2_random.py

. . .
def main():
   env = gym.make('SpaceInvaders-v0')
   env.reset()

Наконец, запустите функцию + main +. Включите проверку + name +, чтобы гарантировать, что + main + запускается только тогда, когда вы вызываете его напрямую с помощью + python bot_2_random.py +. Если вы не добавите проверку + if +, + main + всегда будет срабатывать при запуске файла Python *, даже когда вы импортируете файл *. Следовательно, рекомендуется помещать код в функцию + main +, которая выполняется только тогда, когда + name == 'main ' +.

/AtariBot/bot_2_random.py

. . .
def main():
   . . .
   if done:
       print('Reward %s' % episode_reward)
       break

Сохраните файл и выйдите из редактора. Если вы используете + nano +, сделайте это, нажав + CTRL + X +, + Y +, затем + ENTER +. Затем запустите ваш скрипт, набрав:

python bot_2_random.py

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

OutputMaking new env: SpaceInvaders-v0
Reward:

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

nano bot_2_random.py

После + import random + добавьте + random.seed (0) +. После + env = gym.make ('SpaceInvaders-v0') +, добавьте + env.seed (0) +. Вместе эти линии «затравляют» среду последовательной отправной точкой, гарантируя, что результаты всегда будут воспроизводимыми. Ваш окончательный файл будет точно соответствовать следующему:

/AtariBot/bot_2_random.py

"""
Bot 2 -- Make a random, baseline agent for the SpaceInvaders game.
"""

import gym
import random



def main():
   env = gym.make('SpaceInvaders-v0')


   env.reset()
   episode_reward = 0
   while True:
       action = env.action_space.sample()
       _, reward, done, _ = env.step(action)
       episode_reward += reward
       if done:
           print('Reward: %s' % episode_reward)
           break


if __name__ == '__main__':
   main()

Сохраните файл и закройте редактор, затем запустите скрипт, набрав в своем терминале следующее:

python bot_2_random.py

Это даст следующее вознаграждение, а именно:

OutputMaking new env: SpaceInvaders-v0
Reward: 555.0

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

nano bot_2_random.py

После + random.seed (0) + добавьте следующую выделенную строку, в которой агент должен играть в игру для 10 эпизодов:

/AtariBot/bot_2_random.py

. . .
random.seed(0)


. . .

Сразу после + env.seed (0) +, начните новый список наград:

/AtariBot/bot_2_random.py

. . .
   env.seed(0)

. . .

Вложите весь код от + env.reset () + до конца + main () + в цикле + for, повторяя` + num_episodes + раз. Удостоверьтесь, что отступ каждой строки от `+ env.reset () + до + break + на четыре пробела:

/AtariBot/bot_2_random.py

. . .
def main():
   env = gym.make('SpaceInvaders-v0')
   env.seed(0)
   rewards = []


       env.reset()
       episode_reward = 0

       while True:
           ...

Прямо перед + break +, в настоящее время последней строкой основного игрового цикла, добавьте награду текущего эпизода в список всех наград:

/AtariBot/bot_2_random.py

. . .
       if done:
           print('Reward: %s' % episode_reward)

           break
. . .

В конце функции + main + сообщите среднее вознаграждение:

/AtariBot/bot_2_random.py

. . .
def main():
   ...
           print('Reward: %s' % episode_reward)
           break

   . . .

Ваш файл теперь будет соответствовать следующему. Обратите внимание, что следующий блок кода содержит несколько комментариев для пояснения ключевых частей скрипта:

/AtariBot/bot_2_random.py

"""
Bot 2 -- Make a random, baseline agent for the SpaceInvaders game.
"""

import gym
import random
random.seed(0)  # make results reproducible

num_episodes = 10


def main():
   env = gym.make('SpaceInvaders-v0')  # create the game
   env.seed(0)  # make results reproducible
   rewards = []

   for _ in range(num_episodes):
       env.reset()
       episode_reward = 0
       while True:
           action = env.action_space.sample()
           _, reward, done, _ = env.step(action)  # random action
           episode_reward += reward
           if done:
               print('Reward: %d' % episode_reward)
               rewards.append(episode_reward)
               break
   print('Average reward: %.2f' % (sum(rewards) / len(rewards)))


if __name__ == '__main__':
   main()

Сохраните файл, выйдите из редактора и запустите скрипт:

python bot_2_random.py

Это напечатает следующее среднее вознаграждение, точно:

OutputMaking new env: SpaceInvaders-v0
. . .
Average reward: 163.50

Теперь у нас есть более надежная оценка базовой оценки, чтобы побить. Однако, чтобы создать превосходного агента, вам нужно понять основы для обучения с подкреплением. Как можно сделать абстрактное понятие «принятия решений» более конкретным?

Понимание Усиления обучения

В любой игре цель игрока - максимизировать свой счет. В этом руководстве оценка игрока называется reward. Чтобы максимизировать свою награду, игрок должен уметь совершенствовать свои способности принимать решения. Формально решение - это процесс просмотра игры или наблюдения за состоянием игры и выбора действия. Наша функция принятия решений называется policy; политика принимает состояние в качестве входных данных и «определяет» действие:

policy: state -> action

Чтобы построить такую ​​функцию, мы начнем с определенного набора алгоритмов в обучении подкрепления, называемого Q-learning алгоритмы. Чтобы проиллюстрировать это, рассмотрим начальное состояние игры, которое мы назовем + state0 +: ваш космический корабль и инопланетяне находятся на своих исходных позициях. Затем предположим, что у нас есть доступ к волшебной «Q-таблице», которая говорит нам, сколько вознаграждения принесет каждое действие:

state action reward

state0

shoot

10

state0

right

3

state0

left

3

Действие + Shoot + максимизирует вашу награду, так как это приводит к награде с наибольшим значением: 10. Как вы можете видеть, Q-таблица обеспечивает простой способ принимать решения, основываясь на наблюдаемом состоянии:

policy: state -> look at Q-table, pick action with greatest reward

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

Q(state0, shoot) = 10
Q(state0, right) = 3
Q(state0, left) = 3

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

policy: state -> argmax_{action} Q(state, action)

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

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

  2. Space Invaders - игра с отложенными наградами: игрок получает награду, когда инопланетянин взорван, а не когда он стреляет. Тем не менее, игрок, совершающий действие стрельбой, является истинным стимулом для награды. Каким-то образом Q-функция должна назначить + (state0, shot) + положительную награду.

Эти две идеи кодифицированы в следующих уравнениях:

Q(state, action) = (1 - learning_rate) * Q(state, action) + learning_rate * Q_target
Q_target = reward + discount_factor * max_{action'} Q(state', action')

Эти уравнения используют следующие определения:

  • + state +: состояние на текущем временном шаге

  • + действие +: действие, предпринятое на текущем временном шаге

  • + награда +: награда за текущий шаг по времени

  • + state '+: новое состояние для следующего шага времени, учитывая, что мы предприняли действие + a +

  • + действие '+: все возможные действия

  • + learning_rate +: скорость обучения

  • + discount_factor +: коэффициент дисконтирования, насколько награда «ухудшается» по мере ее распространения

Для полного объяснения этих двух уравнений см. Эту статью на Understanding Q-Learning.

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

Шаг 3 - Создание простого агента Q-обучения для Frozen Lake

Теперь, когда у вас есть базовый агент, вы можете начать создавать новых агентов и сравнивать их с оригиналом. На этом шаге вы создадите агента, который использует Q-learning, метод обучения с подкреплением, используемый для обучения агента, какое действие предпринять в определенном состоянии. Этот агент будет играть в новую игру, FrozenLake. Настройка для этой игры описана на сайте Gym следующим образом:

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

Поверхность описывается с использованием сетки, как показано ниже: _ _

SFFF       (S: starting point, safe)
FHFH       (F: frozen surface, safe)
FFFH       (H: hole, fall to your doom)
HFFG       (G: goal, where the frisbee is located)

Игрок начинает слева вверху, обозначая + S +, и направляется к цели внизу справа, обозначаемой + G +. Доступны следующие действия: * вправо *, * влево *, * вверх * и * вниз *, и достижение цели дает 1 балл. Есть несколько отверстий, обозначенных как «+ H +», и попадание в одно из них немедленно приводит к 0 баллам.

В этом разделе вы реализуете простой Q-Learning Agent. Используя то, что вы узнали ранее, вы создадите агента, который будет торговаться между exploration и exploitation. В этом контексте исследование означает, что агент действует случайным образом, а эксплуатация означает, что он использует свои Q-значения для выбора того, что он считает оптимальным действием. Вы также создадите таблицу для хранения Q-значений, постепенно обновляя ее по мере того, как агент действует и обучается.

Сделайте копию вашего сценария из шага 2:

cp bot_2_random.py bot_3_q_table.py

Затем откройте этот новый файл для редактирования:

nano bot_3_q_table.py

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

/AtariBot/bot_3_q_table.py

"""

"""

. . .

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

/AtariBot/bot_3_q_table.py

"""
Bot 3 -- Build simple q-learning agent for FrozenLake
"""

import gym

import random
random.seed(0)  # make results reproducible
. . .

Под + random.seed (0) + добавьте начальное число для + numpy +:

/AtariBot/bot_3_q_table.py

. . .
import random
random.seed(0)  # make results reproducible

. . .

Затем сделайте игровые состояния доступными. Обновите строку + env.reset () +, чтобы сказать следующее, в котором начальное состояние игры хранится в переменной + state +:

/AtariBot/bot_3_q_table.py

. . .
   for _ in range(num_episodes):
       env.reset()
       . . .

Обновите строку + env.step (…​) +, чтобы сказать следующее, в котором хранится следующее состояние, + state2 +. Вам понадобится как текущее + состояние, так и следующее -` + state2 + `- для обновления Q-функции.

/AtariBot/bot_3_q_table.py

       . . .
       while True:
           action = env.action_space.sample()
           , reward, done, _ = env.step(action)
           . . .

После + episode_reward + = reward + добавьте строку, обновляющую переменную + state +. Это сохраняет переменную + state + обновленной для следующей итерации, так как вы ожидаете, что + state + будет отражать текущее состояние:

/AtariBot/bot_3_q_table.py

. . .
       while True:
           . . .
           episode_reward += reward

           if done:
               . . .

В блоке + if done on удалите оператор` + print`, который печатает награду за каждый эпизод. Вместо этого вы получите среднее вознаграждение за многие эпизоды. Блок + if done + будет выглядеть так:

/AtariBot/bot_3_q_table.py

           . . .
           if done:
               rewards.append(episode_reward)
               break
               . . .

После этих модификаций ваш игровой цикл будет соответствовать следующему:

/AtariBot/bot_3_q_table.py

. . .
   for _ in range(num_episodes):
       state = env.reset()
       episode_reward = 0
       while True:
           action = env.action_space.sample()
           state2, reward, done, _ = env.step(action)
           episode_reward += reward
           state = state2
           if done:
               rewards.append(episode_reward))
               break
               . . .

Затем добавьте возможность для агента обменять между разведкой и эксплуатацией. Прямо перед основным игровым циклом (который начинается с + for …​ +) создайте таблицу Q-значений:

/AtariBot/bot_3_q_table.py

. . .

   for _ in range(num_episodes):
     . . .

Затем перепишите цикл + for, чтобы раскрыть номер эпизода:

/AtariBot/bot_3_q_table.py

. . .
   Q = np.zeros((env.observation_space.n, env.action_space.n))
   for :
     . . .

Внутри внутреннего цикла + while True: + создайте + noise +. Noise, или бессмысленные, случайные данные, иногда вводятся при обучении глубоких нейронных сетей, потому что это может улучшить как производительность, так и точность модели. Обратите внимание, что чем выше шум, тем меньше значения в + Q [state,:] +. В результате, чем выше уровень шума, тем больше вероятность того, что агент действует независимо от своих знаний об игре. Другими словами, более высокий уровень шума побуждает агента к explore случайным действиям:

/AtariBot/bot_3_q_table.py

       . . .
       while True:

           action = env.action_space.sample()
           . . .

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

Обновите строку + action +, чтобы ваш агент выбирал действия в соответствии с таблицей Q-value, с некоторыми встроенными исследованиями:

/AtariBot/bot_3_q_table.py

           . . .
           noise = np.random.random((1, env.action_space.n)) / (episode**2.)
           action =
           state2, reward, done, _ = env.step(action)
           . . .

Ваш основной игровой цикл будет соответствовать следующему:

/AtariBot/bot_3_q_table.py

. . .
   Q = np.zeros((env.observation_space.n, env.action_space.n))
   for episode in range(1, num_episodes + 1):
       state = env.reset()
       episode_reward = 0
       while True:
           noise = np.random.random((1, env.action_space.n)) / (episode**2.)
           action = np.argmax(Q[state, :] + noise)
           state2, reward, done, _ = env.step(action)
           episode_reward += reward
           state = state2
           if done:
               rewards.append(episode_reward)
               break
               . . .

Затем вы обновите свою таблицу Q-значений, используя уравнение обновления Bellman - уравнение, широко используемое в машинном обучении для поиска оптимальной политики в данной среде.

Уравнение Беллмана включает в себя две идеи, которые имеют большое значение для этого проекта. Во-первых, многократное выполнение определенного действия из определенного состояния приведет к хорошей оценке Q-значения, связанного с этим состоянием и действием. С этой целью вы увеличите количество эпизодов, которые этот бот должен пройти, чтобы получить более сильную оценку Q-значения. Во-вторых, награды должны распространяться во времени, поэтому исходному действию присваивается ненулевое вознаграждение. Эта идея наиболее ясна в играх с отложенным вознаграждением; например, в Space Invaders игрок получает вознаграждение, когда инопланетянин взорван, а не когда игрок стреляет. Тем не менее, стрельба игрока является истинным стимулом для награды. Аналогично, Q-функция должна назначить (+ state0 +, + shoot +) положительное вознаграждение.

Во-первых, обновите + num_episodes + до 4000:

/AtariBot/bot_3_q_table.py

. . .
np.random.seed(0)

num_episodes =
. . .

Затем добавьте необходимые гиперпараметры в начало файла в виде еще двух переменных:

/AtariBot/bot_3_q_table.py

. . .
num_episodes = 4000


. . .

Вычислить новое целевое значение Q сразу после строки, содержащей + env.step (…​) +:

/AtariBot/bot_3_q_table.py

           . . .
           state2, reward, done, _ = env.step(action)

           episode_reward += reward
           . . .

В строке сразу после + Qtarget + обновите таблицу Q-значений, используя средневзвешенное значение для старых и новых Q-значений:

/AtariBot/bot_3_q_table.py

           . . .
           Qtarget = reward + discount_factor * np.max(Q[state2, :])

           episode_reward += reward
           . . .

Убедитесь, что ваш основной игровой цикл теперь соответствует следующему:

/AtariBot/bot_3_q_table.py

. . .
   Q = np.zeros((env.observation_space.n, env.action_space.n))
   for episode in range(1, num_episodes + 1):
       state = env.reset()
       episode_reward = 0
       while True:
           noise = np.random.random((1, env.action_space.n)) / (episode**2.)
           action = np.argmax(Q[state, :] + noise)
           state2, reward, done, _ = env.step(action)
           Qtarget = reward + discount_factor * np.max(Q[state2, :])
           Q[state, action] = (1-learning_rate) * Q[state, action] + learning_rate * Qtarget
           episode_reward += reward
           state = state2
           if done:
               rewards.append(episode_reward)
               break
               . . .

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

Несмотря на то, что Python не требует строгой проверки типов, добавьте типы в объявления функций для чистоты. В верхней части файла, перед первой строкой, читающей + import gym +, импортируйте тип + List +:

/AtariBot/bot_3_q_table.py

. . .

import gym
. . .

Сразу после + learning_rate = 0.9 + вне функции + main + объявите интервал и формат для отчетов:

/AtariBot/bot_3_q_table.py

. . .
learning_rate = 0.9




def main():
 . . .

Перед функцией + main + добавьте новую функцию, которая заполнит строку + report +, используя список всех наград:

/AtariBot/bot_3_q_table.py

. . .
report = '100-ep Average: %.2f . Best 100-ep Average: %.2f . Average: %.2f ' \
        '(Episode %d)'














def main():
 . . .

Измените игру на + FrozenLake + вместо + SpaceInvaders +:

/AtariBot/bot_3_q_table.py

. . .
def main():
   env = gym.make('')  # create the game
   . . .

После + rewards.append (…​) + выведите среднее вознаграждение за последние 100 эпизодов и напечатайте среднее вознаграждение по всем эпизодам:

/AtariBot/bot_3_q_table.py

           . . .
           if done:
               rewards.append(episode_reward)


               . . .

В конце функции + main () + еще раз выведите средние значения. Сделайте это, заменив строку с надписью + print ('Среднее вознаграждение:% .2f'% (сумма (вознаграждения) / len (вознаграждения))) + следующей выделенной строкой:

/AtariBot/bot_3_q_table.py

. . .
def main():
   ...
               break

. . .

Наконец, вы завершили своего Q-обучения агента. Убедитесь, что ваш скрипт соответствует следующему:

/AtariBot/bot_3_q_table.py

"""
Bot 3 -- Build simple q-learning agent for FrozenLake
"""

from typing import List
import gym
import numpy as np
import random
random.seed(0)  # make results reproducible
np.random.seed(0)  # make results reproducible

num_episodes = 4000
discount_factor = 0.8
learning_rate = 0.9
report_interval = 500
report = '100-ep Average: %.2f . Best 100-ep Average: %.2f . Average: %.2f ' \
        '(Episode %d)'


def print_report(rewards: List, episode: int):
   """Print rewards report for current episode
   - Average for last 100 episodes
   - Best 100-episode average across all time
   - Average for all episodes across time
   """
   print(report % (
       np.mean(rewards[-100:]),
       max([np.mean(rewards[i:i+100]) for i in range(len(rewards) - 100)]),
       np.mean(rewards),
       episode))


def main():
   env = gym.make('FrozenLake-v0')  # create the game
   env.seed(0)  # make results reproducible
   rewards = []

   Q = np.zeros((env.observation_space.n, env.action_space.n))
   for episode in range(1, num_episodes + 1):
       state = env.reset()
       episode_reward = 0
       while True:
           noise = np.random.random((1, env.action_space.n)) / (episode**2.)
           action = np.argmax(Q[state, :] + noise)
           state2, reward, done, _ = env.step(action)
           Qtarget = reward + discount_factor * np.max(Q[state2, :])
           Q[state, action] = (1-learning_rate) * Q[state, action] + learning_rate * Qtarget
           episode_reward += reward
           state = state2
           if done:
               rewards.append(episode_reward)
               if episode % report_interval == 0:
                   print_report(rewards, episode)
               break
   print_report(rewards, -1)

if __name__ == '__main__':
   main()

Сохраните файл, выйдите из редактора и запустите скрипт:

python bot_3_q_table.py

Ваш вывод будет соответствовать следующему:

Output100-ep Average: 0.11 . Best 100-ep Average: 0.12 . Average: 0.03 (Episode 500)
100-ep Average: 0.25 . Best 100-ep Average: 0.24 . Average: 0.09 (Episode 1000)
100-ep Average: 0.39 . Best 100-ep Average: 0.48 . Average: 0.19 (Episode 1500)
100-ep Average: 0.43 . Best 100-ep Average: 0.55 . Average: 0.25 (Episode 2000)
100-ep Average: 0.44 . Best 100-ep Average: 0.55 . Average: 0.29 (Episode 2500)
100-ep Average: 0.64 . Best 100-ep Average: 0.68 . Average: 0.32 (Episode 3000)
100-ep Average: 0.63 . Best 100-ep Average: 0.71 . Average: 0.36 (Episode 3500)
100-ep Average: 0.56 . Best 100-ep Average: 0.78 . Average: 0.40 (Episode 4000)
100-ep Average: 0.56 . Best 100-ep Average: 0.78 . Average: 0.40 (Episode -1)

Теперь у вас есть первый нетривиальный бот для игр, но давайте посмотрим на это среднее вознаграждение в размере + 0,78 . Согласно странице https://gym.openai.com/envs/FrozenLake-v0/[Gym FrozenLake], «решение» игры означает достижение среднего значения по 100 эпизодам « 0,78 +». Неформально «решение» означает «очень хорошо играет в игру». В то время как не в рекордное время, агент Q-таблицы может решить FrozenLake в 4000 эпизодов.

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

Шаг 4 - Создание агента глубокого обучения для Frozen Lake

В обучении с подкреплением нейронная сеть эффективно предсказывает значение Q на основе входных данных + state + и + action +, используя таблицу для хранения всех возможных значений, но это становится нестабильным в сложных играх. Вместо этого для глубокого обучения используется нейронная сеть для аппроксимации Q-функции. Для получения дополнительной информации см. Http://alvinwan.com/understanding-deep-q-learning#deep-q-learning[Understanding Deep Q-Learning].

Чтобы привыкнуть к Tensorflow, библиотеке глубокого обучения, которую вы установили на шаге 1, вы переопределите всю логику, использованную до сих пор, с абстракциями Tensorflow, и вы будете использовать нейронную сеть для приблизить вашу Q-функцию. Однако ваша нейронная сеть будет чрезвычайно простой: ваш вывод + Q (s) + - это матрица + W +, умноженная на ваш ввод + s +. Это известно как нейронная сеть с одним ly-подключенным слоем:

Q(s) = Ws

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

Начните с дублирования скрипта Q-таблицы из шага 3:

cp bot_3_q_table.py bot_4_q_network.py

Затем откройте новый файл с помощью + nano + или предпочитаемого вами текстового редактора:

nano bot_4_q_network.py

Сначала обновите комментарий вверху файла:

/AtariBot/bot_4_q_network.py

"""

"""

. . .

Затем импортируйте пакет Tensorflow, добавив директиву + import прямо под` + import random`. Кроме того, добавьте + tf.set_radon_seed (0) + прямо под + np.random.seed (0) +. Это гарантирует, что результаты этого сценария будут повторяться во всех сеансах:

/AtariBot/bot_4_q_network.py

. . .
import random

random.seed(0)
np.random.seed(0)

. . .

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

/AtariBot/bot_4_q_network.py

. . .
num_episodes = 4000
discount_factor =
learning_rate =
report_interval = 500

report = '100-ep Average: %.2f . Best 100-ep Average: %.2f . Average: %.2f ' \
        '(Episode %d)'
. . .

Далее вы добавите функцию горячего кодирования. Короче говоря, горячее кодирование - это процесс, посредством которого переменные преобразуются в форму, которая помогает алгоритмам машинного обучения делать более точные прогнозы. Если вы хотите узнать больше о кодировке «горячей» подсказки, вы можете проверить Adversarial Примеры в компьютерном зрении: как построить, а затем обмануть собачий фильтр на основе эмоций ,

Непосредственно под + report = …​ + добавьте функцию + one_hot +:

/AtariBot/bot_4_q_network.py

. . .
report = '100-ep Average: %.2f . Best 100-ep Average: %.2f . Average: %.2f ' \
        '(Episode %d)'





def print_report(rewards: List, episode: int):
. . .

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

В вашей функции + main + непосредственно под + rewards = [] + вставьте следующее выделенное содержимое. Здесь вы определяете заполнители для своего наблюдения в момент времени * t * (как + obs_t_ph +) и времени * t + 1 * (как + obs_tp1_ph +), а также заполнители для ваших действий, наград и цели Q:

/AtariBot/bot_4_q_network.py

. . .
def main():
   env = gym.make('FrozenLake-v0')  # create the game
   env.seed(0)  # make results reproducible
   rewards = []









   Q = np.zeros((env.observation_space.n, env.action_space.n))
   for episode in range(1, num_episodes + 1):
       . . .

Непосредственно под строкой, начинающейся с + q_target_ph = +, вставьте следующие выделенные строки. Этот код запускает ваши вычисления, вычисляя * Q (s, a) * для всех * a , чтобы сделать + q_current + и * Q (s ’, a’) * для всех * a ', чтобы сделать + q_target +:

/AtariBot/bot_4_q_network.py

   . . .
   rew_ph = tf.placeholder(shape=(), dtype=tf.float32)
   q_target_ph = tf.placeholder(shape=[1, n_actions], dtype=tf.float32)






   Q = np.zeros((env.observation_space.n, env.action_space.n))
   for episode in range(1, num_episodes + 1):
       . . .

Снова непосредственно под последней добавленной строкой вставьте следующий выделенный код. Первые две строки эквивалентны строке, добавленной на шаге 3, которая вычисляет + Qtarget +, где + Qtarget = вознаграждение + discount_factor * np.max (Q [state2,:]) +. Следующие две строки определяют вашу потерю, а последняя строка вычисляет действие, которое максимизирует ваше значение Q:

/AtariBot/bot_4_q_network.py

   . . .
   q_current = tf.matmul(obs_t_ph, W)
   q_target = tf.matmul(obs_tp1_ph, W)







   Q = np.zeros((env.observation_space.n, env.action_space.n))
   for episode in range(1, num_episodes + 1):
       . . .

После настройки вашего алгоритма и функции потерь определите ваш оптимизатор:

/AtariBot/bot_4_q_network.py

   . . .
   error = tf.reduce_sum(tf.square(q_target_sa - q_current_sa))
   pred_act_ph = tf.argmax(q_current, 1)





   Q = np.zeros((env.observation_space.n, env.action_space.n))
   for episode in range(1, num_episodes + 1):
       . . .

Далее настройте тело игрового цикла. Для этого передайте данные в заполнители Tensorflow, и абстракции Tensorflow будут обрабатывать вычисления на GPU, возвращая результат алгоритма.

Начните с удаления старой Q-таблицы и логики. В частности, удалите строки, которые определяют + Q + (непосредственно перед циклом + for +), + noise + (в цикле + while +), + action +, + Qtarget + и + Q [состояние, действие] + `. Переименуйте `+ state + в + obs_t + и + state2 + в + obs_tp1 +, чтобы выровнять заполнители Tensorflow, которые вы установили ранее. Когда закончите, ваш цикл + for + будет соответствовать следующему:

/AtariBot/bot_4_q_network.py

   . . .
   # 3. Setup optimization
   trainer = tf.train.GradientDescentOptimizer(learning_rate=learning_rate)
   update_model = trainer.minimize(error)

   for episode in range(1, num_episodes + 1):
        = env.reset()
       episode_reward = 0
       while True:

           , reward, done, _ = env.step(action)

           episode_reward += reward

           if done:
               ...

Прямо над циклом + for + добавьте следующие две выделенные строки. Эти строки инициализируют сеанс Tensorflow, который, в свою очередь, управляет ресурсами, необходимыми для выполнения операций на графическом процессоре. Вторая строка инициализирует все переменные в вашем графе вычислений; например, инициализация весов до 0 перед их обновлением. Кроме того, вы вложите цикл + for + в оператор + with +, поэтому сделайте отступ для всего цикла + for + на четыре пробела:

/AtariBot/bot_4_q_network.py

   . . .
   trainer = tf.train.GradientDescentOptimizer(learning_rate=learning_rate)
       update_model = trainer.minimize(error)




       for episode in range(1, num_episodes + 1):
           obs_t = env.reset()
           ...

Перед строкой, читающей + obs_tp1, reward, done, _ = env.step (action) +, вставьте следующие строки, чтобы вычислить + action +. Этот код оценивает соответствующий заполнитель и заменяет действие случайным действием с некоторой вероятностью:

/AtariBot/bot_4_q_network.py

           . . .
           while True:





               . . .

После строки, содержащей + env.step (action) +, вставьте следующее, чтобы обучить нейронную сеть при оценке вашей функции Q-значения:

/AtariBot/bot_4_q_network.py

               . . .
               obs_tp1, reward, done, _ = env.step(action)










               episode_reward += reward
               . . .

Ваш окончательный файл будет соответствовать this файл, размещенный на GitHub. Сохраните файл, выйдите из редактора и запустите скрипт:

python bot_4_q_network.py

Ваш вывод закончится следующим:

Output100-ep Average: 0.11 . Best 100-ep Average: 0.11 . Average: 0.05 (Episode 500)
100-ep Average: 0.41 . Best 100-ep Average: 0.54 . Average: 0.19 (Episode 1000)
100-ep Average: 0.56 . Best 100-ep Average: 0.73 . Average: 0.31 (Episode 1500)
100-ep Average: 0.57 . Best 100-ep Average: 0.73 . Average: 0.36 (Episode 2000)
100-ep Average: 0.65 . Best 100-ep Average: 0.73 . Average: 0.41 (Episode 2500)
100-ep Average: 0.65 . Best 100-ep Average: 0.73 . Average: 0.43 (Episode 3000)
100-ep Average: 0.69 . Best 100-ep Average: 0.73 . Average: 0.46 (Episode 3500)
100-ep Average: 0.77 . Best 100-ep Average: 0.79 . Average: 0.48 (Episode 4000)
100-ep Average: 0.77 . Best 100-ep Average: 0.79 . Average: 0.48 (Episode -1)

Теперь вы обучили своего самого первого агента глубокого обучения. Для такой простой игры, как FrozenLake, вашему агенту глубокого обучения требовалось 4000 эпизодов. Представьте, если бы игра была намного сложнее. Сколько тренировочных образцов потребуется для обучения? Как выясняется, агент может потребовать millions образцов. Количество требуемых выборок упоминается как «сложность образца», концепция которой будет рассмотрена в следующем разделе.

Понимание компромиссов смещения

Вообще говоря, сложность выборки противоречит сложности модели в машинном обучении:

  1. * Сложность модели *: кто-то хочет достаточно сложную модель для решения своей проблемы. Например, такая простая модель, как линия, недостаточно сложна, чтобы предсказать траекторию движения автомобиля.

  2. * Сложность сэмплов *: Хотелось бы модель, которая не требует большого количества сэмплов. Это может быть связано с тем, что они имеют ограниченный доступ к помеченным данным, недостаточный объем вычислительной мощности, ограниченную память и т. Д.

Скажем, у нас есть две модели, одна простая и одна чрезвычайно сложная. Чтобы обе модели достигли одинаковой производительности, отклонение смещения говорит нам, что чрезвычайно сложной модели потребуется экспоненциально больше образцов для обучения. Пример: вашему агенту Q-Learning на базе нейронной сети потребовалось 4000 эпизодов для решения FrozenLake. Добавление второго слоя к агенту нейронной сети увеличивает количество необходимых обучающих эпизодов в четыре раза. Со все более сложными нейронными сетями этот разрыв только увеличивается. Чтобы поддерживать тот же уровень ошибок, увеличение сложности модели увеличивает сложность выборки в геометрической прогрессии. Аналогично, уменьшение сложности образца уменьшает сложность модели. Таким образом, мы не можем максимизировать сложность модели и минимизировать сложность образца в соответствии с желанием нашего сердца.

Однако мы можем использовать наши знания об этом компромиссе. Для визуальной интерпретации математики, лежащей в основе разложения _bias-дисперсии, см. Http://alvinwan.com/understanding-the-bias-variance-tradeoff[Understanding компромисса смещения дисперсии]. На высоком уровне разложение дисперсии смещения представляет собой разбивку «истинной ошибки» на две составляющие: смещение и дисперсия. Мы называем «истинная ошибка» как «среднеквадратичная ошибка» (MSE), которая является ожидаемой разницей между нашими предсказанными метками и истинными метками. Ниже приведен график, показывающий изменение «истинной ошибки» по мере увеличения сложности модели:

изображение: https: //assets.digitalocean.com/articles/atari_bias_variance/Graph01.png [Кривая среднего квадрата ошибок]

Шаг 5 - Создание агента наименьших квадратов для Ледяного озера

Метод least squares, также известный как linear regression, является средством регрессионного анализа, широко используемым в областях математики и наук о данных. В машинном обучении он часто используется для нахождения оптимальной линейной модели двух параметров или наборов данных.

На шаге 4 вы создали нейронную сеть для вычисления Q-значений. Вместо нейронной сети на этом шаге вы будете использовать ridge regression, вариант наименьших квадратов, для вычисления этого вектора Q-значений. Надежда состоит в том, что с такой простой моделью, как наименьшие квадраты, решение игры потребует меньше тренировочных эпизодов.

Начните с дублирования сценария из шага 3:

cp bot_3_q_table.py bot_5_ls.py

Откройте новый файл:

nano bot_5_ls.py

Снова обновите комментарий в верхней части файла, описывающий, что будет делать этот скрипт:

/AtariBot/bot_4_q_network.py

"""

"""

. . .

Перед блоком импорта в верхней части файла добавьте еще два импорта для проверки типов:

/AtariBot/bot_5_ls.py

. . .


from typing import List
import gym
. . .

В свой список гиперпараметров добавьте еще один гиперпараметр, + w_lr +, чтобы контролировать скорость обучения второй Q-функции. Кроме того, обновите количество эпизодов до 5000, а коэффициент дисконтирования - до «+ 0,85 ». Изменяя гиперпараметры ` num_episodes ` и ` discount_factor +` на более высокие значения, агент сможет повысить производительность:

/AtariBot/bot_5_ls.py

. . .
num_episodes =
discount_factor =
learning_rate = 0.9

report_interval = 500
. . .

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

/AtariBot/bot_5_ls.py

. . .
report_interval = 500
report = '100-ep Average: %.2f . Best 100-ep Average: %.2f . Average: %.2f ' \
        '(Episode %d)'





def print_report(rewards: List, episode: int):
   . . .

После + makeQ + добавьте еще одну функцию + initialize +, которая инициализирует модель с использованием нормально распределенных значений:

/AtariBot/bot_5_ls.py

. . .
def makeQ(model: np.array) -> Callable[[np.array], np.array]:
   """Returns a Q-function, which takes state -> distribution over actions"""
   return lambda X: X.dot(model)







def print_report(rewards: List, episode: int):
   . . .

После блока + initialize + добавьте метод + train +, который вычисляет решение замкнутой формы регрессии гребня, а затем взвешивает старую модель с новой. Возвращает как модель, так и абстрактную Q-функцию:

/AtariBot/bot_5_ls.py

. . .
def initialize(shape: Tuple):
   ...
   return W, Q









def print_report(rewards: List, episode: int):
   . . .

После + train + добавьте одну последнюю функцию + one_hot +, чтобы выполнить горячее кодирование для ваших состояний и действий:

/AtariBot/bot_5_ls.py

. . .
def train(X: np.array, y: np.array, W: np.array) -> Tuple[np.array, Callable]:
   ...
   return W, Q





def print_report(rewards: List, episode: int):
   . . .

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

Перейдите к функции + main + и замените определение Q-таблицы (+ Q = np.zeros (…​) +) следующим:

/AtariBot/bot_5_ls.py

. . .
def main():
   ...
   rewards = []




   for episode in range(1, num_episodes + 1):
       . . .

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

/AtariBot/bot_5_ls.py

. . .
def main():
   ...
   for episode in range(1, num_episodes + 1):


           . . .

Измените строку сразу после этой, которая определяет + state = env.reset () +, чтобы она стала следующей. Это мгновенно закодирует состояние в горячем режиме, так как для всех его применений потребуется вектор в одно горячее состояние:

/AtariBot/bot_5_ls.py

. . .
   for episode in range(1, num_episodes + 1):
       if len(states) >= 10000:
           states, labels = [], []
       state =
. . .

Перед первой строкой в ​​главном игровом цикле + while + исправьте список + states +:

/AtariBot/bot_5_ls.py

. . .
   for episode in range(1, num_episodes + 1):
       ...
       episode_reward = 0
       while True:

           noise = np.random.random((1, env.action_space.n)) / (episode**2.)
           . . .

Обновите вычисление для + action +, уменьшите вероятность шума и измените оценку Q-функции:

/AtariBot/bot_5_ls.py

. . .
       while True:
           states.append(state)


           state2, reward, done, _ = env.step(action)
           . . .

Добавьте горячую версию + state2 + и измените вызов Q-функции в своем определении для + Qtarget + следующим образом:

/AtariBot/bot_5_ls.py

. . .
       while True:
           ...
           state2, reward, done, _ = env.step(action)


           Qtarget = reward + discount_factor * np.max()
           . . .

Удалите строку, которая обновляет + Q [состояние, действие] = …​ +, и замените ее следующими строками. Этот код принимает выходные данные текущей модели и обновляет только значение в этих выходных данных, которое соответствует текущему предпринятому действию. В результате значения Q для других действий не несут потерь:

/AtariBot/bot_5_ls.py

. . .
           state2 = one_hot(state2, n_obs)
           Qtarget = reward + discount_factor * np.max(Q(state2))




           episode_reward += reward
           . . .

Сразу после + state = state2 + добавьте периодическое обновление в модель. Это тренирует вашу модель каждые 10 временных шагов:

/AtariBot/bot_5_ls.py

. . .
           state = state2


           if done:
           . . .

Дважды проверьте, что ваш файл соответствует The source source. Затем сохраните файл, выйдите из редактора и запустите скрипт:

python bot_5_ls.py

Это выведет следующее:

Output100-ep Average: 0.17 . Best 100-ep Average: 0.17 . Average: 0.09 (Episode 500)
100-ep Average: 0.11 . Best 100-ep Average: 0.24 . Average: 0.10 (Episode 1000)
100-ep Average: 0.08 . Best 100-ep Average: 0.24 . Average: 0.10 (Episode 1500)
100-ep Average: 0.24 . Best 100-ep Average: 0.25 . Average: 0.11 (Episode 2000)
100-ep Average: 0.32 . Best 100-ep Average: 0.31 . Average: 0.14 (Episode 2500)
100-ep Average: 0.35 . Best 100-ep Average: 0.38 . Average: 0.16 (Episode 3000)
100-ep Average: 0.59 . Best 100-ep Average: 0.62 . Average: 0.22 (Episode 3500)
100-ep Average: 0.66 . Best 100-ep Average: 0.66 . Average: 0.26 (Episode 4000)
100-ep Average: 0.60 . Best 100-ep Average: 0.72 . Average: 0.30 (Episode 4500)
100-ep Average: 0.75 . Best 100-ep Average: 0.82 . Average: 0.34 (Episode 5000)
100-ep Average: 0.75 . Best 100-ep Average: 0.82 . Average: 0.34 (Episode -1)

Напомним, что, согласно странице Gym FrozenLake, «решение» игры означает достижение 100-эпизодного среднего значения 0,78. Здесь агент получает в среднем 0,82, что означает, что он смог решить игру в 5000 эпизодов. Хотя это не решает игру в меньшем количестве эпизодов, этот базовый метод наименьших квадратов все же способен решить простую игру с примерно таким же количеством обучающих эпизодов. Хотя ваши нейронные сети могут усложниться, вы показали, что для FrozenLake достаточно простых моделей.

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

Шаг 6 - Создание глубокого агента Q-обучения для космических захватчиков

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

Первый универсальный агент, способный постоянно адаптировать свое поведение без какого-либо вмешательства человека, был разработан исследователями из DeepMind, которые также обучили своего агента играть в различные игры Atari. DeepMind оригинальная статья глубокого Q-обучения (DQN) признал две важные проблемы:

  1. * Соотнесенные состояния *: Возьмите состояние нашей игры в момент времени 0, который мы назовем * s0 *. Скажем, мы обновляем * Q (s0) * в соответствии с правилами, которые мы получили ранее. Теперь возьмем состояние в момент 1, которое мы называем * s1 *, и обновим * Q (s1) * в соответствии с теми же правилами. Обратите внимание, что состояние игры в момент времени 0 очень похоже на ее состояние в момент времени 1. Например, в Space Invaders инопланетяне могли двигаться на один пиксель каждый. Говоря более кратко, * s0 * и * s1 * очень похожи. Также мы ожидаем, что * Q (s0) * и * Q (s1) * будут очень похожими, поэтому обновление одного влияет на другое. Это приводит к колебаниям значений Q, поскольку обновление * Q (s0) * может фактически противодействовать обновлению * Q (s1) *. Более формально, * s0 * и * s1 * correlated. Поскольку Q-функция является детерминированной, * Q (s1) * соотносится с * Q (s0) *.

  2. * Нестабильность Q-функции *: Напомним, что функция * Q * является одновременно моделью, которую мы обучаем, и источником наших ярлыков. Скажем, что наши метки являются случайно выбранными значениями, которые действительно представляют собой distribution, * L *. Каждый раз, когда мы обновляем * Q *, мы меняем * L *, что означает, что наша модель пытается изучить движущуюся цель. Это проблема, поскольку используемые нами модели предполагают фиксированное распределение.

Для борьбы с коррелированными состояниями и нестабильной Q-функцией:

  1. Можно сохранить список состояний, называемый replay buffer. Каждый раз, когда вы добавляете игровое состояние к этому буферу воспроизведения. Вы также случайным образом выбираете подмножество состояний из этого списка и тренируетесь по этим состояниям.

  2. Команда в DeepMind продублировала * Q (s, a) *. Один из них называется * Q_current (s, a) *, который является обновленной вами Q-функцией. Вам нужна еще одна Q-функция для состояний-преемников, * Q_target (s ’, a’) *, которую вы не будете обновлять. Напомним, * Q_target (s ’, a’) * используется для создания ваших ярлыков. Отделив * Q_current * от * Q_target * и исправив последнее, вы исправите распределение, из которого выбраны ваши метки. Затем ваша модель глубокого обучения может потратить короткое время на изучение этого распределения. Через некоторое время вы повторно продублируете * Q_current * для нового * Q_target *.

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

mkdir models

Затем используйте + wget + для загрузки параметров предварительно обученной модели Space Invaders:

wget http://models.tensorpack.com/OpenAIGym/SpaceInvaders-v0.tfmodel -P models

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

  • Состояния должны быть уменьшены или уменьшены в размере до 84 x 84.

  • Вход состоит из четырех состояний, расположенных друг над другом.

Мы рассмотрим эти ограничения более подробно позже. Сейчас загрузите скрипт, набрав:

wget https://github.com/alvinwan/bots-for-atari-games/raw/master/src/bot_6_a3c.py

Теперь вы запустите этого предварительно обученного агента Space Invaders, чтобы увидеть, как он работает. В отличие от нескольких последних ботов, которые мы использовали, вы будете писать этот скрипт с нуля.

Создайте новый файл скрипта:

nano bot_6_dqn.py

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

/AtariBot/bot_6_dqn.py

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

/AtariBot/bot_6_dqn.py

. . .
import tensorflow as tf
from bot_6_a3c import a3c_model





def main():
 . . .

Через две строки после объявления + num_episodes + определите функцию + downsample +, которая уменьшает все изображения до размера 84 x 84. Вы будете отбирать все изображения перед передачей их в предварительно обученную нейронную сеть, так как предварительно обученная модель была обучена на 84 x 84 изображениях:

/AtariBot/bot_6_dqn.py

. . .
num_episodes = 10




def main():
 . . .

Создайте игровое окружение в начале вашей функции + main + и заполняйте окружение так, чтобы результаты были воспроизводимыми:

/AtariBot/bot_6_dqn.py

. . .
def main():


   . . .

Сразу после заполнения среды инициализируйте пустой список для хранения наград:

/AtariBot/bot_6_dqn.py

. . .
def main():
   env = gym.make('SpaceInvaders-v0')  # create the game
   env.seed(0)  # make results reproducible

   . . .

Инициализируйте предварительно обученную модель с параметрами предварительно обученной модели, которые вы загрузили в начале этого шага:

/AtariBot/bot_6_dqn.py

. . .
def main():
   env = gym.make('SpaceInvaders-v0')  # create the game
   env.seed(0)  # make results reproducible
   rewards = []

   . . .

Затем добавьте несколько строк, говорящих сценарию итерировать ` num_episodes + `раз", чтобы вычислить среднюю производительность и инициализировать вознаграждение каждого эпизода равным 0. Кроме того, добавьте строку для сброса среды (+ env.reset () `), собирая новое начальное состояние в процессе, уменьшите это начальное состояние с помощью ` downsample () ` и запустите игровой цикл, используя ` while +` loop:

/AtariBot/bot_6_dqn.py

. . .
def main():
   env = gym.make('SpaceInvaders-v0')  # create the game
   env.seed(0)  # make results reproducible
   rewards = []
   model = a3c_model(load='models/SpaceInvaders-v0.tfmodel')




       . . .

Вместо того, чтобы принимать одно состояние за раз, новая нейронная сеть принимает одновременно четыре состояния. В результате вы должны подождать, пока список + states + содержит как минимум четыре состояния, прежде чем применять предварительно обученную модель. Добавьте следующие строки ниже строки, читающей + while True: +. Они говорят агенту выполнить случайное действие, если имеется менее четырех состояний, или объединить состояния и передать его в предварительно обученную модель, если есть хотя бы четыре:

/AtariBot/bot_6_dqn.py

       . . .
       while True:





               . . .

Затем выполните действие и обновите соответствующие данные. Добавьте уменьшенную версию наблюдаемого состояния и обновите награду за этот эпизод:

/AtariBot/bot_6_dqn.py

       . . .
       while True:
           ...
               action = np.argmax(model([frames]))



           . . .

Затем добавьте следующие строки, которые проверяют, является ли эпизод + done +, и, если это так, выведите общее вознаграждение за эпизод, исправьте список всех результатов и досрочно прервите цикл + while +:

/AtariBot/bot_6_dqn.py

       . . .
       while True:
           ...
           episode_reward += reward




               . . .

За пределами циклов + while и` + for` выведите среднее вознаграждение. Поместите это в конец вашей функции + main +:

/AtariBot/bot_6_dqn.py

def main():
   ...
               break

Убедитесь, что ваш файл соответствует следующему:

/AtariBot/bot_6_dqn.py

"""
Bot 6 - Fully featured deep q-learning network.
"""

import cv2
import gym
import numpy as np
import random
import tensorflow as tf
from bot_6_a3c import a3c_model
random.seed(0)  # make results reproducible
tf.set_random_seed(0)

num_episodes = 10


def downsample(state):
   return cv2.resize(state, (84, 84), interpolation=cv2.INTER_LINEAR)[None]

def main():
   env = gym.make('SpaceInvaders-v0')  # create the game
   env.seed(0)  # make results reproducible
   rewards = []

   model = a3c_model(load='models/SpaceInvaders-v0.tfmodel')
   for _ in range(num_episodes):
       episode_reward = 0
       states = [downsample(env.reset())]
       while True:
           if len(states) < 4:
               action = env.action_space.sample()
           else:
               frames = np.concatenate(states[-4:], axis=3)
               action = np.argmax(model([frames]))
           state, reward, done, _ = env.step(action)
           states.append(downsample(state))
           episode_reward += reward
           if done:
               print('Reward: %d' % episode_reward)
               rewards.append(episode_reward)
               break
   print('Average reward: %.2f' % (sum(rewards) / len(rewards)))


if __name__ == '__main__':
   main()

Сохраните файл и выйдите из редактора. Затем запустите скрипт:

python bot_6_dqn.py

Ваш вывод закончится следующим:

Output. . .
Reward: 1230
Reward: 4510
Reward: 1860
Reward: 2555
Reward: 515
Reward: 1830
Reward: 4100
Reward: 4350
Reward: 1705
Reward: 4905
Average reward: 2756.00

Сравните это с результатом первого сценария, где вы запустили случайного агента для Space Invaders. Среднее вознаграждение в этом случае составляло всего около 150, а это значит, что этот результат более чем в двадцать раз лучше. Однако вы запускали свой код только для трех эпизодов, так как он довольно медленный, а среднее значение для трех эпизодов не является надежным показателем. Выполнение этого за 10 эпизодов, в среднем составляет 2756; более 100 эпизодов, в среднем около 2500. Только с этими средними вы можете с уверенностью заключить, что ваш агент действительно работает на порядок лучше, и что теперь у вас есть агент, который довольно хорошо играет в Space Invaders.

Однако напомним, что в предыдущем разделе был поднят вопрос о сложности образца. Оказывается, этот агент Space Invaders берет на тренировку миллионы образцов. Фактически этому агенту потребовалось 24 часа на четырех графических процессорах Titan X для обучения до этого текущего уровня; иными словами, для его адекватной подготовки потребовалось значительное количество вычислений. Можете ли вы обучить столь же высокоэффективного агента с гораздо меньшим количеством образцов? Предыдущие шаги должны вооружить вас достаточно знаний, чтобы начать изучать этот вопрос. Использование гораздо более простых моделей и компромиссов между отклонениями может быть возможным.

Заключение

В этом уроке вы создали несколько ботов для игр и изучили фундаментальную концепцию машинного обучения, называемую смещением. Естественный следующий вопрос: можете ли вы создавать ботов для более сложных игр, таких как StarCraft 2? Как оказалось, это нерешенный вопрос исследования, дополненный инструментами с открытым исходным кодом от сотрудников Google, DeepMind и Blizzard. Если это проблемы, которые вас интересуют, см. Https://openai.com/requests-for-research/[open призывы к исследованиям в OpenAI], чтобы узнать о текущих проблемах.

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

Related