인공지능/자연어 처리

자연어 처리 : 분산학습 - Distributed Training, Python 실습

이게될까 2024. 7. 21. 16:15
728x90
728x90

Data Parallelism - GPU 마다 모델을 넣어서 각각의 연산에 따라 훈련시킨다

Model Parallelism - Model을 쪼개서 GPU에 넣는다.
Tensor Parallelism - 모델을 수평으로 나눈다.
Pipeline Parallelism - 레이어 별로 다른 GPU에 넣는다.

Data Parallelism 
배치가 작을 수록 한 iteration을 빠르게 돌릴 수 있다. -> GPU에 잘 넣을 수 있도록 적당한 size를 가져온다. - GPU가 많을 수록 많은 배치 사이즈로 가져갈 수 있다. 
GPU가 3개라면 3개의 Loss를 가지게 된다. -> 평균 Gradient를 구해서 Back Propagation을 진행한다.
Ring all reduce - 양 옆의 GPU의 Loss만 전달한다. => 이게 최적한 알고리즘은 아니다!

이 방식은 모델이 매우 크면 활용할 수 없다.

 

 

 

Tensor Parallelism - 모델을 수평으로 나눈다.

이런 방식으로 병렬 연산을 진행할 수 있다.

이 결과들을 더해주면 같아진다!

실제 연산은 아래와 같이 진행한다.

 

 

Pipeline Parallelism - 레이어 별로 다른 GPU에 넣는다.

연산 속도에 대한 장점은 없어 보이네요

배치를 한 번 더 나눈 마이크로 배치를 활용하여 C와 같은 방식으로 시간을 단축시켰다!

 

 

Deep speed ZeRO : 파라미터와 Gradient, optimizer states을 따로 GPU에 저장해서 각각 사용한다.

 

 

from datasets import load_dataset
nsmc_dataset = load_dataset('nsmc')

데이터 다운받기!

nsmc_dataset['train'][0]

{'id': '9976970', 'document': '아 더빙.. 진짜 짜증나네요 목소리', 'label': 0}

확인해 보면 이전 실습에서도 많이 사용했던 데이터 들이다.

nsmc_df = nsmc_dataset['train'].to_pandas()
nsmc_df

판다스로 변형시켜서 데이터 처리를 좀 더 쉽게 해준다.

from transformers import AutoTokenizer
tok = AutoTokenizer.from_pretrained('bert-base-multilingual-cased')

허깅 페이스에서 토크나이저 가져오기

tok.tokenize('청춘 영화의 최고봉.')

['청', '##춘', '영화', '##의', '최고', '##봉', '.']

다양한 단어로 학습된 토크나이저므로 완벽하게 나누진 못했지만 그래도 잘 나뉜 것을 볼 수 있다.

tok('청춘 영화의 최고봉.')

