순환 신경망 (RNN / LSTM / GRU)¶
개요¶
순환 신경망(Recurrent Neural Network, RNN)은 시퀀스 데이터를 처리하기 위해 설계된 신경망이다. 이전 시간 단계의 정보를 은닉 상태(hidden state)를 통해 현재로 전달하며, 모든 시간 단계에서 동일한 가중치를 공유한다. 그러나 기본 RNN은 장기 의존성(long-term dependency)을 학습하기 어려워, 이를 해결한 LSTM과 GRU가 등장하였다. 현재는 Transformer가 대부분의 시퀀스 과제에서 RNN을 대체했지만, RNN의 개념적 이해는 여전히 중요하다.
핵심 개념¶
1. 기본 RNN¶
은닉 상태 업데이트: $\(h_t = \tanh(W_{hh} h_{t-1} + W_{xh} x_t + b_h)\)$
출력: $\(y_t = W_{hy} h_t + b_y\)$
- 파라미터 공유: 모든 시간 단계에서 \(W_{hh}\), \(W_{xh}\), \(W_{hy}\)를 공유
- Unfolding: 시간축으로 펼치면 매우 깊은 네트워크로 볼 수 있음
graph LR
x1["x₁"] --> rnn1["RNN<br>Cell"]
h0["h₀"] --> rnn1
rnn1 --> h1["h₁"]
rnn1 --> y1["y₁"]
x2["x₂"] --> rnn2["RNN<br>Cell"]
h1 --> rnn2
rnn2 --> h2["h₂"]
rnn2 --> y2["y₂"]
x3["x₃"] --> rnn3["RNN<br>Cell"]
h2 --> rnn3
rnn3 --> h3["h₃"]
rnn3 --> y3["y₃"] BPTT (Backpropagation Through Time): 시간 방향으로 역전파를 수행한다.
한계 — 장기 의존성 학습 어려움: $\(\frac{\partial h_t}{\partial h_1} = \prod_{i=2}^{t} \frac{\partial h_i}{\partial h_{i-1}}\)$
이 연쇄 곱이 \(t\)가 커지면 기하급수적으로 소실(vanishing)하거나 폭발(exploding)한다.
2. LSTM (Long Short-Term Memory)¶
Hochreiter & Schmidhuber (1997)가 제안. 셀 상태(cell state)라는 "정보 고속도로"를 통해 장기 의존성을 학습한다.
3개의 게이트:
| 게이트 | 수식 | 역할 |
|---|---|---|
| Forget Gate | \(f_t = \sigma(W_f [h_{t-1}, x_t] + b_f)\) | 무엇을 잊을지 |
| Input Gate | \(i_t = \sigma(W_i [h_{t-1}, x_t] + b_i)\) | 무엇을 기억할지 |
| Output Gate | \(o_t = \sigma(W_o [h_{t-1}, x_t] + b_o)\) | 무엇을 출력할지 |
셀 상태 업데이트: $\(\tilde{c}_t = \tanh(W_c [h_{t-1}, x_t] + b_c) \quad \text{(후보 셀)}\)$ $\(c_t = f_t \odot c_{t-1} + i_t \odot \tilde{c}_t\)$
은닉 상태: $\(h_t = o_t \odot \tanh(c_t)\)$
graph TD
subgraph "LSTM Cell"
xt["x_t"] --> fg["Forget<br>Gate σ"]
ht1["h_{t-1}"] --> fg
xt --> ig["Input<br>Gate σ"]
ht1 --> ig
xt --> cand["Candidate<br>tanh"]
ht1 --> cand
xt --> og["Output<br>Gate σ"]
ht1 --> og
ct1["c_{t-1}"] --> mul1["×"]
fg --> mul1
ig --> mul2["×"]
cand --> mul2
mul1 --> add["+"]
mul2 --> add
add --> ct["c_t"]
ct --> tanh1["tanh"]
tanh1 --> mul3["×"]
og --> mul3
mul3 --> ht["h_t"]
end 왜 vanishing gradient를 완화하는가: 셀 상태 \(c_t\)의 업데이트가 덧셈 형태이다. 곱셈과 달리 덧셈은 기울기를 직접 전달하므로, 기울기가 "고속도로"처럼 먼 시간 단계까지 도달할 수 있다.
3. GRU (Gated Recurrent Unit)¶
Cho et al. (2014)이 제안한 LSTM의 간소화 버전이다.
2개의 게이트:
| 게이트 | 수식 | 역할 |
|---|---|---|
| Update Gate | \(z_t = \sigma(W_z [h_{t-1}, x_t])\) | forget + input gate 통합 |
| Reset Gate | \(r_t = \sigma(W_r [h_{t-1}, x_t])\) | 과거 정보 리셋 정도 |
은닉 상태 업데이트: $\(\tilde{h}_t = \tanh(W [r_t \odot h_{t-1}, x_t])\)$ $\(h_t = (1 - z_t) \odot h_{t-1} + z_t \odot \tilde{h}_t\)$
LSTM vs GRU 비교:
| 특성 | LSTM | GRU |
|---|---|---|
| 게이트 수 | 3개 (forget, input, output) | 2개 (update, reset) |
| 별도 셀 상태 | 있음 (\(c_t\)) | 없음 |
| 파라미터 수 | 더 많음 | 더 적음 (~25% 감소) |
| 성능 | 장기 의존성에 약간 유리 | 짧은 시퀀스에서 유사 |
| 학습 속도 | 느림 | 빠름 |
경험적 가이드: 데이터가 많으면 LSTM, 적으면 GRU가 유리한 경향이 있다.
4. 양방향 RNN (Bidirectional RNN)¶
입력 시퀀스를 양방향으로 처리하여, 과거와 미래 문맥을 모두 활용한다.
사용처: NLP (품사 태깅, NER 등 시퀀스 레이블링) 제한: 실시간 예측에는 부적합 (미래 정보가 필요하므로)
5. Sequence-to-Sequence (Seq2Seq)¶
Encoder-Decoder 구조: 입력 시퀀스 → 고정 길이 벡터(context vector) → 출력 시퀀스
Teacher Forcing: 학습 시 디코더에 이전 시간 단계의 정답을 입력으로 제공. 학습 속도를 높이지만, 추론 시와의 괴리(exposure bias)가 발생한다.
한계: 전체 입력 시퀀스를 고정 길이 벡터로 압축해야 하는 정보 병목 → 어텐션 메커니즘의 등장 동기
상세 내용¶
LSTM의 Forget Gate 편향 초기화¶
Forget gate의 편향을 1로 초기화하면 (\(b_f = 1\)), 학습 초기에 forget gate가 열려 있어 장기 기억이 보존된다. Jozefowicz et al. (2015)이 제안하였으며, 실무에서 널리 사용되는 트릭이다.
Truncated BPTT¶
매우 긴 시퀀스에서 전체 시간 단계에 대한 역전파는 메모리와 계산 비용이 과도하다. 일정 시간 단계(예: 35)까지만 역전파를 수행하는 Truncated BPTT를 사용한다.
Stacked RNN (Deep RNN)¶
여러 RNN 층을 쌓아 더 깊은 표현을 학습한다. 보통 2~4층이 적절하며, Dropout을 층 사이에 적용한다.
언제 사용하는가¶
| 과제 | RNN 적합도 | 대안 |
|---|---|---|
| 시계열 예측 | 적합 | Transformer, CNN |
| 음성 인식 | 적합 (과거) | Transformer (현재 주류) |
| 기계 번역 | 부적합 (구식) | Transformer |
| 시퀀스 레이블링 | 적합 | Transformer + CRF |
| 실시간 스트리밍 | 적합 (순차 처리) | 상태 유지 가능 |
| 긴 문서 처리 | 부적합 | Transformer |
현재 트렌드: 대부분의 NLP 과제에서 Transformer가 RNN을 대체하였다. 그러나 메모리 효율성이 중요한 실시간 스트리밍이나, 매우 긴 시퀀스의 온라인 처리에서는 RNN이 여전히 유용하다.
흔한 오해와 함정¶
-
"LSTM이 vanishing gradient를 완전히 해결한다": LSTM은 완화하지만, 매우 긴 시퀀스(수천 스텝)에서는 여전히 어려움이 있다.
-
Gradient clipping을 하지 않음: RNN 학습에서 gradient clipping은 사실상 필수이다. 폭발하는 기울기는 학습을 즉시 불안정하게 만든다.
-
양방향 RNN을 자기회귀 생성에 사용: 양방향 RNN은 미래 정보를 사용하므로, 텍스트 생성 같은 자기회귀 과제에는 사용할 수 없다.
-
패딩 처리 무시: 가변 길이 시퀀스를 배치로 묶을 때, 패딩된 위치의 은닉 상태를 사용하면 오류가 발생한다.
pack_padded_sequence를 활용해야 한다. -
RNN의 순차 처리 한계 무시: RNN은 본질적으로 병렬화가 불가능하다. GPU의 활용도가 떨어져 학습 속도가 느리다.