What is LSTM in PyTorch?

LSTM în PyTorch: Previziuni de Top pentru Serii Temporale

21/09/2022

Rating: 4.76 (1171 votes)

În lumea dinamică a datelor, capacitatea de a prezice evenimente viitoare este un avantaj imens. Fie că vorbim despre fluctuațiile pieței bursiere, modelele meteorologice sau chiar despre performanțele sportive ale unui atlet în timp, serii temporale sunt omniprezente. Provocarea constă în a extrage informații relevante din aceste secvențe de date și a le folosi pentru a face previziuni precise. Aici intervin rețelele neuronale recurente (RNN), iar, în special, o variantă a acestora, cunoscută sub numele de Long Short-Term Memory (LSTM), s-a dovedit a fi excepțională în gestionarea dependențelor pe termen lung. Astăzi, vom explora cum putem valorifica puterea LSTM-urilor folosind PyTorch, unul dintre cele mai populare framework-uri de învățare automată.

How do you use LSTM RNN?
Apply a multi-layer long short-term memory (LSTM) RNN to an input sequence. For each element in the input sequence, each layer computes the following function:

Vom parcurge etapele esențiale, de la pregătirea datelor și definirea arhitecturii modelului, până la antrenarea și evaluarea performanței acestuia. Scopul nostru este să înțelegem nu doar cum funcționează un model LSTM în contextul previziunilor, ci și cum să-l implementăm eficient în PyTorch pentru a obține rezultate superioare.

Cuprins

Ce este un LSTM și de ce este esențial pentru Serii Temporale?

Rețelele Neuronale Recurente (RNN-uri) tradiționale au fost concepute pentru a procesa secvențe de date, având o „memorie” a informațiilor anterioare. Cu toate acestea, ele se confruntă cu o problemă fundamentală: dificultatea de a reține informații pe memorie pe termen lung. Pe măsură ce secvențele devin mai lungi, gradientul de învățare tinde să dispară (problema gradientului evanescent) sau să explodeze (problema gradientului exploziv), făcând dificilă învățarea dependențelor între evenimente care sunt separate de intervale mari de timp. De exemplu, într-o serie temporală, o valoare actuală ar putea depinde de un eveniment care a avut loc cu sute de pași de timp în urmă.

Aici intervin rețelele LSTM. Acestea au fost special create pentru a rezolva problema memoriei pe termen lung prin introducerea unui mecanism de „celulă de memorie” și a unor „porți” (gates) care controlează fluxul de informații. O celulă LSTM conține patru elemente principale, care acționează ca niște filtre:

  • Poarta de intrare (Input Gate): Decide ce informații noi sunt relevante și ar trebui adăugate la starea celulei.
  • Poarta de uitare (Forget Gate): Decide ce informații din starea celulei curente ar trebui uitate sau eliminate.
  • Poarta de ieșire (Output Gate): Decide ce parte din starea celulei va fi expusă ca ieșire a stratului ascuns.
  • Starea celulei (Cell State): Reprezintă „banda transportoare” a memoriei, care transportă informații relevante de-a lungul secvenței, fără a fi afectată de interferențe.

Aceste porți (gates) sunt activate de funcții sigmoide și tanh, permițând modelului să adauge sau să elimine selectiv informații din stare a celulei. Această arhitectură complexă conferă LSTM-urilor capacitatea de a învăța și de a reține dependențe pe termen lung, făcându-le ideale pentru sarcini precum recunoașterea vorbirii, traducerea automată și, desigur, previziunea seriilor temporale.

LSTM în PyTorch: O Prezentare Detaliată a Modulului `nn.LSTM`

PyTorch oferă o implementare eficientă și flexibilă a LSTM-urilor prin modulul torch.nn.LSTM. Acesta este un bloc de construcție fundamental pentru crearea rețelelor neuronale recurente și vine cu o serie de parametri configurabili care permit adaptarea modelului la diverse nevoi.

