NLP Task들을 하다보면 자연스레 Huggingface 라이브러리를 많이 사용하게되고, 그 중에서 특히 많이 다루게 되는 것이 DataCollator다.
그런데 대체 왜 우리는 Tokenizer만 사용하면 되지 DataCollator까지 사용해줄까?
아니,,, Padding 기능도 애당초 Tokenizer에 있지 않나?! 굳이 왜? DataCollator까지?
텍스트 분할 (Tokenization)
입력된 텍스트를 더 작은 단위인 토큰으로 나눈다.
예시:
"Hello, how are you?" → ["Hello", ",", "how", "are", "you", "?"]
숫자 변환 (Encoding)
토큰을 모델이 이해할 수 있는 숫자(token_id)로 변환한다.
예시:
["Hello", "world"] → [101, 7592, 2088, 102]
특수 토큰 추가
[CLS], [SEP] 등 모델에 필요한 특수 토큰을 추가한다.
패딩 (Padding)
입력 시퀀스의 길이를 동일하게 맞추기 위해 패딩 토큰을 추가해준다.
Attention Mask 생성
패딩된 부분을 모델이 무시하도록 하는 마스크를 생성
디코딩 (Decoding)
token_id를 다시 텍스트로 변환한다.
from transformers import BertTokenizer
tokenizer = BertTokenizer.from_pretrained("bert-base-cased")
# 토큰화
text = "Hello, how are you?"
tokens = tokenizer.tokenize(text)
print("Tokens:", tokens)
# 인코딩
input_ids = tokenizer.encode(text, add_special_tokens=True)
print("Input IDs:", input_ids)
# 디코딩
decoded_text = tokenizer.decode(input_ids)
print("Decoded text:", decoded_text)
# 여러 문장 처리
sentences = ["Hello, how are you?", "I'm fine, thank you!"]
encoded = tokenizer(sentences, padding=True, truncation=True, return_tensors="pt")
print("Encoded batch:", encoded)
동적 패딩 (효율적인 처리에 초점)
각 배치 내에서 가장 긴 시퀀스에 맞춰 다른 시퀀스들을 패딩한다.
레이블 처리
레이블도 적절히 패딩하며, 손실 계산 시 무시할 부분을 특수 값(보통 -100)으로 마스킹한다.
예시 : [1,2,3] -> [1,2,3, -100, -100]
모델 특정 입력 준비
예를 들어, seq2seq 모델을 위한 디코더 입력을 자동으로 생성한다.
예시 :
디코더 입력 생성: 타겟 시퀀스를 한 칸씩 오른쪽으로 시프트하고, 시작 토큰을 추가합니다.입력: ["Hello", "World"]
디코더 입력: [<start>, "Hello", "World"]
레이블: ["Hello", "World", <end>]
텐서 변환
입력 데이터를 PyTorch 텐서로 변환한다.
from transformers import DataCollatorForSeq2Seq, T5Tokenizer, T5ForConditionalGeneration
# 모델과 토크나이저 초기화
model = T5ForConditionalGeneration.from_pretrained("t5-small")
tokenizer = T5Tokenizer.from_pretrained("t5-small")
# DataCollator 초기화
data_collator = DataCollatorForSeq2Seq(tokenizer, model=model)
# 입력 데이터 (이미 토큰화되었다고 가정)
batch = [
{
"input_ids": [1, 2, 3, 4],
"attention_mask": [1, 1, 1, 1],
"labels": [5, 6, 7]
},
{
"input_ids": [1, 2, 3],
"attention_mask": [1, 1, 1],
"labels": [5, 6, 7, 8]
},
{
"input_ids": [1, 2, 3, 4, 5],
"attention_mask": [1, 1, 1, 1, 1],
"labels": [5, 6]
}
]
# DataCollator 적용
collated_batch = data_collator(batch)
print("Collated batch:")
for key, value in collated_batch.items():
print(f"{key}:")
print(value)
print()
>>> 출력
Collated batch:
input_ids: # 입력 시퀀스가 가장 긴 시퀀스 5에 맞춰 패딩됨.
tensor([[1, 2, 3, 4, 0],
[1, 2, 3, 0, 0],
[1, 2, 3, 4, 5]])
attention_mask: # 실제 토큰은 1, 패딩된 부분은 0 표시
tensor([[1, 1, 1, 1, 0],
[1, 1, 1, 0, 0],
[1, 1, 1, 1, 1]])
labels: # 가장 긴 레이블 시퀀스인 4에 맞춰 패딩됨. 패딩된 부분은 -100으로 설정되어 손실 계산시 무시됨.
tensor([[ 5, 6, 7, -100],
[ 5, 6, 7, 8],
[ 5, 6, -100, -100]])
decoder_input_ids: # 디코더 입력 자동생성 (시작 부분에 0(시작토큰) 추가
tensor([[0, 5, 6, 7],
[0, 5, 6, 7],
[0, 5, 6, 0]])
-100은 PyTorch와 같은 딥러닝 프레임 워크에서 손실함수 계산시 자동으로 무시되며, 그레디언트 계산에서도 제외된다.
즉, Cross Entropy loss와 같은 손실 함수에서 -100으로 표시된 위치의 예측은 손실 계산에 포함되지 않는다.
Tokenizer와 DataCollator는 상호 보완적인 역할을 한다.
Tokenizer는 텍스트를 모델이 이해할 수 있는 형태로 변환한다면,
DataCollator는 이러한 변환된 데이터를 효율적인 배치 형태로 만들어 모델 훈련을 최적화한다.
Tokenizer의 경우 데이터셋 준비 단계에서 활용되며, 원시 텍스트가 입력되어 token_id, attention mask 등의 출력으로 나오고
DataCollator의 경우 모델 훈련 루프 내에서 사용되며, 토큰화된 샘플들의 리스트가 입력되어 Tensor 형태의 배치로 출력된다.
Tokenizer에서 별도로 패딩을 하지 않더라도, DataCollator에서 패딩을 넣을 수 있다.
data_collator = DataCollatorForSeq2Seq(
tokenizer,
model=model,
padding=True,
max_length=512
)