14. BERT의 이해와 간단한 활용

히나·2024년 6월 3일

들어가기에 앞서

해당 글은 "파이썬 텍스트 마이닝 완벽 가이드"를 기반으로 작성되었습니다.

BERT(Bidirectional Encoder Representations from Transformers)는 현재 텍스트 마이닝 딥러닝 모델 중 가장 중요한 모델로 간주됩니다. 거의 모든 텍스트 마이닝 분야에서 우수한 성능을 보여주기 때문입니다. BERT는 다양한 파생 모델을 포함해 주어진 작업이나 상황에 따라 선택할 수 있습니다. 이 모델은 고유한 학습 방법을 사용하여 우수한 성능을 달성합니다. BERT에 대한 완전한 이해를 위해서는 언어 모델, 학습 방법, 그리고 BERT의 구조에 대한 이해가 필요합니다.

14.1 왜 언어 모델이 중요한가?

언어 모델은 문장이나 단어 시퀀스에 확률을 할당하는 모델로, 더 자연스러운 문장에 더 높은 확률을 부여합니다. 잘 학습된 언어 모델은 문장을 잘 완성하고, GPT와 같은 모델은 인간과 유사하게 글을 만들어냅니다. 언어 모델은 분류, 문서 요약, 추론, 문서 유사도 계산, 질의 응답 등에도 활용될 수 있습니다. 또한, 언어 모델을 이용한 학습은 언어 이해를 높이는 비지도 학습도 가능합니다.

그렇다면 언어모델로 학습한 모형을 문서 분류와 같은 지도학습에 사용할 수 있을까요?

가능합니다. 먼저 언어모델로 언어에 대한 이해를 높이고, 추가로 미세조정학습으로 지도학습을 수행함으로써 분류 성능을 갖게 합니다. 이를 사전학습 언어모델이라고 합니다.

14.2 사전학습 언어모델의 이론적 이해

언어모델은 단어의 시퀀스에 대해 확률을 할당하는 모델을 말하며, 이 확률은 아래 식과 같이 계산할 수 있습니다.

p(w1,w2,...,wn)=i=1Np(w1w1,w2,...,wi1)p(w_1, w_2, ..., w_n) = \prod_{i=1}^{N} p(w_1| w_1, w_2, ..., w_{i-1} )

위 식에서 w1,w2,...,wnw_1, w_2, ..., w_{n}이 어떤 단어의 시퀀스 혹은 문장이라고 가정하면, 이 시퀀스가 나타날 확률은 각 단어들의 결합확률로 표현되며, 식에서와 같이 조건부 확률의 곱으로 계산됩니다. 이때 p(w1w1,w2,...,wi1)p(w_1| w_1, w_2, ..., w_{i-1} )는 언어모델에 따라 다양한 방법으로 계산됩니다.

언어모델링은 이러한 언어모델을 학습하고 사용하는 프로세스로 정의됩니다.

이때 위 식에서 설명한 조건부 확률을 자연어 처리 작업 관점에서 다시 해석해 보면, 주어진 input에 기반하여 output의 확률을 추정하는 p(outputinput)p(output|input)의 형태로 볼 수 있습니다. 여기서 주어진 자연어처리 작업의 input은 어떤 단어의 시퀀스이고, output은 주어진 문제에 따라 하나의 단어 혹은 값이 되거나 혹은 단어의 시퀀스가 되기도 합니다.

즉 언어모델을 학습한다는 것은 문서 분류, 번역, 문서 요약, 질의 응답과 같은 다양한 자연어 처리 문제를 해결할 수 있는 모델로 자연스럽게 확장할 수 있습니다. 트랜스포커, GPT, BERT와 같은 사전학습 모델은 이와 같은 방식으로 지난 몇 년간 눈부신 속도로 발전해 왔다고 할 수 있습니다.

사전학습은 목표 작업에 대해 직접 학습하지 않으므로 바로 적용하기에는 한계가 있습니다. 그럼에도 불구하고 사전학습이 의미를 갖는 이유는 언어 모델의 기본적인 형태가 다양한 자연어 처리 문제로 쉽게 확장될 수 있는 구조이기 때문입니다. 즉 단어의 시퀀스에서 어떤 출력을 생성한다는 점에서 공통점을 가지고 잇다고 할 수 있습니다.

