---
title: "RNN·LSTM·GRU 완전 정복: 순환 신경망의 원리부터 시계열·자연어 처리 실전까지"
id: "1715"
type: "post"
slug: "rnn-lstm-gru-time-series-natural-language-processing2"
published_at: "2026-07-24T05:17:37+00:00"
modified_at: "2026-07-24T05:17:37+00:00"
url: "https://doyouknow.kr/rnn-lstm-gru-time-series-natural-language-processing2/"
markdown_url: "https://doyouknow.kr/rnn-lstm-gru-time-series-natural-language-processing2.md"
excerpt: "RNN은 이전 시점의 은닉 상태를 다음 시점으로 전달해 순서가 있는 데이터를 처리합니다. 이 글은 기본 RNN의 한계, LSTM과 GRU의 게이트 구조, 정확한 수식, 시계열 예측과 텍스트 분류 예제, 그리고 Transformer·상태공간모델 시대에도 순환 모델이 필요한 이유를 설명합니다."
taxonomy_category:
  - "AI 101"
taxonomy_post_tag:
  - "BPTT"
  - "GRU"
  - "LSTM"
  - "RNN"
  - "기울기 소실"
  - "딥러닝"
  - "순환 신경망"
  - "시계열 예측"
  - "자연어 처리"
---

**RNN(Recurrent Neural Network, 순환 신경망)**은 문장, 센서 데이터, 음성, 주가처럼 **순서가 의미를 갖는 데이터**를 처리하도록 설계된 신경망입니다. 현재 입력만 보는 것이 아니라 이전 시점의 정보를 은닉 상태에 압축해 다음 시점으로 전달합니다.

그러나 기본 RNN은 시퀀스가 길어질수록 오래된 정보를 학습하기 어렵습니다. 역전파 과정에서 기울기가 지나치게 작아지거나 커지는 **기울기 소실·폭발 문제**가 발생하기 때문입니다. **LSTM(Long Short-Term Memory)**과 **GRU(Gated Recurrent Unit)**는 게이트를 이용해 어떤 정보를 유지하고 버릴지 학습함으로써 이 문제를 완화합니다.

2026년 현재 대규모 자연어 처리에서는 Transformer가 중심이고, 긴 시퀀스에서는 Mamba 같은 선택적 상태공간모델도 주목받습니다. 그렇다고 RNN 계열이 사라진 것은 아닙니다. 짧거나 중간 길이의 시계열, 스트리밍 추론, 엣지 장치, 제한된 데이터와 연산 자원에서는 LSTM과 GRU가 여전히 강력한 기준 모델입니다.

> **핵심 요약**
> 
> - RNN은 이전 은닉 상태를 현재 계산에 사용해 순서를 모델링합니다.
> - 기본 RNN은 긴 의존성을 학습할 때 기울기 소실·폭발에 취약합니다.
> - LSTM은 셀 상태와 입력·망각·출력 게이트를 사용합니다.
> - GRU는 셀 상태를 따로 두지 않고 업데이트·리셋 게이트로 구조를 단순화합니다.
> - LSTM이 언제나 GRU보다 정확하거나, GRU가 언제나 일정 비율 빠른 것은 아닙니다.
> - 시계열에서는 모델보다 데이터 분할, 누수 방지, 베이스라인, 평가 방식이 더 중요할 수 있습니다.
> - 대규모 NLP는 Transformer가 주류지만, RNN 계열은 스트리밍·소형 모델·시계열에서 여전히 실용적입니다.

## 1. 시퀀스 데이터는 무엇이 다른가

일반적인 표 데이터는 각 행을 서로 독립적인 샘플로 보는 경우가 많습니다. 반면 시퀀스 데이터는 앞뒤 순서와 시간 간격이 의미를 가집니다.

예를 들어 다음 두 문장은 같은 단어를 포함하지만 의미가 다릅니다.

```
개가 사람을 물었다.
사람이 개를 물었다.
```

시간 데이터에서도 순서를 섞으면 추세, 계절성, 지연 효과가 사라집니다.

```
정상 순서: 18.2 → 18.9 → 20.1 → 22.4
순서 섞기: 22.4 → 18.2 → 20.1 → 18.9
```

따라서 시퀀스 모델은 단순히 값의 집합이 아니라 **어떤 값이 언제 나타났는지**를 함께 학습해야 합니다.

### 1.1 대표적인 시퀀스 문제

| 문제 유형 | 입력 | 출력 | 예시 |
| --- | --- | --- | --- |
| 다대일 | 여러 시점 | 하나의 값 | 리뷰 감정 분류 |
| 일대다 | 하나의 조건 | 여러 시점 | 이미지 설명 생성 |
| 다대다, 동일 길이 | 여러 시점 | 시점별 출력 | 품사 태깅, 이상 탐지 |
| 다대다, 다른 길이 | 입력 시퀀스 | 출력 시퀀스 | 번역, 음성 인식 |
| 과거-미래 예측 | 과거 구간 | 미래 구간 | 전력 수요·온도 예측 |

RNN은 이런 구조를 하나의 반복 셀로 표현할 수 있습니다.

## 2. RNN의 핵심: 은닉 상태와 가중치 공유

기본 RNN은 시점 \(t\)에서 현재 입력 \(x_t\)와 이전 은닉 상태 \(h_{t-1}\)를 함께 사용합니다.

### 2.1 기본 RNN 수식

은닉 상태는 다음과 같이 계산할 수 있습니다.

\[ h_t = \tanh(W_{xh}x_t + W_{hh}h_{t-1} + b_h) \]

출력은 문제에 따라 다음과 같이 계산합니다.

\[ \hat{y}_t = g(W_{hy}h_t + b_y) \]

여기서 각 기호의 의미는 다음과 같습니다.

- \(x_t\): 시점 \(t\)의 입력 벡터
- \(h_{t-1}\): 이전 시점의 은닉 상태
- \(h_t\): 현재 시점까지의 정보를 압축한 은닉 상태
- \(\hat{y}_t\): 모델의 예측
- \(W_{xh}, W_{hh}, W_{hy}\): 학습되는 가중치
- \(b_h, b_y\): 편향
- \(g\): 회귀에서는 항등 함수, 분류에서는 시그모이드나 소프트맥스 등

중요한 점은 **같은 RNN 셀과 같은 가중치를 모든 시점에 반복해서 사용한다는 것**입니다. 시퀀스 길이가 10이든 100이든 시점마다 새로운 파라미터를 만드는 것이 아닙니다.

### 2.2 RNN을 시간축으로 펼쳐 보기

