Processing math: 100%
본문 바로가기

딥러닝

시퀀스 투 시퀀스(Sequence toSequence, seq2seq)

1. seq2seq

■ seq2seq는 한 시계열 데이터를 다른 시계열 데이터로 변환하는 신경망 모델로 '인코더-디코더(Encoder-Decoder)' 모델이라는 신경망 모델의 일종이다.

Encoder-Decoder 모델은 입력 데이터를 인코딩(부호화)하는 Encoder와 인코딩된 데이터를 디코딩(복호화)하는 모델로 구성된다.

- 인코딩은 데이터를 어떤 형식, 규칙 등에 따라 변환하는 것을 말한다. 예컨대 문자 'A'는 ASCII에서 숫자 65로 인코딩된다.

- 디코딩은 인코딩된 데이터를 원래의 데이터로 되돌리는 것을 말한다. ASCII로 인코딩된 숫자 65를 디코딩하면 문자'A'로 변환된다.

■ 다음 그림은 '나는 학생이다.'라는 한글 문장을 입력 받아, 'I am a student'라는 영어 문장을 출력하는 seq2seq의 예이다.

- 한 시계열 데이터('나는 학생이다')가 다른 시계열 데이터('I am a student')로 변환되는 것을 볼 수 있다.

- 다른 관점에서 보자면, Encoder는 자연어를 이해하는 역할을 수행하고, 이해한 내용을 Decoder에 전달하면 Decoder는 전달받은 내용을 토대로 자연어를 생성하는 역할을 수행한다.

■ 이 예시의 내부 과정은 먼저, Encoder가 입력 문장의 모든 단어를 순차적으로 받아, 모든 단어 정보들을 압축해 하나의 벡터를 만든다. 

이 벡터를 컨텍스트 벡터(context vector)라고 한다. 이 예시에서 컨텍스트 벡터에는 번역에 필요한 정보가 압축되어 있다.

■ Encoder는 컨텍스트 벡터를 Decoder로 전달하고, Decoder는 컨텍스트 벡터를 받아서 번역된 단어를 한 개씩 순차적으로 출력한다.

■ 이 예시의 내용이 seq2seq의 전체 그림이다. 이때, Encoder와 Decoder로 RNN을 사용할 수 있다.

예를 들어 문장을 단어 단위로 입력하고 Encoder와 Decoder로 RNN을 사용했을 때, Encoder의 구조는 다음과 같이 시계열 데이터를 은닉 상태 벡터로 변환한다.

RNN을 사용한 Encoder의 내부

- 한국어 문장을 토근 단위로 분할하여 Embedding 레이어에 입력한다. 이를 통해 각 토큰을 밀집 벡터로 변환한다.

- '나', '는'. '학생', '이다' 각각이 밀집 벡터로 변환되어 LSTM 레이어에 입력된다.

- 임베딩된 단어 벡터들이 순서대로 LSTM에 들어가서 각 시점마다 은닉 상태와 기억 셀을 업데이트한다.

- 마지막 토큰('이다')까지 처리한 뒤, 최종 은닉 상태를 Decoder에 전달한다. 전달된 은닉 상태 벡터에는 번역에 필요한 정보가 압축되어 있다.

■ Encoder가 마지막 시점에서 출력하는 은닉 상태 벡터 h가 컨텍스트 벡터이다. 즉, 이 h에 입력 문장을 번역하는데 필요한 정보가 인코딩되는 것이다.

■ 이때, 은닉 상태 벡터 h는 입력 데이터의 길이와 관계없이 항상 동일한 길이(크기)를 갖는 벡터, 고정 길이 벡터이다.

■ 입력 문장은 임의 길이의 문장이므로 즉, 인코딩은 임의 길이의 문장을 고정 길이 벡터로 변환하는 작업으로 볼 수 있다.

- 예를 들어 '나는 학생이다'라는 문장과 '나는 고양이와 강아지이다'라는 서로 다른 길이를 가진 문장이 Encoder의 입력으로 들어가도 동일한 길이의 은닉 상태 벡터 h로 산출된다.

- 단, 두 문장을 미니배치로 처리할 경우 두 문장의 길이를 동일하게 맞춰야 한다.

■ 이 h (Encoder가 출력한 은닉 상태 벡터이자 고정 길이 벡터이며 컨텍스트 벡터)는 디코더의 LSTM 계층으로 들어간다.(디코더 LSTM 계층의 입력으로 사용된다.)

■ Decoder로 RNN을 사용했을 때, Decoder의 구조는 다음과 같다.

