Ray RLlib: Aprendizaje por Refuerzo

6 July 2024| Tags: Machine Learning, ML, Cluster ML, Ray, RLlib, Aprendizaje por Refuerzo, RL

Introducción

En este último post de la serie sobre el framework Ray vamos a profundizar en el uso de su librería RLlib (Reinforcement Learning Library) para entrenar algoritmos de aprendizaje por refuerzo en entornos simulados y reales y como utilizar toda la potencia de Ray para entrenarlos, tunearlos y servirlos.

Ray es un framework de Python que permite la ejecución de aplicaciones Python en paralelo y de forma distribuida. Está diseñado principalmente para ser usado en aplicaciones de Machine Learning y AI, pero también se puede usar en cualquier aplicación Python que requiera computación distribuida.

En el primer post de la serie Cluster Ray vimos como desplegar un cluster de Ray en una sola máquina con Dockers (Docker Compose) y explicamos por encima las distintas partes del framework: Ray Data, Ray Train, Ray Tune, Ray Serve y como se puede usar ésta última, Ray Serve, para servir un modelo de Hugging Face que traducía textos del inglés al francés.

Y en el siguiente Cluster Ray II desplegamos un cluster de Ray on premise en varias máquinas de producción y como entrenar y servir los modelos de machine learning que necesitan nuestros productos en él.

RRLib Aprendizaje por Refuerzo con Ray

El aprendizaje por refuerzo (RL) es un área del aprendizaje automático (ML) inspirada en el aprendizaje por ensayo y error utilizado por los humanos, donde un Agente aprende a tomar decisiones óptimas en un entorno para maximizar una recompensa acumulada. A diferencia del aprendizaje supervisado, donde se le proporciona al Agente un conjunto de datos de entrada y salida, en el RL, el Agente aprende por sí mismo a través de la interacción con el entorno.

Componentes clave del aprendizaje por refuerzo:

  • Agente: La entidad que toma decisiones e interactúa con el entorno.
  • Entorno: El mundo en el que opera el Agente, proporcionando al Agente información de estado y recompensas por sus acciones.
  • Acciones: Las opciones disponibles para el Agente en cada estado.
  • Estado: La representación de la información relevante del entorno para el Agente en un momento dado.
  • Recompensa: La señal de retroalimentación que indica al Agente qué tan buena fue su acción.
  • Función de valor: Estima la recompensa esperada a largo plazo a partir de un estado dado.
  • Política: Define la probabilidad de que el Agente tome cada acción en cada estado.

Funcionamiento del entrenamiento de un algoritmo de aprendizaje por refuerzo:

Train

  • 1.- El Agente observa el estado actual del entorno.
  • 2.- Selecciona una acción según su política actual (que irá refinando en el entrenamiento).
  • 3.- El entorno ejecuta la acción y proporciona al Agente un nuevo estado y una recompensa.
  • 4.- El Agente actualiza su función de valor y su política en función de la recompensa recibida.
  • 5.- Los pasos 1-4 se repiten hasta que el Agente alcanza un estado terminal o se cumple un criterio de parada.

Cuando termina el entrenamiento el Agente es capaz de “moverse” por el entorno y según el estado del mismo decidir que acción debe tomar para resolver el problema (maximizar la recompensa acumulada).

Ray RLlib es una biblioteca de aprendizaje por refuerzo escalable y distribuida que proporciona una implementación de alto nivel de algoritmos de aprendizaje por refuerzo.

RLlib se integra con Ray para proporcionar una API simple y escalable para entrenar y evaluar modelos de aprendizaje por refuerzo. RLlib incluye implementaciones de algoritmos de aprendizaje por refuerzo de vanguardia y herramientas para evaluar y analizar los resultados de los entrenamientos de esos aprendizajes.

Ejemplos de uso de RLlib

Frozen Lake

El problema Frozen Lake es un entorno clásico de aprendizaje por refuerzo (RL). Este entorno simula un escenario en el que un Agente debe navegar por un lago helado para llegar a un objetivo. El lago está formado por casillas, algunas de las cuales son seguras y otras son agujeros de hielo. El Agente puede moverse en las cuatro direcciones cardinales (arriba, abajo, izquierda y derecha). Si el Agente se mueve a una casilla con un agujero de hielo, cae en él y recibe una recompensa negativa. El objetivo del Agente es llegar a la casilla objetivo en el menor número de pasos posible, evitando caer en los agujeros de hielo.