실제 구현은 하나의 셀을 반복하지만, 학습 과정을 이해할 때는 시간축으로 펼친 계산 그래프로 생각합니다. 이를 **언롤링(unrolling)**이라고 합니다.

### 2.3 은닉 상태는 완전한 기억이 아니다

은닉 상태를 “지금까지 본 모든 것을 기억하는 메모리”라고 설명하기도 하지만 엄밀히 말하면 그렇지 않습니다. 은닉 상태는 고정된 크기의 벡터이므로 과거 정보를 **손실 없이 저장하는 공간**이 아니라, 현재 작업에 필요하다고 모델이 판단한 정보를 압축한 표현입니다.

예를 들어 128차원 은닉 상태에 수천 개의 입력 정보를 전달하려면 일부 정보는 약화되거나 사라질 수밖에 없습니다.

## 3. RNN은 어떻게 학습하는가: BPTT

RNN은 일반 신경망과 마찬가지로 손실 함수의 기울기를 계산해 파라미터를 갱신합니다. 다만 같은 셀이 여러 시점에서 반복되므로 시간축을 따라 역전파해야 합니다. 이를 **BPTT(Backpropagation Through Time)**라고 합니다.

전체 손실을 시점별 손실의 합으로 두면 다음과 같이 표현할 수 있습니다.

\[ L = \sum_{t=1}^{T} L_t \]

초기 시점의 은닉 상태에 대한 기울기는 여러 야코비안의 곱을 포함합니다.

\[ \frac{\partial L}{\partial h_k} = \sum_{t=k}^{T} \frac{\partial L_t}{\partial h_t} \prod_{j=k+1}^{t} \frac{\partial h_j}{\partial h_{j-1}} \]

기본 RNN에서 각 항에는 반복 가중치 \(W_{hh}\)와 활성화 함수의 미분이 포함됩니다.

\[ \frac{\partial h_j}{\partial h_{j-1}} = \operatorname{diag}\left(1-h_j^2\right)W_{hh} \]

이 행렬이 시간축을 따라 반복해서 곱해지는 것이 장기 의존성 학습을 어렵게 만드는 핵심 원인입니다.

### 3.1 기울기 소실

곱해지는 값의 크기가 대체로 1보다 작으면 시점이 멀어질수록 기울기가 0에 가까워집니다.

\[ 0.8^{50} \approx 1.43 \times 10^{-5} \]

이 경우 마지막 출력의 오차가 초기 입력까지 충분히 전달되지 않습니다. 모델은 가까운 문맥은 배우지만 오래전 정보의 영향을 학습하지 못할 수 있습니다.

> 기울기 소실은 단순히 “tanh의 평균 미분값이 0.3이기 때문”이라고 설명할 수 없습니다. 실제 현상은 활성화 함수의 미분뿐 아니라 반복 가중치 행렬의 스펙트럼 특성, 입력, 은닉 상태, 정규화 등 여러 요인의 영향을 받습니다.

### 3.2 기울기 폭발

반대로 곱해지는 변환의 크기가 지속적으로 1보다 크면 기울기가 빠르게 커집니다.

\[ 1.2^{50} \approx 9{,}100 \]

이 경우 손실이 갑자기 커지거나 `NaN`이 발생할 수 있습니다.

대표적인 완화 방법은 다음과 같습니다.

- 기울기 클리핑
- 적절한 가중치 초기화
- 학습률 조정
- Layer Normalization
- LSTM·GRU 사용
- 긴 시퀀스를 구간으로 나누는 Truncated BPTT

PyTorch에서는 다음처럼 기울기 노름을 제한할 수 있습니다.

```
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
```

### 3.3 Truncated BPTT

매우 긴 시퀀스 전체를 한 번에 역전파하면 메모리 사용량이 커지고 학습이 느려집니다. Truncated BPTT는 일정 길이의 구간만 펼쳐 역전파하고, 은닉 상태는 다음 구간으로 전달하되 이전 계산 그래프와는 분리합니다.

```
hidden = hidden.detach()
```

이 방법은 스트리밍 센서 데이터와 긴 로그 시퀀스에서 자주 사용됩니다. 다만 역전파 범위를 자르므로 구간보다 더 먼 의존성을 직접 학습하기 어려워질 수 있습니다.

## 4. LSTM: 셀 상태와 세 개의 게이트

LSTM은 1997년 Sepp Hochreiter와 Jürgen Schmidhuber가 제안한 RNN 구조입니다. 핵심은 은닉 상태 외에 **셀 상태(cell state)** \(c_t\)를 두고, 정보 흐름을 게이트로 제어하는 것입니다.

LSTM의 일반적인 구성 요소는 다음과 같습니다.

1. 망각 게이트
2. 입력 게이트
3. 후보 셀 상태
4. 출력 게이트
5. 셀 상태
6. 은닉 상태

“네 개의 게이트”라고 설명하는 자료도 있지만, 표준 LSTM의 게이트는 보통 **입력·망각·출력의 세 개**입니다. 후보 셀 상태는 게이트가 아니라 새로 저장할 내용의 후보입니다.

### 4.1 표준 LSTM 수식

현재 입력과 이전 은닉 상태를 연결한 벡터를 사용하면 다음과 같이 쓸 수 있습니다.

#### 망각 게이트

\[ f_t = \sigma\left(W_f[x_t;h_{t-1}] + b_f\right) \]

이 값은 이전 셀 상태에서 얼마나 유지할지를 결정합니다.

#### 입력 게이트

\[ i_t = \sigma\left(W_i[x_t;h_{t-1}] + b_i\right) \]

새 정보 후보는 다음과 같습니다.

\[ \tilde{c}_t = \tanh\left(W_c[x_t;h_{t-1}] + b_c\right) \]

#### 셀 상태 업데이트

\[ c_t = f_t \odot c_{t-1} + i_t \odot \tilde{c}_t \]

#### 출력 게이트

\[ o_t = \sigma\left(W_o[x_t;h_{t-1}] + b_o\right) \]

#### 은닉 상태

\[ h_t = o_t \odot \tanh(c_t) \]

여기서 \([x_t;h_{t-1}]\)는 두 벡터의 연결, \(\odot\)는 원소별 곱셈입니다.

### 4.2 게이트를 직관적으로 이해하기

센서 데이터를 처리한다고 가정해 보겠습니다.

```
입력: 정상 → 정상 → 온도 급상승 → 고온 유지 → 정상 복귀
```

