A DQN Tensorflow.js megvalósítása a megerősítési tanulásban
„Gyakorold, amit tudsz, és segít tisztázni, amit most nem tudsz”
Rembrandt
Áttekintés
A Mnih és munkatársai által javasolt Deep Q-Network. A [2015] számos mélyen megerősítő tanulási algoritmus kiindulópontja és építési pontja. A felszíni egyszerűsége ellenére azonban néhány kihívást jelent a megvalósítás során, illetve a problémák megoldása során.
Ez a cikk a "Tensorflow.js"-t fogja használni megvalósítási technológiaként, a nyilvánvaló okok miatt:
- Minél több dolgot kell telepítenie, annál valószínűtlenebb, hogy erőfeszítéseket tesz a valódi algoritmus kódolására.
- A Javascript egy szép és népszerű nyelv, és nagyon intuitív azok számára, akik ismerik a C++-t, a C#-t, a Java-t stb.
- A demó megosztása az interneten egyszerű és egyértelmű.
Az algoritmus
Mint mondtam, az algoritmus önmagában meglehetősen egyszerű, az alábbi képen foglalható össze
Az alábbi lépések leírják, hogyan működik az algoritmus valójában:
1- Hozzon létre Q-Network és Target-Network
2- Töltse fel az Experience Buffert adatokkal a Q-Network segítségével
3- Ismételje meg a következő lépéseket megfelelő számú alkalommal
4- Szerezzen be egy véletlenszerű minta az Experience Bufferből
5- A minta bemenete a Q-hálózatba és a célhálózatba
6- Használja a célhálózat kimenetét a Q-Network betanításához (azaz a A célhálózat a Q-Network címkéinek szerepét fogja betölteni egy szabványos felügyelt tanulási forgatókönyvben.
7- Alkalmazza a feltárási/kihasználási stratégiát (pl.: Epsilon Greedy)
8- Ha az Exploration van kiválasztva, akkor generáljon véletlenszerű művelet, különben Ha a kihasználás van kiválasztva, akkor adja meg az aktuális állapotot a Q-Network-nek, és következtessen a műveletre a kimenetből.
9- Alkalmazza a műveletet a környezetre, megkapja a jutalmat és az új állapotot
10- Tárolja a régi állapotot, műveletet, jutalmat és új állapotot az élménypufferben (más néven Replay Memory)
11- Néhány epizód után másolja át a súlyokat a Q-Networkből a Target-Networkbe
Miért két hálózat?
Nyilvánvaló, hogy vajon miért van szükségünk két hálózatra?
Valójában a Q tanulási képlet a következő:
Észrevesszük, hogy ahhoz, hogy Q(S, A) elérje végső értékét, majdnem egyenlővé kell válnia R’ + γ max Q(S’,a) értékkel. Tehát ahhoz, hogy Q(S, A) R' + γ max Q(S',a) felé haladjon, a problémát valamilyen felügyelt tanulássá alakítjuk át, ahol becsüljük Q(S,A) és R' + γ max Q értéket. (S',a) lesz a 'címke'.
A Q(S,A) becsléséhez használt hálózat súlyai a Loss függvény és a visszaterjesztés kombinációjával frissülnek.
De a max Q(S',a)-t nem ismerjük, egyszerűen megbecsüljük ugyanazon neurális hálózat segítségével, azonban ennek a hálózatnak a súlyai folyamatosan változnak, ami különböző max Q(S',a) értékeket ad. minden iteráció.
Ez a Q(S, A) becslése egy mozgó célpont üldözésére.
A probléma megoldására létrehozunk egy azonos neurális hálózatot, amelyet célhálózatnak nevezünk, míg a Q(S,A) kiszámításához szükséges első hálózatot Online hálózatnak nevezzük. A Target hálózat célja, hogy egy ideig fix súlyokkal rendelkezzen, hogy stabilizálja a max Q(S’,a) számítását. Ezután néhány iteráció után a Target hálózat frissíti a súlyait az Online hálózat súlyainak egyszerű másolatával, amelyet az utóbbinak volt alkalma megtanulni.
Ügyeljen néhány megvalósítási buktatóra
- Bemenetek normalizálása. Lehet, hogy nyilvánvaló, de a kezdők könnyen elfelejthetik ezt a fontos funkciót. Ennek nagy következményei lennének a kimenetre, mivel egyes bemenetek nagy értéktartományt, míg mások kisebb értéktartományt tartalmazhatnak. Ez nem jelenti azt, hogy az utóbbiak kevésbé fontosak az előbbieknél. Bármilyen számítási szempontból is, a nagy értékek összetörik a kicsiket. Tehát ha furcsa értékeket lát valamilyen nyilvánvaló bemenetnél, jó reflex lehet ellenőrizni, hogy a bemenetet normalizálták-e.
- A két neurális hálózat szinkronizálása. A DQN két neurális hálózatot használ, az egyik „online”, ami azt jelenti, hogy minden iterációnál kiszámítja a Q értéket, a másik hálózat, a „target”, kiszámítja a Q értéket, de stabil súlykészleten alapul.
A „cél” ” hálózat az R + γ max Q(St+1) kifejezést jelenti. Az „online” és a „cél” hálózatok kimenete a Mean Squared Error (MSE) kiszámítására, majd az „online” hálózat súlyainak frissítésére visszaszaporítással történik.
Bizonyos számú iteráció esetén a Az "online" hálózatot át kell másolni a "cél" hálózatba, hogy a "cél" megkapja az "online" hálózat által tanult tapasztalatokat.
Itt a fontos érték az iterációk megfelelő értékének beállítása, amelyeknél a súlyok másolják. A túl kicsi értékek instabillá teszik a rendszert, a túl nagy érték pedig képtelenné teszi a rendszert a tanulásra. Tehát ha a dolgok nem a megfelelő irányba haladnak, fontos ellenőrizni ezt a paramétert. - Az élménypuffer feltöltése. A folyamat elején fontos az élménypuffer feltöltése oly módon, hogy az ügynök interakcióba lép a környezettel, és tárolja az aktuális állapotot, műveletet, jutalmat, következő állapotot, terminális állapotot az élménypufferben, hogy amikor a tanulási folyamat generál véletlenszerű minták ebből az élményből.
- Mintavételi méret. Jó gyakorlat, ha a mintavételi méretet a tapasztalati puffer méretének a felére helyezzük, mert kis mintavételezés esetén a tapasztalatból való tanulás nem lesz hatékony, míg a nagy mintavétel (a maximum körül) olyan torzítást okoz a tanulásban, amelyet nem kíván. Például, ha önvezető autója van, és mivel az utak legtöbbször egyenesek, az egyenes út tapasztalataiból túlzott mintavételezés arra készteti az MI-t, hogy minimálisra csökkentse a kanyarodás hatását.
CartPole példa
A DQN klasszikus megvalósítása a CartPole játék, ahol az AI ügynöknek úgy kell mozgatnia a kocsit, hogy a rúd függőlegesen maradjon.
A rúd nem eshet 30°-nál nagyobb szögben a függőlegeshez képest, és a kocsi nem érheti el a széleket. A nyeremény akkor érhető el, amikor az AI-ügynök 300 jutalmat gyűjt össze.
A játék valódi megvalósítását a https://rl-lab.com/cartpole/ oldalon tekintheti meg
Az alábbiakban az algoritmus fő részének kódrészletei találhatók. Tartalmazza a fő hurkot, a playStep-et és a betanítási bázist a kötegmintán.
A train() metódus tartalmazza a képzés fő hurkát. Először inicializálja az ügynököt, és megtölti a visszajátszási memóriapuffert tapasztalattal, majd elkezdi a hurkolást: edzés, majd egy lépés a lejátszás.
train(agent, maxIterations, batchSize, gamma, learningRate, syncEveryFrames) { // init the agent agent.reset(); // at first fill the replay memory with experience by playing // without any training for (let i = 0; i < agent.replayBufferSize; ++i) { agent.playStep(); } // create optimizer for the training const optimizer = tf.train.adam(learningRate); // loop until all iterations are done let counter = maxIterations; while (counter > 0) { // train the agent by extracting samples from replay memory and // use them as input for the DQN agent.trainOnReplayBatch(batchSize, gamma, optimizer); // after each training, play one step of the game agent.playStep(); if (agent.frameCount % syncEveryFrames === 0) { copyWeights(agent.targetNN, agent.onlineNN); } counter--; } }
A playStep() metódus arra készteti az ügynököt, hogy csak egy lépésig játssza a játékot. Epszilon mohó házirendet használ, amely arra készteti, hogy az idő egy részében véletlenszerű műveletet válasszon, míg a többi időben azt a műveletet, amely a maximális Q értéket adja vissza.
// play one step of the game and store result in replay memory playStep() { ... let action; let actionIndex = 0; const state = this.game.getState(); if (Math.random() < this.epsilon) { // Pick an action at random. actionIndex = this.getRandomAction(); action = ALL_ACTIONS[actionIndex]; } else { // Greedily pick an action based on online DQN output. // use tf.tidy() to clean up after finishing tf.tidy(() => { const stateTensor = getStateTensor(state); actionIndex = this.onlineNN.predict(stateTensor) .argMax(-1).dataSync()[0]; action = ALL_ACTIONS[actionIndex]; }); } // play one step of the game and get the results const result = this.game.step(action); // store experience in replay Memory this.replayMemory.append([state, actionIndex, result.reward, result.state, result.status]); // if terminal state, reset the game if (r.status != 0) {this.reset();} }
A trainOnReplayBatch() kivon egy mintát a replayMemory-ból, és bemenetként használja a DQN-be, majd az átlagos négyzetes hibát veszteségfüggvényként használja a súlyok frissítéséhez.
// This method is used to train the online network by using batch // samples from the memory replay trainOnReplayBatch(batchSize, gamma, optimizer){ // Get a batch of examples from the replay buffer. const batch = this.replayMemory.sample(batchSize); //define the loss function const lossFunction = () => tf.tidy(() => { // example[0] is the state // example[1] is the action // example[2] is the reward // example[3] is the next state // example[4] indicates if this is a terminal state const stateTensor = getStateTensor(batch.map( example => example[0])); const actionTensor = tf.tensor1d(batch.map( example => example[1]), 'int32'); // compute Q value of the current state // note that we use apply() instead of predict // because apply() allow access to the gradient const online = this.onlineNN.apply(stateTensor, {training: true}); const oneHot = tf.oneHot(actionTensor, NUM_ACTIONS); const qs = online.mul(oneHot).sum(-1); // compute the Q value of the next state. // it is R if the next state is terminal // R + max Q(next_state) if the next state is not terminal const rewardTensor = tf.tensor1d(batch.map( example => example[2])); const nextStateTensor = getStateTensor(batch.map( example => example[3])); const nextMaxQTensor = this.targetNN.predict( nextStateTensor).max(-1); const status = tf.tensor1d(batch.map( example => example[4])).asType('float32'); // if terminal state then status = 1 => doneMask = 0 // if not terminal then status = 0 => doneMask = 1 // this will make nextMaxQTensor.mul(doneMask) either 0 or not const doneMask = tf.scalar(1).sub(status); const targetQs = rewardTensor.add( nextMaxQTensor.mul(doneMask).mul(gamma)); // define the mean square error between Q value of current state // and target Q value const mse = tf.losses.meanSquaredError(targetQs, qs); return mse; }); // Calculate the gradients of the loss function with respect // to the weights of the online DQN. const grads = tf.variableGrads(lossFunction); // Use the gradients to update the online DQN's weights.optimizer.applyGradients(grads.grads); tf.dispose(grads); }
Végül itt egy videó, amelyben a DQN ügynök játszik, és megnyeri a CartPole játékot, elérve a 300-as jutalmat.
Következtetés
A DQN az egyik legnépszerűbb Deep Reforcement Learning algoritmus. Először ért el emberfeletti szintű teljesítményt egy Atari játékon.
Az évek múlásával számos fejlesztésen esett át, így még mindig a legjobban teljesítő DRL-ügynökök közé tartozik.