Part VII: Reinforcement Learning
Reinforcement Learning (RL) no SOAR permite agentes alterar o seu comportamento durante el tempo, mudando dinamicamente valores numéricos de preferência. Este mecanismo de aprendizado é uma forma incremental de aprendizado que altera probabilisticamente o comportamento do agente.
1. Reinforcement Learning in Action
Antes que ver como é que o mecanismo funciona vai-se apresentar um exemplo chamado de agente Left-Right. O agente Left-Right é um agente simples que pode escolher entre se mover para a direita ou para a esquerda, sem embargo uma das direções será preferencial. Apos o agente decidir para que lado se vai movimentar ele vai receber recompensa. Usando RL o agente vai aprender rapidamente que se mover para a direita é preferível do que mover para a esquerda.
1.1 The Left-Right Agent
Este agente vai ter um operador de movimento o qual vai selecionar entre se mover para a direita ou para a esquerda. O agente terá preferências indiferentes entre as duas ações.
Inicialização:
O agente salva direções e a recompensa associada ao estado.
sp {propose*initialize-left-right
(state <s> ^superstate nil
-^name)
-->
(<s> ^operator <o> +)
(<o> ^name initialize-left-right)
}
sp {apply*initialize-left-right
(state <s> ^operator <op>)
(<op> ^name initialize-left-right)
-->
(<s> ^name left-right
^direction <d1> <d2>
^location start)
(<d1> ^name left ^reward -1)
(<d2> ^name right ^reward 1)}
Operador de movimento:
O agente pode-se mover em qualquer das duas direções. A direção escolhida é armazenada no estado.
sp { left-right*propose*move
(state <s> ^name left-right
^direction.name <dir>
^location start)
-->
(<s> ^operator <op> +)
(<op> ^name move
^dir <dir>)}
sp {left-right*rl*left
(state <s> ^name left-right
^operator <op> +)
(<op> ^name move
^dir left)
-->
(<s> ^operator <op> = 0)}
sp {left-right*rl*right
(state <s> ^name left-right
^operator <op> +)
(<op> ^name move
^dir right)
-->
(<s> ^operator <op> = 0)}
Recompensa:
Quando o agente escolhe uma direção, é proporcionada a respectiva recompensa.
sp {elaborate*reward
(state <s> ^name left-right
^reward-link <r>
^location <d-name>
^direction <dir>)
(<dir> ^name <d-name> ^reward <d-reward>)
-->
(<r> ^reward.value <d-reward>)}
Conclusão:
Quando o agente escolhe uma direção a tarefa terminou e o agente se suspende.
sp {elaborate*done
(state <s> ^name left-right
^location {<> start})
-->
(halt)}
1.2 Running the Left-Right Agent
De padrão o RL está desativado, para ativá-lo é necessário colocar as seguintes linhas no inicio do arquivo:
rl --set learning on
indifferent-selection --epsilon-greedyO primeiro comando ativa o RL, o segundo estabelece a política de pesquisa de exploração.
Apos fazer o primeiro Passo no SoarDebuger e executar o comando print --rl a saída é apresentada na seguinte imagem
O comando print --rl apresenta a preferência de inferência numérica em cada atualização do RL.
A saída apresenta que as duas instâncias do operador após 0 iterações é 0. Apos mas dois passos a saída do comando print --rl é
Apos aplicar o operador de movimentação o valor numérico de inferência para a regra associada a o movimento para a direita a sido atualizada a 0.3.
Depois é necessário “reiniciar” o Soar fazendo click no botão de “Init-Soar”, esto reiniciará o agente. O método como o RL aprende, é lembrando os valores de inferência entre cada execução, assim mostrando de nodo os valores de inferência eles não vão mudar desde a última execução.
2. Building a Learning Agent
Para fazer que um agente tenha as vantagens do RL geralmente são precisos três passos: 1) uso das regras RL, 2) implementar uma ou mais regras de recompensa e 3) habilitar o mecanismo de aprendizado reforçado (RL).
2.1 RL Rules
As regras que podem ser atualizadas pelo mecanismo de RL devem ter uma estrutura padrão como é apresentado a seguir:
sp {my*proposal*rule
(state <s> ^operator <op> +
^condition <c>)
-->
(<s> ^operator <op> = 2.3)}
O nome da regra pode ser qualquer, e as condições no lado esquerdo (LHS ou Left-Hand Side) podem ter qualquer forma. Sem embargo o lado direito (RHS ou Right-Hand Side) deve ter a seguinte forma:
(<s> ^operator <op> = number)
Mais exatamente o lado direito só pode ter uma ação, e number deve ser um valor numérico constante, como 2.3 no exemplo. Qualquer outra ação deve ser colocada em uma regra separada.
Voltando ao problema do Water Jug o objetivo é que o agente aprenda a melhor forma de utilizar as ações de empty, fill, pour. Assim devem-se modificar os operadores dessas regras para habilitar a utilização do RL.
O processo de modificar as regras será dividido em dois, (1) modificar as regras de proposição existentes e (2) criar novas regras de RL.
A primeira parte é fácil, é só eliminar o símbolo de “=” na proposição da regra. Assim as três propostas ficam:
sp {water-jug*propose*empty
(state <s> ^name water-jug
^jug <j>)
(<j> ^contents > 0)
-->
(<s> ^operator <o> +)
(<o> ^name empty
^empty-jug <j>)}
sp {water-jug*propose*fill
(state <s> ^name water-jug
^jug <j>)
(<j> ^empty > 0)
-->
(<s> ^operator <o> +)
(<o> ^name fill
^fill-jug <j>)}
sp {water-jug*propose*pour
(state <s> ^name water-jug
^jug <i> { <> <i> <j> })
(<i> ^contents > 0 )
(<j> ^empty > 0)
-->
(<s> ^operator <o> +)
(<o> ^name pour
^empty-jug <i>
^fill-jug <j>)}
Agora serão escritas as novas regras que detetarão as preferências aceitáveis e calcularão suas preferências numéricas.
Para o caso do Water Jug são requeridas (3*2*4*6) = 144 RL regras para representar completamente o problema, para gerar todas as posivel posibilidades é utilizado o comando de gp para gerar todas as regras que são necessarias.
gp {rl*water-jug*empty
(state <s> ^name water-jug
^operator <op> +
^jug <j1> <j2>)
(<op> ^name empty
^empty-jug.volume [3 5])
(<j1> ^volume 3
^contents [0 1 2 3])
(<j2> ^volume 5
^contents [0 1 2 3 4 5])
-->
(<s> ^operator <op> = 0)
}
gp {rl*water-jug*fill
(state <s> ^name water-jug
^operator <op> +
^jug <j1> <j2>)
(<op> ^name fill
^fill-jug.volume [3 5])
(<j1> ^volume 3
^contents [0 1 2 3])
(<j2> ^volume 5
^contents [0 1 2 3 4 5])
-->
(<s> ^operator <op> = 0)
}
gp {rl*water-jug*pour
(state <s> ^name water-jug
^operator <op> +
^jug <j1> <j2>)
(<op> ^name pour
^empty-jug.volume [3 5])
(<j1> ^volume 3
^contents [0 1 2 3])
(<j2> ^volume 5
^contents [0 1 2 3 4 5])
-->
(<s> ^operator <op> = 0)
}
2.2 Reward Rules
A regra de recompensa é igual a qualquer outra regra no SOAR, só que ela modifica a estrutura de reward-link do estado para refletir a recompensa associada ao operador atual do agente. Os valores de recompensa devem ser armazenados no elemento value do atributo reward do identificador reward-link ou seja, em (estate.reward-link.reward.value).
No casso do agente Water-Jug, será dada a recompensa somente quando o agente atinga o estado desejado. Assim deve-se modificar a regra de detecção de estado desejado.
sp {water-jug*detect*goal*achieved
(state <s> ^name water-jug
^jug <j>
^reward-link <rl>)
(<j> ^volume 3 ^contents 1)
-->
(write (crlf) |The problem has been solved.|)
(<rl> ^reward.value 10)
(halt)}
O programa encontro o algoritmo ótimo em quatro iterações.
A saída do Soar é:
Os valores de preferência de inferência numérica RL são:
3. Further Exploration
As explicações adicionais tem que ver com a chamada política de exploração. Se é executado o comando de “indifferent-selection --stats” a saída será:
Neste casso apresenta uma política de exploração chamada epsilon-greedy. SOAR tem implementado cinco políticas de exploração: boltzmann, epsilon-greedy , softmax, first e last. Pode-se modificar a política usando o seguinte comando.
indifferent-selection --policy_name
Onde policy_name é o nome da política.
A política mais adequada para o RL é a de epsilon-greedy.