LLM 엔지니어링4장 · LLM 을 완성하는 3단계와 Hugging Face18 / 23← 이전목차다음 →MetaContents

4장. LLM 을 완성하는 3단계와 Hugging Face

실습 4 — 사전학습 데이터 만들기

실습 3의 토크나이저로 shop_corpus.txt 를 토큰으로 바꾸고, 128토큰짜리 묶음으로 잘라 사전학습용 데이터를 만듭니다. datasets 의 Dataset 과 map 을 씁니다. 실습 3을 먼저 실행해 out_tok/tokenizer.json 이 있어야 합니다.

1단계. 토크나이저 불러오고 줄을 Dataset 으로

Python
from transformers import PreTrainedTokenizerFast
from datasets import Dataset

tok = PreTrainedTokenizerFast(tokenizer_file="out_tok/tokenizer.json", pad_token="<pad>", eos_token="<eos>")

with open("shop_corpus.txt", encoding="utf-8") as f:
    lines = [line for line in f.read().splitlines() if line]     # 빈 줄은 뺀다

ds = Dataset.from_dict({"text": lines})
# 학습용 95%, 검증용 5%. seed 로 나누는 순서를 고정한다
ds = ds.train_test_split(test_size=0.05, seed=42)
print(ds)
출력
DatasetDict({
    train: Dataset({
        features: ['text'],
        num_rows: 2375
    })
    test: Dataset({
        features: ['text'],
        num_rows: 125
    })
})

2단계. 토큰으로 바꾸기

Python
def tokenize(batch):
    # 줄마다 끝에 <eos> 를 붙여 "한 글이 여기서 끝난다"를 알려 준다
    return tok([t + "<eos>" for t in batch["text"]])

# batched=True: 줄을 묶음으로 한꺼번에 처리해 빠르다. 원래 글 열은 지운다
tokenized = ds.map(tokenize, batched=True, remove_columns=["text"])
print(tokenized["train"][0]["input_ids"][:15])        # 첫 줄의 앞 15개 토큰 번호
print("학습용 토큰 수:", sum(len(x) for x in tokenized["train"]["input_ids"]))
출력
[60, 493, 62, 464, 494, 1160, 913, 921, 923, 446, 918, 925, 15, 1]
학습용 토큰 수: 46393

3단계. 128토큰 묶음으로 자르기

Python
BLOCK = 128

def group(batch):
    """여러 줄의 토큰을 한 줄로 이어 붙인 뒤 128개씩 자른다. 남는 끝부분은 버린다."""
    ids = sum(batch["input_ids"], [])            # 리스트들을 하나로 이어 붙이기
    n = len(ids) // BLOCK * BLOCK
    return {"input_ids": [ids[i:i + BLOCK] for i in range(0, n, BLOCK)]}

lm_data = tokenized.map(group, batched=True, remove_columns=["attention_mask"])
print(lm_data)
print("묶음 하나의 길이:", len(lm_data["train"][0]["input_ids"]))
print(tok.decode(lm_data["train"][0]["input_ids"][:40]))     # 첫 묶음의 앞부분을 글로
출력
DatasetDict({
    train: Dataset({
        features: ['input_ids'],
        num_rows: 360
    })
    test: Dataset({
        features: ['input_ids'],
        num_rows: 18
    })
})
묶음 하나의 길이: 128
[공지] 7월 21일은 택배사 휴무로 배송이 하루 늦어집니다.<eos>[상담] 고객: 백팩 관련해서 궁금한 게 있어요. 상담원: 안녕하세요, 고객님. 출고 전이라면 배송 주소를 바꿀 수 있습니다. 감사합니다

이 실습이 뜻하는 것

이 실습은 긴 두루마리 공책을 같은 길이의 공부 카드로 자르는 것과 같습니다. 글 줄을 모아 공부용과 시험용으로 나누고(1단계), 줄마다 끝 표시를 붙여 번호로 바꾸고(2단계), 모든 줄을 한 줄로 이어 붙인 뒤 128칸짜리 카드로 똑같이 자릅니다(3단계). 여기서 카드 한 장이 128토큰 묶음, 끝 표시가 <eos>, 공부용과 시험용이 train 과 test 입니다. 비유와 다른 점은, 보통 카드에는 한 장에 한 내용을 적지만 여기서는 한 장에 여러 줄이 들어가고 줄 중간에서 잘리기도 한다는 것입니다.

단계출력뜻
1. 나누기train 2375, test 1252,500줄을 95%와 5%로 나눴습니다. 시험용 줄은 학습에 쓰지 않고 실력을 잴 때만 씁니다
2. 토큰 번호[60, 493, …, 15, 1]앞 15개를 찍게 했지만 첫 줄이 14토큰뿐이라 14개가 나왔습니다. 마지막 1 은 줄 끝에 붙인 <eos> 의 번호입니다
2. 토큰 수46393학습용 글이 토큰 약 4만 6천 개입니다. 모델이 사전학습에서 볼 글의 전부입니다
3. 묶음 수train 360, test 18128토큰 카드가 학습용 360장, 검증용 18장 나왔습니다. 남는 끝부분은 버렸습니다
3. 묶음 길이128모든 카드의 길이가 같아 채움 토큰 없이 여러 장을 한 번에 묶어 넣을 수 있습니다
3. 글로 되돌리기…늦어집니다.<eos>[상담] …카드 한 장 안에 공지와 상담 두 줄이 <eos> 를 사이에 두고 이어져 있습니다

한 줄로 줄이면, 쇼핑몰 글 2,500줄을 같은 길이의 공부 카드 378장(학습용 360장, 검증용 18장)으로 바꾼 실습입니다. 이 카드로 실습 5에서 작은 GPT(Generative Pre-trained Transformer, 생성형 사전학습 트랜스포머)를 사전학습합니다. 아직 배운 것은 없고, 데이터의 모양만 맞춘 단계입니다.

코드 설명

  • Dataset.from_dict 는 열 이름과 값 목록으로 데이터를 만듭니다. train_test_split 은 1장의 train_test_split 과 같은 일을 하고, DatasetDict 로 두 묶음을 돌려줍니다.
  • 2,500줄이 학습용 2,375줄과 검증용 125줄로 나뉘었습니다. 학습용 글은 46,393토큰이고, 128토큰씩 자르니 학습용 360묶음, 검증용 18묶음이 되었습니다. "사전학습" 절 그림의 "약 5만 토큰"이 이 숫자입니다.
  • 줄을 이어 붙여 자르는 까닭은, 줄마다 길이가 달라 그대로 두면 짧은 줄 뒤를 채움 토큰으로 메워야 하기 때문입니다. 이어 붙이면 버리는 자리 없이 모든 토큰으로 배웁니다. 묶음 하나에 여러 줄이 <eos> 를 사이에 두고 들어갑니다.
  • 실습 5와 프로젝트에서는 이 준비를 그대로 다시 적고 시작합니다.
← 이전 절4장 · 실습 3 — 우리 토크나이저 학습하기다음 절 →4장 · 실습 5 — 작은 GPT 사전학습하기