Reinforcement Learning mit Keras + OpenAI: Die Grundlagen

Das Reinforcement-Lernen wurde von vielen als eine der Gateway-Technologien / -Konzepte bezeichnet, die aus den theoretischen Studien des maschinellen Lernens hervorgegangen sind. Bevor wir uns mit dem Code befassen, erhalten wir einen kurzen Überblick über das Erlernen von Verstärkung.

Schneller Hintergrund

Reinforcement Learning (RL) ist ein allgemeiner Überbegriff für jeden Algorithmus, für den keine expliziten Datenpaare und die entsprechenden gewünschten Bezeichnungen erforderlich sind, wie dies beim herkömmlichen überwachten Lernen der Fall ist, sondern für den eine numerische Angabe erforderlich ist, wie eine Stichprobe aussieht Die „Güte“ einer Probe hat absolut keine Bedeutung. Sie können sich das generisch als Ihre Punktzahl in einem Videospiel vorstellen. Wenn der Bildschirm eine Punktzahl von „218“ anzeigt, die für Sie, den Spieler, vermutlich absolut bedeutungslos ist, es sei denn, Sie wissen, wie schwierig oder einfach es ist, einen Punkt zu verdienen, und mit welcher Punktzahl Sie beginnen. Und das ist im Grunde das Ausmaß des Hintergrunds, auf das wir uns konzentrieren werden: Es wird in Zukunft ausführlichere Diskussionen über RL geben, aber der Code, den wir in diesem Beitrag durcharbeiten, ist ein sehr einfaches Beispiel für RL und beinhaltet daher keine weitere Einmischung in die Fachtheorie.

Keras Notes

Willkommen für alle, die gerade erst mit der AI / ML-Programmierung beginnen! Das Feld ist in den letzten Jahren so stark gewachsen, dass es ziemlich überwältigend ist, gerade jetzt einzuspringen. Aber es bleibt noch viel Zeit, sich in diesem riesigen Feld zu engagieren und zu lernen! Dementsprechend ist Keras die Bibliothek, die ich in erster Linie für die kommenden Tutorials verwende, einschließlich dieses Tutorials. Keras ist im Wesentlichen eine Wrapper-Bibliothek für Tensorflow und Theano. Die Benutzeroberfläche ist der von tflearn sehr ähnlich, lässt sich jedoch etwas allgemeiner auf Theano als Backend anwenden. Beachten Sie jedoch! Die Abmessungen in Theano unterscheiden sich geringfügig von denen in Tensorflow. Ich würde daher empfehlen, Ihre Keras so anzupassen, dass TF als Backend verwendet wird, um Frustrationen mit zukünftigen Dimensionen zu vermeiden (sollte die Standardeinstellung bei der Installation sein, wenn Sie TF bereits installiert haben).

Sie könnten dies auch genauso einfach mit TF tun, aber Keras gibt uns die nette Flexibilität, wenn wir anfangen, Dimensionen nicht durch Windungen und all diesen Mist verfolgen zu müssen. Wie auch immer, genug Worte: Zeit, um zum Code zu gelangen!

Code

Wir werden hier die grundlegendste OpenAI-Umgebung erkunden: CartPole! Zum Schluss finden Sie eine kurze Anleitung zur Installation des OpenAI-Fitnesspakets unter https://gym.openai.com/docs. Das Ausführen von "sudo pip install gym" sollte auf den meisten Plattformen funktionieren.

Die CartPole-Umgebung hat eine sehr einfache Voraussetzung: Ausbalancieren der Stange auf dem Wagen.

Datensammlung

Der erste Teil eines maschinellen Lernproblems ist das Sammeln der Daten, und dies ist nicht anders. Glücklicherweise bietet die Fitnessumgebung von OpenAI eine sehr einfache Möglichkeit zum Erfassen von Daten: Wir können die Simulation im Wesentlichen nur viele Male durchlaufen und jedes Mal zufällige Schritte ausführen. OpenAI Gym-Umgebungen gliedern sich in zwei Hauptteile: einen Beobachtungsraum und einen Aktionsraum. Wir beobachten erstere von der Umgebung aus und bestimmen damit, wie sie am besten durch eine Aktion aktualisiert werden kann, d. H. Basierend auf dem aktuellen Zustand des Pfostens (Beobachtung), ob der Wagen nach links oder rechts bewegt werden soll (Aktion).

