<jupyter_start><jupyter_text>Jugando juegos de Atari usando DQN Atari 2600 es una consola de videojuegos popular de una compañia de juegos llamada Atari. Esta consola poseia muchos titulos populares como pong, space invaders, <NAME>, entre otros. En este ejercicio en particular se construye una red Q profunda (DQN por sus siglas en inglés) para jugar Ms Pacman. Arquitectura de DQNEn el ambiente de Atari, la imagen del juego en pantalla es el estado del ambiente. Por tanto, la imágen del juego es la que se introduce como entrada a la DQN y retorna el valor Q de todas las acciones en el estado. Ya que se están ocupando imágenes, en ves de unar una red neuronal profunda simple para aproximar el valor de Q, se ocupa una red convolucional ya que estas son muy efectivas para tratamiento de imágenes.Por tanto, la DQN es una red neuronal convolucional. Se alimenta la imágen del juego como una entrada en la red neuronal convolucional y da como salida el valor Q de todas las acciones en el estado.Como se muestra en la siguiente figura, las capas convolucionales extraen las caracteristicas de la imagen y producen un mapa de caracteristicas. Despues, se aplana este mapa de caracteristicas y se alimenta la red con este mapa. La parte de feedforward de la red toma este mapa aplanado como una entrada y retorna el valor Q de todas las acciones en el estado.![title](Images/4.png)Algo a considerar es que no hay operaciones de pooling. La operacion de pooling es util cuando se realizan tareas tales como deteccion de objetos, clasificacion de imágenes y otras cosas donde no se considera la posición del objeto en la imágen y solo se desea saber si el objeto deseado está presente en esta. Como lo que se desea es saber el estado del juego, la posición de los objetos es muy importante, por esto no se realiza una operación de pooling. Implementando la red DQNPrimero se importan las librerias necesarias. El autor realizó la prueba con tensorflow 2.0, en mi caso lo hice con la version 2.3.1 y usando la gpu de mi computadora.<jupyter_code>import tensorflow as tf
gpus = tf.config.experimental.list_physical_devices('GPU')
tf.config.experimental.set_memory_growth(gpus[0], True)
print(tf.__version__)
import random
import gym
import numpy as np
from collections import deque
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Activation, Flatten, Conv2D, MaxPooling2D
from tensorflow.keras.optimizers import Adam<jupyter_output><empty_output><jupyter_text>Ahora, se crea el ambiente de Ms Pacman usando gym:<jupyter_code>env = gym.make("MsPacman-v0")<jupyter_output><empty_output><jupyter_text>Se establece el tamaño del estado:<jupyter_code>state_size = (88, 80, 1)<jupyter_output><empty_output><jupyter_text>Se obtiene el numero de acciones posibles por el agente<jupyter_code>action_size = env.action_space.n<jupyter_output><empty_output><jupyter_text>Preprocesamiento de la imagen del juegoYa se mencionó que se alimentaria la red DQN con el estado del juego (imagen de la pantalla del juego), sin embarbo, alimentar la red directamente con la imagen cruda no es eficiente, ya que el tamaño de esta es de 210x160x3 lo cual es computacionalmente muy costoso.Para evitar esto, se hace un preprocesamiento a la imagen y despues se introduce a la red DQN. Primero, se corta y redimensiona el tamaño de la imágen, convirtiendo la imágen a escala de grises, normalizando y redimensionando la imágen a 88x80x1. Para esto se ocupa la siguiente funcion llamada preprocess_state<jupyter_code>color = np.array([210, 164, 74]).mean()

