Förstärkningslärande w / Keras + OpenAI: Grunderna

Förstärkningslärande har av många förtalats som en av gateway-teknologierna / koncepten som har framkommit från teoretiska studier av maskininlärning. Vi kommer att gå igenom en mycket snabb översikt över förstärkningslärande innan vi dyker in i koden.

Snabb bakgrund

Förstärkningslärande (RL) är ett allmänt paraplyterm för alla algoritmer som inte kräver uttryckliga parpar och deras motsvarande önskade etiketter som är fallet i traditionellt övervakat inlärning men kräver viss numerisk indikering av "hur ett prov är." provets "godhet" har ingen mening i absolut mening. Du kan föreställa dig detta generiskt som din poäng i ett videospel. Om skärmen visar en poäng på "218", som förmodligen inte har någon mening för dig, spelaren, såvida du inte är medveten om hur svårt eller enkelt det är att tjäna en poäng och vilken poäng du börjar med. Och det är i grund och botten graden av bakgrund vi kommer att undersöka: det kommer att finnas mer djupgående diskussioner om RL i framtiden, men koden vi går igenom i det här inlägget är ett väldigt grundläggande exempel på RL och innebär inte några vidare blandning i ämnet teorin.

Keras-anteckningar

Välkommen för alla som bara kommer igång med AI / ML-programmering! Fältet har vuxit så mycket de senaste åren att det är ganska överväldigande att hoppa in just nu. Men det finns fortfarande gott om tid att engagera sig och lära sig inom detta massiva område! I linje med det är Keras det bibliotek som jag främst kommer att använda för mina självstudier, inklusive den här. Keras är i huvudsak ett omslagsbibliotek för Tensorflow och Theano. Dess gränssnitt liknar det som blottas av tflearn men är lite mer generiskt vad gäller dess användbarhet för Theano som backend. Observera dock! Dimensionerna i Theano skiljer sig något från de i Tensorflow. Jag rekommenderar därför att du justerar din Keras så att du använder TF som backend för att undvika frustrationer med dimensioner framöver (bör vara standard när du installerar, om du redan har TF installerat).

Du kan också lika enkelt göra detta med TF, men Keras ger oss den trevliga flexibiliteten när vi börjar inte behöva hålla reda på dimensioner genom vridningar och all den skiten. Hur som helst, tillräckligt med ord: dags att gå till koden!

Koda

Vi kommer att utforska den mest grundläggande OpenAI-miljön här: CartPole! Som en sista snabbanmälan kan du hitta instruktionerna för att installera OpenAIs gympaket här: https://gym.openai.com/docs. Att bara köra "sudo pip install gym" bör fungera på de flesta plattformar.

CartPole-miljön har en mycket enkel förutsättning: balansera polen på vagnen.

Datainsamling

Den första delen av något maskininlärningsproblem är att samla in data, och den här är inte annorlunda. Lyckligtvis erbjuder OpenAIs gymmiljö ett mycket enkelt sätt att samla in data: vi kan i princip bara springa igenom simuleringen många gånger och ta slumpmässiga steg varje gång. OpenAI Gym-miljöer är strukturerade kring två huvuddelar: ett observationsutrymme och ett handlingsutrymme. Vi observerar förstnämnda från miljön och använder det för att bestämma hur man bäst ska uppdatera den genom en åtgärd, dvs baserat på polens nuvarande tillstånd (observation), avgöra om vagnen ska flyttas åt vänster eller höger (åtgärd).

Som ett resultat måste vi vidta en åtgärd som passar räckvidden för de tillåtna handlingarna i handlingsutrymmet, som är i storlek 2 i detta fall (vänster eller höger). Vi tar utgångsutrymmet för att vara en hetkodat, anledningen är att vi vill att nervnätet så småningom förutsäger sannolikheten för att röra sig åt vänster mot höger med tanke på miljöns nuvarande tillstånd. I det här fallet kan vi komma undan med att bara ha utgången som en enkel 1x1 flottörmatris (dvs en skalär) och runda den för vårt slutliga resultat, men en het kodningspraxis kan tillämpas mer allmänt.

Så för att ackumulera handlingarna och motsvarande observationer kan en första tanke helt enkelt vara:

för _ inom intervallet (10000):
    observation = env.reset ()
    training_sampleX, training_sampleY = [], []
    för steg inom räckvidd (sim_steps):
        action = np.random.randint (0, 2)
        one_hot_action = np.zeros (2)
        one_hot_action [action] = 1
        training_sampleX.append (observation)
        training_sampleY.append (one_hot_action)
        
        observation, belöning, gjort, _ = env.step (action)
        om gjort:
            ha sönder
    trainingX + = training_sampleX
    trainingY + = training_sampleY

Men om vi skulle träna på detta skulle den slutliga prediktornen sannolikt inte göra bättre än slumpmässiga chanser. När allt kommer "skräp in, skräp ut": vi skulle inte göra mer än att mata nervnätet en samling av både bra och dåliga prover och förväntar oss att det enbart skulle lära av det goda. Om vi ​​tar ett steg tillbaka är detta emellertid helt otroligt, eftersom ett enda prov inte kan skiljas från något annat, till och med att jämföra dem som kommer från goda försök och de från dåliga försök.

Så istället tittar vi bara på de prover som resulterar i försök med hög poäng. Det vill säga, vi vill filtrera proverna för att endast tillåta de som så småningom resulterar i höga poäng i sina försök. I det här fallet valde vi godtyckligt 50 att vara "minsta avbrott" för att betraktas som en "bra prövning" och bara välja de proverna:

