
오늘은 RNN부터 LLM의 동작 과정까지 살펴보면서, 텍스트가 실제
딥러닝 모델에 어떻게 입력되고 처리되는지 정리해보았다.
RNN(Recurrent Neural Network, 순환 신경망)은 Transformer가 등장하기
전에 자연어 처리에서 많이 사용되었던 언어 모델이다.
RNN은 문장의 토큰을 왼쪽에서 오른쪽으로 순차적으로 처리하면서
지금까지 입력된 내용을 바탕으로 다음 단어(토큰)를 예측한다.
순전파 → Loss → 역전파 → 가중치 업데이트 → 반복
문장의 맨 왼쪽부터 맨 오른쪽까지 순서대로 데이터를 전달하면서
예측값을 만들어내는 과정이다.
예를 들어 다음과 같은 문장이 있다면:
나는 → 서울에 → 갔다
RNN은 앞에서부터 순서대로 처리하면서 현재까지의 정보를 다음 단계로
전달한다.
순전파를 통해 예측한 결과와 실제 정답을 비교하여 Loss(오차)를
계산한다.
그 다음 Loss를 줄이기 위해 어떤 가중치를 얼마나 수정해야 하는지를
계산한다. 이때 사용하는 것이 Gradient(기울기)이다.
역전파는 순전파와 반대 방향으로 문장의 뒤쪽에서 앞쪽으로 이동하면서
Gradient를 계산한다.
문장이 짧다면 역전파 과정에서 문제가 크게 발생하지 않을 수 있다. 하지만
문장이 길어지면 계산해야 하는 단계가 많아진다.
이때 역전파 과정에서 Gradient가 계속 계산되면서 점점 작아져 거의 0이
되거나, 반대로 지나치게 커지는 문제가 발생할 수 있다.
결국 RNN은 긴 문장에서 앞부분의 정보를 제대로 학습하기 어렵다는
문제가 있었다. 이러한 문제를 해결하기 위해 이후 LSTM, GRU 등의 구조가
등장했고, 더 나아가 Transformer가 등장하게 되었다.
Gradient는 쉽게 말하면 "가중치를 아주 조금 바꿨을 때 Loss가 얼마나
변하는가?"를 나타내는 값이다.
| Gradient | 의미 | 가중치 조정 |
|---|---|---|
| 양수(+) | 가중치를 키우면 Loss가 증가 | 가중치를 줄이는 방향 |
| 음수(-) | 가중치를 키우면 Loss가 감소 | 가중치를 키우는 방향 |
즉, 모델은 Gradient를 참고하여 Loss가 줄어드는 방향으로 가중치를
수정한다.
가중치는 딥러닝 모델이 입력값을 얼마나 중요하게 반영할지 결정하는
숫자라고 생각하면 이해하기 쉽다.
예를 들어 모델이 어떤 단어를 예측할 때, 앞에서 입력된 특정 정보가 결과에
큰 영향을 줘야 한다면 그 정보와 관련된 가중치가 더 크게 조정될 수 있다.
아주 단순하게 다음과 같은 계산이 있다고 생각해보자.
입력값 × 가중치 = 결과에 반영되는 값
예를 들어:
입력값 = 10
가중치 = 0.2
10 × 0.2 = 2
이 경우 입력값 10이 최종 계산에 2만큼 반영된다.
학습 과정에서 모델의 예측이 틀렸다면 Loss를 계산하고 Gradient를 이용하여
"이 가중치를 조금 더 키워야 할까? 줄여야 할까?"를 판단한다.
그리고 가중치를 조금씩 수정하면서 예측이 더 정확해지는 방향으로 모델을
학습한다.
가중치 = 입력 정보의 중요도를 조절하는 값
Gradient = 가중치를 어느 방향으로 얼마나 바꿀지 알려주는 값
RNN에서는 문장이 길어질수록 앞부분의 정보와 관련된 가중치가 역전파
과정에서 제대로 업데이트되지 않을 수 있다.
예를 들어:
나는 / 약속을 / 위해 / 서울로 / 버스를 / 타고 / 올라갔다.
그리고 / ... / 거기서 / 새로운 / 친구를 / 사귀게 / 되었다.
여기서 '거기서'가 가리키는 장소는 앞에 나온 '서울'이다. 하지만
두 단어 사이에 많은 토큰이 존재한다.
RNN은 역전파 과정에서 뒤쪽에서 앞쪽으로 거슬러 올라가며 가중치를
업데이트하기 때문에, 문장이 너무 길어지면 '서울'과 관련된 앞부분의
가중치까지 Gradient가 제대로 전달되지 않을 수 있다.
이것이 RNN이 긴 문장에서 장기 의존성(Long-term Dependency)을
학습하기 어려운 대표적인 이유이다.
LLM(Large Language Model, 대규모 언어 모델)은 사람의 언어를 이해하고
처리하는 자연어 처리(NLP) 분야에서 활용되는 대규모 인공지능
모델이다.
LLM은 지금까지 주어진 내용을 바탕으로 다음에 올 가능성이 높은 토큰을
예측하고, 이 과정을 반복하면서 문장을 만들어낸다.
입력된 내용 → 다음 토큰 예측 → 예측한 토큰 추가 → 다시 다음 토큰
예측 → 반복
언어 모델(Language Model)은 주어진 텍스트를 바탕으로 다음에 올
단어 또는 토큰을 예측하도록 학습한 모델이다.
모델은 학습을 통해 적절한 가중치(파라미터)를 찾아낸 하나의 계산
구조라고 생각할 수 있다.
전체적인 흐름은 다음과 같다.
데이터 → 학습 → 모델 → 새로운 입력 → 예측
그리고 실제 LLM에서는 정확히 '단어'를 예측하는 것이 아니라
Token(토큰)을 예측한다.
토큰은 텍스트를 모델이 처리할 수 있도록 나눈 작은 단위이다. 단어 하나가
토큰 하나가 될 수도 있지만, 하나의 단어가 여러 개의 토큰으로 나뉠 수도
있다.
컴퓨터가 텍스트를 그대로 이해하는 것은 어렵기 때문에 딥러닝 모델이
처리할 수 있도록 숫자로 표현해야 한다.
이때 데이터의 특징과 의미를 여러 개의 숫자로 이루어진 벡터(Vector)로
표현하는 것을 임베딩(Embedding)이라고 한다.
벡터는 간단하게 말하면 숫자 여러 개를 순서대로 나열한 것이다.
[0.9, 0.8]
[-0.7, 0.1]
임베딩은 단순히 단어를 숫자로 바꾸는 것이 아니라, 데이터의 특징과
의미가 숫자 공간에 반영되도록 표현하는 것이다.
따라서 의미나 특성이 비슷한 데이터는 임베딩 공간에서도 서로 가까운 값을
가지도록 학습될 수 있다.
| 단어 | Token ID | Embedding 예시 |
|---|---|---|
| 고양이 | 5321 | [0.9, 0.8] |
| 강아지 | 8807 | [0.8, 0.9] |
| 자동차 | 5322 | [-0.7, 0.1] |
주의: 위 숫자는 이해를 위한 예시이다. 실제 모델의 임베딩 값은 훨씬
많은 차원의 숫자로 구성된다.
텍스트가 LLM에 입력되면 대략 다음과 같은 과정을 거친다.
Text → Token → Token ID → Embedding → Transformer → 출력(예측)
사람이 입력한 텍스트를 모델이 처리할 수 있도록 먼저 Token 단위로
나눈다.
text = '나는 배를 타고 제주도에 갔다'
tokens = text.split()
print(tokens)
결과:
['나는', '배를', '타고', '제주도에', '갔다']
위 예제에서는 이해를 쉽게 하기 위해 띄어쓰기 기준으로 나누었다.
하지만 실제 LLM은 단순히 띄어쓰기만을 기준으로 토큰을 나누지 않는다.
컴퓨터는 문자열 자체를 계산에 직접 사용하지 않고, 어휘사전(Vocabulary)에
정의된 고유한 번호인 Token ID로 변환하여 처리한다.
vocab = {
'나는': 104,
'사과를': 230,
'배를': 582,
'타고': 77,
'제주도에': 1823,
'갔다': 42
}
token_ids = [vocab[token] for token in tokens]
print('Tokens:', tokens)
print('Token IDs:', token_ids)
결과:
Tokens: ['나는', '배를', '타고', '제주도에', '갔다']
Token IDs: [104, 582, 77, 1823, 42]
Token ID는 단순한 식별 번호이다. 예를 들어 '갔다'의 ID가 42라고 해서
42라는 숫자 자체에 '갔다'라는 의미가 들어 있는 것은 아니다.
Token ID를 모델의 임베딩 층(Embedding Layer)에 통과시키면 각 토큰을
숫자로 이루어진 벡터로 변환할 수 있다.
import torch
import torch.nn as nn
token_ids = [104, 582, 77, 1823, 42]
torch.manual_seed(42)
# Token ID를 Tensor로 변환
input_ids = torch.tensor(token_ids)
# 어휘사전 크기 50,000
# 각 Token을 4개의 숫자로 표현
embedding_layer = nn.Embedding(50000, 4)
embedding_vectors = embedding_layer(input_ids)
print(embedding_vectors)
출력되는 값은 예를 들어 다음과 같다.
tensor([
[ 0.6839, -1.3246, -0.5161, 0.6002],
[ 0.8467, 0.8503, 1.8044, 1.4647],
[ 0.4536, 1.2461, -2.3065, -1.2869],
[-1.8654, -1.2593, 0.4450, 1.5879],
[-1.6022, 1.3529, 1.2888, 0.0523]
])
이 코드에서 만들어진 임베딩 값은 처음에는 무작위로 초기화된 값이다.
따라서 처음부터 '고양이와 강아지는 비슷하다'와 같은 의미를 가지고 있는
것은 아니다.
모델이 데이터를 반복해서 학습하면서 Loss를 줄이는 방향으로 임베딩을
포함한 가중치를 업데이트하고, 그 과정에서 토큰 간의 의미와 관계가 숫자
공간에 반영된다.
| 개념 | 핵심 내용 |
|---|---|
| RNN | 토큰을 순차적으로 처리하는 신경망 |
| 순전파 | 입력 데이터를 앞에서 뒤로 처리하여 예측 |
| 역전파 | Loss를 줄이기 위해 Gradient를 계산하고 가중치 수정 |
| Gradient | 가중치를 조금 바꿨을 때 Loss가 얼마나 변하는지 나타내는 값 |
| 가중치 | 입력 정보가 결과에 얼마나 영향을 줄지 조절하는 값 |
| LLM | 다음에 올 가능성이 높은 토큰을 예측하는 대규모 언어 모델 |
| Token | LLM이 처리하는 텍스트의 작은 단위 |
| Token ID | 토큰을 구분하기 위한 고유 번호 |
| Embedding | 토큰의 의미와 특징을 숫자 벡터로 표현한 것 |
Text → Token → Token ID → Embedding → Transformer → 예측
그리고 모델의 학습은 기본적으로 순전파 → Loss 계산 → 역전파 → 가중치
업데이트를 반복하면서 예측이 점점 정확해지는 방향으로 이루어진다.