상위 목록: 하위 목록: 작성 날짜: 읽는 데 9 분 소요

순서가 있는 데이터(Sequence Data)

단어 임베딩(Word Embedding)으로 각 토큰을 벡터로 바꿀 수 있게 되었습니다. 그러나 문장은 벡터의 집합이 아니라 나열입니다. “개가 사람을 물었다”와 “사람이 개를 물었다”는 같은 단어로 이루어져 있지만 뜻이 정반대이며, 그 차이는 순서에서만 나옵니다. 이처럼 원소의 순서 자체가 정보를 담고 있는 데이터를 시퀀스 데이터(Sequence Data)라 합니다.

일반적인 신경망은 이런 데이터를 다루기 어렵습니다. 입력 크기가 고정되어 있으므로 길이가 제각각인 문장을 그대로 넣을 수 없고, 문장을 하나의 긴 벡터로 이어 붙인다 해도 첫 번째 위치의 “개”와 세 번째 위치의 “개”를 서로 다른 입력으로 학습하게 됩니다. 같은 단어가 어느 위치에 오든 같은 방식으로 처리되어야 한다는 언어의 성질을 반영하지 못하는 것입니다.

사람은 문장을 읽을 때 앞에서부터 한 단어씩 읽어 나가며, 지금까지 읽은 내용을 머릿속에 유지한 채 다음 단어를 해석합니다. “그는 은행에 가서”까지 읽었다면 다음에 “돈을 찾았다”가 나올 것이라는 기대를 이미 하고 있습니다. 순환 신경망(Recurrent Neural Network, RNN)은 이 방식을 그대로 모방해, 토큰을 하나씩 순서대로 읽으면서 지금까지의 내용을 요약한 상태를 갱신해 나가는 구조입니다.



순환 신경망(Recurrent Neural Network)

순환 신경망의 핵심은 은닉 상태(Hidden State)입니다. 은닉 상태는 지금까지 읽은 모든 토큰의 정보를 요약한 하나의 벡터로, 매 시점마다 이전 은닉 상태와 현재 입력을 함께 받아 새로운 은닉 상태를 만듭니다.

\[\mathbf{h}_{t} = \tanh \left( W_{xh} \mathbf{x}_{t} + W_{hh} \mathbf{h}_{t-1} + \mathbf{b}_{h} \right)\]


\(\mathbf{x}_{t}\)는 \(t\)번째 토큰의 임베딩 벡터이고, \(\mathbf{h}_{t-1}\)은 바로 앞 시점의 은닉 상태입니다. \(W_{xh}\)는 입력을, \(W_{hh}\)는 이전 상태를 변환하는 가중치이며, 두 결과를 더한 뒤 활성화 함수(Activation Function)인 tanh를 통과시켜 -1에서 1 사이의 값으로 만듭니다. 첫 시점의 이전 상태 \(\mathbf{h}_{0}\)은 보통 0 벡터로 둡니다.

“나는 밥을 먹었다”를 읽는 과정을 따라가 보겠습니다. 먼저 “나는”과 \(\mathbf{h}_{0}\)으로 \(\mathbf{h}_{1}\)을 만듭니다. 다음으로 “밥을”과 \(\mathbf{h}_{1}\)로 \(\mathbf{h}_{2}\)를 만들며, 이때 \(\mathbf{h}_{2}\)에는 “나는”의 정보가 \(\mathbf{h}_{1}\)을 통해 간접적으로 담겨 있습니다. 마지막으로 “먹었다”와 \(\mathbf{h}_{2}\)로 \(\mathbf{h}_{3}\)을 만들면, \(\mathbf{h}_{3}\)은 문장 전체를 요약한 벡터가 됩니다.

