본문 바로가기

AI/NLP

[10/26] 아이펠 리서치 15기 TIL | RNN & LSTM

반응형

오늘은 기본 RNN, MLP 같은 다중 RNN, RNN의 단점을 개선한 LSTM 등 여러 모델에 대해 공부했다.


1. RNN의 의미

RNN(Recurrent Neural Network) 모델은 순서가 있는 Sequence data를 처리하기 위해 설계된 인공신경망이다.

 

일반적인 인공신경망이나 CNN은 각 입력 데이터를 독립적인 것으로 취급해 순서를 파악하지 못 하기 때문에 언어, 시계열 데이터, 음성 데이터처럼 순서가 중요한 데이터들에 적용하기 적합하지 않다.

반면, RNN은 각 시점(Time step)의 데이터가 이전 시점의 데이터와 독립적이지 않다(ex: 자연어, 주가 데이터 등)는 특성 때문에 효과적으로 작동한다.


2. RNN의 작동 원리

RNN의 핵심 아이디어는 Loop 이다. 

먼저, RNN은 각 시점에서 계산한 결과를 hidden state의 형태로 저장하는데, 이것이 '기억' 역할을 한다.

각 시점의 데이터를 입력으로 받아 hidden state와 출력값을 계산하는 노드를 RNN의 셀(Cell)이라고 하는데,

여기서 계산을 할 때 새로운 입력 데이터 방금 저장한 기억(hidden state)을 같이 사용한다.

 

이 과정을 데이터의 길이만큼 계속 반복하는데, 정리하면 아래와 같다.

이때, t는 시점, W는 가중치이다.

 

1. t = 1

  • 첫 번재 단어 x1을 입력받고, 이를 처리해서 첫 번째 hidden state (h1)를 만든다.
  • h1 = F(W * x1)

2. t = 2

  • 두 번째 단어 x2를 입력받고, h1도 고려하며 두 번째 hidden state (h2)를 만든다.
  • h2 = F(Wx * x2 + Wh * h1)

3. t = 3

  • 세 번째 단어 x3를 입력받고, h2도 고려하며 세 번째 hidden state (h3)를 만든다.
  • h3 = F(Wx * x3 + Wh * h2)

이처럼 이전 시점의 hiddenstate( h_{t-1})현재의 데이터(x_t)를 같이 고려해 현재의 hidden state(h_t)를 만드는 과정을 반복한다.


3. RNN의 구조


3.1. 입출력 구조

RNN의 구조는 입력/출력 시퀀스의 길이에 따라 결정되는데, 크게 3가지로 나눌 수 있다.

  1. 다대일 (Many-to-One)
  2. 일대다 (One-to-Many)
  3. 다대다 (Many-to-Many)

3.1.1. 다대일 (Many-to-One)

입력 시퀀스 N개를 받아서 한 개의 출력을 생성하는 구조이다.

  • 감성 분석: 입력 시퀀스가 긍정인지 부정인지 판단한다.
  • 텍스트 분류: 입력 시퀀스가 어떤 범주에 속하는지 구분한다(ex: 스팸 메일 구분).
  • 자연어 추론: 두 문장 간의 관계를 추론한다.

3.1.2. 일대다 (One-to-Many)

하나의 입력을 받아서 N개의 출력 시퀀스를 생성하는 구조이다.

  • 이미지 캡셔닝(Image captioning): 특정 이미지를 입력하면 그 이미지에 대한 설명을 출력한다.
  • 음악/텍스트 생성: 특정 장르나 시작 노트를 주면, 이어지는 멜로디 시퀀스를 만들고, 특정 단어를 주면 다음 문장을 작성한다.

3.1.3. 다대다 (Many-to-Many)

입력 시퀀스 N개를 받아서 N개 또는 M개의 출력 시퀀스를 생성하는 구조이다.

시퀀스-시퀀스(Seq2Seq) 구조로 이뤄져 있는데, 입력 시퀀스를 하나의 압축된 벡터로 만드는 인코더(Encoder)와 압축된 벡터를 입력받아 출력 시퀀스를 생성하는 디코더(Decoder)로 구성된다.

 

만약 출력 시퀀스의 길이가 입력과 다를 경우에는 둘의 길이를 맞추기 위해 padding을 추가하거나 잘라내는 등 전처리 과정이 필요하다.

  • 기계 번역: 입력 문장을 번역해서 출력 문장을 생성한다.
  • 챗봇: 사용자의 질문을 이해하고 답변을 생성한다.

