Bert fine-tuning 설명 - bert fine-tuning seolmyeong

팀원(Team.AK):

  • K(Kim): 김재원, ERICA-전자공학부
  • A(Ahn): 안세윤, ERICA-소프트웨어학부

목차

  1. Introduction: 이게 뭐 하는 건데?
    1. Motivation: 이거 왜 하는 건데?
    2. Purpose: 그래서 목적이 뭔데?
  2. Notion: 데이터 불균형과 BERT가 뭔데?
    1. Notion1: 데이터 불균형 먼저 말해줘!
    2. Notion2: BERT도 알려줘!
  3. Methodology: 그걸 어떻게 할 건데?
  4. Dataset: 그걸 뭘로 할 건데?
    1. 데이터셋 선정
    2. 데이터 전처리
  5. Code & Execution: 코드는 어떻게 했는데?
    1. 클래스 불균형 데이터 문제 해결 방법 선정
    2. 2개의 pre-trained BERT모델 준비
    3. 해결법이 미적용된 데이터로 BERT의 Fine-tuning
    4. 클래스 불균형 데이터 문제 처리
    5. 해결법이 적용된 데이터로 BERT의 Fine-tuning
  6. Evaluation & Analysis: 해보니까 어때?
  7. Conclusion: 그래서 결론이 뭔데?
  8. Reference: 뭐보고 했는데?

1. Introduction: 이게 뭐 하는 건데?

  1-1. Motivation: 이거 왜 하는 건데?

 평생 개만 봐온 사람에게 늑대를 보여주면 개라고 답할 확률이 높겠죠. 머신러닝도 마찬가지입니다. 데이터가 수 십만 개가 있다고 한들, 다른 클래스에 비해 현저히 적은 클래스가 있다면 해당 클래스의 (분류 문제라면) 분류는 어려워집니다. 극단적인 예를 들면 늑대 사진 100장과 개 사진 10,000장을 학습시키면 사람도 구분을 못하는데 기계는 더더욱 개와 늑대를 구분할 수 없습니다. 이런 불균형 데이터 문제를 해결하기 위해 Data Augmentation, Oversampling, Undersampling 등의 방법이 나와있습니다.

여러분은 늑대와 개를 얼마나 잘 구분할 수 있나요? (이미지 출처: wolfsanctuary.co)

 모든 지도학습 머신러닝 모델은 불균형 데이터에 좋은 성능을 보일 수 없습니다. 자연어처리 분야에서 한 획을 그었다고 평가받는 BERT(Bidirectional Encoder Representations from Transformers)도 마찬가지입니다. 무적처럼 보였던 BERT도 불균형 데이터 문제에는 당연하다는 듯이 낮은 성능을 보여줍니다. 따라서 Team.AK는 생각했습니다.

여러 가지 방법으로 데이터 불균형 문제를 해결할 수 있다면 BERT의 성능이 좋아질까?
좋아진다면 얼마나 좋아질까?

이 궁금증을 직접 해결해보기로 했습니다. 

-K-

  1-2. Purpose: 그래서 목적이 뭔데?

 Team.AK의 이 프로젝트 목적은 불균형 데이터 처리(Data Augmentation, Oversampling, Undersampling 등)가 BERT에도 얼마나 성능 향상이 있는가를 처리하지 않은 모델과 비교하는 것입니다. 즉, 불균형 데이터 처리 BERT 모델과 미처리 BERT 모델의 비교입니다. 뒤에 설명한 Random Swaping과 Random Deletion을 적용하여 한국어 BERT 중 KorBERT를 통한 불균형 데이터 성능 향상을 비교한 논문*이 있습니다. 이뿐 아니라 저희가 선정한 방법들에 대하여 얼마나 큰 성능 향상을 보이는지 확인하고 싶었습니다. 여러 방법을 비교하는 것이 아닌 저희가 선택한 한 가지 방법만 비교하는 것인 만큼, 방법 선정에 있어 나름의 근거를 가지고 신중을 기해야겠죠.

간단한 프로젝트 진행과정

 Team.AK가 선정한 데이터셋은 영어 댓글의 악성 댓글 데이터셋입니다. 챕터 '4. Dataset'에서 더 자세히 설명하겠지만, Kaggle의 영어 악성 댓글 분류 챌린지 데이터셋**을 가져왔습니다. 해당 데이터셋은 일반 댓글과 악성 댓글의 비율이 약 9:1 정도로 분포되어 있어 해당 프로젝트에 적합하다는 판단입니다. 이 데이터셋으로 불균형 데이터 문제를 처리하고 BERT 모델에 Fine-tuning 해보겠습니다.

-K-

*) 김정우 외, 「소수 클래스 데이터 증강을 통한 BERT 기반의 유형 분류 모델 성능 개선」, 『2020 온라인 추계학술발표대회 논문집』, 제27권 제2호, 한국정보처리학회, 2020

**) Kaggle, 2017.12.20, Jigsaw/Conversation AI, 「Toxic Comment Classification Challenge」, Toxic Comment Classification Challenge | Kaggle

2. Notion: 데이터 불균형과 BERT가 뭔데?

 이번 프로젝트에서 가장 큰 두 가지의 개념은 데이터 불균형과 BERT입니다. 개념 설명이 위주인 프로젝트가 아닌 만큼 각각에 대해 간단히 설명하겠습니다.

  2-1. Notion1: 데이터 불균형 먼저 말해줘!

 딥러닝 프로젝트를 진행해본 사람이라면 한 번쯤 불균형 데이터에 대해 들어본 적이 있을 것입니다. 분류 문제를 해결할 때 데이터의 분포를 가장 먼저 확인하게 됩니다. 이때 예측해야 하는 결과값이 100:1, 400:1,… 정도로 불균형한 분포를 띄고 있는 데이터들을 불균형 데이터라고 합니다. 아래의 그림은 저희가 선정한 불균형 데이터에 대한 그래프입니다.