def preprocess_state(state):

    #crop and resize the image
    image = state[1:176:2, ::2]

    #convert the image to greyscale
    image = image.mean(axis=2)

    #improve image contrast
    image[image==color] = 0

    #normalize the image
    image = (image - 128) / 128 - 1
    
    #reshape the image
    image = np.expand_dims(image.reshape(88, 80, 1), axis=0)

    return image<jupyter_output><empty_output><jupyter_text>Construyendo la DQN Ahora, se construye la red Q profunda. Esta red recibe una imágen de entrada, la cual procesa con capas convolucionales y da como resultado los valores Q.Se definen tres capas convolucionales. Estas capas convolucionales extraen las caracteristicas de la imágen, mas adelante se aplanan los datos obtenidos y se introducen a dos capas densas, y la ultima capa retorna los valores Q.<jupyter_code>class DQN:
    def __init__(self, state_size, action_size):
        
        #define el tamaño del estado
        self.state_size = state_size
        
        #define el tamaño de la accion
        self.action_size = action_size
        
        #define el buffer de rejuego
        self.replay_buffer = deque(maxlen=5000)
        
        #define el factor de descuento
        self.gamma = 0.9  
        
        #define el valor epsilon
        self.epsilon = 0.8   
        
        #define el ratio de actualizacion con el que se desea actualizar el objetivo de la red
        self.update_rate = 1000    
        
        #define la red principal
        self.main_network = self.build_network()
        
        #define el objetivo de la red
        self.target_network = self.build_network()
        
        #copia los pesos de la red principal a la red de objetivo
        self.target_network.set_weights(self.main_network.get_weights())
        

    #Ahora se define una funcion llamada build_network la cual es necesaria para la DQN

    def build_network(self):
        model = Sequential()
        model.add(Conv2D(32, (8, 8), strides=4, padding='same', input_shape=self.state_size))
        model.add(Activation('relu'))
        
        model.add(Conv2D(64, (4, 4), strides=2, padding='same'))
        model.add(Activation('relu'))
        
        model.add(Conv2D(64, (3, 3), strides=1, padding='same'))
        model.add(Activation('relu'))
        model.add(Flatten())


        model.add(Dense(512, activation='relu'))
        model.add(Dense(self.action_size, activation='linear'))
        
        model.compile(loss='mse', optimizer=Adam())

        return model


    #La red se entrena mediante pequeños bloques de muestras de trancisiones del buffer de rejuego.
    #Por tanto, se define una función llamada store_transition la cual guarda la
    # informacion de las trancisiones en el buffer de rejuego

    def store_transistion(self, state, action, reward, next_state, done):
        self.replay_buffer.append((state, action, reward, next_state, done))
        

    #Para tener un control del balance exploración-explotacion, se seleccionan las acciones
    #usando la política epsilon-greedy. Por tanto, se define esta funcion para seleccionar
    #una accion usando la politica epsilon-greedy
    
    def epsilon_greedy(self, state):
        if random.uniform(0,1) < self.epsilon:
            return np.random.randint(self.action_size)
        
        Q_values = self.main_network.predict(state)
        
        return np.argmax(Q_values[0])

    
    #Entrenamiento de la red
    def train(self, batch_size):
        
        #muestrea un minibloque de transiciones desde el buffer de replayr
        minibatch = random.sample(self.replay_buffer, batch_size)
        
        #Calcula el valor de Q usando la red de objetivo
        for state, action, reward, next_state, done in minibatch:
            if not done:
                target_Q = (reward + self.gamma * np.amax(self.target_network.predict(next_state)))
            else:
                target_Q = reward
                
            #Calcula el valor de Q usando la red principal
            Q_values = self.main_network.predict(state)
            
            Q_values[0][action] = target_Q
            
            #Entrena la red principal
            self.main_network.fit(state, Q_values, epochs=1, verbose=0)
            
    #actualiza los pesos de la red de objetivo copiandolos desde la red principal
    def update_target_network(self):
        self.target_network.set_weights(self.main_network.get_weights())<jupyter_output><empty_output><jupyter_text>Entrenando la redAhora, se entrena la red, para esto, primero se establece el numero de episodios deseados.en el codigo original realizan más de 10 episodios, si mal no recuerdo eran 50 o 100, pero son muy tardados por lo que yo solo ejecuté 10 episodios.<jupyter_code>num_episodes = 10<jupyter_output><empty_output><jupyter_text>Se define el numero de pasos de tiempo<jupyter_code>num_timesteps = 20000<jupyter_output><empty_output><jupyter_text>Se define el tamaño del bloque<jupyter_code>batch_size = 8<jupyter_output><empty_output><jupyter_text>Se establece el numero de imágenes de la pantalla despues del juego que se desean considerar<jupyter_code>num_screens = 4<jupyter_output><empty_output><jupyter_text>Se instancia la clase DQN<jupyter_code>dqn = DQN(state_size, action_size)
done = False
time_step = 0

#for para cada episodio
for i in range(num_episodes):
    
    #establece el valor de retorno en 0
    Return = 0
    
    #realiza el preprocesamiento de la imagen en pantalla, además de reiniciar el entorno
    state = preprocess_state(env.reset())

    #for que recorre cada paso de tiempo del episodio
    for t in range(num_timesteps):
        
        #renderiza el entorno
        env.render()
        
        #actualiza el paso de tiempo
        time_step += 1
        
        #actualiza la red de objetivo
        if time_step % dqn.update_rate == 0:
            dqn.update_target_network()
        
        #seleciona la accion
        action = dqn.epsilon_greedy(state)
        
        #realiza la accion seleccionada
        next_state, reward, done, _ = env.step(action)
        
        #preprocesa el siguiente estado
        next_state = preprocess_state(next_state)
        
        #guarda la información de transición
        dqn.store_transistion(state, action, reward, next_state, done)
        
        #actualiza el estado actual a el estado siguiente
        state = next_state
        
        #actualiza el valor de retorno añadiendo el valor de la recompenzza
        Return += reward
        
        #si el episodio ha terminado, muestra el valor de retorno y sale del for interno
        if done:
            print('Episode: ',i, ',' 'Return', Return)
            break
            
        #si el numero de transiciones del buffer de rejuego es mayor
        #que el tamaño del bloque, entonces se entrena la red
        if len(dqn.replay_buffer) > batch_size:
            dqn.train(batch_size)<jupyter_output>Episode:  0 ,Return 290.0
Episode:  1 ,Return 140.0
Episode:  2 ,Return 440.0
Episode:  3 ,Return 270.0
Episode:  4 ,Return 200.0
Episode:  5 ,Return 280.0
Episode:  6 ,Return 450.0
Episode:  7 ,Return 330.0
Episode:  8 ,Return 300.0
Episode:  9 ,Return 210.0
{'fraction_non_alphanumeric': 0.0625694187338023, 'fraction_numerical': 0.015920029618659754, 'mean_word_length': 3.1831204026325977, 'pattern_counts': {'":': 0, '<': 46, '<?xml version=': 0, '>': 46, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 11, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '11', 'dir': 'jupyter-structured-clean-dedup', 'id': '189745', 'license': 'MIT', 'n_tokens_mistral': 3524, 'n_tokens_neox': 3364, 'n_words': 1217, 'path': 'atari-game/Playing-Atari-Games-using-DQN.ipynb', 'repo_name': 'HectorJuarezL/machine-learning-codes', 'size': '10807'}