지난 모임에서 대학원생 언니가 BERT라는 것에 대해서 소개했다. BERT에 대해 처음 들어보는 것이었어서 더욱 관심이 갔다. BERT는 Bidirectional Encoder Representations from Transformers의 약자이다. 나는 컴퓨터공학과 학생이나 컴퓨터사이언티스트가 아니기 때문에 BERT의 원리와 구조 등에 대해서 자세하게 설명하진 않을 것이다. 하지만, 이 포스트에서 BERT를 사용해 감성 분류를 할 것이기 때문에 BERT를 이해하기 위한 단순하지만 중요한 포인트들을 적어보겠다.
BERT는 위키피디아와 BooksCorpus와 같은 라벨링이 되어 있지 않은 텍스트 데이터로 사전 학습이 되어 있는 딥러닝 모델이다. 2018년에 구글에 의해 처음 공개된 후로부터 다양한 자연어 처리에서 최고의 성능을 보여왔다. BERT가 이렇게나 좋은 성능을 보이는 데는 사전 학습 과정에서 습득한 지식을 사용한다는 이유 때문이다. 즉, BERT가 아닌 다른 모델로 분석을 진행한다면, 그 모델은 단지 연구자가 부여한 학습 데이터셋이 포함하고 있는 내용만 알게 되지만, BERT는 연구자의 학습 데이터 뿐만 아니라 사전 학습 과정으로부터의 추가적인 지식도 알게 되는 것이고, 이로 인해 BERT가 더 나은 성능을 보여줄 수 있다는 것이다.
위의 설명이 BERT 자체와 내가 왜 더 나은 분석 결과를 위해 BERT를 사용하려고 하는 것인지 이해하기에 쉽고 명확하길 바란다. 그럼, 코드 설명으로 넘어가 보자. 아래 코드는 BERT 감성 분석을 위해 사용한 코드이다. BERT 감성 분석은 지도 학습 기반이기 때문에 이번에는 학습을 위한 또 다른 데이터셋을 준비했다. 마스카라 제품 리뷰에 대한 라벨링이 되어 있는 텍스트 데이터셋을 찾는 것에 실패했기 때문에 다시 데이터 수집을 함으로써 직접 학습용 데이터셋을 만들었다. 세포라 웹사이트의 29개 마스카라 제품들로부터 20,186개의 고객 리뷰를 수집했다. 5점 리뷰와 1점 리뷰만 필터링을 한 다음, 모델이 긍정적 리뷰와 부정적 리뷰를 공평하게 학습할 수 있도록 하기 위해서 각각의 리뷰를 똑같은 수로 수집했다(5점 리뷰 10,093개, 1점 리뷰 10,093개).
아래 첨부된 CSV 파일이 내가 만들어서 사용한 학습 데이터셋이다. 긍정과 부정 이진 분류를 할 것이기 때문에 5점 리뷰에는 1(긍정)이라고 라벨링을 했고, 1점 리뷰에는 0(부정)이라고 라벨링을 했다.
In the previous meeting, one of the fellow students introduced BERT. It was my first time learning about BERT, which made me more interested in it. BERT is an abbreviation of Bidirectional Encoder Representations from Transformers. Since I'm not a computer scientist, I'm not going to talk much about the principle and structure of it in detail. However, because I'm going to use it as a sentiment classifying model in this post, I will explain simple but key points to understand what BERT is.
BERT, from Google, is a deep learning model that is pre-trained with text data without any labels, such as Wikipedia and BooksCorpus. It was first opened to the public in 2018, and since then, it has shown the best performances in various NLP(natural language processing) tasks. The reason why BERT has such high performances is that it can utilize the knowledge obtained previously in the pre-training process. That is, when we don't use BERT, a model only knows what a training dataset includes, but BERT knows not only the data from the training dataset but also the additional knowledge from the pre-training process, which leads to the better performance.
I believe the explanation above is easy and clear enough to understand what BERT is and why I'm going to use it for better analysis results. Then, let's move on to the code explanation. The followings are the steps and codes for the sentiment analysis using BERT. BERT sentiment analysis is based on supervised learning, so I had to prepare another dataset for training. First I looked for a dataset including text reviews about mascara products with sentiment labels, but I couldn't find one, so I decided to make one for myself. For that, I collected 20,186 customer reviews of 29 other mascaras from the Sephora website. For the data collection, I filtered only five-star and one-star reviews and collected the same number of them (10,093 five-star and 10,093 one-star reviews) so that the model can learn both positive and negative reviews equally.
The attached CSV document below is the training dataset that I used. I labeled 1 to the five-star reviews as the meaning 'positive' and 0 indicating 'negative' to the one-star reviews.
* BERT 파인 튜닝에 굉장히 오랜 시간이 걸리기 때문에 GPU accelerator을 사용하길 아주 아주 적극 추천한다. 이를 위해서 꼭 구글 코랩을 사용하길 바란다. (주피터 사용 시 하루 이상, GPU accelerator 기능을 켠 구글 코랩 사용 시 50분 정도 소요)
* Since the BERT fine-tuning step takes extremely long, you should use a GPU accelerator. Because of this, I highly recommend using Google Colab. (It takes more than a day if you use Jupyter, but on Google Colab with GPU accelerator it takes about 50 minutes.)
1. Install the package called transformers.
pip install transformers
2. Build the model and the tokenizer.
from transformers import BertTokenizer, TFBertForSequenceClassification
from transformers import InputExample, InputFeatures
model = TFBertForSequenceClassification.from_pretrained("bert-base-uncased")
tokenizer = BertTokenizer.from_pretrained("bert-base-uncased")
3. See the model summary.
model.summary()
4. Import packages.
import tensorflow as tf
import pandas as pd
5. Import the training dataset.
train = pd.read_csv('train.csv')
6. Define the function for preprocessing.
import re
def data_text_cleaning(data):
# 영문자 이외 문자는 공백으로 변환
only_english = re.sub('[^a-zA-Z]', ' ', data)
# 소문자 변환
no_capitals = only_english.lower()
return no_capitals
7. Preprocess the training dataset.
for i in range(len(train)):
review = str(train['review'][i])
train['review'][i] = data_text_cleaning(review)
8. Split the train and test data.
from sklearn.model_selection import train_test_split
x_data = train['review']
y_data = train['positive']
x_train, x_test, y_train, y_test = train_test_split(x_data, y_data, test_size=0.3, random_state=777)
9. Create the dataset for train and test data each.
# train data
from pandas import Series, DataFrame
train_data = {'review': x_train,
'positive': y_train}
train_df = DataFrame(train_data)
# test data
test_data = {'review': x_test,
'positive': y_test}
test_df = DataFrame(test_data)
10. Create input sequences.
- converting the two pandas data frames (train_df, test_df) into suitable objects for the BERT model
- First, define two functions called 'convert_data_to_examples' and 'convert_examples_to_tf_dataset'.
- convert_data_to_examples: convert each row of the train and test datasets into an InputExample object
- convert_examples_to_tf_dataset: convert the InputExample objects into an input dataset that can be fed to the model by tokenizing the InputExample objects and creating the required input format with them
def convert_data_to_examples(train_df, test_df, review, positive):
train_InputExamples = train_df.apply(lambda x: InputExample(guid=None, # Globally unique ID for bookkeeping, unused in this case
text_a = x[review],
text_b = None,
label = x[positive]), axis = 1)
validation_InputExamples = test_df.apply(lambda x: InputExample(guid=None, # Globally unique ID for bookkeeping, unused in this case
text_a = x[review],
text_b = None,
label = x[positive]), axis = 1)
return train_InputExamples, validation_InputExamples
train_InputExamples, validation_InputExamples = convert_data_to_examples(train_df,
test_df,
'review',
'positive')
def convert_examples_to_tf_dataset(examples, tokenizer, max_length=128):
features = [] # -> will hold InputFeatures to be converted later
for e in examples:
# Documentation is really strong for this method, so please take a look at it
input_dict = tokenizer.encode_plus(
e.text_a,
add_special_tokens=True,
max_length=max_length, # truncates if len(s) > max_length
return_token_type_ids=True,
return_attention_mask=True,
pad_to_max_length=True, # pads to the right by default # CHECK THIS for pad_to_max_length
truncation=True
)
input_ids, token_type_ids, attention_mask = (input_dict["input_ids"],
input_dict["token_type_ids"], input_dict['attention_mask'])
features.append(
InputFeatures(
input_ids=input_ids, attention_mask=attention_mask, token_type_ids=token_type_ids, label=e.label
)
)
def gen():
for f in features:
yield (
{
"input_ids": f.input_ids,
"attention_mask": f.attention_mask,
"token_type_ids": f.token_type_ids,
},
f.label,
)
return tf.data.Dataset.from_generator(
gen,
({"input_ids": tf.int32, "attention_mask": tf.int32, "token_type_ids": tf.int32}, tf.int64),
(
{
"input_ids": tf.TensorShape([None]),
"attention_mask": tf.TensorShape([None]),
"token_type_ids": tf.TensorShape([None]),
},
tf.TensorShape([]),
),
)
review = 'review'
positive = 'positive'
- Next, use the functions to create input sequences.
train_InputExamples, validation_InputExamples = convert_data_to_examples(train_df, test_df, review, positive)
trainData = convert_examples_to_tf_dataset(list(train_InputExamples), tokenizer)
trainData = trainData.shuffle(100).batch(32).repeat(2)
validation_data = convert_examples_to_tf_dataset(list(validation_InputExamples), tokenizer)
validation_data = validation_data.batch(32)
11. Configure the BERT model and fine-tuning
- This process takes a while. It took me about 50 minutes with the GPU accelerator.
model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=3e-5, epsilon=1e-08, clipnorm=1.0),
loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
metrics=[tf.keras.metrics.SparseCategoricalAccuracy('accuracy')])
model.fit(trainData, epochs=2, validation_data=validation_data)
12. Import datasets to make sentiment predictions.
suc = pd.read_csv('suc.csv')
un = pd.read_csv('un.csv')
13. Add a column of sentiment labels predicted with BERT
# successful product reviews
for i in range(len(suc)):
suc['review'][i] = str(suc['review'][i])
i += 1
s_label_list = []
for i in range(1482):
sReview_list = list(suc['review'])[i*5:i*5+5]
s_tf_batch = tokenizer(sReview_list, max_length=128, padding=True, truncation=True, return_tensors='tf')
s_tf_outputs = model(s_tf_batch)
s_tf_predictions = tf.nn.softmax(s_tf_outputs[0], axis=-1)
labels = [0, 1]
s_label = tf.argmax(s_tf_predictions, axis=1)
s_label = s_label.numpy()
for j in range(len(sReview_list)):
s_label_list.append(labels[s_label[j]])
i += 1
sReview_list_1482 = list(suc['review'])[7410:]
s_tf_batch = tokenizer(sReview_list_1482, max_length=128, padding=True, truncation=True, return_tensors='tf')
s_tf_outputs = model(s_tf_batch)
s_tf_predictions = tf.nn.softmax(s_tf_outputs[0], axis=-1)
labels = [0, 1]
s_label = tf.argmax(s_tf_predictions, axis=1)
s_label = s_label.numpy()
for j in range(len(sReview_list_1482)):
s_label_list.append(labels[s_label[j]])
suc['label_BERT'] = 0
for i in range(len(suc)):
suc['label_BERT'][i] = s_label_list[i]
i += 1
처음에는 sReview_list에 list(suc['review'])를 부여했다. 그랬더니, 계속해서 OOM 에러가 발생했다. 구글링해본 결과, 이 에러를 해결할 수 있는 방법에는 세 가지가 있다고 했다: 1) 배치 크기 줄이기, 2) 이미지 크기 줄이기, 3) GPU 메모리 늘리기. 이 중에서 첫 번째 방법인 배치 크기를 줄여보기로 했다. 결과적으로, 에러가 생기지 않는 배치 사이즈는 5였다. 그래서 sReview_list에 list(suc['review'])[i*5:i*5+5]를 부여하고 for 반복문을 쓰게 되었다.
At first, I put sReview_list as list(suc['review']). But then, I kept getting OOM errors. And based on what I found from googling, to solve the error, there are three major solutions: 1) to reduce the batch size, 2) to reduce the image size, or 3) to increase the memory of GPU. Among these, I could try the first solution, to reduce the batch size. So, the one that I finally didn't get the error from was 5. That's why I put sReview_list as list(suc['review'])[i*5:i*5+5] and use for iterator.
# unsuccessful product reviews
for i in range(len(un)):
un['review'][i] = str(un['review'][i])
i += 1
u_label_list = []
for i in range(27):
uReview_list = list(un['review'])[i*5:i*5+5]
u_tf_batch = tokenizer(uReview_list, max_length=128, padding=True, truncation=True, return_tensors='tf')
u_tf_outputs = model(u_tf_batch)
u_tf_predictions = tf.nn.softmax(u_tf_outputs[0], axis=-1)
labels = [0, 1]
u_label = tf.argmax(u_tf_predictions, axis=1)
u_label = u_label.numpy()
for j in range(len(uReview_list)):
u_label_list.append(labels[u_label[j]])
i += 1
un['label_BERT'] = 0
for i in range(len(un)):
un['label_BERT'][i] = u_label_list[i]
i += 1
14. Save the data frame to a csv file.
suc.to_csv('suc_bert.csv')
un.to_csv('un_bert.csv')
References
Yalçın, O. G. (2021, December 16). Sentiment Analysis in 10 Minutes with BERT and TensorFlow | by Orhan G. Yalçın | Medium | Towards Data Science. Medium. https://towardsdatascience.com/sentiment-analysis-in-10-minutes-with-bert-and-hugging-face-294e8a04b671
* Unauthorized copying and distribution of this post are not allowed.
* 해당 글에 대한 무단 배포 및 복사를 허용하지 않습니다.
'Bachelor of Business Administration @PNU > Marketing Analytics' 카테고리의 다른 글
불용어 제거한 데이터셋 만들기 | How to Make Stop Words-Removed Datasets (13.04.2022.) (0) | 2022.04.21 |
---|---|
BERT 감성 분석 - 2 | BERT Sentiment Analysis - 2 (13.04.2022.) (0) | 2022.04.21 |
토픽 별 감성 분석 | Sentiment Analysis for Each Topic (30.03.2022.) (0) | 2022.04.19 |
선행 연구 - 2 | Pilot Study - 2 (16.03.2022.) (0) | 2022.04.18 |
감성 분석 - 지도 학습 | Sentiment Analysis - Supervised Learning (16.03.2022.) (0) | 2022.04.17 |