- 망각 게이트는 오래된 정상 상태의 일부를 줄일 수 있습니다.
- 입력 게이트는 온도 급상승 정보를 셀 상태에 강하게 기록할 수 있습니다.
- 출력 게이트는 현재 판단에 필요한 위험 정보를 은닉 상태로 내보낼 수 있습니다.
- 정상으로 복귀하면 망각 게이트가 이전 경보 상태를 점차 지울 수 있습니다.

게이트 값은 사람이 규칙으로 정하는 것이 아니라 학습을 통해 결정됩니다.

### 4.3 LSTM은 기울기 소실을 완전히 해결하는가

LSTM은 기본 RNN보다 기울기가 장기간 흐르기 쉬운 경로를 제공합니다. 셀 상태에 대한 직접 미분은 다음 항을 포함합니다.

\[ \frac{\partial c_t}{\partial c_{t-1}} = f_t \]

망각 게이트가 1에 가까우면 기울기가 비교적 잘 보존됩니다. 하지만 매 시점의 \(f_t\)가 항상 1인 것은 아니므로 “덧셈만 사용하기 때문에 기울기가 절대 사라지지 않는다”는 설명은 부정확합니다.

LSTM도 다음 상황에서는 장기 정보를 잃을 수 있습니다.

- 망각 게이트가 반복해서 작은 값을 출력할 때
- 시퀀스가 매우 길 때
- 학습 데이터가 부족할 때
- 최적화가 불안정할 때
- 은닉 상태 크기가 작업에 비해 너무 작을 때

즉 LSTM은 기울기 소실을 **완전히 제거**하는 것이 아니라 **완화하고 학습 가능한 경로를 제공**합니다.

### 4.4 LSTM 파라미터 수

입력 특성 수를 \(D\), 은닉 크기를 \(H\)라고 하면 단일 방향, 단일 층 LSTM의 기본 파라미터 수는 다음과 같습니다.

\[ 4H(D + H + 1) \]

네 묶음은 입력 게이트, 망각 게이트, 후보 셀 상태, 출력 게이트에 해당합니다.

예를 들어 \(D=16\), \(H=64\)라면 다음과 같습니다.

\[ 4 \times 64 \times (16+64+1) = 20{,}736 \]

프레임워크에 따라 입력 편향과 반복 편향을 별도로 저장하므로 실제 표시되는 파라미터 구성은 조금 다를 수 있습니다.

## 5. GRU: LSTM을 단순화한 게이트 구조

GRU는 2014년 Kyunghyun Cho 등이 RNN Encoder–Decoder 연구에서 제안했습니다. 별도의 셀 상태 없이 은닉 상태 하나를 사용하고, 게이트를 두 개로 줄였습니다.

- 업데이트 게이트 \(z_t\)
- 리셋 게이트 \(r_t\)

### 5.1 GRU 수식

GRU 수식은 논문과 프레임워크에 따라 후보 은닉 상태의 행렬 연산 순서가 조금 다를 수 있습니다. 다음은 널리 쓰이는 형태입니다.

#### 업데이트 게이트

\[ z_t = \sigma(W_zx_t + U_zh_{t-1} + b_z) \]

#### 리셋 게이트

\[ r_t = \sigma(W_rx_t + U_rh_{t-1} + b_r) \]

#### 후보 은닉 상태

\[ \tilde{h}_t = \tanh\left(W_hx_t + U_h(r_t \odot h_{t-1}) + b_h\right) \]

#### 최종 은닉 상태

\[ h_t = z_t \odot h_{t-1} + (1-z_t)\odot \tilde{h}_t \]

이 정의에서는 \(z_t\)가 1에 가까울수록 이전 상태를 더 많이 유지합니다.

> 일부 자료와 라이브러리는 마지막 식의 \(z_t\)와 \(1-z_t\) 역할을 반대로 표기합니다. 어느 쪽이든 수학적으로 일관되게 정의하면 되지만, 설명과 식을 섞으면 “업데이트 게이트가 1일 때 과거를 유지하는지 새 값을 사용하는지”가 뒤바뀝니다.

PyTorch의 `torch.nn.GRU`는 후보 상태 계산에서 리셋 게이트를 적용하는 위치가 원 논문의 한 형태와 다르며, 이는 효율적인 구현을 위한 차이입니다. 라이브러리 간 가중치를 직접 옮길 때는 이 점을 확인해야 합니다.

### 5.2 GRU 파라미터 수

단일 방향, 단일 층 GRU의 기본 파라미터 수는 대략 다음과 같습니다.

\[ 3H(D + H + 1) \]

같은 입력 크기와 은닉 크기라면 LSTM보다 이론상 게이트 한 묶음만큼 파라미터가 적습니다.

\[ \frac{3}{4}=75\% \]

즉 단순한 조건에서는 LSTM보다 약 25% 적은 파라미터를 가질 수 있습니다. 그러나 실제 학습 속도가 정확히 25% 또는 30% 빨라진다고 일반화할 수는 없습니다. 속도는 다음 요소에 따라 달라집니다.

- GPU·CPU 종류
- 라이브러리와 커널 최적화
- 배치 크기
- 시퀀스 길이
- 은닉 크기와 층 수
- 양방향 사용 여부
- 데이터 로딩 병목

## 6. RNN·LSTM·GRU 비교

| 항목 | 기본 RNN | LSTM | GRU |
| --- | --- | --- | --- |
| 상태 | 은닉 상태 | 은닉 상태 + 셀 상태 | 은닉 상태 |
| 주요 게이트 | 없음 | 입력·망각·출력 | 업데이트·리셋 |
| 장기 의존성 | 취약 | 비교적 강함 | 비교적 강함 |
| 파라미터 수 | 가장 적음 | 가장 많음 | LSTM보다 적음 |
| 학습 난이도 | 긴 시퀀스에서 높음 | 안정적인 편 | 안정적인 편 |
| 추론 상태 | 작음 | \(h_t, c_t\) 필요 | \(h_t\)만 필요 |
| 적합한 시작점 | 짧고 단순한 패턴 | 복잡한 장기 패턴 | 효율이 중요한 문제 |

### 6.1 어떤 모델을 먼저 선택할까

절대적인 규칙은 없지만 다음 순서가 실용적입니다.

1. **나이브 예측과 선형 모델을 먼저 만든다.**
2. 짧은 시퀀스라면 기본 RNN 또는 1D CNN을 기준선으로 둔다.
3. 연산량과 지연 시간이 중요하면 GRU를 먼저 시도한다.
4. 셀 상태의 분리와 더 큰 표현력이 필요하면 LSTM을 비교한다.
5. 매우 긴 컨텍스트나 대규모 병렬 학습이 중요하면 Transformer·TCN·SSM도 함께 비교한다.

