[PYTHON] [Renforcement de l'apprentissage] Commentaire de vengeance (Keras-RL) qui a tenté de mettre en œuvre / d'expliquer R2D2

R2D2 précédemment implémenté, mais l'apprentissage par mini-lots n'a pas pu être mis en œuvre. Après cela, j'ai réussi à l'implémenter cette fois par essais et erreurs.

Cela fait longtemps depuis l'article précédent, je vais donc vous expliquer le flux global en gros. Nous corrigerons également les erreurs de l'implémentation précédente. .. ..

En outre, cet article se compose de deux parties, une section commentaire et une section hyper paramétrage. Voir ci-dessous pour les hyper paramètres [Renforcement de l'apprentissage] Implémentation / explication de R2D2 explication de l'hyper paramètre de vengeance (Keras-RL)

Postscript: R2D3 a également été implémenté. [Renforcement de l'apprentissage] J'ai implémenté / expliqué R2D3 (Keras-RL)

Code entier

Le code créé dans cet article est ci-dessous. Cette fois seulement github.

table des matières

Explication de l'implémentation DQN (Rainbow)

En guise de bilan, je vous expliquerai à nouveau l'image de l'implémentation de DQN (Rainbow). Voir l'article précédemment publié pour une explication détaillée.

Ce qui suit est un résumé des images de l'apprentissage avec DQN (Rainbow).

zu1.PNG

zu2.PNG

DQN stocke les données d'expérience (expérience) en mémoire comme suit.

e_{t} = (s_{t},a_{t},r_{t},s_{t+1})

Si l'étape de l'apprentissage en plusieurs étapes est 1, l'état suivant sera $ t + 1 $, Si c'est 3 étapes, ce sera $ t + 3 $.

Formule Figure
État précédent s_{t} observation: t(n-6) ~ t(n-3)
État suivant s_{t+1} observation: t(n-3) ~ t(n)
action a_{t} action: t(n-3)
Récompense r_{t} reward: t(n)

En outre, la taille contenue dans chaque variable est la suivante.

Longueur à tenir Longueur à enregistrer en mémoire
rewards multisteps 0(Utilisé pour les calculs uniquement)
Récompenses calculées 1 1(État actuel)
actions multisteps + 1 1(État précédent)
observations input_sequence + multisteps input_sequence + multisteps

Action incorrecte référencée dans l'apprentissage en plusieurs étapes

Dans l'article précédent Multi-Step learning, j'avais l'habitude de faire référence à l'action avec $ t_n $, ce qui est incorrect. Tu ... $ T_ {n-multisteps} $ était la bonne réponse car elle se réfère à l'action dans l'état précédent.

Échantillonnage d'importance incorrect

L'article précédent est ci-dessous.

Pour faire simple, l'échantillonnage par importance est priorisé lors de la récupération des expériences avec Priority Experience Reply. Ensuite, le nombre d'expériences acquises sera biaisé. Ensuite, le biais mettra un biais sur l'apprentissage, c'est donc l'importance d'échantillonnage pour corriger cela.

Plus précisément, une expérience sélectionnée avec une probabilité élevée a un faible taux de réflexion pour mettre à jour la valeur Q, et une expérience sélectionnée avec une faible probabilité a un taux de réflexion élevé pour mettre à jour la valeur Q.

Dans le passé, il semblait que la mise en œuvre était un peu étrange et qu'elle n'apprenait pas bien. Auparavant, il était appliqué à la valeur Q mise à jour elle-même, mais il aurait dû être appliqué à td_error lui-même. (La dénomination des variables n'était pas bonne) De plus, comme il se reflète dans la mise à jour de la valeur Q, il n'est pas appliqué à la priorité.

-Implémentation précédente (pseudo code)

IS


def train():

    #Obtenez de l'expérience du PER en fonction de la probabilité
    batchs, batch_weight = memory.sample(batch_size)

    #Obtenir la valeur Q de l'état précédent à partir du modèle
    # state0_qvals contient la valeur Q pour chaque action
    state0_qvals = model.predict(state0_batch)
    
    for batch_i in range(batch_size):
        reward = batchs[batch_i]Récompense
        action = batchs[batch_i]action
        q0 = state0_qvals[batch_i][action]  #Valeur Q avant la mise à jour

        #modèle et cible_Obtenir la valeur Q maximale de l'état actuel à l'aide du modèle
        # (La méthode d'acquisition diffère entre DQN et DDQN)
        maxq =modèle et cible_Obtenir du modèle

        td_error = reward + (gamma ** reward_multisteps) * maxq
        td_error *= batch_weight

        priority = abs(td_error - q0)
        
        #Apprenez en modifiant uniquement la valeur Q de l'action cible
        state0_qvals[batch_i][action] = td_error

    # train
    model.train_on_batch(state0_qvals)

-Mise en œuvre après changement (pseudo code)

IS


def train():

    #Obtenez de l'expérience du PER en fonction de la probabilité
    batchs, batch_weight = memory.sample(batch_size)

    #Obtenir la valeur Q de l'état précédent à partir du modèle
    # state0_qvals contient la valeur Q pour chaque action
    state0_qvals = model.predict(state0_batch)
    
    for batch_i in range(batch_size):
        reward = batchs[batch_i]Récompense
        action = batchs[batch_i]action
        q0 = state0_qvals[batch_i][action]  #Valeur Q avant la mise à jour

        #modèle et cible_Obtenir la valeur Q maximale de l'état actuel à l'aide du modèle
        # (La méthode d'acquisition diffère entre DQN et DDQN)
        maxq =modèle et cible_Obtenir du modèle

        #※ -Ajouter correctement q0 et td_Émettre une erreur
        #* Aussi, lot_Appliquer le poids ici
        td_error = reward + (gamma ** reward_multisteps) * maxq - q0

        #※ td_La valeur absolue de l'erreur devient prioritaire telle quelle
        priority = abs(td_error)
        
        #Apprenez en modifiant uniquement la valeur Q de l'action cible
        #※ td_Puisque l'erreur est devenue une différence, appliquez-lui un poids et mettez à jour la valeur Q avec la différence.
        state0_qvals[batch_i][action] += td_error * batch_weight

    # train
    model.train_on_batch(state0_qvals)

Explication de l'implémentation R2D2

Mini apprentissage par lots

Auparavant, l'apprentissage mini-abeille ne pouvait pas être mis en œuvre car le LSTM avec état de Keras n'était pas bien compris. Les articles de recherche précédents sont les suivants.

zu3.PNG

Apparemment, il y a une valeur de batch_size d'états dans hidden_states et vous pouvez les spécifier. Vous pouvez maintenant procéder à plusieurs entraînements entre les séquences en même temps.

DRQN(R2D2) Pour rendre l'histoire plus facile à comprendre, je vais l'expliquer avec R2D2, qui a supprimé la partie de traitement parallèle. L'article précédent est ci-dessous.

C'est un diagramme d'image comme DQN.

zu4.PNG

zu5.PNG

Ça devient assez compliqué ... J'ai écrit ce chiffre parce que j'étais confus lors de sa mise en œuvre ...

La méthode de mise à jour de la valeur Q et d'émission de la priorité est la même que DQN, elle est donc omise de la figure.

Les points sont la séquence d'entrée et la longueur d'entrée. La dernière fois, je n'étais pas au courant de cela. (En supposant que la séquence d'entrée = 1, la longueur d'entrée a été exprimée comme séquence d'entrée)

La séquence d'entrée est la longueur de l'état à entrer dans le modèle et le nombre d'entrées est la longueur d'entrée. La valeur Q est mise à jour pour chaque longueur d'entrée et la priorité est également calculée. (Je ne suis pas sûr de cette interprétation, mais dans la section 2.3 du document R2D2, j'ai proposé une nouvelle façon de mettre en place la priorité, et il est logique de penser qu'une expérience donne plusieurs priorités comme décrit ci-dessus.)

La taille contenue dans chaque variable est la suivante.

Longueur à tenir Longueur à enregistrer en mémoire
rewards multisteps + input_length - 1 0(Utilisé pour les calculs uniquement)
Récompenses calculées input_length input_length
actions multisteps + input_length input_length(De l'état précédent)
hidden states burnin + multisteps + input_length + 1 1(État le plus ancien)
observations burnin + input_sequence + multisteps + input_length - 1 0(Pour résumer ci-dessous)
Résumé des observations burnin + multisteps + input_length Même longueur

fonction de redimensionnement

h(x) = sign(x)(\sqrt{|x|+1}-1)+\epsilon x

rescaling2.png

La fonction de redimensionnement a été introduite dans R2D2 et devait être utilisée à la place de l'écrêtage de récompense (-1 à 1). J'avais l'habitude de m'inquiéter de la fonction inverse, mais je l'ai forcément rendue inutile.

La formule pour dériver l'erreur TD à l'aide de la fonction de redimensionnement est la suivante. ($ y_t $ est l'erreur TD)

y_{t} = h \Bigl(r_{t} + \gamma h^{-1}(\max_pQ_{target}(s_{t+1},a_{t}))\Bigr)

Développez $ h () $ dans la formule ci-dessus.

y_{t} = h (r_{t}) + h \Bigl(\gamma h^{-1}(\max_pQ_{target}(s_{t+1},a_{t}))\Bigr)

L'application de la fonction inverse à une fonction la ramène à sa valeur d'origine. * $ H (h ^ {-1} (x)) = x $ Ainsi, le côté droit peut être décalé ($ \ gamma $ est ignoré comme une erreur ...)

Ensuite, cela devient comme suit.

y_{t} = h (r_{t}) + \gamma (\max_pQ_{target}(s_{t+1},a_{t}))

La fonction de redimensionnement est désormais appliquée uniquement aux récompenses ($ r_ {t} $). Si vous regardez le graphique, vous pouvez voir que les récompenses sont bien arrondies. (100 récompenses seront d'environ 10) C'est une bonne alternative au détourage.

Traitement parallèle (communication inter-processus)

L'article précédent est ci-dessous.

Référence: Compréhension complète du thread Python et du multitraitement

Au début, j'ai utilisé Queue, mais comme la quantité de données de poids était importante et que cela semblait être un goulot d'étranglement, j'ai étudié la communication entre chaque processus. Les résultats de l'enquête sont les articles suivants.

À partir de là, la communication est la suivante. (En conséquence, la file d'attente est utilisée telle quelle)

zu10.PNG

zu11.PNG

zu12.PNG

L'échange d'informations entre les processus est mis en œuvre dans la mémoire partagée. Il n'y a pas de verrou particulier car l'écrivain et le lecteur sont clairement séparés.

Callbacks

Il s'avère que la communication inter-processus est assez coûteuse Je l'ai implémenté parce qu'il y avait un processus qui chevauchait Actor et Leaner.

Je le crée principalement pour la sauvegarde / le chargement et les journaux. La classe de base du Callback implémenté est la suivante.

R2D2Callback


import rl.callbacks
class R2D2Callback(rl.callbacks.Callback):
    def __init__(self):
        pass

    #--- train ---

    def on_r2d2_train_begin(self):
        pass

    def on_r2d2_train_end(self):
        pass

    #--- learner ---

    def on_r2d2_learner_begin(self, learner):
        pass
    
    def on_r2d2_learner_end(self, learner):
        pass

    def on_r2d2_learner_train_begin(self, learner):
        pass

    def on_r2d2_learner_train_end(self, learner):
        pass

    #--- actor ---
    #Ci-dessous et rl.callbacks.Méthode d'héritage de rappel

    def on_r2d2_actor_begin(self, actor_index, runner):
        pass

    def on_r2d2_actor_end(self, actor_index, runner):
        pass

Comme vous pouvez le voir, il hérite de Callback de Keras-rl. Il est utilisé tel quel par l'agent.

Notez que le train, l'apprenant et l'acteur sont censés être appelés par un autre processus. Ainsi, même si vous écrivez un processus qui les chevauche, la valeur ne sera pas conservée car le processus est différent.

Enregistrer / charger et enregistrer en utilisant ceux-ci sera expliqué dans la section des paramètres.

GPU

Lorsque j'exécute le GPU tel quel avec tensorflow 2.1.0, j'obtiens l'erreur suivante.

tensorflow.python.framework.errors_impl.InternalError:  Blas GEMM launch failed : a.shape=(32, 12), b.shape=(12, 128), m=32, n=128, k=12     

Apparemment, c'est une erreur qui se produit lors de l'utilisation du GPU dans plusieurs processus. Reportez-vous à ce qui suit et configurez-le pour utiliser le GPU dans plusieurs processus.

#Je veux que vous le définissiez pour tous les processus, donc il est décrit globalement
for device in tf.config.experimental.list_physical_devices('GPU'):
    tf.config.experimental.set_memory_growth(device, True)

De plus, j'écris un processus qui détermine automatiquement s'il s'agit d'un CPU ou d'un GPU dans R2D2Manager.

import tensorflow as tf

def train(self):
    (réduction)
    if len(tf.config.experimental.list_physical_devices('GPU')) > 0:
        self.enable_GPU = True
    else:
        self.enable_GPU = False
    (réduction)

Autres implémentations

Extension ImageModel

La couche de traitement d'image dans NN (Neural Network) est inchangée par rapport à DQN. Je l'ai donc étendu pour pouvoir le changer ici.

La couche NN dans DQN est la suivante.

couche Aperçu
1 Couche d'entrée
2 Couche de conversion d'entrée Couche pour généraliser le format d'entrée
3 Couche de traitement d'image Pour le traitement d'image
4 Couche LSTM Lors de l'utilisation de LSTM
5 couche réseau duel Lors de l'utilisation du réseau de duel
6 Couche dense Inclus lors de l'utilisation du réseau de duel
7 (Couche de sortie) En fait inclus dans la couche réseau duel

Généralisation de la couche de conversion d'entrée

La couche de conversion d'entrée est une couche qui crée une sortie unidimensionnelle (Flatten) pour le format d'entrée. Il est créé en supposant les quatre types d'entrée suivants.

InputType


import enum
class InputType(enum.Enum):
    VALUES = 1    #Pas d'image
    GRAY_2ch = 3  # (width, height)
    GRAY_3ch = 4  # (width, height, 1)
    COLOR = 5     # (width, height, ch)

Couche d'entrée sans image (VALUES) (sans LSTM)

Il suffit de l'aplatir.

input_sequence = 4
input_shape = 3

c = Input(shape=(input_sequence,) + input_shape)
# output_shape == (None, 4, 3)

c = Flatten()(c)
# output_shape == (None, 12)

Couche d'entrée sans image (VALUES) (avec LSTM)

Il suffit de l'aplatir tel quel. Enveloppé dans TimeDistributed pour contenir les pas de temps.

batch_size = 16
input_sequence = 4
input_shape = 3

c = Input(batch_shape=(batch_size, input_sequence,) + input_shape)
# output_shape == (16, 4, 3)

c = TimeDistributed(Flatten())(c)
# output_shape == (16, 4, 3)

Couche d'entrée de l'image grise (sans canal) (GRAY_2ch) (sans LSTM)

C'est la conversion utilisée dans DQN. Remplacez le canal par input_sequence (taille d'entrée).

input_sequence = 4
input_shape = (84, 84)  #(widht, height)

c = Input(shape=(input_sequence,) + input_shape)
# output_shape == (None, 4, 84, 84)

c = Permute((2, 3, 1))(c)  #Calque pour changer l'ordre
# output_shape == (None, 84, 84, 4)

c =Couche de traitement d'image(c)

Couche d'entrée de l'image grise (sans canal) (GRAY_2ch) (avec LSTM)

Si LSTM est activé, les informations de séquence peuvent être complétées par des pas de temps. Nous augmentons la couche de canal.

batch_size = 16
input_sequence = 4
input_shape = (84, 84)  #(widht, height)

c = Input(batch_shape=(batch_size, input_sequence,) + input_shape)
# output_shape == (16, 4, 84, 84)

c = Reshape((input_sequence, ) + input_shape + (1,) )(c)  #Ajouter une couche de canal
# output_shape == (16, 4, 84, 84, 1)

c =Couche de traitement d'image(c)

Couche d'entrée d'image (avec canal) (GRAY_3ch, COLOR) (sans LSTM)

Passez-le au calque de traitement d'image tel quel. Cependant, les informations de input_sequence ne peuvent pas être exprimées.

input_sequence = 4
input_shape = (84, 84, 3)  #(widht, height, channel)

c = Input(shape=input_shape)
# output_shape == (None, 84, 84, 3)

c =Couche de traitement d'image(c)

Couche d'entrée d'image (avec canal) (GRAY_3ch, COLOR) (avec LSTM)

Il n'y a pas de différence.

batch_size = 16
input_sequence = 4
input_shape = (84, 84, 3)  #(widht, height, channel)

c = Input(batch_shape=(batch_size, input_sequence,) + input_shape)
# output_shape == (16, 4, 84, 84, 3)

c =Couche de traitement d'image(c)

Généralisation de la couche de traitement d'image

La classe ImageModel est définie pour que le calque puisse être modifié.

L'argument c de create_image_model est passé au format suivant.

Sans LSTM: forme(batch_size, width, height, channel) 
Avec LSTM: forme(batch_size, timesteps, width, height, channel)

La valeur de retour doit être au format suivant:

Sans LSTM: forme(batch_size, dim) 
Avec LSTM: forme(batch_size, timesteps, dim)

Voici un exemple de format DQN.

DQNImageModel


class DQNImageModel(ImageModel):
    """ native dqn image model
    https://arxiv.org/abs/1312.5602
    """

    def create_image_model(self, c, enable_lstm):
        """
        c shape(batch_size, width, height, channel)
        return shape(batch_size, dim)
        """

        if enable_lstm:
            c = TimeDistributed(Conv2D(32, (8, 8), strides=(4, 4), padding="same"), name="c1")(c)
            c = Activation("relu")(c)
            
            c = TimeDistributed(Conv2D(64, (4, 4), strides=(2, 2), padding="same"), name="c2")(c)
            c = Activation("relu")(c)
            
            c = TimeDistributed(Conv2D(64, (3, 3), strides=(1, 1), padding="same"), name="c3")(c)
            c = Activation("relu")(c)
            
            c = TimeDistributed(Flatten())(c)

        else:
                
            c = Conv2D(32, (8, 8), strides=(4, 4), padding="same", name="c1")(c)
            c = Activation("relu")(c)

            c = Conv2D(64, (4, 4), strides=(2, 2), padding="same", name="c2")(c)
            c = Activation("relu")(c)

            c = Conv2D(64, (3, 3), strides=(1, 1), padding="same", name="c3")(c)
            c = Activation("relu")(c)

            c = Flatten()(c)
        return c

Extension de politique

L'article de commentaire précédent est ci-dessous.

DQN utilise uniquement la politique de recherche de méthode ε-gourmande, mais certaines politiques sont introduites dans l'article ci-dessus. Je les ai implémentés pour qu'ils puissent être utilisés, mais il semble que ε-gourmand soit suffisant. Les détails seront expliqués dans la section des paramètres.

Épilogue

Je l'ai mis en œuvre pour le moment. La prochaine fois, j'aimerais faire un exemple d'article sur la façon de définir chaque paramètre.

Liens papier

Recommended Posts

[Renforcement de l'apprentissage] Commentaire de vengeance (Keras-RL) qui a tenté de mettre en œuvre / d'expliquer R2D2
[Renforcement de l'apprentissage] R2D2 implémenté / expliqué Explication du paramètre Revenge Hyper (Keras-RL)
[Renforcer l'apprentissage] J'ai implémenté / expliqué R2D3 (Keras-RL)
Apprentissage par renforcement profond 2 Mise en œuvre de l'apprentissage par renforcement
[Python] Essayez facilement l'apprentissage amélioré (DQN) avec Keras-RL
[Introduction] Renforcer l'apprentissage
Apprentissage par renforcement futur_2
Apprentissage par renforcement futur_1