- <sos>와 <eos>는 구분 기호로 사용하는 토큰이다. Decoder 문장 생성의 시작, 종료를 알리는 구분자로 사용된다.

- Decoder는 먼저 <sos>라는 시작 토큰을 입력 받는다. 그리고 Encoder로부터 전달된 h와 함께 다음 단어를 예측하기 시작한다.

- Decoder의 LSTM 계층의 출력(은닉 상태)을 Affine 레이어에 통과시킨 다음, Softmax 레이어에 전달한다.

- Softmax 계층은 입력된 점수(score)를 기반으로 다음에 나올 단어를 확률적으로 예측한다.

- 예측된 단어를 다음 시점의 입력으로 사용한다. 이 과정을 종료 토큰 <eos>가 예측될 때까지 반복한다.

■ Decoder는 기본적으로 언어 모델이다. 이 예시에서의 Decoder는 RNNLM으로, 첫 번째 시점에서 <sos>를 입력으로 받고 다음으로 등장할 단어로 'I'를 예측했다. 그리고 단어 'I'를 다음 시점의 입력으로 사용해서 다음에 등장할 단어 'am'을 예측한다.

■ 이 과정을 종료를 의미하는 구분자 <eos>가 다음에 등장할 단어로 예측될 때까지 반복한다.

■ 예시에서 사용한 Encoder와 Decoder가 연결된 구조, 즉 seq2seq의 전체 구조는 다음과 같다.

■ 위의 구조를 보면, h가 Encoder와 Decoder를 연결하는 것을 볼 수 있다.

■ 순전파 과정에서는 Encoder에서 출력된 h가 Decoder에 전해지고, 역전파 과정에서는 기울기가 Decoder로부터 h를 통해 Encoder로 전해진다.

■ 위의 예시는 문장을 단어 단위로 분할해서 입력 데이터로 사용한 예시이다.

■ 문장을 반드시 '단어' 단위로 분할해야 하는 것은 아니다. '문자' 단위로 분할해서 입력으로 사용해도 된다.

 

2. 패딩(padding)을 이용한 가변 길이 시계열 데이터 처리

■ seq2seq 모델에서는 입력·출력 시퀀스 길이가 제각각인 여러 문장을 미니배치로 묶어 한 번에 학습시키고자 할 때, 패딩을 사용하여 길이를 맞추는 방식이 일반적이다.

- 미니배치에 속한 샘플들은 데이터 형상이 모두 동일해야 하기 때문에 길이를 맞춰 형상을 맞춰야 한다.

■ 패딩이란 원래의 데이터에 의미 없는 데이터를 채우는 기법이다.

■ 길이를 맞추는 방법은 패딩을 통해 최대 시퀀스 길이로 모든 샘플 데이터의 길이를 통일하는 것이다.

■ 단, 주의할 점이 있다. 패딩은 가변 길이 시계열 데이터를 처리하기 위해 적용하는 것. 즉, 패딩을 넣은 구간은 실제로는 아무 의미가 없는 구간이다.

■ 그러므로 정확성이 중요하다면,  Decoder에 입력된 데이터에 패딩이 적용된 구간은 손실(loss) 계산 과정에서 제외되어야 한다.

■ Encoder도 마찬가지이다. 원래 존재하지 않았던 패딩까지 seq2seq 모델이 학습하지 않도록 처리해야 한다.

■ 입력된 데이터에 패딩이 적용되었다면, '이 시점은 패딩 위치'라고 명시해서 마치 처음부터 패딩이 존재하지 않았던 것처럼 처리할 수 있다.

- 예컨대 <pad> 토큰을 사용해서, <pad> 토큰이 입력으로 들어오는 시점을 무시할 수 있다.

- 혹은 숫자 0을 넣는 방법도 있는데, 이를 제로 패딩(zero padding)이라고 한다. 0이 아닌 다른 숫자로도 패딩에 사용할 수 있다.

-- <pad>는 시퀀스 길이를 맞추기 위한 패딩 토큰이다. <pad> 토큰을 넣어서 시퀀스들의 길이를 동일하게 맞춘다.

■ 패딩 처리 연습 https://hyeon-jae.tistory.com/152

■ seq2seq 구현 https://hyeon-jae.tistory.com/153

 

3. seq2seq 개선

3.1 입력 데이터 반전(reverse)

이 방법은 다음과 같이 입력 데이터를 반전시킴으로써, 학습 진행 속도를 빠르게 하여 결과적으로 최종 정확도를 개선한다.

- 입력 데이터 반전은 예를 들어, '그는 돈을 센다'가 입력되면 이를 반전시켜 '센다 돈을 그는'을 입력 데이터로 사용하는 것이다. 