Als Ergebnis müssen wir eine Aktion ausführen, die in den Bereich der zulässigen Aktionen des Aktionsbereichs passt, der in diesem Fall die Größe 2 hat (links oder rechts). Wir nehmen den Ausgaberaum als One-Hot-Codierung an. Der Grund dafür ist, dass das neuronale Netz die Wahrscheinlichkeit einer Bewegung von links nach rechts unter Berücksichtigung des aktuellen Umgebungszustands vorhersagen soll. In diesem Fall könnten wir es vermeiden, nur eine einzige 1x1-Float-Matrix (d. H. Einen Skalar) als Ausgabe zu haben und sie für unser Endergebnis zu runden, aber die One-Hot-Codierungspraxis kann umfassender angewendet werden.

Um die Handlungen und die entsprechenden Beobachtungen zusammenzufassen, kann ein erster Gedanke einfach sein:

für _ in range (10000):
    Beobachtung = env.reset ()
    training_sampleX, training_sampleY = [], []
    für Schritt in Reichweite (sim_steps):
        action = np.random.randint (0, 2)
        one_hot_action = np.zeros (2)
        one_hot_action [action] = 1
        training_sampleX.append (Beobachtung)
        training_sampleY.append (one_hot_action)
        
        Beobachtung, Belohnung, erledigt, _ = env.step (Aktion)
        wenn fertig:
            brechen
    trainingX + = training_sampleX
    trainingY + = training_sampleY

Wenn wir dies jedoch trainieren würden, würde der endgültige Prädiktor wahrscheinlich nicht besser sein als die zufällige Chance. Immerhin „Müll rein, Müll raus“: Wir würden dem neuronalen Netz nur eine Sammlung von sowohl guten als auch schlechten Proben zuführen und erwarten, dass es nur von den Guten lernt. Wenn wir jedoch einen Schritt zurücktreten, ist dies völlig unplausibel, da eine einzelne Stichprobe nicht von einer anderen zu unterscheiden ist, selbst wenn diejenigen, die aus guten Versuchen stammen, mit denen aus schlechten Versuchen verglichen werden.

Wir werden uns stattdessen nur die Stichproben ansehen, die zu Versuchen mit hohen Punktzahlen führen. Das heißt, wir möchten die Stichproben filtern, um nur diejenigen zuzulassen, die in ihren Versuchen zu hohen Punktzahlen führen. In diesem Fall haben wir willkürlich 50 als "Mindestgrenzwert" ausgewählt, um als "guter Versuch" zu gelten, und nur die folgenden Stichproben ausgewählt:

def gather_data (env):
    min_score = 50
    sim_steps = 500
    trainingX, trainingY = [], []
    Scores = []
    für _ in range (10000):
        Beobachtung = env.reset ()
        score = 0
        training_sampleX, training_sampleY = [], []
        für Schritt in Reichweite (sim_steps):
            action = np.random.randint (0, 2)
            one_hot_action = np.zeros (2)
            one_hot_action [action] = 1
            training_sampleX.append (Beobachtung)
            training_sampleY.append (one_hot_action)
            
            Beobachtung, Belohnung, erledigt, _ = env.step (Aktion)
            Punktzahl + = Belohnung
            wenn fertig:
                brechen
        if score> min_score:
            scores.append (score)
            trainingX + = training_sampleX
            trainingY + = training_sampleY
    trainingX, trainingY = np.array (trainingX), np.array (trainingY)
    print ("Average: {}". format (np.mean (scores)))
    print ("Median: {}". Format (np.median (score)))
    return trainingX, trainingY

Modelldefinition

Nachdem wir die Daten haben, müssen wir das Modell definieren. Bevor Sie ein Problem mit maschinellem Lernen lösen, sollten Sie immer einen Schritt zurücktreten und sich überlegen, was wir modellieren, insbesondere welche Eingaben und gewünschten Ergebnisse zu erwarten sind. In unserem Fall erhalten wir den aktuellen Zustand der Umgebung (d. H. Die "Beobachtungen" von früher) und möchten die Wahrscheinlichkeiten der Bewegung in jede der beiden Richtungen vorhersagen. Daraus können wir leicht herausfinden, welche der beiden zu nehmen ist, indem wir das max arg nehmen.