Parametri Cheie ai `nn.LSTM`:

  • input_size: Reprezintă numărul de caracteristici (features) așteptate în intrarea x. Dacă previzionăm o singură serie temporală (univariată), acesta va fi 1. Dacă avem mai multe caracteristici per pas de timp (multivariată), va fi numărul acestora.
  • hidden_size: Numărul de unități ascunse (neuroni) în fiecare strat LSTM. Această dimensiune influențează capacitatea modelului de a învăța reprezentări complexe ale datelor.
  • num_layers: Numărul de straturi LSTM stivuite. Un num_layers=2 înseamnă că ieșirea primului strat LSTM devine intrarea pentru al doilea strat LSTM, și așa mai departe. Mai multe straturi pot capta dependențe mai complexe, dar necesită mai multe resurse de calcul și pot duce la supra-antrenare.
  • bias: Un boolean care, dacă este False, indică faptul că stratul nu va folosi ponderi de bias. Valoarea implicită este True.
  • batch_first: Dacă este True, tensorii de intrare și ieșire sunt furnizați în formatul (batch, seq, feature), în loc de (seq, batch, feature). Aceasta este o setare comună și adesea preferabilă pentru ușurința manipulării datelor.
  • dropout: Dacă este diferit de zero, introduce un strat Dropout pe ieșirile fiecărui strat LSTM, cu excepția ultimului. Dropout este o tehnică de regularizare care ajută la prevenirea supra-antrenare (overfitting).
  • bidirectional: Dacă este True, modelul devine un LSTM bidirecțional. Acesta procesează secvența atât înainte, cât și înapoi, capturând informații din ambele direcții și, ulterior, concatenând stările ascunse. Utile în aplicații unde contextul viitor este relevant pentru predicția prezentă.
  • proj_size: Dacă este mai mare decât 0, va utiliza LSTM-uri cu proiecții, schimbând dimensiunea stării ascunse la proj_size. Aceasta poate reduce numărul de parametri și poate fi utilă pentru modele mai mari.

Intrări și Ieșiri: Formatul Tensorilor

Înțelegerea formatului tensorilor de intrare și ieșire este crucială pentru a lucra corect cu nn.LSTM:

  • Intrare (input):
    • Fără batching: (L, H_in)
    • Cu batching (batch_first=False): (L, N, H_in)
    • Cu batching (batch_first=True): (N, L, H_in)

    Unde: L = lungimea secvenței (timpul), N = dimensiunea batch-ului, H_in = input_size.

  • Stări inițiale (h_0, c_0):
    • h_0 (starea ascunsă inițială): (D * num_layers, N, H_out)
    • c_0 (starea celulei inițială): (D * num_layers, N, H_cell)

    Unde: D = 2 dacă bidirectional=True, altfel 1. H_cell = hidden_size. H_out = proj_size dacă proj_size > 0, altfel hidden_size. Dacă nu sunt furnizate, se inițializează cu zerouri.

  • Ieșire (output):
    • Fără batching: (L, D * H_out)
    • Cu batching (batch_first=False): (L, N, D * H_out)
    • Cu batching (batch_first=True): (N, L, D * H_out)

    Conține caracteristicile de ieșire (h_t) de la ultimul strat al LSTM-ului pentru fiecare pas de timp t.

  • Stări finale (h_n, c_n):
    • h_n (starea ascunsă finală): (D * num_layers, N, H_out)
    • c_n (starea celulei finală): (D * num_layers, N, H_cell)

    Conțin starea ascunsă și starea celulei finale pentru fiecare element din secvență.

Pregătirea Datelor pentru Previziuni cu LSTM

Pentru a antrena un model LSTM pentru previziuni de serii temporale, trebuie să transformăm datele secvențiale într-un format adecvat pentru învățarea supervizată. Acest lucru se realizează, de obicei, printr-o tehnică numită „fereastră glisantă” (sliding window).

Tehnica Fereastră Glisantă:

Scopul acestei funcții este de a genera perechi de intrare-ieșire pentru antrenarea și testarea unui model de previziune a seriilor temporale. Aceste perechi sunt create prin glisarea unei ferestre de o anumită lungime (seq_length) peste datele seriei temporale. Pentru fiecare iterație, se creează o secvență de intrare x prin extragerea datelor de la indexul i la i + seq_length. Apoi, se creează valoarea țintă corespunzătoare y, selectând punctul de date de la indexul i + seq_length. Acest lucru transformă o serie temporală într-un set de date în care fiecare intrare este o secvență de observații anterioare, iar ieșirea este valoarea următoare de previzionat.

De exemplu, dacă avem o serie temporală [10, 20, 30, 40, 50] și seq_length = 2:

  • Prima pereche: Intrarea [10, 20], Ieșirea 30
  • A doua pereche: Intrarea [20, 30], Ieșirea 40
  • A treia pereche: Intrarea [30, 40], Ieșirea 50

După ce datele sunt aranjate în acest format, este esențial să le împărțim în seturi de antrenament și testare. Pentru serii temporale, este crucial să se mențină ordinea cronologică; prin urmare, se ia, de obicei, un procent din datele inițiale pentru antrenament și restul pentru testare.

