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

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

Reward function: r(s, a) -> r
Policy function: δ(s, a) -> s'

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

Q-обучение

Одним из популярных подходов к RL является Q-learning, метод, основанный на ценностях, который стремится найти наилучшее действие в данном состоянии, оценивая максимальное ожидаемое будущее вознаграждение за каждое действие. В этом подходе агент пытается извлечь уроки из своей среды, передавая свой предыдущий опыт функции вознаграждения. Имея скорость обучения α и скорость дисконтирования γ, предыдущий опыт обновляется за счет новых действий и предписания функции вознаграждения [2].

Q(s, a) <- Q(s, a) + α[r + γ max Q(s’, a’) – Q(s, a)]
                              a’

Другими словами, таблица создается для каждой пары состояний и действий, а затем каждый регистр инициализируется нулем — пока просто считайте, что функция вознаграждения неположительна — и будет обновляться на протяжении всего этапа обучения реализации алгоритма [3] .

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

Проблема лабиринта

Цель задачи лабиринта — или крысы в ​​лабиринте — найти путь от начальной точки к цели, пока путь не заблокирован никаким препятствием. В изучаемой версии есть некоторые флаги, которые следует собрать перед достижением цели. Кроме того, в лабиринте могут быть объекты, движение к которым приводит к их отталкиванию; некоторые толчки могут привести к блокировке допустимого пути, а некоторые другие толчки могут открыть путь к действительному пути. Следовательно, при разработке функции вознаграждения необходимо тщательное рассмотрение. Задача лабиринта представлена ​​​​ниже, где агент обозначен синим цветом, цель - зеленым, а флажки - красным; Блоки представлены черными квадратами.

Теперь формализуем задачу.

I. Государства

Состояния этой задачи состоят из любого положения агента в среде лабиринта относительно его накопленных флагов. Это означает, что для n*m лабиринта с p флагами пространство состояний состоит из n*m*p состояний, из которых только одно является целевым состоянием. Обратите внимание, что в этом описании исключены толкаемые объекты (чтобы рассмотреть их, мы можем сосредоточиться на позициях, в которые они могут перемещаться, таким образом, для каждого объекта член, такой как o_i, будет умножен на исходное пространство).

Мы можем уменьшить количество состояний, исключив флаги в пространстве и изменив функцию вознаграждения таким образом, чтобы агент получал вознаграждение за каждый заработанный флаг и не мог выиграть игру, пока не будут заработаны все флаги. При этой попытке размер пространства уменьшится до n*m.

def take_a_step(self):
        action_set = {"L":0,"U":0,"R":0,"D":0}

        if self.agent[0] == 0: action_set.pop("U", None)
        if self.agent[0] == self.env_0-1: action_set.pop("D", None)
        if self.agent[1] == 0: action_set.pop("L", None)
        if self.agent[1] == self.env_1-1: action_set.pop("R",None)

        #...

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

II. Целевое состояние

Как уже отмечалось, целевое пространство с первым определением состояний состояния, в котором агент находится в целевой позиции и является п-1 позицией 3-го измерения; это означает, что все флаги достигнуты.

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

Функция вознаграждения

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

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

  def reward(self,action):
        new_agent = self.find_position(action)
            
        if self.visited[new_agent[0]][new_agent[1]]>=2:
            return (-0.5*self.visited[new_agent[0]][new_agent[1]])/self.env_size
        elif self.visited[new_agent[0]][new_agent[1]]>=1:
            return 0.9/self.env_size

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

#...  
      elif self.env[new_agent[0]][new_agent[1]]=="B":
            return 10.0*self.env_lose

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

#...
    elif self.env[new_agent[0]][new_agent[1]]=="F":
            self.achive_flag(new_agent)

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

#...
     elif self.env[new_agent[0]][new_agent[1]]=="T" and len(self.flags)==0:
            return 1*self.env_size

Кроме того, игра исключается, когда агент приближается к цели или когда агент не может оценить ценную будущую награду — ценная награда устанавливается больше -1.

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

Кроме того, обратите внимание, как каждое вознаграждение/наказание пропорционально размеру среды, чтобы иметь однородное распределение значений в Q-таблице любой проблемы.

def reward(self,action):
        new_agent = self.find_position(action)

        if self.env[new_agent[0]][new_agent[1]]=="B":
            return 10.0*self.env_lose
        elif self.env[new_agent[0]][new_agent[1]]=="F":
            self.achive_flag(new_agent)
            return 2*(self.env_size/self.flag_num)
        elif self.env[new_agent[0]][new_agent[1]]=="T" and len(self.flags)==0:
            return 1*self.env_size
        elif self.visited[new_agent[0]][new_agent[1]]>=2:
            return (-0.5*self.visited[new_agent[0]][new_agent[1]])/self.env_size
        elif self.visited[new_agent[0]][new_agent[1]]>=1:
            return 0.9/self.env_size
        elif self.env[new_agent[0]][new_agent[1]]=='O':
            #...
        else:
            return 1.0/self.env_size

