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)
Le code créé dans cet article est ci-dessous. Cette fois seulement github.
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).
DQN stocke les données d'expérience (expérience) en mémoire comme suit.
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 | observation: t(n-6) ~ t(n-3) | |
État suivant | observation: t(n-3) ~ t(n) | |
action | action: t(n-3) | |
Récompense | 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 |
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.
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)
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.
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.
Ç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 |
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)
Développez $ h () $ dans la formule ci-dessus.
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.
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.
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)
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)
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 |
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)
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)
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)
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)
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)
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)
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)
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
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.
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.
Recommended Posts