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

Вдохновленный последними разработками в области технологий беспилотных автомобилей; Стремясь экспериментировать с машинным обучением и работой, проделанной Deep Mind, я решил перейти на глубокий конец и попытаться реализовать Deep Q-Network. Позже я понял, что для моего первого знакомства с нейронными сетями было немного амбициозно - быть CNN, реализующей Deep Q-Learning. Однако вот и мы. Если вы не будете настаивать на себе, вы никогда не станете лучше… не так ли?

Q-Learning на основе таблиц

Начнем с самого начала. Q-Learning на основе таблиц (примечание: Не Deep Q-Learning) - это тип машинного обучения с подкреплением. Q-Learning впервые был подробно описан в Кембриджской докторской диссертации Кристофера Уоткинса в 1989 году. Он основан на том, как вы обычно тренируете животное или ребенка. Вы НАГРАДАЙТЕ желаемое поведение и наказываете (ОТРИЦАТЕЛЬНОЕ НАГРАЖДЕНИЕ) за нежелательное поведение.

Теперь немного терминологии.

АГЕНТ имеет СОСТОЯНИЕ. СОСТОЯНИЕ состоит из наблюдений, которые АГЕНТ может сделать. АГЕНТ может выбрать ДЕЙСТВИЕ, которое затем переведет его в другое СОСТОЯНИЕ. Каждое ДЕЙСТВИЕ оценивается по вероятности получения НАГРАДЫ, это называется КАЧЕСТВО действия.

Пример, который я почти везде просматривал, касается Hello World обучения с подкреплением, «Grid World». Для смены обстановки я применил иронический пример из своего многолетнего ухода за баром.

Рассмотрим изображение и таблицу ниже. Человек (агент) находится в панели и может находиться в одном из следующих состояний;

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

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

Награды не обязательно связаны ТОЛЬКО с конечными состояниями. Вот как работает этот пример.

Из текущего состояния (У двери) нам легко увидеть, что лучшим действием было бы "Панель подхода" . Затем следуют «Заказать напиток», «Заплатить бармену», затем «Сделать глоток вкусного напитка». (Примечание: «Щелкни пальцем и свистни бармену.» НИКОГДА не является хорошим действием). Цель Q-Learning - определить правильное действие, которое следует предпринять с учетом текущего состояния, это известно как политика.

Q-Learning делает это с помощью уравнения Беллмана:

Несколько замечаний;

  • скорость обучения - это значение от 0 до 1, которое определяет, насколько алгоритм перезаписывает текущее Q-значение новой информацией.
  • коэффициент дисконтирования - это также значение от 0 до 1, которое определяет, насколько вы доверяете будущему прогнозу Q-значения.
  • если после действия агент оказывается в терминальном состоянии, уравнения выглядят просто:

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

Представьте, что после кучи случайных действий агент оказывается в состоянии «обслуживается». Агент случайным образом выбирает действие «Заплатить бармену», в результате чего появляется новое состояние «Наслаждайтесь вкусным напитком» и награда +1.

Применяя уравнение Беллмана для терминального состояния, качество пары состояние-действие обновляется (установка alpha на 1 для простоты) получаем:

которые мы затем обновляем в Q-таблице

Теперь, когда в следующий раз агент окажется в состоянии «обслуживается», он будет знать, что нужно предпринять действие «Pay Bartender». Что действительно круто, так это то, что в следующий раз, когда агент окажется в состоянии ‘At Bar’, уравнение Беллмана будет выглядеть так (при условии, что gamma = 1)

Таким образом, качество пары состояние-действие «В баре - Заказать напиток» обновляется. Этот процесс работы в обратном направлении от начального состояния целевого состояния продолжается до тех пор, пока не будет определен четкий путь от «У двери» к «Наслаждаться вкусным напитком».

В целом алгоритм выглядит так:

setup q_table
initialize state, alpha, gamma
while training:
  get q values for each action given the state
  select action with highest q
  apply action
  observe reward
  observe next_state
  if next_state is terminal:
    q_update = reward
  else:
    get q_next values for each action given the next_state
    observe the largest q value (q_max) in q_next
    q_update = alpha*(reward + gamma*q_max))
  
  q_table[state][action] = q_update
  state = next_state

Так что это Q-Learning, теперь мы все можем идти домой… не совсем. На дороге есть несколько ухабов, которые, возможно, заметили некоторые из вас.

Разведка против эксплуатации

Некоторые из вас, возможно, заметили, что как только агент обнаруживает пару состояние-действие, которая приводит к вознаграждению, у него нет стимула исследовать другие возможности. Это все равно, что каждый раз выбирать первый коктейль в меню. С алгоритмической точки зрения это имеет смысл, если предположить, что коктейль был приготовлен опытным барменом и подавался с улыбкой. Вы испытали награду в виде вкусного напитка, выбрав первый коктейль в списке, зачем вам исследовать дальше? Когда всего на 2 места ниже Коктейль последнего слова… очевидно, оптимальный выбор!

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

