Învățare Flappy Bird: Un tutorial de învățare de întărire

Publicat: 2022-03-11

În programarea clasică, instrucțiunile software sunt făcute în mod explicit de programatori și nu se învață absolut nimic din date. În schimb, învățarea automată este un domeniu al informaticii care utilizează metode statistice pentru a permite computerelor să învețe și să extragă cunoștințe din date fără a fi programate în mod explicit.

În acest tutorial de învățare prin întărire, voi arăta cum putem folosi PyTorch pentru a învăța o rețea neuronală de învățare prin întărire cum să joace Flappy Bird. Dar mai întâi, va trebui să acoperim o serie de blocuri de construcție.

Algoritmii de învățare automată pot fi împărțiți aproximativ în două părți: algoritmi tradiționali de învățare și algoritmi de învățare profundă. Algoritmii de învățare tradiționali au de obicei mult mai puțini parametri de învățare decât algoritmii de învățare profundă și au o capacitate de învățare mult mai mică.

De asemenea, algoritmii tradiționali de învățare nu sunt capabili să facă extragerea caracteristicilor : specialiștii în inteligență artificială trebuie să descopere o reprezentare bună a datelor care este apoi trimisă algoritmului de învățare. Exemplele de tehnici tradiționale de învățare automată includ SVM, pădure aleatoare, arbore de decizie și $k$-means, în timp ce algoritmul central în învățarea profundă este rețeaua neuronală profundă .

Intrarea într-o rețea neuronală profundă poate fi imagini brute, iar un specialist în inteligență artificială nu trebuie să găsească nicio reprezentare a datelor - rețeaua neuronală găsește cea mai bună reprezentare în timpul procesului de antrenament.

O mulțime de tehnici de învățare profundă sunt cunoscute de foarte mult timp, dar progresele recente în hardware au stimulat rapid cercetarea și dezvoltarea învățării profunde. Nvidia este responsabilă pentru extinderea domeniului, deoarece GPU-urile sale au permis experimente rapide de învățare profundă.

Parametri și hiperparametri învățați

Algoritmii de învățare automată constau din parametri care se pot învăța, care sunt reglați în procesul de antrenament și parametri care nu pot fi învățați, care sunt setați înaintea procesului de antrenament. Parametrii stabiliți înainte de învățare se numesc hiperparametri .

Căutarea în grilă este o metodă comună pentru găsirea hiperparametrilor optimi. Este o metodă cu forță brută: înseamnă să încercați toate combinațiile posibile de hiperparametri într-un interval definit și să alegeți combinația care maximizează o valoare predefinită.

Algoritmi de învățare supravegheat, nesupravegheat și de întărire

O modalitate de a clasifica algoritmii de învățare este trasarea unei linii între algoritmii supravegheați și cei nesupravegheați. (Dar asta nu este neapărat atât de simplu: învățarea prin întărire se află undeva între aceste două tipuri.)

Când vorbim despre învățarea supravegheată, ne uităm la perechi $ (x_i, y_i) $. $ x_i $ este intrarea algoritmului și $ y_i $ este rezultatul. Sarcina noastră este să găsim o funcție care să facă maparea corectă de la $ x_i $ la $ y_i $.

Pentru a regla parametrii care pot fi învățați astfel încât să definească o funcție care mapează $ x_i $ la $ y_i $, trebuie definite o funcție de pierdere și un optimizator. Un optimizator minimizează funcția de pierdere. Un exemplu de funcție de pierdere este eroarea medie pătratică (MSE):

\[MSE = \sum_{i=1}^{n} (y_i - \widehat{y_i} )^2\]

Aici, $ y_i $ este o etichetă de adevăr de bază și $ \widehat{y_i} $ este o etichetă prezisă. Un optimizator care este foarte popular în învățarea profundă este coborârea gradientului stocastic . Există o mulțime de variații care încearcă să îmbunătățească metoda de coborâre a gradientului stocastic: Adam, Adadelta, Adagrad și așa mai departe.