이런 불균형 데이터 그대로 예측을 진행하면 과대적합 문제의 발생 가능성이 높아집니다. 데이터가 불균형하면 분포도가 높은 클래스에 가중치를 많이 두기 때문에 모델 자체에서 예측 성능을 높게 평가합니다. 그렇기 때문에 불균형 데이터 문제를 그대로 놔두면 accuracy는 높아질 수 있지만 분포가 작은 값에 대한 precision은 낮은 과대적합 문제가 발생하는 것입니다.

데이터의 불균형성을 해결하는 방법은 다양하지만, 저희는 그중에서 세 가지 방법에 대해서 간단히 언급하겠습니다.

  1) 해결 방법: SMOTE(Synthetic Minority Oversampling Technique)

 SMOTE는 오버샘플링 방법 중 하나로, 소수의 클래스에 속하는 데이터 주변에 원본 데이터와 동일하지 않으면서 소수의 클래스에 해당하는 가상의 데이터를 생성하는 방법입니다.

  2) 해결 방법: Focal loss

 Focal loss는 Object Detection에서 학습 중 클래스 불균형 문제를 손실 함수로 해결하기 위해 고안된 방법입니다. Cross entropy의 클래스 불균형 문제는 백그라운드로 분류될 수 있는 easy negative가 대부분이어서 학습에 비효율적입니다. Focal loss는 Cross Entropy의 이런 클래스 불균형 문제를 개선해, 어렵거나 쉽게 잘못 분류되는 케이스에 더 큰 가중치를 줍니다. Focal loss에 대해 간단히 요약하자면, 잘 찾은 class의 경우에는 loss를 적게 줘 loss 갱신을 거의 하지 못하게 하고, 잘 찾지 못한 class의 경우 loss를 크게 줘서 loss 갱신을 크게 하는 것입니다. 결론적으로 잘 찾지 못한 class에 대해 더 집중해서 학습하도록 하는 방법이라고 볼 수 있습니다.

  3) 해결 방법: EDA(Easy Data Augmentation)

 EDA는 학습 데이터가 부족하거나, 불균형 문제가 발생했을 때, 현재 보유하고 있는 데이터를 변형시켜 데이터의 양을 늘리는 기법입니다. EDA에는 다음과 같은 기법들이 있습니다.

① SR(Synonym Replacement, 동의어 교체): 문장에서 랜덤으로 stop words가 아닌 단어들 중 n개를 선택해 임의로 선택한 동의어들 중 하나로 바꿈

② RI(Random Insertion, 무작위 삽입): stop word를 제외한 나머지 단어들 중, 랜덤으로 단어를 선택하여 동의어를 임의어로 정하고, 이를 각 문장 내에 임의의 자리에 넣음

③ RS(Random Swap, 무작위 교체): 각 문장에서 무작위로 두 단어를 선택해 그 위치를 바꿈

④ RD(Random Deletion, 무작위 삭제): 각 문장 내에서 랜덤하게 단어를 선택해 이를 삭제함

-A-

2-2. Notion2: BERT도 알려줘!

 아까부터 Team.AK가 강조하는 BERT란 무엇일까요? 버트는 사실 알 만한 분들은 아실 1969년부터 지금까지 방영 중인 세서미 스트리트(Sesame Street)의 캐릭터입니다. 흔히 말하는 '노잼 컨셉'이 역설적으로 개그 캐릭터가 되어버린 세서미 스트리트의 버트처럼 구글 BERT도 처음에는 재미없어 보이지만 알면 알수록 재미있는 모델입니다. 이런 특성까지 비슷한 건 구글이 노린 걸까요?

