유용한것 이것저것

Hugging Face 사용법 (2) Fine-tuning

seungbeomdo 2023. 7. 26. 19:22
 

Hugging Face 사용법 (1) pipeline 함수 사용하기 및 모델 객체 로컬에 다운 받기

Hugging Face – The AI community building the future. The AI community building the future. Build, train and deploy state of the art models powered by the reference open source in machine learning. huggingface.co 0. Hugging Face 소개 허깅페이스는

seungbeomdo.tistory.com


 

  • Fine tuning이란 pre-trained 모델을 구체적인 태스크에 맞게 추가적으로 훈련하는 과정을 말한다. 가령 주어진 직무 기술서를 보고 어떤 직무에 대한 설명인지 분류해야 하는 태스크가 있다고 하자. 
  • 이때 직무 기술서만을 가지고 처음부터 훈련하는 방법도 있지만, 데이터셋의 크기가 충분히 크지 못하거나 복잡한 최신 모델을 훈련할 만한 여건이 안 되는 경우에는 fine-tuning 방법을 사용할 수 있다.

 

  • Fine-tuning 방법은 이미 잘 만들어진 모델을 불러와서 추가적인 트레인셋을 집어넣어주는 것이기 때문에, 작은 부담으로도 고사양의 모델을 다룰 수 있다는 장점이 있다. 물론 사용자의 필요에 딱 맞는 pre-trained 모델이 없을 수도 있다.

 

  • 앞서 든 예시처럼 직무 기술 - 직무 예측 과제에 맞춰 훈련된 pre-trained 모델이 없는 경우에는 다른 자연어 처리 과제에 활용된 모델을 사용하면 된다. 다른 과제라고 하더라도 자연어 처리라는 큰 도메인에서 훈련된 모델은 자연어 자체에 대한 일반적인 학습이 충분히 이루어져있기 때문이다. 이 모델에 대해서 직무 기술 데이터셋을 추가로 훈련시키는 편이 맨바닥에서부터 훈련하는 것보다는 훨씬 효율적이다.
  • 추리 소설을 쓸 작가를 모집하는데 아무것도 모르는 사람을 데리고 와서 처음부터 추리 소설 작가에 특화된 훈련을 시키는 것보다는, 기자라든지 아니면 판타지 소설 작가라든지 유사한 작업에 전문성이 있는 사람을 데리고 와서 추리 소설에 맞게 fine-tuning을 해주는 것이 낫다.

3. Freezing 방법

 

  • 굳이 분류하자면 fine-tuning은 두 가지로 나누어볼 수 있다. 하나는 주어진 pre-trained 모델의 모든 레이어를 추가 훈련하는 end-to-end 방법이다. 다른 하나는 일부 레이어는 놔두고(freezing이라고 한다) 일부 레이어만 추가 훈련하는 방법이다.

 

  • 어떤 자연어 처리 모델이 이미 일반적인 자연어에 대해서는 잘 학습되어있다고 하자. 이 경우에 구체적인 한 태스크(직무 기술 - 직무 예측)를 위해 fine-tuning을 한다면 '일반적인 자연어'에 대해 학습된 부분은 그대로 두고, 그 위에서 '직무 기술서에 자주 등장하는 전문 용어'에 대한 추가 학습을 하는 것이 효율적이다. 이런 아이디어가 구현된 방법이 freezing 방법이다.
  • 주로 pre-trained 모델의 backend 레이어들을 freezing하고 frontend 레이어들을 fine-tuning하는 방식이다.

 

  • Freezing 방법은 구현하기가 매우 쉽다. 복잡하게 생각할 것 없이, 뉴럴넷의 backend 레이어들을 일종의 특성 추출기처럼 사용한 후, 추출된 특성을 인풋으로 하는 뉴럴넷을 만들어서 훈련하면 된다. 그러면 backend 레이어들은 고정된 상태에서 frontend 레이어들만 추가 훈련되는 결과를 얻는다.

 

1) Tokenizer

  • Fine-tuning 방법은 pipeline 함수만으로 구현하기는 어렵다. 모델을 그대로 가져다 쓰는 것이 아니고 모델을 분해하는 과정이 필요하기 때문이다. 우선 NLP 모델의 경우 모델이 직접 입력 받는 인풋은 token들의 임베딩 벡터이다. Transformers 패키지는 주어진 모델에 사용된 tokenizer도 간편하게 load해준다. 
  • 전편의 글을 따라서 facebook/bart-large-mnli 모델을 다운 받았다면, tokenizer도 함께 다운 받아졌을 것이다. 다음과 같이 하면 tokenizer 객체를 생성한다.
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("모델 위치한 폴더 경로")

 