중요한 점은 모든 시점에서 같은 가중치를 사용한다는 것입니다. \(W_{xh}\)와 \(W_{hh}\)는 시점마다 새로 만들어지지 않고 하나를 계속 재사용합니다. 그 덕분에 문장의 길이와 무관하게 매개변수의 수가 일정하며, 같은 단어는 어느 위치에 오든 같은 방식으로 처리됩니다. 시간 축으로 펼쳐 그리면 같은 신경망이 여러 번 복사된 것처럼 보이지만, 실제로는 하나의 신경망을 반복해서 호출하는 것입니다.

  • Tip : 순환 신경망의 은닉 상태는 크기가 고정된 벡터입니다. 문장이 3단어든 300단어든 모든 정보를 같은 크기의 벡터 하나에 담아야 하므로, 문장이 길어질수록 앞쪽 정보가 희미해지는 것은 구조적으로 피하기 어렵습니다.



입력과 출력의 형태

순환 신경망은 매 시점마다 은닉 상태를 만들어 내므로, 그중 어떤 것을 출력으로 사용하느냐에 따라 다양한 과제에 적용할 수 있습니다. 출력이 필요한 시점에서는 은닉 상태에 또 하나의 가중치를 곱해 결과를 계산합니다.

  • 다대일(Many-to-One) : 문장 전체를 읽은 뒤 마지막 은닉 상태 하나로 답을 냅니다. 리뷰를 읽고 긍정·부정을 판단하는 감성 분석이 대표적입니다.
  • 다대다(Many-to-Many) : 매 시점의 은닉 상태마다 답을 냅니다. 각 단어에 품사를 붙이는 품사 태깅이나, 사람·장소 이름을 표시하는 개체명 인식이 여기에 해당합니다.
  • 일대다(One-to-Many) : 하나의 입력에서 출발해 나열을 생성합니다. 이미지 하나를 입력받아 설명 문장을 만드는 과제가 대표적이며, 이때 각 시점의 출력 단어를 다음 시점의 입력으로 다시 넣습니다.
  • 시퀀스-투-시퀀스(Sequence-to-Sequence) : 입력 나열을 모두 읽은 뒤 길이가 다른 출력 나열을 생성합니다. 번역과 요약이 여기에 속하며, 다음 강좌에서 자세히 다룹니다.


같은 순환 구조로 언어 모델도 만들 수 있습니다. 매 시점의 은닉 상태로 다음 토큰의 확률을 예측하도록 학습하면, “나는 밥을” 다음에 “먹었다”가 올 확률이 높다는 것을 배우게 됩니다. 이렇게 학습한 모델로 다음 토큰을 하나씩 뽑아 이어 붙이면 문장을 생성할 수 있으며, 이 원리는 오늘날의 대규모 언어 모델에서도 그대로 사용됩니다.



시간에 따른 역전파(BPTT)

순환 신경망도 순전파와 역전파로 학습합니다. 다만 같은 가중치가 모든 시점에서 쓰이므로, 손실에 대한 기울기도 모든 시점을 거슬러 올라가며 계산해야 합니다. 시간 축으로 펼친 신경망 전체에 역전파를 적용하는 것으로, 이를 시간에 따른 역전파(Backpropagation Through Time, BPTT)라 합니다.

마지막 시점의 손실이 첫 시점의 입력에 어떤 영향을 받았는지 알려면, 기울기가 \(\mathbf{h}_{T} \rightarrow \mathbf{h}_{T-1} \rightarrow \cdots \rightarrow \mathbf{h}_{1}\)의 경로를 따라 전달되어야 합니다. 연쇄 법칙에 따라 이 경로의 기울기는 각 단계의 미분값을 모두 곱한 것이 됩니다.

\[\frac{\partial \mathbf{h}_{T}}{\partial \mathbf{h}_{1}} = \prod_{t=2}^{T} \frac{\partial \mathbf{h}_{t}}{\partial \mathbf{h}_{t-1}}\]


각 단계의 미분값에는 같은 가중치 \(W_{hh}\)와 tanh의 미분이 반복해서 들어갑니다. 같은 값을 수십, 수백 번 곱한다는 것이 순환 신경망의 가장 큰 약점으로 이어집니다.



기울기 소실과 폭주