재미없지만 재미있는 BERT (이미지 출처: Codemotion Magazine)

  1) 트랜스포머 설명과 BERT의 구조

 먼저 BERT*가 무엇의 약자인지부터 살펴보겠습니다. BERT는 Bidirectional Encoder Representations from Transformers의 약자입니다. 한국어로 번역하면 트랜스포머스를 사용한 양방향 인코더라고 할 수 있겠네요. 트랜스포머에 대해 간단히 설명하고 넘어가자면 입력된 문장의 토큰마다 Q(Query), K(Key), V(Value) 값을 생성하고, 이 값을 바탕으로 어떤 토큰이 얼마나 중요한지, 즉, 얼마나 해당 토큰에 집중(attention)해야하는지 점수(score)를 매깁니다. 이 점수를 매기는 방식을 병렬적으로 처리합니다. 이를 멀티 헤드 어텐션이라고 하며, 순서를 가지는 RNN 계열 딥러닝 모델이 아닙니다. BERT는 이런 트랜스포머의 인코더 부분이 여러 겹으로 쌓여 있는 구조입니다. 레이어 내부에서 토큰들이 모든 다른 토큰들을 참고합니다. 물론 똑같은 정도로 참고하는 것이 아닌 앞서 언급한 attention score에 따라 다릅니다. 모델의 구조도 장점이지만 BERT의 가장 큰 장점은 사전 훈련(pre-trained) 되어 있다는 것입니다.

  2) BERT의 사전 훈련(pre-training)

 보통 인공지능 모델을 만든다면 학습을 모델 설계자가 목적에 맞게 처음부터 훈련시켜야합니다. 하지만 BERT는 구글이 25억 단어의 위키피디아와 8억 단어의 BookCorpus, 총 33억 단어로 이미 학습한 상태입니다. 4일에 걸쳐 훈련되었다고 하는데 시간 뿐 아니라 어느 정도의 전력을 사용했을지도 궁금해지네요! 이 사전 학습은 크게 마스크드 언어 모델과 다음 문장 예측, 두 가지로 이루어집니다.

 마스크란 어떤 단어나 토큰을 가리는 것을 의미합니다. BERT는 먼저 무작위로 15%의 단어들을 선정합니다. 다음으로 선정된 단어들을 마스킹하거나, 변경하거나, 그대로 둡니다. (대부분 마스킹 합니다.) 마스킹되거나 변경된 단어들을 맞추는 방식으로 학습이 진행됩니다. 이렇게 단어를 맞추는 과정에서 다른 단어들을 참고하기 때문에 양방향성을 가집니다. 또한 label이 없기 때문에 비지도학습이라고 할 수 있겠습니다.

 다음 문장 예측의 경우, 실제 이어진 문장 반과 무작위로 이어붙인 두 개의 문장으로 훈련을 하는 방식입니다. 이어지는 문장인지 아닌지의 label이 존재하므로 이는 지도학습이라고 할 수 있겠습니다.

  3) BERT의 파인 튜닝(fine-tuning)

 이렇게 훈련되어 있으면 우리는 감사해하며 사용해야겠죠? 파인 튜닝이란 사용자가 해결하고자 하는 문제에 대해 출력층을 추가한 뒤에 데이터를 추가로 학습 시키는 것입니다. 파인 튜닝 과정에서는 추가 학습을 하는 동안 모델의 전체 파라미터(가중치나 편향 등)가 변화합니다. 즉, 이미 학습된 모델에 대해 사용자가 원하는 태스크에 맞춰 모델을 '미세 조정'한다고 이해할 수 있습니다. BERT의 fine-tuning으로 해결하고자 하는 과제는 크게 다음과 같은 4가지가 있습니다.

①텍스트 하나에 대한 분류

②토큰의 태깅

③텍스트 쌍 분류/회귀

④질문과 대답

Team.AK가 하려는 악플을 분류하는 태스크의 경우 텍스트를 입력으로 넣으면 해당 텍스트가 악플인지 아닌지를 출력으로 나타내는 문제이므로 ①텍스트 하나에 대한 분류 문제라고 할 수 있습니다. 따라서 데이터 불균형 문제만 처리할 수 있다면 설명한 바와 같이 자연어처리에서 강력한 BERT를 통해 잘 해결할 수 있겠습니다.

-K-

*) Jacob Devlin 외, 「BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding」, arXiv, 2018

3. Methodology: 그걸 어떻게 할 건데?

 그럼 이제 프로젝트를 어떻게 진행할지 설명하겠습니다. 먼저 클래스 불균형 데이터를 선정합니다. 해당 프로젝트는 이진 분류로 문제를 해결해 볼 생각입니다. 그렇다면 이진 분류 문제 중, 클래스가 9:1에서 8:2의 비율을 가지고 있는 데이터 셋을 구해야 합니다. 이렇게 클래스 불균형 데이터를 선정했다면 알고 있거나 조사한 불균형 데이터 해결 방법 중 어떤 방법으로 클래스 불균형 문제를 해결할 것인지 선정합니다. 이렇게 선정 된 방법을 바로 불균형 데이터에 적용하면 적용하기 전과 비교할 수 없겠죠? 먼저, 불균형 미처리 데이터를 fine-tuning할 BERT모델 하나와, 처리된 데이터로 fine-tuning할 BERT모델, 이렇게 두 모델을 불러옵니다. 다음으로 처리되지 않은 클래스 불균형 데이터를 가지고 BERT를 fine-tuning합니다. 그 이후에 불균형 데이터를 처리하고 처리한 데이터를 가지고 fine-tuning합니다. 마지막으로 같은 test set으로 둘의 loss와 accuracy를 비교합니다.

 정리하면 다음과 같습니다.

① 클래스 불균형 데이터 선정

② 클래스 불균형 데이터 해결 방법 선정

③ BERT모델 2개 준비

④ 해결법 미적용된 데이터로 BERT모델 fine-tuning

⑤ 클래스 불균형 데이터 처리

⑥ 해결법이 적용된 데이터로 BERT모델 fine-tuning

⑦ 두 모델의 성능 비교

-K-

4. Dataset: 그걸 뭘로 할 건데?

  4-1. 데이터셋 선정

 이제 불균형 데이터를 정해볼까요? 먼저 Team.AK는 캐글의 악성댓글 분류 대회(Toxic Comment Classification Challenge | Kaggle)를 고려해보았습니다. 해당 데이터셋은댓글을 구분하기 위한 id, 댓글 자체와 악성 댓글 여부, 그리고 악성 댓글일 경우 그 정도나 속성을 알 수 있는 label로 이루어져 있습니다.

df_raw = pd.read_csv(path + '/train.csv') df_raw

이 중에서 toxic 열만 label로 사용하겠습니다. toxic을 label로 사용했을 때 클래스 불균형이 어느정도인지 확인하면 다음과 같습니다.

print_class_num(df_raw, 'toxic')

