-
BERT로 다중 감정 분류하기(2)개발기록 2022. 6. 11. 16:23
https://imchangrok.tistory.com/26
BERT로 다중 감정 분류하기(1)
저번에는 네이버 영화 리뷰 데이터로 이진 감정 분류를 했다면, 이번에는 다중 감정 분류를 해봤다. 아래에서 만든 모델을 기반으로 만들었다. https://imchangrok.tistory.com/25 BERT로 네이버 영화 감성
imchangrok.tistory.com
저번에 이어서, 다중감정 분류를 마저 해보겠다. 이번에는 accuracy를 높히기보다는, 코드를 좀 보기좋게 수정하는 작업을 많이 진행했다. 맨 처음에 기반이 된 nsmc분류 모델이 이것저것을 참고해서 작성되었기 때문에, 다소 주먹구구로 작성된 부분이 있었다. 이런 부분을 많이 수정했다. 중간에 막혔던 부분이 있었지만 어찌저찌 잘 해결했다.
웹에서 jupyter를 사용하던 것도, remote-ssh를 이용해서 vscode에서 연결하는 것에 성공해 vscode로 옮겼다.
문장별 길이 분포 가장 먼저 확인한 건, 저번 포스팅에서 언급해던대로, 데이터로 받은 문장들의 길이였다. BERTTokenizer로 encode할 때, padding을 해줘야 해서 max_length가 필요한데, 그냥 어림잡아 512로 설정해놓은 상태였다. hist() 함수를 이용해 확인해보니 대부분 100글자 이내였다. tokenize를 해도 길이가 많이 커지지는 않을 것이라고 생각해서 max_len을 128로 설정했다.
train안에서 encode 작업을 하는 모습 그 다음으로 바꾼 것은, 학습루프 안에서 encode를 하는 문제였다.
미리 전처리를 하고 들어가는게 깔끔하고, 맞을 것 같다는 생각이 들어서 Dataset 클래스를 수정했다.
class MyDataset(Dataset): def __init__(self, df): self.df = df def __len__(self): return len(self.df) def __getitem__(self, idx): text = self.df.iloc[idx, 0] label = self.df.iloc[idx, 1] return text, label
기존 Dataset은 단순히 getitem정도밖에 구현해놓지 않았다. __init__에서 미리 encode해서 저장하기로 했다.
class MyDataset(Dataset): def __init__(self, df): total_list = [] for index, row in tqdm(df.iterrows(), total=df.shape[0]): text = row[0] encoded_list = tokenizer.encode(text, add_special_tokens=True, max_length=max_len, padding='max_length', truncation=True) total_list.append(encoded_list) self.df = pd.DataFrame({'사람문장' : total_list, '감정_대분류' :df['감정_대분류']}).reset_index(drop=True) def __len__(self): return len(self.df) def __getitem__(self, idx): text = np.array(self.df.iloc[idx, 0]) label = self.df.iloc[idx, 1] return text, label
그래서, 아래와같이 클래스를 수정했다. 이 작업을 하는데 정말 애를 먹었다. __getitem__에서 text=np.array()...부분을 추가해야하는걸 알아내는게 정말 힘들었다. init에서 dataframe에 단순히 list 가 들어가게되는데, 이를 pytorch 의 DataLoader에서는 하나의 리스트로 인식하는게 아니라, 각 인덱스마다 배치로 가져왔다.
하나의 리스트를 인덱스마다 배치로 가져오는 모습 보면, 첫 리스트에 다 101이 들어있는걸 볼 수 있는데, 이는 tokenzie할때 add_special_tokens=True로 해서 CLS 토큰이 추가되어서 모두 CLS 토큰을 가져온 것이다. 내가 원했던 건, 101, 9689, ... 이런식으로 한 문장을 인코딩한 것 전체를 배치로 불러오는 것이었다.
collate_fn등을 사용해봐도 처음 사용해보는 것이라그런지 해결되지 않았다. stackoverflow에 나와 비슷한 일을 겪는 사람이 있어 참고해서 해결이 가능했다.
Why pytorch DataLoader behaves differently on numpy array and list?
The only difference is one of the parameter passed to DataLoader is in type "numpy.array" and the other is in type "list", but the DataLoader gives totally different results. You can use the follo...
stackoverflow.com
이 포스팅이 뭐가 문제인지 깨닫게 해줬고
pytorch custom dataset: DataLoader returns a list of tensors rather than tensor of a list
import torch class Custom_Dataset(torch.utils.data.dataset.Dataset): def __init__(self, _dataset): self.dataset = _dataset def __getitem__(self, index): example, target = ...
stackoverflow.com
이 포스팅을 보고 np.array를 추가해서 해결할 수 있었다.
일일히 correct개수와 len을 계산하는 모습 그 다음으로는 accuracy 측정 방식을 바꿨다. 일일히 labels의 길이를 세고있어서, 변수를 너무 많이 사용한 것 같은 느낌이 들었다.
def categorical_accuracy(logits, label): top_logits = torch.argmax(F.softmax(logits, dim=1), dim=1) correct = top_logits.eq(label).sum() return correct
위와 같은 함수를 정의해 맞은 개수를 return하는 함수를 만들어서 train과 eval모두 사용하게 했다.
print('[Epoch {}/{}] itr {} -> Train Loss: {:.4f}, Accuracy: {:.3f}'.format(epoch+1, epochs, itr, epoch_loss/(batch_id+1), epoch_acc/((1+batch_id)*batch_size)))
출력하는 방식도 label을 다 더한것으로 나누는 것이 아니라, (1+batch_id)*batch_size로 나누었다.
이 방식이 잘 작동하긴 하지만, batch의 마지막에서는 잘 작동할지 의문이 든다. 그래도 큰 차이는 나지 않을 것 같아 일단은 내비두었다.
def predict(sentence): df = pd.DataFrame({'사람문장':[sentence], '감정_대분류':[0]}) dataset = MyDataset(df) dataloader = DataLoader(dataset, batch_size=batch_size, shuffle=True, num_workers=0) model.eval() for batch_id, (text, label) in tqdm(enumerate(dataloader), total=len(dataloader)): sample = text.to(device) with torch.no_grad(): outputs = model(sample) logits = outputs[0] return [k for k, v in emodict.items() if v == torch.argmax(logits)][0]
모델이 잘 학습되었는지 여태는 그냥 accuracy만 체크했는데, 실제로 문장을 넣어보고 싶어서 predict 함수를 만들었다.
test할 때 구조를 참고했고, label을 그냥 임의로 넣은다음 logit과 비교하지 않고 logit만 출력하는 방식으로 구현했다.
predict 함수 예시 몇몇 문장을 넣어봤는데, 나름 잘 예측을 잘 하는 것 같다. 일단 accuracy를 올리는 건 좀 더 나중에 생각하기로 했다.
이렇게 만든 모델을 내가 원래 만들고 싶은 서비스에 적용하는 것을 먼저 진행할 것이다.
'개발기록' 카테고리의 다른 글
쿠버네티스 용어 한줄 요약 정리 (1) 2023.04.23 BERT로 다중 감정 분류하기(1) (0) 2022.06.02 BERT로 네이버 영화 감성 분류 (0) 2022.06.01