En los algoritmos de aprendizaje por refuerzo de RRlib se puede usar entornos definidos con la biblioteca Gymnasium de OpenAI.

En Gymnasium se pueden encontrar muchos entornos de aprendizaje por refuerzo predefinidos, como el Frozen Lake, que se pueden usar para entrenar Agentes de aprendizaje por refuerzo.

Vamos a redefinir el entorno Frozen Lake de Gymnasium para entender los conceptos del aprendizaje por refuerzo y entrenar un Agente con RRlib.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
import numpy as np
import gymnasium as gym

class MyFrozenLake(gym.Env):
    def __init__(self, env_config=None):
        if env_config is None:
            env_config = dict()

        # El entorno Frozen Lake es un cuadrado de tamaño size x size 
        self.size = env_config.get("size", 4)
        # El espacio de observaciones es un espacio discreto de tamaño 16 que indica en qué casilla está el jugador
        # [0, 1,   2,  3]
        # [4, 5,   6,  7]
        # [8, 9,  10, 11]
        # [12, 13, 14, 15]        
        self.observation_space = gym.spaces.Discrete(self.size*self.size)
        # El espacio de acciones es un espacio discreto de tamaño 4 que indica la dirección en la que 
        # se puede mover el jugador        
        self.action_space = gym.spaces.Discrete(self.size)

    def reset(self,seed,options):
        self.player = (0, 0) # El Agente empieza en la casilla superior izquierda
        self.goal = (self.size-1, self.size-1)  # El objetivo es llegar a la casilla inferior derecha

        # Los agujeros en el hielo son casillas fijas en el tablero
        if self.size == 4:
            self.holes = np.array([
                [0, 0, 0, 0],
                [0, 1, 0, 1],
                [0, 0, 0, 1],
                [1, 0, 0, 0] 
            ])
        else:
            raise Exception("Frozen Pond only supports size 4")

        return self.observation(), {}

    def observation(self):
        # La observación es la casilla en la que se encuentra el jugador
        return self.size*self.player[0] + self.player[1]

    def reward(self):
        # La recompensa es 1 si el jugador llega al objetivo y 0 en caso contrario
        return int(self.player == self.goal)

    def done(self):
        # El episodio termina si el jugador llega al objetivo o cae en un agujero
        return self.player == self.goal or bool(self.holes[self.player] == 1)

    def is_valid_loc(self, location):
        return 0 <= location[0] < self.size and 0 <= location[1] < self.size and not self.holes[location]

    def step(self, action):
        # Mover al jugador en la dirección especificada por la acción (0=izquierda, 1=abajo, 2=derecha, 3=arriba) 
        # que le dejará en una casilla (estado) nueva
        if action == 0:   # left
            new_loc = (self.player[0], self.player[1]-1)
        elif action == 1: # down
            new_loc = (self.player[0]+1, self.player[1])
        elif action == 2: # right
            new_loc = (self.player[0], self.player[1]+1)
        elif action == 3: # up
            new_loc = (self.player[0]-1, self.player[1])
        else:
            raise ValueError("Action must be in {0,1,2,3}")

        if self.is_valid_loc(new_loc):
            self.player = new_loc

        # Devuelve observation/reward/done/info
        return self.observation(), self.reward(), self.done(), {"player" : self.player, "goal" : self.goal}, {}

    def render(self):
        # Imprime el tablero en la consola
        for i in range(self.size):
            for j in range(self.size):
                if (i,j) == self.player:
                    print("🧑", end="")
                elif (i,j) == self.goal:
                    print("⛳️", end="")
                elif self.holes[i,j]:
                    print("🕳", end="")
                else:
                    print("🧊", end="")
            print()

Podemos “movernos” dentro del entorno con el siguiente código:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

lake = MyFrozenLake()
lake.reset({},{})
lake.render()

obs, rewards, done, _, _ = lake.step(2)
print(obs, rewards, done)
lake.render()
obs, rewards, done, _, _= lake.step(2)
print(obs, rewards, done)
lake.render()
obs, rewards, done, _, _= lake.step(1)
print(obs, rewards, done)
lake.render()
obs, rewards, done, _, _= lake.step(1)
print(obs, rewards, done)
lake.render()
obs, rewards, done, _, _= lake.step(1)
print(obs, rewards, done)
lake.render()
obs, rewards, done, _, _= lake.step(2)
print(obs, rewards, done)
lake.render()

Steps

