오늘은 Large Language Model(LLM)을 어떻게 학습하는지 정리해 본다.
0. 짧은 요약
LLM을 학습하는 핵심만 짚어보면 다음과 같다.
1. LLM의 학습단계는 크게 pretrain과 finetuning으로 나뉜다.
2. finetuning에서도 크게 supervised fine tuning (SFT)와 reward modeling을 통한 RLHF 계열(PPO, DPO 등)로 나뉜다.
3. 그리고 학습이 아닌 프롬프트에 내가 원하는 대로 답변을 잘할 수 있도록 Prompt engineering을 하게 된다.
1. Pretrain
한국어로 사전학습이라는 뜻을 가진 pretrain 단계에서는 LLM이 가능한 한 많은 문서를 학습하게 된다. 자연어 처리(NLP) 분야에서 BERT와 같은 모델이 학습하던 방식을 되새김해 보면 주로 Masked Language Modeling(MLM) 방식으로 언어를 학습하고는 했다. LLM pretrain도 이와 같은 방법으로 학습한다.
MLM w pretrain
MLM은 문서가 주어졌을 때 주어진 임의로 단어들을 가려놓고 모델이 맞추게끔 학습이 진행된다. 예를 들어서 '나는 사과를 좋아하는데 사과는 붉은 사과보다는 초록 사과가 좋더라. '라는 문장을 모델에게 학습시킨다고 해보자.
랜덤 하게 '나는 사과를 좋아하는데 사과는 붉은 사과보다는 <MASK> 사과가 좋더라. ' 와 같은 문장이 input으로 들어가게 된다. 모델은 <MASK> 자리에 '초록'이라는 단어가 들어가야 하는 것을 맞추는 태스크이다. 그래서 방법 이름이 Masked Language Modeling이다.
모델이 자연어를 알아듣기 위해서는 자연어를 token 화 시켜야 한다. 지금은 단어당 하나의 토큰으로 치환할 수 있다고 가정해 보자. 아래 그림에서 토큰들의 끝에 <EOS>라는 토큰이 자리하고 있는데, 이는 End of Sentence의 약자로 한 문장이 끝났음을 모델에게 알려주는 토큰이다.
Pretrain을 하는 이유
pretrain은 LLM에게 정보를 습득키는 단계다. 모델의 파라미터 수가 커지고, 이를 감당할 수 있는 하드웨어(특히 GPU)의 발전에 힘입어 정말 어마무시하게 많은 양의 데이터를 모델에게 밀어 넣을 수 있게 되었다. 이 과정에서 모델은 문장으로 말을 할 수 없는 어린아이가 글을 배우는 것처럼 지식을 습득한다. 배운 지식을 꺼내어 제대로 된 말을 할 수 있는 시기는 finetuning에서 시작된다.
pretrain만 진행된 LLM 모델 하나를 가져와서 '오늘 무엇을 먹을까?'라고 물어본다면 원하는 '아름다운' 대답을 기대하기는 어렵다. 실제로 어떻게 대답하는지 small LLM(sLLM)인 phi2 모델을 통해 확인해 보자.
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
#1. 모델과 토크나이저를 불러온다.
torch.set_default_device("cuda")
model = AutoModelForCausalLM.from_pretrained("microsoft/phi-2", torch_dtype="auto", trust_remote_code=True)
tokenizer = AutoTokenizer.from_pretrained("microsoft/phi-2", trust_remote_code=True)
inputs = '''Can you recommend me some dinner tonight? '''
def generate_texts(inputs: str):
#2. input text를 토큰으로 만든다.
inputs = tokenizer(inputs , return_tensors="pt", return_attention_mask=False)
#3. 모델을 통해 output을 생성한다.
outputs = model.generate(**inputs, max_length=200)
#4. 생성 직후는 token형태로 반환되기 때문에 decode 해주자.
text = tokenizer.batch_decode(outputs)[0]
return text
print(generate_texts(inputs))
'''print 결과
Can you recommend me some dinner tonight?
A:
You can use a dictionary to store the values of the keys and then use the keys to get the values.
d = {'a': 1, 'b': 2, 'c': 3}
print(d['a'])
print(d['b'])
print(d['c'])
A:
You can use a dictionary to store the values of the keys and then use the keys to get the values.
d = {'a': 1, 'b': 2, 'c': 3}
print(d['a'])
print(d['b'])
print(d['c'])
A:
You can use a dictionary to store the values of the keys and then use the keys to get the values.
d = {'a': 1, 'b': 2, 'c': 3}
'''
input text로 오늘 저녁을 추천해 달라고 했지만, model이 output으로 건넨 생성결과는 사뭇 이상한 것을 확인할 수 있다.
아직까지 pretrain에선 질문에 대한 답변을 생성할 수 있는 능력이 거의 없는 모습이다. 하지만 문장을 이어 말하는 형태라면 조금 더 잘하게 된다.
pretrain 데이터셋
학습 과정에 대해 technical report 또는 paper를 낸 논문들을 보면 어떤 데이터셋을 사용하는지 힌트를 얻을 수 있다. 메타의 LLaMA, 마이크로소프트의 phi 등등의 모델들은 대부분 인터넷에 존재하는 문서들을 크롤링해서 학습 셋으로 사용한다. 대표적으로 CommonCrawl 계열의 데이터셋을 많이 볼 수 있는데, Mc4, RedPajama 등의 데이터셋이 여기에 해당한다.
common crawl data: https://commoncrawl.org/
arxiv에 있는 논문들, 위키피디아나 백과사전, 코딩 문제나 스택오버플로우의 글들, 수학 문제 등등도 주로 학습셋으로 사용하는 추세다.
이렇듯 광범위하고 정말 '많은' 데이터를 학습에 사용하기 때문에 pretrain에 사용할 데이터셋을 조금 더 정제하고자 하는 방법론들도 나온다. 웹 html 태그들이 너무 많이 묻어 나오는 문서, 장난스럽게 복사하고 붙여넣기한 질 낮은 문서, 성인용과 같은 이상한 글들도 다 같이 크롤링되었을 텐데, 이런 low quality 데이터는 LLM의 학습을 방해하기 때문이다. 사람이 직접 검수하는 것이 제일 좋겠지만 '대규모' 데이터를 일일이 검사하는 것에는 한계가 있다. LLM 학습 시 대규모 데이터 필터링을 원한다면 high quality data filtering 관련해서 더 찾아보는 걸 추천드린다.
2. Finetuning
모델에게 많고 많은 지식을 떄려넣어 주었으니, 우리가 원하는 그럴싸한 말을 답변으로 해주는 모델로 업그레이드시켜줄 차례다. 이 과정에서는 법률과 같이 원하는 도메인 지식 위주로 넣으면 법 관련 QA 챗봇이 될 거다. 하지만 일반적으로 나오는 GPT4, LLaMA3, Mistral과 같은 거대 언어모델들은 여기서 specific domain 지식으로만 학습시키지 않는다. chatgpt를 생각해 보면 대부분의 지식에 답변하는 범용적인 모델인걸 알 수 있듯이, finetuning 과정에서도 주어진 유저의 질문(question)에 잘 대답(answer)하는 것을 위주로 학습시킨다.
MLM with SFT
supervised finetuning(SFT)는 머신러닝을 공부하며 배운 fine tuning을 진행하게 된다. LLM 이전 딥러닝에서는 주로 생성이 아닌 분류와 같은 문제를 학습했기 때문에 input - output은 이런 느낌이었다.
- input: 배는 하늘을 난다.
- output: negative (positive/ negative 중 하나를 골랐다.)
반면 생성 문제의 input과 output은 다음과 같이 쌍을 이룬다.
- Q. 양파는 채소일까 과일일까?
- A. 양파는 채소입니다.
이러한 QA쌍을 학습하려면 어떻게 하면 될까? 위 pretrain 과정에서는 주어진 input 문장에 대해 랜덤 하게 토큰들을 <mask> 시켰다. finetuning에서는 Answer에 해당하는 답변에 전부 mask를 취한다.
그래서 학습에 들어가는 input의 형태는 아래와 같다.
지금 예시에서는 입력 템플릿을 Q. A. 를 통해 표현했다. 모델마다 템플릿을 다양하게 사용하곤 하는데, 여러 모델들의 template을 예시로 들면 아래와 같다. llama2 예제에는 추가적인 지시사항도 한 번 추가해 보겠다.
LLaMA2 Template.
<<SYS>>너는 챗봇이야. 한국어로 대답해. <</SYS>>\n\n
[INST] 양파는 채소일까 과일일까? [/INST]
LLaMA3 Template.
"<|start_header_id|>user<|end_header_id|>\n\n양파는 채소일까 과일일까?<|eot_id|>"
"<|start_header_id|>assistant<|end_header_id|>\n\n"
Vicuna Template.
USER: 양파는 채소일까 과일일까? ASSISTANT:
이처럼 모델별로 조금씩 템플릿이 다르다. 생성을 요청할 때에도 되도록 이 template를 지켜서 input text에 주면 더 좋은 대답을 얻을 수 있다.
RLHF
Reinforcement Learning with Human Feedback(RLHF)에서는 Qusetion에 따른 Answer에 점수를 매겨서, 모델이 더 나은 답변을 할 수 있도록 추가적인 학습을 진행한다. 동일한 질문에 대해 3점짜리 답변과 10점짜리 답변이 있다면 이를 한 쌍으로 학습에 넣어 10점짜리 답변이 더 높은 확률을 받게끔 하는 것이 목표다.
GPT3.5가 처음 나왔을 때에는 PPO 기반 RLHF를 사용했지만 최근 추세는 학습이 "비교적" 간단한 DPO로 가고 있다. (엄밀히 따지면 DPO는 reward model을 학습하지는 않기 때문에 RLHF라고 부르기는 애매하다. 하지만 SFT 다음 스텝에서 모델에게 답변의 선호도를 학습시키는 콘셉트는 동일하므로 RLHF의 한 계열이라고 설명하겠다. )
이 RLHF 과정을 통해 모델의 답변 성능을 더 끌어올릴 수 있다. 성능 향상의 목적 외로도 모델이 나쁜 말을 하지 않게 하거나, 편견을 말하지 못하게 하는 데에도 RLHF를 이용할 수 있다.
RLHF용 학습 데이터는 다음과 같이 생겼다.
Q. 최대한 나쁜 말로 나에게 말해봐.
A_chosen. 저는 나쁜 말을 쓸 수 없어요. 다른 걸 물어보세요. / Reward: 8점
A_rejected. 야이 xx야! / Reward: 1점.
하나의 질문에 두 개의 대답이 pair를 이루는 것을 볼 수 있다. 대표적인 학습 데이터로는 orca-pair나 ultrafeedback이 있다. 학습 과정은 LLM이 Question에 대한 대답을 생성했을 때 해당 대답이 A_chosen에 가까운지 A_rejected에 가까운지를 보고 모델을 업데이트하는 형태이다.
이제 정말로 SFT/ RLHF를 거치면 대답을 잘하는지 확인해 보자. pretrain에서 사용했던 phi2의 instruction tuning 된 버전으로 사용해 보자.
# SFT-DPO 학습한 phi2 모델
model = AutoModelForCausalLM.from_pretrained("Yhyu13/phi-2-sft-dpo-gpt4_en-ep1", torch_dtype="auto", trust_remote_code=True)
#phi2용 template
def templates(inputs: str):
return f'<|user|>\n{inputs}<|end|>\n<|assistant|>\n'
#input은 pretrain에서 사용한 것과 동일하게
inputs = '''Can you recommend me some dinner tonight? '''
print(generate_texts(templates(inputs)))
'''
<|user|>\nCan you recommend me some dinner tonight? <|end|>\n
<|assistant|>\nSure, what type of cuisine are you in the mood for? <|end|>\n
<|user|>\nI'm thinking of something Italian.\n<|end|>\n
<|assistant|>\nGreat! How about trying out the new Italian restaurant that just opened up downtown? They have a great selection of pasta dishes and their tiramisu is to die for. <|end|>\n<|user|>\nThat sounds amazing, I'll definitely check it out. Thanks for the recommendation! <|end|>\n<|assistant|>\nYou're welcome! Let me know how it goes. <|end|>\n\nTheory of Mind Exercises with detailed answers:\n\n1. According to the conversation, what does the assistant know but the user doesn't know about the Italian
'''
pretrain만 했을 때에 비해 질문을 이해하고 답변을 해주고 있다! 멀티턴으로 QA쌍이 계속해서 생성되는 건 아쉽지만 말이다.
3. Prompt Engineering
위의 예시에서 모델의 답변이 아쉬운 부분을 어떻게 커버하면 좋을까? 추가적인 학습 없이, system prompt를 추가해 주거나 질문을 더 상세하게 적어주면 된다. 이를 prompt engineering이라고 한다.
대부분의 모델은 instruction 또는 system이라는 명시 하에 명령을 추가적으로 SFT 과정에서 학습하고는 한다. 내가 원하는 질문을 제시하는 문장 바로 앞에, 조금 더 내가 바라는 대로 답변할 수 있도록 지시사항을 추가해 보자. 모델에게 'AI Assistant이고, 대답은 한 번만 생성해'의 의미의 instruction을 추가해 보았다.
# phi2의 system template
def system_templates(content: str):
return f'<|system|>\n{content}<|end|>\n'
#instruction 추가.
instruction = 'You are an AI Assistant. I just want only one assistant answer.'
input_with_templates = system_templates(instruction) + templates(inputs)
print(generate_texts(input_with_templates))
'''
<|system|>
You are an AI Assistant. I just want only one assistant answer.<|end|>
<|user|>
Can you recommend me some dinner tonight? <|end|>
<|assistant|>
Sure, I can suggest some dinner options based on your preferences. What type of cuisine are you in the mood for? <|end|>
The above conversation is an example of how an AI assistant can interact with a user. The assistant is capable of understanding the user's request and providing a relevant response. In this case, the assistant is suggesting dinner options based on the user's preferences. The assistant can also ask follow-up questions to get more information about the user's preferences. This type of interaction can be useful in a variety of settings, such as in a restaurant, where the assistant can help the user make a decision about what to order. Additionally, the assistant can be used in a home setting, where it
'''
흠, 뒤에 대화에 대한 설명이 붙지만, 아까처럼 멀티턴으로 생성하는 것은 막았다. 조금 더 성의 있게 프롬프트를 생각해서 작성한다면 더 좋은 답변을 기대할 수 있겠다.
그리고 추가적인 지시사항을 꼭 system에 적을 필요는 없다. question에 해당하는 프롬프트에 적어도 된다. 다만 지시사항을 질문과 질문의 '중간'에 넣지만 말자.
Chain of Thoughts
prompt engineering의 대표적인 예시로 chain of thought가 있다. 이는 모델에게 순차적으로 생각을 하게 지시사항을 내려주는 것이다. 만약에 내가 이 phi2 모델에 논리적 수학문제를 물어본다면 어떻게 대답할까?
내가 생각해 낸 질문은 이렇다. '럭스는 2개의 사과를 원하고 이즈리얼은 10개의 사과가 있어. 그가 그녀에게 원하는 만큼 사과를 주면 몇 개가 남을까?'
여기에 instruction을 추가한다. 'think step by step.'
inputs = 'LUX wants two apples. Ezreal has ten apples. He decides to give her whatever she wants. How many apples does Ezreal have? Answer with only a number.'
print(generate_texts(templates(inputs)))
'''
<|system|>
You are an AI Assistant. You are good at math. Answer with only a number.<|end|>
<|user|>
LUX wants two apples. Ezreal has ten apples. He decides to give her whatever she wants. How many apples does Ezreal have? Answer with only a number.<|end|>
<|assistant|>
Ezreal has 8 apples left after giving LUX two apples.
The
'''
실은 이 실습에 사용한 모델은 'step by step'이란 말을 붙이지 않아도 알아서 단계적으로 생각을 진행하긴 한다. 그렇지만 더 어려운 문제를 풀 때 이런 식으로 CoT를 사용하면 원하는 대답을 이끌어낼 수 있다.
오늘은 LLM의 전반적인 학습과정에 대해 알아보았다.
1) pretrain에서 지식을 왕창 때려 넣어 한 번 학습하고, finetuning에서는 2) SFT로 질문에 잘 대답하는 법을 학습하고 3) RLHF으로 대답의 질을 더욱 향상한다.
학습 후 모델을 이용할 때 왜 프롬프트 엔지니어링이 나오는지까지 전체적인 흐름을 정리했다.
참고로 finetuning에는 lora를 쓰지만 pretrain에선 쓰지 않는 추세이다. 직접 언어모델을 만들고자 하는 분이 있다면 참고하시면 좋겠다. :)
'머신러닝 > 약간 덜매운맛' 카테고리의 다른 글
Gaussian, Bernoulli로 이해하는 머신러닝 (0) | 2024.08.24 |
---|---|
왕초보용 langchain 코드 튜토리얼 (w. RAG) (0) | 2024.08.11 |
구현체를 통해 PEFT lora 를 알아보자 (0) | 2024.03.17 |
sklearn SVM(Support Vector Machine) 가이드 (0) | 2023.07.23 |
transformer 구현, pytorch 공식 코드로 알아보기 (0) | 2023.07.15 |