3.2. RNN의 확장 모델

3.2.1. 양방향 순환 신경망 (Bidirectional RNN, BiRNN)

이전 시점(t-1)의 hidden state만 참고하는 기존 RNN과 달리 이후 시점(t+1)의 hidden state도 이용하는 모델이다.

작동 방식은 아래와 같다.

  1. Forward RNN: 시퀀스를 "나는 - 학교에 - 간다" 순서로 처리한다
  2. Backward RNN: 시퀀스를 "간다 - 학교에 - 나는" 순서로 거꾸로 처리한다.
  3. Forward의 hidden state(h_f)와 Backward의 hidden state(h_b)를 연결(Concatenate)해서 해당 단어의 의미를 파악할 때 양쪽 문맥을 모두 고려해서 파악한다.

문맥 파악이 중요한 대부분의 NLP 작업(기계 번역, 개체명 인식 등)에서 사용된다.

Bidirectional RNN 구조

3.2.2. 다중 순환 신경망 (Multi-layer RNN / Stacked RNN)

MLP(Multi Layer Perceptron)처럼, 여러 개의 RNN Layer를 깊게 쌓은 모델이며 각 RNN 레이어가 서로 다른 정보를 처리하도록 설계됐다. CNN의 레이어가 깊을수록 더 복잡하고 추상적인 패턴을 학습하는 것처럼 RNN도 마찬가지로 깊은 레이어를 가질수록 더 복잡하고 추상적인 패턴을 학습할 수 있다는 것이 사용 이유이다.

 

작동 방식은 간단한데, 첫 번째 RNN 레이어가 입력 시퀀스를 처리해서 hidden state의 시퀀스를 출력하고, 그 다음 시퀀스의 입력으로 들어가는 과정이 레이어마다 반복되는 것이다. 

 

하지만 이것도 마찬가지로 레이어가 너무 깊어지면 당연히 학습 시간도 길어지고 기울기 소실 문제도 발생한다.


4. 장단기 메모리(Long Short-Term Memory, LSTM)

4.1. RNN의 한계

위에서 설명한 기본적인 RNN은 특정 시점에서 이전 입력 데이터의 정보를 이용해 출력값을 예측하는 구조이므로 시간적으로 먼 과거의 정보는 잘 기억하지 못한다는 장기 의존성 문제(Long-term dependencies)가 발생할 수 있다.

 

또한 앞선 시점에서의 정보를 계속 반영하기 때문에 학습 데이터의 크기가 커질수록 앞에서 학습한 정보가 제대로 전달되지 않는다는 기울기 소실 문제가 역시나 발생할 수 있다.

 

이러한 문제를 해결하기 위해 LSTM(Long Short-Trem Memory)이라는 셀(Cell)을 사용한다.


4.2. LSTM의 구조

LSTM은 기존 RNN의 장기 의존성 문제를 해결하기 위해 설계된 RNN의 Cell이다.

LSTM의 핵심은 셀 상태(Cell state) 또는 메모리 셀(Memory Cell)이라는 RNN의 기본 hidden state와는 별개의 기억 통로를 만든 것이다. 

 

기본 RNN은 hidden state 하나에 과거의 hidden state와 현재의 출력을 모두 넣으려고 했기 때문에 과거의 정보가 희석되거나 사라졌다(기울기 소실 문제).

이것을 해결하기 위해 도입한 개념이 Cell state 인데, Cell state  '장기 기억'을 담당하는 컨베이어 벨트라고 할 수 있다.

Cell state

 

Cell state는 정보를 저장하고 유지하는 메모리 역할을 하며 3개의 Gate에 영향을 받는다. 

 

이 3개의 Gate는 각각 시그모이드 활성화 함수를 사용한다.

이 시그모이드 함수는 출력을 0과 1 사이의 값으로 만들며, 정보를 얼마나 보존할 지 정하는 역할을 한다.

예를 들면,

  • 0: 정보를 모두 버려라
  • 1: 정보를 모두 보존해라
  • 0.5: 정보의 절반만 보존해라

LSTM은 이전 시점(t-1)의 hidden state(h_{t-1})와 현재 시점(t)의 입력 x1을 받아 3개의 게이트를 조작한다.

 

4.2.1. 망각 게이트 (Forget Gate, f_t)

