Aprendizaje por Refuerzo: Políticas.

Autor/a

Miguel Aron, Lab 25

Presentación

¿Que es aprendizaje por refuerzo?

Imaginemos que hay un organismo o agente en un entorno desconocido, que este entorno tiene diferente tipo de consecuencias dependiendo de la interacción del agente con su entorno. Y que el objetivo final del agente es maximizar sus recompensas cumulativas. El aprendizaje por refuerzo es el poder aprender una estrategia efectiva para completar el objetivo por medio de prueba y error, feedback.

Muchos de los conceptos principales se construyen de temas básicos como procesos de decisión Markovianos y el dilema de exploración-explotación.

Procesos de decisión Markovianos

Casi todos los problemas de aprendizaje por refuerzo pueden ser conceptualizados como un problema Markoviano de decisión. ¿a que nos referimos con esto? una propiedad “Markoviana” que todos nuestros estados deben de tener es el hecho de que el futuro depende del estado actual no de toda la historia.

\[ \begin{equation} \mathbb{P}\left[ S(t+1) | S(t) \right] = \mathbb{P} \left[ S(t+1) |S1,..., S(t)\right] \end{equation} \]

Que el futuro y el pasado con condicionalmente independientes según el presente, o que en el estado actual tenemos todo lo necesario para tomar una decisión futura.

Un proceso Markoviano tiene 5 elementos que son los siguientes:

  1. \(\color{red}{S}\) - un set de estados

  2. \(\color{blue}{A}\) - un set de acciones

  3. \(\color{yellow}{P}\) -una función de probabilidad que determina la transición

  4. \(\color{green}{R}\) -una función de recompensa

  5. \(\gamma\) - un factor de descuento para las recompensas futuras

Se incluye un ejemplo:

El dilema de exploración-explotación es un dilema que existe en muchas facetas de nuestra vida.Tomemos un ejemplo comunmente usado respecto a las posibles parejas que podríamos tener en nuestra vida y cuando sería el momento apropiado para “sentar cabeza”. Si tienes una pareja por mucho tiempo ya sabes el tipo de interacción que tienes, o la cantidad de momentos agradables vs problemas, que tienes con esta. Sin embargo, si cambias constantemente de pareja realmente no conoces del todo esta nueva persona, no sabes bien el tipo de interacción y lo bien o mal que te puedas llevar con estas parejas desconocidas. Sin embargo, el dilema consiste en como saber que la pareja con la que estas es la mejor opción y como saber cuando es tiempo de dejar de buscar otras parejas y elegir alguna. En esto consiste el dilema. Trasladandolo al campo de inteligencia artifical, si has aprendido toda la información acerca del ambiente, si sabemos absolutamente todas las parejas disponibles y el valor de cada una, la decisión es sencilla,solo tomar la de mayor valor. El problema existe cuando se tiene información incompleta, y necesitamos recolectar suficiente información para poder maximizar la recompensa y minimizar el riesgo. Una forma de cuantificar y hacer experimentación en el campo se refiere a los “bandidos”, que basicamente son máquinas al ser usadas tienen una cierta probabilidad para dar una cierta recompensa, el paradigma consiste en que el tiempo es limitado y solo puedes usar 1 en cada momento. La pregunta es: ¿Cual es la mejor estrategia para maximizar las recompensas?

Si tenemos a un agente que debe seleccionar que maquina usar. Lo primero que el agente debe de decidir es cual escoger.Aunque podriamos solo elegir aleatoriamente, queremos tener alguna creencia que justifique o describa cada decisión.Esto en la literatura se ejemplifica con el uso de una politica determinada por \(\color{blue}{\pi}\). También se necesita una función de valor por cada acción, al valor se le conoce como valor \(\color{green}{Q}\) . El valor \(\color{green}{Q}\) por tomar la acción \(\color{blue}{a \in A}\) en un tiempo determinado como \(t\) que no dará el valor de la recompensa en este tiempo \(r_t\) dada la acción elegida \(\color{blue}{a}\) . La fórmula es la siguiente:

\[\begin{align} \color{green}{Q}(\color{blue}{a}) = \mathbb{E} [\color{green}{r}_{t} | \color{blue}{a}_{t} = \color{blue}{a}] \end{align}\]

El objetivo sería el poder maximizar la recompensa la probabilidad optima de recompensa \[\theta^{}\] de la acción optima \[a^{}\] es:

\[\ \color{blue}{a_t}=\arg max_{a \in \mathcal{A}}\color{green}{Q}(\color{blue}{a})\]

Pero retomando lo aprendido acerca del dilema de exploración-exploración, si siempre tomamos la opción de mayor valor no sabemos bien cual es el valor del resto de las opciones o ¿de que nos estamos perdiendo? La solución más común a este problema es agregar un poco de aletoriedad al modelo. Determinar una probabilidad en la que se toma una acción diferente a la que tiene el valor máximo. Normalmente a esta estrategia se le llama “epsilon greedy”, que indica que 1 - \(\epsilon\) para \(\epsilon \in [0,1]\) seleccionamos la opción con mayor valor o “greedy”, en caso contrario con una probabilidad pequeña \(\epsilon\) elegimos una acción aleatoriamente (incluyendo la opción con mayor valor)

