본문 바로가기
머신러닝, 딥러닝/딥러닝

BERT로 한글 영화 리뷰 감성분석 하기

by 장찐 2022. 2. 17.

📚 BERT를 이용한 영화 한글리뷰 감성 분석 

 

✔ 🏷️📌📘

 

• 설명 

 

 일반적으로 한글 텍스트 분석은 영어보다 전처리 과정이 까다로운 편이다. 조사나 어미의 변화가 다양하고, 형태 변화에 따라서 단어의 의미가 달라지기 때문이다. BERT로 한글을 분석하는 방법은 크게 다음고 같다. 

 

1.Hugging Face에서 제공하는 Multilingual BERT 사용

 

2.한글 데이터를 학습한 모델(KoBERT, KcBERT 등) 사용

https://github.com/SKTBrain/KoBERT

https://github.com/Beomi/KcBERT

 

 

 

📚 1. 다국어 버전 BERT를 이용한 감성 분석 

 

✅ 1. 1 특성 기반 (feature based) 방법 

 다국어 버전 BERT는 여러 언어 중에서 문서의 수가 많은 상위 104개 언어를 학습 데이터로 사용하였다. 우선 특정 기반 방식으로 분석을 시도한다. BERT 각 리뷰에 대한 정보를 추출하고 로지스틱 회귀 모형에 입력해서 분류 결과를 출력한다. BERT모델의 CLS 토큰에는 해당 문서 전체에 대한 hidden state 정보가 담겨있다. 따라서 이 토큰의 정보를 로지스틱 회귀의 입력 변수로 사용하면 문장의 감성을 분류할 수 있다. 

 

 

📌 라이브러리/모델 불러오기 

import numpy as np
import pandas as pd
import tensorflow as tf
from transformers import BertTokenizer, TFBertModel
import warnings
warnings.filterwarnings('ignore')

#토크나이저 불러오기
tokenizer = BertTokenizer.from_pretrained('bert-base-multilingual-cased')

#모델 불러오기 
model = TFBertModel.from_pretrained("bert-base-multilingual-cased", output_hidden_states = True)

필요한 라이브러리와 모델을 불러온다. bert-base-multilingual-cased 인자를 입력해서 다국어 모델로 설정한다

 

 

📌 토큰 ID, Attenion Mask 추출 

def encode(sents, tokenizer):
    input_ids = []
    attention_mask = []

    for text in sents:
        tokenized_text = tokenizer.encode_plus(text,
                                            max_length=20,
                                            add_special_tokens = True,
                                            pad_to_max_length=True,
                                            return_attention_mask=True)
        
        input_ids.append(tokenized_text['input_ids'])
        attention_mask.append(tokenized_text['attention_mask'])
    return tf.convert_to_tensor(input_ids, dtype=tf.int32), tf.convert_to_tensor(attention_mask, dtype=tf.int32)

 각 문서의 토큰 ID 와 attention mask 정보를 추출한다. 함수를 통해서 각 정보를 별도의 리스트에 저장하고, 텐서플로우에서 사용하기 위한 텐서 형태로 반환한다. 

 

 

 

📌 데이터 불러오기 

#코랩 경로 이동
from google.colab import drive
drive.mount('/content/drive')
cd /content/drive/MyDrive/CDSS 딥러닝 강의/BERT/

#데이터 불러오기
with open('Korean_movie_reviews_2016.txt', encoding='utf-8') as f:
    docs = [doc.strip().split('\t') for doc in f ]
    docs = [(doc[0], int(doc[1])) for doc in docs if len(doc) == 2]
    texts, labels = zip(*docs)

 분석에 사용할 데이터를 불러온다. 기본적인 텍스트 전처리가 완료된 리뷰 데이터를 사용하고, 리뷰마다 감성 정보를 나타내는라벨이 표기되어 있다.  (1 : 긍정 / 0 : 부정) 

 

 

📌 토큰화 

#각 영화 리뷰의 토큰ID와 attention mask 정보 추출
tokenized_sents = encode(texts[:3000], tokenizer)

각 리뷰의 토큰ID와 attention mask 정보를 추출한다. 코랩으로 학습하는 데에 램 한계가 있으므로 3천개의 데이터만 선별해서 분석한다. (전체 데이터 16만개) 

 

for layer in model.layers:
    layer.trainable=False

여기서는 특성 기반 분석을 실시하기 때문에 기존 모형의 파라미터를 업데이트 하지 않아야 하므로 위 코드를 실행한다. 

 

 