각 단계에서 곱해지는 값의 크기를 단순화해 하나의 숫자 \(\gamma\)라고 하면, \(k\)개의 시점을 거슬러 올라간 기울기는 대략 \(\gamma^{k}\)배가 됩니다. 위 그래프는 세로축을 로그 눈금으로 그린 것으로, 곱해지는 값이 1보다 조금만 작거나 커도 거리가 멀어질수록 차이가 기하급수적으로 벌어진다는 것을 보여 줍니다.

\(\gamma = 0.9\)라면 10단계 뒤에는 약 0.35배, 50단계 뒤에는 약 0.005배, 100단계 뒤에는 약 \(2.7 \times 10^{-5}\)배로 줄어듭니다. 이처럼 기울기가 0에 가까워져 먼 과거의 입력이 학습에 거의 반영되지 않는 현상을 기울기 소실(Vanishing Gradient)이라 합니다. tanh의 미분값은 최대 1이고 입력이 커질수록 0에 가까워지므로, 실제 순환 신경망에서는 곱해지는 값이 1보다 작은 경우가 많아 기울기 소실이 특히 흔합니다.

기울기 소실이 일어나면 모델은 가까운 단어의 관계만 학습하게 됩니다. “내가 어제 서점에서 산 그 두꺼운 책은 정말 재미있었다”에서 “재미있었다”의 주어가 “책”이라는 사실을 학습하려면 기울기가 여러 단어를 건너 전달되어야 하는데, 그 사이에 신호가 사라져 버립니다. 이를 장기 의존성(Long-term Dependency) 문제라 합니다.

반대로 \(\gamma = 1.1\)이라면 50단계 뒤에 약 117배, 100단계 뒤에는 약 13,780배로 커집니다. 기울기가 지나치게 커져 가중치가 한 번에 크게 바뀌고 손실이 발산하는 이 현상을 기울기 폭주(Exploding Gradient)라 합니다. 폭주는 기울기의 크기가 정해진 값을 넘으면 비율을 유지한 채 그 값으로 줄이는 기울기 클리핑(Gradient Clipping)으로 비교적 쉽게 막을 수 있습니다.

기울기 소실은 클리핑처럼 간단한 방법으로 해결되지 않습니다. 이미 작아진 기울기를 키운다고 사라진 정보가 돌아오지 않기 때문입니다. 그래서 기울기가 먼 시점까지 줄어들지 않고 전달될 수 있는 통로를 구조 안에 만드는 방법이 연구되었습니다.

  • Tip : 학습 중 손실이 갑자기 NaN이 된다면 기울기 폭주를 의심해 볼 수 있습니다. PyTorch에서는 역전파 후 가중치를 갱신하기 전에 torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)을 호출해 기울기의 크기를 제한합니다.



LSTM(Long Short-Term Memory)

LSTM(Long Short-Term Memory)은 1997년 호흐라이터(Sepp Hochreiter)와 슈미트후버(Jürgen Schmidhuber)가 기울기 소실 문제를 해결하기 위해 제안한 구조입니다. 이름은 “길게 유지되는 단기 기억”이라는 뜻으로, 순환 신경망의 은닉 상태가 짧은 기간만 기억한다는 한계를 넘어서려는 의도를 담고 있습니다.

LSTM의 핵심은 은닉 상태와 별도로 셀 상태(Cell State) \(\mathbf{c}_{t}\)라는 기억 통로를 두는 것입니다. 기본 순환 신경망은 매 시점마다 은닉 상태 전체를 행렬 곱과 tanh로 새로 계산했지만, LSTM의 셀 상태는 이전 값에 필요한 만큼만 지우고 필요한 만큼만 더하는 방식으로 갱신됩니다. 무엇을 지우고 무엇을 더할지는 게이트(Gate)라는 장치가 결정합니다.

