Обучение с подкреплением: давайте научим такси водить

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

В этой статье я покажу вам, как реализовать решение RL с использованием Python и его библиотеки gym-OpenAI, которую вы можете легко установить, запустив на консоли Jupyter pip install gym. Я собираюсь представить вам следующую проблему:

Ваша среда состоит из матрицы 5x5, где каждая ячейка - это позиция, на которой может оставаться ваше такси. Затем у вас есть 4 координаты, которые представляют точки посадки и высадки, а именно (0,0), (0,4), (4,0), (4,3) (для согласованности с Python язык, первый индекс равен 0, а не 1). Мы будем называть их R, G, Y, B и индексировать их местоположение соответственно 0,1,2,3. Наконец, есть один пассажир, которого можно либо поднять, либо высадить, а также перевозить (следовательно, проводить время в кабине). В частности, этот пассажир хочет добраться до точки Б.

Теперь, если мы импортируем наш модуль gym и инициализируем среду такси, мы увидим, что он повторяет то, что мы говорили до сих пор:

import gym
env = gym.make("Taxi-v2").env
env.render()

Как видите, у нас есть пространство 5x5 с нашими 4 местоположениями, где синяя буква представляет местоположение текущего пассажира, а фиолетовая буква - место высадки. У нас также есть наше такси / агент в этом пространстве, которое представляет собой желтый прямоугольник, а также некоторые стены, представленные символом «|».

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

Давайте сначала рассмотрим наши действия. Согласно импортированному модулю агент может действовать 6 способами:

  • 0: спускаться (юг)
  • 1: восхождение (север)
  • 2: иду направо (восток)
  • 3: идти налево (запад)
  • 4: собирать
  • 5: высадка

Во-вторых, сколько у нас состояний? Ну, по крайней мере, 25: действительно, имея пространство 5x5, мы знаем, что кабина может просто занимать эти ячейки. Кроме того, такси также может находиться в состоянии посадки или высадки пассажира (независимо от того, находится ли оно на самом деле: помните, что такси будет двигаться по попыткам), следовательно, у нас есть еще 4 состояния. Наконец, мы должны вычислить те состояния, в которых пассажира действительно подобрали, высадили (+ 4 состояния, поскольку мест, где мог бы находиться пассажир, равно 4) или просто транспортировали (+ 1 состояние). Итак, всего у нас 5x5x4x5 = 500 состояний.

Каждое состояние нашего агента, которое представлено вектором значений [строка такси, столбец такси, индекс пассажира, индекс пункта назначения], соответственно кодируется значением от 0 до 499. А именно, мы можем воспроизвести местоположение нашего предыдущего изображения следующим образом:

state = env.encode(4, 2, 3, 2) 
print("State:", state)env.s = state
env.render()

Как вы можете видеть, зная, что наше такси находится в позиции (4,2), а индекс этого пассажира = 3 и место высадки = 2, мы можем вывести, что закодированное состояние - 454. Для следующего эксперимента мы будем использовать это отправная точка, но прежде чем углубиться в нее, нам нужно ввести последний элемент: систему вознаграждения.

Система вознаграждения - это основная идея обучения с подкреплением: агент вознаграждается каждый раз, когда он действует хорошо, в противном случае он «наказывается» отрицательным вознаграждением. В этом конкретном случае встроенная таблица вознаграждения P создается сразу после создания env. Логика следующая:

  • если такси правильно принимает / высадку пассажира, оно награждается +20 баллами
  • если такси совершает незаконную посадку / высадку, оно наказывается -10 баллов
  • за каждый шаг, не включающий вышеперечисленные состояния, он теряет 1 балл

Итак, давайте посмотрим, как это выглядит для нашего состояния 454:

env.P[454]

Первое, на что следует обратить внимание, это то, что каждая запись нашей таблицы P представляет собой словарь со структурой {action: [(probability, nextstate, reward, done)]}

  • Действие: от 0 до 5.
  • Вероятность: в этом случае всегда 1
  • Nextstate: это состояние, которое возникает, если это действие выполнено.
  • награда: награда / штраф, связанный с этим действием.
  • done: если True, это означает, что серия окончена, в противном случае - нет.

Давайте попробуем прочитать наш результат: первая строка говорит нам, что если мы спустимся (действие 0 = юг), мы останемся в том же положении, так как у нас есть граница, следовательно, награда будет -1, а эпизод - нет. над; вторая строка, соответствующая action = north, приведет наше такси к позиции 354, но награда всегда будет -1, и эпизод не закончен. Аргументация одинакова для всех действий. Обратите внимание, что если действие происходит подъём или высадка, поскольку такси находится не в правильных местах (R, Y, G, B), как в последних двух строках (соответствующих действиям 4 и 5), оно получает штраф в размере -10.

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

Процедура поясняется следующим кодом:

import random
# setting yperparameters
lr = 0.1  #learning rate
gamma = 0.6 #discount factor
epsilon = 0.1 #trade-off between exploration and exploitation
for i in range(1, 1000):  #we will see 1000 episodes
    state = env.reset()  #let's reset our env
epochs, penalties, reward, = 0, 0, 0
    done = False
    
    while not done:
        if random.uniform(0, 1) < epsilon:
            action = env.action_space.sample() # explore action space
        else:
            action = np.argmax(q_table[state]) # exploit learned values
next_state, reward, done, info = env.step(action) 
        
        old_value = q_table[state, action]
        next_max = np.max(q_table[next_state])
        
        new_value = (1 - alpha) * old_value + lr * (reward + gamma * next_max)
        q_table[state, action] = new_value
if reward == -10:
            penalties += 1
state = next_state
        epochs += 1
        

Теперь представьте, что вам нужно решить, какое действие максимизирует вашу полезность (в переводе, что ведет к лучшему из возможных путей к вашему пассажиру в положении 3). Ваш ответ, вероятно, будет север, отсюда действие 1: действительно, это будет самый быстрый способ добраться до места (4,3), где находится ваш пассажир. Что скажет наш алгоритм?

np.argmax(q_table[454]) #argmax function return the position of the 
#maximum value among those in the vector examined

1

Как видите, функция argmax возвращает позицию 1, которая соответствует действию «север». Итак, для каждой позиции наша q-таблица сообщит нам, какое действие максимизирует текущие и будущие вознаграждения.