Otro concepto importante es REGRET , o “arrepentimiento”, básicamente es que tanto nos arrepentimos de seleccionar la acción elegida y no la opción optima en ese momento. Matematicamente la identificamos como:

\[ \mathcal{L}_T = \text{E} \left[ \sum_{t=1}^T (\theta^* - Q(a_t)) \right] \]

Se carga lo necesario.

Haz Clic para ver el Código
```{python}
import time
import numpy as np
import matplotlib.pyplot as plt
from scipy.signal import convolve as conv
from scipy.stats import beta

np.set_printoptions(precision=3)

def plot_choices(q, epsilon, choice_fn,c, n_steps=5000, rng_seed=1,init_a=1,init_b=1):
  np.random.seed(rng_seed)
  counts = np.zeros_like(q)
  asbeta = [init_a]*len(q)
  bsbeta = [init_b]*len(q)
  for t in range(n_steps):
    action = choice_fn(q, epsilon,t,counts,asbeta,bsbeta,c)
    counts[action] += 1
    if np.random.random() < q[action]:
      reward=1
    else:
      reward=0
    # Observe the reward for the chosen action
    asbeta[action] += reward
    bsbeta[action] += (1 - reward)



  fig, ax = plt.subplots()
  ax.bar(range(len(q)), counts/n_steps)
  ax.set(ylabel='% escogido', xlabel='acción', ylim=(0,1), xticks=range(len(q)))


def plot_multi_armed_bandit_results(results):
  fig, (ax1, ax2, ax3) = plt.subplots(ncols=3, figsize=(20, 4))
  ax1.plot(results['rewards'])
  ax1.set(title=f"Total Reward: {np.sum(results['rewards']):.2f}",
          xlabel='step', ylabel='reward')
  ax2.plot(results['qs'])
  ax2.set(xlabel='step', ylabel='value')
  ax2.legend(range(len(results['mu'])))
  ax3.plot(results['mu'], label='latent')
  ax3.plot(results['qs'][-1], label='learned')
  ax3.set(xlabel='action', ylabel='value')
  ax3.legend()

def plot_parameter_performance(labels, fixed, trial_rewards, trial_optimal):
  fig, (ax1, ax2) = plt.subplots(ncols=2, figsize=(16, 6))
  ax1.plot(np.mean(trial_rewards, axis=1).T)
  ax1.set(title=f'Average Reward ({fixed})', xlabel='step', ylabel='reward')
  ax1.legend(labels)
  ax2.plot(np.mean(trial_optimal, axis=1).T)
  ax2.set(title=f'Performance ({fixed})', xlabel='step', ylabel='% optimal')
  ax2.legend(labels)
def plot_state_action_values(env, value, ax=None):
  if ax is None:
    fig, ax = plt.subplots()
  for a in range(env.n_actions):
    ax.plot(range(env.n_states), value[:, a], marker='o', linestyle='--')
  ax.set(xlabel='States', ylabel='Values')
  ax.legend(['R','U','L','D'], loc='lower right')

def plot_quiver_max_action(env, value, ax=None):

  if ax is None:
    fig, ax = plt.subplots()
  X = np.tile(np.arange(env.dim_x), [env.dim_y,1]) + 0.5
  Y = np.tile(np.arange(env.dim_y)[::-1][:,np.newaxis], [1,env.dim_x]) + 0.5
  which_max = np.reshape(value.argmax(axis=1), (env.dim_y,env.dim_x))
  which_max = which_max[::-1,:]
  U = np.zeros(X.shape)
  V = np.zeros(X.shape)
  U[which_max == 0] = 1
  V[which_max == 1] = 1
  U[which_max == 2] = -1
  V[which_max == 3] = -1

  ax.quiver(X, Y, U, V)
  ax.set(
      title='Maximum value/probability actions',
      xlim=[-0.5, env.dim_x+0.5],
      ylim=[-0.5, env.dim_y+0.5],
  )
  ax.set_xticks(np.linspace(0.5, env.dim_x-0.5, num=env.dim_x))
  ax.set_xticklabels(["%d" % x for x in np.arange(env.dim_x)])
  ax.set_xticks(np.arange(env.dim_x+1), minor=True)
  ax.set_yticks(np.linspace(0.5, env.dim_y-0.5, num=env.dim_y))
  ax.set_yticklabels(["%d" % y for y in np.arange(0, env.dim_y*env.dim_x,
                                                  env.dim_x)])
  ax.set_yticks(np.arange(env.dim_y+1), minor=True)
  ax.grid(which='minor',linestyle='-')


def plot_heatmap_max_val(env, value, ax=None):

  if ax is None:
    fig, ax = plt.subplots()

  if value.ndim == 1:
      value_max = np.reshape(value, (env.dim_y,env.dim_x))
  else:
      value_max = np.reshape(value.max(axis=1), (env.dim_y,env.dim_x))
  value_max = value_max[::-1,:]

  im = ax.imshow(value_max, aspect='auto', interpolation='none', cmap='afmhot')
  ax.set(title='Maximum value per state')
  ax.set_xticks(np.linspace(0, env.dim_x-1, num=env.dim_x))
  ax.set_xticklabels(["%d" % x for x in np.arange(env.dim_x)])
  ax.set_yticks(np.linspace(0, env.dim_y-1, num=env.dim_y))
  if env.name != 'windy_cliff_grid':
      ax.set_yticklabels(
          ["%d" % y for y in np.arange(
              0, env.dim_y*env.dim_x, env.dim_x)][::-1])
  return im

def plot_rewards(n_episodes, rewards, average_range=10, ax=None):
  """
  Generate plot showing total reward accumulated in each episode.
  """
  if ax is None:
    fig, ax = plt.subplots()

  smoothed_rewards = (conv(rewards, np.ones(average_range), mode='same')
                      / average_range)

  ax.plot(range(0, n_episodes, average_range),
          smoothed_rewards[0:n_episodes:average_range],
          marker='o', linestyle='--')
  ax.set(xlabel='Episodes', ylabel='Total reward')


def plot_performance(env, value, reward_sums):
  fig, axes = plt.subplots(nrows=2, ncols=2, figsize=(16, 12))
  plot_state_action_values(env, value, ax=axes[0,0])
  plot_quiver_max_action(env, value, ax=axes[0,1])
  plot_rewards(n_episodes, reward_sums, ax=axes[1,0])
  im = plot_heatmap_max_val(env, value, ax=axes[1,1])
  fig.colorbar(im)

import ipywidgets as widgets       # interactive display
%config InlineBackend.figure_format = 'retina'
plt.style.use("bmh")
```

