개발자의 스터디 노트
KoBERT로 이진 분류 학습 본문
우선 학습하고자 하는 데이터를 준비합니다.
여기서는 리포트의 외향 내향 분석을 하기 위해 따로 자료를 준비해두었습니다.
2진 분류 데이터라면 어떠한 데이터도 상관없으니 준비하여 학습하면 됩니다.
1. 학습에 필요한 패키지 import
import pandas as pd
import csv
## Import
import torch
from torch import nn
import torch.nn.functional as F
import torch.optim as optim
from torch.utils.data import Dataset, DataLoader
import gluonnlp as nlp
import numpy as np
from tqdm import tqdm, tqdm_notebook
#KoBERT
from kobert.utils import get_tokenizer
from kobert.pytorch_kobert import get_pytorch_kobert_model
#transformers
from transformers import AdamW
from transformers.optimization import get_cosine_schedule_with_warmup
2. GPU 설정
#GPU 사용
device = torch.device("cuda:0")
3. KoBERT모델, Vocabulary 불러오기
#BERT 모델, Vocabulary 불러오기
bertmodel, vocab = get_pytorch_kobert_model()
/home/JupyterNotebook/pytorch_ex/.cache/kobert_v1.zip[██████████████████████████████████████████████████]
/home/JupyterNotebook/pytorch_ex/.cache/kobert_news_wiki_ko_cased-1087f8699e.spiece[██████████████████████████████████████████████████]
4. 데이터 로딩
# csv data 읽어서 학습에 사용할 데이터로 변경
def load_csv(csv_path):
f = open(csv_path, 'r', encoding='utf-8')
lines = csv.reader(f)
data_list = []
for rpt_parent,propensity in lines:
data = []
data.append(rpt_parent)
data.append(str(propensity))
data_list.append(data)
f.close()
return data_list
## 데이터 준비
report_path = "data/report.csv"
data_list = load_csv(report_path)
print(data_list[0:2])
[
['정석대로라면 월간계획표를 진행해야하는 시기이나, ..... 확인되었습니다. 감사합니다.', '1'],
['지난 시간에 내주었던 숙제 1장 반 중 절반정도의 ..... 넘어가도록 하겠습니다.', '0']
]
출력된것을 확인하면 ['sentence', 'class']의 형태로 이루어져 있는 것을 확인할 수 있고, class는 숫자로 라벨링 되어있는 것을 확인할 수 있습니다.
5. 학습 데이터와 테스트 데이터로 나누기
#train & test 데이터로 나누기
from sklearn.model_selection import train_test_split
dataset_train, dataset_test = train_test_split(data_list, test_size=0.25, random_state=0)
print(len(dataset_train))
print(len(dataset_test))
3907
1303
테스트 데이터가 25% 나뉘어 있는것을 확인할 수 있습니다.
6. KoBERT 입력 데이터로 만들기
토큰화, 정수 인코딩, 패딩 들을 해주는 클래스입니다.
class BERTDataset(Dataset):
def __init__(self, dataset, sent_idx, label_idx, bert_tokenizer, max_len,
pad, pair):
transform = nlp.data.BERTSentenceTransform(
bert_tokenizer, max_seq_length=max_len, pad=pad, pair=pair)
self.sentences = [transform([i[sent_idx]]) for i in dataset]
self.labels = [np.int32(i[label_idx]) for i in dataset]
def __getitem__(self, i):
return (self.sentences[i] + (self.labels[i], ))
def __len__(self):
return (len(self.labels))
7. 하이퍼 파라미터 설정
# Setting parameters
max_len = 64
batch_size = 64
warmup_ratio = 0.1
num_epochs = 5
max_grad_norm = 1
log_interval = 200
learning_rate = 5e-5
KoBERT 학습을 위한 하이퍼 파라메터 정보입니다.
8. 토큰화와 패딩 실행
#토큰화
tokenizer = get_tokenizer()
tok = nlp.data.BERTSPTokenizer(tokenizer, vocab, lower=False)
data_train = BERTDataset(dataset_train, 0, 1, tok, max_len, True, False)
data_test = BERTDataset(dataset_test, 0, 1, tok, max_len, True, False)
토큰화와 패딩이 잘 되었는지를 확인하기 위해 한건만 출력해 봅시다.
data_train[0]
(array([ 2, 2890, 6706, 1436, 4389, 7088, 2086, 7798, 5330, 1919, 6896,
2408, 6234, 2120, 7178, 2912, 6553, 6312, 6940, 4930, 2125, 4888,
6199, 834, 3084, 5760, 2963, 517, 5330, 7248, 5761, 1741, 7903,
7096, 5330, 3647, 2437, 6903, 4389, 6064, 2387, 6199, 3367, 4930,
2058, 2355, 6948, 517, 54, 2415, 3836, 7078, 5859, 3084, 5760,
5400, 3978, 5782, 5439, 517, 46, 2890, 6706, 3], dtype=int32),
array(64, dtype=int32),
array([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, 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],
dtype=int32),
1)
출력 값들을 보면 3개의 array가 출력됩니다.
첫 번째는 패딩 된 시퀀스
두 번째는 길이와 타입에 대한 내용
세 번째는 어텐션 마스크 시퀀스입니다.
BERT에 데이터가 입력되었을 때 어텐션 함수가 적용되어 연산이 됩니다. 이때 1로 패딩 된 값들을 연산할 필요가 없기 때문에 연산을 하지 ㅇ낳아도 된다고 알려주는 데이터가 있어야 하는 게 그게 바로 어텐션 마스크 시퀀스인 것입니다. BERT나 KoBERT에는 어텐션 마스크 데이터도 함께 입력되어야 합니다.
9. torch의 dataset 만들기
train_dataloader = torch.utils.data.DataLoader(data_train, batch_size=batch_size, num_workers=5)
test_dataloader = torch.utils.data.DataLoader(data_test, batch_size=batch_size, num_workers=5)
10. BERT 분류기
num_classes=2 인 부분을 수정하여 다항 분류 학습에 이용할 수 있습니다.
class BERTClassifier(nn.Module):
def __init__(self,
bert,
hidden_size = 768,
num_classes=2, ##클래스 수 조정##
dr_rate=None,
params=None):
super(BERTClassifier, self).__init__()
self.bert = bert
self.dr_rate = dr_rate
self.classifier = nn.Linear(hidden_size , num_classes)
if dr_rate:
self.dropout = nn.Dropout(p=dr_rate)
def gen_attention_mask(self, token_ids, valid_length):
attention_mask = torch.zeros_like(token_ids)
for i, v in enumerate(valid_length):
attention_mask[i][:v] = 1
return attention_mask.float()
def forward(self, token_ids, valid_length, segment_ids):
attention_mask = self.gen_attention_mask(token_ids, valid_length)
_, pooler = self.bert(input_ids = token_ids, token_type_ids = segment_ids.long(), attention_mask = attention_mask.float().to(token_ids.device))
if self.dr_rate:
out = self.dropout(pooler)
return self.classifier(out)
11. 학습하기
#BERT 모델 불러오기
model = BERTClassifier(bertmodel, dr_rate=0.5).to(device)
#optimizer와 schedule 설정
no_decay = ['bias', 'LayerNorm.weight']
optimizer_grouped_parameters = [
{'params': [p for n, p in model.named_parameters() if not any(nd in n for nd in no_decay)], 'weight_decay': 0.01},
{'params': [p for n, p in model.named_parameters() if any(nd in n for nd in no_decay)], 'weight_decay': 0.0}
]
optimizer = AdamW(optimizer_grouped_parameters, lr=learning_rate)
loss_fn = nn.CrossEntropyLoss()
t_total = len(train_dataloader) * num_epochs
warmup_step = int(t_total * warmup_ratio)
scheduler = get_cosine_schedule_with_warmup(optimizer, num_warmup_steps=warmup_step, num_training_steps=t_total)
#정확도 측정을 위한 함수 정의
def calc_accuracy(X,Y):
max_vals, max_indices = torch.max(X, 1)
train_acc = (max_indices == Y).sum().data.cpu().numpy()/max_indices.size()[0]
return train_acc
train_dataloader
for e in range(num_epochs):
train_acc = 0.0
test_acc = 0.0
model.train()
for batch_id, (token_ids, valid_length, segment_ids, label) in enumerate(tqdm(train_dataloader)):
optimizer.zero_grad()
token_ids = token_ids.long().to(device)
segment_ids = segment_ids.long().to(device)
valid_length= valid_length
label = label.long().to(device)
out = model(token_ids, valid_length, segment_ids)
loss = loss_fn(out, label)
loss.backward()
torch.nn.utils.clip_grad_norm_(model.parameters(), max_grad_norm)
optimizer.step()
scheduler.step() # Update learning rate schedule
train_acc += calc_accuracy(out, label)
if batch_id % log_interval == 0:
print("epoch {} batch id {} loss {} train acc {}".format(e+1, batch_id+1, loss.data.cpu().numpy(), train_acc / (batch_id+1)))
print("epoch {} train acc {}".format(e+1, train_acc / (batch_id+1)))
model.eval()
for batch_id, (token_ids, valid_length, segment_ids, label) in enumerate(tqdm(test_dataloader)):
token_ids = token_ids.long().to(device)
segment_ids = segment_ids.long().to(device)
valid_length= valid_length
label = label.long().to(device)
out = model(token_ids, valid_length, segment_ids)
test_acc += calc_accuracy(out, label)
print("epoch {} test acc {}".format(e+1, test_acc / (batch_id+1)))
3%|▎ | 2/62 [00:00<00:20, 2.90it/s]
5%|▍ | 3/62 [00:00<00:18, 3.27it/s]
6%|▋ | 4/62 [00:01<00:16, 3.49it/s]
8%|▊ | 5/62 [00:01<00:15, 3.62it/s]
10%|▉ | 6/62 [00:01<00:15, 3.72it/s]
11%|█▏ | 7/62 [00:02<00:14, 3.79it/s]
13%|█▎ | 8/62 [00:02<00:14, 3.83it/s]
15%|█▍ | 9/62 [00:02<00:13, 3.86it/s]
16%|█▌ | 10/62 [00:02<00:13, 3.88it/s]
18%|█▊ | 11/62 [00:03<00:13, 3.89it/s]
19%|█▉ | 12/62 [00:03<00:12, 3.90it/s]
21%|██ | 13/62 [00:03<00:12, 3.90it/s]
23%|██▎ | 14/62 [00:03<00:12, 3.90it/s]
24%|██▍ | 15/62 [00:04<00:12, 3.90it/s]
26%|██▌ | 16/62 [00:04<00:11, 3.90it/s]
27%|██▋ | 17/62 [00:04<00:11, 3.90it/s]
29%|██▉ | 18/62 [00:04<00:11, 3.90it/s]
31%|███ | 19/62 [00:05<00:11, 3.90it/s]
32%|███▏ | 20/62 [00:05<00:10, 3.91it/s]
34%|███▍ | 21/62 [00:05<00:10, 3.90it/s]
35%|███▌ | 22/62 [00:05<00:10, 3.90it/s]
37%|███▋ | 23/62 [00:06<00:09, 3.90it/s]
39%|███▊ | 24/62 [00:06<00:09, 3.90it/s]
40%|████ | 25/62 [00:06<00:09, 3.91it/s]
42%|████▏ | 26/62 [00:06<00:09, 3.91it/s]
44%|████▎ | 27/62 [00:07<00:08, 3.91it/s]
45%|████▌ | 28/62 [00:07<00:08, 3.91it/s]
47%|████▋ | 29/62 [00:07<00:08, 3.91it/s]
48%|████▊ | 30/62 [00:07<00:08, 3.91it/s]
50%|█████ | 31/62 [00:08<00:07, 3.91it/s]
52%|█████▏ | 32/62 [00:08<00:07, 3.90it/s]
53%|█████▎ | 33/62 [00:08<00:07, 3.90it/s]
55%|█████▍ | 34/62 [00:08<00:07, 3.90it/s]
56%|█████▋ | 35/62 [00:09<00:06, 3.88it/s]
58%|█████▊ | 36/62 [00:09<00:06, 3.88it/s]
60%|█████▉ | 37/62 [00:09<00:06, 3.89it/s]
61%|██████▏ | 38/62 [00:09<00:06, 3.89it/s]
63%|██████▎ | 39/62 [00:10<00:05, 3.89it/s]
65%|██████▍ | 40/62 [00:10<00:05, 3.90it/s]
66%|██████▌ | 41/62 [00:10<00:05, 3.91it/s]
68%|██████▊ | 42/62 [00:10<00:05, 3.91it/s]
69%|██████▉ | 43/62 [00:11<00:04, 3.91it/s]
71%|███████ | 44/62 [00:11<00:04, 3.92it/s]
73%|███████▎ | 45/62 [00:11<00:04, 3.92it/s]
74%|███████▍ | 46/62 [00:11<00:04, 3.92it/s]
76%|███████▌ | 47/62 [00:12<00:03, 3.92it/s]
77%|███████▋ | 48/62 [00:12<00:03, 3.91it/s]
79%|███████▉ | 49/62 [00:12<00:03, 3.91it/s]
81%|████████ | 50/62 [00:13<00:03, 3.91it/s]
82%|████████▏ | 51/62 [00:13<00:02, 3.91it/s]
84%|████████▍ | 52/62 [00:13<00:02, 3.91it/s]
85%|████████▌ | 53/62 [00:13<00:02, 3.91it/s]
87%|████████▋ | 54/62 [00:14<00:02, 3.91it/s]
89%|████████▊ | 55/62 [00:14<00:01, 3.91it/s]
90%|█████████ | 56/62 [00:14<00:01, 3.91it/s]
92%|█████████▏| 57/62 [00:14<00:01, 3.91it/s]
94%|█████████▎| 58/62 [00:15<00:01, 3.91it/s]
95%|█████████▌| 59/62 [00:15<00:00, 3.91it/s]
97%|█████████▋| 60/62 [00:15<00:00, 3.91it/s]
98%|█████████▊| 61/62 [00:15<00:00, 3.91it/s]
100%|██████████| 62/62 [00:16<00:00, 3.85it/s]
epoch 5 train acc 0.8500504032258065
0%| | 0/21 [00:00<?, ?it/s]
5%|▍ | 1/21 [00:00<00:05, 3.61it/s]
10%|▉ | 2/21 [00:00<00:03, 5.77it/s]
19%|█▉ | 4/21 [00:00<00:01, 8.63it/s]
29%|██▊ | 6/21 [00:00<00:01, 9.90it/s]
38%|███▊ | 8/21 [00:00<00:01, 10.69it/s]
48%|████▊ | 10/21 [00:01<00:00, 11.16it/s]
57%|█████▋ | 12/21 [00:01<00:00, 11.46it/s]
67%|██████▋ | 14/21 [00:01<00:00, 11.64it/s]
76%|███████▌ | 16/21 [00:01<00:00, 11.75it/s]
86%|████████▌ | 18/21 [00:01<00:00, 11.82it/s]
100%|██████████| 21/21 [00:01<00:00, 10.80it/s]
epoch 5 test acc 0.7050012939958592
훈련 정확도는 85% 테스트 정확도는 70% 수준을 가지고 있습니다.
12. 새로운 데이터로 테스트 하기
#토큰화
tokenizer = get_tokenizer()
tok = nlp.data.BERTSPTokenizer(tokenizer, vocab, lower=False)
def predict(predict_sentence):
data = [predict_sentence, '0']
dataset_another = [data]
another_test = BERTDataset(dataset_another, 0, 1, tok, max_len, True, False)
test_dataloader = torch.utils.data.DataLoader(another_test, batch_size=batch_size, num_workers=5)
model.eval()
for batch_id, (token_ids, valid_length, segment_ids, label) in enumerate(test_dataloader):
token_ids = token_ids.long().to(device)
segment_ids = segment_ids.long().to(device)
valid_length= valid_length
label = label.long().to(device)
out = model(token_ids, valid_length, segment_ids)
test_eval=[]
for i in out:
logits=i
logits = logits.detach().cpu().numpy()
if np.argmax(logits) == 0:
test_eval.append("내향")
elif np.argmax(logits) == 1:
test_eval.append("외향")
print(">> 입력하신 레포트는 " + test_eval[0])
#질문 무한반복하기! 0 입력시 종료
end = 1
while end == 1 :
sentence = input("레포트 내용을 입력해주세요 : ")
if sentence == 0 :
break
predict(sentence)
print("\n")
새로운 리포트를 입력하여 결과를 테스트해볼 수 있습니다.
'파이썬 > 파이토치 자연어처리' 카테고리의 다른 글
한국어 버트(KoBERT) (0) | 2022.03.12 |
---|---|
사전 훈련된 임베딩을 사용한 문서분류 전이학습 (0) | 2022.03.10 |
한국어 위키백과 데이터베이스로 GloVe로 학습 (0) | 2022.03.10 |
한국어 위키백과 데이터베이스로 Word2Vec 학습 (0) | 2022.03.09 |
인공지능 참고 싸이트 - 학습 및 데이터 싸이트 모음 (0) | 2022.03.09 |