“데이터가 100만 개 이상이면 LSTM, 그보다 적으면 GRU” 같은 고정 기준은 없습니다. 모델 선택은 검증 데이터 성능, 추론 지연, 메모리, 안정성을 함께 측정해 결정해야 합니다.

## 7. 양방향 RNN과 다층 RNN

### 7.1 양방향 RNN

양방향 RNN은 시퀀스를 정방향과 역방향으로 각각 처리한 뒤 두 표현을 결합합니다.

\[ h_t = [\overrightarrow{h_t};\overleftarrow{h_t}] \]

문장 분류, 품사 태깅처럼 전체 문장을 미리 볼 수 있는 문제에서는 유용합니다.

그러나 미래 입력을 볼 수 없는 실시간 예측에서는 사용할 수 없습니다.

- 가능: 완성된 문서의 개체명 인식
- 가능: 녹음이 끝난 음성 파일 분석
- 부적합: 다음 10초의 센서 값 예측
- 부적합: 실시간 이상 탐지에서 미래 관측 사용

시계열 예측에 Bidirectional LSTM을 무심코 사용하면 미래 정보가 학습에 들어가는 **데이터 누수**가 발생할 수 있습니다.

### 7.2 다층 RNN

첫 번째 순환층이 저수준 패턴을 만들고 다음 순환층이 더 추상적인 패턴을 학습하도록 여러 층을 쌓을 수 있습니다.

```
nn.LSTM(
    input_size=8,
    hidden_size=64,
    num_layers=2,
    dropout=0.2,
    batch_first=True,
)
```

PyTorch의 순환층 `dropout`은 일반적으로 **마지막 층을 제외한 층 사이**에 적용됩니다. `num_layers=1`이면 해당 옵션이 실제로 적용되지 않습니다.

## 8. 입력과 출력 텐서 모양 이해하기

시계열 모델에서 가장 흔한 오류 중 하나는 텐서 차원을 잘못 넣는 것입니다.

PyTorch에서 `batch_first=True`일 때 기본 입력 모양은 다음과 같습니다.

```
(batch_size, sequence_length, input_size)
```

예를 들어 32개 샘플, 과거 24시간, 특성 5개라면 다음과 같습니다.

```
(32, 24, 5)
```

LSTM 출력은 일반적으로 다음 세 묶음입니다.

```
output, (h_n, c_n) = lstm(x)
```

- `output`: 모든 시점의 마지막 층 은닉 상태
- `h_n`: 각 층·방향의 마지막 은닉 상태
- `c_n`: 각 층·방향의 마지막 셀 상태

다대일 예측에서는 다음과 같이 마지막 시점 출력을 사용할 수 있습니다.

```
last = output[:, -1, :]
prediction = head(last)
```

단, 패딩된 가변 길이 시퀀스라면 `output[:, -1, :]`이 실제 마지막 토큰이 아니라 패딩일 수 있습니다. 이때는 길이 정보나 `pack_padded_sequence`를 사용해야 합니다.

## 9. 실전 예제 1: PyTorch로 다음 시점 예측하기

아래 예제는 사인파에 추세와 잡음을 더한 시계열을 만들고, 과거 24개 값으로 다음 값을 예측합니다.

중요한 점은 다음과 같습니다.

- 시간 순서를 유지해 학습·검증·테스트를 나눕니다.
- 스케일러는 학습 구간에만 적합합니다.
- 테스트 데이터를 학습 중 검증 데이터로 사용하지 않습니다.
- 단순한 나이브 예측과 비교합니다.
- 조기 종료와 기울기 클리핑을 사용합니다.

