생성 모델
Part 1에서 다룬 신경망은 대부분 판별(discriminative) 과제를 풀었습니다. 입력 가 주어졌을 때 정답 레이블 의 조건부 확률 를 학습하여 분류하거나 회귀하는 식입니다. 이 장에서 다루는 생성(generative) 모델은 목표가 다릅니다. 데이터 자체의 분포 를 학습하여, 그 분포에서 새로운 표본을 샘플링해 만들어 냅니다. 텍스트를 한 토큰씩 이어 쓰는 언어 모델, 순수한 노이즈에서 이미지를 빚어내는 확산 모델이 모두 여기에 속합니다.
이 장에서는 두 갈래의 생성 방식을 봅니다. 먼저 자기회귀(autoregressive) 언어 모델이 다음 토큰을 어떻게 예측하는지를 사전학습된 GPT-2로 직접 들여다보고, 이어서 이미지 생성의 대표 패러다임인 확산 모델(diffusion) 의 아이디어를 정리합니다.
1자기회귀 언어 모델: 다음 토큰 예측¶
언어 모델은 토큰 시퀀스 의 결합 확률을 다음과 같이 연쇄 법칙(chain rule) 으로 분해합니다.
즉 매 시점에서 지금까지의 토큰을 조건으로 다음 토큰의 확률 분포를 출력하는 것이 전부입니다. 생성은 이 분포에서 토큰을 하나 뽑아 입력 끝에 붙이고, 늘어난 입력으로 다시 다음 토큰을 예측하는 과정을 반복하는 것입니다.
사전학습된 한국어 GPT-2에 짧은 문장을 넣어 봅시다. 먼저 입력 텍스트를 형태소 단위로 토큰화하고, 각 토큰을 어휘 사전의 정수 ID로 인코딩한 뒤 모델에 통과시킵니다.
input_text = '나는 너를 사랑하는데, 너도 나를'
형태소목록 = tokenizer.tokenize(input_text)
input_ids = tokenizer.encode(input_text, return_tensors='pt')
display(pd.DataFrame({
'형태소': 형태소목록,
'ID': input_ids[0].tolist()
}).T)
model = GPT2LMHeadModel.from_pretrained(model_id)
with torch.inference_mode():
outputs = model(input_ids)모델의 출력 outputs.logits는 형태 의 텐서입니다. 여기서 는 입력 토큰 개수, 는 어휘 사전 크기입니다. 입력의 각 위치 마다 어휘 전체에 대한 점수(logit) 벡터가 하나씩 나온다는 뜻입니다. 이 점수를 어휘 차원으로 소프트맥스하면 그 위치에서 "다음에 올 토큰"의 확률 분포 가 됩니다.
분포에서 가장 확률이 높은 토큰 하나만 고르는 것을 탐욕적(greedy) 선택 또는 Top-1이라 합니다.
# 입력의 각 위치(y_t)에서 다음 토큰(y_{t+1})의 확률 분포
확률분포 = torch.nn.functional.softmax(outputs.logits, dim=-1) # (1, T, V)
# 최고 확률(Top-1)만 사용
다음단어확률, 다음단어ID = torch.max(확률분포, dim=-1) # (1, T)
print('확률분포:', tuple(확률분포.shape))
print('Top-1 확률:', tuple(다음단어확률.shape), 'Top-1 ID:', tuple(다음단어ID.shape))여기서 핵심은 예측이 마지막 한 값만 내놓는 것이 아니라는 점입니다. 입력 시퀀스의 모든 위치에서 동시에 다음 토큰이 예측됩니다. 위치 1은 위치 2를, 위치 2는 위치 3을 예측하는 식으로 한 칸씩 어긋난 대응이 만들어집니다.
그래서 예측표는 입력과 같은 길이를 가집니다. 각 입력 토큰 옆에 그 위치에서 예측된 다음 토큰과 확률을 나란히 놓아 보면 모델이 문맥을 어떻게 이어 가는지 한눈에 들어옵니다.
# 각 입력 토큰 위치별 다음 토큰 예측(Top-1) 표
토큰ID목록 = input_ids[0].tolist()
입력토큰목록 = tokenizer.convert_ids_to_tokens(토큰ID목록)
rows = []
for t, y_t in enumerate(입력토큰목록):
pred_id = int(다음단어ID[0, t].item())
pred_p = float(다음단어확률[0, t].item())
pred_token = tokenizer.decode([pred_id]).replace('\n', '\\n')
rows.append({
'입력토큰(y_t)': y_t,
'다음토큰(y_t+1)': pred_token,
'확률': pred_p,
})
# 예측은 입력 토큰 시퀀스의 각 위치에서 다음 토큰을 예측하는 것이므로, 예측표는 입력 토큰 시퀀스와 같은 길이를 가집니다.
# 즉, 마지막 하나의 값을 예측하는 것이 아니라, 각 입력 토큰 위치마다 다음 토큰을 예측하는 형태입니다.
# y1 -> y2, y2 -> y3, ..., yT-1 -> yT
예측표 = pd.DataFrame(rows)
예측표.index = 예측표.index + 1 # 위치를 1부터 시작하도록 조정
예측표.index.name = '위치(t)'
display(예측표.round(3).T)마지막 위치 의 예측이 곧 실제 생성에 쓰이는 값입니다. 입력 끝까지의 문맥을 조건으로 한 다음 토큰이기 때문입니다. 이 토큰을 입력 뒤에 이어 붙이고 같은 절차를 반복하면 문장이 한 토큰씩 자라납니다. 학습 시점에는 정답 시퀀스가 통째로 주어지므로 모든 위치의 예측을 한 번에 비교하여 손실을 계산할 수 있고(이것이 Part 1의 교사 강요, teacher forcing와 같은 맥락입니다), 추론 시점에는 이렇게 자기 출력을 다시 입력으로 먹이며 순차적으로 생성합니다.
2확산 모델: 노이즈에서 이미지로¶
이미지 생성에서 널리 쓰이는 또 다른 패러다임은 확산 모델(Denoising Diffusion Model) 입니다. 자기회귀 언어 모델이 토큰을 왼쪽에서 오른쪽으로 이어 가는 것과 달리, 확산 모델은 전혀 다른 방식으로 표본을 만듭니다.
핵심 아이디어는 노이즈 제거(denoising) 입니다. 모델은 노이즈가 섞인 이미지에서 그 노이즈를 제거하도록 학습됩니다. 생성할 때는 순수한 노이즈에서 출발하여, 학습된 노이즈 제거를 반복적으로 적용합니다. 무작위 노이즈가 단계를 거치며 조금씩 정돈되어 마침내 하나의 이미지로 떠오르는 것입니다.
정리하면 두 생성 방식은 다음과 같이 대비됩니다.
| 자기회귀 언어 모델 | 확산 모델 | |
|---|---|---|
| 생성 단위 | 토큰(이산) | 이미지 전체(연속) |
| 진행 방향 | 시퀀스 한 방향으로 한 토큰씩 | 순수 노이즈에서 반복적 노이즈 제거 |
| 매 단계가 하는 일 | 다음 토큰의 확률 분포 예측 | 현재 이미지의 노이즈 추정·제거 |
두 모델 모두 데이터 분포에서 새로운 표본을 만들어 낸다는 생성 모델의 목표를 공유하지만, 데이터의 성격(이산적인 언어 대 연속적인 픽셀)에 맞춰 서로 다른 경로를 택한 셈입니다.