Epsilon greedy

Haz Clic para ver el Código
```{python}
def epsilon_greedy(q, epsilon,t,counts,asbeta,bsbeta,c):
  be_greedy = np.random.random()> epsilon
  if be_greedy:
    action = np.argmax(q)
  else:
    action = np.random.choice(len(q))
  return action

def UCB1(q, epsilon,t,counts,asbeta,bsbeta,c):
  action= max(range(len(q)),key=lambda x:q[x]+np.sqrt((2*np.log(t))/(1+counts[x])) )
  return action
def BayesianUCB(q, epsilon,t,counts,asbeta,bsbeta,c):
  action= max(
            range(len(q)),
            key=lambda x: asbeta[x] / float(asbeta[x] + bsbeta[x]) + beta.std(
                asbeta[x], bsbeta[x]) *c
        )
  return action

def ThompsonSampling(q, epsilon,t,counts,asbeta,bsbeta,c):
  samples= [np.random.beta(asbeta[x], bsbeta[x]) for x in range(len(q))]
  action= max(range(len(q)),key=lambda x: samples[x])
  return action

q = [0.01,0.89, 0.2, 0.8]
epsilon = 0.1

plot_choices(q, epsilon,epsilon_greedy ,c=1)
```

¿Cómo resolvemos este problema?

Existen varios acercamientos básicos que por falta de tiempo evitare exponer en esta libreta, pero que son la base de acercamientos un poco más complejos. Cuando nos referimos a términos como \(basado\) \(en\) \(modelos\), nos referimos a que existe un modelo del ambiente, que podemos conocer el ambiente o que se crea un modelo del ambiente dado el algoritmo. El término \(libre\) \(de\) \(modelo\) nos indica que no hay una dependencia en un modelo durante en el aprendizaje. También existen algoritmos \(en\) \(politica\) o \(sin\) politica, que determinan si el algoritmo va a usar outputs determinados de una función o samples de una política para el aprendizaje que sería el caso en los algoritmos \(on\) \(policy\), por otra parte los \(off\) \(policy\) se entrenan en una distribución de transiciones o episodios producidos por una política de comportamiento en lugar de la política optima objetivo.

Ahora que tenemos una política para tomar decisiones a cada momento, hablaremos de como podemos aprender de las recompensas de cada una de las acciones. Recordamos que tenemos una función de valor-acción llamada función Q, asociada con seguir una cierta politica \(\pi\) en un proceso de decisión Markoviano como:

\[\begin{equation} \color{green}Q^{\color{blue}{\pi}}(\color{red}{s},\color{blue}{a}) = \mathbb{E}_{\tau \sim P^{\color{blue}{\pi}}} \left[ \sum_t \gamma^t \color{green}{r_t}| s_0=\color{red}s,a_0=\color{blue}{a} \right] \end{equation}\]

donde \(\tau = \{\color{red}{s_0}, \color{blue}{a_0}, \color{green}{r_0}, \color{red}{s_1}, \color{blue}{a_1}, \color{green}{r_1}, \cdots \}\) Recordando la propiedad Markoviana que explica que el siguiente estado depende solo del estado actual y no en todos los estados que vinieron antes de el.

Tomando como el objetivo del agente, o el \(Goal\) es el maximizar la suma de las recompensas futuras \(descontadas\) \(\gamma\) especifica que tanto van a valer las recompensas futuras comparadas con la recompensa actual. Mientras que la politica mapea estados a acciones.