2) Dataset

  • Transformers 패키지는 Dataset type으로 주어진 데이터를 사용한다. 간단하게 pd.DataFrame에 from_pandas 함수를 사용하면 된다.
from datasets import Dataset
ds_JD = Dataset.from_pandas(df_JD)
  • pd.DataFrame 타입이었던 df_JD를 Dataset type으로 바꾸었다.

 

3) 특성 추출

  • 데이터 처리가 모두 완성되었으니 모델을 불러온다.
import torch
from transformers import AutoModelForSequenceClassification

device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
bart = AutoModelForSequenceClassification.from_pretrained('모델 저장 폴더 경로',
						num_labels = 2, ignore_mismatched_sizes = True).to(device)

 

  • 모델을 통과시켜서 특성 벡터를 추출한다. 
X = []
for text in ds_JD['text']:
    token = tokenizer(text, return_tensors='pt')
    input = {k:v.to(device) for k,v in token.items()}
    with torch.no_grad():
    	output = bart(input['inputs_ids'])
    X.append(output.encoder_last_hidden_state[:,0])
    
X_tensor = torch.stack(X, 0).squeeze()
y = ds_JD['label']
y_tensor = torch.tensor(y, dtype=float).reshape(len(y),1)
  • 첨부한 코드를 보면, 먼저 ds_JD의 각 text(자연어로 된 직무 기술)들을 tokenize-embedding한 후 파이토치 텐서 형태로 바꾼다.
  • 텐서로 바뀐 임베딩 벡터들을 bart 모델을 통과시켜 output 객체를 만든다.
  • output 객체에는 임베딩 벡터가 거쳐 온 모든 상태들이 담겨 있는데, 그 중에서 인코더 파트의 마지막 히든 스테이트만을 저장한다.

 

4) 뉴럴넷 훈련

  • 나머지는 일반적인 뉴럴넷 훈련과 동일하다. Pre-trained 모델로 추출한 특성을 인풋으로 받을 뉴럴넷이 클래스 Net으로 이미 만들어져있다고 하자.
mynet = Net()

criterion = nn.BCELoss()
optimizer = optim.Adam(mynet.parameters(), lr=0.001)

epochs = 3000
losses = []

for i, epoch in enumerate(range(epochs)):
    optimizer.zero.grad()
    y_pred = mynet(X_train).to(torch.float64)
    loss = criterion(y_pred, y_tensor)
    loss.append(loss.item())
    loss.backward()
    
    optimizer.step()
    
    if i % 100 == 0:
        print('Epoch': {}, 'Loss': {}'.format(epoch+1, loss.item()))
    else:
        pass

 

  • 추출된 특성을, 뉴럴넷이 아닌 다른 머신러닝 알고리즘의 인풋으로 사용하는 것도 당연히 가능하다.

4. End-to-End 방법

 

  • 딥러닝 모델은 수많은 레이어로 구성되어 있고, 이 모든 레이어가 구체적인 목표를 위해서 훈련된다. 따라서 Freezing 방법이 상정하는 것처럼 backend의 레이어들이 일반적인 차원의 특성을 학습하고 frontend의 레이어들이 구체적인 차원의 특성을 학습하는 것이라고 단정 지을 수는 없다. 뉴럴넷의 모든 레이어들은 주어진 목적을 달성하는 데 가장 최적화된 방식으로 조정되어 있는 것일 뿐이다.

 

  • 모델의 구조를 유지하면서 end-to-end로 fine-tuning하는 것은 transformers 패키지에서는 굉장히 간편하다. 데이터 처리 부분은 3장에서 다룬 내용과 동일하고, trainer라는 라이브러리를 사용한다.
from transformers import Trainer, TrainingArguments
from sklearn import metrics

training_args = TrainingArguments(output_dirs = "test_trainer")

def comput_metrics(eval_pred):
    logits, labels = eval_pred
    predictions = np.argmax(logits, axis = -1)
    acc = metrics.accuracy_score(labels, predictions)
    pre = metrics.accuracy_score(labels, predictions)
    rec = metrics.accuracy_score(labels, predictions)
    f1s = metrics.accuracy_score(labels, predictions)
    return {'acc':acc, 'pre':pre, 'rec':rec, 'f1s':f1s}
 
trainer = Trainer(model = bart,
                 args = training_args,
                 train_dataset = ds_JD,
                 test_dataset = ds_JD,
                 compute_metrics = compute_metrics)

trainer.train()