Los conceptos clave en el Aprendizaje por Refuerzo en este entorno son:

  • Estados: El estado del entorno es todo lo que describe el entorno: El tablero, dónde están los hoyos y la casilla en la que se encuentra el jugador.
  • Acciones: Las acciones disponibles para el jugador son moverse en una de las cuatro direcciones cardinales (izquierda, abajo, derecha, arriba). Cada acción se representa como un número entero de 0 a 3.
  • Observaciones: La observación es la parte del entorno que el Agente puede ver. En este caso es un número entero que va de 0 a 15 que indica en qué casilla está el jugador. En este caso el Agente solo puede ver la casilla en la que se encuentra y no le dejamos ver dónde están los hoyos para que lo tenga que aprender con ensayo y error como evitarlos. Si complicamos el entorno y los agujeros son aleatorios, ayudaráimos al Agente por ejemplo añadiendo en las observaciones los hoyos cercanos que tiene a su alrededor.
  • Recompensas: El jugador recibe una recompensa de 1 si llega al objetivo y 0 en caso contrario.
  • Terminación: Llamamos episodio al conjunto de acciones que terminan si el jugador llega al objetivo o cae en un agujero de hielo.

Y las funciones que se pueden usar en este entorno son:

  • reset: Inicializa el entorno y devuelve el estado inicial.
  • observation: Devuelve la observación actual del entorno. En este caso, la casilla en la que se encuentra el jugador.
  • reward: Devuelve la recompensa actual del entorno. En este caso, 1 si el jugador llega al objetivo y 0 en caso contrario.
  • done: Devuelve True si el episodio ha terminado y False en caso contrario.
  • step: Realiza una acción en el entorno y devuelve la observación, la recompensa, si el episodio ha terminado y la información adicional.
  • render: Imprime el estado actual del entorno en la consola.

Ahora podríamos entrenar un algortimo de RRlib pasándole el entorno que hemos definido para que en su entrenamiento utilice estas funciones siguiendo los siguientes pasos por cada episodio de entrenamiento:

Mientras el episodio no haya terminado (done):

  • 1.- Inicializar el entorno (reset)
  • 2.- El Agente observa el estado actual del entorno (observation)
  • 3.- El Agente selecciona una acción según su política actual
  • 4.- El entorno ejecuta la acción (step) y proporciona al Agente un nuevo estado y una recompensa
  • 5.- El Agente actualiza su función de valor y su política en función de la recompensa recibida (reward)

El código de entrenamiento sería :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
from ray.rllib.algorithms.ppo import PPOConfig
from ray.tune.logger import pretty_print

algo = (
    PPOConfig()
    .env_runners(num_env_runners=1)
    .resources(num_gpus=0)
    .environment(env=MyFrozenLake)
    .build()
)

for i in range(10):
    result = algo.train()
    print(pretty_print(result))

    if i % 5 == 0:
        checkpoint_dir = algo.save().checkpoint.path
        print(f"Checkpoint saved in directory {checkpoint_dir}")

Estamos entrenando un algoritmo PPO (Proximal Policy Optimization) en el entorno Frozen Lake que hemos definido. El entrenamiento se realiza en 10 iteraciones y se imprime el resultado de cada iteración. Cada 5 iteraciones se guarda un checkpoint del modelo entrenado.

PPO es un algoritmo clasificado como on-policy, lo que significa que implementan una política que decide qué acción tomar en cada momento de forma explícita y off-model que significa que no necesita un modelo predefinido de transición de estado para entrenar. El modelo de política del PPO se basa en redes neuronales artificiales y entra dentro de la categoría de algoritmos de políticas de gradiente.

PPO es un algoritmo de aprendizaje por refuerzo de vanguardia que ha demostrado ser eficaz en una amplia variedad de entornos de aprendizaje por refuerzo, pero podemos entrenar otros muchos algoritmos de aprendizaje por refuerzo de RRlib como DQN, A3C, etc.

Para utilizar el modelo entrenado y resolver el problema de alcanzar la casilla de salida, usaríamos el siguiente código:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from IPython import display
import time

env = MyFrozenLake()
env.reset(_,_)

obs, rewards, done, _, _ = env.step(0)
print(obs, rewards, done)

done = False
while not done:
  action = algo.compute_single_action(obs, explore=True)
  print(action)
  obs, reward, done, _, _ = env.step(action)

  display.clear_output(wait=True)
  env.render()
  time.sleep(0.25)

if reward == 1:
    print("Success!")
else:
    print("Agent failed to reach the goal :(")

algo.stop()

Alarmas en una Central eléctrica

