본문 바로가기

텍스트 마이닝

한국어 문서에 대한 BERT 활용 (2)

반응형

16.2 KoBERT 사전학습모형에 대한 파이토치 기반 미세조정학습

!pip install sentencepiece
!pip install 'git+https://github.com/SKTBrain/KoBERT.git#egg=kobert_tokenizer&subdirectory=kobert_hf'
del model
del trainer
torch.cuda.empty_cache()
from kobert_tokenizer import KoBERTTokenizer
tokenizer = KoBERTTokenizer.from_pretrained('skt/kobert-base-v1')

print(tokenizer.tokenize("안녕하세요. 반갑습니다."))
inputs = tokenizer("안녕하세요. 반갑습니다.")
print(inputs)


"""
['▁안', '녕', '하세요', '.', '▁반', '갑', '습니다', '.']
{'input_ids': [2, 3135, 5724, 7814, 54, 2207, 5345, 6701, 54, 3], 'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]}
"""
from transformers import BertModel
from torch.utils.data import DataLoader

# 토큰화
train_input = tokenizer(X_train, truncation=True, padding=True, return_tensors="pt")
val_input = tokenizer(X_val, truncation=True, padding=True, return_tensors="pt")
test_input = tokenizer(X_test, truncation=True, padding=True, return_tensors="pt")

# Dataset 생성
train_dataset = OurDataset(train_input, y_train)
val_dataset = OurDataset(val_input, y_val)
test_dataset = OurDataset(test_input, y_test)

# 데이터로더 생성
train_loader = DataLoader(train_dataset, shuffle=True, batch_size=8)
val_loader = DataLoader(val_dataset, batch_size=16)
test_loader = DataLoader(test_dataset, batch_size=16)

# KoBERT 사전학습모형 로드
bert_model = BertModel.from_pretrained('skt/kobert-base-v1')

# BERT를 포함한 신경망 모형
class MyModel(torch.nn.Module):
    def __init__(self, pretrained_model, token_size, num_labels): 
        super(MyModel, self).__init__()
        self.token_size = token_size
        self.num_labels = num_labels
        self.pretrained_model = pretrained_model

        # 분류기 정의
        self.classifier = torch.nn.Linear(self.token_size, self.num_labels)

    def forward(self, inputs):
        # BERT 모형에 입력을 넣고 출력을 받음
        outputs = self.pretrained_model(**inputs)
        # BERT 출력에서 CLS 토큰에 해당하는 부분만 가져옴
        bert_clf_token = outputs.last_hidden_state[:,0,:]
        
        return self.classifier(bert_clf_token)

# token_size는 BERT 토큰과 동일, bert_model.config.hidden_size로 알 수 있음
model = MyModel(bert_model, num_labels=2, token_size=bert_model.config.hidden_size)
from transformers import AdamW, get_linear_schedule_with_warmup
import torch.nn.functional as F
import time

# GPU 가속을 사용할 수 있으면 device를 cuda로 설정하고, 아니면 cpu로 설정
device = torch.device('cuda') if torch.cuda.is_available() else torch.device('cpu')

model.to(device)  # 모형을 GPU로 복사
model.train()     # 학습모드로 전환

# 옵티마이저를 트랜스포머가 제공하는 AdamW로 설정
optim = AdamW(model.parameters(), lr=5e-5, weight_decay=0.01) # 가중치 감쇠 설정
criterion = torch.nn.CrossEntropyLoss()    # 멀티클래스이므로 크로스 엔트로피를 손실함수로 사용

num_epochs = 2      # 학습 epoch를 3회로 설정
total_training_steps = num_epochs * len(train_loader)
# 학습 스케줄러 설정
scheduler = get_linear_schedule_with_warmup(optimizer=optim,
                                            num_training_steps=total_training_steps,
                                            num_warmup_steps=200)

start = time.time() # 시작시간 기록
train_loss = 0
eval_steps = 500
step = 0

for epoch in range(num_epochs):
    #total_epoch_loss = 0  # epoch의 총 loss 초기화
    for batch in train_loader:
        model.train()     # 학습모드로 전환
        optim.zero_grad()     # 그래디언트 초기화
        # 배치에서 label을 제외한 입력만 추출하여  GPU로 복사
        inputs = {k: v.to(device) for k, v in batch.items() if k != 'labels'} 
        labels = batch['labels'].to(device) # 배치에서 라벨을 추출하여 GPU로 복사
        outputs = model(inputs) # 모형으로 결과 예측
        # 두 클래스에 대해 예측하고 각각 비교해야 하므로 labels에 대해 원핫인코딩을 적용한 후에 손실을 게산
        loss = criterion(outputs, F.one_hot(labels, num_classes=2).float()) # loss 계산

        train_loss += loss
        loss.backward() # 그래디언트 계산
        optim.step()    # 가중치 업데이트
        scheduler.step() # 스케줄러 업데이트
        
        step += 1
        if step % eval_steps == 0: # eval_steps 마다 경과한 시간과 loss를 출력
            with torch.no_grad():
                val_loss = 0
                model.eval()
                for batch in val_loader:
                    inputs = {k: v.to(device) for k, v in batch.items() if k != 'labels'}
                    labels = batch['labels'].to(device)
                    outputs = model(inputs)
                    loss = criterion(outputs, F.one_hot(labels, num_classes=2).float()) # loss 계산
                    val_loss += loss
                avg_val_loss = val_loss / len(val_loader)
            avg_train_loss = train_loss / eval_steps
            elapsed = time.time() - start
            print('Step %d, elapsed time: %.2f, train loss: %.4f, validation loss: %.4f' 
                  % (step, elapsed, avg_train_loss, avg_val_loss))
            train_loss = 0


"""
Step 500, elapsed time: 141.06, train loss: 0.4933, validation loss: 0.4198
Step 1000, elapsed time: 281.86, train loss: 0.3552, validation loss: 0.3710
Step 1500, elapsed time: 423.89, train loss: 0.2663, validation loss: 0.2829
Step 2000, elapsed time: 566.67, train loss: 0.2225, validation loss: 0.2967
"""
from datasets import load_metric

metric= load_metric("accuracy")
model.eval()
for batch in test_loader:
    inputs = {k: v.to(device) for k, v in batch.items() if k != 'labels'}
    labels = batch['labels'].to(device)
    
    with torch.no_grad(): # 학습할 필요가 없으므로 그래디언트 계산을 끔
        outputs = model(inputs)
        #print(outputs)

    predictions = torch.argmax(outputs, dim=-1)
    metric.add_batch(predictions=predictions, references=labels)

metric.compute()


"""
{'accuracy': 0.8693644758283542}
"""

 

 

 

 

 

※ 해당 내용은 <파이썬 텍스트 마이닝 완벽 가이드>의 내용을 토대로 학습하며 정리한 내용입니다.

반응형