Conversia la Tensori PyTorch:

PyTorch necesită ca datele să fie în formatul său specific de tensori, nu în array-uri NumPy. Astfel, după pregătirea datelor, este necesară o conversie simplă:

X_train_tensor = torch.tensor(X_train, dtype=torch.float32) y_train_tensor = torch.tensor(y_train, dtype=torch.float32) X_test_tensor = torch.tensor(X_test, dtype=torch.float32) y_test_tensor = torch.tensor(y_test, dtype=torch.float32)

Asigurați-vă că tipul de date este float32, deoarece majoritatea modelelor de învățare profundă lucrează cu acest tip de precizie.

Construirea Modelului LSTM în PyTorch

Pentru a defini modelul nostru LSTM, vom crea o clasă personalizată care moștenește de la torch.nn.Module. Aceasta ne permite să structurăm arhitectura rețelei și să definim cum datele trec prin ea.

import torch.nn as nn class LSTMModel(nn.Module): def __init__(self, input_size, hidden_size, num_layers, output_size): super(LSTMModel, self).__init__() self.hidden_size = hidden_size self.num_layers = num_layers self.lstm = nn.LSTM(input_size, hidden_size, num_layers, batch_first=True) self.fc = nn.Linear(hidden_size, output_size) def forward(self, x): # x shape: (batch_size, seq_length, input_size) h0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(x.device) c0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(x.device) # Forward propagate LSTM out, _ = self.lstm(x, (h0, c0)) # out shape: (batch_size, seq_length, hidden_size) # Decode the hidden state of the last time step out = self.fc(out[:, -1, :]) return out

Să explicăm componentele:

  • __init__: Constructorul clasei.
    • self.lstm: Definește stratul LSTM principal. Am setat batch_first=True, ceea ce înseamnă că ne așteptăm ca intrarea să fie de forma (batch_size, seq_length, input_size).
    • self.fc: Definește un strat liniar (Fully Connected) care va prelua ieșirea ultimului pas de timp al LSTM-ului și o va transforma în dimensiunea de ieșire dorită (output_size). Deoarece avem o țintă univariată (o singură valoare de previzionat), output_size va fi 1.
  • forward: Definește logica de propagare înainte a datelor prin rețea.
    • h0 și c0: Stările ascunse și ale celulei inițiale, inițializate cu zerouri. Este important să le trimitem pe același dispozitiv (CPU/GPU) ca și intrarea x.
    • self.lstm(x, (h0, c0)): Aici se realizează propagarea prin stratul LSTM. Ieșirea out va avea forma (batch_size, seq_length, hidden_size). Parametrul _ captează stările finale (h_n, c_n), pe care nu le folosim direct în acest caz.
    • out[:, -1, :]: Selectăm ieșirea ultimului pas de timp din secvență pentru fiecare element din batch. Aceasta este ieșirea pe care o vom folosi pentru a face predicția finală, deoarece conține „rezumatul” întregii secvențe de intrare.
    • self.fc(...): Aplicăm stratul liniar pentru a obține predicția finală.

Puteți experimenta cu numărul de straturi ascunse (num_layers) sau dimensiunea acestora (hidden_size) pentru a vedea cum afectează performanța modelului. Acestea sunt hiperparametri cheie ce necesită ajustare.

Antrenarea Modelului LSTM

Antrenarea unui model LSTM urmează un ciclu standard de învățare profundă: definirea unei funcții de pierdere, alegerea unui optimizator și parcurgerea repetată a datelor în epoci.

Funcția de Pierdere și Optimizatorul:

Pentru probleme de previziune (regresie), eroarea pătratică medie (Mean Squared Error - MSE) este o alegere comună pentru funcția de pierdere. Optimizatorul Adam este adesea o alegere bună, fiind rapid și robust.

loss_function = nn.MSELoss() optimizer = torch.optim.Adam(model.parameters(), lr=0.001)

Puteți ajusta rata de învățare (lr) și numărul de epoci (num_epochs) pentru a optimiza procesul de antrenare.

What is LSTM in PyTorch?
This code defines a custom PyTorch nn.Module class named LSTM that represents a Long Short-Term Memory (LSTM) neural network model for time series forecasting. To explain the inputs: input_size: The number of input features for each time step. hidden_size: The number of hidden units in each LSTM layer. num_layers: The number of stacked LSTM layers.

Bucla de Antrenare:

Bucla de antrenare implică iterarea de mai multe ori (epoci) peste setul de date de antrenament. Pentru fiecare epocă, modelul face predicții, calculează pierderea și actualizează ponderile.