📌특성 정보 추출 

outputs = model(tokenized_sents[0], attention_mask = tokenized_sents[1])

outputs[2][-1].shape

 outputs에는 3개의 원소가 저장되어 있으며, 제일 마지막에 12개의 encoder block에서 출력하는 hidden state 정보와 embedding 정보 (즉, 13개의 원소로 구성)가 저장되어 있다. 이중에서 제일 마지막 원소가 12번째 encoder block의 hidden state 정보를 저장하고 있는데, outputs[2][-1]이 이에 해당한다. 

 

 3000=리뷰의 수 / 20=각 리뷰를 구성하는 토큰 수 / 768=각 토큰을 구성하는 벡터 차원

 

 

#독립변수 지정 
hidden_states = outputs[2][-1]
features = hidden_states[:,0,:].numpy()

#종속변수 지정 
selected_labels=labels[:3000]

각 문서를 768차원의 벡터로 표현하기 위해서, 20개의 토큰 중에서 CLS 토큰의 hidden state 정보를 사용한다. 종속변수도 labels에서 추출해서 지정한다. 

features에는 768차원의 벡터 정보가 저장되어 있다. 

 

 

 

📌로지스틱 회귀로 분류 

from sklearn.model_selection import train_test_split
train_features, test_features, train_labels, test_labels = train_test_split(features, selected_labels, test_size=0.2, random_state=0)

from sklearn.linear_model import LogisticRegression
lr2 = LogisticRegression(C=0.1, penalty='l2', solver='saga', max_iter=1000)
lr2.fit(train_features, train_labels)
pred_labels = lr2.predict(test_features)

#정확도 확인
from sklearn.metrics import accuracy_score
accuracy_score(test_labels, pred_labels)

로지스틱 회귀로 최종 분류를 실시하면 정확도가 0.66 정도로 계산된다. 

 

 

 

📌FNN 으로 분류 

#원핫 인코딩 
from tensorflow.keras.utils import to_categorical
y_one_hot = to_categorical(selected_labels)

#train/test 분리 
from sklearn.model_selection import train_test_split 
X_train, X_test, y_train, y_test = train_test_split(features, y_one_hot, test_size=0.2)

#FNN 모델 정의 
from tensorflow.keras import models
model = models.Sequential()

from tensorflow.keras import layers
model.add(layers.Dense(64, activation = 'tanh', input_shape=(X_train.shape[1],)))
# model.add(layers.Dropout(0.5))
model.add(layers.Dense(64, activation = 'tanh'))
model.add(layers.Dense(2, activation='softmax'))

#모델 학습 
from tensorflow.keras.optimizers import RMSprop
model.compile(optimizer=RMSprop(learning_rate=0.0001), loss='binary_crossentropy', metrics=['accuracy'])

history = model.fit(X_train, y_train, epochs=100, batch_size=32, validation_split=0.2)

#테스트셋 성과 평가 
model.evaluate(X_test,y_test)[1]

 간단한 뉴럴넷을 추가해서 정확도를 확인했다. 이 경우에도 정확도가 0.64 가량으로 크게 높지 않았는데, 이는 아무래도 모델의 파라미터에 비해서 학습 데이터의 양이 부족했기 때문에 제대로 학습이 이루어지지 않았기 때문이라고 볼 수 있다. 

 

 

📌그래프로 성능 확인 

import matplotlib.pyplot as plt
plt.plot(history.history['loss'])
plt.plot(history.history['val_loss'])
plt.xlabel('epoch')
plt.ylabel('loss')
plt.legend(['train','val'])
plt.show()

 

import matplotlib.pyplot as plt
plt.plot(history.history['accuracy'])
plt.plot(history.history['val_accuracy'])
plt.xlabel('epoch')
plt.ylabel('acc')
plt.legend(['train','val'])
plt.show()

 

 학습 과정을 살펴보면 val set에서 과적합이 일어나서 올바르게 수렴하지 않는 것을 확인할 수 있다. 

 

 


📚 1.2 Fine Tuning 방법 

 

 BERT 다국어 모델을 바탕으로 fine tuning을 실시하기 위해서는 task에 적합한 새로운 데이터를 이용해서 학습시키면서 사전학습 모델의 파라미터를 업데이트 해야한다. 

 

 

📌라이브러리, 데이터 불러오기 

#코랩 마운트
from google.colab import drive
drive.mount('/content/gdrive')

cd/content/gdrive/MyDrive/CDSS 딥러닝 강의/BERT