약 9:1의 비율로 분포하는군요. 이는 Team.AK가 원하는 비율이랑 비슷하지만 부족한 데이터가 10%에 조금 못 미칩니다. 따라서 test 파일의 데이터셋 중, toxic열이 1인 데이터만 가져와서 합쳐보겠습니다. 합친 뒤의 클래스 분포는 다음과 같습니다.

print_class_num(df_data, 'toxic')

큰 차이가 있진 않지만 약 6,000개의 댓글이 추가되어 약 87:13의 비율을 가집니다. 이 정도면 Team.AK가 고려한 클래스 불균형 데이터와 일치합니다. 해당 데이터셋으로 진행하도록 하겠습니다!

-K-

  4-2. 데이터 전처리

 이번에는 데이터를 불러오는 과정부터 불균형 데이터 처리를 제외한 fine-tuning 직전까지 전처리 하는 과정을 설명하겠습니다. 먼저 전처리에 필요한 패키지들을 임포트하겠습니다. 작성은 google Colab 기준입니다.

import tensorflow as tf import os import pandas as pd import numpy as np import matplotlib.pyplot as plt from sklearn.model_selection import train_test_split import re from tensorflow import keras from tqdm import tqdm

다음으로 데이터셋이 저장되어 있는 경로를 지정해줍니다. 앞서 말씀드렸다시피 google Colab으로 작성하였기 때문에 데이터를 다음과 같은 경로에 저장하였습니다.

path = './drive/Shareddrives/AI-X'

이제 데이터를 불러와 볼까요?

df_raw = pd.read_csv(path + '/train.csv') df_raw

앞서 설명한 코드와 같습니다. 그렇다면 이번엔 각각의 클래스가 어느정도 비율로 분포하는지 알아보기 위한 함수를 작성해 봅시다.

def print_class_num(df, label_name): # 이진 클래스의 비율을 알아보기 위한 함수 n_total = len(df) # 데이터의 총 개수 n_neg = len(df.query(label_name + ' == 1')) # label로 사용하는 열의 1 개수 n_pos = len(df.query(label_name + ' == 0')) # label로 사용하는 열의 0 개수 print("부정 댓글: {}개, {:.2f}%".format(n_neg, n_neg*100/n_total)) print("긍정 댓글: {}개, {:.2f}%\n".format(n_pos, n_pos*100/n_total)) values = df[label_name].value_counts() # label로 사용하는 열의 값을 카운트 values.plot(kind='bar') print(values)

이 함수를 사용해서 개수를 출력해 볼까요?

print_class_num(df_raw, 'toxic')

이제 test set으로 사용되었던 파일 중 toxic열이 1, 즉, 모자란 클래스의 데이터만 불러오도록 하겠습니다.

tmp_data = pd.read_csv(path + '/train2.csv') # 미리 test.csv의 이름을 train2.csv로 바꾸어줌 tmp_labels = pd.read_csv(path + '/train2_labels.csv') # label 파일이 따로 존재하기에 해당 파일도 같이 불러옴 tmp_data.set_index('id', inplace=True) tmp_labels.set_index('id', inplace=True) df_raw2 = pd.concat([tmp_data, tmp_labels], axis=1) df_raw2.reset_index(inplace=True) df_raw2.query('toxic == 1', inplace=True) df_raw2

해당 데이터프레임을 기존 데이터프레임인 df_raw에 이어붙여주도록 하겠습니다.

df_raw = pd.concat([df_raw, df_raw2], axis=0) df_raw

데이터가 잘 추가된 것을 확인하였습니다.

이번에는 데이터프레임에서 불필요한 열을 제거해보도록 하겠습니다. 불필요 열은 id열, severe_toxic열, obscene열, threat열, insult열, identity_hate열로 총 6개의 열입니다. 해당 열들을 일일히 제거하는 것은 비효율적이어 보입니다. 그렇다면 필요한 열은 몇 개인지 살펴볼까요? comment_text열과 toxic열, 두 가지입니다. 해당 두 열을 추출하는 것이 더 효과적이어 보입니다. 따라서 다음과 같이 코드를 작성할 수 있습니다.

df_data = pd.concat([df_raw['comment_text'], df_raw['toxic']], axis=1) df_data

원하는 두 열을 제외한 모든 열이 삭제되었습니다.

이제 앞서 작성하였던 print_class_num()함수를 이용해 추가된 데이터의 개수와 비율을 알아볼까요?

print_class_num(df_data, 'toxic')

이제부터는 불용어 제거, 중복 데이터 제거, 특수문자나 의미 없는 공백 등을 제거하는 과정을 거친 후에, train, validation, test 셋으로 데이터를 나눠주도록 하겠습니다.

먼저 비어있는 데이터가 있는지 확인해보겠습니다.

train_ = df_data print(train_.isnull().values.any())

중복되는 데이터는 없어보입니다. 그럼 이제 영어 댓글이므로 정규 표현식을 이용해 영어 알파벳(소문자a부터 소문자z까지, 대문자 A부터 대문자 Z까지)을 제외한 다른 글자들을 지워주도록 하겠습니다. 또한 스페이스 바가 2번 이상 눌린 글들은 하나씩 지워주도록 합시다. 만약 지워서 공백만 남은 데이터는 NaN(Not a Number)변수, 즉 Null을 갖도록 지정해주겠습니다.

train_['comment_text'] = train_['comment_text'].str.replace('[^a-zA-Z ]', '') train_['comment_text'] = train_['comment_text'].str.replace('^ +', "") train_['comment_text'].replace('', np.nan, inplace=True) print(train_.isnull().sum())

