[PYTHON] [Lernen stärken] Rache-Kommentar (Keras-RL), der versucht hat, R2D2 zu implementieren / zu erklären

Zuvor implementiertes R2D2, aber Mini-Batch-Lernen konnte nicht implementiert werden. Danach habe ich es dieses Mal durch Ausprobieren geschafft.

Es ist lange her seit dem vorherigen Artikel, daher werde ich den Gesamtfluss grob erklären. Wir werden auch alle Fehler in der vorherigen Implementierung korrigieren. .. ..

Darüber hinaus besteht dieser Artikel aus zwei Teilen, einem Kommentarbereich und einem Abschnitt zur Einstellung von Hyperparametern. Siehe unten für Hyperparameter [Lernen stärken] R2D2-Implementierung / Erklärung Rache Hyperparameter Erklärung (Keras-RL)

Nachtrag: R2D3 wurde ebenfalls implementiert. [Lernen stärken] Ich habe R2D3 (Keras-RL) implementiert / erklärt

Ganzer Code

Der in diesem Artikel erstellte Code ist unten aufgeführt. Diesmal nur Github.

Inhaltsverzeichnis

Erklärung zur Implementierung von DQN (Rainbow)

Als Rückblick werde ich das Bild der Implementierung von DQN (Rainbow) noch einmal erläutern. Eine ausführliche Erklärung finden Sie im zuvor veröffentlichten Artikel.

Das Folgende ist eine Zusammenfassung der Bilder des Lernens mit DQN (Rainbow).

zu1.PNG

zu2.PNG

DQN speichert Erfahrungsdaten (Erfahrung) wie folgt im Speicher.

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

Wenn der Schritt des mehrstufigen Lernens 1 ist, ist der nächste Zustand $ t + 1 $, Wenn es 3 Schritte sind, ist es $ t + 3 $.

Formel Zahl
Vorheriger Status s_{t} observation: t(n-6) ~ t(n-3)
Nächster Zustand s_{t+1} observation: t(n-3) ~ t(n)
Aktion a_{t} action: t(n-3)
Belohnung r_{t} reward: t(n)

Darüber hinaus ist die Größe in jeder Variablen wie folgt.

Länge zu halten Länge zum Speichern im Speicher
rewards multisteps 0(Wird nur für Berechnungen verwendet)
Berechnete Belohnungen 1 1(Aktuellen Zustand)
actions multisteps + 1 1(Vorheriger Status)
observations input_sequence + multisteps input_sequence + multisteps

Falsche Aktion, auf die beim mehrstufigen Lernen verwiesen wird

Im vorherigen Artikel Mehrstufiges Lernen habe ich mich auf die Aktion mit $ t_n $ bezogen, was falsch ist. Du ... $ T_ {n-Multisteps} $ war die richtige Antwort, da sie sich auf die Aktion im vorherigen Status bezieht.

Falsche Stichprobenerhebung

Der vorherige Artikel ist unten.

Um es einfach auszudrücken: Wichtige Stichproben werden beim Abrufen von Erfahrungen mit Priority Experience Reply priorisiert. Dann wird die Anzahl der gesammelten Erfahrungen verzerrt. Dann wird die Verzerrung das Lernen beeinflussen, daher ist es wichtig, dies zu korrigieren.

Insbesondere hat eine mit hoher Wahrscheinlichkeit ausgewählte Erfahrung eine niedrige Reflexionsrate zum Aktualisieren des Q-Werts, und eine mit niedriger Wahrscheinlichkeit ausgewählte Erfahrung hat eine hohe Reflexionsrate zum Aktualisieren des Q-Werts.

In der Vergangenheit schien die Implementierung etwas seltsam zu sein und sie lernte nicht gut. Zuvor wurde es auf den aktualisierten Q-Wert selbst angewendet, sollte jedoch auf td_error selbst angewendet werden. (Die Benennung der Variablen war nicht gut.) Da dies in der Aktualisierung des Q-Werts berücksichtigt wird, wird es auch nicht auf die Priorität angewendet.

-Vorherige Implementierung (Pseudocode)

IS