Выбор действия

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

max_reward = max(action_set.values())
        random.choice([k for (k, v) in action_set.items()
                                        if v >= 0.8 * max_reward])

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

Обучение

Во-первых, мы обучаем агента в легкой среде. Ниже показан лабиринт 9*9 с агентом и целью, расположенными в точках [0,0] и [4,4] соответственно, с шестью флажками, распределенными по доске.

Далее первый лабиринт решается по алгоритму и с помощью введенной функции вознаграждения. Четыре разные переменные для альфа и лямбда выбираются из набора {1, 0,75, 0,5, 0,1} — всего девять. И для каждого лабиринт разгадывается через 1000 эпох

n_epochs = 1000
fig = plt.figure(figsize=(20, 15))
fig.subplots_adjust(hspace=0.3, wspace=0.2)
plot_num = 0
for alpha_ in [1,0.75,0.5,0.1]:
    for lambda_ in [1,0.75,0.5,0.1]:
        plot_num+=1
        tmaze = maze([
                ["W","W","W","W","B","W","F","W","W"],
                ["W","W","B","W","W","W","W","W","B"],
                ["F","W","B","B","F","B","B","W","W"],
                ["B","W","W","W","W","W","W","W","W"],
                ["B","W","B","B","W","W","W","W","B"],
                ["W","W","B","W","B","W","W","W","F"],
                ["W","F","W","W","B","W","W","B","W"],
                ["B","W","B","W","W","B","W","W","W"],
                ["B","B","B","B","B","W","W","W","F"]
                ],[0,0],[4,4])
        
        tmaze.train(n_epochs,alpha_,lambda_)
        ax = fig.add_subplot(4, 4, plot_num)
        ax.set_title("alpha: "+str(alpha_)+", lambda: "+str(lambda_))
        ax.plot(tmaze.train_curve)
        
plt.show()

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

{'R': 0.004, 'D': 0.004}
>-> D
{'U': 0.0036400000000000004, 'R': 0.004, 'D': 0.004}
>-> R
{'L': 0.0036400000000000004, 'U': 0.004, 'R': -1.0, 'D': 0.004}
>-> U
{'L': 0.0036400000000000004, 'R': 0.004, 'D': 0.0036400000000000004}
>-> R
{'L': 0.0036400000000000004, 'R': 0.004, 'D': -1.0}
>-> R
{'L': 0.0036400000000000004, 'R': -1.0, 'D': 0.004}
>-> D
{'L': -1.0, 'U': 0.0036400000000000004, 'R': 0.004, 'D': -1.0}
>-> R
{'L': 0.0036400000000000004, 'U': -1.0, 'D': 0.004}
>-> D
{'L': -1.0, 'U': 0.0036400000000000004, 'D': 0.004}
>-> D
{'L': 0.004, 'U': 0.0036400000000000004, 'D': 2.5}
>-> D
R , 0.00 |R , 0.00 |R , 0.00 |D , 0.00 |L , 0.00 |
R , 0.00 |U , 0.00 |L , 0.00 |R , 0.00 |D , 0.00 |
U , 0.00 |L , 0.00 |L , 0.00 |L , 0.00 |D , 0.00 |
U , 0.00 |L , 0.00 |L , 0.00 |L , 0.00 |D , 2.50 |
U , 0.00 |L , 0.00 |L , 0.00 |L , 0.00 |L , 0.00 |
------------------------------------------------------------------
.
.
.
------------------------------------------------------------------
R , 0.03 |R , 0.03 |R , 0.03 |D , 0.03 |L , 0.00 |
R , 0.01 |U , 0.01 |L , 0.00 |R , 0.03 |D , 0.03 |
U , 0.00 |L , 0.00 |L , 0.00 |L , 0.00 |D , 0.12 |
U , 0.00 |L , 0.00 |L , 0.00 |L , 0.00 |D , 0.99 |
U , 0.00 |L , 0.00 |L , 0.00 |L , 0.00 |L , 0.00 |

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

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

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

Что дальше?

Будет решена еще одна проблема и обсуждены ограничения этого подхода. Кроме того, вводятся объекты, которые агент может перемещать, когда он движется к ним. Эти объекты — две стороны меча; их можно использовать, чтобы открыть путь или полностью его заблокировать. Таким образом, функция вознаграждения должна быть точно определена. Перейдите к части 2 по этой ссылке.

Обновленную версию кода можно найти в связанном репозитории.

Рекомендации

[1] Ричард С. Саттон и Эндрю Г. Барто, Обучение с подкреплением: введение

[2] Т. Митчелл, Машинное обучение.

[3] Сатиш Кумар, Простое обучение с подкреплением с использованием Q-таблиц.