처리를 한 다음 comment_text열에 Null 변수가 7개가 생겼습니다. 이제 이를 없애주도록 하겠습니다.

print('NULL제거 전 길이: ', len(train_)) train_ = train_.dropna(how = 'any') print('NULL제거 후 길이: ', len(train_))

7개의 데이터가 삭제된 것을 알 수 있습니다. 이 때까지 한 과정중에서 영어가 아닌 데이터, 공백이 여러개 있는 데이터가 다시 생겼을 수 있습니다. 따라서 같은 과정을 반복해줍니다.

train_.drop_duplicates(subset=['comment_text'], inplace=True) train_['comment_text'] = train_['comment_text'].str.replace('[^a-zA-Z ]', '') train_['comment_text'] = train_['comment_text'].str.replace('^ +', "") train_['comment_text'].replace('', np.nan, inplace=True) train_ = train_.dropna(how = 'any') print('최종 길이: ', len(train_))

이제 BERT의 토크나이저를 불러오기 위해 hugging face사의 transformers를 설치해주도록 하겠습니다.

!pip install transformers

이제 transformers를 이용하여 BERT 토크나이저를 불러오겠습니다.

from transformers import BertTokenizer bert_tokenizer = BertTokenizer.from_pretrained("bert-base-uncased")

토크나이저가 다운로드 완료되었습니다.

토크나이즈를 진행해볼까요? 리스트를 만들어준 뒤에 토크나이즈 된 값을 리스트에 하나씩 넣어주도록 하겠습니다. 이 토크나이즈 과정은 시간이 오래 걸리므로 어느 정도 진행이 되는지 알아보기 위해 tqdm으로 감싸주겠습니다. 모든 토큰화된 문장은 개수가 다릅니다. 따라서 일반적으로 쓰는 30을 최대값으로, 그 이상 값은 지우고 그 이하 값은 패딩해주도록 하겠습니다.

tokenized = [] for sent in tqdm(train_['comment_text']): # tokenized.append(bert_tokenizer(sent)) t = bert_tokenizer.encode_plus(sent, add_special_tokens=True, max_length=30, pad_to_max_length=True, return_attention_mask=True) tokenized.append(t)

몇 분에 걸쳐 토크나이징이 완료되었습니다. 이제

tokenized[:5]

결과창을 전부 캡처할 순 없었지만 딕셔너리 자료형으로 키값은 다음과 같은 세 가지로 구성되어 있습니다. 'input_ids', 'token_type_ids', 'attention_mask'입니다. 이 중 우리는 'input_ids'와 'attention_mask'를 사용할 것이므로 이 둘을 추출해줍니다.

input_ids = [] attention_masks = [] for sent in tokenized: input_ids.append(sent['input_ids']) attention_masks.append(sent['attention_mask']) input_ids=np.asarray(input_ids) attention_masks=np.array(attention_masks)

먼저 train set과 test set을 나눠줍니다.

x_train, x_test, y_train, y_test, mask_train, mask_test = train_test_split(input_ids, train_['toxic'], attention_masks, test_size=0.2, random_state=42) print("x_train: {}개,\ty_train: {}개".format(len(x_train), len(y_train))) print("x_test: {}개,\ty_test: {}개".format(len(x_test), len(y_test))) print("mask_train: {}개,\tmask_test: {}개".format(len(mask_train), len(mask_test)))

이제 train set에서 validation set을 나눠줍니다. 보통 검증 데이터는 훈련 데이터와 2:8로 나누어주는게 일반적입니다.

x_train, x_valid, y_train, y_valid, mask_train, mask_valid = train_test_split(x_train, y_train, mask_train, test_size=0.2, random_state=42) print("x_train: {}개,\ty_train: {}개".format(len(x_train), len(y_train))) print("x_valid: {}개,\ty_valid: {}개".format(len(x_valid), len(y_valid))) print("mask_train: {}개,\tmask_valid: {}개".format(len(mask_train), len(mask_valid)))

이렇게 전처리가 완료되었습니다. 이제 이 전처리한 데이터를 가지고 fine-tuning을 진행해야겠네요!

-K-

5. Code & Execution: 코드는 어떻게 했는데?

  5-1. 클래스 불균형 데이터 문제 해결 방법 선정

 저희는 클래스 불균형 데이터 문제를 해결하는 방법으로, 2-1에서 언급했던 sampling 기법 중 EDA의 RD와 RS를 사용하였는데요! 다른 기법들을 다 제쳐두고 EDA를 선정한 이유는 다음과 같습니다. 앞에서 BERT 토크나이즈를 진행하면서 'input_ids'와 'attention_mask'를 사용한다고 언급했었는데요! 여기서 attention_mask를 건드리지 않고 데이터를 증강해줄 수 있는 방법으로는 EDA가 가장 적합하기에 소수 클래스의 데이터에 EDA를 적용해주었습니다. 

또, 2-2에서 언급한 EDA의 4가지 기법 중 SR과 SI는 유의어의 사전이 필요하기 때문에, 저희가 처리해야하는 데이터와는 방향이 맞지 않다고 판단했습니다. 이러한 이유로 저희는 따로 유의어 사전 등의 조건이 필요하지 않은 RD와 RS를 선정하였습니다.

