시퀀스 모델의 한계와 트랜스포머의 중요성
초기 신경망은 컴퓨터가 텍스트 시퀀스를 처리하는 방법을 제공했습니다. 최초의 큰 성공 사례로는 순환신경망(Recurrent Neural Networks, RNN)이 사용되었으며, 이후 장단기 기억(Long Short-Term Memory, LSTM) 네트워크로 개선되었습니다. RNN은 문장을 단어 단위로 하나씩 처리하며, 각 단계마다 정보를 앞으로 전달합니다. 이는 마치 계주 경기에서 바통을 넘기는 것과 같습니다. 이 단계별 과정은 단어가 순서대로 도착하는 언어의 작동 방식과 유사해 보입니다.
하지만 이러한 설계에는 주요한 단점이 있습니다:
- RNN은 단어를 하나씩 처리합니다. 훈련 과정을 완전히 병렬화(parallelized)할 수 없기 때문에, 특히 긴 문장의 경우 속도가 느립니다.
- 초기 단어의 정보는 시퀀스를 따라 이동하면서 종종 "사라집니다." 이는 소실되는 기울기(vanishing gradients)라는 문제입니다. 도움을 주기 위해 개발된 LSTM조차도 여전히 멀리 떨어진 단어를 기억하는 데 어려움을 겪습니다.
- RNN은 본질적으로 주변 단어에 더 초점을 맞춥니다. 왜냐하면 먼 단어의 정보가 많은 중간 단계를 거쳐야 하기 때문입니다. 하지만 실제 문장에서는 중요한 연결고리가 양 끝단의 단어 사이에 있을 수 있습니다.
이러한 문제들 때문에 RNN은 긴 텍스트 처리에 어려움을 겪고, 멀리 떨어진 단어를 연결하는 데 취약하며, 훈련 속도가 느립니다.
트랜스포머(Transformers)가 판도를 바꿨습니다. 2017년, 연구자들(Vaswani et al.)은 단어 처리를 순차적으로 진행하지 않는 아키텍처를 소개했습니다. 대신 트랜스포머는 문장 전체를 한 번에 고려하며, 각 단어가 다른 모든 단어와 직접 연결되도록 합니다. 이는 RNN의 병목 현상을 피했으며, 자연어 처리 작업에서 기존 모델들을 빠르게 능가하는 성능을 보였습니다. 트랜스포머는 BERT나 GPT 같은 대규모 언어 모델(large language models)로 가는 길을 열었습니다.
시퀀스로부터 집합으로: 트랜스포머의 핵심 아이디어
전통적인 모델들은 텍스트를 한 고리씩 이동하는 사슬처럼 취급합니다. 트랜스포머는 다르게 작동합니다. 전체 사슬을 한 번에 살펴보고, 각 단어에게 가장 중요한 연결고리가 무엇인지 찾아냅니다.
실제로 이는 모든 단어를 병렬로 처리한다는 것을 의미합니다. 각 단어는 문장의 다른 모든 단어들을 살펴봄으로써 업데이트됩니다. 이러한 병렬성은 Transformers를 훨씬 빠르게 만듭니다. 모든 단어가 함께 처리되므로 최신 하드웨어를 최대한 활용할 수 있습니다.
또한 더 효과적입니다. 각 단어는 위치에 관계없이 다른 어떤 단어에서도 정보를 직접 가져올 수 있습니다. 최근 단어에 대한 특별한 편향이 없고, 중요한 연결 고리를 기억하는 데 있어 흐트러짐도 없습니다.
Transformer 인코더 둘러보기: 큰 그림
Transformer 인코더는 텍스트를 읽고 각 단어에 대해 스마트하고 문맥을 인식하는 표현(context-aware representation)을 구축하는 아키텍처의 일부입니다. 이는 거의 동일한 레이어들로 쌓여 있으며, 보통 6개 이상이고 대규모 모델에서는 훨씬 더 많습니다.
각 인코더 레이어는 여러 부분으로 구성됩니다:
- 입력 임베딩 (Input Embeddings): 단어(또는 때로는 서브워드 토큰)를 숫자들의 밀집 벡터(dense vectors)로 변환합니다. 각 벡터는 하나의 단어를 나타냅니다.
- 위치 인코딩 (Positional Encoding): Transformers에는 단어 순서를 알 수 있는 내장된 방식이 없기 때문에, 어떤 단어가 첫 번째인지, 두 번째인지를 알려주기 위해 숫자를 추가합니다.
- 셀프 어텐션 (Self-Attention): 핵심 혁신입니다. 각 단어는 자신을 이해하는 데 가장 중요한 것이 무엇인지 결정하기 위해 다른 모든 단어를 '살펴봅니다'.
- 피드 포워드 레이어 (Feed-Forward Layers): 각 단어의 벡터에 개별적으로 적용되는 표준 신경망 레이어입니다.
- 정규화 및 잔차 연결 (Normalization and Residual Connections): 훈련을 안정적으로 유지하고 정보가 깊은 레이어 스택을 통해 원활하게 흐르도록 돕는 방법들입니다.
현재는 문장을 읽고 문맥 기반 단어 벡터를 생성하는 섹션인 인코더에 초점을 맞추겠습니다. 디코더(텍스트 생성에 사용됨)나 전체 시퀀스-투-시퀀스 Transformer는 아직 다루지 않겠습니다.
셀프 어텐션: 한 번에 모든 것을 보기
셀프 어텐션이 Transformers를 강력하게 만드는 요소입니다. 문장을 읽으면서 각 단어마다 '이것을 이해하기 위해 어떤 다른 단어들에 주의를 기울여야 할까?'라고 생각하는 것을 상상해 보세요.
다음 문장을 예로 들어보겠습니다:
"The animal didn't cross the street because it was too tired."
여기서 "it"은 무엇을 가리킬까요? 셀프 어텐션(Self-attention)은 모델이 "it"을 다른 모든 단어와 비교하게 하고, 훈련을 통해 "animal"이 가장 관련성 높은 연결 고리임을 학습할 수 있게 합니다.
비유: 모두가 무선 헤드셋을 착용한 그룹 회의를 상상해 보세요. 어느 순간이라도 당신(모델)은 전체 대화를 들을 수 있고, 다음 응답에 중요한 부분에 집중할 수 있습니다. 이전 화자가 말한 내용에만 국한되지 않습니다.
각 단어는 문장의 모든 단어—자기 자신을 포함하여—에 대한 어텐션 점수를 계산합니다. 그런 다음, 이 점수들을 가중치로 사용하여 다른 모든 단어의 가중 평균으로 새로운 벡터를 형성합니다. 이 단계는 모델이 장거리 관계(long-range relationships)와 맥락을 한 번에 포착할 수 있게 합니다.
인코더를 통과하는 과정: 단계별 설명
하나의 트랜스포머 인코더 레이어를 통해 데이터가 어떻게 이동하는지 살펴보겠습니다:
- 단어를 토큰으로 변환: 문장의 각 단어(또는 서브워드)는 어휘집에서 고유한 인덱스로 매핑됩니다.
- 임베딩 생성: 각 토큰 인덱스는 임베딩—단어 특징을 포착하는 밀집 벡터(dense vector)—가 됩니다.
- 위치 인코딩 추가: 각 임베딩에 숫자를 더하여 문장에서의 위치를 포착합니다. 이는 모델에게 단어 순서에 대한 감각을 부여합니다.
- 셀프 어텐션 적용: 각 단어에 대해 다른 모든 단어로부터 정보를 결합하여 새로운 벡터를 만듭니다. 각 단어가 기여하는 양은 어텐션 가중치(attention weights)에 의해 결정됩니다.
- 피드 포워드 레이어 적용: 각 단어의 벡터는 동일한 작은 신경망을 통해 처리됩니다. 이는 각 위치별로 개별적으로 적용됩니다.
- 더하기 및 정규화 (Add & Normalize): 모델은 원래 벡터와 새로운 벡터를 결합하고(잔차 연결, residual connection), 훈련 중 안정성을 유지하기 위해 정규화합니다.
- 레이어 쌓기: 한 레이어의 출력은 다음 레이어의 입력으로 전달됩니다. 각 레이어를 거치면서 표현(representations)은 더 풍부해지고 맥락을 인식하게 됩니다.
모든 레이어를 거치면, 입력된 단어마다 하나의 벡터 세트를 얻게 됩니다. 이 벡터들에는 이제 단어의 의미와 문장 내에서의 맥락 정보가 모두 담겨 있습니다. 따라서 어떤 다운스트림 NLP 작업에도 사용되거나, 더 나아가 모델 구성 요소로 전달될 수 있습니다.
실제 작동 예시: PyTorch를 이용한 최소 인코더 구현
여기에 실행 가능한 PyTorch 예제가 있습니다. 이 코드는 최소한의 트랜스포머 인코더를 구축하고, 단어 임베딩 형태를 띠는 무작위 입력에 대해 이를 실행합니다. 실제 데이터는 없으므로, 아키텍처가 어떻게 구성 요소들로 맞춰지는지에 초점을 맞출 수 있습니다.
import torch
import torch.nn as nn
...
- dummy_embeddings: 실제 토큰 임베딩을 대신하는 더미 값—각 단어를 벡터로 간주합니다.
- PositionalEncoding: 각 임베딩에 단어 순서에 대한 정보를 추가합니다.
- encoder_layer와 transformer_encoder: 주요 트랜스포머 구성 요소들—셀프 어텐션(self-attention)과 피드 포워드 네트워크(feed-forward networks)를 결합하여 만듭니다.
- output: 각 토큰에 대해 이제 '맥락화된(contextualized)' 벡터를 갖게 됩니다. 이 벡터는 단어 자체의 정체성과 그것이 전체 문장에 어떻게 들어맞는지 모두 포착합니다.
토큰 수, 레이어 수, 또는 어텐션 헤드 수를 늘려가며 아키텍처와 데이터 형태가 어떻게 변하는지 확인해 볼 수 있습니다. 이 코드는 트랜스포머 이론을 구체적이고 실행 가능한 무언가에 기반하게 합니다.
핵심 요약 (Key Takeaways)
- RNN은 텍스트를 순차적으로 처리하며 장거리 의존성(long-range dependencies) 문제와 느린 학습 속도에 어려움을 겪습니다.
- 트랜스포머는 모든 단어를 병렬로 처리하고 셀프 어텐션을 사용하여 멀리 떨어진 단어들을 직접 연결합니다.
- 위치 인코딩(Positional encoding)은 트랜스포머에게 단어 순서에 대한 인식을 부여합니다.
- 트랜스포머 인코더는 셀프 어텐션 레이어와 피드 포워드 네트워크 레이어를 쌓아 올려 풍부한 맥락적 표현을 구축합니다.
- PyTorch를 사용하면 트랜스포머 인코더 구성 요소를 구현하고 실험하는 것이 매우 간단합니다.
직접 시도해보기 (Try It Yourself)
제공된 PyTorch 코드에서 dummy_embeddings를 수정하여 새로운 문장(예: 모양이나 값을 변경)을 시뮬레이션해 보세요. 코드를 수정 전과 후에 실행하고, 출력되는 텐서들을 비교하며 서로 다른 입력 임베딩이 어떻게 다른 인코딩 표현을 생성하는지 관찰하세요.
추가 자료 (Further Resources)
31일차에 공개: 트랜스포머 디코더 아키텍처