■ 단순히 입력 시퀀스(데이터)를 거꾸로 뒤집었는데, 일반 순서로 배치했을 때보다 학습 진행 속도가 빨라지는 이유는 입력 초반부의 정보가 디코더 초반부에 도달하기까지 거쳐야 하는 경로 길이가 짧아지기 때문이다.

■ 예를 들어, 입력으로 '그는 돈을 센다'라는 문장을 Encoder에 입력했을 때, Decoder의 출력으로 'He counts money'가 되는 seq2seq 모델을 생각해 보자. (RNN 계층을 이용해 만든 Encoder와 Decoder) 

일반 순서로 배치했을 때. 입력 '그'부터 출력 'He'까지 도달하려면 '-는', '돈', '-을', '센다'를 담당하는 각 시점의 RNN 계층을 거쳐야 한다.

이 예에서는 문장이 단순하여 RNN 계층을 몇 단계 거치지 않지만 문장이 길어질 경우, 즉 문장에 많은 단어가 사용되는 경우 매우 많은 스텝을 거치게 되며, 이는 역전파 과정에서 더 많은 기울기 전파를 의미한다.

■ 만약, 입력을 뒤집는다면? 즉,  '센다'가 첫 시점에 등장하고 '그'가 마지막 시점에 등장한다면, Encoder의 마지막 입력 '그'와 Decoder가 생성하는 첫 단어 'He'는 바로 옆에 위치하게 된다.

■ 즉, 입력 데이터를 반전시키면 입력의 첫 단어가 RNN 순방향상(오른쪽 방향) 마지막에 등장하므로, Decoder가 첫 단어를 생성할 때까지 필요한 RNN 스텝 수가 줄어들어 기울기 전파가 더 쉽게 일어나고('그'와 'He'간의 기울기 전파 경로가 더 짧아지고), 학습이 원활해진다.

■ 따라서 입력 데이터 반전은 긴 문장에서도 학습 안정성을 높여줄 수 있는 핵심적인 트릭이다.

■ 예를 들어 다음과 같이 word_id의 0이 '그는', 1이 '돈을', 2가 '센다'를 의미한다고 하자. 파이썬에서 입력 데이터 반전은 다음과 같이 간단하게 할 수 있다. 

text = '그는 돈을 센다'
word_id
```#결과#```
array([0, 1, 2])
````````````

word_id = word_id[::-1]
word_id
```#결과#```
array([2, 1, 0])
````````````

## 7개 단어로 구성된 문장이 1000개인 경우 
word_id2.shape # shape은 행렬
```#결과#```
(1000, 7)
````````````

word_id2[999] # 999번째 문장
```#결과#```
array([ 8,  2,  7,  9, 10,  5,  5])
````````````

word_id2 = word_id2[:, ::-1]

word_id2[999]
```#결과#```
array([ 5,  5, 10,  9,  7,  2,  8])
````````````

- "::-1"는 전체 구간에 대해 -1 스텝으로(거꾸로) 이동하라는 뜻이므로, 원본 배열의 열 순서를 반대로 뒤집어 새로운 배열을 만든다.

 

3.2 엿보기(peeky)

■ 위의 그림에서 Encoder-Decoder 전체 그림을 보면, 일반적인 Encoder-Decoder 모델애서 Encoder는 Decoder에 필요한 정보(인코딩된 정보) h를 산출해서 Decoder의 첫 번째 시점의 RNN(또는 LSTM) 계층에 전달한다.

반면, 이 방법은 Encoder에서 산출된 h를 Decoder의 모든 시점의 RNN 계층에 전달하는 방법이다. 구조는 다음 그림과 같다.

Peeky Decoder 구조 및 컨텍스트 벡터와 Decoder 은닉 상태 연결 구조 [출처] Understanding the Seq2Seq Model — What You Should Know Before Understanding Transformers by Hyunjong Lee Medium

h는 Decoder가 출력을 생성할 때 참조할 수 있는 유일한 정보이다.

Peeky Decoder는 이 정보를 매 디코딩 시점마다 Encoder가 만든 컨텍스트 벡터(은닉 상태 벡터)를 계속 추가로 참조하게 만들어 입력 문장의 전체 맥락을 유지하고, 필요한 맥락을 즉시 활용할 수 있으므로 Decoder가 보다 유연하게 정보를 활용할 수 있게 만든다. 

Peeky Decoder 구현 https://hyeon-jae.tistory.com/154

'딥러닝' 카테고리의 다른 글