-A-

  5-2. 2개의 pre-trained BERT모델 준비

 이제 클래스 불균형 데이터로 fine-tuning할 모델 하나와 클래스 불균형을 해결한 데이터로 fine-tuning할 모델 하나, 그렇게 총 두 개의 BERT모델을 만들어주겠습니다. 분류에 사용되는 Sequence Classification로 만들어주겠습니다. BERT의 모든 기능을 사용할 수는 없지만 분류 문제에 빠른 fine-tuning 속도를 보여줍니다.

bert_model1 = TFBertForSequenceClassification.from_pretrained('bert-base-uncased') bert_model2 = TFBertForSequenceClassification.from_pretrained('bert-base-uncased')

불러오려는 모델이 TFBertForSequenceClassification하나이므로 다운로드는 한 번만 진행됩니다.

-K-

  5-3. 해결법이 미적용된 데이터로 BERT의 Fine-tuning

 이 절에서는 클래스 불균형이 미적용된 데이터로 BERT모델의 fine-tuning을 진행하겠습니다. 먼저 옵티마이저를 정해줍니다. 옵티마이저는 Adam 옵티마이저를 사용하도록 하겠습니다. 지정한 옵티마이저와 이진분류가 가능한 binary cross entropy를 loss를 정한 뒤 모델을 컴파일해줍니다.

optimizer = tf.keras.optimizers.Adam(learning_rate=2e-5) bert_sc_model1.compile(optimizer = optimizer, loss='binary_crossentropy', metrics=['accuracy'])

해당 절에서는 bert_sc_model1을 fine-tuning하겠습니다.

이제 fine-tuning을 진행해볼까요? Fine-tuning의 시간은 데이터도 많을 뿐더러 BERT모델 자체도 fine-tuning이 오래 걸리기 때문에 5번의 에포크만 진행하겠습니다. 

history = bert_sc_model1.fit([x_train, mask_train], y_train, epochs=5, validation_data=([x_valid, mask_valid], y_valid), batch_size=32)

약 4시간 30분에 걸쳐 fine-tuning이 완료되었습니다. 성능 평가는 클래스 불균형 데이터를 처리한 데이터로 fine-tuning한 모델과 같이 하겠습니다.

-K-

  5-4. 클래스 불균형 데이터 문제 처리

5-1에서 선정한 방법으로 클래스의 불균형 데이터 문제를 처리해보도록 하겠습니다.

y_train = pd.DataFrame(y_train) x_train = pd.DataFrame(x_train) mask_train = pd.DataFrame(mask_train) xm_train = pd.concat([x_train, mask_train], axis=1) y_train = y_train['toxic'] y_t = np.array(y_train) n_train['toxic'] = y_tnn_train = n_train[n_train['toxic'] == 1]

x_train, y_train, mark_train을 합쳐 n_train을 만들고, n_train의 'toxic'열의 값이 1인 데이터 행만 추출하여 새로운 데이터 프레임인 nn_train을 생성했습니다.

import random rd_train = nn_train.copy() rand_num = random.randint(1, 29) for i in range(len(rd_train)): if(int(rd_train.iloc[i, rand_num]) != 0 & int(rd_train.iloc[i, rand_num]) != 102): rd_train.iloc[i, rand_num] = 103 else: while(int(rd_train.iloc[i, rand_num]) == 0 & int(rd_train.iloc[i, rand_num]) == 102): rand_num = random.randint(1,29) if(int(rd_train.iloc[i, rand_num]) != 0 & int(rd_train.iloc[i, rand_num]) != 102): rd_train.iloc[i, rand_num] = 103 rand_num = random.randint(1,29)

데이터를 증강해주기 위한 방법으로 먼저 RD(Random Deletion)를 이용했습니다. 각 행마다 random으로 열을 골라, 해당 위치의 데이터가 0 또는 102가 아니면, 그 값을 masking 값인 103으로 바꿔 값을 삭제해주었습니다. 

이렇게 생성한 rd_train을 확인해보면, 위의 그림처럼 각 행마다 하나씩 데이터 값이 103으로 바뀐 것을 확인할 수 있습니다.

rs_train = nn_train.copy() rand_1 = random.randint(1, 29) rand_2 = rand_1 while rand_2 == rand_1: rand_2 = random.randint(1, 29) for i in range(len(rs_train)): if (int(rs_train.iloc[i, rand_1]) != 0 & int(rs_train.iloc[i, rand_1]) != 102 & int(rs_train.iloc[i, rand_2]) != 0 & int(rs_train.iloc[i, rand_2]) != 102): rs_train.iloc[i, rand_1], rs_train.iloc[i, rand_2] = rs_train.iloc[i, rand_2], rs_train.iloc[i, rand_1] else: while(int(rd_train.iloc[i, rand_1]) == 0 | int(rd_train.iloc[i, rand_1]) == 102 | int(rs_train.iloc[i, rand_2]) == 0 | int(rs_train.iloc[i, rand_2]) == 102): if (int(rs_train.iloc[i, rand_1]) != 0 & int(rs_train.iloc[i, rand_1]) != 102 & int(rs_train.iloc[i, rand_2]) != 0 & int(rs_train.iloc[i, rand_2]) != 102): rs_train.iloc[i, rand_1], rs_train.iloc[i, rand_2] = rs_train.iloc[i, rand_2], rs_train.iloc[i, rand_1] else: rand_1 = random.randint(1, 29) rand_2 = rand_1 while rand_2 == rand_1: rand_2 = random.randint(1, 29) rand_1 = random.randint(1, 29) rand_2 = rand_1 while rand_2 == rand_1: rand_2 = random.randint(1, 29)

