RNN แบบพื้นฐาน: สถานะซ่อนเร้นและการคลี่ลำดับ
ผู้เรียนจะสร้างเซลล์ RNN แบบหนึ่งขั้นด้วยตนเอง คลี่การทำงานไปตามลำดับสั้น ๆ และแสดงภาพว่าสถานะซ่อนเร้นสะสมบริบทอย่างไร
RNN แบบพื้นฐาน: สถานะซ่อนเร้นและการคลี่ลำดับ เป็นบทเรียน Machine Learning Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 1 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน Machine Learning Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส Machine Learning Academy มีบทเรียนทั้งหมด 4 บทเรียน
เหตุใดเราจึงต้องใช้โครงข่ายเวียนกลับ
โครงข่ายแบบป้อนไปข้างหน้ามาตรฐานจะประมวลผลอินพุตแต่ละรายการแยกจากกัน จึงไม่มีความจำเกี่ยวกับอินพุตก่อนหน้า แต่ปัญหาในโลกจริงจำนวนมากเกี่ยวข้องกับข้อมูลลำดับซึ่งบริบทจากอดีตมีความสำคัญ เช่น การทำนายคำถัดไปในประโยค การพยากรณ์ราคาหุ้นในวันพรุ่งนี้จากข้อมูลในอดีต หรือการจำแนกท่าทางจากลำดับเฟรมวิดีโอ โครงข่ายประสาทเทียมแบบเวียนกลับ (RNN)แก้ปัญหานี้ด้วยการรักษาสถานะซ่อนเร้นที่ส่งต่อข้อมูลข้ามช่วงเวลา
# 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}')เซลล์ RNN แบบพื้นฐาน
เซลล์ RNN แบบพื้นฐานรับอินพุตสองรายการ ได้แก่ อินพุตปัจจุบัน x_t และสถานะซ่อนเร้นก่อนหน้า h_{t-1} จากนั้นสร้างสถานะซ่อนเร้นถัดไป h_t โดยใช้สูตร h_t = tanh(W_hh * h_{t-1} + W_xh * x_t + b) เมทริกซ์น้ำหนัก W_hh และ W_xh เดิมจะถูกใช้ในทุกช่วงเวลา ซึ่งเรียกว่าการใช้น้ำหนักร่วมกันตามเวลา คล้ายกับการที่ CNN ใช้น้ำหนักร่วมกันตามตำแหน่งเชิงพื้นที่ สถานะซ่อนเร้นจะเก็บความจำของโครงข่ายเกี่ยวกับอินพุตทั้งหมดในอดีต
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 ตามเวลา
ในการประมวลผลลำดับที่มีความยาว T จะใช้เซลล์ RNN ซ้ำ T ครั้งในลูป ซึ่งเรียกว่าการคลี่ (หรือการกาง) ตามเวลา สถานะซ่อนเร้นจากขั้นที่ t-1 จะถูกส่งต่อไปยังขั้นที่ t เพื่อเชื่อมโยงทุกช่วงเวลา ระหว่างการแพร่ย้อนกลับ เกรเดียนต์ต้องไหลย้อนกลับผ่านทุกช่วงเวลา ซึ่งเรียกว่าการแพร่ย้อนกลับตามเวลา (BPTT) ความลึกของกราฟที่คลี่ออกนี้เท่ากับความยาวของลำดับ จึงทำให้เกิดความท้าทายเมื่อลำดับยาว
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: รุ่นโมดูล
nn.RNNจัดการการคลี่ตามเวลาให้โดยอัตโนมัติ รับอินพุตที่มีรูปร่าง (seq_len, batch, input_size) (หรือ (batch, seq_len, input_size) เมื่อใช้ batch_first=True) และส่งคืนสถานะซ่อนเร้นทั้งหมดพร้อมสถานะซ่อนเร้นสุดท้าย พารามิเตอร์สำคัญ ได้แก่ num_layers ซึ่งซ้อนชั้น RNN หลายชั้น bidirectional=True ซึ่งประมวลผลลำดับทั้งสองทิศทาง และ dropout ซึ่งใช้ดรอปเอาต์ระหว่างชั้นใน RNN หลายชั้น
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 hiddenการกำหนดค่าเริ่มต้นของสถานะซ่อนเร้น
สถานะซ่อนเร้นเริ่มต้น h_0 จะถูกส่งเป็นอาร์กิวเมนต์ตัวที่สองให้กับ nn.RNN หากไม่ระบุ ระบบจะกำหนดเป็นศูนย์โดยปริยาย สำหรับการจำแนกลำดับ สถานะซ่อนเร้นสุดท้าย h_n จะสรุปลำดับทั้งหมด สำหรับงานลำดับต่อลำดับ จะใช้สถานะซ่อนเร้นระหว่างทางทั้งหมดใน output การกำหนด h_0 เป็นศูนย์ถือเป็นมาตรฐาน แต่บางแอปพลิเคชันจะเรียนรู้สถานะเริ่มต้นเป็นพารามิเตอร์ เพื่อเพิ่มประสิทธิภาพกับลำดับสั้น
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)การจำแนกลำดับด้วยสถานะซ่อนเร้นสุดท้าย
การใช้งาน RNN ที่พบบ่อยคือการจำแนกลำดับ ซึ่งรับลำดับแล้วทำนายป้ายกำกับคลาสเดียว แนวทางมาตรฐานคือใช้เฉพาะสถานะซ่อนเร้นสุดท้าย h_n เป็นอินพุตให้ตัวจำแนกเชิงเส้น เนื่องจากสถานะนี้เห็นลำดับทั้งหมดจนถึงจุดนั้นแล้ว สำหรับ RNN แบบสองทิศทาง ให้ต่อสถานะซ่อนเร้นสุดท้ายของทิศทางไปข้างหน้าและย้อนกลับเข้าด้วยกัน เพื่อเก็บบริบททั้งจากอดีตและอนาคต
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 classesหลายต่อหลาย: การติดป้ายกำกับลำดับ
ในงานการติดป้ายกำกับลำดับ (การติดแท็ก POS การรู้จำเอนทิตีที่มีชื่อ และการตรวจจับความผิดปกติในอนุกรมเวลา) จำเป็นต้องทำนายที่ทุกช่วงเวลา ไม่ใช่เฉพาะช่วงสุดท้าย ให้ใช้เทนเซอร์ output ทั้งหมดจาก nn.RNN (รูปร่าง: batch x seq_len x hidden_size) แล้วใช้ชั้นเชิงเส้นกับแต่ละช่วงเวลาแยกกัน น้ำหนักของชั้นเชิงเส้นจะใช้ร่วมกันตลอดทุกช่วงเวลา ซึ่งเป็นอีกตัวอย่างหนึ่งของการใช้น้ำหนักร่วมกันในโมเดลลำดับ
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 labelsRNN แบบซ้อนชั้นและสองทิศทาง
RNN แบบซ้อนชั้น (num_layers > 1) จะส่งผลลัพธ์จากชั้น RNN หนึ่งไปเป็นข้อมูลนำเข้าของชั้นถัดไป ทำให้เรียนรู้ตัวแทนเชิงเวลาที่เป็นนามธรรมมากขึ้นตามลำดับ RNN แบบสองทิศทาง จะประมวลผลลำดับทั้งจากด้านหน้า (ซ้ายไปขวา) และด้านหลัง (ขวาไปซ้าย) พร้อมกัน จากนั้นจึงนำสถานะซ่อนเร้นมาต่อกัน การประมวลผลสองทิศทางช่วยให้ผลลัพธ์แต่ละตำแหน่งนำบริบทจากทั้งอดีตและอนาคตมาประกอบได้ ซึ่งมีประโยชน์ต่อการทำความเข้าใจประโยค โดยคำในอนาคตอาจช่วยให้ความหมายของคำในอดีตชัดเจนขึ้น
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)การแพร่ย้อนกลับผ่านเวลา (BPTT)
การแพร่ย้อนกลับผ่านเวลา จะคลี่ RNN ออกตามลำดับเวลา แล้วใช้การแพร่ย้อนกลับมาตรฐานกับกราฟที่คลี่ออกนั้น สำหรับลำดับที่มีความยาว T จะคำนวณเกรเดียนต์ในทุก timestep และเผยแพร่ย้อนกลับ เกรเดียนต์ของค่าความสูญเสียเมื่อเทียบกับพารามิเตอร์จะเกี่ยวข้องกับผลคูณของเมทริกซ์น้ำหนัก W_hh เดิมที่คูณกัน T ครั้ง เมื่อรัศมีสเปกตรัมของ W_hh น้อยกว่า 1 ผลคูณเหล่านี้จะ หายไป แต่เมื่อมากกว่า 1 ผลคูณจะ พุ่งระเบิด ซึ่งเป็นความท้าทายพื้นฐานของการฝึก RNN กับลำดับที่ยาว
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 inventedการจัดการลำดับที่มีความยาวแปรผัน
ในทางปฏิบัติ ลำดับในชุดข้อมูลย่อยเดียวกันมักมีความยาวต่างกัน เช่น ประโยคที่มีจำนวนคำไม่เท่ากัน PyTorch จัดการกรณีนี้ด้วย ลำดับแบบแพ็ก: torch.nn.utils.rnn.pack_padded_sequence จะนำส่วนเติมให้เต็มออกจากชุดข้อมูลย่อย แล้วแพ็กลำดับอย่างมีประสิทธิภาพ หลังผ่าน RNN แล้ว pad_packed_sequence จะคืนรูปแบบที่มีส่วนเติมให้เต็มกลับมา หากไม่ใช้การแพ็ก RNN จะประมวลผลโทเค็นส่วนเติมให้เต็มโดยไม่จำเป็น และอาจทำให้สถานะซ่อนเร้นปนเปื้อนด้วยข้อมูลส่วนเติมให้เต็มที่ไม่มีความหมาย
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 formควรใช้ RNN แบบพื้นฐานเมื่อใด
โดยทั่วไปแทบไม่ใช้ RNN แบบพื้นฐานในทางปฏิบัติ เพราะประสบปัญหาเกรเดียนต์หายไปอย่างรุนแรงกับลำดับที่ยาวกว่า 10-20 timestep โดยหลักแล้วเหมาะสำหรับ วัตถุประสงค์ด้านการเรียนรู้ และ ลำดับที่สั้นมาก สำหรับการใช้งานจริงที่มีลำดับยาวกว่า 20 timestep ให้ใช้ LSTM หรือ GRU ซึ่งมีกลไกประตูที่ออกแบบมาโดยเฉพาะเพื่อรักษาข้อมูลเมื่อระยะห่างยาวขึ้น สำหรับลำดับที่ลำดับก่อนหลังมีความสำคัญน้อย สถาปัตยกรรม Transformer มักให้ผลดีกว่าทั้งสองแบบ
# 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}')ตรวจสอบความเข้าใจอย่างรวดเร็ว
ทดสอบความเข้าใจแนวคิดการเรียนรู้ของเครื่องด้วย Python จากบทเรียนนี้
สรุปบทเรียน
ในบทเรียนนี้ คุณได้เรียนรู้ว่า RNN แบบพื้นฐานจะรักษาสถานะซ่อนเร้น ซึ่งเก็บความจำข้าม timestep โดยใช้สูตร h_t = tanh(W_xh * x_t + W_hh * h_{t-1} + b), การคลี่ลำดับจะใช้เซลล์เดิมในทุก timestep โดยใช้น้ำหนักร่วมกัน และ BPTT จะแพร่เกรเดียนต์ย้อนกลับผ่านกราฟที่คลี่ออก ทำให้เกิดปัญหาเกรเดียนต์หายไปหรือพุ่งระเบิดในลำดับยาว ต่อไปเราจะตรวจสอบปัญหาเกรเดียนต์หายไปอย่างละเอียด และทำความเข้าใจว่าเหตุใดจึงออกแบบ LSTM ขึ้นมาเพื่อแก้ปัญหานี้
เรียนรู้ Python ด้วย AI tutor — ฟรี
เขียนและเรียกใช้โค้ดจริงในเบราว์เซอร์ของคุณ รับความช่วยเหลือทันทีจาก AI tutor 24/7 และเรียนรู้ต่อจากที่คุณหยุดบนเว็บหรือในแอป
- คอร์ส
- 30
- บทเรียน
- 120
คำถามที่พบบ่อย
บทเรียน “RNN แบบพื้นฐาน: สถานะซ่อนเร้นและการคลี่ลำดับ” ฟรีหรือไม่
ใช่ — ข้อความเต็มของ “RNN แบบพื้นฐาน: สถานะซ่อนเร้นและการคลี่ลำดับ” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส Machine Learning Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส Machine Learning Academy มีบทเรียนทั้งหมด 4 บทเรียน
คุณจะเรียนรู้อะไรในบทเรียน “RNN แบบพื้นฐาน: สถานะซ่อนเร้นและการคลี่ลำดับ”
ผู้เรียนจะสร้างเซลล์ RNN แบบหนึ่งขั้นด้วยตนเอง คลี่การทำงานไปตามลำดับสั้น ๆ และแสดงภาพว่าสถานะซ่อนเร้นสะสมบริบทอย่างไร คุณปฏิบัติ Machine Learning Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน
คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน Machine Learning Academy หรือไม่
ไม่จำเป็นต้องมีประสบการณ์มาก่อน Machine Learning Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 1 จากทั้งหมด 4 บทเรียน
บทเรียน “RNN แบบพื้นฐาน: สถานะซ่อนเร้นและการคลี่ลำดับ” ใช้เวลานานแค่ไหน
บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย
ฉันเขียนและรันโค้ดในบทเรียน Machine Learning Academy นี้ได้ไหม
ได้ บทเรียน Machine Learning Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- RNN แบบพื้นฐาน: สถานะซ่อนเร้นและการคลี่ลำดับ
- ปัญหาเกรเดียนต์หายไปในขั้นเวลาจำนวนมาก
- เซลล์ LSTM: ประตูรับเข้า ลืม และส่งออก
- จากลำดับสู่หนึ่งค่า: การวิเคราะห์ความรู้สึกด้วย LSTM