Perinteiset RNN:t: piilotila ja sekvenssin purkaminen
Oppijat toteuttavat yhden askeleen RNN-solun manuaalisesti, purkavat sen lyhyen sekvenssin yli ja visualisoivat, miten piilotila kerää asiayhteyttä.
Perinteiset RNN:t: piilotila ja sekvenssin purkaminen on ilmainen Machine Learning Academy-oppitunti CoddyKitissä. Tämä on oppitunti 1/4. Voit lukea koko oppitunnin alta ilmaiseksi ja harjoitella sen jälkeen käytännössä selaimessa sisäänrakennetulla koodieditorilla ja ympäri vuorokauden käytettävissä olevan tekoälytuutorin avulla. Oppitunti kuuluu Machine Learning Academy-oppimispolkuun, ja edistymisesi synkronoituu verkon ja CoddyKit-sovelluksen välillä. Machine Learning Academy-kurssilla on yhteensä 4 oppituntia.
Miksi tarvitsemme rekurrenttisia verkkoja
Tavalliset eteenpäinsyöttävät verkot käsittelevät jokaista syötettä itsenäisesti — niillä ei ole muistia aiemmista syötteistä. Monet todellisen maailman ongelmat kuitenkin sisältävät sekventiaalista dataa, jossa menneisyyden kontekstilla on merkitystä: seuraavan sanan ennustaminen lauseessa, huomisen osakekurssin ennustaminen historiadatan perusteella tai eleen luokitteleminen videon kuvasekvenssistä. Recurrent Neural Networks (RNNs) ratkaisevat tämän ylläpitämällä piilotilaa, joka kuljettaa tietoa aika-askelten välillä.
# Examples of sequential data:
sequences = {
'NLP': 'The cat sat on the ___ (predict next word)',
'Time Series': '[1.2, 1.5, 1.3, 1.8, ???]',
'Speech': '[audio_t0, audio_t1, ..., audio_tN]',
'Video': '[frame_1, frame_2, ..., frame_T]',
'DNA': 'ATCGATCG... (biological sequence)',
}
for name, example in sequences.items():
print(f'{name}: {example}')Perustason RNN-solu
Perustason RNN-solu ottaa kaksi syötettä: nykyisen syötteen x_t ja edellisen piilotilan h_{t-1}. Se tuottaa seuraavan piilotilan h_t kaavalla: h_t = tanh(W_hh * h_{t-1} + W_xh * x_t + b). Painomatriiseja W_hh ja W_xh käytetään jokaisella aika-askeleella — tätä kutsutaan painojen jakamiseksi ajan yli, mikä vastaa sitä, miten CNN:t jakavat painoja avaruuden yli. Piilotila sisältää verkon muistin kaikista aiemmista syötteistä.
import torch
def rnn_cell(x_t, h_prev, W_xh, W_hh, b):
'''One step of a vanilla RNN cell'''
# x_t: (batch, input_size)
# h_prev: (batch, hidden_size)
h_t = torch.tanh(
x_t @ W_xh.T + # input contribution
h_prev @ W_hh.T + # hidden-to-hidden contribution
b # bias
)
return h_t
# Example: input_size=4, hidden_size=8
batch = 3
x_t = torch.randn(batch, 4)
h_prev = torch.zeros(batch, 8)
W_xh = torch.randn(8, 4) * 0.01
W_hh = torch.randn(8, 8) * 0.01
b = torch.zeros(8)
h_t = rnn_cell(x_t, h_prev, W_xh, W_hh, b)
print(h_t.shape) # (3, 8)RNN:n avaaminen ajan kuluessa
Kun käsitellään T:n pituista sekvenssiä, RNN-solua käytetään silmukassa T kertaa — tätä kutsutaan avaamiseksi (tai levittämiseksi) ajan kuluessa. Askeleen t-1 piilotila välitetään askeleelle t, jolloin kaikki aika-askeleet liittyvät toisiinsa. Takaisinlevityksessä gradienttien on kuljettava taaksepäin jokaisen aika-askeleen läpi — tätä kutsutaan Backpropagation Through Time (BPTT) -menetelmäksi. Tämän avatun graafin syvyys vastaa sekvenssin pituutta, mikä aiheuttaa haasteita pitkien sekvenssien käsittelyssä.
import torch
import torch.nn as nn
# Unroll RNN manually over a sequence
batch_size, seq_len, input_size, hidden_size = 4, 10, 8, 16
rnn_cell = nn.RNNCell(input_size, hidden_size)
sequence = torch.randn(seq_len, batch_size, input_size)
h = torch.zeros(batch_size, hidden_size) # initial hidden state
hidden_states = []
for t in range(seq_len):
x_t = sequence[t] # (batch, input_size)
h = rnn_cell(x_t, h) # apply RNN cell
hidden_states.append(h)
print('Final hidden state:', h.shape) # (4, 16)
print('All hidden states:', len(hidden_states), 'steps')nn.RNN:n käyttö: moduuliversio
nn.RNN huolehtii avaamisesta automaattisesti. Se ottaa syötteitä muodossa (seq_len, batch, input_size) (tai muodossa (batch, seq_len, input_size), kun käytetään asetusta batch_first=True) ja palauttaa kaikki piilotilat sekä viimeisen piilotilan. Keskeiset parametrit: num_layers pinoaa useita RNN-kerroksia, bidirectional=True käsittelee sekvenssin molempiin suuntiin ja dropout käyttää dropout-menetelmää monikerroksisten RNN:ien kerrosten välillä.
import torch
import torch.nn as nn
rnn = nn.RNN(
input_size=16,
hidden_size=32,
num_layers=2,
batch_first=True, # input: (batch, seq, features)
dropout=0.2 # between layers
)
X = torch.randn(8, 20, 16) # batch=8, seq=20, features=16
output, h_n = rnn(X)
print('Output shape:', output.shape) # (8, 20, 32) all steps
print('h_n shape:', h_n.shape) # (2, 8, 32) last hiddenPiilotilan alustaminen
Alustava piilotila h_0 välitetään nn.RNN:lle toisena argumenttina. Jos sitä ei anneta, oletusarvona käytetään nollia. Sekvenssien luokittelussa viimeinen piilotila h_n tiivistää koko sekvenssin. Sekvenssistä sekvenssiksi -tehtävissä käytetään kaikkia output-muuttujan välipiilotiloja. h_0:n alustaminen nollilla on vakiokäytäntö; joissakin sovelluksissa alustava tila opitaan parametrina, jotta lyhyiden sekvenssien suorituskyky paranisi.
import torch
import torch.nn as nn
rnn = nn.RNN(8, 16, batch_first=True)
X = torch.randn(4, 10, 8) # batch=4, seq=10, input=8
# Default: h_0 = zeros
output, h_n = rnn(X)
print('h_n with zero init:', h_n.shape) # (1, 4, 16)
# Custom initial hidden state
h_0 = torch.randn(1, 4, 16) # (num_layers, batch, hidden)
output, h_n = rnn(X, h_0)
print('h_n with custom init:', h_n.shape) # (1, 4, 16)Sekvenssien luokittelu viimeisen piilotilan avulla
Yksi RNN:ien yleinen käyttötapaus on sekvenssien luokittelu: sekvenssin perusteella ennustetaan yksi luokkatunniste. Tavallinen lähestymistapa on käyttää vain viimeistä piilotilaa h_n lineaarisen luokittelijan syötteenä, koska se on nähnyt koko sekvenssin siihen asti. Kaksisuuntaisessa RNN:ssä eteenpäin ja taaksepäin kulkevat viimeiset piilotilat yhdistetään, jotta mukaan saadaan sekä menneisyyden että tulevaisuuden konteksti.
import torch
import torch.nn as nn
class SequenceClassifier(nn.Module):
def __init__(self, input_size, hidden_size, num_classes):
super().__init__()
self.rnn = nn.RNN(input_size, hidden_size,
batch_first=True)
self.fc = nn.Linear(hidden_size, num_classes)
def forward(self, x):
_, h_n = self.rnn(x)
# h_n shape: (1, batch, hidden) -> squeeze to (batch, hidden)
h_n = h_n.squeeze(0)
return self.fc(h_n)
model = SequenceClassifier(16, 32, 5)
X = torch.randn(8, 20, 16)
logits = model(X)
print(logits.shape) # (8, 5) -- 8 samples, 5 classesMonesta moneen: sekvenssien nimeäminen
Sekvenssien annotoinnissa (POS-tägäys, nimettyjen entiteettien tunnistus, aikasarjojen poikkeamien tunnistus) tarvitsette ennusteen jokaisessa aikavaiheessa, ei vain viimeisessä. Käyttäkää nn.RNN:n koko output-tensor ia (muoto: batch x seq_len x hidden_size) ja käyttäkää lineaarista kerrosta kuhunkin aikavaiheeseen erikseen. Lineaarisen kerroksen painot jaetaan aikavaiheiden kesken — tämä on jälleen yksi esimerkki sekvenssimallien painojen jakamisesta.
import torch
import torch.nn as nn
class SequenceLabeler(nn.Module):
def __init__(self, input_size, hidden_size, num_classes):
super().__init__()
self.rnn = nn.RNN(input_size, hidden_size,
batch_first=True)
self.fc = nn.Linear(hidden_size, num_classes)
def forward(self, x):
output, _ = self.rnn(x) # (batch, seq, hidden)
return self.fc(output) # (batch, seq, num_classes)
model = SequenceLabeler(8, 16, 3)
X = torch.randn(4, 10, 8) # 4 seqs of 10 timesteps
logits = model(X)
print(logits.shape) # (4, 10, 3) per-step labelsPinotut ja kaksisuuntaiset RNN:t
Pinotut RNN:t (num_layers > 1) syöttävät yhden RNN-kerroksen tulosteen seuraavan kerroksen syötteeksi ja oppivat vähitellen abstraktimpia ajallisia esityksiä. Kaksisuuntaiset RNN:t käsittelevät sekvenssin samanaikaisesti sekä eteenpäin (vasemmalta oikealle) että taaksepäin (oikealta vasemmalle) ja yhdistävät sitten piilotilat. Kaksisuuntainen käsittely mahdollistaa sen, että jokainen tuloste sisältää kontekstia sekä menneisyydestä että tulevaisuudesta — tästä on hyötyä lauseiden ymmärtämisessä, jossa myöhemmät sanat selventävät aiempien sanojen merkitystä.
import torch
import torch.nn as nn
# Stacked bidirectional RNN
brnn = nn.RNN(
input_size=16,
hidden_size=32,
num_layers=3, # 3 stacked layers
batch_first=True,
bidirectional=True # forward + backward
)
X = torch.randn(4, 10, 16)
output, h_n = brnn(X)
# Output: (batch, seq, hidden*2) because bidirectional
print('Output shape:', output.shape) # (4, 10, 64)
# h_n: (num_layers*2, batch, hidden) -- 2 dirs x 3 layers
print('h_n shape:', h_n.shape) # (6, 4, 32)Takaisinkytkentä ajan läpi (BPTT)
Takaisinkytkentä ajan läpi avaa RNN:n aikavaiheiksi ja käyttää tavallista takaisinkytkentää avatussa laskentagraafissa. Kun sekvenssin pituus on T, gradientit lasketaan jokaisessa aikavaiheessa ja välitetään taaksepäin. Häviön parametrien suhteen laskettu gradientti sisältää saman painomatriisin W_hh tulon itseensä T kertaa. Kun W_hh:n spektraalisäde on pienempi kuin 1, nämä tulot häviävät; kun se on suurempi kuin 1, ne räjähtävät — tämä on RNN:ien pitkien sekvenssien opettamisen keskeinen haaste.
import torch
import torch.nn as nn
# Demonstrate gradient flow through different sequence lengths
rnn = nn.RNNCell(4, 8)
h = torch.zeros(1, 8, requires_grad=True)
# Short sequence: gradients flow back relatively well
for t in range(5):
x = torch.randn(1, 4)
h = rnn(x, h)
loss = h.sum()
loss.backward()
print('h.grad (seq=5):', h.grad.norm().item())
# For long sequences (T=100+), vanilla RNN gradients
# typically vanish (near zero) or explode (very large)
# This is why LSTM/GRU were inventedEripituisten sekvenssien käsittely
Käytännössä erässä olevat sekvenssit ovat eripituisia (lauseissa on eri määrä sanoja). PyTorch käsittelee tämän pakatuilla sekvensseillä: torch.nn.utils.rnn.pack_padded_sequence poistaa täytteen erästä ja pakkaa sekvenssit tehokkaasti. RNN:n jälkeen pad_packed_sequence palauttaa täytetyn muodon. Ilman pakkaamista RNN käsittelee täytetunnuksia tarpeettomasti, ja merkityksetön täytetieto voi sekoittaa piilotilaa.
import torch
import torch.nn as nn
from torch.nn.utils.rnn import pack_padded_sequence, pad_packed_sequence
rnn = nn.RNN(4, 8, batch_first=True)
# Padded sequences: length 5, 3, 2
X = torch.zeros(3, 5, 4) # (batch=3, max_seq=5, features=4)
X[0, :, :] = torch.randn(5, 4) # full seq
X[1, :3, :] = torch.randn(3, 4) # length 3
X[2, :2, :] = torch.randn(2, 4) # length 2
lengths = torch.tensor([5, 3, 2])
packed = pack_padded_sequence(X, lengths, batch_first=True)
out_packed, h_n = rnn(packed)
out, _ = pad_packed_sequence(out_packed, batch_first=True)
print(out.shape) # (3, 5, 8) -- back to padded formMilloin kannattaa käyttää tavallisia RNN:iä
Tavallisia RNN:iä käytetään käytännössä harvoin, koska niiden gradientit häviävät voimakkaasti yli 10–20 aikavaiheen sekvensseissä. Ne soveltuvat ensisijaisesti opetustarkoituksiin ja erittäin lyhyille sekvensseille. Käyttäkää kaikissa tosielämän sovelluksissa, joissa sekvenssit ovat yli 20 aikavaiheen pituisia, LSTM- tai GRU-verkkoja, joiden porttirakenteet on suunniteltu säilyttämään tietoa pitkien etäisyyksien yli. Kun järjestys ei ole yhtä tärkeä, Transformer-arkkitehtuurit suoriutuvat usein paremmin kuin kumpikaan näistä.
# When to use each sequence model:
use_cases = {
'Vanilla RNN (nn.RNN)': 'Short sequences (<20 steps), learning/demos',
'LSTM': 'Long sequences, NLP, time-series (general)',
'GRU': 'Similar to LSTM but faster, fewer params',
'Transformer': 'Parallelisable, long documents, state-of-art NLP',
'Temporal Conv (TCN)': 'Long sequences, strong parallelism, audio',
}
for model, use in use_cases.items():
print(f'{model}: {use}')Pikatarkistus
Testatkaa, kuinka hyvin hallitsette tämän oppitunnin Machine Learning with Python -käsitteet.
Oppitunnin yhteenveto
Tässä oppitunnissa opitte, että tavalliset RNN:t ylläpitävät piilotilaa, joka säilyttää muistin aikavaiheiden yli kaavalla h_t = tanh(W_xh * x_t + W_hh * h_{t-1} + b), avaaminen käyttää samaa solua jokaisessa aikavaiheessa ja jakaa painot, ja BPTT välittää gradientit taaksepäin avatun laskentagraafin läpi, mikä aiheuttaa pitkissä sekvensseissä gradienttien häviämis- ja räjähtämisongelmia. Seuraavaksi tarkastelemme gradienttien häviämisongelmaa perusteellisesti ja ymmärrämme, miksi LSTM suunniteltiin ratkaisemaan se.
Opi Python tekoälytuutorin avulla — ilmaiseksi
Kirjoita ja suorita oikeaa koodia selaimessa, saa välitöntä apua tekoälytuutorilta ympäri vuorokauden ja jatka siitä, mihin jäit, verkossa tai sovelluksessa.
- Kurssit
- 30
- Oppitunnit
- 120
Usein kysytyt kysymykset
Onko oppitunti ”Perinteiset RNN:t: piilotila ja sekvenssin purkaminen” ilmainen?
Kyllä – oppitunnin ”Perinteiset RNN:t: piilotila ja sekvenssin purkaminen” koko tekstin voi lukea täällä verkossa ilmaiseksi. Jos haluat harjoitella interaktiivisesti sisäänrakennetulla koodieditorilla ja ympäri vuorokauden käytettävissä olevan tekoälytuutorin avulla sekä avata koko Machine Learning Academy-kurssin, päivitä CoddyKit PROhon. Machine Learning Academy-kurssilla on yhteensä 4 oppituntia.
Mitä opin oppitunnilla ”Perinteiset RNN:t: piilotila ja sekvenssin purkaminen”?
Oppijat toteuttavat yhden askeleen RNN-solun manuaalisesti, purkavat sen lyhyen sekvenssin yli ja visualisoivat, miten piilotila kerää asiayhteyttä. Harjoittelet Machine Learning Academy-aihetta koodilla, jonka suoritat suoraan selaimessa. Ympäri vuorokauden käytettävissä oleva tekoälytuutori vastaa kysymyksiisi oppitunnin aikana.
Tarvitsenko kokemusta aloittaakseni Machine Learning Academy-opiskelun?
Aiempi kokemus ei ole tarpeen. CoddyKitin Machine Learning Academy-oppimispolku sopii vasta-alkajista edistyneisiin, joten voit aloittaa tästä tai alusta ja edetä omaan tahtiisi. Tämä on oppitunti 1/4.
Kuinka kauan ”Perinteiset RNN:t: piilotila ja sekvenssin purkaminen”-oppitunnin suorittaminen kestää?
Useimmat CoddyKitin oppitunnit kestävät noin 5–10 minuuttia. Jokainen oppitunti on lyhyt ja interaktiivinen, joten edistyt tasaisesti ja voit jatkaa siitä, mihin jäit – sekä verkossa että sovelluksessa.
Voinko kirjoittaa ja suorittaa koodia tällä Machine Learning Academy-oppitunnilla?
Kyllä. Jokainen Machine Learning Academy-oppitunti sisältää sisäänrakennetun koodieditorin, joten voit kirjoittaa ja suorittaa oikeaa koodia suoraan selaimessa ja saada välitöntä palautetta tekoälyltä – paikallista asennusta ei tarvita.
Kaikki tämän kurssin oppitunnit
- Perinteiset RNN:t: piilotila ja sekvenssin purkaminen
- Gradientin katoamisongelma pitkissä aika-askelissa
- LSTM-solu: syöte-, unohdus- ja tulostusportit
- Sekvenssistä yhteen tulokseen: mielipideanalyysi LSTM:llä