게이트는 시그모이드 함수 \(\sigma\)를 거친 0에서 1 사이의 벡터로, 다른 벡터에 원소별로 곱해져 각 값을 얼마나 통과시킬지 조절합니다. 0에 가까우면 막고, 1에 가까우면 그대로 통과시킵니다. 세 개의 게이트는 모두 현재 입력 \(\mathbf{x}_{t}\)와 이전 은닉 상태 \(\mathbf{h}_{t-1}\)로 계산됩니다.

\[\mathbf{f}_{t} = \sigma \left( W_{f} [\mathbf{h}_{t-1}, \mathbf{x}_{t}] + \mathbf{b}_{f} \right), \quad \mathbf{i}_{t} = \sigma \left( W_{i} [\mathbf{h}_{t-1}, \mathbf{x}_{t}] + \mathbf{b}_{i} \right), \quad \mathbf{o}_{t} = \sigma \left( W_{o} [\mathbf{h}_{t-1}, \mathbf{x}_{t}] + \mathbf{b}_{o} \right)\] \[\tilde{\mathbf{c}}_{t} = \tanh \left( W_{c} [\mathbf{h}_{t-1}, \mathbf{x}_{t}] + \mathbf{b}_{c} \right)\] \[\mathbf{c}_{t} = \mathbf{f}_{t} \odot \mathbf{c}_{t-1} + \mathbf{i}_{t} \odot \tilde{\mathbf{c}}_{t}, \qquad \mathbf{h}_{t} = \mathbf{o}_{t} \odot \tanh(\mathbf{c}_{t})\]


\([\mathbf{h}_{t-1}, \mathbf{x}_{t}]\)는 두 벡터를 이어 붙인 것이고, \(\odot\)는 원소별 곱셈입니다. 각 구성 요소의 역할은 다음과 같습니다.

  • 망각 게이트(Forget Gate) \(\mathbf{f}_{t}\) : 이전 셀 상태에서 무엇을 지울지 결정합니다. 문장에서 새로운 주어가 등장했다면 이전 주어에 대한 정보를 지우는 식입니다.
  • 입력 게이트(Input Gate) \(\mathbf{i}_{t}\) : 새로 만든 후보 값 \(\tilde{\mathbf{c}}_{t}\) 중 무엇을 기억에 더할지 결정합니다.
  • 출력 게이트(Output Gate) \(\mathbf{o}_{t}\) : 셀 상태 중 무엇을 이번 시점의 은닉 상태로 내보낼지 결정합니다. 셀 상태는 장기 기억이고, 은닉 상태는 지금 당장 필요한 정보입니다.


LSTM이 기울기 소실을 완화하는 이유는 셀 상태의 갱신식에 있습니다. \(\mathbf{c}_{t} = \mathbf{f}_{t} \odot \mathbf{c}_{t-1} + \cdots\)에서 \(\mathbf{c}_{t-1}\)에 대한 미분은 망각 게이트의 값 그 자체입니다. 행렬 곱이나 tanh를 거치지 않고 덧셈과 원소별 곱셈만으로 이어지므로, 망각 게이트가 1에 가깝게 열려 있는 동안에는 기울기가 거의 줄어들지 않고 먼 과거까지 전달됩니다. 모델은 기억을 유지해야 할 정보에 대해 망각 게이트를 열어 두도록 학습합니다.

  • Tip : 1997년에 발표된 최초의 LSTM에는 망각 게이트가 없었으며, 2000년에 게르스(Felix Gers) 연구진이 추가했습니다. 오늘날 LSTM이라 하면 보통 망각 게이트가 포함된 구조를 가리킵니다. 학습 초기에 기억이 쉽게 지워지지 않도록 망각 게이트의 편향을 1 정도로 초기화하는 기법도 널리 쓰입니다.



GRU(Gated Recurrent Unit)

LSTM은 효과적이지만 게이트가 세 개이고 상태도 두 개라 계산량이 많습니다. 2014년 조경현 연구진이 제안한 GRU(Gated Recurrent Unit)는 LSTM의 아이디어를 유지하면서 구조를 단순화했습니다.