num_epochs = 100 for epoch in range(num_epochs): model.train() optimizer.zero_grad() # Resetează gradienții # Adăugăm o dimensiune suplimentară pentru a se potrivi cu intrarea așteptată de LSTM X_train_input = X_train_tensor.unsqueeze(-1) # Shape (batch, seq, feature) outputs = model(X_train_input) # Squeeze pentru a elimina dimensiunile de 1, dacă este necesar outputs = outputs.squeeze() loss = loss_function(outputs, y_train_tensor) loss.backward() # Calculul gradienților optimizer.step() # Actualizarea ponderilor if (epoch+1) % 10 == 0: print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {loss.item():.4f}')

Observați liniile X_train_input = X_train_tensor.unsqueeze(-1) și outputs = outputs.squeeze(). Acestea sunt cruciale pentru a gestiona dimensiunile tensorilor:

  • X_train_tensor.unsqueeze(-1): Adaugă o dimensiune suplimentară la tensorul X_train_tensor. Acest lucru este necesar deoarece modelul LSTM așteaptă tensori de intrare în formatul (batch, sequence, feature). Chiar dacă aveți o singură caracteristică (cum ar fi într-o serie univariată), PyTorch are nevoie de acea dimensiune explicită a caracteristicilor.
  • outputs = outputs.squeeze(): Această operație elimină orice dimensiuni cu o mărime de 1 din tensorul de ieșire. De exemplu, dacă ieșirea modelului este (batch_size, 1), squeeze() o va transforma în (batch_size), simplificând calculele ulterioare (cum ar fi calculul pierderii cu ținta y_train_tensor care, probabil, este de forma (batch_size)).

optimizer.zero_grad() resetează gradienții parametrilor modelului înainte de a începe o nouă iterație de optimizare. Acest lucru este important, deoarece gradienții se acumulează implicit în PyTorch, iar eșecul de a-i reseta ar duce la calcule incorecte ale gradienților în iterațiile ulterioare.

Evaluarea și Benchmarking-ul Performanței

După antrenare, este esențial să evaluăm performanța modelului pe date nevăzute (setul de testare) și să o comparăm cu alte abordări pentru a înțelege cât de bine funcționează.

Evaluarea Pe Setul de Testare:

Pentru testare, nu avem nevoie de calculul gradienților, deoarece nu mai antrenăm modelul. De aceea, folosim torch.no_grad().

model.eval() # Setează modelul în modul de evaluare with torch.no_grad(): X_test_input = X_test_tensor.unsqueeze(-1) test_predictions = model(X_test_input) test_predictions = test_predictions.squeeze() test_loss = loss_function(test_predictions, y_test_tensor) print(f'Test Loss: {test_loss.item():.4f}')

Un aspect interesant de urmărit este comparația dintre pierderea de antrenament și pierderea de testare. Dacă pierderea de antrenament continuă să scadă semnificativ, în timp ce pierderea de testare stagnează sau crește, acesta este un semn clar de supra-antrenare (overfitting). În acest caz, modelul a învățat prea mult zgomotul din datele de antrenament și nu generalizează bine pe date noi.

Benchmarking Împotriva Alternativelor:

Este modelul nostru LSTM cu adevărat bun? Pentru a răspunde la această întrebare, trebuie să-l comparăm cu alte modele, inclusiv cu alternative mai simple sau mai clasice. Pachetul timemachines, de exemplu, oferă o gamă de „skateri” (modele de previziune) pentru a compara performanța.

Să presupunem că folosim o funcție helper pentru predicție care dezactivează gradienții:

def predict_torch_model(model, data_tensor): model.eval() with torch.no_grad(): input_tensor = torch.tensor(data_tensor, dtype=torch.float32).unsqueeze(-1) prediction = model(input_tensor).squeeze().item() return prediction

Apoi, putem compara performanța LSTM-ului cu un model precum thinking_fast_and_slow din pachetul timemachines, care este un model simplu de previziune a seriilor temporale ce combină netezirea exponențială cu urmărirea reziduurilor.

Pentru o înțelegere mai cuprinzătoare a performanței modelului LSTM, este benefic să-i testați performanța împotriva mai multor „skateri” din pachetul timemachines. Unii „skateri” populari includ tbats (Trigonometric Seasonal, Box-Cox Transformation, ARIMA Errors, Trend, and Seasonal Components) și prophet (modelul de previziune al seriilor temporale de la Facebook).

