한 권으로 끝내는 실전 LLM 파인튜닝 - 강다솔 지음/위키북스 |
LLM은커녕 전반적인 AI 관련 지식이 전무하다 보니 스터디 진도 따라가기가 쉽지 않다. 어제까지 스터디 5일 차가 끝났어야 하는데 아직 2일 차의 학습 범위를 벗아지 못하고 있다. 일단 동작 확인 중심으로 해서 진도를 따라가 보려고 한다. 오늘은 '2장 GPT'에서 '기본적인 언어 모델 구조 학습(46~66p)', '셀프 어텐션 매커니즘 이해(67~88p)' 부분을 공부했다.
02. GPT
언어 모델 만들기 (Optimizer 포함)
sepmGPT라는 이름의 클래스를 작성하는 것으로부터 간단한 언어 모델을 만드는 과정이 시작된다. 해당 클래스는 크게 '__init__' 메서드와 'forward' 메서드로 이뤄져 있다. 나는 파이썬도 잘 모르는데 __init__ 메서드는 파이썬의 생성자라고 한다. 신경망 모델에서는 이 메서드에서 모델의 구조와 필요한 초기 설정을 정의한다고. forward 메서드는 데이터가 전달될 때 호출되고 실제 계산 과정을 담당한다. 구조 확인 차 초기 코드를 첨부한다. 여기에 점점 살이 붙게 된다.
import torch
import torch.nn as nn
from torch.nn import functional as F
class semiGPT(nn.Module):
def __init__(self, vocab_length):
super().__init__()
self.embedding_token_table = nn.Embedding(vocab_length, vocab_length)
def forward(self, inputs, targets):
logits = self.embedding_token_table(inputs)
return logits
model = semiGPT(ko_vocab_size)
output = model(example_x, example_y)
print(output.shape)
위 코드의 출력 결과는 아래와 같다.
torch.Size([4, 8, 2701])
배치 크기 4, 시퀀스 길이 8, 어휘 크기는 2701인 텐서가 생성됐음을 의미한다고. 위 코드만으로는 모델이 학습될 수 없고 손실 계산이라는 것이 들어가야 한다. 손실이 낮을수록 모델의 예측이 실제 레이블에 더 가깝다는 것을 의미한다고 한다. 손실 계산을 위해 forward 메서드에 추가된 코드 중 주요한 부분은 아래와 같다.
logits = logits.view(batch * seq_length, vocab_length)
targets = targets.view(batch*seq_length)
loss = F.cross_entropy(logits, targets)
파이토치의 크로스 엔트로피(cross_entropy)함수가 손실 계산에 해당하는데 모델이 예측한 확률 분포(임베딩)와 실제 레이블(target) 분포 간의 차이를 계산한다. view 함수는 텐서의 모양을 변경해 주는 메서드로 같은 데이터를 다른 방식으로 보는 것처럼 만드는 효과가 있다고 한다. 즉, 4(batch) * 8(seq_length)를 평탄화(flatten)해서 32로 만든다.
다음은 학습한 모델이 예측한 글자를 생성하기 위해 generate 메서드를 추가하는 과정이다. 입력된 시작 문자열에 기반해서 다음 글자를 예측하고 텍스트를 생성한다. 코드는 아래와 같다.
def generate(self, inputs, max_new_tokens):
for _ in range(max_new_tokens):
logits, loss = self.forward(inputs)
logits = logits[:, -1, :]
print(logits.shape)
probs = F.softmax(logits, dim=-1)
next_inputs = torch.multinomial(probs, num_samples=1)
inputs = torch.cat((inputs, next_inputs), dim=1)
return inputs
실행 결과는 아래와 같은데 아직까지는 의미 있는 결과물이 나오지 않는다.
tensor(8.4701, grad_fn=<NllLossBackward0>)
torch.Size([1, 2701])
torch.Size([1, 2701])
torch.Size([1, 2701])
torch.Size([1, 2701])
torch.Size([1, 2701])
torch.Size([1, 2701])
torch.Size([1, 2701])
torch.Size([1, 2701])
torch.Size([1, 2701])
torch.Size([1, 2701])
' 鋪좇異e₊굿比끄족公'
다음은 Optimizer 추가 단계다. 모델 훈련 시 손실 함수를 이용해서 모델의 예측 값과 정답 사이의 차이(손실)를 계산하고, 손실을 최소화하기 위해 매개변수를 조절한다. Optimizer가 바로 이 매개변수 조정 과정을 담당한다고 한다. Optimizer 추가 이후 텍스트를 생성한 결과는 아래와 같다. '뉴스 통해 1일 반'? 여전히 이상한 말이지만 그래도 모양은 그럴듯해 보이는 결과가 나온다.
torch.Size([1, 2701])
torch.Size([1, 2701])
torch.Size([1, 2701])
torch.Size([1, 2701])
torch.Size([1, 2701])
torch.Size([1, 2701])
torch.Size([1, 2701])
torch.Size([1, 2701])
torch.Size([1, 2701])
torch.Size([1, 2701])
뉴스 통해 1일 반
중간 생략하고 아래는 드디어 완성된 대략적인 전체 '학습 루프'다.
from tqdm.auto import tqdm
batch_size = 32
for steps in tqdm(range(10000)):
example_x, example_y = batch_function("train")
logits, loss = model(example_x, example_y)
# 옵티마이저 초기화
optimizer.zero_grad(set_to_none=True)
# 역전파 계산
loss.backward()
# 가중치 업데이트
optimizer.step()
print(loss.item())
이후 calculate_loss 라는 이름의 Loss 함수를 만드는 과정을 거치게 되고(생략), 지금까지 작성 코드의 끝부분에 아래 코드를 붙여서 실행하면 모델이 일정 간격으로 학습 손실과 검증 손실을 계산하고 출력하는 과정을 반복한다. 여기까지 해서 드디어 '데이터 준비, 모델 정의, 손실 및 최적화, 모델 학습, 텍스트 생성'에 이르는 언어 모델의 기본적인 틀을 갖추게 됐다.
for step in range(max_iteration):
if step % eval_interval == 0 :
losses = compute_loss_metrics()
print(f'step : {step}, train loss : {losses["train"]:.4f}, val loss : {losses["eval"]:.4f}')
example_x, example_y = batch_function("train")
logits, loss = model(example_x, example_y)
optimizer.zero_grad(set_to_none=True)
loss.backward()
optimizer.step()
inputs = torch.zeros((1,1), dtype=torch.long, device=device)
print(token_decode(model.generate(inputs, max_new_tokens=100)[0].tolist()))
실행 결과는 아래와 같다.
step : 0, train loss : 8.4359, val loss : 8.4330
step : 300, train loss : 6.1704, val loss : 6.1665
// .. 중간 생략..
step : 48300, train loss : 3.3989, val loss : 3.3995
step : 48600, train loss : 3.3784, val loss : 3.4077
step : 48900, train loss : 3.3897, val loss : 3.4019
step : 49200, train loss : 3.3989, val loss : 3.4049
step : 49500, train loss : 3.3906, val loss : 3.4127
step : 49800, train loss : 3.3901, val loss : 3.3934
2개인프리 것을 시장했다립한 기록했다 SM특허준다산업에서 핵심상단에게임대회장조사내 항 하게 상위한 예측센터팜 개월 2위가능력과 수 한·지 후 표절해 개 결과를 감한다 차단편 등을
셀프 어텐션 추가하기
지금까지의 실습 결과는 문맥 이해와 의미 있는 단어 생성이 어렵다는 초창기 언어 모델의 문제점을 그대로 보여준다. 이런 문제를 해결하고자 어텐션 메커니즘을 중심으로 한 새로운 모델 아키텍처인 트랜스포머가 나오게 된다. 포스트의 분량이 너무 길어져서 이 부분은 간략히 적고 넘어가려고 간다. 셀프 어텐션은 입력 시퀀스(문장) 내 모든 단어 간의 관계를 직접 분석하고 처리해서 어떤 단어가 더 중요한지 판단하는 메커니즘으로 핵심 구성 요소는 쿼리(Q), 키(K), 밸류(V)이다. 다음은 마스크드 셀프 어텐션을 구현한 Head 클래스다.
class Head(nn.Module):
def __init__(self, head_size):
super().__init__()
self.key = nn.Linear(n_embed, head_size, bias=False)
self.query = nn.Linear(n_embed, head_size, bias=False)
self.value = nn.Linear(n_embed, head_size, bias=False)
self.register_buffer("tril", torch.tril(torch.ones(block_size, block_size)))
def forward(self, inputs):
batch_size, sequence_length, embedding_dim = inputs.shape
keys = self.key(inputs)
queries = self.query(inputs)
weights = queries @ keys.transpose(-2, -1) * (embedding_dim ** -0.5)
weights = weights.masked_fill(
self.tril[:sequence_length, :sequence_length] == 0, float("-inf")
)
weights = F.softmax(weights, dim=-1)
values = self.value(inputs)
output = weights @ values
return output
이전까지 작업한 코드에 이 코드를 붙여서 실행한 결과는 아래와 같다.
step : 0, train loss : 7.9190, val loss : 7.9190
step : 300, train loss : 4.1713, val loss : 4.1825
step : 600, train loss : 3.8909, val loss : 3.8957
// .. 중간 생략 ..
step : 49200, train loss : 3.4335, val loss : 3.4380
step : 49500, train loss : 3.4418, val loss : 3.4429
step : 49800, train loss : 3.4535, val loss : 3.4353
-----------------------------------------------
마급 취약 다기로 맞 인어닷는 지역량을 벌식에서 최근지를 장후 보거로 우설만 민간 보증가건 I의금 계 않아야 자무엇필요요. 수익성 늘수는 데안과인 둔 거래위 4남도지조정기지만냈다.
많이 좋아지긴 했지만 아직도 의미 있는 결과는 아닌 듯하다. 오늘은 여기까지.
'개발 > AI' 카테고리의 다른 글
[Day6] LLM 스터디 1기 - GPU 병렬화 기법 (0) | 2025.01.16 |
---|---|
[Day5] LLM 스터디 1기 - GPT, Gemma, Llama3 모델 특징 비교 (1) | 2025.01.15 |
[Day4] LLM 스터디 1기 - 파인튜닝 개념 (1) | 2025.01.11 |
[Day3] LLM 스터디 1기 - 멀티헤드 어텐션 & 피드포워드 (1) | 2025.01.10 |
[Day1] LLM 스터디 1기 - NLP 이해와 런팟 설치 (2) | 2024.12.30 |