Las ecuaciones de Bellman son un set de ecuaciones que descomponen el valor de la acción, en el valor actual junto con los valores descontados futuros. \[ \begin{aligned} V(s_t) &= \mathbb{E}_t[G_t \vert S_t = s] \\ &= \mathbb{E}_t [r_{t+1} + \gamma r_{t+2} + \gamma^2 r_{t+3} + \dots \vert S_t=s] \\ &= \mathbb{E}_t [r_{t+1} + \gamma (R_{t+2} + \gamma r_{t+3} + \dots) \vert S_t = s] \\ &= \mathbb{E}_t [r_{t+1} + \gamma G_{t+1} \vert S_t = s] \\ &= \mathbb{E}_t [r_{t+1} + \gamma V(S_{t+1}) \vert S_t = s] \end{aligned} \]

Similarmente para el valor Q,

\[ \begin{aligned} Q(s_t) &= \mathbb{E}_t [r_{t+1} + \gamma V(S_{t+1}) \vert S_t = s, A_t = a] \\&= \mathbb{E}_t [r_{t+1} + \gamma\mathbb{E}_{\ a \sim \pi} Q^\pi(S_{t+1}, A_{t+1}) \vert S_t = s, A_t = a] \end{aligned} \]

A la que también podemos descomponer nuestra funcion de valor estado-accion usando esta ecuación de Bellman:

\[\begin{equation} \color{green}Q^{\color{blue}{\pi}}(\color{red}{s_t},\color{blue}{a_t}) = \mathbb{E}_{\color{blue}{\pi}} \mathbb{E}_{\color{red}{S_t+1}} [ \color{green}{r_{t+1}} + \gamma \color{green}{Q}^{\color{blue}{\pi}}(\color{red}{S_{t+1}}, \color{blue}{A_{t+1}}) \vert \color{red}{S_t = s}, \color{blue}{A_t = a} ] \end{equation}\]

Todo esto basado en la forma eficiente de valuar estimaciones con la Ecuación de expectación de Bellman:

\[\begin{equation} \color{green}Q^{\color{blue}{\pi}}(\color{red}{s},\color{blue}{a}) = \sum_{\color{red}{s'}\in \color{red}{\mathcal{S}}} \color{purple}{P}(\color{red}{s'} |\color{red}{s},\color{blue}{a}) \left( \color{green}{R}(\color{red}{s},\color{blue}{a}, \color{red}{s'}) + \gamma \color{green}V^{\color{blue}{\pi}}(\color{red}{s'}) \right) \end{equation}\]

donde \(\color{green}V^{\color{blue}{\pi}}\) es el valor \(\color{green}Q^{\color{blue}{\pi}}\) esperado para un estado particular \(\color{green}V^{\color{blue}{\pi}}(\color{red}{s}) = \sum_{\color{blue}{a} \in \color{blue}{\mathcal{A}}} \color{blue}{\pi}(\color{blue}{a} |\color{red}{s}) \color{green}Q^{\color{blue}{\pi}}(\color{red}{s},\color{blue}{a})\).

Ahora tendremos que repasar el principio de optimalización de Bellman. Que nos indica que el valor de un estado con una política optimizada es igual a la recompensa esperada de la mejor acción de ese estado.

Principio de optimización de Bellman \[\begin{equation} \color{green}{V}^{\color{green}{*}}(\color{red}{s},\color{blue}{a}) = \max_{\color{blue}{a'}} \color{green}{Q^{*}}(\color{red}{s'},\color{blue}{a'}) \end{equation}\] \[\begin{equation} \color{green}{Q}^{\color{green}{*}}(\color{red}{s},\color{blue}{a}) = \color{green}{R}(\color{red}{s},\color{blue}{a}) +\gamma \sum_{\color{red}{s'}\in \color{red}{\mathcal{S}}} \color{purple}{P}(\color{red}{s'} |\color{red}{s},\color{blue}{a})\end{equation}\] La política optima se obtiene maximizando la función de acción-estado. Se dice que la política es greedy con respecto de la función de valor optimo. \[\begin{equation} \color{blue}{\pi}^{\color{green}{*}}(\color{red}{s},\color{blue}{a}) = \max_{\color{blue}{a'}} \color{green}{Q^{*}}(\color{red}{s'},\color{blue}{a'}) \end{equation}\]

Y si solo estamos interesados en los valores optimos, sin tener en cuenta la expectativa de seguir una política podríamos solamente usar los valores máximos sin usar una política. Los valores óptimos \(V^*\) y \(Q^*\) son los mejores que podemos obtener.