{'input_ids': [101, 9751, 97707, 42428, 10459, 83491, 118989, 119, 102], 'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0, 0], 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1]}

토큰화 되었다!

tok(['청춘 영화의 최고봉.', '청춘'], padding=True)

{'input_ids': [[101, 9751, 97707, 42428, 10459, 83491, 118989, 119, 102], [101, 9751, 97707, 102, 0, 0, 0, 0, 0]], 'token_type_ids': [[0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0]], 'attention_mask': [[1, 1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 0, 0, 0, 0, 0]]}

단어 길이를 맞추는 것을 볼 수 있다.

def tokenizer(data):
    return tok(data['document'], max_length=32, padding='max_length', truncation=True)

최대 길이를 맞추는 함수를 만들었다. 

BERT는 동일한 길이로 맞춰줘야 하기 때문에...

nsmc_dataset_tokenized = nsmc_dataset.map(tokenizer)

데이터에 토크나이저를 적용한다.

nsmc_dataset_tokenized['train'][0]

{'id': '9976970',
 'document': '아 더빙.. 진짜 짜증나네요 목소리',
 'label': 0,
 'input_ids': [101,
  9519,
  9074,
  119005,
  119,
  119,
  9708,
  119235,
  9715,
  119230,
  16439,
  77884,
  48549,
  9284,
  22333,
  12692,
  102,
  0,
  0,
  0,
  0,
  0,
...
  0,
  0,
  0,
  0,
  0]}
잘 된 것을 확인했다!

import torch
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

device

GPU 확인하기!

from transformers import AutoModelForSequenceClassification
model = AutoModelForSequenceClassification.from_pretrained('bert-base-multilingual-cased',
                                                          num_labels=2)

학습된 모델을 불러온다

num_train_epochs = 2
learning_rate = 2e-7
batch_size = 128

학습 파라미터들 정해주기!

from torch.utils.data import DataLoader

데이터 로더 불러오기

tr_ds = nsmc_dataset_tokenized['train'].remove_columns(['id', 'document'])
tr_ds.set_format(type='torch')

필요 없는 열들 제거해주기

tr_dl = DataLoader(tr_ds, batch_size=batch_size)
tr_dl

데이터 로더에 데이터 넣어주기

val_ds = nsmc_dataset_tokenized['test'].remove_columns(['id', 'document'])
val_ds.set_format(type='torch')
val_dl = DataLoader(tr_ds, batch_size=batch_size)
val_dl

validation도 만들어주기

그런데 이렇게 만들면 동일한 데이터에서 뽑아오니까 이러면 안되는거 아닌가...?

next(iter(tr_dl))

{'label': tensor([0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1,
         0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0,
         0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1,
         0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1,
         1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0,
         0, 0, 1, 1, 0, 1, 0, 1]),
 'input_ids': tensor([[  101,  9519,  9074,  ...,     0,     0,     0],
         [  101,   100,   119,  ..., 16439,   102,     0],
         [  101,   100,   102,  ...,     0,     0,     0],
         ...,
         [  101,  9358, 12508,  ...,     0,     0,     0],
         [  101,  9519, 25503,  ...,     0,     0,     0],
         [  101, 10150, 10954,  ...,  9568, 12310,   102]]),
 'token_type_ids': tensor([[0, 0, 0,  ..., 0, 0, 0],
         [0, 0, 0,  ..., 0, 0, 0],
         [0, 0, 0,  ..., 0, 0, 0],
         ...,
         [0, 0, 0,  ..., 0, 0, 0],
         [0, 0, 0,  ..., 0, 0, 0],
         [0, 0, 0,  ..., 0, 0, 0]]),
 'attention_mask': tensor([[1, 1, 1,  ..., 0, 0, 0],
         [1, 1, 1,  ..., 1, 1, 0],
         [1, 1, 1,  ..., 0, 0, 0],
         ...,
         [1, 1, 1,  ..., 0, 0, 0],
         [1, 1, 1,  ..., 0, 0, 0],
         [1, 1, 1,  ..., 1, 1, 1]])}

tensor 형태로 잘 들어가 있는 것을 볼 수 있다.

import numpy as np
from tqdm import tqdm
from torch.nn import CrossEntropyLoss
from torch.optim import Adam


def acc(pred,label):
    pred = torch.round(pred.squeeze())
    return torch.sum(pred == label.squeeze()).item()

model.to(device)
criterion = CrossEntropyLoss()
optimizer = Adam(model.parameters(), lr=learning_rate)

정답의 개수를 리턴하는 acc 함수! 

for epoch in range(num_train_epochs):
    train_losses = []
    train_acc = 0.0
    model.train()
    
    for step, batch in enumerate(tqdm(tr_dl)):
        label = batch['label'].to(device)
        input_id, token_type_ids, attention_mask = batch['input_ids'].to(device), batch['token_type_ids'].to(device), batch['attention_mask'].to(device)
        
        model.zero_grad()
        pred = model(input_id, token_type_ids, attention_mask)
        
        loss = criterion(torch.sigmoid(pred.logits.t()[1]), label.float())
        
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
        
        train_losses.append(loss.item())
        train_acc += acc(pred.logits.argmax(dim=1), label)
        
#         if (step+1)%100==0:
#             print("train loss: ", np.mean(train_losses))
#             print("train acc: ", train_acc/(step*batch_size))
            
        
    print("train loss: ", np.mean(train_losses))
    print("train acc: ", train_acc/len(tr_dl.dataset))
    
    val_losses = []
    val_acc = 0
    model.eval()
    # validation 데이터를 통한 확인
    
    for step, batch in enumerate(tqdm(val_dl)):
        label = batch['label'].to(device)
        input_id, token_type_ids, attention_mask = batch['input_ids'].to(device), batch['token_type_ids'].to(device), batch['attention_mask'].to(device)
        
        pred = model(input_id, token_type_ids, attention_mask)
        loss = criterion(torch.sigmoid(pred.logits.t()[1]), label.float())
        
        val_losses.append(loss.item())
        val_acc += acc(pred.logits.argmax(dim=1), label)
        

    print("val loss: ", np.mean(val_losses))
    print("val acc: ", val_acc/len(val_dl.dataset))

 

허깅페이스의 트레이너를 활용한 학습!

from transformers import TrainingArguments

logging_steps = len(nsmc_dataset['train']) // batch_size
output_dir = 'trainer_test'

training_args = TrainingArguments(output_dir=output_dir,
                                 num_train_epochs=num_train_epochs,
                                 learning_rate = learning_rate,
                                 per_device_train_batch_size=batch_size,
                                 per_device_eval_batch_size=batch_size,
                                 evaluation_strategy='epoch',
                                 logging_steps=logging_steps,
                                 fp16=True,
                                 push_to_hub=False)

 

from sklearn.metrics import precision_recall_fscore_support, accuracy_score

def compute_metrics(pred):
    labels = pred.label_ids
    preds = pred.predictions.argmax(-1)
    precision, recall, f1, _ = precision_recall_fscore_support(labels, preds, average='binary')
    acc = accuracy_score(labels, preds)
    return {
        'accuracy': acc,
        'f1': f1,
        'precision': precision,
        'recall': recall
    }

다양한 평가 방법들을 넣어줬다.

from transformers import Trainer
from transformers import AutoModelForSequenceClassification

model = AutoModelForSequenceClassification.from_pretrained('bert-base-multilingual-cased',
                                                          num_labels=2)

trainer = Trainer(model=model,
                 args=training_args,
                 compute_metrics=compute_metrics,
                 train_dataset=nsmc_dataset_tokenized['train'],
                 eval_dataset=nsmc_dataset_tokenized['test'],
                 tokenizer=tok)

모델의 학습이 진행되었으므로 다시 불러온다!

평가 방식도 다시 넣어준다.

trainer.train()

 

 

학습하는 법!

 

728x90