def train():

    #Holen Sie sich Erfahrung von PER nach Wahrscheinlichkeit
    batchs, batch_weight = memory.sample(batch_size)

    #Ruft den Q-Wert des vorherigen Status aus dem Modell ab
    # state0_qvals enthält den Q-Wert für jede Aktion
    state0_qvals = model.predict(state0_batch)
    
    for batch_i in range(batch_size):
        reward = batchs[batch_i]Belohnung
        action = batchs[batch_i]Aktion
        q0 = state0_qvals[batch_i][action]  #Q-Wert vor dem Update

        #Modell und Ziel_Ermitteln Sie den maximalen Q-Wert des aktuellen Status mithilfe des Modells
        # (Die Erfassungsmethode unterscheidet sich zwischen DQN und DDQN)
        maxq =Modell und Ziel_Holen Sie sich vom Modell

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

        priority = abs(td_error - q0)
        
        #Lernen Sie, indem Sie nur den Q-Wert der Zielaktion ändern
        state0_qvals[batch_i][action] = td_error

    # train
    model.train_on_batch(state0_qvals)

-Implementierung nach Änderung (Pseudocode)

IS


def train():

    #Holen Sie sich Erfahrung von PER nach Wahrscheinlichkeit
    batchs, batch_weight = memory.sample(batch_size)

    #Ruft den Q-Wert des vorherigen Status aus dem Modell ab
    # state0_qvals enthält den Q-Wert für jede Aktion
    state0_qvals = model.predict(state0_batch)
    
    for batch_i in range(batch_size):
        reward = batchs[batch_i]Belohnung
        action = batchs[batch_i]Aktion
        q0 = state0_qvals[batch_i][action]  #Q-Wert vor dem Update

        #Modell und Ziel_Ermitteln Sie den maximalen Q-Wert des aktuellen Status mithilfe des Modells
        # (Die Erfassungsmethode unterscheidet sich zwischen DQN und DDQN)
        maxq =Modell und Ziel_Holen Sie sich vom Modell

        #※ -Addiere q0 und td richtig_Geben Sie einen Fehler aus
        #* Auch Charge_Tragen Sie hier Gewicht auf
        td_error = reward + (gamma ** reward_multisteps) * maxq - q0

        #※ td_Der absolute Fehlerwert wird so wie er ist Priorität
        priority = abs(td_error)
        
        #Lernen Sie, indem Sie nur den Q-Wert der Zielaktion ändern
        #※ td_Da der Fehler zu einem Unterschied wurde, wenden Sie Gewicht an und aktualisieren Sie den Q-Wert mit dem Unterschied.
        state0_qvals[batch_i][action] += td_error * batch_weight

    # train
    model.train_on_batch(state0_qvals)

Erklärung zur Implementierung von R2D2

Mini-Batch-Lernen

Bisher konnte das Lernen von Mini-Bienen nicht implementiert werden, da Keras 'zustandsbehaftetes LSTM nicht gut verstanden wurde. Die vorherigen Forschungsartikel lauten wie folgt.

zu3.PNG

Anscheinend gibt es in hidden_states Zustände im Wert von batch_size, und Sie können sie angeben. Jetzt können Sie mehrere Trainings gleichzeitig zwischen Sequenzen durchführen.

DRQN(R2D2) Um die Geschichte verständlicher zu machen, werde ich mit R2D2 erklären, bei dem der Parallelverarbeitungsteil entfernt wurde. Der vorherige Artikel ist unten.

Es ist ein Bilddiagramm wie DQN.

zu4.PNG

zu5.PNG

Es ist ziemlich kompliziert ... Ich habe diese Abbildung geschrieben, weil ich bei der Implementierung verwirrt war ...

Die Methode zum Aktualisieren des Q-Werts und zum Ausgeben der Priorität ist dieselbe wie bei DQN, daher wird sie in der Abbildung weggelassen.

Die Punkte sind Eingabesequenz und Eingabelänge. Das letzte Mal war mir das nicht bewusst. (Unter der Annahme, dass die Eingabesequenz = 1 ist, wurde die Eingabelänge als Eingabesequenz ausgedrückt.)

Die Eingabesequenz ist die Länge des Zustands, der in das Modell eingegeben werden soll, und die Anzahl der Eingaben ist die Eingabelänge. Der Q-Wert wird für jede Eingabelänge aktualisiert und die Priorität wird ebenfalls berechnet. (Ich bin mir über diese Interpretation ein wenig unsicher, aber in Abschnitt 2.3 des R2D2-Papiers habe ich einen neuen Weg vorgeschlagen, um die Priorität festzulegen, und es ist sinnvoll zu glauben, dass eine Erfahrung mehrere Prioritäten gibt, wie oben beschrieben.)

Die in jeder Variablen enthaltene Größe ist wie folgt.

Länge zu halten Länge zum Speichern im Speicher
rewards multisteps + input_length - 1 0(Wird nur für Berechnungen verwendet)
Berechnete Belohnungen input_length input_length
actions multisteps + input_length input_length(Aus dem vorherigen Zustand)
hidden states burnin + multisteps + input_length + 1 1(Ältester Staat)
observations burnin + input_sequence + multisteps + input_length - 1 0(Zur Zusammenfassung unten)
Zusammenfassende Beobachtungen burnin + multisteps + input_length Die gleiche Länge