Algoritmii nesupravegheați încearcă să găsească structură în date fără a fi furnizați în mod explicit cu etichete. $k$-means este unul dintre exemplele de algoritmi nesupravegheați care încearcă să găsească clustere optime în date. Mai jos este o imagine cu 300 de puncte de date. Algoritmii $k$-means au găsit structura în date și au atribuit o etichetă de cluster fiecărui punct de date. Fiecare cluster are propria sa culoare.

Grupuri de puncte de date împărțite în culori. Clusterele au fost găsite de un algoritm nesupravegheat

Învățarea prin întărire folosește recompense: etichete rare, întârziate. Un agent ia măsuri, care schimbă mediul, din care poate obține o nouă observație și recompensă. O observație este stimulul pe care un agent îl percepe din mediu. Poate fi ceea ce agentul vede, aude, miroase și așa mai departe.

O recompensă este acordată agentului atunci când efectuează o acțiune. Îi spune agentului cât de bună este acțiunea. Percepând observații și recompense, un agent învață cum să se comporte optim în mediu. Voi intra în asta mai detaliat mai jos.

Învățare prin întărire activă, pasivă și inversă

Există câteva abordări diferite ale acestei tehnici. În primul rând, există învățarea activă prin întărire, pe care o folosim aici. În schimb, există învățare prin întărire pasivă, în care recompensele sunt doar un alt tip de observație, iar deciziile sunt luate în schimb conform unei politici fixe.

În sfârșit, învățarea prin întărire inversă încearcă să reconstruiască o funcție de recompensă având în vedere istoria acțiunilor și recompensele acestora în diferite stări.

Generalizare, supraajustare și subadaptare

Orice instanță fixă ​​de parametri și hiperparametri se numește model. Experimentele de învățare automată constau de obicei din două părți: antrenament și testare.

În timpul procesului de antrenament, parametrii care pot fi învățați sunt reglați folosind datele de antrenament. În procesul de testare, parametrii învățați sunt înghețați, iar sarcina este de a verifica cât de bine modelul face predicții asupra datelor nevăzute anterior. Generalizarea este capacitatea unei mașini de învățare de a efectua cu acuratețe un exemplu sau o sarcină nouă, nevăzută, după ce a experimentat un set de date de învățare.

Dacă un model este prea simplu în ceea ce privește datele, nu se va putea potrivi cu datele de antrenament și va funcționa slab atât pe setul de date de antrenament, cât și pe setul de date de testare. În acest caz, spunem că modelul este insuficient .

Dacă un model de învățare automată funcționează bine pe un set de date de antrenament, dar slab pe un set de date de testare, spunem că este supraadaptat . Supraadaptarea este situația în care un model este prea complex în ceea ce privește datele. Se poate potrivi perfect cu datele de antrenament, dar este atât de mult adaptat la setul de date de antrenament încât are performanțe slabe la datele de testare, adică pur și simplu nu se generalizează.

Mai jos este o imagine care arată subadaptarea și supraadaptarea în comparație cu o situație echilibrată între datele generale și funcția de predicție.

Grafice neadaptate, echilibrate și supraadaptate. O funcție echilibrată urmează destul de bine tendința generală a punctelor de date, fără a rămâne prea aproape de punctele de date individuale

Scalabilitate

Datele sunt cruciale în construirea modelelor de învățare automată. De obicei, algoritmii tradiționali de învățare nu necesită prea multe date. Dar din cauza capacității lor limitate, performanța este, de asemenea, limitată. Mai jos este un grafic care arată cât de bine se scalează metodele de învățare profundă în comparație cu algoritmii tradiționali de învățare automată.

Performanță față de cantitatea de date pentru deep learning și algoritmi tradiționali. Rețelele neuronale funcționează mai bine la scară.

Rețele neuronale

Rețelele neuronale constau din mai multe straturi. Imaginea de mai jos arată o rețea neuronală simplă cu patru straturi. Primul strat este stratul de intrare, iar ultimul strat este stratul de ieșire. Cele două straturi dintre straturile de intrare și de ieșire sunt straturi ascunse.