사전학습 언어모델의 장점은 두 가지 관점에서 설명될 수 있습니다.

  1. 사전학습 언어모델은 다양한 자연어 처리 작업의 정확도를 크게 높일 수 있습니다.
  2. 사전학습 언어모델은 머신러닝 기반의 자연어 처리 모형 학습의 부담을 크게 줄였습니다.

위의 두 가지 장점은 모두 사전학습 언어 모델을 이용한 전이학습이 가능하다는 점에서 발생합니다. 전이학습은 다른 분야에서 학습한 결과를 재사용함으로써 학습의 속도와 성능을 모두 향상할 수 있는 방법을 말합니다. 다만 이때 두 분야의 지식과 모형의 유사성이 높아야 큰 효과를 낼 수 있습니다.

14.3 BERT의 구조

BERT는 언어 모델 기반의 학습을 이용해 언어에 대한 이해를 높이는 데 목적이 있습니다. BERT는 트랜스포머에서 인코더 부분만 사용한 모형으로 이 인코더의 특징은 양방향 셀프 어텐션을 구현하고 있다는 것입니다.

왜 BERT는 이것을 강조하고 있을까요? 이유는 GPT와 비교해 설명하고자 했기 때문입니다. GPT는 인코더에서 디코더로의 어탠션은 생략되어있고, 셀프 어텐션은 순방향만 적용됩니다. 반면 BERT는 트랜스포머의 인코더 부분을 떼어서 사용했습니다. 반면 BERT는 트랜스포머의 인코더 부분만 떼어서 적용하였습니다. BERT는 양방향 셀프 어텐션을 모두 활용할 수 있습니다. 이부분이 BERT의 핵심이 되며 그 외 몇 가지 아이디어들이 추가된 구조입니다.


위 그림은 방금 설명한 BERT와 GPT의 구조적 차이 외에 ELMo의 차이점도 함께 보여줍니다. BERT는 양뱡향 어탠션이 있는 반면, GPT에는 순방향 셀프 어텐션만 있는 것을 볼 수 있습니다. ELMo는 양방향 LSTM구조로 되어있습니다.

14.4 언어모델을 이용한 사전학습과 미세조정학습

BERT의 학습은 사전학습과 미세조정학습의 두 단계로 나뉘어집니다. 미세조정학습은 실제 수행하고자 하는 작업에 대한 지도학습입니다.

먼저 사전학습에 대해 알아봅시다. 사전학습은 언어 모델 학습으로 구성되는데, BERT의 사전학습은 지금까지 배운 언어 모델 학습과 차이가 있습니다. BERT는 양방향 셀프 어텐션 인코더로 어떻게 언어 모델을 학습하는 것일까요?

BERT는 masking, 즉 단어를 가리고, 가린 단어를 예측하게 하고 있습니다. 즉 순서와 관계 없이, 문장 안에서 랜덤한 위치의 단어를 지우고, 모형이 이 단어들을 예측하도록 한 것입니다. 이를 masked language model이라고 부릅니다.

가려진 단어는 문장의 중간에 위치하며, 양쪽에 단어들이 있어 양방향 셀프 어텐션을 모두 이용해 예측하는 것이 가능합니다. 셀프 어텐션을 모두 활용한다는 점에서 언어에 대한 이해를 더 높일 수 있을 것으로도 기대할 수 있습니다.

추가적인 아이디어는 한 번에 두 개의 문장을 다룰 수 있도록 한 것입니다. 트랜스포머와는 달리 BERT는 인코더만으로 구성되어 있는데, 어떻게 두 문장을 다루도록 학습할 수 있을까요? BERT의 아이디어는 두 문장을 구분하는 토큰을 정의하고, 두 문장 사이에 넣어서 하나의 시퀀스를 만든 후에 인코더에서 한 번에 처리하도록 하는 것입니다.

