ПРИМЕЧАНИЕ. Я перемещаю этот блог в подстак. Подпишитесь на новые сообщения или чтобы читать другие

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

Обучение с подкреплением не должно быть трудным. Идея достаточно проста:

  1. Попробуйте что-нибудь наугад и сохраните состояния и награды
  2. Обучите сеть предсказывать вознаграждение
  3. Используйте сеть, чтобы выбрать самую высокую награду с учетом некоторой случайности
  4. Продолжайте тренироваться, основываясь на этом опыте

Вы прочтете учебные пособия, в которых все будет работать безупречно. Вы взглянете на гиперпараметры и поверите, что они интуитивно понятны (почему бы вам не использовать 4 сложенных сверточных слоя с ядрами 4x4 и размером шага 2x2?). Кроме того, модели машинного обучения надежны, и вы уверены, что другие параметры будут работать аналогичным образом. Вас утешает тот факт, что с помощью эмуляции вы получаете практически неограниченное количество обучающих выборок. Несомненно, все эти великолепные результаты - это просто результат более быстрых графических процессоров и отличных библиотек машинного обучения.

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

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

  1. Неэффективность: на мой взгляд, это не серьезная проблема. Долгосрочная тенденция аппаратного обеспечения со временем уменьшит эту проблему. Кроме того, мы не должны антропоморфизировать то, что на самом деле происходит. Вы можете научить человека перемещать ящик из точки A в точку B на одном учебном примере, но не на компьютере. Но почему мы должны ожидать, что сможем это сделать? Обучение с подкреплением занимается совершенно другим делом, и тот факт, что оно требует данных, не должен никого отговаривать от его полезности.
  2. Сложность определения функции вознаграждения и непредвиденные последствия. Конечно, определить вознаграждение сложно, но это также присутствует в человеческой деятельности. Одна из самых сложных задач, с которыми сталкиваются менеджеры, организации и родители, - это создание стимулов для других. Менеджеры хотят получить максимум от своего лучшего, но не перегружать их. Организации балансируют краткосрочные и долгосрочные цели. Родители стараются поддерживать и дисциплинировать. Было бы наивно думать, что есть какая-то интуитивно понятная целевая функция, которую мы можем просто передать машине и не иметь непредвиденных последствий или компромиссов.
  3. Локальный оптимум: это верно для всего машинного обучения, но проблема четко определена и хорошо изучена. Одно из решений - обучить несколько моделей в тандеме и использовать методы ансамбля или своего рода метод исключения модели, или просто добавить больше случайности. Более быстрые графические процессоры также упрощают эксперименты, что должно помочь.
  4. Чрезмерное приспособление к странным шаблонам и нестабильным результатам: эта проблема больше всего расстраивает тех, кто просто пытается заставить работать обучение с подкреплением. Это то, что я попытаюсь обсудить в этом посте.

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

Разберитесь в своей среде и ознакомьтесь с примерами обучения

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

В последнем посте я говорил о Воспроизведении опыта, который, по сути, представляет собой буфер, в котором хранятся ваши впечатления (состояние, предпринятые действия, награда, следующие состояния и окончание игры). Случайная выборка из буфера - эффективное средство для получения разнообразных впечатлений, которые со временем обновляются. Но во многих играх награды невелики, поэтому 99% вашего опыта просто дают нулевую награду. Ваша модель будет отличной, поскольку она просто предсказывает 0, и по большей части она будет правильной. Итак, вы хотите сосредоточиться на примерах, в которых что-то произошло. Вот пример случайной игры змейкой на доске 20x20:

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

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

from collections import Counter
import numpy as np
import gym
import snake
env = gym.make("Snake-v0")
num_games = 1000
num_steps = []
rewards = []
for num_game in range(num_games):
    num_step = 0
    done = False
    env.reset()
    while not done:
        num_step += 1
        action = env.action_space.sample()
        state, reward, done, _ = env.step(action)
        rewards.append(reward)
        
    num_steps.append(num_step)
print("Number of games played: {}".format(num_games))
print("Average number of steps: {:0.2f}".format(np.mean(num_steps)))
print("Number of steps distribution \n\t10%: {:0.2f} \t25%: {:0.2f} \t50%: {:0.2f} \t75%: {:0.2f} \t99%: {:0.2f}".format(
    *np.percentile(num_steps, [10, 25, 50, 75, 99])))
print("Distribution of rewards: {}".format(Counter(rewards)))

Вот результаты:

Количество сыгранных игр: 1000
Среднее количество шагов: 34,18
Распределение количества шагов
10%: 5,00 25%: 10,00 50%: 22,00 75%: 48,00 99%: 143,12
Распределение наград: счетчик ({0: 33123, -1: 1000, 1: 60})