GRU는 셀 상태와 은닉 상태를 하나로 합치고, 게이트를 두 개로 줄였습니다.

  • 갱신 게이트(Update Gate) \(\mathbf{z}_{t}\) : 이전 은닉 상태를 얼마나 유지하고 새 후보 값을 얼마나 받아들일지 한 번에 결정합니다. LSTM의 망각 게이트와 입력 게이트를 하나로 묶은 역할로, 한쪽을 많이 받으면 다른 쪽은 그만큼 덜 받습니다.
  • 리셋 게이트(Reset Gate) \(\mathbf{r}_{t}\) : 새 후보 값을 계산할 때 이전 은닉 상태를 얼마나 참고할지 결정합니다. 0에 가까우면 과거를 무시하고 현재 입력만으로 후보를 만듭니다.


\[\mathbf{h}_{t} = (1 - \mathbf{z}_{t}) \odot \mathbf{h}_{t-1} + \mathbf{z}_{t} \odot \tilde{\mathbf{h}}_{t}\]


새 은닉 상태는 이전 상태와 후보 값을 갱신 게이트의 비율로 섞은 것입니다. 갱신 게이트가 0에 가까우면 이전 상태가 거의 그대로 전달되므로, LSTM의 셀 상태와 같은 방식으로 기울기가 먼 시점까지 이어집니다.

  • Tip : 갱신 게이트를 어느 쪽에 곱하는지는 문헌마다 다릅니다. 원 논문과 PyTorch의 nn.GRU는 \(\mathbf{z}_{t}\)를 이전 상태 쪽에 곱하므로, 그 표기에서는 갱신 게이트가 1에 가까울 때 이전 상태가 유지됩니다. 두 표기는 \(\mathbf{z}_{t}\)와 \(1 - \mathbf{z}_{t}\)의 이름만 바꾼 것으로, 모델의 표현력은 같습니다.

GRU는 LSTM보다 매개변수가 적어 학습이 빠르고, 데이터가 적을 때 과대적합에 덜 민감한 편입니다. 성능은 과제에 따라 둘 중 어느 쪽이 낫다고 단정하기 어려우며, 여러 비교 연구에서도 뚜렷한 우열이 나타나지 않았습니다. 실무에서는 둘 다 시도해 보고 검증 성능으로 고르는 경우가 많습니다.



양방향과 다층 구조

기본 순환 신경망은 왼쪽에서 오른쪽으로만 읽으므로, 각 시점의 은닉 상태는 앞쪽 문맥만 알고 있습니다. 그러나 “나는 ___ 을 마셨다”의 빈칸을 채우려면 뒤쪽의 “마셨다”가 결정적인 단서가 됩니다.

양방향 순환 신경망(Bidirectional RNN)은 왼쪽에서 오른쪽으로 읽는 순환 신경망과 오른쪽에서 왼쪽으로 읽는 순환 신경망을 따로 두고, 같은 시점의 두 은닉 상태를 이어 붙여 사용합니다. 각 시점의 표현이 앞뒤 문맥을 모두 반영하므로 품사 태깅이나 개체명 인식처럼 문장 전체를 보고 판단하는 과제에서 성능이 크게 오릅니다. 다만 문장 끝까지 읽어야 뒤쪽 방향의 계산이 가능하므로, 다음 단어를 하나씩 생성하는 과제에는 사용할 수 없습니다.

다층 순환 신경망(Stacked RNN)은 순환 층을 여러 개 쌓아, 아래층의 은닉 상태 나열을 위층의 입력으로 사용합니다. 아래층은 단어 수준의 국소적인 패턴을, 위층은 더 넓은 범위의 의미를 학습하는 경향이 있습니다. 일반적으로 2~4층 정도를 사용하며, 더 깊게 쌓으면 학습이 어려워지고 효과도 줄어듭니다.