BERT에서 두 문장에 대한 학습 목표는 순서를 맞히는 것으로 하였습니다. 즉 A와 B 두 문장을 주고 순서가 올바른지 거꾸로인지 예측하도록 하였습니다. 이 경우에도 문서로부터 문장의 순서를 알 수 있으므로 직접적인 형태는 지도학습처럼 보이지만 문서에 대한 라벨이 필요 없단ㄴ 점에서는 비지도학습으로 볼 수 있습니다.

이와 같이 마스크를 이용한 언어 모델과 두 문장을 이용한 사전학습이 완료되면 다음 단계에서는 주어진 목표에 맞게 학습을 하는 미세조정학습을 실시합니다.

그림 14.3은 논문에 소개된 것 중 분류와 관련한 두 개의 작업을 보여줍니다.
(b)의 작업은 문장 분류를 다루고 있습니다. BERT에는 분류를 위한 특별한 토크나이저가 있습니다.

이 중요한 토큰 중 하나는 '[CLS]'로, 분류에 사용되며 다른 단어와 동일하게 모델의 끝까지 이어지고 필요한 어텐션을 학습합니다. 문장 분류에서는 이 토큰의 최종 출력을 사용하며, 다른 단어들의 최종 값은 임베딩 용도로 사용됩니다.

최근에는 BERT의 성능을 향상시키기 위해 단어 임베딩을 양방향 LSTM이나 CNN 모델의 입력으로 사용하기도 합니다. 또한, 두 문장을 입력으로 하는 분류 문제는 MNLI 데이터셋을 예로 들 수 있으며, 미세조정학습을 통해 사전학습된 가중치들이 목표에 맞게 조정됩니다. 미세조정학습을 원하는 방식에 따라 모델을 가져와 직접 학습하거나, 이미 미세조정학습이 완료된 모델을 사용할 수 있습니다.

14.5 사전학습된 BERT 모형의 직접 사용 방법

BERT는 사전학습된 모형을 전이학습으로 활용할 수 있다는 점이 가장 매력적이라고 할 수 있습니다. 허깅체페이스는 자연어 분야에서 가장 널리 알려진 AI 커뮤니티로 트랜스포머, BERT, GPT를 비롯해 거의 모든 자연어 처리 딥러닝 모형들을 제공합니다.

허깅페이스 사이트에 가면 트렌스포머와 관련한 모형들에 대해 설치 및 사용법이 설명되어 있습니다. 제공되는 모형이 딥러닝 모형이므로 텐서플로(2.0)이상 또는 파이토치가 필요합니다. 트랜스포머 라이브러리는 pip 또는 conda 명령어로 설치할 수 있습니다.

pip3 install transformers # 따로 alias 지정했다면 pip로 하면 된다.

파이프라인은 다음과 같이 간편하게 사용할 수 있습니다. 아래에서는 작업을 sentiment-analysis로 지정하여 파이프라인 분류기를 생성하고, 결과를 점수와 함께 출력했습니다.

from transformers import pipeline

clf = pipeline('sentiment-analysis')
result = clf('what a beautiful day')[0]
print("감성 분석 결과 : %s, 감성스코어 : %0.4f" % (result['label'], result['score']))

실행 결과

