[DeepTecTok #2] 번역 특화 모델(LLM)의 미세조정을 통한 모델 향상 사례
- TecAce Software
- May 21, 2024
- 5 min read

번역 전문 LLM 파인튜닝 사례
들어가기
거대언어모델 (Large Language Model; LLM) 기술이 날로 발전하고 있다. LLM을 활용하는 방법은 여러가지가 있으며 활용하기 위한 기본 방법으로는 프롬프팅, 임베딩, 파인튜닝 등이 있다. 이 글에서는 이 중에 GPU 컴퓨팅 리소스를 많이 필요로 하는 파인튜닝 방법을 다루고자 한다.
고성능 컴퓨팅 리소스는 디바이스로 확보해서 사용할 수도 있지만 클라우드 환경에서 사용한다면 좀 더 번거러움을 줄일 수 있는 장점이 있다. 이런 상황에 맞추어 클라우드에서 AI 학습과 추론을 편하게 할 수 있는 특화된 클라우드 서비스들이 많이 나오고 있다. Vessl은 그 중의 하나로 LLM을 비롯한 생성AI를 다루는 부분도 기본적으로 고려되고 있는 서비스이다[1]. 이 글에서는 LLM 중의 하나인 M2M100 모델을 Vessl 허브를 이용해 파인튜닝한 사례를 다루고자한다[2,3].
M2M100은 메타에서 만든 트랜스포머 기반 LLM이다. 인코더와 디코더가 같이 있으며 100개 언어를 상호 번역하는 것을 지원하는 다중 언어 번역 모델이다.

Vessl에서 AI 사용 환경 만들기
Vessl 사용법은 아래 관련 사이트에서 설명하고 있다[1]. Vessl 허브로 들어가서 사용할 수 있고 vessl 명령행 툴을 이용해서 사용할 수 있다. 여기에서는 Vessl에 대한 기본적인 사용법보다는 번역 모델을 파인튜닝 하는 방법에 초점을 맞추고 있다.
Vessl 허브에서는 주피터 사용을 기본으로 지원하고 있다. 주피터는 AI 개발자들이 많이 활용하는 코딩 환경이다. 주피터가 있는 AI 환경을 만들려면 아래와 같이 명령하면 된다.
$ poetry shell
$ cd vessl_use
$ vessl run create -f jupyter-notebook.yamlVessl은 파이썬으로 인스톨되는 명령어이기 때문에 Poetry를 이용해 vessl을 사용할 수 있는 환경으로 변환했다. 그리고 Poetry는 사용자 코드가 서브 폴더에 저장되기 때문에 vessl_use로 폴더를 바꾸어 주었다. Vessl 명령은 run으로 실행할 수 있다. create를 이용하면 Vessl의 클라우드 서비스를 사용할 수 있는 환경을 주어진 yaml 파일에 적혀있는데로 만든다.
다음은 주피터를 인터랙티브하게 사용할 수 있는 환경을 구축하는 Vessl용 yaml 파일이다. 이 파일은 하나의 활용 예로 Vessl 홈페이지에서 제공되고 있다.
name: gpu-interactive-run
description: Run an interactive GPU-backed Jupyter and SSH server.
tags:
- interactive
- jupyter
- ssh
resources:
cluster: vessl-gcp-oregon
preset: gpu-l4-small-spot
image: quay.io/vessl-ai/torch:2.1.0-cuda12.2-r3
interactive:
max_runtime: 8h
jupyter:
idle_timeout: 120m리소스 부분에서 cluster는 vessl-gcp-oregon에 있는 걸 사용하고 머쉰 규격에 해당하는 preset은 gpu-l4-small-spot로 정의했다. 운영에 사용할 이미지는 Vessl이 제공하는 Nvidia Cuda와 Pytorch를 기반으로 하는 것을 선택했다. 인터랙티브 환경과 관련해서 최대 사용 시간은 8시간이고 주피터에서 허용하는 최대 유휴 시간은 120분으로 지정했다.
M2M100 파인튜닝
다음 단계를 통해서 M2M100 모델을 파인튜닝을 진행하고 테스트하였다.
단계 1. 패키지 설치하기
파인튜닝을 위해 필요한 다음 패키지를 설치한다.
transformers==4.40.0
sentencepiece==0.2.0
accelerate==0.29.3
datasets==2.19.0여기서 sentencepiece는 언어 독립적인 토큰화를 진행하는 패키지이며 LLM 모델의 Tokenizer에 사용된다. 또한 accelerate는 PyTorch, Tensorflow에 상관없이 CPU, GPU, TPU 등 다양한 하드웨어서 딥러닝 학습의 가속화를 지원하는 패키지이며 사용이 간단하다. 예를 들어 PyTorch의 경우, loss를 구하는 부분을 임포트등 사전 처리가 되었다면 학습 수행 단계에서 loss.backword()에서 accelerator.backward(loss)로 변경으로 가속이 가능하다.
단계 2. 패키지 가져오기
파인튜닝에 필요한 패키지들을 불러온다.
import json
from transformers import (M2M100ForConditionalGeneration, M2M100Tokenizer,
Seq2SeqTrainer, Seq2SeqTrainingArguments)
from datasets import Dataset데이터를 불러오는데 필요한 json 패키지를 가져왔다.
허깅페이스의 transformers 패키지로부터는 M2M100 번역 전용 모델을 다루기 위해 설계된 M2M100ForConditionalGeneration과 해당 토크나이저를 불러올 수 있는 M2M100Tokenize 클래스와 파인튜닝 학습에 필요한 나머지 두 클래스를 포함해 총 4가지 클래스를 불러왔다. M2M100Tokenize 클래스는 100개 이상 언어를 토큰화할 수 있도록 sentencepiece 패키지를 기반으로 구성되어 있다. 반면 Llama2 등 일반적인 디코드 기반 LLM들은 자동화된 AutoModelForCausalLM, AutoTokenizer을 통해 모델과 토크나이저를 불러오게 되어 있다.
데이터 관련된 처리를 할 수 있는 Dataset 클래스를 datasets 패키지에서 불러왔다.
단계 3. M2M 번역을 위한 클래스 만들기
M2M 번역 클래스는 418M 크기의 m2m100 모델과 m2m100용 토크나이저를 가져오도록 초기화하고 관련 멤버 함수를 구성한다.
class M2M:
"""M2M100"""
def __init__(self, ft_fold, src_lang:str="ko", tgt_lang:str="en"):
"""M2M100 model and tokenizer initialization"""
self.model = M2M100ForConditionalGeneration.from_pretrained("facebook/m2m100_418M")
self.tokenizer = M2M100Tokenizer.from_pretrained("facebook/m2m100_418M")
self.src_lang = src_lang
self.tgt_lang = tgt_lang
self.tokenizer.src_lang = src_lang
self.tokenizer.tgt_lang = tgt_lang
def trans(self, input_text:str):
"""Translate input_text from source language to target language"""
encoded_pt = self.tokenizer(input_text, return_tensors="pt")
generated_tokens = self.model.generate(**encoded_pt,
forced_bos_token_id=self.tokenizer.get_lang_id(self.tgt_lang))
output = self.tokenizer.batch_decode(generated_tokens, skip_special_tokens=True)
return output[0]
def print_trans(self, d_source_list: list[str]):
"""Print translation of source text"""
for idx, input_text in enumerate(d_source_list):
output_text = self.trans(input_text)
print(f"{idx+1}. {input_text} ==> {output_text}")
def encode(self, data):
# Assuming tokenizer is already initialized and configured
inputs = self.tokenizer(data['source'], padding="max_length", truncation=True, max_length=128)
outputs = self.tokenizer(data['target'], padding="max_length", truncation=True, max_length=128)
return {
'input_ids': inputs['input_ids'],
'attention_mask': inputs['attention_mask'],
'labels': outputs['input_ids'] # using output input_ids as labels for training
}
def tokenize(self, data_dict: dict):
raw_datasets = Dataset.from_dict(data_dict)
split_datasets = raw_datasets.train_test_split(test_size=0.2) # 80% train, 20% test
tokenized_datasets = split_datasets.map(self.encode, batched=True)
return tokenized_datasets우선 클래스 초기화 과정에서 M2M 번역에 필요한 AI 모델과 토크나이저를 불러왔다. 이제 번역을 수행하는 멤버 함수와 번역 결과를 보여주는 멤버 함수를 각각 trans()와 print_trans()로 만들었다.
파인튜닝 단계에서 토큰화를 진행하는 tokenize()와 여기에 사용되는 encode()를 구성했다. 멤버함수 tokenize()에서는 훈련과 테스트에 80:20으로 데이터를 나누도록 했다. 그리고 encode()에서는 최대 입력과 출력 길이를 128로 한정시켰다. M2M100 모델의 입출력 최대 길이는 1024이지만 모델 복잡도와 응답 시간을 줄이기 위해 더 짧은 길이로 한정을 시켰다. 길어지면 Attention으로 인해 번역의 성능에 문제가 생길 수도 있기 때문에 줄여서 잡는게 번역 성능에도 도움이 된다. 다만, 순서대로 번역되는 문장들간에 문맥이나 단어 사용에 일관성이 없어지는 이슈가 생길 수도 있기 때문에 이를 보완하는 다양한 접근이 고려될 수 있다. 예를 들면 일부가 겹쳐지는 방식의 번역, 번역후에 일관성을 맞춰주는 번역 그리고 용어집을 이용한 번역등이 해결책이 될 수 있다.
파인튜닝 이후에 검증하기 위해 번역하는 함수는 새롭게 만들어진 코드를 통해 수행할 수 있게 별도 함수로 만든다.
def trans(model, tokenizer, input_text:str, src_lang:str="ko", tgt_lang:str="en"):
"""Translate input_text from source language to target language"""
tokenizer.src_lang = src_lang
encoded_pt = tokenizer(input_text, return_tensors="pt")
generated_tokens = model.generate(**encoded_pt,
forced_bos_token_id=tokenizer.get_lang_id(tgt_lang))
# max_length=200, early_stopping=True, num_beams=5)
# generated_tokens = self.model.generate(**encoded_pt)
output = tokenizer.batch_decode(generated_tokens, skip_special_tokens=True)
return output[0]단계 4. 파인튜닝에 사용할 데이터 불러오기
파인튜닝에 필요한 데이터를 불러오는 함수와 어떤 데이터가 들어있는지를 프린트하는 함수를 만든다.
def load_data(data_file:str = 'ft_data/data_m2m.json'):
"""Load data from data_m2m.json"""
with open(data_file, "r", encoding="utf-8") as f:
data_dict = json.load(f)
return data_dict
def print_data_dict(data_dict: dict):
for idx, (input_text, output_text) in enumerate(zip(data_dict["source"], data_dict["target"])):
print(f"{idx}. {input_text} ==> {output_text}")위의 load_data는 Json 포맷으로 들어있는 data를 로드하는 함수이다. 그리고 print_data_dict는 로드한 데이터에 어떤 것이 들어있는지 프린트한다.
파인튜닝을 위한 학습 데이터는 “source”와 “target”으로 구분되어 원문과 번역문 문장들이 다음과 같이 들어있다.
{"source":["안녕", "안녕, 나는 인공지능 로봇이야.", "어떻게 도와줄까?", "나는 인공지능 로봇이야, 너를 도와주기 위해 여기 있어."],
"target":["Hi", "Hi, I am a AI robot.", "How can I help you?", "I am a AI robot, I am here to help you."]}위의 학습데이터는 다음 의도를 가지고 구성하였다. 한글로 안녕은 친근한 표현이므로 Hi로 번역하게 했다. 그리고 인공지능 로봇은 새롭게 사용되는 용어라 AI robot이라 표현되도록 하였다.
단계 5. 파인튜닝 함수 만들기
파인튜닝을 위해서는 필요한 파라메터를 셋딩한 후 학습을 시작하는 과정이 필요하다.
def finetune(m2m:M2M, data_dict:dict, ft_fold:str):
tokenized_datasets = m2m.tokenize(data_dict)
training_args = Seq2SeqTrainingArguments(
output_dir=ft_fold, # Where to store the final model
evaluation_strategy='epoch', # Evaluation is done at the end of each epoch
learning_rate=5e-5,
per_device_train_batch_size=16,
per_device_eval_batch_size=16,
weight_decay=0.01,
save_total_limit=3,
num_train_epochs=3,
predict_with_generate=True
)
trainer = Seq2SeqTrainer(
model=m2m.model,
args=training_args,
train_dataset=tokenized_datasets['train'],
eval_dataset=tokenized_datasets['test'],
tokenizer=m2m.tokenizer
)
trainer.train()
trainer.save_model(ft_fold)
학습률은 5e-5, 학습 횟수는 3회등으로 파라메터를 정했다. 또한 데이터 중에 학습 데이터와 검증 데이터를 나눠서 학습 클래스의 인스턴스를 만들었다. 그리고 학습을 수행하고 그 결과는 정해진 폴더에 넣도록 했다.
단계 6. 파인튜닝 수행하기
지금까지 만든 클래스와 함수들을 이용해 파인튜닝은 다음 절차로 수행한다.
"""Main function for m2m_ft_ahnlab.py"""
FT_FOLD = './ft_fold'
m2m = M2M(FT_FOLD)
data_dict = load_data()
print("\\nBefore finetuning")
m2m.print_trans(data_dict["source"])
print("\\nTraining data")
print_data_dict(data_dict)
finetune(m2m, data_dict, FT_FOLD)모델 클래스의 인스턴스를 만들과 기존에 모델이 어떻게 번역하는지를 먼저 확인했다. 그 과정이 끝나고 나면 파인튜닝을 수행하였다.
단계 7. 파인튜닝된 모델을 이용해 결과 검증하기
학습된 결과가 저장된 폴더에서 모델과 토크나이저를 불러오고 이들을 이용해 어떻게 번역되지는지를 보았다.
# %% Load model
model_dir = FT_FOLD # Adjust this to your specified output_dir
# Load the trained model
model = M2M100ForConditionalGeneration.from_pretrained(model_dir)
tokenizer = M2M100Tokenizer.from_pretrained(model_dir)
print("After Fine-tuning")
for input_text in data_dict["source"]:
output_text = trans(model, tokenizer, input_text)
print(f"{input_text} ==> {output_text}")앞서 학습전에는 안녕을 Hello로 번역했지만 파인튜닝이 진행되고 나니 의도한데로 Hi로 번역하는 것을 확인할 수 있었다.

시사점
파인튜닝을 이용해 번역 전문 LLM을 원하는 용어나 문장 패턴으로 번역하는 방법을 살펴보았다. 파인튜닝은 번역 모델 뿐 아니라 다른 LLM 모델도 이처럼 주어진 태스크에 맞는 역할을 하도록 조정할 수 있다. 본 글에서는 지면 관계상 구체적으로 다루지는 않았지만 데이터 셋 준비와 파인튜닝 파라메터를 잘 조정할 때 최적의 결과를 얻을 수 있다는 점도 인지할 필요가 있다.
테크에이스에서는 자체 기술력과 주요 협업을 통해 LLM 등 인공지능을 활용한 고객사의 니즈를 대응하는 다양한 노력을 하고 있다. 특히, AssistAce, ResourceAce등 인공지능 소프트웨어 툴과 GPU 하드웨어와 클라우드 서비스 등 인공지능을 이용한 비지니스에 필요한 풀스택을 제공할 준비를 해나가고 있다. 향후에는 인공지능 기술 및 트랜스포메이션 분야에서 더욱 고객 만족도를 높힐 수 있도록 계속 노력해 나갈 예정이다.
참고자료

Comments