Prin compararea performanței modelului LSTM cu multiple metode, puteți obține o înțelegere mai bună a performanței sale relative în contextul diferitelor abordări de previziune a seriilor temporale. De multe ori, veți constata că, pentru seturi de date complexe și dependențe pe termen lung, LSTM-ul va ieși învingător.

Comparație a Performanței Modelelor de Previziune (Exemplu Ilustrativ)

ModelMSE (Eroare Pătratică Medie)
Modelul LSTM antrenat0.0092
Thinking Fast and Slow (timemachines)0.0150
TBATS (timemachines)0.0120
Prophet (timemachines)0.0115

După cum se vede în acest exemplu ilustrativ, modelul nostru LSTM antrenat a obținut o eroare pătratică medie semnificativ mai mică, demonstrând superioritatea sa în acest scenariu particular. Aceasta subliniază importanța alegerii modelului potrivit și a unei implementări atente pentru a obține cele mai bune rezultate în previziunea seriilor temporale.

Întrebări Frecvente (FAQ)

Ce este diferența dintre RNN și LSTM?

Principala diferență este capacitatea LSTM-urilor de a gestiona dependențele pe termen lung. RNN-urile tradiționale suferă de problema gradientului evanescent, ceea ce le face să „uite” informațiile relevante din trecutul îndepărtat. LSTM-urile rezolvă această problemă prin introducerea porților de intrare, uitare și ieșire, precum și a unei stări a celulei, care le permite să controleze fluxul de informații și să rețină memoria pe perioade mai lungi.

Când ar trebui să folosesc un LSTM?

LSTM-urile sunt ideale pentru orice problemă care implică date secvențiale și necesită înțelegerea contextului pe termen lung. Exemple includ previziunea seriilor temporale (financiare, meteo, trafic), procesarea limbajului natural (traducere automată, generare de text), recunoașterea vorbirii și analiza video.

Cum aleg `hidden_size` și `num_layers`?

Acestea sunt hiperparametri care necesită experimentare. Un hidden_size mai mare permite modelului să învețe reprezentări mai complexe, dar crește costul computațional și riscul de supra-antrenare. Similar, un număr mai mare de straturi (num_layers) poate capta relații mai abstracte. Începeți cu valori mici (ex: hidden_size=50-100, num_layers=1-2) și măriți-le treptat, monitorizând performanța pe setul de validare.

Ce înseamnă `batch_first`?

batch_first este un parametru în modulele RNN din PyTorch (inclusiv LSTM) care definește ordinea dimensiunilor tensorilor de intrare și ieșire. Dacă este True, dimensiunile sunt (batch_size, sequence_length, features). Dacă este False (implicit), ordinea este (sequence_length, batch_size, features). Setarea la True este adesea mai intuitivă și se aliniază cu modul în care sunt structurate datele în majoritatea operațiilor de învățare profundă.

De ce este important `unsqueeze(-1)` și `squeeze()`?

unsqueeze(-1) este utilizat pentru a adăuga o nouă dimensiune la un tensor. În cazul LSTM-urilor univariate, chiar dacă avem o singură caracteristică, modelul se așteaptă la o dimensiune explicită pentru caracteristici. De exemplu, un tensor de (batch_size, seq_length) trebuie să devină (batch_size, seq_length, 1). squeeze() este inversul, eliminând dimensiunile cu mărimea 1, ceea ce este util pentru a aduce ieșirile modelului la o formă compatibilă cu țintele (de exemplu, de la (batch_size, 1) la (batch_size)).

Concluzie

Am explorat în detaliu cum să construim și să antrenăm un model LSTM în PyTorch pentru sarcini de previziune a seriilor temporale. De la înțelegerea arhitecturii fundamentale a LSTM-urilor, la pregătirea datelor, definirea modelului în PyTorch și, în final, la procesul de antrenare și evaluare, am parcurs pașii esențiali.

LSTM-urile reprezintă un instrument puternic în arsenalul oricărui specialist în știința datelor, oferind o soluție robustă pentru problemele complexe de previziune care implică dependențe pe termen lung. Capacitatea lor de a reține și de a filtra informațiile relevante din secvențe lungi de date le face indispensabile în domenii variate, de la finanțe la medicină și dincolo de acestea. Prin PyTorch, implementarea și experimentarea cu aceste modele devin accesibile, deschizând uși către predicții mai precise și decizii mai bine informate.

Dacă vrei să descoperi și alte articole similare cu LSTM în PyTorch: Previziuni de Top pentru Serii Temporale, poți vizita categoria Fitness.

Go up