def collect_data (env):
    min_score = 50
    sim_steps = 500
    trainingX, trainingY = [], []
    poäng = []
    för _ inom intervallet (10000):
        observation = env.reset ()
        poäng = 0
        training_sampleX, training_sampleY = [], []
        för steg inom räckvidd (sim_steps):
            action = np.random.randint (0, 2)
            one_hot_action = np.zeros (2)
            one_hot_action [action] = 1
            training_sampleX.append (observation)
            training_sampleY.append (one_hot_action)
            
            observation, belöning, gjort, _ = env.step (action)
            poäng + = belöning
            om gjort:
                ha sönder
        om poäng> min_score:
            scores.append (poäng)
            trainingX + = training_sampleX
            trainingY + = training_sampleY
    trainingX, trainingY = np.array (trainingX), np.array (trainingY)
    print ("Genomsnitt: {}". format (np.mean (poäng))
    print ("Median: {}". format (np.median (poäng))
    returträningX, träningY

Model Definition

Nu när vi har uppgifterna måste vi gå vidare med att definiera modellen. Innan du gör något maskininlärningsproblem är det alltid värt att gå tillbaka för att överväga vad det är vi modellerar, specifikt vad de förväntade ingångarna och önskade resultat är. I vårt fall kommer vi att få det aktuella miljötillståndet (dvs "observationerna" från tidigare) och vill förutsäga sannolikheten för att röra sig i var och en av de två riktningarna. Från detta kan vi enkelt ta reda på vilken av de två man ska ta genom att ta max arg.

Modellen vi använder här är en mycket enkel modell: flera helt anslutna skikt (t.ex. täta lager i Keras). Dessa är ofta de sista lagren som används i djupa CNN: er (Convolution Neural Networks), eftersom det är de som kombinerar alla funktionskartor eller input-lager i de slutliga skalvärdena. Helt anslutna lager utgör i huvudsak ryggraden i nervnätverk och är det som gör det möjligt för dem att effektivt kartlägga högdimensionella funktioner, och ignorera alla moderna förbättringar med vridningar, LSTM: er, bortfall osv ...

Den enda av dessa förbättringar som är relevant här är Dropout, eftersom det hjälper till att säkerställa att vi inte överutrustar träningsdata. Så vi smälter i huvudsak ett Dropout-lager mellan varje helt anslutna kartläggning för att se till att inget lager av kartläggningen blir beroende av någon liten delmängd av anslutningar som är särskilt synliga i träningsdata.

Slutligen måste vi bestämma förlustfunktionen som vi kommer att träna mot. Eftersom vi kodade utgångsutrymmet som en en het 2D-vektor blir det naturliga valet kategorisk korsentropi, med tanke på att vi vill identifiera resultatet som antingen vänster ([1,0]) eller höger ([0,1]). Jag kommer inte att fördjupa mig om vad tvärentropi innebär, men det är en mycket värdefull funktion att förstå, med tanke på dess utbredning i sådana problem. Från en hög nivå är korsentropin, med tanke på två fördelningar (en sann underliggande distribution och vår modell därav), ett mått på hur mycket information vi behöver för att förmedla något som dras från den verkliga fördelningen med modellfördelningen.

Därför definierar vi modellen som:

från keras.models import Sequential
från keras.layers importerar Dense, Dropout
def create_model ():
    modell = Sekventiell ()
    model.add (Tät (128, input_shape = (4,), aktivering = "relu"))
    model.add (dropout (0,6))
    
    model.add (Tät (256, aktivering = "relu"))
    model.add (dropout (0,6))
    
    model.add (Tät (512, aktivering = "relu"))
    model.add (dropout (0,6))
    
    model.add (Tät (256, aktivering = "relu"))
    model.add (dropout (0,6))
    
    model.add (Tät (128, aktivering = "relu"))
    model.add (dropout (0,6))
    model.add (Tät (2, aktivering = "softmax"))
    
    model.compile (
        förlust = "categorical_crossentropy",
        Optimizer = "Adam",
        metrics = [ "noggrannheten"])
    returmodell

Några mer subtila tekniska punkter för modellen: vart och ett av lagren i modellen har ReLU-aktiveringar för att låta modellen träna snabbare än med mättande aktiveringsfunktioner, som tanh och sigmoid. Modellen kommer troligtvis också att träna i dessa fall, men det skulle ta mycket längre tid att konvergera än om man använder ReLU-aktivering.

Förutsägelse

Därifrån kan vi helt enkelt få våra träningsdata, träna modellen och upprepa igenom flera försök för att se hur bra vår modell presterar!

importera gymmet
importera numpy som np
från dataimport collect_data
från modellimport create_model
def förutse ():
    env = gym.make ("CartPole-v0")
    trainingX, trainingY = collect_data (env)
    modell = skapa_modell ()
    model.fit (trainingX, trainingY, epochs = 5)
    
    poäng = []
    num_trials = 50
    sim_steps = 500
    för prövning inom räckvidd (antal test):
        observation = env.reset ()
        poäng = 0
        för steg inom räckvidd (sim_steps):
            action = np.argmax (model.predict (
                observation.reshape (1,4)))
            observation, belöning, gjort, _ = env.step (action)
            poäng + = belöning
            om gjort:
                ha sönder
        scores.append (poäng)
    
    trycket (np.mean (poäng))

Fullständig kod

Med det steg-för-steg, här är den fullständiga källkoden för OpenAI Cartpole Keras-implementeringen!

Håll utkik efter nästa Keras + OpenAI-tutorial!

Kommentera och klicka på nedan för att visa support!