import tensorflow as tf
import numpy as np
import pandas as pd
import warnings
warnings.filterwarnings('ignore')
with open('Korean_movie_reviews_2016.txt', encoding='utf-8') as f:
    docs = [doc.strip().split('\t') for doc in f ]
    docs = [(doc[0], int(doc[1])) for doc in docs if len(doc) == 2]
    texts, labels = zip(*docs)

 동일한 한국어 리뷰 데이터를 불러온다. 전체 데이터는 16만개 가량으로 구성되어 있다. 

 

📌Train / Test 나누기 

from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(texts, labels, random_state=42, test_size=0.2)

 

📌토큰화 실시 

from transformers import BertTokenizer, TFBertForSequenceClassification

tokenizer = BertTokenizer.from_pretrained('bert-base-multilingual-cased')

def encode(data, tokenizer):
    input_ids = []
    attention_masks = []
    token_type_ids = []
    for text in data:
        tokenized_text = tokenizer.encode_plus(text,
                                            max_length=50,
                                            add_special_tokens = True,
                                            pad_to_max_length=True,
                                            return_attention_mask=True,
                                              truncation=True)
        input_ids.append(tokenized_text['input_ids'])
        attention_masks.append(tokenized_text['attention_mask'])
        token_type_ids.append(tokenized_text['token_type_ids'])
    
    return input_ids, attention_masks, token_type_ids

 토크나이저를 불러와서 각 토큰에 대한 ID와 attenion mask를 추출한다. 그리고 fine tuning 을 할 때는 추가적으로 token_type_ids도 추출해야 한다. (영화 리뷰 분류 게시글 참고)

 

#학습 데이터
train_input_ids, train_attention_masks, train_token_type_ids = encode(X_train, tokenizer)

#테스트 데이터
test_input_ids, test_attention_masks, test_token_type_ids = encode(X_test, tokenizer)

 학습 데이터와 테스트 데이터 각각에 대해서 토큰화를 실시한다. 

 

 

📌모델 학습 

def map_example_to_dict(input_ids, attention_masks, token_type_ids, label):
    return {
      "input_ids": input_ids,
      "token_type_ids": token_type_ids,
      "attention_mask": attention_masks,
      }, label
      
      
def data_encode(input_ids_list, attention_mask_list, token_type_ids_list, label_list):
    return tf.data.Dataset.from_tensor_slices((input_ids_list, attention_mask_list, token_type_ids_list, label_list)).map(map_example_to_dict)
    
    BATCH_SIZE=32
    
train_data_encoded = data_encode(train_input_ids, train_attention_masks, train_token_type_ids,y_train).shuffle(10000).batch(BATCH_SIZE)
test_data_encoded = data_encode(test_input_ids, test_attention_masks, test_token_type_ids, y_test).batch(BATCH_SIZE)

 BERT 모델에 입력하기 위해서 map_example_to_dict 함수와 data_encode 함수를 이요해서 데이터를 딕셔너리 형태로 변경한다. 

 

 

model = TFBertForSequenceClassification.from_pretrained(
    "bert-base-multilingual-cased", # Use the 12-layer BERT model, with an uncased vocab.
    num_labels = 2
)


optimizer = tf.keras.optimizers.Adam(1e-5)
loss = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True)
metric = tf.keras.metrics.SparseCategoricalAccuracy('accuracy')
model.compile(optimizer=optimizer, loss=loss, metrics=[metric])

NUM_EPOCHS = 5
history = model.fit(train_data_encoded, epochs=NUM_EPOCHS, batch_size=BATCH_SIZE, validation_data=test_data_encoded)

 사전학습 모델을 불러와서 학습을 진행한다. BERT 모델 자체가 꽤 크기 때문에 학습에 상당한 시간이 소요된다. 

 

 

# loss 확인 
import matplotlib.pyplot as plt
plt.plot(history.history['loss'])
plt.plot(history.history['val_loss'])
plt.xlabel('epoch')
plt.ylabel('loss')
plt.legend(['train','val'])
plt.show()


# accuracy 확인 
import matplotlib.pyplot as plt
plt.plot(history.history['accuracy'])
plt.plot(history.history['val_accuracy'])
plt.xlabel('epoch')
plt.ylabel('accuracy')
plt.legend(['train','val'])
plt.show()

 

 

 

 

 

 

 


📚  Reference

• 연세대학교 디지털사회과학센터(CDSS) 파이썬을 활용한 딥러닝 기초 워크숍, 이상엽 교수님 


 

댓글