Das Modell, das wir hier verwenden, ist sehr einfach: Mehrere vollständig verbundene Ebenen (a.k.a. dichte Ebenen in Keras). Dies sind häufig die letzten Ebenen, die in tiefen CNNs (Convolution Neural Networks) verwendet werden, da sie alle Feature-Maps oder Eingabe-Ebenen zu den endgültigen Skalarwerten kombinieren. Vollständig verbundene Schichten bilden im Wesentlichen das Rückgrat neuronaler Netze und ermöglichen es ihnen, hochdimensionale Funktionen effektiv abzubilden, wobei alle modernen Verbesserungen mit Faltungen, LSTMs, Dropout usw. ignoriert werden.

Die einzige dieser Verbesserungen, die hier relevant ist, ist Dropout, da hierdurch sichergestellt wird, dass die Trainingsdaten nicht zu stark angepasst werden. Wir schieben also im Wesentlichen eine Dropout-Ebene zwischen jedes vollständig verbundene Mapping, um sicherzustellen, dass keine einzelne Ebene des Mappings von einer kleinen Teilmenge von Verbindungen abhängig ist, die in den Trainingsdaten spezifisch erkennbar sind.

Schließlich müssen wir die Verlustfunktion bestimmen, gegen die wir trainieren werden. Da wir den Ausgaberaum als One-Hot-2D-Vektor codiert haben, wird die natürliche Wahl zur kategorialen Kreuzentropie, vorausgesetzt, wir möchten das Ergebnis als links ([1,0]) oder rechts ([0,1]) identifizieren. Ich werde nicht näher darauf eingehen, was Kreuzentropie bedeutet, aber es ist eine sehr lohnende Funktion, dies zu verstehen, da es in dieser Art von Problemen häufig vorkommt. Von einem hohen Niveau aus ist die Kreuzentropie bei zwei Verteilungen (eine wahre zugrunde liegende Verteilung und unser Modell davon) ein Maß dafür, wie viel Information wir benötigen, um etwas zu vermitteln, das von der wahren Verteilung durch die Modellverteilung gezogen wird.

Daher definieren wir das Modell als:

aus keras.models importieren Sequential
aus keras.layers importieren Dense, Dropout
def create_model ():
    model = Sequential ()
    model.add (Dense (128, input_shape = (4,), activation = "relu"))
    model.add (Ausfallende (0.6))
    
    model.add (Dichte (256, Aktivierung = "relu"))
    model.add (Ausfallende (0.6))
    
    model.add (Dense (512, activation = "relu"))
    model.add (Ausfallende (0.6))
    
    model.add (Dichte (256, Aktivierung = "relu"))
    model.add (Ausfallende (0.6))
    
    model.add (Dense (128, activation = "relu"))
    model.add (Ausfallende (0.6))
    model.add (Dense (2, Aktivierung = "softmax"))
    
    model.compile (
        loss = "categorical_crossentropy",
        optimizer = "adam",
        metrics = ["Genauigkeit"])
    Rückgabemodell

Einige subtilere technische Punkte des Modells: Jede der Ebenen des Modells verfügt über ReLU-Aktivierungen, damit das Modell schneller trainiert als mit sättigenden Aktivierungsfunktionen wie Tanh und Sigmoid. Das Modell wird wahrscheinlich auch in diesen Fällen trainiert, die Konvergenz dauert jedoch viel länger als bei Verwendung der ReLU-Aktivierung.

Prognose

Von dort aus können wir einfach unsere Trainingsdaten abrufen, das Modell trainieren und mehrere Versuche durchlaufen, um zu sehen, wie gut unser Modell funktioniert!

Import Fitnessstudio
numpy als np importieren
aus dem Datenimport gather_data
aus dem Modellimport create_model
def predict ():
    env = gym.make ("CartPole-v0")
    trainingX, trainingY = gather_data (env)
    model = create_model ()
    model.fit (trainingX, trainingY, epochen = 5)
    
    Scores = []
    num_trials = 50
    sim_steps = 500
    für den Versuch in Reichweite (num_trials):
        Beobachtung = env.reset ()
        score = 0
        für Schritt in Reichweite (sim_steps):
            action = np.argmax (model.predict (
                Beobachtungsform (1,4)))
            Beobachtung, Belohnung, erledigt, _ = env.step (Aktion)
            Punktzahl + = Belohnung
            wenn fertig:
                brechen
        scores.append (score)
    
    print (np.mean (score))

Vollständiger Code

Hier finden Sie Schritt für Schritt den vollständigen Quellcode der OpenAI Cartpole Keras-Implementierung!

Halten Sie Ausschau nach dem nächsten Keras + OpenAI-Tutorial!

Kommentiere und klicke auf unten, um die Unterstützung anzuzeigen!