Tenemos una central electrica con un histórico de datos de varios años de alarmas que se han producido en ella cuando surgen problemas en su suministro. Queremos entrenar un Agente que sea capaz de predecir si se va a producir una alarma con una antelación de 15 minutos y así poder tomar medidas preventivas.

Para ello vamos a definir un entorno de aprendizaje por refuerzo que simula el entorno de la central eléctrica con los datos del suministro de electricidad.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
import numpy as np
import gymnasium as gym
from gym.spaces import Dict, Box

class AIAlarm(gym.Env):
    def __init__(self, df, env_config=None):
        if env_config is None:
            env_config = dict()

        # El tamaño de la ventana de tiempo que se usará para predecir las alarmas son 15 minutos
        self.size_window = env_config.get("size_window", 15)
        # El número de características que se usarán para predecir las alarmas son 5 (frecuencia, carga, potencia 
        # y desviación de la frecuencia)
        self.num_features = env_config.get("num_features", 4)
        # El dataframe con el histórico de datos de la central eléctrica para el entrenamiento es df
        self.df = df
        # El tiempo avazando en el histórico de datos de la central eléctrica es timestep que empieza en 0 y se va 
        # incrementando minuto a minuto hasta terminar los datos del histórico
        self.timestep = 0

        # Inicializamos las recompensas a 0
        self.rewards = 0

        # El espacio de observaciones es un espacio continuo de tamaño (size_window, num_features) que indica 
        # las características que se han producido en los últimos 15 minutos        
        self.observation_space = gym.spaces.Box(low=-np.inf, high=np.inf, shape=(self.size_window, self.num_features), 
                                                dtype=np.float64)
        # 0 NO Alarma - 1: ALARMA
        self.action_space = gym.spaces.Discrete(2) 

    # Dado un minuto en el histórico de datos de la central eléctrica, devuelve la ventana con la información 
    # histórica de los próximos 15 minutos
    def future_creator(self, data, timestep, window_size):
      finish_id = timestep + window_size -1

      windowed_data = np.array(data[timestep:finish_id+1])

      state = []

      for i in range(len(windowed_data)):
        state.append([windowed_data[i][0],windowed_data[i][1],	windowed_data[i][2],windowed_data[i][3],
                    int(windowed_data[i][4])])

      return np.array(state)

    # Dado un minuto devuelve la ventana con la información que se ha producido en la central eléctrica 
    # los últimos 15 minutos
    def observation_creator(self, data, timestep, window_size):
      starting_id = timestep - window_size + 1

      if starting_id >= 0:
        if (len(data) - starting_id) < window_size:
          repeated_rows = np.tile(np.array(data.iloc[-1]), (starting_id,len(data)))
          windowed_data = np.vstack((data.iloc[starting_id:len(data)],repeated_rows))
        else:
          windowed_data = np.array(data[starting_id:timestep+1])
      else:
        repeated_rows = np.tile(np.array(data.iloc[0]), (-starting_id,1))
        windowed_data = np.vstack((repeated_rows,data.iloc[0:timestep+1]))

      state = []

      for i in range(len(windowed_data)):
        state.append([windowed_data[i][0],windowed_data[i][1],	windowed_data[i][2],windowed_data[i][3],
                    int(windowed_data[i][4])])

      return np.array(state)

    # Inicializa el entorno y devuelve el estado inicial
    def reset(self, seed, options):
        self.timestep = 0
        self.rewards = 0
        return self.observation(), {}

    # Devuelve la observación actual del entorno que es la información de los últimos 15 minutos
    def observation(self):
        return self.observation_creator(self.df, self.timestep, self.size_window)

    # Devuelve la recompensa actual del entorno
    def reward(self,action):
      # Obtenemos el histŕico de las alarmas que se han producido en los próximos 15 minutos
      alarms = np.array(np.where(self.future_creator(self.df, self.timestep, self.size_window)[:,-1] >= 1))

      # Si hay una alarma en el histórico de los próximos 15 minutos y el Agente predice que va a haber 
      # una alarma, a la actual recompensa se le suma 1
      if (action == 1 and alarms.size > 0):
        self.rewards = self.rewards + 10
      # Si hay una alarma en el histórico de los próximos 15 minutos y el Agente predice que NO va a haber 
      # una alarma, a la actual recompensa se le resta 1 
      elif (action == 0 and alarms.size > 0):
        self.rewards = self.rewards - 1
      else:
        pass

      # Si hay una alarma en el histórico de los próximos 15 minutos y el Agente predice que va a haber 
      # una alarma, a la actual recompensa se le suma 10
      if (action == 1 and alarms.size > 0):
        self.rewards = self.rewards + 10
      # Si NO hay una alarma en el histórico de los próximos 15 minutos y el Agente predice que va a haber 
      # una alarma, a la actual recompensa se le resta 10
      elif (action == 1 and alarms.size == 0):
        self.rewards = self.rewards -10
      # Si hay una alarma en el histórico de los próximos 15 minutos y el Agente predice que NO va a haber 
      # una alarma, a la actual recompensa se le resta 10        
      elif (action == 0 and alarms.size > 0):
        self.rewards = self.rewards - 10
      # elif (action == 0 and alarms.size == 0):
      #   self.rewards = self.rewards + 5
      else:
        pass

      # Si hay una alarma en el histórico de los próximos 15 minutos y el Agente predice que va a haber 
      # una alarma, a la actual recompensa se le suma 10
      if (action == 1 and alarms.size > 0):
        self.rewards = self.rewards + 10
      # Si hay una alarma en el histórico de los próximos 15 minutos y el Agente predice que NO va a haber
      # una alarma, a la actual recompensa se le resta 10
      elif (action == 1 and alarms.size == 0):
        self.rewards = self.rewards -10
      # Si hay una alarma en el histórico de los próximos 15 minutos y el Agente predice que NO va a haber 
      # una alarma, a la actual recompensa se le resta 10        
      elif (action == 0 and alarms.size > 0):
        self.rewards = self.rewards - 10
      # Si NO hay una alarma en el histórico de los próximos 15 minutos y el Agente predice que NO va a haber 
      # una alarma, a la actual recompensa se le suma 10
      elif (action == 0 and alarms.size == 0):
        self.rewards = self.rewards + 10
      else:
        pass        

      return self.rewards

    # El episodio termina si el Agente ha recorrido todo el histórico de datos de la central eléctrica
    def done(self):
      return bool(self.timestep <= (len(self.df) - self.size_window))

    # La acción es añadir 1 minuto al paso anterior y devolver la observación/recompensa/done correspondiente
    def step(self, action):
      self.timestep = self.timestep + 1

      # Return observation/reward/done
      return self.observation(), self.reward(action), self.done(), {}, {}

    # La observación es la ventana con la información histórica de los próximos 15 minutos
    def render(self):
      print(self.observation())