단어 임베딩(Word Embedding)의 끝에서 언급한 문맥 임베딩(Contextual Embedding)은 바로 이 구조로 처음 실현되었습니다. 2018년에 발표된 ELMo는 대량의 말뭉치로 양방향 다층 LSTM 언어 모델을 학습한 뒤, 그 은닉 상태를 단어의 표현으로 사용했습니다. 같은 “배”라도 앞뒤 문맥에 따라 은닉 상태가 달라지므로, 정적 임베딩이 해결하지 못했던 중의성 문제를 다룰 수 있게 되었습니다.



순환 신경망의 한계

LSTM과 GRU로 기울기 소실이 크게 완화되면서, 순환 신경망은 2010년대 중반까지 번역, 음성 인식, 문장 분류 등 거의 모든 자연어 처리 과제의 표준이 되었습니다. 그러나 구조에서 비롯된 근본적인 한계가 남아 있었습니다.

첫 번째는 병렬 처리가 불가능하다는 점입니다. \(\mathbf{h}_{t}\)를 계산하려면 \(\mathbf{h}_{t-1}\)이 먼저 있어야 하므로, 100개의 토큰으로 이루어진 문장은 100번의 계산을 차례대로 수행해야 합니다. 수천 개의 연산을 동시에 처리하는 GPU의 장점을 살리기 어렵고, 모델과 데이터를 키울수록 학습 시간이 크게 늘어납니다.

두 번째는 정보가 하나의 경로로만 전달된다는 점입니다. 게이트가 기울기 소실을 완화했다고는 하지만, 첫 단어의 정보가 마지막 단어에 도달하려면 여전히 모든 중간 시점을 거쳐야 합니다. 거리가 멀수록 정보는 희미해지며, 수백 토큰이 넘는 긴 문서에서는 이 한계가 분명하게 드러납니다.

세 번째는 모든 정보를 고정된 크기의 벡터에 담아야 한다는 점입니다. 특히 번역처럼 입력 문장 전체를 읽은 뒤 출력을 생성하는 과제에서는, 마지막 은닉 상태 하나가 문장 전체를 대표해야 합니다. 다음 강좌에서는 이 병목을 해결하기 위해 등장한 어텐션(Attention)을 다루며, 어텐션은 결국 순환 구조 자체를 대체하는 트랜스포머로 이어집니다.



순환 신경망 정리

구조 상태 게이트 장점 한계
기본 RNN 은닉 상태 없음 단순하고 가벼움 기울기 소실로 장기 의존성 학습 불가
LSTM 은닉 상태 + 셀 상태 망각 · 입력 · 출력 장기 의존성 학습 가능 계산량과 매개변수가 많음
GRU 은닉 상태 갱신 · 리셋 LSTM보다 가볍고 빠름 성능은 과제에 따라 LSTM과 비슷
양방향 RNN 정방향 + 역방향 기반 구조에 따름 앞뒤 문맥을 모두 반영 문장 생성에 사용할 수 없음
다층 RNN 층마다 상태 기반 구조에 따름 더 추상적인 표현 학습 깊어질수록 학습이 어려움

순환 신경망은 같은 가중치를 반복해서 적용하며 상태를 갱신한다는 단순한 아이디어로 시퀀스 데이터를 다룰 수 있게 했습니다. 그러나 같은 값을 반복해서 곱하는 구조는 기울기 소실을 낳았고, LSTM과 GRU는 게이트로 기억을 조절하는 통로를 만들어 이를 완화했습니다.

순환 신경망이 남긴 가장 중요한 유산은 문맥에 따라 단어의 표현이 달라진다는 개념입니다. 오늘날의 모델은 순환 구조를 사용하지 않지만, 문장 전체를 읽고 각 토큰의 표현을 문맥에 맞게 고쳐 나간다는 목표는 그대로 이어받았습니다.

  • Tip : 순환 신경망은 지금도 짧은 시계열 데이터나 연산 자원이 제한된 장치에서 유용하게 쓰입니다. 토큰을 하나씩 처리하면서 고정된 크기의 상태만 유지하므로, 입력이 길어져도 메모리 사용량이 늘어나지 않는다는 장점이 있기 때문입니다.



  • Writer by : 윤대희

댓글 남기기