Эксперимент в Q-обучении, нейронных сетях и Pygame.

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

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

Я многому научился из «Что такое обучение с подкреплением?» наблюдать, как мой Робокар умело перемещается по окружающей среде, поэтому я решил поделиться этими знаниями со всем миром.

Вот как это работает…

Обновление, 24 февраля 2016 г .: Часть 2 уже доступна. Ищите больше анализа и уроков

Обновление, 7 марта 2016 г.. Теперь доступна третья часть. Здесь мы превращаем нашего сима во что-то, что приближает нас к реальной модели.

Раскрытие

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

На этом, я заранее извиняюсь за любые концепции, которые я испортил, и с энтузиазмом приветствую ваши отзывы и исправления.

Кредиты и полезные ресурсы

Мое путешествие было примерно таким:

  1. Смотрел на YouTube видео DeepMind побеждает игры Atari. Разум взорван.
  2. Наткнулся на эту реплику, слишком сложную для понимания.
  3. Нашел эту версию, которая имела отличную запись и простой для понимания код.
  4. Решил, что буду использовать эту игру« машина на ходу » и взломал ее, чтобы позволить вышеупомянутому алгоритму играть в нее.
  5. При попытке редактировать входные данные сети быстро понял, что свертка была у меня над головой, и я все равно не совсем понимал алгоритм. Я закончил тем, что вернулся к части Q-обучения после прохождения следующего шага, и в конце концов это было весьма полезно.
  6. Я наткнулся на этот удивительный учебник по обучению с подкреплением, который заложил основу для большей части этого. Помимо урока Q-обучения, он также дал мне простую структуру для нейронной сети с использованием Keras. Если вы попали сюда с таким же небольшим объемом знаний в области обучения с подкреплением, как и я, я рекомендую вам также прочитать части 1 и 2.
  7. Я понял, что игра машина на ходу, которую я использовал, была медленной и у меня болели глаза, поэтому я построил свою собственную игру, используя Pygame и Pymunk.

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

Общая концепция и отличия

Цель виртуального самообучающегося Робокара - как можно дольше объезжать окружающую среду, ни во что не задевая.

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

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

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

Давай займемся этим!

Используемые библиотеки

Я использовал Python3 и Keras (с бэкэндом Theano) для машинного обучения; Pygame и Pymunk для самой игры.

Код!

Код этого проекта доступен на GitHub.

nn.py - здесь находится нейронная сеть Keras.

carmunk.py - это сама игра. Код ужасен, и я приношу свои извинения.

learning.py - здесь живет сердце процесса Q-обучения.

play.py - просто берет обученную модель и едет!

Управление игрой, состояние и награда

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

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

В каждом кадре игра возвращает и состояние, и награду.

Состояние представляет собой 1d массив значений датчика, которые могут быть 0, 1 или 2, как указано выше.

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

Алгоритм обучения

Outlace.com проделал такую ​​большую работу по объяснению, как работает Q-Learning, что я не буду повторять его здесь. (Клянусь, я не связан с этим сайтом или автором, я просто в восторге от того, насколько помогли учебные материалы!) Вместо этого позвольте мне попытаться объяснить свою реализацию шаг за шагом:

  1. Начните новую игру и переместите машину на один кадр вперед, не поворачиваясь.
  2. Получите показания датчиков.
  3. Основываясь на этих показаниях, спрогнозируйте значения Q. Эти прогнозы показывают уверенность Robocar в том, что он должен предпринять каждое из трех действий, перечисленных выше. В первый раз это будет бесполезно, но мы должны с чего-то начать.
  4. Сгенерируйте случайное число. Если он меньше нашего эпсилона (см. Ниже), выберите случайное действие. Если он выше, чем наш эпсилон, выберите наиболее уверенное действие, полученное в результате нашего прогноза.
  5. Выполните действие (влево, вправо, ничего) и получите еще одно показание датчика и нашу награду.
  6. Мы сохраняем эти вещи - исходное чтение, предпринятые действия, полученное вознаграждение и новое чтение - в массиве, который мы называем буфером.
  7. Возьмите случайную выборку «чтение, действие, награда, новое чтение» из нашего буфера и учитесь, создавая обучающий набор X, y, которому мы «подгоняем» нашу модель.
  8. Это самая сложная часть всего процесса, и у меня больше всего проблем с головой. Но позвольте мне попробовать…
  9. Мы устанавливаем значение y для итерации как прогноз, основанный на исходном чтении.
  10. Мы делаем новый прогноз на основе нашего нового чтения (состояние после действия).
  11. Мы смотрим на награду, которую мы получили за выполнение действия. Если это -500, мы с чем-то столкнулись, поэтому мы установили y для этой итерации и этого действия на -500. Если мы ни с чем не столкнулись, мы умножаем наше максимальное прогнозируемое значение Q на гамму (чтобы дисконтировать его) и устанавливаем значение y итерации для действия, которое мы предприняли.
  12. Вернитесь к шагу 2, пока не натолкнетесь на что-нибудь.
  13. Когда мы с чем-то сталкиваемся, уменьшаем наш эпсилон и возвращаемся к шагу 1.

