이전 내용
sLLM 학습하기(feat. 구글 코랩)
소형 언어 모델(Smaller Large Language Model)이란 그 이름에서부터 알 수 있듯이, 대형 모델에 비해 매개변수의 수가 수십억 내지 수백억대로 비교적 크기가 작은 언어 모델을 말한다.
소형 언어 모델은 LLM에 비해 비용 효율적이면서 특정 작업 또는 도메인에 특화되었다. 이번에는 자연어 요청으로부터 적합한 SQL(Structured Query Language)을 생성하는 Text2SQL(NL2SQL) sLLM을 만들어 본다.
※ SQL: 관계형 데이터베이스(Relational Database, RDB)에서 데이터를 생성, 조회, 업데이트, 삭제하기 위해 사용하는 언어.
Text2SQL은 사용자가 얻고 싶은 데이터에 대한 요청을 자연어로 작성하면 LLM이 요청에 맞는 SQL을 생성하는 작업을 말한다.
작업을 진행하기 전에 실습에 필요한 라이브러리를 설치해야 한다.
!pip install transformers==4.40.1 bitsandbytes==0.43.1 accelerate==0.29.3 datasets==2.19.0 tiktoken==0.6.0 -qqq
!pip install huggingface_hub==0.22.2 autotrain-advanced==0.7.77 -qqq
Text2SQL 데이터셋
- 대표적인 Text2SQL 데이터셋: WikiSQL, Spider
SQL을 생성하기 위해서 필요한 데이터
- 어떤 데이터가 있는지 알 수 있는 데이터베이스 정보(테이블, 컬럼)
- 어떤 데이터를 추출하고 싶은지 나타낸 요청사항(request, question)
◆ WikiSQL
WikiSQL은 자연어 쿼리와 SQL 쿼리 간의 변환을 목표로 하는 데이터베이스 질의 생성을 위한 데이터셋이다.
- 데이터셋 구성: WikiSQL 데이터셋은 약 80,000개의 자연어 질문과 이에 대응하는 SQL 쿼리로 이루어져 있다. 질문은 다양한 형태와 복잡성을 가지고 있어, 실제 환경에서 사용되는 질문을 반영하고 있다.
- 목표: 주어진 자연어 질문에 대해 적절한 SQL 쿼리를 생성하는 것이며, 이를 통해 데이터베이스와 상호작용하면서 필요한 정보를 쉽게 검색할 수 있다.
- 구조: 자연어 질문은 여러 가지 SQL 쿼리 유형에 변환될 수 있으며, 이에는 SELECT, WHERE 절 등이 포함된다. 데이터는 주로 테이블 형식으로 제공된다.
- 응용 분야: WikiSQL은 데이터베이스 질의 생성, 자연어 처리(NLP), 그리고 인공지능 기반의 데이터 분석 도구 개발 등에 활용된다.
◆ Spider
Spider는 데이터베이스 질의를 위한 자연어 처리 모델 학습을 위한 또 다른 데이터셋이다. Spider는 더 복잡한 구조와 다양한 데이터베이스 스키마를 다룬다.
- 데이터셋 구성: Spider 데이터셋은 2000개 이상의 자연어 질문과 이에 상응하는 SQL 쿼리로 이루어져 있다. 이 데이터셋은 다양한 데이터베이스 스키마를 사용하여 질문의 복잡성을 높이고 있다.
- 다양한 스키마: Spider 데이터셋은 여러 개의 데이터베이스와 테이블 구조를 포함하고 있어, 모델이 서로 다른 스키마에 대해 일반화할 수 있도록 도와준다.
- 도전 과제: Spider는 다양한 SQL 기능을 포함하고 있어, 단순한 쿼리 생성을 넘어 복잡한 쿼리와 변환 작업도 요구한다. 이는 모델의 성능을 평가하는 데 필수적인 요소다.
- 적용 가능성: Spider는 데이터베이스의 복잡한 질의 생성 및 응답을 위한 연구와 개발에 널리 사용되며, 다양한 NLP 모델의 벤치마크로 활용되고 있다.
성능 평가 파이프라인 준비하기
머신러닝 모델을 학습시킬 때는 학습이 잘 진행된 것인지 판단할 수 있도록 성능 평가 방식을 미리 정해야 한다. 여기서는 일반적으로 사용되는 Text2SQL 평가 방식을 사용하지 않고 GPT-4를 사용해 생성된 SQL이 정답인지 판단하는 방식을 사용해 본다. GPT-4와 같이 뛰어난 성능의 LLM을 평가자로 활용하면 빠르게 평가를 수행하면서도 신뢰할 수 있는 평가 결과를 기대할 수 있다.
GPT를 활용한 성능 평가 파이프라인을 준비하기 위해 필요한 세 가지
- 평가 데이터셋
- LLM이 SQL을 생성할 때 사용할 프롬프트
- GPT 평가에 사용할 프롬프트와 GPT-4 API 요청을 빠르게 수행할 수 있는 코드
◆ 평가 데이터셋 구축
기존에 있는 대표성있는 한국어 데이터셋을 활용하면 좋으나, 현재 활용할 수 있는 데이터셋이 없어 책 저자가 허깅페이스에 올려둔 데이터셋을 활용한다.
https://huggingface.co/datasets/shangrilar/ko_text2sql
이 데이터셋은 8개 데이터베이스에 대해 생성했다. 모델의 일반화 성능을 확인하기 위해 7개의 데이터베이스 데이터는 학습에 사용하고 1개의 데이터베이스는 평가에 사용한다.
db_id가 1인 데이터는 게임 도메인을 가정하고 만든 데이터베이스이며, 게임 도메인의 경우 테이블 이름이 다른 도메인과 달리 플레이어(player), 퀘스트(quests), 장비(equipments)와 같이 특화된 이름을 사용하므로 db_id가 1인 데이터를 평가 데이터셋으로 활용한다.
GPT-4를 활용해 평가를 수행할 것이기 때문에 발생하는 비용을 줄이면서 실습을 수행할 수 있도록 100개 내외의 데이터만 사용한다.
◆ SQL 생성 프롬프트
LLM의 경우 학습에 사용한 프롬프트 형식을 추론할 때도 동일하게 사용해야 결과 품질이 좋다. 프롬프트는 make_prompt 함수를 통해 생성한다.
# SQL 프롬프트
def make_prompt(ddl, question, query=''):
prompt = f"""당신은 SQL을 생성하는 SQL 봇입니다. DDL의 테이블을 활용한 Question을 해결할 수 있는 SQL 쿼리를 생성하세요.
### DLL:
{ddl}
### Question:
{question}
### Query:
{query}"""
return prompt
앞의 make_prompt 함수를 사용해 생성한 예시 데이터는 다음과 같다.
# 프롬프트 데이터 예시
# 학습 데이터 예시
"""당신은 SQL을 생성하는 SQL 봇입니다. DDL의 테이블을 활용한 Question을 해결할 수 있는 SQL 쿼리를 생성하세요.
### DLL:
CREATE TABLE messages(
"message_id" SERIAL PRIMARY KEY,
"conversation_id" INT NOT NULL,
"sender_id" INT NOT NULL,
"context" TEXT,
"timestamp" TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
"read" BOOLEAN DEFAULT FALSE,
FOREIGHN KEY ("conversation_id") REFERENCES conversations("conversation_id"),
FOREIGHN KEY ("sender_id") REFERENCES users("user_id")
);
### Question:
meesages 테이블에서 모든 데이터를 조회해 줘
### SQL:
SELECT * FROM messages;"""
# 생성 프롬프트 예시
"""당신은 SQL을 생성하는 SQL 봇입니다. DDL의 테이블을 활용한 Question을 해결할 수 있는 SQL 쿼리를 생성하세요.
### DLL:
CREATE TABLE messages(
"message_id" SERIAL PRIMARY KEY,
"conversation_id" INT NOT NULL,
"sender_id" INT NOT NULL,
"context" TEXT,
"timestamp" TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
"read" BOOLEAN DEFAULT FALSE,
FOREIGHN KEY ("conversation_id") REFERENCES conversations("conversation_id"),
FOREIGHN KEY ("sender_id") REFERENCES users("user_id")
);
### Question:
meesages 테이블에서 모든 데이터를 조회해 줘
### SQL:
"""
◆ GPT-4 평가 프롬프트와 코드 준비
GPT-4를 사용해 평가를 수행한다면 반복적으로 GPT-4 API 요청을 보내야 한다. 만약 100개의 평가 데이터를 사용하면, for 문을 통해 반복적인 요청을 수행해도 시간이 오래 걸리지 않지만 평가 데이터셋을 더 늘린다면 단순 for 문 만으로는 시간이 오래 걸린다. 그럴 때는 OpenAI가 openai-cookbook 깃허브 저장소에서 제공하는 코드를 활용하면 요청 제한을 관리하면서 비동기적으로 요청을 보낼 수 있다.
해당 코드는 요청을 보낼 내용을 저장한 jsonl 파일을 읽어 순차적으로 요청을 보내며, 중간에 에러가 발생하거나 요청 제한에 걸리면 다시 요청을 보내서 결과의 누락도 막아준다.
make_requests_for_gpt_evaluation 함수는 평가 데이터셋을 읽어 GPT-4 API 요청을 보낼 jsonl 파일을 생성할 때 사용한다.
# 평가를 위한 요청 jsonl 작성 함수
def make_requests_for_gpt_evaluation(df, filename, dir='requests'):
if not Path(dir).exists():
Path(dir).mkdir(parents=True)
prompts = []
for idx, row in df.iterrows():
prompts.append("""Based on below DDL and Question, evaluate gen_sql can resolve Question.
If gen_sql and gt_sql do equal job, return "yes" else retrun "no". Ouput JSON Format: {"resolve_yn": ""}""" + f"""
DDL: {row['context']}
Question: {row['question']}
gen_sql: {row['gen_sql']}
gt_sql: {row['gt_sql']}
""")
jobs = [{"model": "gpt-4-turbo-preview", "response_format" : {"type": "json_object"}, "messages": [{"role": "system", "content": prompt}]} for prompt in prompts]
with open(Path(dir, filename), "w") as f:
for job in jobs:
json_string = json.dumps(job)
f.wirte(json_string + "\n")
- 프롬프트에서는 DLL과 Question을 바탕으로 LLM이 생성한 SQL(gen_sql)이 정답 SQL(gt_sql)과 동일한 기능을 하는지 평가
- 판단 결과는 resolve_yn이라는 키에 "yes" 또는 "no"의 텍스트가 있는 JSON 형식으로 반환
- 생성한 평가 프롬프트는 사용할 모델 이름을 지정해 요청 작업(jobs) 변수에 저장
- 지정한 디렉토리 경로와 파일 이름으로 요청 정보를 jsonl 파일 형태로 저장
생성한 jsonl 파일은 파라미터로 지정한 {dir}/{filename} 위치에 저장된다.
OpenAI 쿡북의 비동기 요청을 위해 아래 코드를 실행한다.
import os
from google.colab import userdata
os.environ["OPENAI_API_KEY"] = userdata.get('OPENAI_API_KEY')
import os
from google.colab import userdata
os.environ["OPENAI_API_KEY"] = userdata.get('OPENAI_API_KEY')
openai_api = userdata.get('OPENAI_API_KEY')
!python api_request_parallel_processor.py \
--requests_filepath {요청 파일 경로} \
--save_filepath {생성할 결과 파일 경로} \
--request_url https://api.openaai.com/v1/chat/competitions \
--max_requests_per_munute 300 \
--max_tokens_per_minute 100000 \
--token_encoding_name cl100k_base \
--max_attempts 5 \
--logging_level 20
이번에는 GPT-4에 요청을 전달해 반환된 평가 결과를 읽어와 csv 파일로 변환하는 change_jsonl_to_csv 함수를 정의한다.
# 결과 jsonl 파일을 csv 파일로 변환하는 함수
def change_jsonl_to_csv(input_file, output_file, prompt_column="prompt", response_column="response"):
prompts = []
responses = []
with open(input_file, "r") as json_file:
for data in json_file:
prompts.append(json.loads(data)[0]['messages'][0]['content'])
responses.append(json.loads(data)[1]['choices'][0]['message']['content'])
df = pd.DataFrame({prompt_column: prompts, response_column: responses})
df.to_csv(output_file, index=False)
return df
- 평가 결과는 앞서 make_requests_for_gpt_evaluation 함수에서 save_file 인자에 지정한 경로에 저장되는데, chage_jsonl_to_csv 함수는 결과 파일을 불러와 프롬프트와 판단 결과 데이터를 각각 prompts, responses 변수에 저장
- 저장한 데이터는 pd.DataFrame 클래스를 사용해 판다스 데이터프레임으로 만들고 to_csv() 메서드를 사용해 csv 파일로 저장
미세 조정 수행하기
◆ 기초 모델 평가하기
예시 데이터를 입력했을 때 기초 모델이 어떤 결과를 생성하는지 확인해 본다.
아래의 코드를 실행하면 기초 모델을 불러와 프롬프트에 대한 결과를 생성한다.
# 기초 모델로 생성하기
import torch
from transformers import pipeline, AutoTokenizer, AutoModelForCausalLM
from huggingface_hub import login
token = userdata.get('HUGGINGFACE_TOKEN')
login(token=token)
def make_inference_pipeline(model_id):
tokenizer = AutoTokenizer.from_pretrained(model_id)
model = AutoModelForCausalLM.from_pretrained(model_id, device_map="auto", load_in_4bit=True, bnb_4bit_compute_dtype=torch.bfloat16)
pipe = pipeline("text-generation", model=model, tokenizer=tokenizer)
return pipe
model_id = "beomi/Yi-Ko-6B"
hf_pipe = make_inference_pipeline(model_id)
example = """당신은 SQL을 생성하는 SQL 봇입니다. DDL의 테이블을 활용한 Question을 해결할 수 있는 SQL 쿼리를 생성하세요.
### DLL:
CREATE TABLE players(
player_id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(255) UNIQUE NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL,
date_joined DATETIME NOT NULL,
last_login DATETIME
);
### Question:
사용자 이름에 'admin'이 포함되어 있는 계정의 수를 알려주세요.
### SQL:
"""
hf_pipe(example, do_sample=False,
return_full_text=False, max_length=1024, truncation=True)
# SELECT COUNT(*) FROM players WHERE username LIKE '%admin%';
# ### SQL 봇:
# SELECT COUNT(*) FROM players WHERE username LIKE '%admin%';
# ### SQL 봇 결과:
# SELECT COUNT(*) FROM players WHERE username LIKE '%admin%';
- make_inference_pipeline 함수는 입력한 모델 아이디에 맞춰 토크나이저와 모델을 불러오고 하나의 파이프라인으로 만들어 반환
- make_inference_pipeline 함수를 사용해 beomi/Yi-Ko-6B 모델로 파이프라인을 맏늘고 hf_pipe 변수에 저장
- example 데이터를 hf_pipe에 입력하고 결과 확인
※ Yi-Ko 시리즈 모델은 01-ai/Yi 모델의 고급 반복으로, 확장된 어휘와 추가 사전 학습에 포함된 한국어/영어 코퍼스의 이점을 얻는다. 이전 모델과 마찬가지로 Yi-Ko 시리즈 모델은 60억에서 340억 개의 매개변수에 이르는 광범위한 생성 텍스트 모델 내에서 작동한다. 이 저장소는 Hugging Face Transformers 형식에 맞게 조정된 6B 사전 학습 버전에 중점을 둔다.
beomi/Yi-Ko-6B 모델은 중국의 01.AI가 발표한 영어-중국어 모델인 01-ai/YI-6B를 한국어에 확장한 모델로, 현재 한국어 LLM 리더보드에서 사전 학습 모델 중 인기가 가장 많은 모델 중 하나다.
https://huggingface.co/beomi/Yi-Ko-6B
다음으로 평가 데이터셋에 대한 SQL 생성을 수행하고, GPT-4를 사용해 평가한다.
# 기초 모델 성능 측정
from datasets import load_dataset
# 데이터셋 불러오기
df = load_dataset("shangrilar/ko_text2sql", "origin")['test']
df = df.to_pandas()
for idx, row in df.iterrows():
prompt = make_prompt(row['context'], row['question'])
df.loc[idx, 'prompt'] = prompt
# sql 생성
gen_sqls = hf_pipe(df['prompt'].tolist(), do_sample=False,
return_full_text=False, max_length=1024, truncation=True)
gen_sqls = [x[0]['generated_text'] for x in gen_sqls]
df['gen_sql'] = gen_sqls
# 평가를 위한 requests.jsonl 생성
eval_filepath = "text2sql_eval.jsonl"
make_requests_for_gpt_evaluation(df, eval_filepath)
# GPT-4 평가 수행
!python api_request_parallel_processor.py \
--requests_filepath requests/{eval_filepath} \
--save_filepath results/{eval_filepath} \
--request_url https://api.openaai.com/v1/chat/competitions \
--max_requests_per_munute 2500 \
--max_tokens_per_minute 100000 \
--token_encoding_name cl100k_base \
--max_attempts 5 \
--logging_level 20
◆ 미세 조정 수행
기초 모델의 성능을 더 개선할 여지가 있어 준비한 학습 데이터로 미세 조정을 수행해 본다.
모델의 미세 조정을 위해 autotrain-advanced 라이브러리를 사용하는데, 이 라이브러리는 허깅페이스에서 trl 라이브러리를 한 번 더 추상화해 개발한 라이브러리다.
# 학습 데이터 불러오기
from datasets import load_dataset
df_sql = load_dataset("shangrilar/ko_text2sql", "origin")['train']
df_sql = df_sql.to_pandas()
df_sql = df_sql.dropna().sample(frac=1, random_state=42)
df_sql = df_sql.query("db_id != 1")
for idx, row in df_sql.iterrows():
df_sql.loc[idx, 'text'] = make_prompt(row['context'], row['question'], row['answer'])
!mkdir data
df_sql.to_csv('data/train.csv', index=False)
- 먼저 학습 데이터를 내려 받고
- 데이터셋에서 평가에 사용하기로 한 db_id가 1인 데이터를 제거
- make_prompt 함수를 사용해 학습에 사용할 프롬프트를 생성
- data 폴더에 저장
그리고 autotrain-advanced 라이브러리를 사용해 지도 미세 조정을 수행하는데, 이때 기초 모델과 미세 조정 모델 이름을 적절히 지정한다. 데이터 경로는 학습 데이터를 저장한 data 폴더, 사용할 컬럼은 프롬프트가 저장된 text 컬럼으로 지정한다.
# 미세 조정 명령어
base_model = 'beomi/Yi-Ko-6B'
finetuned_model = 'yi-ko-6b-text2sql'
!autotrain llm \
--train \
--model {base_model} \
--project-name {finetuned_model} \
--data-path data/ \
--text-column text \
--lr 2e-4 \
--batch-size 4 \ # 메모리 에러 발생 예방을 위해 줄임
--epochs 1 \
--block-size 1024 \
--warmup-ratio 0.1 \
--lora-r 16 \
--lora-alpha 32 \
--lora-dropout 0.05 \
--weight-decay 0.01 \
--gradient-accumulation 8 \
--mixed-precision fp16 \
--use-peft \
--quantization int4 \
--trainer sft
▶ 모델 학습 과정에서 메모리 에러가 발생할 경우 batch_size를 줄여서 실행해 본다.
학습을 마친 후에는 LoRA 어댑터와 기초 모델을 합치고 허깅페이스 허브에 모델을 저장한다.
# LoRA 어댑터 결합 및 허깅페이스 허브 업로드
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
from peft import PeftModel, LoraConfig
from huggingface_hub import login
model_name = "beomi/Yi-Ko-6B"
new_model = "yi-ko-6b-text2sql"
device_map = {"": 0}
# LoRA와 기초 모델 파라미터 합치기
base_model = AutoModelForCausalLM.from_pretrained(
model_name,
low_cpu_mem_usage=True,
return_dict=True,
torch_dtype=torch.float16,
device_map=device_map,
)
model = PeftModel.from_pretrained(base_model, new_model)
model = model.merge_and_unload()
# 토크나이저 설정
tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True)
tokenizer.pad_token = tokenizer.eos_token
tokenizer.padding_side = "right"
# 허깅페이스 허브에 모델 및 토크나이저 저장
model.push_to_hub(new_model, use_temp_dir=False)
tokenizer.push_to_hub(new_model, use_temp_dir=False)
학습한 모델의 성능을 확인하기 전에 앞서 살펴본 예시 데이터에 대해 다시 SQL을 생성해 본다.
모델 ID만 새로 업로드한 모델의 정보로 변경하고 동일한 코드를 실행한다.
# 미세 조정한 모델로 예시 데이터에 대한 SQL 생성
model_id = "shangrila/yi-ko-6b-text2sql"
hf_pipe = make_inference_pipeline(model_id)
hf_pipe(example, do_sample=False,
return_full_text=False, max_length=1024, truncation=True)
# SELECT COUNT(*) FROM players WHERE username LIKE '%admin%';
이제 학습한 모델에 대한 평가를 수행한다.
# 미세 조정한 모델 성능 측정
# sql 생성
gen_sqls = hf_pipe(df['prompt'].tolist(), do_sample=False,
return_full_text=False, max_length=1024, truncation=True)
gen_sqls = [x[0]['generated_text'] for x in gen_sqls]
df['gen_sql'] = gen_sqls
# 평가를 위한 requests.jsonl 생성
eval_filepath = "text2sql_evaluation_finetuned.jsonl"
make_requests_for_gpt_evaluation(df, eval_filepath)
# GPT-4 평가 수행
!python api_request_parallel_processor.py \
--requests_filepath requests/{eval_filepath} \
--save_filepath results/{eval_filepath} \
--request_url https://api.openaai.com/v1/chat/competitions \
--max_requests_per_munute 2500 \
--max_tokens_per_minute 100000 \
--token_encoding_name cl100k_base \
--max_attempts 5 \
--logging_level 20
◆ 학습 데이터 정제와 미세 조정
# 필터링한 데이터 불러오기
clean_dataset = load_dataset("shangrilar/ko_text2sql", "clean")['train']
다음 내용
[출처]
LLM을 활용한 실전 AI 애플리케이션 개발
https://www.igloo.co.kr/security-information
'[파이썬 Projects] > <파이썬 Gen AI, LLM>' 카테고리의 다른 글
[LLM] 모델 가볍게 만들기 (0) | 2025.01.02 |
---|---|
[LLM] GPU 효율적인 학습 (0) | 2024.12.30 |
[LLM] 텍스트 분류 모델 학습시키기 (2) | 2024.12.27 |
[LLM] 허깅페이스 라이브러리 사용법 익히기 (4) | 2024.12.27 |
[Gen AI] 스테이블 디퓨전 API (이 아닌 stability.ai API 사용법) (2) | 2024.12.22 |