Так что ~ 33к шагов вознаграждения нет. У меня есть награда -1 за 1к из них (как и ожидалось, так как я сыграл в 1к игр). И я получил награду на 60. Если мы просто случайным образом попробуем этот опыт, мы, очевидно, будем очень предвзято относиться к тому, чтобы ничего не происходило. И это не так. В большинстве случаев ничего не происходит. Но нам нужно попытаться точно узнать, что происходит в тех 60 случаях, когда мы получали вознаграждение, и в тех 1000 случаях, которые привели к моей смерти.

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

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

Так, например, вот как будет выглядеть необычный_выборочный_фактор, равный 0,9, на 8 образцах:

rewards:        [1, 0, 0, 0, 0, 1, -1, 0]
sorted rewards: [1, 1, -1, 0, 0, 0, 0, 0]
probs:          [ 0.18  0.16  0.14  0.13  0.12  0.1   0.09  0.08]

В зависимости от того, насколько велик ваш буфер опыта и насколько вы хотите поддерживать необычный опыт в своем буфере, вы можете изменить необычный_выборочный_фактор. На уровне 1.0 все элементы одинаково вероятны. По мере приближения к 0 вероятность появления первых образцов возрастает. Есть также другие методы, которые вы можете использовать, например, взвешивание вероятности по абсолютной величине вознаграждения, но этого, вероятно, достаточно. И необычные награды по-прежнему с равной вероятностью будут отброшены, поскольку функция добавления просто без разбора отбрасывает старый опыт. Вы также можете это изменить.

Сокращение интервала

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

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

Перед тем как начать, проведите множество испытаний и запишите среднюю производительность, а также распределение. Если ваша средняя награда составляет 10 +/- 20, то вам не следует слишком волноваться, увидев, что результаты меняются примерно на 20–30. Также есть более сопоставимые боты. Иметь бота, который всегда выполняет одно и то же действие. Сделайте случайный, но необъективный (например, 60% ВВЕРХ, 20% ВНИЗ, 20% ВЛЕВО, 20% ВПРАВО). Я бы создал отдельную функцию, которая оценивает бота, и вы можете просто кормить его различными моделями. Ниже представлена ​​реализация простой функции и класса, которые можно использовать для тестирования производительности.

Вы могли бы использовать это, создавая функцию для CustomModel и оценивая ее. См. ниже

from benchmark import CustomModel, test_model
model_random = CustomModel(lambda x: np.random.random(4), "random")
model_0 = CustomModel(lambda x: [1,0,0,0], name="always_0")
model_1 = CustomModel(lambda x: [0,1,0,0], name="always_1")
model_2 = CustomModel(lambda x: [0,0,1,0], name="always_2")
model_3 = CustomModel(lambda x: [0,0,0,1], name="always_3")
model_bias_0 = CustomModel(lambda x: np.random.random(4) * [2,1,1,1], name="bias_0")
model_bias_1 = CustomModel(lambda x: np.random.random(4) * [1,2,1,1], name="bias_1")
model_bias_2 = CustomModel(lambda x: np.random.random(4) * [1,1,2,1], name="bias_2")
model_bias_3 = CustomModel(lambda x: np.random.random(4) * [1,1,1,2], name="bias_3")
models = [
    model_random,
    model_0,
    model_1,
    model_2,
    model_3,
    model_bias_0,
    model_bias_1,
    model_bias_2,
    model_bias_3
]
for model in models:
    print("model: {} score: {:0.2f}".format(model.name, test_model(env, model)))

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

Проверка прогнозов

Что прогнозирует ваш бот? Вы можете посмотреть на цифры убытков, и их довольно легко определить, когда они выходят из-под контроля. Но посмотрите также и на свои предсказания. Если ваша игра большую часть времени возвращает 0 с вознаграждением 1 каждые 10 шагов или около того (при условии идеальной игры), то вы должны ожидать, что ваши прогнозы будут в основном от 0 до 1. Однако, когда вы выполняете начальную загрузку, как мы делали в последнем посте, награды могут выйти из-под контроля и стать невероятно положительными или отрицательными. Вы можете справиться с этим, уменьшив скорость обучения или уменьшив частоту обновления целевой сети. Но самое главное, определите, что это происходит на раннем этапе, и не тратьте время на то, чтобы дать модели больше опыта.

Наконец, попробуйте разные архитектуры нейронных сетей

Если вы получаете хорошие учебные примеры, награды выдают, как и ожидалось, и ваш бот действительно становится лучше, но количество потерь не уменьшается, тогда вы можете попробовать разные архитектуры. Вы можете попробовать все обычные методы (отсев, пакетная нормализация, методы ансамбля, увеличение количества слоев или нейронов). Вы также можете рассмотреть возможность использования RNN, такого как GRU или LSTM, о чем я расскажу в одном из следующих постов. Но если ваш бот не улучшается, скорее всего, происходит что-то еще, и возиться с моделью - пустая трата времени.

ПРИМЕЧАНИЕ. Я перемещаю этот блог в подстак. Подпишитесь на новые сообщения или чтобы читать другие