Neuskalierungsfunktion

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

rescaling2.png

Die Neuskalierungsfunktion wurde in R2D2 eingeführt und sollte anstelle des Belohnungsbeschneidens (-1 bis 1) verwendet werden. Früher habe ich mir Sorgen um die Umkehrfunktion gemacht, aber ich habe sie gewaltsam unnötig gemacht.

Die Formel zum Ableiten des TD-Fehlers unter Verwendung der Neuskalierungsfunktion lautet wie folgt. ($ y_t $ ist der TD-Fehler)

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

Erweitern Sie $ h () $ in der obigen Formel.

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

Durch Anwenden der Umkehrfunktion auf eine Funktion wird der ursprüngliche Wert wiederhergestellt. * $ H (h ^ {-1} (x)) = x $ Die rechte Seite kann also versetzt werden ($ \ gamma $ wird als Fehler ignoriert ...)

Dann wird es wie folgt.

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

Die Neuskalierungsfunktion wird jetzt nur auf Belohnungen angewendet ($ r_ {t} $). Wenn Sie sich die Grafik ansehen, können Sie sehen, dass die Belohnungen gut gerundet sind. (100 Belohnungen sind ungefähr 10) Es ist eine gute Alternative zum Ausschneiden.

Parallelverarbeitung (prozessübergreifende Kommunikation)

Der vorherige Artikel ist unten.

Referenz: Vollständiges Verständnis von Python-Threading und Multiprocessing

Zuerst habe ich Queue verwendet, aber da die Menge der Gewichtsdaten groß war und es sich um einen Engpass handelte, habe ich die Kommunikation zwischen den einzelnen Prozessen untersucht. Die Umfrageergebnisse sind die folgenden Artikel.

Daraus ergibt sich folgende Kommunikation. (Infolgedessen wird die Warteschlange so verwendet, wie sie ist.)

zu10.PNG

zu11.PNG

zu12.PNG

Der Informationsaustausch zwischen Prozessen wird im gemeinsamen Speicher implementiert. Es gibt keine besondere Sperre, da der Schreiber und der Leser klar voneinander getrennt sind.

Callbacks

Es stellt sich heraus, dass die Kommunikation zwischen Prozessen ziemlich kostspielig ist Ich habe es implementiert, weil es einen Prozess gab, der Schauspieler und Leaner überspannte.

Ich erstelle es hauptsächlich zum Speichern / Laden und für Protokolle. Die Basisklasse des implementierten Rückrufs lautet wie folgt.

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 ---
    #Unten und rl.callbacks.Rückrufvererbungsmethode

    def on_r2d2_actor_begin(self, actor_index, runner):
        pass

    def on_r2d2_actor_end(self, actor_index, runner):
        pass

Wie Sie sehen können, erbt es von Keras-rls Rückruf. Es wird unverändert vom Agenten verwendet.

Beachten Sie, dass Zug, Lernender und Schauspieler von einem anderen Prozess angerufen werden sollen. Selbst wenn Sie einen Prozess schreiben, der diese überspannt, wird der Wert daher nicht beibehalten, da der Prozess anders ist.

Das Speichern / Laden und Protokollieren mit diesen wird im Parameterabschnitt erläutert.

GPU

Wenn ich die GPU wie bei Tensorflow 2.1.0 ausführe, wird der folgende Fehler angezeigt.

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

Anscheinend ist es ein Fehler, der auftritt, wenn die GPU in mehreren Prozessen verwendet wird. Lesen Sie die folgenden Informationen und stellen Sie die Verwendung der GPU in mehreren Prozessen ein.

#Ich möchte, dass Sie es für alle Prozesse festlegen, damit es global beschrieben wird
for device in tf.config.experimental.list_physical_devices('GPU'):
    tf.config.experimental.set_memory_growth(device, True)

Außerdem schreibe ich in R2D2Manager einen Prozess, um automatisch zu bestimmen, ob es sich um eine CPU oder eine GPU handelt.

import tensorflow as tf

def train(self):
    (Kürzung)
    if len(tf.config.experimental.list_physical_devices('GPU')) > 0:
        self.enable_GPU = True
    else:
        self.enable_GPU = False
    (Kürzung)

Andere Implementierungen

ImageModel-Erweiterung

Die Bildverarbeitungsschicht in NN (Neural Network) ist gegenüber DQN unverändert. Also habe ich es erweitert, damit ich es hier ändern kann.

