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

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

Ограничения

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

Как видно, в позиции [4,5] стоит флаг. Если мы хотим решить эту задачу с помощью Q-таблицы, агент должен сначала войти в блок [4,5] из единственной доступной позиции [5,5], а затем двигаться к цели, выйдя из этой ниши из единственной доступной позиции. доступное [5,5] положение. Следовательно, в оптимальном пути должны быть «U» и «D» в позиции [5,5]; таким образом, когда агент входит в [5,5], он идет вверх, а после повторного достижения он идет вниз и следует по следу к конечной цели.

В заключение с 2-й таблицей Q-обучения проблема не может быть решена; но есть два способа преодолеть это: во-первых, использовать нейронную сеть, чтобы она напоминала более сильную Q-таблицу; мы будем решать эту проблему в соответствии с Deep Q-learning в этом репозитории. Второе решение состоит в расширении состояний в соответствии с количеством флагов, поэтому агент будет находиться в новой Q-таблице после достижения флага [4,5].

В конце концов, мы считаем замечательным проиллюстрировать процесс обучения агента, чтобы увидеть, как он борется на каждой итерации и как каждая итерация занимает больше времени по сравнению с проблемой, представленной в первой части. На приведенном ниже графике показана кривая обучения задачи Q-обучения с 16 различными наборами переменных для альфы и лямбда, для каждого из которых потребовалось 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","B","W","W","W","W","W","W","W","W"],
                ["W","W","W","F","W","B","W","W","W","W"],
                ["F","W","W","W","W","B","F","W","W","W"],
                ["B","B","W","B","B","W","B","W","W","W"],
                ["W","F","B","W","B","F","B","B","B","W"],
                ["W","W","B","W","B","W","W","W","W","W"],
                ["W","W","W","W","W","W","W","W","W","W"],
                ["W","F","W","W","W","W","B","B","B","B"],
                ["W","B","B","B","B","B","W","W","W","W"],
                ["W","W","W","W","W","W","W","B","F","W"]
                ],[0,0],[9,9])
        
        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()

Как видно, многие алгоритмы даже не смогли найти тонкий путь. Однако, среди прочего, тот, у которого альфа = 0,1 и лямбда = 0,75, показывает лучшую производительность. Однако агенту потребуется около 3000 шагов, чтобы достичь цели, помеченной.

По сравнению с предыдущей проблемой. Общее количество шагов в каждой итерации в этой намного больше; сравните 3000 с 500. И как было сказано, ответ не может быть найден в 2D среде, поэтому для решения задачи следует рассматривать 3D Q-таблицу с использованием флажков как еще одно измерение.

Объекты

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

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

#...
    elif self.env[new_agent[0]][new_agent[1]]=='O':
            #find position of the moved object:
            new_obj = self.new_position(action,new_agent)
            
            #cases in which object acts as a block
            
            #when the object goes out of the board
            if new_obj[0]<0 or new_obj[1]<0 or new_obj[0]>=env_0 or new_obj[1]>=env_1:
                return 10.0*self.env_lose
            
            #when the object cannot move because of a block 
            if self.env[new_obj[0]][new_obj[1]]=='B':
                return 10.0*self.env_lose
            
            #we will not be punishing the agent if the object goes on a flag or target;
            #because maybe the agent is trying to open a new way
            
            #now that we are sure that object moves to a valid position
            #we can determine blocks around the object's old position
            #and its new position to determine how we want to punish the agent
            score1 = self.score_block(new_agent)
            score2 = self.score_block(new_obj)
            
            #if the object is moved to a better position
            if score2>=score1:
                return (1.0+(score2-score1)/5)/self.env_size
            elif score2+1==score1:
                return (1.0+2(score2-score1)/5)/self.env_size
            elif score2+2==score1:
                return (1.0+4(score2-score1)/5)/self.env_size
            else:
                #punish hardly if new new position looks terrible
                return (-0.5*(score2-score1)/5)/self.env_size

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

def score(self,position):
        # This function scores based on the souranding enviornment
        score = 0
        for p in ps:
            if p[0]>=self.env_0: score+=1
            elif p[0]<0: score+=1
            elif p[1]>=self.env_1: score+=1
            elif p[1]<0: score+=1
            elif self.env[p[0]][p[1]]=='B': score+=1
            elif self.env[p[0]][p[1]]=='O': score+=1
        
        return score

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

Например, за ход ниже агент наказывается 0,8; Обратите внимание, что в этом алгоритме свободный ход наказывается 1.0.

Таким образом, полная функция вознаграждения выглядит следующим образом.

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':
            #find position of the moved object:
            new_obj = self.new_position(action,new_agent)
            
            #cases in which object acts as a block
            
            #when the object goes out of the board
            if new_obj[0]<0 or new_obj[1]<0 or new_obj[0]>=env_0 or new_obj[1]>=env_1:
                return 10.0*self.env_lose
            
            #when the object cannot move because of a block 
            if self.env[new_obj[0]][new_obj[1]]=='B':
                return 10.0*self.env_lose
            
            #we will not be punishing the agent if the object goes on a flag or target;
            #because maybe the agent is trying to open a new way
            
            #now that we are sure that object moves to a valid position
            #we can determine blocks around the object's old position
            #and its new position to determine how we want to punish the agent
            score1 = self.score_block(new_agent)
            score2 = self.score_block(new_obj)
            
            #if the object is moved to a better position
            if score2>=score1:
                return (1.0+(score2-score1)/5)/self.env_size
            elif score2+1==score1:
                return (1.0+2(score2-score1)/5)/self.env_size
            elif score2+2==score1:
                return (1.0+4(score2-score1)/5)/self.env_size
            else:
                #punish hardly if new new position looks terrible
                return (-0.5*(score2-score1)/5)/self.env_size
        else:
            return 1.0/self.env_size

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

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

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

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

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