이번 포스팅의 내용은 https://mari970.tistory.com/61 블로그의 내용과 이어진다.
dataset 가 source 와 target 으로 이루어져 있을 때 p(y|x) 방식으로 학습시킬 수 있는 방안에 대한 방법론이다.
https://github.com/huggingface/transformers/issues/1464
위 링크가 내가 생각했던 문제와 매우 비슷해서 정리한다. (2019 글)
위 issues 에서 화자가 생각하는 question 주제는 모델에 input (”source seq가 포함되어 있는” 이라고 되어있는데, 그건 target 도 같이 넣는다는 의미로 해석) 을 forward 시킬 때 source token 에 대한 loss 는 0 으로 만들어야 하나? 이다.
= 이는 lm_logit 에 포함된 source seq token 을 0 으로 만들어야 하나? 그래서 backward 계산이 되지 않도록?
⇒ 답변자: (가 해본 test 에 따르면) 포함시켜도 상관없다고 한다. Total loss 가 좀 더 커지긴 하겠지만..
<sos> 와 <eos> 토큰 등을 사용해서 src seq = trg seq text ( ‘=’ 을 사용하는 format 이 input 형태라는 뜻) 이 형식만 잘 사용하면 파인튜닝에 문제는 없는 듯하다고 답변한다.
[추가 질문]
(1) <sep> 같은 special token 없으면 추가하라고 warning 뜨잖아 그거 꼭 다 추가해야 하나?
Add a [SOS], [SEP] and [EOS] to the vocabulary (we should train it also!)
tokenizer.add_special_tokens({'start_token': '[CLS]', 'sep_token': '[SEP]', 'end_token': '[EOS]'})
model.resize_token_embeddings(len(tokenizer)) # Update the model embeddings with the new vocabulary size
⇒ 니맘대로~.~ 답변자는 (예를 들면) '[', 'E', 'OS', ']' 이런 식으로 (따로 새로운 토큰을 추가하지 않고) already known token 을 이용해서 prompt 처럼 사용했는데 이란 방법도 효과가 있었다고.
따로 추가하려면 이 새로운 token들에 대한 train 을 따로 해야해서 그렇다.
→ 이거는 나도 고려해봐야겠당
(2) GPT 나 GPT-2 는 right padding 을 지원하지 않는다고.(이게 [pad] 토큰이 없어서 지원을 하지 않는다는 뜻인 것 같다.) fine-tuning 에서 어떻게 해결하였나?
⇒ translation sample 이라고 하면, 랜덤으로 고른 sample (+lables) 들을 concat 해서 1개 input 으로 만든다.
Ex) "[SOS] something in English_#1 = something in French_#1 [EOS] [SOS] something in English_#32 = something in French_#32 [EOS] [SOS] .. etc”
이 후 토큰화하고 max_length 로 truncate 한다. 이렇게 하면 모델이 다양한 길이에 대한 sequence 를 학습할 수 있다.
"[SOS] something in English = " 를 인풋 프롬프트로 주고 [EOS] token이 나오면 생성을 끝낸다.
= 이는 (지금은 존재하지 않는 그 당시의) hf의 transformers 의 examples 의 run_lm_finetuning.py 방법과 같다고 한다.
여기서는 모든 input 을 하나의 긴 문자열로 읽은 후 max_length 로 잘라낸다고.
이때 셔플은 없다. (근데 이건 pre-train 방법 아니었나? 라는 의문이 든다.)
이후 alpaca (decoder-only model) 에서 의 (2) seq2seq 의 방법론을 구체화한 듯 하다.
Alpaca
source string 에서도 next-token-prediction 에 대한 loss 가 발생하기 때문에 Alpaca 에서는 이 loss 를 무시한다.
def preprocess(
sources: Sequence[str],
targets: Sequence[str],
tokenizer: transformers.PreTrainedTokenizer,
) -> Dict:
"""Preprocess the data by tokenizing."""
examples = [s + t for s, t in zip(sources, targets)] # concatenate source and target strings
examples_tokenized, sources_tokenized = [_tokenize_fn(strings, tokenizer) for strings in (examples, sources)]
input_ids = examples_tokenized["input_ids"]
labels = copy.deepcopy(input_ids)
for label, source_len in zip(labels, sources_tokenized["input_ids_lens"]):
label[:source_len] = IGNORE_INDEX # the source string's loss is ignored with IGNORE_INDEX
return dict(input_ids=input_ids, labels=labels)
IGNORE_INDEX 가 어떻게 구현되어있는지는 모르겠지만 source string 은 ignore 되고 있다.
그렇다면 input 과 target 를 어떻게 model 로 하여금 어떻게 구분하도록 할 것인가?
PROMPT_DICT = {
"prompt_input": (
"Below is an instruction that describes a task, paired with an input that provides further context. "
"Write a response that appropriately completes the request.\\n\\n"
"### Instruction:\\n{instruction}\\n\\n### Input:\\n{input}\\n\\n### Response:"
),
"prompt_no_input": (
"Below is an instruction that describes a task. "
"Write a response that appropriately completes the request.\\n\\n"
"### Instruction:\\n{instruction}\\n\\n### Response:"
),
}
위의 예시처럼 ### Response: 를 줘서 target string 을 generate 하도록 한다.
Packing
을 통해 training examples 를 묶어 padding 을 방지할 수 있다.
(source->target)[IGNORE_INDEX](source->target)[IGNORE_INDEX]...(source->target)[IGNORE_INDEX])
위는 causal LM
Input: (source)[IGNORE_INDEX](source)[IGNORE_INDEX]...(source)[IGNORE_INDEX]
Target: (target)[IGNORE_INDEX](target)[IGNORE_INDEX]...(target)[IGNORE_INDEX]
위는 seq2seq 방식이다. (이해는 안됨)
그렇기에 다음 포스팅에서는 alpaca 와 llama, vicuna 코드를 보고 조금 더 알아봐야겠다.
'머신러닝 이모저모' 카테고리의 다른 글
Precision 개념 (0) | 2024.02.18 |
---|---|
VScode 에서 tmux 사용하기 (0) | 2024.01.03 |
UserWarning: CUDA initialization: Unexpected error from cudaGetDeviceCount() 에러 처리 (0) | 2024.01.03 |
Ensemble (앙상블 기법) (0) | 2023.09.08 |
딥러닝 에서의 Bayes’ Rule (1) | 2023.08.09 |