```
from __future__ import annotations

import copy
import math
import random
from dataclasses import dataclass

import numpy as np
import torch
from sklearn.metrics import mean_absolute_error, mean_squared_error
from sklearn.preprocessing import StandardScaler
from torch import nn
from torch.utils.data import DataLoader, Dataset

def set_seed(seed: int = 42) -> None:
    random.seed(seed)
    np.random.seed(seed)
    torch.manual_seed(seed)
    if torch.cuda.is_available():
        torch.cuda.manual_seed_all(seed)

def make_series(n: int = 2400) -> np.ndarray:
    t = np.arange(n, dtype=np.float32)
    seasonal = np.sin(2 * math.pi * t / 48)
    slow = 0.5 * np.sin(2 * math.pi * t / 240)
    trend = 0.0005 * t
    noise = np.random.normal(0, 0.12, size=n)
    return (seasonal + slow + trend + noise).astype(np.float32)

def make_windows(
    values: np.ndarray,
    lookback: int,
) -> tuple[np.ndarray, np.ndarray]:
    x_list, y_list = [], []

    for end in range(lookback, len(values)):
        start = end - lookback
        x_list.append(values[start:end])
        y_list.append(values[end])

    x = np.asarray(x_list, dtype=np.float32)[..., None]
    y = np.asarray(y_list, dtype=np.float32)[:, None]
    return x, y

class WindowDataset(Dataset):
    def __init__(self, x: np.ndarray, y: np.ndarray) -> None:
        self.x = torch.from_numpy(x)
        self.y = torch.from_numpy(y)

    def __len__(self) -> int:
        return len(self.x)

    def __getitem__(self, index: int) -> tuple[torch.Tensor, torch.Tensor]:
        return self.x[index], self.y[index]

class RecurrentRegressor(nn.Module):
    def __init__(
        self,
        cell: str = "gru",
        input_size: int = 1,
        hidden_size: int = 64,
        num_layers: int = 1,
    ) -> None:
        super().__init__()

        recurrent_cls = {
            "rnn": nn.RNN,
            "lstm": nn.LSTM,
            "gru": nn.GRU,
        }.get(cell.lower())

        if recurrent_cls is None:
            raise ValueError("cell은 'rnn', 'lstm', 'gru' 중 하나여야 합니다.")

        self.recurrent = recurrent_cls(
            input_size=input_size,
            hidden_size=hidden_size,
            num_layers=num_layers,
            batch_first=True,
        )
        self.head = nn.Linear(hidden_size, 1)

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        output, _ = self.recurrent(x)
        return self.head(output[:, -1, :])

@dataclass
class TrainResult:
    model: nn.Module
    best_val_loss: float

def train_model(
    model: nn.Module,
    train_loader: DataLoader,
    val_loader: DataLoader,
    device: torch.device,
    epochs: int = 100,
    patience: int = 12,
) -> TrainResult:
    criterion = nn.MSELoss()
    optimizer = torch.optim.AdamW(model.parameters(), lr=1e-3)

    best_state = copy.deepcopy(model.state_dict())
    best_val_loss = float("inf")
    wait = 0

    model.to(device)

    for epoch in range(1, epochs + 1):
        model.train()
        train_loss_sum = 0.0

        for x_batch, y_batch in train_loader:
            x_batch = x_batch.to(device)
            y_batch = y_batch.to(device)

            optimizer.zero_grad(set_to_none=True)
            prediction = model(x_batch)
            loss = criterion(prediction, y_batch)
            loss.backward()

            nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
            optimizer.step()

            train_loss_sum += loss.item() * len(x_batch)

        model.eval()
        val_loss_sum = 0.0

        with torch.no_grad():
            for x_batch, y_batch in val_loader:
                x_batch = x_batch.to(device)
                y_batch = y_batch.to(device)

                prediction = model(x_batch)
                loss = criterion(prediction, y_batch)
                val_loss_sum += loss.item() * len(x_batch)

        train_loss = train_loss_sum / len(train_loader.dataset)
        val_loss = val_loss_sum / len(val_loader.dataset)

        if epoch == 1 or epoch % 10 == 0:
            print(
                f"epoch={epoch:03d} "
                f"train={train_loss:.5f} "
                f"val={val_loss:.5f}"
            )

        if val_loss < best_val_loss - 1e-6:
            best_val_loss = val_loss
            best_state = copy.deepcopy(model.state_dict())
            wait = 0
        else:
            wait += 1

        if wait >= patience:
            print(f"조기 종료: epoch {epoch}")
            break

    model.load_state_dict(best_state)
    return TrainResult(model=model, best_val_loss=best_val_loss)

def predict(
    model: nn.Module,
    loader: DataLoader,
    device: torch.device,
) -> tuple[np.ndarray, np.ndarray]:
    model.eval()
    predictions, targets = [], []

    with torch.no_grad():
        for x_batch, y_batch in loader:
            output = model(x_batch.to(device)).cpu().numpy()
            predictions.append(output)
            targets.append(y_batch.numpy())

    return np.vstack(predictions), np.vstack(targets)

def main() -> None:
    set_seed(42)
    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

    raw = make_series()

    train_end = int(len(raw) * 0.70)
    val_end = int(len(raw) * 0.85)

    raw_train = raw[:train_end]
    raw_val = raw[train_end:val_end]
    raw_test = raw[val_end:]

# 스케일러는 학습 구간에만 적합한다.
    scaler = StandardScaler()
    scaler.fit(raw_train.reshape(-1, 1))

    train_scaled = scaler.transform(raw_train.reshape(-1, 1)).ravel()
    val_scaled = scaler.transform(raw_val.reshape(-1, 1)).ravel()
    test_scaled = scaler.transform(raw_test.reshape(-1, 1)).ravel()

    lookback = 24
    x_train, y_train = make_windows(train_scaled, lookback)
    x_val, y_val = make_windows(val_scaled, lookback)
    x_test, y_test = make_windows(test_scaled, lookback)

    train_loader = DataLoader(
        WindowDataset(x_train, y_train),
        batch_size=64,
        shuffle=True,
    )
    val_loader = DataLoader(
        WindowDataset(x_val, y_val),
        batch_size=128,
        shuffle=False,
    )
    test_loader = DataLoader(
        WindowDataset(x_test, y_test),
        batch_size=128,
        shuffle=False,
    )

    model = RecurrentRegressor(
        cell="gru",  # "rnn", "lstm", "gru" 비교 가능
        input_size=1,
        hidden_size=64,
    )

    result = train_model(
        model=model,
        train_loader=train_loader,
        val_loader=val_loader,
        device=device,
    )

    pred_scaled, true_scaled = predict(
        result.model,
        test_loader,
        device,
    )

    prediction = scaler.inverse_transform(pred_scaled).ravel()
    target = scaler.inverse_transform(true_scaled).ravel()

    mae = mean_absolute_error(target, prediction)
    rmse = mean_squared_error(target, prediction) ** 0.5

# 나이브 기준선: 마지막 관측값을 다음 값으로 사용한다.
    naive_scaled = x_test[:, -1, 0].reshape(-1, 1)
    naive = scaler.inverse_transform(naive_scaled).ravel()
    naive_mae = mean_absolute_error(target, naive)
    naive_rmse = mean_squared_error(target, naive) ** 0.5

    print(f"GRU  MAE={mae:.4f}, RMSE={rmse:.4f}")
    print(f"Naive MAE={naive_mae:.4f}, RMSE={naive_rmse:.4f}")

if __name__ == "__main__":
    main()
```

### 9.1 왜 무작위 분할을 사용하지 않았나

시계열을 무작위로 섞으면 미래 샘플이 학습 데이터에 들어가고 과거 샘플이 테스트 데이터에 들어갈 수 있습니다. 시계열 예측은 실제 운영 상황처럼 과거로 학습하고 미래를 평가해야 합니다.

```
학습: 1월 ~ 8월
검증: 9월 ~ 10월
테스트: 11월 ~ 12월
```

더 안정적인 평가가 필요하면 여러 시점을 기준으로 반복 평가하는 **워크포워드 검증**을 사용합니다.

### 9.2 기존 예제에서 흔한 데이터 누수

다음 방식은 전체 데이터의 평균과 표준편차를 먼저 계산하므로 테스트 구간 정보가 학습 전처리에 들어갑니다.

```
# 잘못된 예
mean = X.mean()
std = X.std()
X = (X - mean) / std
```

올바른 방법은 학습 구간에만 `fit`하고 검증·테스트에는 `transform`만 적용하는 것입니다.

## 10. 실전 예제 2: 텍스트 분류용 BiLSTM

대규모 NLP에서는 사전학습 Transformer가 일반적이지만, 작은 데이터셋이나 엣지 환경에서는 임베딩과 BiLSTM을 결합한 모델이 여전히 유용합니다.