Graficul rețelei neuronale, care arată fiecare nod al unui strat de intrare mapat la fiecare nod al stratului ascuns 1, mapat la rândul său cu fiecare nod al stratului ascuns 2 și apoi mapat în cele din urmă la stratul de ieșire, care constă dintr-un singur nod

Dacă o rețea neuronală are mai mult de un strat ascuns, o numim o rețea neuronală profundă. Setul de intrare $ X $ este dat rețelei neuronale și se obține rezultatul $ y $. Învățarea se face folosind un algoritm de backpropagation care combină o funcție de pierdere și un optimizator.

Propagarea înapoi constă din două părți: o trecere înainte și o trecere înapoi. În trecerea înainte, datele de intrare sunt puse pe intrarea rețelei neuronale și se obține ieșirea. Se calculează pierderea dintre adevărul de bază și predicție, iar apoi în trecerea înapoi, parametrii rețelelor neuronale sunt reglați în funcție de pierdere.

Rețeaua neuronală convoluțională

O variație a rețelei neuronale este rețeaua neuronală convoluțională . Este folosit în principal pentru sarcini de viziune computerizată.

Cel mai important strat din rețelele neuronale convoluționale este stratul convoluțional (de unde și numele). Parametrii săi sunt alcătuiți din filtre care pot fi învățate, numite și nuclee. Straturile convoluționale aplică o operație de convoluție la intrare, trecând rezultatul la stratul următor. Operația de convoluție reduce numărul de parametri învățați, funcționând ca un fel de euristică și făcând rețeaua neuronală mai ușor de antrenat.

Mai jos este cum funcționează un nucleu convoluțional dintr-un strat convoluțional. Nucleul este aplicat imaginii și se obține o caracteristică convolută.

Animația care evidențiază un nucleu și funcționarea acestuia ca imagine este procesată și ieșirea caracteristică convolută corespunzătoare

Straturile ReLU sunt folosite pentru a introduce neliniarități în rețeaua neuronală. Neliniaritățile sunt importante deoarece cu ele putem modela toate tipurile de funcții, nu numai liniare, făcând rețeaua neuronală un aproximator universal de funcții. Aceasta face ca o funcție ReLU să fie definită astfel:

\[ReLU = \max(0, x)\]

ReLU este unul dintre exemplele de așa-numitele funcții de activare utilizate pentru a introduce neliniarități în rețelele neuronale. Exemple de alte funcții de activare includ funcțiile sigmoide și hipertangente. ReLU este cea mai populară funcție de activare, deoarece se arată că face trenul rețelei neuronale mai eficient în comparație cu alte funcții de activare.

Mai jos este un grafic al unei funcții ReLU.

O funcție ReLU, similară cu diagonala simplă a unui grafic y=x, dar cu toate valorile negative x mapate la zero

După cum puteți vedea, această funcție ReLU pur și simplu schimbă valorile negative în zerouri. Acest lucru ajută la prevenirea problemei gradientului de dispariție. Dacă un gradient dispare, acesta nu va avea un impact mare în reglarea greutății rețelei neuronale.

O rețea neuronală convoluțională constă din mai multe straturi: straturi convoluționale, straturi ReLU și straturi complet conectate. Straturile complet conectate conectează fiecare neuron dintr-un strat la fiecare neuron din alt strat, așa cum se vede cu cele două straturi ascunse din imaginea de la începutul acestei secțiuni. Ultimul strat complet conectat mapează ieșirile din stratul anterior la, în acest caz, valorile number_of_actions .

Aplicații

Învățarea profundă are succes și depășește algoritmii clasici de învățare automată în mai multe subdomenii de învățare automată, inclusiv viziunea computerizată, recunoașterea vorbirii și învățarea prin consolidare. Aceste domenii de învățare profundă sunt aplicate în diverse domenii din lumea reală: finanțe, medicină, divertisment etc.

Consolidarea învățării