No model was supplied, defaulted to distilbert/distilbert-base-uncased-finetuned-sst-2-english and revision af0f99b (https://huggingface.co/distilbert/distilbert-base-uncased-finetuned-sst-2-english).
Using a pipeline without specifying a model name and revision in production is not recommended.
All PyTorch model weights were used when initializing TFDistilBertForSequenceClassification.

All the weights of TFDistilBertForSequenceClassification were initialized from the PyTorch model.
If your task is similar to the task the model of the checkpoint was trained on, you can already use TFDistilBertForSequenceClassification for predictions without further training.
감성 분석 결과 : POSITIVE, 감성스코어 : 0.9999

파이프라인에 보형을 지정하지 않으면 자동으로 사전학습된 모형을 사용하는데, 메시지를 보면 어떤 모형을 사용했는지 알 수 있습니다. 감성분석에서는 distilbert/distilbert-base-uncased-finetuned-sst-2-english가 사용되었습니다.

다음단계에서는 문서 생성을 해봅시다. 여기서는 GPT-2를 사용할 것입니다.

from transformers import pipeline

text_generater = pipeline('text-generation')
result = text_generater('Alice was beginning to get very tired of sitting by her sister on the bank,')
print(result[0]['generated_text'])

실행 결과

Alice was beginning to get very tired of sitting by her sister on the bank, and it wouldn't take much. "I am going to have to go talk to somebody right now. I'm very concerned," she said with a laugh.

14.6 자동 클래스를 이용한 토크나이저와 모형의 사용

위와 같이 간단한 작업을 한다면 사용법은 매우 간단합니다. 그러나 이를 활용해서 복잡한 작업을 한다면 어려워집니다. BERT 모형은 토큰 수 512개 제한 등의 제약이 있습니다.

다음은 두 문장의 의미적 유사성을 판단하는 예제입니다. 아래에서는 자동 클래스(Auto Classes)를 사용해 적절한 토크나이저와 모형을 선택할 수 있습니다.

트랜스포머는 파이토치와 텐서플로를 모두 지원하며, 둘 중 하나를 선택할 때는 본인에게 맞는 것을 고르면 됩니다. bert-base-cased-finetuned-mrpc 모형은 MRPC 데이터셋으로 미세 조정된 모형으로, 두 문장의 의미적 유사성을 예측할 수 있습니다.

허깅페이스의 pretrained_models 페이지에서 다양한 사전학습 모형을 확인할 수 있습니다.

from transformers import AutoTokenizer, AutoModelForSequenceClassification
import torch
tokenizer = AutoTokenizer.from_pretrained("bert-base-cased—finetuned-mrpc")
model = AutoModelForSequenceClassification.from_pretrained(
"bert-base-cased-finetuned-mrpc"
)

input_sentence = "She angered me with her inappropriate comments, rumor-spreading, and disrespectfulness at the formal dinner table"
target_sequence = "She made me angry when she was rude at dinner"

tokens = tokenizer(input_sentence, target_sequence, return_tensors="pt")

logits = model(**tokens).logits

results = torch.softmax(logits, dim=1).tolist()[0]

for i, label in enumerate(['no', 'yes']):
    print(f"{label}: {int(round(results[i] * 100))}%")

실행 결과

no: 29%
yes: 71%

bert-base-cased-finetuned-mrpc 모형을 사용해 적절한 토크나이저와 모형을 생성했습니다. AutoTokenizerAutoModelForSequenceClassification은 사전학습 모형의 이름을 이용해 적절한 토크나이저와 모형을 자동으로 선택합니다. 토크나이저는 BERT에 최적화된 토큰화 방식을 사용합니다. 전문가가 되려면 토크나이저를 상세히 알아야 하지만, 지금은 직접 토크나이저를 사용하는 것에 만족합시다.

테스트를 위해 의미적으로 유사한 두 문장을 정의합니다. 모형이 제대로 작동하면 두 문장의 의미적 유사성이 높다고 판단합니다. 토크나이저로 토큰화하고 모형을 통과시켜 예측 결과를 얻습니다. logits는 소프트맥스를 적용하기 이전의 결과로, 확률값을 얻기 위해 소프트맥스를 적용합니다. 라벨은 보통 0, 1 순서로, 1이 '의미적으로 유사하다'라는 결론을 나타냅니다. 결과는 두 문장이 의미적으로 유사할 확률이 71%임을 보여줍니다. 관련 없는 문장에 대해서도 동일한 테스트를 실행할 수 있습니다.

그러면 관련 없는 문장에 대해서도 실행해봅시다.

target_sequence = "The boy quickly ran across the finish line, seizing yet another victory"

tokens = tokenizer(input_sentence, target_sequence, return_tensors="pt")
logits = model(**tokens).logits
results = torch.softmax(logits, dim=1).tolist()[0]
for i, label in enumerate(['no', 'yes']):
    print(f"{label}: {int(round(results[i] * 100))}%")

실행 결과

no: 95%
yes: 5%

"No"가 95%로 잘 작동하는 것을 확인할 수 있습니다. 파이프라인을 사용하는 것보다 할 수 있는 것이 조금 더 많아졌지만, 아직 본격적인 작업을 하기에는 부족한 느낌이 듭니다. 이제 BERT를 이용한 감성 분석 작업을 통해 BERT에 익숙해져봅시다. NLTK의 영화 리뷰 데이터를 가져와서 라벨을 0과 1로 변경하고, 학습 데이터셋과 테스트 데이터셋으로 분리합니다. 이 분리는 테스트 데이터셋에 대한 평가와 이전 결과와의 비교를 위한 것으로, 학습 데이터셋에 대한 학습은 수행하지 않고 사전학습된 모형을 즉시 적용합니다.

import nltk
from nltk.corpus import movie_reviews
from sklearn.model_selection import train_test_split
import numpy as np
nltk.download('movie_reviews')

fileids = movie_reviews.fileids()

reviews = [movie_reviews.raw(fileid) for fileid in fileids]
categories = [movie_reviews.categories(fileid)[0] for fileid in fileids]

label_dict = {'pos':1, 'neg':0}
y = np.array([label_dict[c] for c in categories])
X_train, X_test, y_train, y_test = train_test_split(reviews, y, test_size=0.2, random_state=7)

print('Train set count: ', len(X_train))
print('Test set count: ', len(X_test))

실행 결과

Train set count:  1600
Test set count:  400

NLTK의 영화 리뷰는 길이가 길어 위에서 사용한 감성분석 파이프라인을 사용하면 에러가 발생합니다. 따라서 더 본격적으로 BERT를 이용해서 실습을 진행해봅시다. 다만 BERT는 많은 메모리를 사용하므로 10개씩 잘라서 모형을 돌리고 결과를 합쳐 성능을 살펴봅시다.

from transformers import AutoTokenizer, AutoModelForSequenceClassification
import torch
import torch.nn.functional as F

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

tokenizer = AutoTokenizer.from_pretrained(
"distilbert-base-uncased-finetuned-sst-2-english"
)

model = AutoModelForSequenceClassification.from_pretrained(
"distilbert-base-uncased-finetuned-sst-2-english"
)


model = model.to(device)

batch_size = 10 
y_pred = [] 
num_batch = len(y_test)//batch_size

for i in range(num_batch):
    inputs = tokenizer(
        X_test[i*batch_size:(i+1)*batch_size],
        truncation=True,
        padding=True,
        return_tensors="pt"
    )  
    
    inputs = inputs.to(device)
    
    logits = model(**inputs).logits
    
    pred = F.softmax(logits, dim=-1)
    
    results = pred.cpu().detach().numpy().argmax(axis=1)
    
    y_pred.extend(results.tolist())

torch.cuda.empty_cache()

score = sum(y_test == np.array(y_pred))/len(y_test)
print("NLTK 영화리뷰 감성분석 정확도:", score)

실행 결과

NLTK 영화리뷰 감성분석 정확도: 0.8425

위 예제에서는 감성 분석 작업에 미세조정학습이 된 distilbert-base-uncased-finetuned-sst-2-english 모형을 사용했습니다. 코드는 GPU 가속을 위해 모형과 연산을 GPU로 이동하고, 연산 결과를 CPU로 가져오는 과정을 포함합니다. 모형은 확률로 계산되기 이전의 신경망 결과를 반환하므로, 소프트맥스를 이용해 확률로 변환하고 argmax 함수를 이용해 가장 큰 클래스를 선택해야 합니다.

저자와 필자가 얻은 정확도는 84.25%로, 나이브 베이즈로 얻은 79.7%보다 높고, CNN으로 얻은 81.2%보다도 좋습니다. 더불어 학습 데이터셋을 사용하지 않았음에도 높은 성능을 보입니다. 이는 놀라운 성능으로, TextBlob, AFINN, BADER를 사용한 결과와 비교했을 때 특히 뚜렷합니다.

NLTK의 학습 데이터를 사용해 학습한다면 성능을 더 높일 수 있을까요? 이는 미세조정학습의 필요성에 대한 질문으로, 사전학습된 모형을 사용하는 것이 아니라 내가 가진 학습 데이터를 사용해 성능을 향상시키는 것이 미세조정학습입니다.

profile
git블로그와 티스토리를 떠돌다 벨로그에 정착하다.

0개의 댓글