```
from __future__ import annotations

import torch
from torch import nn

class BiLSTMClassifier(nn.Module):
    def __init__(
        self,
        vocab_size: int,
        embedding_dim: int = 128,
        hidden_size: int = 96,
        num_classes: int = 2,
        padding_idx: int = 0,
    ) -> None:
        super().__init__()

        self.embedding = nn.Embedding(
            num_embeddings=vocab_size,
            embedding_dim=embedding_dim,
            padding_idx=padding_idx,
        )
        self.encoder = nn.LSTM(
            input_size=embedding_dim,
            hidden_size=hidden_size,
            batch_first=True,
            bidirectional=True,
        )
        self.dropout = nn.Dropout(0.3)
        self.classifier = nn.Linear(hidden_size * 2, num_classes)

    def forward(
        self,
        token_ids: torch.Tensor,
        lengths: torch.Tensor,
    ) -> torch.Tensor:
        embedded = self.embedding(token_ids)

        packed = nn.utils.rnn.pack_padded_sequence(
            embedded,
            lengths.cpu(),
            batch_first=True,
            enforce_sorted=False,
        )

        _, (h_n, _) = self.encoder(packed)

# 단일 층 양방향 LSTM:
# h_n[-2]는 정방향 마지막 상태, h_n[-1]은 역방향 마지막 상태
        representation = torch.cat((h_n[-2], h_n[-1]), dim=1)
        return self.classifier(self.dropout(representation))
```

### 10.1 패딩을 그냥 넣으면 안 되는 이유

문장 길이를 맞추기 위해 0 토큰을 뒤에 붙였다고 가정해 보겠습니다.

```
문장 A: [12, 93, 44, 7]
문장 B: [51, 20, 0, 0]
```

순환층이 패딩까지 처리하면 문장 B의 마지막 상태가 실제 단어가 아니라 패딩을 반영할 수 있습니다. `pack_padded_sequence`는 각 문장의 실제 길이까지만 계산하도록 돕습니다.

### 10.2 BiLSTM이 실시간 생성에 부적합한 이유

양방향 모델은 현재 토큰을 표현할 때 뒤쪽 토큰도 봅니다. 전체 문장을 이미 확보한 분류·태깅에는 적합하지만, 다음 단어를 하나씩 생성하거나 실시간 스트림을 처리하는 인과적 문제에는 사용할 수 없습니다.

## 11. 시계열 예측에서 모델보다 먼저 확인할 것

LSTM을 사용했다고 해서 좋은 시계열 예측이 보장되지는 않습니다. 다음 항목을 먼저 점검해야 합니다.

### 11.1 나이브 베이스라인

대표적인 기준선은 다음과 같습니다.

- 마지막 값 그대로 예측
- 계절 주기 전 값 사용
- 이동평균
- 선형 회귀
- 지수평활
- ARIMA 계열

복잡한 LSTM이 “어제 값 = 오늘 값” 기준선보다 좋지 않다면 모델을 운영할 이유가 없습니다.

### 11.2 예측 지평

다음 한 시점 예측과 앞으로 24개 시점 예측은 다른 문제입니다.

- 1-step: 과거 24시간 → 다음 1시간
- direct multi-horizon: 과거 168시간 → 다음 24시간을 한 번에 출력
- recursive: 한 시간 예측을 다시 입력해 24회 반복
- encoder–decoder: 입력 시퀀스를 인코딩하고 미래 시퀀스를 디코딩

재귀 예측은 앞선 예측 오차가 다음 입력에 누적될 수 있습니다.

### 11.3 외생 변수

전력 수요를 과거 전력량만으로 예측하면 기온, 휴일, 요일 같은 중요한 원인을 놓칠 수 있습니다.

```
입력 특성:
- 과거 전력 수요
- 기온·습도
- 시간대
- 요일
- 공휴일 여부
- 설비 가동 상태
```

RNN의 강점은 여러 시점의 여러 특성을 함께 처리하는 데 있습니다.

### 11.4 결측치와 불규칙 간격

기본 RNN은 각 시점의 간격이 일정하다고 암묵적으로 가정합니다. 측정 간격이 불규칙하면 다음 방법을 고려할 수 있습니다.

- 일정 간격으로 리샘플링
- 결측 마스크 추가
- 마지막 관측 이후 경과 시간 추가
- 보간 여부 플래그 추가
- 시간 간격을 직접 모델 입력에 포함
- 불규칙 시계열용 모델 사용

단순히 0으로 채우면 실제 0과 결측을 구분하지 못할 수 있습니다.

### 11.5 평가 지표

| 지표 | 장점 | 주의점 |
| --- | --- | --- |
| MAE | 해석이 쉽고 이상치 영향이 비교적 작음 | 큰 오차를 강하게 벌하지 않음 |
| MSE | 큰 오차를 강하게 벌함 | 단위가 제곱됨 |
| RMSE | 원래 단위로 해석 가능 | 이상치에 민감 |
| MAPE | 백분율로 직관적 | 실제값이 0 또는 매우 작으면 불안정 |
| sMAPE | MAPE의 일부 문제 완화 | 정의가 여러 가지 |
| MASE | 다른 시계열과 비교 가능 | 계절 주기 설정 필요 |

주가 방향, 이상 경보처럼 목적이 다른 문제에서는 방향 정확도, 정밀도·재현율, F1, AUROC, 지연 시간도 함께 봐야 합니다.

## 12. 자연어 처리에서 RNN의 위치

RNN·LSTM·GRU는 한때 기계 번역, 음성 인식, 텍스트 생성의 중심 구조였습니다. 인코더–디코더, 어텐션, ELMo 같은 발전도 RNN 계열 위에서 이루어졌습니다.

현재 대규모 NLP는 다음 이유로 Transformer 중심입니다.

- 학습 시 시퀀스 위치를 병렬 처리하기 쉬움
- 장거리 토큰 관계를 직접 연결
- 대규모 사전학습에 유리
- 범용 언어 모델로 확장하기 쉬움

따라서 “현재 기계 번역과 음성 인식의 모든 것을 RNN이 담당한다”는 설명은 더 이상 맞지 않습니다. 많은 최신 시스템은 Transformer, Conformer, 하이브리드 구조를 사용합니다.

그럼에도 RNN이 실용적인 경우가 있습니다.

- 모델 크기를 작게 유지해야 할 때
- 스트리밍으로 한 시점씩 처리할 때
- 고정 크기 상태만 유지하며 추론해야 할 때
- 데이터셋이 작고 문제 범위가 명확할 때
- 짧거나 중간 길이 시퀀스에서 강한 기준선이 필요할 때
- 엣지 CPU·NPU에서 낮은 메모리로 동작해야 할 때

## 13. RNN과 Transformer, TCN, Mamba 비교

### 13.1 Transformer

Transformer의 셀프 어텐션은 각 토큰이 다른 모든 토큰을 직접 참조할 수 있습니다. 기본 어텐션의 시퀀스 길이에 대한 계산·메모리 비용은 대체로 이차적으로 증가하지만, 학습 중 시점별 연산을 병렬화하기 쉽습니다.

RNN은 시점별로 상태를 갱신해야 하므로 학습 병렬화가 어렵지만, 추론 시에는 이전 상태만 유지하면 됩니다.