다음으로 RS(Random Swap)을 이용하였는데요! 랜덤으로 두 개의 열을 골라 그 값이 0 또는 102가 아니면 두 데이터 값을 swap 해주는 방식으로 rs_train을 생성해주었습니다.

좌: 변경 전 nn_train / 우: 변경 후 rs_train

변경 전인 nn_train과 비교했을 때 데이터셋이 바뀐 것이 보이시나요? 열을 random으로 선택하였기 때문에 각 행마다 다른 열의 데이터 값이 바뀐 것을 확인할 수 있습니다.

그럼 데이터의 불균형이 전보다 해소 되었는지 확인해볼까요? 

values = result_train['toxic'].value_counts() values.plot(kind='bar') print(values)

기존의 nn_train에 증강해준 데이터인 rs_train과 rd_train을 합쳐 result_train을 생성하고 데이터의 개수를 세보았습니다.

아직 데이터의 불균형이 있는 편이지만, 이전의 데이터셋과 비교했을 때 toxic이 1인 데이터가 3배 증가했음을 알 수 있습니다.

-A-

  5-5. 해결법이 적용된 데이터로 BERT의 Fine-tuning

 이번에는 bert_sc_model2를 클래스 불균형을 어느정도 해결한 데이터로 fine-tuning해봅시다. 5-3과 마찬가지로 모델을 컴파일해줍니다.

bert_sc_model2.compile(optimizer = optimizer, loss='binary_crossentropy', metrics=['accuracy'])

 바로 fine-tuning을 진행해볼까요?

history = bert_sc_model2.fit([x_train_add, mask_train_add], y_train_add, epochs=5, validation_data=([x_valid, mask_valid], y_valid), batch_size=32)

5-3과 같은 조건으로 실험을 진행하기 위해 5번의 에포크로 진행했습니다. 데이터가 많아지자 약 9시간에 걸쳐 완료됐습니다.

-K-

6. Evaluation & Analysis: 해보니까 어때?

 모델1(bert_sc_model1)과 모델2(bert_sc_model2)를 평가하고 비교해볼까요? 각각의 loss와 accuracy를 비교해 보았습니다.

loss, acc = bert_sc_model1.evaluate([x_test, mask_test], y_test) print("loss: ", loss) print("accuracy: {}%". format(acc))

클래스 불균형 데이터로 fine-tuning한 BERT모델의 경우 test set으로 평가하자 정확도가 약 12.7%로 나타났습니다.

loss, acc = bert_sc_model2.evaluate([x_test, mask_test], y_test) print("loss: ", loss) print("accuracy: {}%". format(acc))

클래스 불균형을 약간 해결한 데이터로 fine-tuning을 진행한 모델은 정확도가 약 21.3%로 약간 증가한 것을 알 수 있습니다.

-K-

7. Conclusion: 그래서 결론이 뭔데?

 클래스 불균형 데이터를 1:1 정도로 맞춘 것도 아니고 약 2:1로 맞췄는데도 약 8.6%p라는 유의미한 정확도 향상이 일어났습니다. 낮은 정확도는 시간 관계상 fine-tuning진행에 에포크를 5회로 가져갔기 때문으로 보입니다. 따라서 에포크를 더 가져가거나 loss나 정확도가 높은 시점에 early stopping을 진행했다면 더 좋은 결과를 얻을 수 있었을 것입니다. 위의 훈련 과정 중, validation의 정확도가 높은 에포크를 관찰해보면 다음 사진과 같이 나타납니다.

클래스 불균형 데이터로 fine-tuning 진행 중 validation의 정확도가 가장 높았던 지점
클래스 불균형 해결 데이터로 fine-tuning 진행 중  validation의 정확도가 가장 높았던 지점

에포크가 5회 진행된 후에도 test-set과 validation-set의 loss와 정확도가 거의 비슷하게 나타났기 때문에 해당 BERT 모델에 체크포인트나 얼리 스타핑을 사용했다면 5회 에포크 만으로도 각각 60%와 80% 정도의 정확도를 가지는 모델을 얻을 수 있었을 것이라 생각합니다. 즉, validation으로 판단했을 때, 가장 높았던 정확도를 가졌던 시점을 기준으로 성능향상을 분석해보자면 약 20%p의 성능 향상이 일어났을 것입니다. 이를 바탕으로 클래스 불균형 데이터로 BERT를 fine tuning할 때에는, Random Deletion과 Random Swaping으로 증강된 데이터를 사용하면 정확도에 유의미한 성능 향상이 있다는 것을 알 수 있습니다.

-K-

다음은 해당 프로젝트를 설명한 유튜브 영상 링크입니다

//youtu.be/2STsjk4ciKI

▶  K(Kim): 김재원, ERICA-전자공학부

- 주제 선정

- 데이터셋 선정

- 데이터셋 전처리

- BERT 개념 설명

- 클래스 불균형 처리 전후 BERT모델 fine-tuning

- Fine-tuning된 두 BERT 모델 분석 및 비교

- 작업 부분 블로그 글 작성

- 프로젝트 설명 유튜브 영상 제작

   ▶  A(Ahn): 안세윤, ERICA-소프트웨어학부

- 주제 선정

- 불균형 데이터 개념 설명

- 불균형 데이터 처리 방법 설명

- 불균형 데이터 처리

- 작업 부분 블로그 글 작성

8. Reference: 뭐보고 했는데?

BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding

//arxiv.org/pdf/1810.04805.pdf

소수 클래스 데이터 증강을 통한 BERT 기반의 유형 분류 모델 성능 개선