Învățarea prin întărire se bazează pe un agent . Un agent ia acțiuni într-un mediu și primește observații și recompense din acesta. Un agent trebuie să fie instruit pentru a maximiza recompensa cumulativă. După cum s-a menționat în introducere, cu algoritmii clasici de învățare automată, inginerii de învățare automată trebuie să facă extragerea caracteristicilor, adică să creeze caracteristici bune care reprezintă bine mediul și care sunt introduse într-un algoritm de învățare automată.

Folosind învățarea profundă, este posibil să se creeze un sistem end-to-end care preia intrări dimensionale înalte - de exemplu video - și din acesta, învață strategia optimă pentru ca un agent să întreprindă acțiuni bune.

În 2013, startup-ul londonez DeepMind a creat o descoperire extraordinară în învățarea de a controla agenții direct din intrările senzoriale de înaltă dimensiune. Ei au publicat o lucrare, Playing Atari with Deep Reinforcement Learning , în care au arătat cum au învățat o rețea neuronală artificială să joace jocuri Atari doar uitându-se la ecran. Aceștia au fost achiziționați de Google și apoi au publicat o nouă lucrare în Nature cu câteva îmbunătățiri: Control la nivel uman prin învățare prin consolidare profundă .

Spre deosebire de alte paradigme de învățare automată, învățarea prin consolidare nu are un supervizor, ci doar un semnal de recompensă. Feedback-ul este întârziat: nu este instantaneu ca în algoritmii de învățare supravegheată. Datele sunt secvențiale și acțiunile unui agent afectează datele ulterioare pe care le primește.

Acum, un agent este situat în mediul său, care se află într-o anumită stare. Pentru a descrie acest lucru mai detaliat, folosim un proces de decizie Markov, care este o modalitate formală de modelare a acestui mediu de învățare prin întărire. Este alcătuit dintr-un set de stări, un set de acțiuni posibile și reguli (de exemplu, probabilități) pentru trecerea de la o stare la alta.

Un grafic al procesului de decizie Markov: Statele (marcate cu „S”) iau acțiuni (marcate cu „a”) care apoi au diferite probabilități pentru care starea în care ajunge agentul următor; unele căi urmate indică și o recompensă

Agentul este capabil să efectueze acțiuni, transformând mediul. Numim recompensa $ R_t $. Este un semnal de feedback scalar care indică cât de bine se descurcă agentul la pasul $t$.

Un agent de rețea neuronală decide ce acțiune să întreprindă la orice pas pe baza observațiilor și recompenselor.

Pentru o performanță bună pe termen lung, trebuie luate în considerare nu numai recompensele imediate, ci și recompensele viitoare. Recompensa totală pentru un episod din pasul de timp $t$ este $ R_t = r_t + r_{t+1} + r_{t+2} + \ldots + r_n $. Viitorul este incert și cu cât mergem mai departe în viitor, cu atât previziunile viitoare pot diverge. Din acest motiv, se utilizează o recompensă viitoare redusă: $ R_t = r_t +\gamma r_{t+1} + \gamma^2r_{t+2} + \ldots + \gamma^{nt}r_n = r_t + \gamma R_{t+1} $. Un agent ar trebui să aleagă acțiunea care maximizează recompensa viitoare redusă.

Învățare Q profundă

Funcția $ Q(s, a) $ reprezintă recompensa viitoare maximă redusă atunci când acțiunea $ a $ este efectuată în starea $ s $:

\[Q(s_t, a_t) = \max R_{t+1}\]