| 항목 | RNN·LSTM·GRU | Transformer |
| --- | --- | --- |
| 학습 병렬화 | 제한적 | 유리 |
| 순차 스트리밍 | 자연스러움 | 캐시·마스크 설계 필요 |
| 긴 거리 연결 | 상태를 거쳐 전달 | 어텐션으로 직접 연결 |
| 추론 상태 | 고정 크기 가능 | 일반적으로 KV 캐시 증가 |
| 대규모 NLP | 제한적 | 주류 |
| 소형 시계열 기준선 | 여전히 유용 | 데이터·연산 요구가 클 수 있음 |

### 13.2 TCN

TCN(Temporal Convolutional Network)은 인과적 합성곱과 팽창 합성곱을 사용해 넓은 수용 영역을 만듭니다.

장점은 다음과 같습니다.

- 합성곱 기반 병렬 처리
- 안정적인 기울기
- 수용 영역을 명확히 계산 가능
- 시계열 분류·예측에서 강한 기준선

RNN만 비교하지 말고 1D CNN 또는 TCN도 함께 시험하는 것이 좋습니다.

### 13.3 상태공간모델과 Mamba

상태공간모델(SSM)은 연속 또는 이산 동적 시스템의 상태 전이를 이용해 시퀀스를 표현합니다. Mamba는 입력 내용에 따라 정보를 선택적으로 유지·제거하는 선택적 상태공간모델을 제안하며, 긴 시퀀스에서 선형적인 확장성을 목표로 합니다.

Mamba가 “새로운 LSTM”이라는 뜻은 아닙니다. 둘 다 상태를 전달하지만 학습 방식, 병렬 알고리즘, 파라미터화, 하드웨어 최적화가 다릅니다. 다만 긴 시퀀스를 어텐션 이외의 방식으로 처리하려는 흐름에서 **순환 상태라는 아이디어가 다시 중요해졌다**고 볼 수 있습니다.

2026년의 실전 선택은 “RNN 대 Transformer”처럼 하나만 고르는 문제가 아닙니다. 데이터 길이, 샘플 수, 지연 시간, 하드웨어, 해석 가능성, 유지 비용을 기준으로 여러 계열을 검증해야 합니다.

## 14. 흔한 오해 바로잡기

### 오해 1. LSTM은 100단어 전 정보를 99% 기억한다

그런 보장이나 일반적인 수치는 없습니다. 기억 지속 시간은 데이터, 학습, 은닉 크기, 게이트 값, 최적화에 따라 달라집니다.

### 오해 2. GRU는 항상 LSTM보다 30% 빠르다

GRU의 파라미터가 더 적은 경우가 많지만 실제 속도 차이는 환경에 따라 달라집니다. 반드시 같은 하드웨어와 조건에서 측정해야 합니다.

### 오해 3. LSTM은 기울기 소실을 완전히 해결한다

기본 RNN보다 완화하지만 완전히 제거하지 않습니다. 망각 게이트가 작게 유지되거나 시퀀스가 매우 길면 기울기가 약해질 수 있습니다.

### 오해 4. RNN만 순서를 보존하고 CNN은 순서를 무시한다

1D CNN과 TCN도 커널 위치와 인과적 합성곱을 통해 순서를 모델링할 수 있습니다. MLP도 위치 특성이나 윈도우 입력을 주면 제한적으로 순서를 학습할 수 있습니다.

### 오해 5. 시계열 데이터를 LSTM에 넣으면 미래를 예측할 수 있다

미래에 대한 신호가 데이터에 존재해야 하며, 비정상성, 구조 변화, 외생 변수, 노이즈에 따라 예측 가능성이 제한됩니다. 특히 금융 가격은 단순한 과거 종가만으로 안정적인 초과수익을 얻기 어렵습니다.

### 오해 6. 정확도가 가장 높은 모델이 가장 좋은 모델이다

운영 환경에서는 다음도 중요합니다.

- 추론 지연
- 메모리 사용량
- 재학습 비용
- 드리프트 대응
- 실패 시 영향
- 예측 구간과 불확실성
- 베이스라인 대비 개선 폭

## 15. 학습 안정화를 위한 실전 체크리스트

### 데이터

- 시간 순서를 유지해 분할했는가
- 스케일러를 학습 데이터에만 적합했는가
- 미래 정보를 담은 특성이 섞이지 않았는가
- 결측과 실제 0을 구분했는가
- 학습 시점에 사용할 수 없던 정보가 포함되지 않았는가
- 예측 시점과 레이블 정렬이 정확한가

### 모델

- 기본 RNN·GRU·LSTM을 같은 조건에서 비교했는가
- 은닉 크기와 층 수가 과도하지 않은가
- 양방향 모델이 문제의 인과성을 위반하지 않는가
- 패딩 마스크 또는 PackedSequence를 적용했는가
- 기울기 클리핑을 고려했는가

### 평가

- 나이브·선형 베이스라인과 비교했는가
- 검증 데이터와 테스트 데이터를 분리했는가
- 여러 시점의 워크포워드 검증을 했는가
- 실제 운영 지표로 평가했는가
- 평균 성능뿐 아니라 최악 구간도 확인했는가
- 예측 시간과 메모리도 기록했는가

## 16. FAQ

### Q1. LSTM과 GRU 중 무엇을 먼저 사용해야 하나요?

연산량과 구현 단순성이 중요하면 GRU를 먼저 시험하기 좋습니다. 하지만 LSTM과 GRU의 우열은 데이터에 따라 달라집니다. 같은 데이터 분할, 은닉 크기, 학습 예산에서 둘을 비교하고 베이스라인 대비 개선 폭을 확인해야 합니다.

### Q2. 시퀀스 길이는 길수록 좋은가요?

아닙니다. 너무 짧으면 필요한 패턴을 놓치고, 너무 길면 노이즈와 계산량이 늘어납니다. 자기상관, 계절 주기, 도메인 지식으로 후보 길이를 정한 뒤 검증해야 합니다.

예를 들어 시간별 전력 수요라면 다음 후보를 비교할 수 있습니다.

```
24시간: 하루 주기
168시간: 일주일 주기
336시간: 2주 주기
```

### Q3. LSTM의 `return_sequences=True`는 언제 필요한가요?

다음 순환층에 전체 시점 출력을 넘기거나 시점별 예측을 할 때 필요합니다. 다대일 예측에서 마지막 순환층만 사용한다면 마지막 상태만 반환해도 됩니다.

### Q4. Stateful RNN은 무엇인가요?