Die NN-Schicht in DQN ist wie folgt.

Schicht Überblick
1 Eingabeebene
2 Eingabekonvertierungsschicht Ebene zur Verallgemeinerung des Eingabeformats
3 Bildverarbeitungsschicht Zur Bildverarbeitung
4 LSTM-Schicht Bei Verwendung von LSTM
5 Duell Netzwerkschicht Bei Verwendung eines Duell-Netzwerks
6 Dichte Schicht Bei Verwendung eines Duell-Netzwerks enthalten
7 (Ausgabeschicht) Eigentlich in der Duell-Netzwerkschicht enthalten

Verallgemeinerung der Eingabekonvertierungsschicht

Die Eingabekonvertierungsebene ist eine Ebene, die eine eindimensionale Ausgabe (Abflachen) für das Eingabeformat erstellt. Es wird unter der Annahme der folgenden vier Eingabetypen erstellt.

InputType


import enum
class InputType(enum.Enum):
    VALUES = 1    #Kein Bild
    GRAY_2ch = 3  # (width, height)
    GRAY_3ch = 4  # (width, height, 1)
    COLOR = 5     # (width, height, ch)

Eingabeebene ohne Bild (VALUES) (ohne LSTM)

Einfach abflachen.

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)

Eingabeebene ohne Bild (VALUES) (mit LSTM)

Flache es einfach so wie es ist. Eingehüllt in TimeDistributed, um Zeitschritte zu halten.

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)

Eingabeebene des grauen Bildes (ohne Kanal) (GRAY_2ch) (ohne LSTM)

Es ist die in DQN verwendete Konvertierung. Ersetzen Sie den Kanal durch input_sequence (Eingangsgröß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)  #Ebene zum Ändern der Reihenfolge
# output_shape == (None, 84, 84, 4)

c =Bildverarbeitungsschicht(c)

Eingabeebene des grauen Bildes (ohne Kanal) (GRAY_2ch) (mit LSTM)

Wenn LSTM aktiviert ist, können Sequenzinformationen durch Zeitschritte ergänzt werden. Wir erhöhen die Kanalschicht.

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)  #Kanalebene hinzufügen
# output_shape == (16, 4, 84, 84, 1)

c =Bildverarbeitungsschicht(c)

Bildeingabeebene (mit Kanal) (GRAY_3ch, COLOR) (ohne LSTM)

Übergeben Sie es so wie es ist an die Bildverarbeitungsebene. Die Informationen von input_sequence können jedoch nicht ausgedrückt werden.

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

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

c =Bildverarbeitungsschicht(c)

Bildeingabeebene (mit Kanal) (GRAY_3ch, COLOR) (mit LSTM)

Es gibt keinen Unterschied.

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 =Bildverarbeitungsschicht(c)

Verallgemeinerung der Bildverarbeitungsschicht

Die ImageModel-Klasse ist so definiert, dass die Ebene geändert werden kann.

Das Argument c von create_image_model wird im folgenden Format übergeben.

Ohne LSTM: Form(batch_size, width, height, channel) 
Mit LSTM: Form(batch_size, timesteps, width, height, channel)

Der Rückgabewert sollte das folgende Format haben:

Ohne LSTM: Form(batch_size, dim) 
Mit LSTM: Form(batch_size, timesteps, dim)

Das Folgende ist ein Beispiel für das DQN-Format.

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

Richtlinienerweiterung

Der vorherige Kommentarartikel ist unten.

DQN verwendet nur die Suchrichtlinie für die ε-gierige Methode. Im obigen Artikel wurden jedoch einige Richtlinien eingeführt. Ich habe sie implementiert, damit sie verwendet werden können, aber es scheint, dass ε-gierig genug ist. Details werden im Parameterabschnitt erläutert.

Nachwort

Ich habe es vorerst implementiert. Das nächste Mal möchte ich einen Beispielartikel zum Einstellen der einzelnen Parameter erstellen.

Papierlinks

Recommended Posts

[Lernen stärken] Rache-Kommentar (Keras-RL), der versucht hat, R2D2 zu implementieren / zu erklären
[Lernen stärken] R2D2 implementiert / erklärt Revenge Hyper Parameter Explanation (Keras-RL)
[Lernen stärken] Ich habe R2D3 (Keras-RL) implementiert / erklärt.
Tiefes Lernen der Verstärkung 2 Implementierung des Lernens der Verstärkung
[Python] Probieren Sie mit Keras-RL ganz einfach erweitertes Lernen (DQN) aus
[Einführung] Stärkung des Lernens
Zukünftiges Verstärkungslernen_2
Zukünftiges Verstärkungslernen_1