Cómo podemos ver en los comentarios del código anterior, los conceptos clave en el Aprendizaje por Refuerzo en este entorno son:

  • Estados: El estado del entorno es todo lo que describe el entorno. El histórico de datos de la central eléctrica y las alarmas que se han producido en ella.
  • Acciones: Las acciones disponibles para el Agente son predecir si se va a producir una alarma en los próximos 15 minutos o no. Cada acción se representa como un número entero de 0: NO SE VA A PRODUCIR o 1: SI SE VA A PRODUCIR.
  • Observaciones: La observación es la parte del entorno que el Agente puede ver. En este caso es un conjunto de arrays de 4 números que indica las características de la central (frecuencia, carga, potencia, desviación de frecuencia) en cada uno de últimos 15 minutos.
  • Recompensas: El Agente recibe una recompensa de +10 si predice que se va a producir una alarma en los próximos 15 minutos y acierta, de -10 si predice que se va aproducir una alarma pero no acierta y de -10 si predice que no se va a producir y al final si se produce.
  • Terminación: Un episodio termina si el Agente ha recorrido todo el histórico de datos de la central eléctrica.

Y las funciones que se pueden usar en este entorno son:

  • reset: Inicializa el entorno y devuelve el estado inicial. En este caso, el estado inicial es la información histórica de los primeros 15 minutos.
  • observation: Devuelve la observación actual del entorno. En este caso es la información histórica de los últimos 15 minutos.
  • reward: Devuelve la recompensa actual del entorno.
  • done: Devuelve True si el episodio ha recorrido todos los datos históricos.
  • step: Realiza una acción (Predecir si va a haber una Alarma o no) y devuelve la observación, la recompensa, si el episodio ha terminado y la información adicional.
  • render: Imprime el estado actual del entorno en la consola.

Para entrenar y usar el modelo entrenado en este entorno, se seguirían los mismos pasos que en el entorno Frozen Lake.

Ray Framework

Jobs:

Si ponemos el código del entrenamiento en un fichero llamado train.py, podemos lanzar el entrenamiento dentro del cluster de Ray con el siguiente comando:

1
ray job submit train.py

Hyperparameters Tuning:

Si queremos hacer un ajuste de hiperparámetros, podemos usar la biblioteca Tune de Ray. Tune es una biblioteca de ajuste de hiperparámetros escalable y distribuida que proporciona una API simple para ajustar hiperparámetros y realizar experimentos de aprendizaje automático.

En el caso de los ejemplos anteriores, podríamos ajustar los hiperparámetros de los algoritmos de aprendizaje por refuerzo de RRlib para mejorar su rendimiento.

Estos algoritmos pueden tener hasta 129 hiperparámetros que se pueden ajustar para mejorarlo. Algunos de los hiperparámetros más importantes son:

  • lr: tasa de aprendizaje
  • train_batch_size: número de iteraciones de datos que se agruparán juntos
  • sgd_minibatch_size: tamaño del minibatch para SGD (descenso por gradiente estocástico)
  • num_sgd_iter: épocas de SGD por iteración de PPO
  • entropy_coeff: mide la cantidad de exploración durante el entrenamiento
  • model architecture: Arquitectura de la red artificial de las políticas (acciones a llevar a cabo)

Por ejempo si queremos ajustar el hiperparámetro lr (learning rate) del algoritmo PPO de RRlib, podríamos hacerlo con el siguiente código:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from ray.rllib.algorithms.ppo import PPOConfig

ppo_config = (
    PPOConfig()
    .framework("torch")
    .rollouts(create_env_on_local_worker=True)
    .debugging(seed=0, log_level="ERROR")
)

# Intervalo de valores posibles del hiperparámetro lr para encontrar el óptimo
ppo_config = ppo_config.training(
    lr=tune.grid_search([1e-4, 5e-5])
)

po_config = ppo_config.environment(env="MyFrozenLake")

analysis = tune.run(
    "PPO",
    config            = ppo_config.to_dict(),
    stop              = {"training_iteration" : 5},
    checkpoint_freq   = 1,
    verbose           = 0,
    metric            = "episode_reward_mean",
    mode              = "max",
)

analysis.best_config["lr"]

También se pueden ajustar multiples parámetros a la vez con Tune usando una búsqueda “Grid Search” que prueba todas las combinaciones posibles de los hiperparámetros que le especifiquemos.

Ray Serve

Ray Serve es un framework de Python que permite la creación de servicios API REST escalables y de alto rendimiento. Ray Serve se integra con Ray para proporcionar una API simple y escalable para servir modelos de aprendizaje automático y aplicaciones web.

Podemos usar Ray Serve para servir modelos de aprendizaje por refuerzo entrenados con RRlib y hacer predicciones en tiempo real.

Ray Distributed Training

También podemos aprovechar toda la potencia de Ray para entrenar modelos de aprendizaje por refuerzo en paralelo y de forma distribuida. Ray proporciona una API simple y escalable para entrenar y evaluar modelos de aprendizaje por refuerzo en clústeres de máquinas.

1
2
3
4
5
6
ppo_config = (
    PPOConfig()
    .framework("torch")
    .rollouts(num_rollout_workers=4, num_envs_per_worker=2)
    .resources(num_gpus=0)
)

Expecificando num_rollout_workers=4, estamos diciendo a RRlib que use 4 workers para recoger datos del entorno en paralelo. Para la mayoría de los entornos de simulación se puede replicar el entorno en un clúster. Por lo tanto, se pueden recoger datos mucho más rápido y evitar el cuello de botella del entrenamiento. Sea cual sea el clúster al que Ray esté conectado en el backend, num_rollout_workers=4 funciona sin problemas.

Redes Neuronales Artificiales

Muchos de los modelos de aprendizaje por refuerzo de RRlib (PPO, DQN, etc) utilizan redes neuronales artificiales para aproximar la función de valor y la política.

En RRLib podemos definir la arquitectura de las redes neuronal de la política y de la función de valor creando un Modelo a medida tanto en TensorFlow como en PyTorch.

Conclusión

En este post hemos visto como usar RRlib para entrenar algoritmos de aprendizaje por refuerzo en entornos simulados y reales y como usar la potencia de la plataforma distribuida de Ray para entrenarlos, tunearlos y servirlos.

Y por fin (ya era hora) hemos terminado la serie de posts sobre Ray

Referencias

SO WHAT DO YOU THINK ?

Contact us and challenge us with a problem
+34 644 237 135
hola@taniwa.es

SOMOS PYME INNOVADORA
Sello PYME INNOVADORA 06/11/2027 escudo de MEIC 06/11/2027

CONTACT TANIWA