배치가 끝난 뒤에도 은닉 상태를 다음 배치로 이어가는 방식입니다. 연속 스트림에는 유용할 수 있지만 샘플 순서, 배치 크기, 상태 초기화 시점을 엄격히 관리해야 합니다. 잘못 사용하면 서로 다른 시퀀스의 상태가 섞입니다.

### Q5. RNN에 드롭아웃을 넣으면 장기 기억이 손상되지 않나요?

적용 위치와 방식에 따라 다릅니다. 순환 상태에 매 시점 독립적인 드롭아웃을 적용하면 시간적 일관성이 깨질 수 있습니다. 프레임워크 기본 옵션이 층 사이에만 적용되는지, recurrent dropout을 별도로 지원하는지 확인해야 합니다.

### Q6. 시계열에서 Transformer가 항상 더 좋은가요?

아닙니다. 데이터가 작거나 시퀀스가 짧고 실시간 추론이 중요하면 GRU나 LSTM이 더 효율적일 수 있습니다. Transformer 계열도 강력하지만 모델 선택은 동일한 평가 조건에서 결정해야 합니다.

### Q7. 주가 예측 예제를 학습하면 실제 투자에 사용할 수 있나요?

교육용 예제와 실전 투자 시스템은 다릅니다. 거래 비용, 슬리피지, 생존 편향, 데이터 수정, 시장 체제 변화, 미래 정보 누수, 리스크 관리가 포함되어야 합니다. 낮은 MSE가 수익성을 의미하지도 않습니다.

## 17. 정리

RNN은 이전 은닉 상태를 다음 시점에 전달해 시퀀스의 순서를 모델링합니다. 같은 파라미터를 시간축 전체에서 공유하므로 가변 길이 데이터를 처리할 수 있지만, BPTT에서 반복되는 행렬 곱 때문에 장기 의존성과 기울기 소실·폭발 문제가 나타날 수 있습니다.

LSTM은 셀 상태와 세 개의 게이트를 사용해 정보와 기울기의 흐름을 조절합니다. GRU는 이를 두 개의 게이트와 하나의 은닉 상태로 단순화합니다. 두 모델 중 하나가 항상 더 정확하거나 빠른 것은 아니므로 동일 조건에서 비교해야 합니다.

시계열 문제에서는 모델 구조보다 다음 원칙이 더 중요할 수 있습니다.

1. 미래 정보 누수를 막는다.
2. 시간 순서대로 학습·검증·테스트를 분리한다.
3. 학습 구간으로만 전처리 통계를 계산한다.
4. 나이브·선형 모델과 비교한다.
5. 실제 예측 지평과 운영 지표로 평가한다.
6. RNN뿐 아니라 TCN, Transformer, 상태공간모델도 후보에 둔다.

Transformer 시대에도 RNN·LSTM·GRU는 시퀀스 모델의 핵심 원리를 이해하는 출발점이며, 스트리밍·엣지·소형 시계열 모델에서는 여전히 실용적인 선택입니다.

## 같이 보면 좋은 글

- [시계열 예측 AI 완벽 가이드: LSTM vs Transformer vs TimesFM](https://doyouknow.kr/time-series-forecasting-ai-lstm-transformer-timesfm/)
- [Transformer와 Attention: ChatGPT의 핵심 원리](https://doyouknow.kr/transformer-attention-self-attention-multi-head-bert-gpt/)
- [자연어 처리(NLP): AI와 대화하기](https://doyouknow.kr/natural-language-processing-nlp-tokenization-embedding/)
- [BERT: 양방향으로 언어를 이해하는 AI](https://doyouknow.kr/bert-bidirectional-transformer-language-model/)
- [CNN과 RNN의 결합: 복합 모델의 가능성](https://doyouknow.kr/cnn-rnn-hybrid-model/)
- [모델 학습과 최적화: 손실, 경사하강법, 역전파](https://doyouknow.kr/ai-model-training-optimization-loss-gradient-descent-backpropagation2/)
- [과적합과 과소적합, 정규화·드롭아웃·조기 종료](https://doyouknow.kr/overfitting-underfitting-regularization-dropout-early-stopping2/)

## 외부 참고 자료

- [Hochreiter & Schmidhuber, Long Short-Term Memory, 1997](https://www.bioinf.jku.at/publications/older/2604.pdf)
- [Cho et al., Learning Phrase Representations using RNN Encoder–Decoder, 2014](https://arxiv.org/abs/1406.1078)
- [PyTorch LSTM 공식 문서](https://docs.pytorch.org/docs/stable/generated/torch.nn.LSTM.html)
- [PyTorch GRU 공식 문서](https://docs.pytorch.org/docs/stable/generated/torch.nn.GRU.html)
- [TensorFlow: RNN with Keras](https://www.tensorflow.org/guide/keras/working_with_rnns)
- [Stanford CS230: Recurrent Neural Networks Cheatsheet](https://stanford.edu/~shervine/teaching/cs-230/cheatsheet-recurrent-neural-networks)
- [Mamba: Linear-Time Sequence Modeling with Selective State Spaces](https://arxiv.org/abs/2312.00752)
- [Mamba 공식 구현](https://github.com/state-spaces/mamba)

### 이 글 공유하기:

- [Facebook으로 공유하기 (새 창에서 열림)Facebook](https://doyouknow.kr/rnn-lstm-gru-time-series-natural-language-processing2/?share=facebook)
- [X에 공유 (새 창에서 열림)X](https://doyouknow.kr/rnn-lstm-gru-time-series-natural-language-processing2/?share=x)
-

### 이것이 좋아요:

좋아하기로드 중...

### *관련*

### 관련된 글:

1. [[AI 101] RNN과 LSTM – 시간을 기억하는 AI의 마법](https://doyouknow.kr/rnn-lstm-gru-time-series-natural-language-processing/)
2. [시계열 예측 AI 완벽 가이드: LSTM vs Transformer vs TimesFM! 주가·수요 예측의 진실](https://doyouknow.kr/time-series-forecasting-lstm-transformer-timesfm-stock-demand-prediction/)
3. [[AI 101] 퍼셉트론에서 딥러닝까지 – 신경망의 모든 것](https://doyouknow.kr/perceptron-deep-learning-neural-network-activation-function/)
4. [딥러닝을 깨우는 마법 버튼, 활성화 함수 완전 정복: ReLU부터 GELU·SwiGLU·TeLU까지](https://doyouknow.kr/activation-function-comparison-guide/)

### 댓글 남기기[응답 취소](/rnn-lstm-gru-time-series-natural-language-processing2/?t=1785796903#respond)