Вот код для шагов с 8 по 11:

def process_minibatch(minibatch):
   X_train = []
   y_train = []
   # Loop through our batch and create arrays for X and y
   # so that we can fit our model at every step.
   for memory in minibatch:
     # Get stored values.
     old_state_m, action_m, reward_m, new_state_m = memory
     # Get prediction on old state.
     old_qval = model.predict(old_state_m, batch_size=1)
     # Get prediction on new state.
     newQ = model.predict(new_state_m, batch_size=1)
     # Get our best move. I think?
     maxQ = np.max(newQ)
     y = np.zeros((1, 3))
     y[:] = old_qval[:]
     # Check for terminal state.
     if reward_m != -500: # non-terminal state
       update = (reward_m + (GAMMA * maxQ))
     else: # terminal state
       update = reward_m
     # Update the value for the action we took.
     y[0][action_m] = update
     X_train.append(old_state_m.reshape(NUM_SENSORS,))
     y_train.append(y.reshape(3,))
  X_train = np.array(X_train)
  y_train = np.array(y_train)
  return X_train, y_train

Вся эта штука "эпсилон"

Эпсилон помогает нам решить, исследовать ли новое действие или предпринять то, что мы считаем лучшим действием, доступным в любое время. Мы начинаем с того, что всегда выбираем случайное действие, потому что Robocar ничему не научился. Но со временем во время обучения эпсилон уменьшается, и поэтому мы «случайным образом» выбираем «лучшее» действие чаще. Эпсилон уменьшается до 0,1, поэтому даже в конце цикла обучения он все еще выбирает случайное действие в 10% случаев.

Я упомянул об этом отдельно потому, что столкнулся с забавной проблемой. Я потратил часы на вычисления, пробуя всевозможные значения и настройки практически для всего в этом проекте, у которого есть изменяемые значения или настройки. Я не мог понять, почему после всех этих тренировок и обучения Robocar все еще мог выполнить не более 22000 кадров действий перед тем, как столкнуться со стеной, и в целом справлялся гораздо хуже. (Примечание: это для поколения 1, скорость которого со временем не увеличивалась.) Я подумал: «Эта штука наверняка переедет моих кошек, если она не сможет прожить дольше 5 минут, двигаясь по кругу».

Затем я позволил эпсилону опуститься ниже 0,1 до нуля. И когда это произошло, машина не умерла. Это просто крутилось, пока мне не стало скучно, и я сам не прекратил. Оказывается, он узнал гораздо больше, чем я думал.

Итак, извлеченный урок: 10% случайности могут быть значительным фактором и могут заставить вас тратить часы на уточнение настроек, хотя все было бы хорошо с самого начала.

Нейронная сеть

В качестве модели мы используем относительно простую полносвязную нейронную сеть. У нас есть три плотных слоя: вход, единственный скрытый слой и выход. Мы добавляем выпрямленные линейные единицы после входного и скрытого слоя для ускорения тренировки, а также добавляем выпадение 0,2, чтобы предотвратить чрезмерную подгонку. Учитывая, что это не очень сложная проблема, многое из этого (и размер скрытого слоя) является чрезмерным, но интересно изучить, как различные активации, выпадение и размер скрытого слоя влияют на точность и время обучения. Если вы скачали код, я рекомендую вам поэкспериментировать с этими параметрами, чтобы увидеть, насколько они подходят для вас.

Результаты, достижения

В первом поколении с одним препятствием и без увеличения скорости Robocar безупречно водил машину после очень небольшого обучения. Я говорю о бесконечной жизни после 10 минут тренировок!

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

Я обучил версию 2-го поколения, состоящую из нескольких случайно сгенерированных препятствий, случайно сгенерированного начального угла и новой матрицы датчиков, используя лучшие настройки от 1-го поколения, которые показаны в серии P на графике выше. Все 1000 игровых процессов с использованием скользящего среднего из 10 игр приводят к следующему шаблону обучения:

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

Я думаю, это неплохо! Что вы думаете?

Обновление, 24 февраля 2016 г .: Обязательно ознакомьтесь с частью 2, где я анализирую потери, настраиваю параметры и отображаю красивые графики: Обучение с подкреплением в Python, чтобы научить виртуальная машина для объезда препятствий - часть 2

Обновление, 7 марта 2016 г .: Часть 3 уже доступна. Здесь мы превращаем нашего симулятора во что-то, что приближает нас к реальной модели.

Если вы пропустили ссылку выше, весь код доступен на GitHub здесь: Reinforcement Learning Car with Python