setup q_table
initialize state, epsilon, alpha, epsilon, decay_rate
while training:
  get q values for each action given the state
if (epsilon > random_float_between_0_and_1):
    select random action
  else:
    select action with highest q
apply action
  observe next_state
  observe reward
  if next_state is terminal:
    q_update = reward
  else:
    get q_next values for each action given the next_state
    observe the largest q value (q_max) in q_next
    q_update = alpha*(reward + gamma*q_max))
  
  q_table[state][action] = q_update
  state = next_state
  epsilon -= decay_rate

Проблема эффективности

Следующая проблема заключается в скорости роста Q-Table со сложностью. Даже для нашего простого примера бара у нас есть 20 записей. Даже для простой игры в крестики-нолики с учетом симметрии и других редукций существует почти 27 000 возможных игровых комбинаций (Я не придумал, если вам интересно, можете нажать здесь). Как вы понимаете, для любого приложения с любой реальной сложностью Q-Table не поможет.

Войдите в Q-Net

Это то, что побудило использовать искусственную нейронную сеть для аппроксимации поведения Q-таблицы. В 2013 году DeepMind выпустил Игра в Atari с глубоким обучением с подкреплением и 2015 Контроль на уровне человека с помощью глубокого обучения с подкреплением.

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

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

Чтобы обучить NN вместо Q-Table, мы делаем два прохода с прямой связью и один проход с обратной связью. При прямом проходе мы используем состояние в качестве входных данных. На выходе получается матрица 1xN, представляющая значение качества для каждого возможного действия.

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

Затем мы наблюдаем следующее состояние и пропускаем его через NN. Затем мы наблюдаем наибольшее значение Q на выходе и используем модифицированное уравнение Беллмана для вычисления целевого значения. Наконец, мы вычисляем среднеквадратичную ошибку между первым прямым проходом и целью, а затем обратно распространяем ее обратно через NN.

Вот это блок-схема из:

Ниже приводится реализация псевдокода epsilon greedy для обучения Deep Q-NN:

initialize state, epsilon, alpha, epsilon
while training:  
  observe state
  feed forward state through NN to get q_matrix
  if (epsilon > random_float_between_0_and_1):
    select random action
  else:
    action_idx = index of the element of q_matrix with largest value
apply action at action
  observe next_state
  observe reward
  if next_state is terminal:
    q_update = reward
  else:
    feed forward next_state through NN to get q_matrix_next
    observe the largest q value (q_max) in q_matrix_next
    q_update = reward + gamma*q_max
  
  make copy of q_matrix (q_target)
  q_target[action_idx] = q_update
  loss = MSE(q_matrix, q_update)
  optimize NN using loss funciton
  state = next_state
  epsilon -= decay_rate

Катастрофическое забвение

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

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

В качестве примера я оставлю простую симуляцию бара и сосредоточусь на симуляторе шоссе, на котором мы будем тренироваться позже. В кадре ниже агент - красная машина, а препятствия - серые машины. Линии, выходящие из красной машины, представляют смоделированный LiDAR. Агент использует данные LiDAR в качестве состояния. Каждый луч возвращает расстояние до объекта, на который он попадает. Если луч ни во что не попадает, он вернет значение, равное дальности действия LiDAR. Красный кружок предназначен для обнаружения столкновений. Горизонтальные линии - это чисто эстетические маркеры дорожек. Награда +1 присуждается за каждый кадр, если агент не сталкивается с препятствием. В случае столкновения присуждается -1. Агент может «ускориться», «сломать», «повернуть влево», «повернуть вправо» или «ничего не делать». Действие «ничего не делать» просто означает, что агент продолжит свой текущий путь с постоянной скоростью.

А теперь вернемся к катастрофическому забыванию. Во время обучения Q-Net сталкивается с описанной выше ситуацией. Агент обгоняет более медленную машину по той же полосе. Используя реализацию epsilon greedy, агент выбирает «повернуть налево», избегая машины и получая награду +1. Это приводит к низкой ошибке, которая распространяется по сети в обратном направлении, способствуя такому поведению в будущем. Так в чем проблема?

Теперь рассмотрим ситуацию ниже.

Здесь тоже идет медленная машина на той же полосе. Для молодого Q-Net два состояния могут выглядеть очень похожими. Состояние передается через Q-Net, что приводит к «повороту влево», но на этот раз это приводит к столкновению и награде -1. Это приводит к большой ошибке, которая распространяется обратно по сети, стирая то, что мы узнали ранее. К счастью, решение есть.

Опыт Replay

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

Сначала мы будем заполнять буфер случайными выборками, но как вообще могут помочь данные, зарегистрированные случайным управлением агентом? Идея состоит в том, что при обучении на пакете Q-Net с меньшей вероятностью будет подвержена катастрофическому забыванию, кроме того, поскольку Q-Net станет лучше правильно оценивать качество действия, агент будет принимать более правильные решения. Это, в свою очередь, перезапишет менее релевантные случайные данные.

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