Estimarea unei recompense viitoare este dată de ecuația Bellman: $ Q(s, a) = r + \gamma \max_{a'}Q(s', a') $ . Cu alte cuvinte, recompensa viitoare maximă dată unui stat $ s $ și o acțiune $ a $ este recompensa imediată plus recompensa viitoare maximă pentru următorul stat.

Aproximarea valorilor Q folosind funcții neliniare (rețele neuronale) nu este foarte stabilă. Din acest motiv, reluarea experienței este folosită pentru stabilitate. Experiența din timpul episoadelor dintr-o sesiune de antrenament este stocată într-o memorie de reluare. Sunt utilizate mini-loturi aleatorii din memoria de reluare în loc de cea mai recentă tranziție. Acest lucru rupe asemănarea mostrelor de antrenament ulterioare, care altfel ar conduce rețeaua neuronală la un minim local.

Mai sunt două aspecte importante de menționat despre deep Q-learning: explorarea și exploatarea. Odată cu exploatarea, se ia cea mai bună decizie având în vedere informațiile actuale. Explorarea adună mai multe informații.

Când algoritmul efectuează o acțiune propusă de rețeaua neuronală, face exploatare: exploatează cunoștințele învățate ale rețelei neuronale. În schimb, un algoritm poate lua o acțiune aleatorie, explorând noi posibilități și introducând potențiale noi cunoștințe în rețeaua neuronală.

„Algoritmul Deep Q-learning with Experience Replay” din lucrarea DeepMind Playing Atari with Deep Reinforcement Learning este prezentat mai jos.

Algoritmul Deep Q-learning cu Experience Replay în pseudocod

DeepMind se referă la rețelele convoluționale antrenate cu abordarea lor ca Deep Q-networks (DQN).

Exemplu de învățare Q profundă folosind Flappy Bird

Flappy Bird a fost un joc mobil popular dezvoltat inițial de artistul și programatorul vietnamez de jocuri video Dong Nguyen. În ea, jucătorul controlează o pasăre și încearcă să zboare între țevile verzi fără să le lovească.

Mai jos este o captură de ecran de la o clonă Flappy Bird codificată folosind PyGame:

O captură de ecran de la FlapPyBird, o clonă Flappy Bird codificată folosind PyGame

De atunci, clona a fost bifurcată și modificată: fundalul, sunetele și diferitele stiluri de păsări și țevi au fost eliminate, iar codul a fost ajustat, astfel încât să poată fi utilizat cu ușurință cu cadre simple de învățare de întărire. Motorul de joc modificat este preluat din acest proiect TensorFlow:

O captură de ecran a DeepLearningFlappyBird, o bifurcătură a clonei cu grafică simplificată

Dar în loc să folosesc TensorFlow, am construit un cadru de învățare de întărire profundă folosind PyTorch. PyTorch este un cadru de învățare profundă pentru experimentare rapidă și flexibilă. Oferă tensori și rețele neuronale dinamice în Python cu o accelerare puternică a GPU.

Arhitectura rețelei neuronale este aceeași cu cea a DeepMind folosită în lucrarea Controlul la nivel uman prin învățare prin consolidare profundă .

Strat Intrare Dimensiunea filtrului Pas Numărul de filtre Activare Ieșire
conv1 84x84x4 8x8 4 32 ReLU 20x20x32
conv2 20x20x32 4x4 2 64 ReLU 9x9x64
conv3 9x9x64 3x3 1 64 ReLU 7x7x64
fc4 7x7x64 512 ReLU 512
fc5 512 2 Liniar 2

Există trei straturi convoluționale și două straturi complet conectate. Fiecare strat folosește activarea ReLU, cu excepția ultimului, care folosește activarea liniară. Rețeaua neuronală emite două valori reprezentând singurele acțiuni posibile ale jucătorului: „Zburați în sus” și „nu faceți nimic”.

Intrarea constă din patru imagini consecutive de 84x84 alb-negru. Mai jos este un exemplu de patru imagini care sunt transmise rețelei neuronale.

Veți observa că imaginile sunt rotite. Asta pentru că ieșirea motorului de joc al clonei este rotită. Dar dacă rețeaua neuronală este predată și apoi testată folosind astfel de imagini, aceasta nu îi va afecta performanța.

Patru cadre alb-negru consecutive ale clonei Flappy Bird, alimentate direct la o rețea neuronală

De asemenea, puteți observa că imaginea este decupată astfel încât podeaua este omisă, deoarece este irelevant pentru această sarcină. Toți pixelii care reprezintă țevi și pasărea sunt albi și toți pixelii care reprezintă fundalul sunt negri.

Aceasta face parte din codul care definește rețeaua neuronală. Greutățile rețelelor neuronale sunt inițializate pentru a urma distribuția uniformă $\mathcal{U}(-0,01, 0,01)$. Partea de polarizare a parametrilor rețelelor neuronale este setată la 0,01. Au fost încercate mai multe inițializari diferite (uniformă Xavier, normală Xavier, uniformă Kaiming, normală Kaiming, uniformă și normală), dar inițializarea de mai sus a făcut ca rețeaua neuronală să convergă și să se antreneze cel mai rapid. Dimensiunea rețelei neuronale este de 6,8 MB.

 class NeuralNetwork(nn.Module): def __init__(self): super(NeuralNetwork, self).__init__() self.number_of_actions = 2 self.gamma = 0.99 self.final_epsilon = 0.0001 self.initial_epsilon = 0.1 self.number_of_iterations = 2000000 self.replay_memory_size = 10000 self.minibatch_size = 32 self.conv1 = nn.Conv2d(4, 32, 8, 4) self.relu1 = nn.ReLU(inplace=True) self.conv2 = nn.Conv2d(32, 64, 4, 2) self.relu2 = nn.ReLU(inplace=True) self.conv3 = nn.Conv2d(64, 64, 3, 1) self.relu3 = nn.ReLU(inplace=True) self.fc4 = nn.Linear(3136, 512) self.relu4 = nn.ReLU(inplace=True) self.fc5 = nn.Linear(512, self.number_of_actions) def forward(self, x): out = self.conv1(x) out = self.relu1(out) out = self.conv2(out) out = self.relu2(out) out = self.conv3(out) out = self.relu3(out) out = out.view(out.size()[0], -1) out = self.fc4(out) out = self.relu4(out) out = self.fc5(out) return out

În constructor, veți observa că există hiperparametri definiți. Optimizarea hiperparametrului nu se face în scopul acestei postări de blog. În schimb, hiperparametrii sunt utilizați în cea mai mare parte din lucrările lui DeepMind. Aici, unii dintre hiperparametri sunt scalați pentru a fi mai mici decât în ​​lucrarea lui DeepMind, deoarece Flappy Bird este mai puțin complex decât jocurile Atari pe care le-au folosit pentru reglare.

De asemenea, epsilonul este modificat pentru a fi mult mai rezonabil pentru acest joc. DeepMind folosește un epsilon de unu, dar aici folosim 0.1. Acest lucru se datorează faptului că epsilonii mai mari obligă pasărea să bată mult, ceea ce împinge pasărea spre marginea superioară a ecranului, rezultând întotdeauna în cele din urmă ca pasărea să se prăbușească într-o țeavă.

Codul de învățare prin întărire are două moduri: Antrenează și testează. În timpul fazei de testare, putem vedea cât de bine a învățat algoritmul de învățare prin întărire să joace jocul. Dar mai întâi, rețeaua neuronală trebuie antrenată. Trebuie să definim funcția de pierdere care trebuie minimizată și optimizatorii care vor minimiza funcția de pierdere. Vom folosi metoda de optimizare Adam și eroarea pătratică medie pentru funcția de pierdere:

 optimizer = optim.Adam(model.parameters(), lr=1e-6) criterion = nn.MSELoss()

Jocul ar trebui să fie instanțiat:

 game_state = GameState()

Memoria de reluare este definită ca o listă Python:

 replay_memory = []

Acum trebuie să inițializam prima stare. O acțiune este un tensor bidimensional:

  • [1, 0] reprezintă „nu face nimic”
  • [0, 1] reprezintă „zboară în sus”

Metoda frame_step ne oferă următorul ecran, recompensă și informații despre dacă următoarea stare este terminală. Recompensa este de 0.1 pentru mișcarea fiecărei păsări fără a muri atunci când aceasta nu trece printr-o țeavă, 1 dacă pasărea trece cu succes printr-o țeavă și -1 dacă pasărea se prăbușește.

Funcția resize_and_bgr2gray podeaua, redimensionează ecranul la o imagine de 84x84 și schimbă spațiul de culoare de la BGR la alb-negru. Funcția image_to_tensor convertește imaginea într-un tensor PyTorch și o pune în memoria GPU dacă CUDA este disponibil. În cele din urmă, ultimele patru ecrane secvențiale sunt concatenate împreună și sunt gata să fie trimise în rețeaua neuronală.

 action = torch.zeros([model.number_of_actions], dtype=torch.float32) action[0] = 1 image_data, reward, terminal = game_state.frame_step(action) image_data = resize_and_bgr2gray(image_data) image_data = image_to_tensor(image_data) state = torch.cat((image_data, image_data, image_data, image_data)).unsqueeze(0)

Epsilonul inițial este setat folosind această linie de cod:

 epsilon = model.initial_epsilon

Urmează bucla infinită principală. Comentariile sunt scrise în cod și puteți compara codul cu algoritmul Deep Q-learning with Experience Replay scris mai sus.

Algoritmul prelevează mini-loturi din memoria de reluare și actualizează parametrii rețelei neuronale. Acțiunile sunt executate folosind explorarea epsilon greedy . Epsilon este recoaptă în timp. Funcția de pierdere care este minimizată este $ L = \frac{1}{2}\left[\max_{a'}Q(s', a') - Q(s, a)\right]^2 $ . $ Q(s, a) $ este valoarea adevărului de bază calculată folosind ecuația Bellman și $ \max_{a'}Q(s', a') $ este obținut din rețeaua neuronală. Rețeaua neuronală oferă două valori Q pentru cele două acțiuni posibile, iar algoritmul preia acțiunea cu cea mai mare valoare Q.

 while iteration < model.number_of_iterations: # get output from the neural network output = model(state)[0] # initialize action action = torch.zeros([model.number_of_actions], dtype=torch.float32) if torch.cuda.is_available(): # put on GPU if CUDA is available action = action.cuda() # epsilon greedy exploration random_action = random.random() <= epsilon if random_action: print("Performed random action!") action_index = [torch.randint(model.number_of_actions, torch.Size([]), dtype=torch.int) if random_action else torch.argmax(output)][0] if torch.cuda.is_available(): # put on GPU if CUDA is available action_index = action_index.cuda() action[action_index] = 1 # get next state and reward image_data_1, reward, terminal = game_state.frame_step(action) image_data_1 = resize_and_bgr2gray(image_data_1) image_data_1 = image_to_tensor(image_data_1) state_1 = torch.cat((state.squeeze(0)[1:, :, :], image_data_1)).unsqueeze(0) action = action.unsqueeze(0) reward = torch.from_numpy(np.array([reward], dtype=np.float32)).unsqueeze(0) # save transition to replay memory replay_memory.append((state, action, reward, state_1, terminal)) # if replay memory is full, remove the oldest transition if len(replay_memory) > model.replay_memory_size: replay_memory.pop(0) # epsilon annealing epsilon = epsilon_decrements[iteration] # sample random minibatch minibatch = random.sample(replay_memory, min(len(replay_memory), model.minibatch_size)) # unpack minibatch state_batch = torch.cat(tuple(d[0] for d in minibatch)) action_batch = torch.cat(tuple(d[1] for d in minibatch)) reward_batch = torch.cat(tuple(d[2] for d in minibatch)) state_1_batch = torch.cat(tuple(d[3] for d in minibatch)) if torch.cuda.is_available(): # put on GPU if CUDA is available state_batch = state_batch.cuda() action_batch = action_batch.cuda() reward_batch = reward_batch.cuda() state_1_batch = state_1_batch.cuda() # get output for the next state output_1_batch = model(state_1_batch) # set y_j to r_j for terminal state, otherwise to r_j + gamma*max(Q) y_batch = torch.cat(tuple(reward_batch[i] if minibatch[i][4] else reward_batch[i] + model.gamma * torch.max(output_1_batch[i]) for i in range(len(minibatch)))) # extract Q-value q_value = torch.sum(model(state_batch) * action_batch, dim=1) # PyTorch accumulates gradients by default, so they need to be reset in each pass optimizer.zero_grad() # returns a new Tensor, detached from the current graph, the result will never require gradient y_batch = y_batch.detach() # calculate loss loss = criterion(q_value, y_batch) # do backward pass loss.backward() optimizer.step() # set state to be state_1 state = state_1

Acum că toate piesele sunt la locul lor, iată o prezentare generală la nivel înalt a fluxului de date folosind rețeaua noastră neuronală:

Prezentare generală finală la nivel înalt a fluxului de date folosind rețeaua neuronală: starea de intrare constă din patru ecrane consecutive. De aici, rețeaua neuronală oferă două valori Q pentru cele două acțiuni posibile ("nu face nimic" și "zboară sus")

Iată o scurtă secvență cu o rețea neuronală antrenată.

O animație a clonei Flappy Bird redată de rețeaua neuronală antrenată rezultată

Rețeaua neuronală prezentată mai sus a fost antrenată folosind un GPU Nvidia GTX 1080 de vârf pentru câteva ore; folosind o soluție bazată pe CPU, această sarcină particulară ar dura câteva zile. FPS-urile motorului de joc au fost setate la un număr foarte mare în timpul antrenamentului: 999…999 – cu alte cuvinte, cât mai multe cadre pe secundă. În faza de testare, FPS a fost setat la 30.

Mai jos este o diagramă care arată cum s-a schimbat valoarea maximă Q în timpul iterațiilor. Este afișată fiecare a 10.000-a iterație. Un vârf descendent înseamnă că pentru un anumit cadru (o iterație fiind un cadru) rețeaua neuronală prezice că pasărea va primi o recompensă foarte mică în viitor - adică, se va prăbuși foarte curând.

Un grafic care arată cum s-a schimbat valoarea Q maximă în timpul iterațiilor. Începând de la zero și cu mai multe vârfuri în scădere, arată o tendință generală către un maxim al valorii Q în jurul valorii de 12 sau 13 după aproximativ un milion de iterații.

Codul complet și modelul pregătit sunt disponibile aici.

Învățare prin consolidare profundă: 2D, 3D și chiar viața reală

În acest tutorial de învățare cu întărire PyTorch, am arătat cum un computer poate învăța să joace Flappy Bird fără cunoștințe anterioare despre joc, folosind doar o abordare de încercare și eroare, așa cum ar face un om atunci când întâlnește jocul pentru prima dată.

Este interesant că algoritmul poate fi implementat în câteva linii de cod folosind cadrul PyTorch. Lucrarea pe care se bazează metoda din acest blog este relativ veche și sunt disponibile o mulțime de lucrări mai noi, cu diverse modificări care permit o convergență mai rapidă. De atunci, învățarea prin consolidare profundă a fost folosită pentru a juca jocuri 3D și în sistemele robotice din lumea reală.

Companii precum DeepMind, Maluuba și Vicarious lucrează intens la învățarea prin consolidare profundă. O astfel de tehnologie a fost folosită în AlphaGo, care l-a învins pe Lee Sedol, unul dintre cei mai buni jucători din lume la Go. La acea vreme, se considera că va dura cel puțin zece ani până când mașinile ar putea învinge cei mai buni jucători de la Go.

Fluxul de interes și investițiile în învățarea prin consolidare profundă (și în inteligența artificială în general) ar putea duce chiar la o potențială inteligență generală artificială (AGI) - inteligență la nivel uman (sau chiar mai departe) care poate fi exprimată sub forma unui algoritm și simulate pe computere. Dar AGI va trebui să facă obiectul unui alt articol.


Referinte:

  • Demistificarea Învățării prin întărire profundă
  • Învățare de întărire profundă pentru Flappy Bird
  • „Rețea neuronală convoluțională” la Wikipedia
  • „Învățare prin consolidare” la Wikipedia
  • „Procesul de decizie Markov” la Wikipedia
  • Curs University College London pe RL
Înrudit: Un tutorial de învățare profundă: de la perceptroni la rețele profunde