KIPS_C2020B0033.pdf (manuscriptlink-society-file.s3-ap-northeast-1.amazonaws.com)

Attention is All you Need (neurips.cc)

//swatimeena989.medium.com/bert-text-classification-using-keras-903671e0207d

BERT Text Classification using Keras

The BERT (Bidirectional Encoder Representations from Transformers) model was proposed in BERT: Pre-training of Deep Bidirectional…

swatimeena989.medium.com

 //velog.io/@jaehyeong/Fine-tuning-Bert-using-Transformers-and-TensorFlow

[Basic NLP] Transformers와 Tensorflow를 활용한 BERT Fine-tuning

이번 포스트에서는 🤗HuggingFace의 Transformers 라이브러리와 Tensorflow를 통해 사전 학습된 BERT모델을 Fine-tuning하여 Multi-Class Text Classification을 수행하는 방법에 대해 알아보고자 한다. 특히 이번

velog.io

//freesearch.pe.kr/archives/4963

버트(BERT) 파인튜닝 간단하게 해보자. - from __future__ import dream

작년 말에 GluonNLP 0.6버전 개발에 활발하게 참여하였는데, 그중에서 사용자들이 편리하게 사용할만한 부분에 대해 소개하기 위해 글을 써봤다. 다들 버트, 버트 하는데, 어떻게 사용할지 모를 분

freesearch.pe.kr

//moondol-ai.tistory.com/241

KoBERT 쉽게 따라하고 간단한 fine-tuning 하기

"데이터셋에 대한 문의가 많습니다. 해당 데이터셋은 제가 프로젝트의 일환으로 "하이닥"이란 웹 사이트에서 크롤링으로 수집한 것입니다. 아시다시피 제 3자 데이터를 수집한 것을 다시 공유하

moondol-ai.tistory.com

//blog.nerdfactory.ai/2019/04/25/learn-bert-with-colab.html

너드팩토리

너드팩토리에서 운영하는 블로그 입니다.

blog.nerdfactory.ai

//ebbnflow.tistory.com/164

[BERT] BERT에 대해 쉽게 알아보기4 - BERT 파인튜닝

● BERT 파인튜닝 2편에서 구글에서 제공하는 공식 BERT코드로 대량의 위키디피아 코퍼스로 사전훈련하여 생성한 모델을, 이번 포스팅에서는 전이학습시켜 다른 nlp task에 적용하는 파인튜닝 실습

ebbnflow.tistory.com

//docs.likejazz.com/bert/

BERT 톺아보기 · The Missing Papers

BERT 톺아보기 17 Dec 2018 어느날 SQuAD 리더보드에 낯선 모델이 등장했다. BERT라는 이름의 모델은 싱글 모델로도 지금껏 state-of-the-art 였던 앙상블 모델을 가볍게 누르며 1위를 차지했다. 마치 ELMo를

docs.likejazz.com

//wikidocs.net/44249

6) 네이버 영화 리뷰 감성 분류하기(Naver Movie Review Sentiment Analysis)

이번 챕터에서는 영어 데이터가 아닌 한국어/한글 데이터에 대해서 텍스트 분류를 수행해보겠습니다. 방법 자체는 영어 데이터에 대한 텍스트 분류와 크게 달라지지는 않았습니다. ...

wikidocs.net

//tensorflowkorea.gitbooks.io/tensorflow-kr/content/g3doc/api_docs/python/array_ops.html

텐서 변환 · 텐서플로우 문서 한글 번역본

tensorflowkorea.gitbooks.io

//www.yuyongze.me/blog/BERT-text-classification-movie/

BERT text classification on movie sst2 dataset

State-of-art NLP model using transformers

www.yuyongze.me

//atheros.ai/blog/text-classification-with-transformers-in-tensorflow-2

Text classification with transformers in Tensorflow 2: BERT, XLNet

Text classification with transformers in TensorFlow 2 and Keras API. How to fine-tune BERT and XLNet on a text classification problem on IMDB reviews dataset.

atheros.ai

//towardsdatascience.com/best-practices-for-nlp-classification-in-tensorflow-2-0-a5a3d43b7b73

Best Practices for NLP Classification in TensorFlow 2.0

Use Data Pipelines, Transfer Learning and BERT to achieve 85% accuracy in Sentiment Analysis

towardsdatascience.com

//huggingface.co/transformers/model_doc/bert.html#tfbertforsequenceclassification

BERT

Overview: The BERT model was proposed in BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding by Jacob Devlin, Ming-Wei Chang, Ke...

huggingface.co

//huggingface.co/transformers/training.html#fine-tuning-with-keras

Fine-tuning a pretrained model

In this tutorial, we will show you how to fine-tune a pretrained model from the Transformers library. In TensorFlow, models can be directly trained using Ker...

huggingface.co

//stackoverflow.com/questions/60463829/training-tfbertforsequenceclassification-with-custom-x-and-y-data

Training TFBertForSequenceClassification with custom X and Y data

I am working on a TextClassification problem, for which I am trying to traing my model on TFBertForSequenceClassification given in huggingface-transformers library. I followed the example given on...

stackoverflow.com

//zzaebok.github.io/deep_learning/nlp/Bert-for-classification/

BERT를 이용한 Classification 예제

zzaebok.github.io

BERT: how Google changed NLP - Codemotion Magazine

BERT: how Google changed NLP - Codemotion Magazine

A brief overview of the history behind NLP, arriving at today's state-of-the-art algorithm BERT, and learning how to use it in Python.

www.codemotion.com

Toplist

최신 우편물

태그