LSTM의 첫 단계로, 이전 시점의 cell state, C_{t-1}에서 어떤 정보를 버릴지 결정하며, sigmoid layer에 의해 결정된다.

그 과정은 아래와 같다.

  1. 이전 시점의 hidden state와 현재 시점의 입력(h_{t-1}, x_t)을 받아서,
  2. sigmoid로 0과 1 사이의 값을 만들고,
  3. 그 값을 이전 시점의 cell state(C_{t-1})에 보내준다.
  4. 그 값이 1이면 "모든 정보를 보존해라", 0이면 "모든 정보를 버려라"가 된다.

예를 들면, "나는 사과를 좋아한다. ... (중략) ... 나는 두리안을 싫어한다." 에서 "두리안"이라는 새로운 과일(주제)이 들어왔을 때, forget gate layer는 '사과'에 대한 기억은 이제 덜 중요하다고 판단해서 0에 가까운 값을 곱하여 정보를 버리도록 작동할 수 있다.

Forget Gate Layer

 

4.2.2. 입력 게이트 (Input Gate, i_t)

다음 단계는, 현재 입력된 정보(x_t) 중에서 어떤 것을 C_t 에 추가할지 결정한다.

  1. x_t 와 h_{t-1}을 바탕으로 tanh layer가 새로운 기억 후보 벡터를 만들고, → \tiled{C_t}
  2. sigmoid layer가 이 기억 후보 벡터 중에서 C_t에 추가할 정보의 비율을 결정한다. i_t

위의 예시에 이어서, "두리안"이라는 정보가 들어왔을 때 input gate는 "이 '두리안'이라는 정보는 중요하다. 100% 기억하자(1을 곱함)"라고 결정한다.

Input Gate Layer

 

(중간 과정) Cell state 업데이트 (C_{t-1} → C_t)

위의 두 게이트에서 "어떤 값을 버리고", "어떤 값을 얼마나 업데이트할 지" 정해놨으니 C_t를 계산하기만 하면 된다.

먼저, C_{t-1}에 망각 비율(f_t)을 곱해서 버리기로 했던 정보를 진짜로 버리고, 입력 비율(i_t)과 새 기억 후보(\tiled{C_t})를 곱한 벡터를 더한다.

즉, (새로운 Cell state) = (이전 cell state x 망각 비율) + (새 기억 후보 x 입력 비율)

여기에서의 곱하기는 원소별 곱셈 연산을 의미하는 아다마르 곱(Hadamard Product)

 

예시로, '사과' 정보는 잊고(0을 곱함), "두리안" 정보는 새로 추가(1을 곱함)하는 식으로 Cell state가 업데이트된다.

Cell state 업데이트

 

4.2.3. 출력 게이트 (Output Gate, o_t)

마지막으로 무엇을 output으로 내보낼 지 정해야 한다.

방금 업데이트된 Cell state (C_t) 전체 중에서 "지금 당장 필요한 정보"(지금 당장 무슨 말을 할 것인지)를 걸러내서 h_t이자 현재 시점의 output으로 내보낸다.

  1. 먼저, sigmoid layer에 input 데이터를 태워서 C_t의 어느 부분을 output으로 내보낼 지를 정한다(지금 당장 출력할 부분).
  2. 그 다음, C_t를 tanh layer에 태워서 -1과 1 사이의 값을 받고,
  3. 이 -1과 1 사이의 값을 1번의 output과 곱해준다(아다마르 곱).

다시 예시로 가면, "나는 두리안을 싫어한다. 왜냐하면..."이라는 문장이 이어진다면, output gate는 Cell state에 있는 "두리안"이라는 정보를 꺼내서(h_t) 다음 단어인 "왜냐하면"을 처리하는 데 사용한다.

Output Gate Layer


5. 코드 실습

위의 모든 내용을 기반으로 감정 분석 모델을 구현한 코드는 아래 깃허브 링크에서 확인할 수 있습니다.

https://github.com/choiwonjini/AIFFEL_practice/blob/main/NLP/3_RNN.ipynb

 

AIFFEL_practice/NLP/3_RNN.ipynb at main · choiwonjini/AIFFEL_practice

Contribute to choiwonjini/AIFFEL_practice development by creating an account on GitHub.

github.com


회고

CNN 공부했을 때부터 RNN이 어떤 원리인지 궁금했는데, 이제 좀 알 것 같다.

LSTM의 아이디어로 기억력을 개선하는 방식이 굉장히 흥미로웠다.

 

반응형