\[\begin{equation} \color{green}V^{\color{green}{*}}(\color{red}{s},\color{blue}{a}) = \max_{\color{blue}{a'}} \color{green}{Q^{*}}(\color{red}{s'},\color{blue}{a'}) \end{equation}\]

\[\begin{equation} \color{green}Q^{\color{green}{*}}(\color{red}{s},\color{blue}{a}) = \color{green}{R}(\color{red}{s},\color{blue}{a}) +\gamma \sum_{\color{red}{s'}\in \color{red}{\mathcal{S}}} \color{purple}P(\color{red}{s'} |\color{red}{s},\color{blue}{a})\\ \color{green}V^{\color{green}{*}}(\color{red}{s},\color{blue}{a}) = \max_{\color{blue}{a \in \mathcal{A}}} (\color{green}{R}(\color{red}{s},\color{blue}{a}) +\gamma \sum_{\color{red}{s'}\in \color{red}{\mathcal{S}}} \color{purple}{P}(\color{red}{s'} |\color{red}{s},\color{blue}{a})) \end{equation}\]

\[\begin{equation} \color{green}Q^{\color{green}{*}}(\color{red}{s},\color{blue}{a}) = \color{green}{R}(\color{red}{s},\color{blue}{a}) +\gamma \sum_{\color{red}{s'}\in \color{red}{\mathcal{S}}} \color{purple}{P}(\color{red}{s'} |\color{red}{s},\color{blue}{a})\max_{\color{blue}{a'}} \color{green}{Q^{*}}(\color{red}{s'},\color{blue}{a'}) \end{equation}\]

Si tenemos información compelta de el ambiente, esto se convierte en un problema de planeación. Sin embargo, no siempre tenemos las probabilidades de cada acción dado los estados ni la cantidad de refuerzo dado el estado y la acción, por lo tanto no podemos solo aplicar las ecuaciones y tomarlo como un proceso de decisión Markoviano. Pero es una parte importante de las formas de solucionar estos problemas.

Iteración de política vs iteración de valor:

Iteración de política (usando la ecuación de Bellman) \[\begin{equation} \color{green}{Q}_{\color{green}{k}}(\color{red}{s},\color{blue}{a}) \leftarrow \color{green}{R}(\color{red}{s},\color{blue}{a}) +\gamma \sum_{\color{red}{s'}\in \color{red}{\mathcal{S}}} \color{purple}{P}(\color{red}{s'} |\color{red}{s},\color{blue}{a}) \sum_{\color{blue}{a'} \in \color{blue}{\mathcal{A}}} \color{blue}{\pi_{k-1}}(\color{blue}{a'} |\color{red}{s'}) \color{green}{Q_{k-1}}(\color{red}{s'},\color{blue}{a'}) \end{equation}\]

Iteración de valor(usando la ecuación de optimalización de Bellman) \[\begin{equation} \color{green}{Q}_{\color{green}{k}}(\color{red}{s},\color{blue}{a}) \leftarrow \color{green}{R}(\color{red}{s},\color{blue}{a}) +\gamma \sum_{\color{red}{s'}\in \color{red}{\mathcal{S}}} \color{purple}{P}(\color{red}{s'} |\color{red}{s},\color{blue}{a}) \max_{\color{blue}{a'}} \color{green}{Q_{k-1}}(\color{red}{s'},\color{blue}{a'}) \end{equation}\]

El \(aprendizaje\) \(por\) \(diferencia\) \(temporal\) temporal o Temporal Difference Learning es un algoritmo libre de modelos, que puede aprender de la experiencia de cada episodio, pero no necesariamente episodios completos pero de igual manera de episodios, esto se le llama “bootstrapping”. El algoritmo actualiza los objetivos con respecto a estimados actuales en lugar de solo la recompensa actual y la recompensa total. La idea principal de este algoritmo es que vamos a actualizar la función de valor hacia una recompensa estimada, u “objetivo” determinada por \(R_{t+1} + \gamma V(S_{t+1})\) el valor que le damos a el objetivo esta controlado por el “learning rate” identificado por el signo \[ \begin{aligned} V(S_t) &\leftarrow (1- \alpha) V(S_t) + \alpha G_t \\ V(S_t) &\leftarrow V(S_t) + \alpha (G_t - V(S_t)) \\ V(S_t) &\leftarrow V(S_t) + \alpha (R_{t+1} + \gamma V(S_{t+1}) - V(S_t)) \end{aligned} \] y nuestra estimación de acción-valor quedaría como: \[ Q(S_t, A_t) \leftarrow Q(S_t, A_t) + \alpha (R_{t+1} + \gamma Q(S_{t+1}, A_{t+1}) - Q(S_t, A_t)) \]

Sarsa vs Qlearning:

SARSA

Sarsa es un algoritmo que recibe su nombre gracias al proceso de actualizar el valor Q usando una secuencia de \(S_t\),\(A_t\),\(R_{t+1}\),\(S_{t+1}\),\(A_{t+1}\)… El algoritmo es el siguiente:

1.- Se iniciliza t=0

2.- Se inicia con el estado \(S_0\) y se escoge la acción \[A_0= \arg\max_{a \in \mathcal{A}} Q(S_t, a)\]; ε-greedy normalmente es aplicada

3.-En el tiempo \(t\), despues de aplicar la acción \(At\), se obtiene \(R_{t+1}\) y se llega al estado $S_{t+1}

4.- Se selecciona la siguiente acción de la misma forma que en el paso 2 \[A_{t+1}= \arg\max_{a \in \mathcal{A}} Q(S_t, a)\]

5.- Se actualiza la función del valor de Q de acuerdo a la siguiente fórmula: \[ Q(S_t, A_t) \leftarrow Q(S_t, A_t) + \alpha (R_{t+1} + \gamma Q(S_{t+1}, A_{t+1}) - Q(S_t, A_t)) \].

o escrito de otra forma: \[\begin{equation} \color{green}Q(\color{red}{s},\color{blue}{a}) \leftarrow \color{green}Q(\color{red}{s},\color{blue}{a}) +\alpha(\color{green}{r} + \gamma\color{green}{Q}(\color{red}{s'},\color{blue}{a'}) - \color{green}{Q}(\color{red}{s},\color{blue}{a})) \end{equation}\]

6.- t=t+1 y se repite desde el paso 1

Q-LEARNING Es un algoritmo que no necesita una política y que aprende antes de que se acabe el episodio. El algoritmo es el siguiente:

1.- Se iniciliza t=0

2.- Se inicia con el estado \(S_0\)

3.-En el tiempo \(t\), despues de aplicar la acción \(A_t= arg max Q(S_t,a)\) con ε-greedy

4.- Se obtiene \(R_{t+1}\) y se llega al estado \(S_{t+1}\)

5.- Se actualiza la función del valor de Q de acuerdo a la siguiente fórmula: \[ Q(S_t, A_t) \leftarrow Q(S_t, A_t) + \alpha (R_{t+1} + \gamma \max_{a \in \mathcal{A}} Q(S_{t+1}, a) - Q(S_t, A_t)) \] o escrito de otra forma: \[\begin{equation} \color{green}Q(\color{red}{s},\color{blue}{a}) \leftarrow \color{green}Q(\color{red}{s},\color{blue}{a}) +\alpha(\color{green}{r} + \gamma\max_{\color{blue}{a'}} \color{green}{Q}(\color{red}{s'},\color{blue}{a'}) - \color{green}{Q}(\color{red}{s},\color{blue}{a})) \end{equation}\]

6.- t=t+1 y se repite desde el paso 3


La diferencia principal entre Sarsa y Q-learning es que Q-learning no sigue la política actual para elegir la segunda acción \(A_{t+1}\). Estima la \(Q_*\) de acuerdo a los mejores valores de Q, pero cual acción \(a_*\) dirige al máximo Q. Sin importar la política actual.

Ahora implementaremos todos los algoritmos que hemos aprendido en un ambiente donde un agente tenga que tomar decisiones y pueda recibir consecuencias por sus acciones.

Ambiente Quentin’s World

Usaremos el ambiente llamado Quentin World.

Para este ambiente tenemos 100 estados y acciones posibles, desplazarse a cada uno de los cuadros contiguos a cada momento. El agente inicia en el cuadro verde, los cuadros rojos dan una recompensa negativa, no se puede salir del gridworld y moverse al cuadro amarillo le dará una recompensa. En este ambiente usaremos SARSA y Q-learning, además de unos algoritmos más avanzados y nuevos.

Inicializamos el ambiente y sus restricciones.

Haz Clic para ver el Código
```{python}
#Primero inicializamos el ambiente y codificamos sus restricciones
class world(object):
    def __init__(self):
        return

    def get_outcome(self):
        print("Abstract method, not implemented")
        return

    def get_all_outcomes(self):
        outcomes = {}
        for state in range(self.n_states):
            for action in range(self.n_actions):
                next_state, reward = self.get_outcome(state, action)
                outcomes[state, action] = [(1, next_state, reward)]
        return outcomes

class QuentinsWorld(world):

    def __init__(self):
        self.name = "QuentinsWorld"
        self.n_states = 100
        self.n_actions = 4
        self.dim_x = 10
        self.dim_y = 10
        self.init_state = 54
        self.shortcut_state = 64

    def toggle_shortcut(self):
      if self.shortcut_state == 64:
        self.shortcut_state = 2
      else:
        self.shortcut_state = 64

    def get_outcome(self, state, action):
        if state == 99: 
            reward = 0
            next_state = None
            return next_state, reward
        reward = 0  
        if action == 0: 
            next_state = state + 1
            if state == 98: 
                reward = 1
            elif state % 10 == 9: 
                next_state = state
            elif state in [11, 21, 31, 41, 51, 61, 71,
                           12, 72,
                           73,
                           14, 74,
                           15, 25, 35, 45, 55, 65, 75]:  
                reward = -1
        elif action == 1: 
            next_state = state + 10
            if state == 89:  
                reward = 1
            if state >= 90:  
                next_state = state
            elif state in [2, 12, 22, 32, 42, 52, 62,
                           3, 63,
                           self.shortcut_state,
                           5, 65,
                           6, 16, 26, 36, 46, 56, 66]:  
                reward = -1
        elif action == 2:  
            next_state = state - 1
            if state % 10 == 0:  # left border
                next_state = state
            elif state in [17, 27, 37, 47, 57, 67, 77,
                           16, 76,
                           75,
                           14, 74,
                           13, 23, 33, 43, 53, 63, 73]: 
                reward = -1
        elif action == 3:  
            next_state = state - 10
            if state <= 9:  
                next_state = state
            elif state in [22, 32, 42, 52, 62, 72, 82,
                           23, 83,
                           84,
                           25, 85,
                           26, 36, 46, 56, 66, 76, 86]:  
                reward = -1
        else:
            print("Accion debe ser entre 0 y 3.")
            next_state = None
            reward = None
        return int(next_state) if next_state is not None else None, reward
```

Se definen los algoritmos de aprendizaje.

Haz Clic para ver el Código
```{python}
def epsilon_greedy(q, epsilon):

  be_greedy = np.random.random() > epsilon
  if be_greedy:
    action = np.argmax(q)
  else:
    action = np.random.choice(len(q))

  return action
def q_learning(state, action, reward, next_state, value, params):
  prev_value = value[int(state), int(action)]
  if next_state is None or np.isnan(next_state):
      max_value = 0
  else:
      max_value = np.max(value[int(next_state)])
  delta = reward + params['gamma'] * max_value - prev_value
  value[int(state), int(action)] = prev_value + params['alpha'] * delta

  return value

def sarsa (state, action, reward, next_state, value, params):
  prev_value = value[int(state), int(action)]
  if next_state is None or np.isnan(next_state):

    policy_next_q = 0
  else:
    policy_action=epsilon_greedy(value[next_state], params['epsilon'])
    policy_next_q = value[next_state, policy_action]

  delta = reward + params['gamma'] * policy_next_q - prev_value
  value[int(state), int(action)] = prev_value + params['alpha'] * delta
  return value

def learn_environment(env, model_updater, planner, params, max_steps,
                      n_episodes, shortcut_episode=None):
  value = np.ones((env.n_states, env.n_actions))
  reward_sums = np.zeros(n_episodes)
  episode_steps = np.zeros(n_episodes)
  model = np.nan*np.zeros((env.n_states, env.n_actions, 2))
  for episode in range(n_episodes):
    if shortcut_episode is not None and episode == shortcut_episode:
      env.toggle_shortcut()
      state = 64
      action = 1
      next_state, reward = env.get_outcome(state, action)
      model[state, action] = reward, next_state
      value = q_learning(state, action, reward, next_state, value, params)

    state = env.init_state  
    reward_sum = 0
    for t in range(max_steps):
      action = epsilon_greedy(value[state], params['epsilon'])
      next_state, reward = env.get_outcome(state, action)
      reward_sum += reward
      value = q_learning(state, action, reward, next_state, value, params)
      model = model_updater(model, state, action, reward, next_state)
      value = planner(model, value, params)

      if next_state is None:
        break  # episode ends
      state = next_state

    reward_sums[episode] = reward_sum
    episode_steps[episode] = t+1

  return value, reward_sums, episode_steps
def learn_environment2(env, learning_rule, params, max_steps, n_episodes):
  value = np.ones((env.n_states, env.n_actions))
  reward_sums = np.zeros(n_episodes)
  episode_steps = np.zeros(n_episodes)

  for episode in range(n_episodes):
    state = env.init_state  
    reward_sum = 0

    for t in range(max_steps):
      action = epsilon_greedy(value[state], params['epsilon'])
      next_state, reward = env.get_outcome(state, action)
      value = learning_rule(state, action, reward, next_state, value, params)
      reward_sum += reward

      if next_state is None:
          break  
      state = next_state

    reward_sums[episode] = reward_sum
    episode_steps[episode] = t+1

  return value, reward_sums, episode_steps
```

Definimos los gráficos.

Haz Clic para ver el Código
```{python}
#@title Plotting Functions
# Imports

import numpy as np
import matplotlib.pyplot as plt
from scipy.signal import convolve as conv
def plot_state_action_values(env, value, ax=None):

  if ax is None:
    fig, ax = plt.subplots()

  for a in range(env.n_actions):
    ax.plot(range(env.n_states), value[:, a], marker='o', linestyle='--')
  ax.set(xlabel='Estados', ylabel='Valores')
  ax.legend(['N','D','I','S'], loc='lower right')


def plot_quiver_max_action(env, value, ax=None):

  if ax is None:
    fig, ax = plt.subplots()

  X = np.tile(np.arange(env.dim_x), [env.dim_y,1]) + 0.5
  Y = np.tile(np.arange(env.dim_y)[::-1][:,np.newaxis], [1,env.dim_x]) + 0.5
  which_max = np.reshape(value.argmax(axis=1), (env.dim_y,env.dim_x))
  which_max = which_max[::-1,:]
  U = np.zeros(X.shape)
  V = np.zeros(X.shape)
  U[which_max == 0] = 1
  V[which_max == 1] = 1
  U[which_max == 2] = -1
  V[which_max == 3] = -1

  ax.quiver(X, Y, U, V)
  ax.set(
      title='Maximo valor/acciones de probabilidad',
      xlim=[-0.5, env.dim_x+0.5],
      ylim=[-0.5, env.dim_y+0.5],
  )
  ax.set_xticks(np.linspace(0.5, env.dim_x-0.5, num=env.dim_x))
  ax.set_xticklabels(["%d" % x for x in np.arange(env.dim_x)])
  ax.set_xticks(np.arange(env.dim_x+1), minor=True)
  ax.set_yticks(np.linspace(0.5, env.dim_y-0.5, num=env.dim_y))
  ax.set_yticklabels(["%d" % y for y in np.arange(0, env.dim_y*env.dim_x, env.dim_x)])
  ax.set_yticks(np.arange(env.dim_y+1), minor=True)
  ax.grid(which='minor',linestyle='-')


def plot_heatmap_max_val(env, value, ax=None):
  if ax is None:
    fig, ax = plt.subplots()

  if value.ndim == 1:
      value_max = np.reshape(value, (env.dim_y,env.dim_x))
  else:
      value_max = np.reshape(value.max(axis=1), (env.dim_y,env.dim_x))
  value_max = value_max[::-1,:]

  im = ax.imshow(value_max, aspect='auto', interpolation='none', cmap='afmhot')
  ax.set(title='Valor maximo por estado')
  ax.set_xticks(np.linspace(0, env.dim_x-1, num=env.dim_x))
  ax.set_xticklabels(["%d" % x for x in np.arange(env.dim_x)])
  ax.set_yticks(np.linspace(0, env.dim_y-1, num=env.dim_y))
  if env.name != 'windy_cliff_grid':
      ax.set_yticklabels(
          ["%d" % y for y in np.arange(
              0, env.dim_y*env.dim_x, env.dim_x)][::-1])
  return im


def plot_rewards(n_episodes, rewards, average_range=10, ax=None):

  if ax is None:
    fig, ax = plt.subplots()

  smoothed_rewards = (conv(rewards, np.ones(average_range), mode='same')
                      / average_range)

  ax.plot(range(0, n_episodes, average_range),
          smoothed_rewards[0:n_episodes:average_range],
          marker='o', linestyle='--')
  ax.set(xlabel='Episodios', ylabel='Recompensa total')


def plot_performance(env, value, reward_sums):
  fig, axes = plt.subplots(nrows=2, ncols=2, figsize=(16, 12))
  plot_state_action_values(env, value, ax=axes[0,0])
  plot_quiver_max_action(env, value, ax=axes[0,1])
  plot_rewards(n_episodes, reward_sums, ax=axes[1,0])
  im = plot_heatmap_max_val(env, value, ax=axes[1,1])
  fig.colorbar(im)
```

Provemos cada uno de nuestros algoritmos

Parametros de la simulación.

Haz Clic para ver el Código
```{python}
np.random.seed(1)

params = {
  'epsilon': 0.05,  # epsilon-greedy policy
  'alpha': 0.5,  # learning rate
  'gamma': 0.8,  # temporal discount factor
  'k': 10,  # number of Dyna-Q planning steps
}

n_episodes = 500
max_steps = 1000

env = QuentinsWorld()
np.random.seed(1)
```

SARSA.

Evaluamos su rendimiento.

Haz Clic para ver el Código
```{python}
#SARSA

results = learn_environment2(env, sarsa, params, max_steps, n_episodes)
value_sarsa, reward_sums_sarsa, episodes_sarsa = results

plot_performance(env, value_sarsa, reward_sums_sarsa)
```

Q-Learning.

Veamos su rendimiento.

Haz Clic para ver el Código
```{python}
np.random.seed(1)
results = learn_environment2(env, q_learning, params, max_steps, n_episodes)
value_qlearning, reward_sums_qlearning, episode_steps_q = results
plot_performance(env, value_qlearning, reward_sums_qlearning)
```

Comparando Resultados.

En las 4 gráficas vemos el comportamiento del algoritmo. En la esquina izquierda superior tenemos los valores Q para cada una de las acciones en cada estado. Podemos ver que dependiendo del estado actual ciertas acciones tendrán un valor mayor o menor. Como al estar abajo o la lado del objetivo esta acción tendra un valor mayor, que ir a la derecha o izquierda. En la esquina superior derecha tenemos el valor máximo de cada acción, es decir si tomaramos la acción greedy,se tomaría la dirección marcada. Podemos ver en cada estado cual sería la acción óptima aprendida por el algortimo. En la esquina inferior izquierda vemos el refuerzo obtenido a través del tiempo por el algoritmo. Y en la esquina inferior derecha vemos de acuerdo a una escala de colores el valor de cada estado en el ambiente. A continuación comparamos los resultados de cada algoritmo.

Haz Clic para ver el Código
```{python}
fig, ax = plt.subplots()
ax.plot(reward_sums_qlearning, label='Q-learning')
ax.plot(reward_sums_sarsa, label='SARSA')
ax.set(xlabel='Episodes', ylabel='Total reward')
plt.legend(loc='lower right');
```

Haz Clic para ver el Código
```{python}
fig, (ax1, ax2) = plt.subplots(ncols=2, figsize=(16, 6))
plot_quiver_max_action(env, value_qlearning, ax=ax1)
ax1.set(title='Q-learning maximum value/probability actions')
plot_quiver_max_action(env, value_sarsa, ax=ax2)
ax2.set(title='SARSA maximum value/probability actions');
```

Referencias

Mattar, M. G. (2022, October 9). Course-content/w3d4_tutorial3.ipynb at main · NeuromatchAcademy/Course-Content. GitHub. Retrieved November 17, 2022 de https://github.com/NeuromatchAcademy/course-content/blob/main/tutorials/W3D4_ReinforcementLearning/W3D4_Tutorial3.ipynb

Neuromatch Academy. (2022). Github Repository. https://github.com/NeuromatchAcademy/course-content/blob/main/tutorials/W3D4_ReinforcementLearning/W3D4_Tutorial2.ipynb

Versión Interactiva.

Aquí puedes encontrar la versión alternativa.