본문 바로가기
논문/Code

BERT (2)

by p-jiho 2023. 5. 24.

저번에 BERT를 시도했는데 정확도가 너무 낮았던 문제가 있었다.

그래서 추가적으로 알아보았고, finBERT를 알게되었다.

원래 기존 BERT는 긍정, 부정 등 감성분석을 말한다. 근데 finBERT는 금융 즉, 경제를 위해 만들어진 모델이다.

 

https://www.kaggle.com/code/nlpquant/finbert-ext

 

FinBERT-EXT

Explore and run machine learning code with Kaggle Notebooks | Using data from multiple data sources

www.kaggle.com

코드는 여기에 나오는 코드를 전부 가져다 썼다.

여기서 나와 다른 점은 Sentences_50Agree 데이터를 쓰는 것이 아닌 Sentences_AllAgree 데이터를 사용했고, 

bert-base-cased 모델을 사용하는 것이 아닌 finBERT를 사용해 yiyanghkust/finbert-pretrain를 사용하였다.

 

그리고 위 코드 중 다른 점은

bs = 16
lr = 0.0001
epochs = 5

배치사이즈와 에폭, 학습률을 위와 같이 설정했다.

그 결과 test 데이터의 정확도는 0.95가 나왔다. 에폭 수를 더 길게 해보기도 했는데 0.90 이상의 정확도를 보였고, test 데이터를 사용한 결과이므로 과적합이라고 볼 수 없으므로 에폭은 5로 설정했다.

 

이를 이용해서

f = open("data/NBC_News.txt","r", encoding = "UTF-8")
NBC_News = f.readlines()
f.close()


NBC_News = list(map(lambda x: x.split("%/%/%/%/%"), NBC_News))

NBC_News = pd.DataFrame(NBC_News)
sentence = NBC_News[1]
sentence = pd.DataFrame(sentence)
sentence.columns = ["text"]
sentence = Dataset.from_pandas(sentence)
sentence = sentence.map(tok_func, batched=True)
sentence = sentence.remove_columns('text')

preds = trainer.predict(sentence)
preds = np.argmax(preds.predictions, axis=-1)

NBC_News.columns = ["Date", "Text"]
NBC_News["Bert_Score"] = preds
NBC_News[["Date","Bert_Score"]].to_csv("BERT_NBC_Score.csv", header = True, index = None)
#NBC_News.to_csv("data/BERT_NBC_Score.csv", header = True, index = None)

f = open("NYT_News.txt","r", encoding = "UTF-8")
NYT_News = f.readlines()
f.close()


NYT_News = list(map(lambda x: x.split("%/%/%/%/%"), NYT_News))
NYT_News = pd.DataFrame(NYT_News)
sentence = NYT_News[1]
sentence = pd.DataFrame(sentence)
sentence.columns = ["text"]
sentence = Dataset.from_pandas(sentence)
sentence = sentence.map(tok_func, batched=True)
sentence = sentence.remove_columns('text')

preds = trainer.predict(sentence)
preds = np.argmax(preds.predictions, axis=-1)

NYT_News.columns = ["Date", "Text"]
NYT_News["NYT_B_Score"] = preds
NYT_News[["Date","NYT_B_Score"]].to_csv("BERT_NYT_Score.csv", header = True, index = None)
#NYT_News.to_csv("data/BERT_NYT_Score.csv", header = True, index = None)

이렇게 NBC 뉴스와 NYT 뉴스에 대한 BERT 감성점수를 저장하였다.

대부분 중립이어서 데이터가 예측하기에 적합한지 걱정이 된다.

 

이전 코드와 위의 변경 내용을 결합해서 코드를 돌려보기도 하였다.

import requests
import json
import pandas as pd

from sklearn.preprocessing import LabelEncoder
from sklearn.model_selection import train_test_split

from transformers import BertTokenizer, TextClassificationPipeline, TFBertForSequenceClassification
from collections import Counter

import tensorflow as tf
from tensorflow.keras.callbacks import EarlyStopping
import requests
import json
import pandas as pd
import numpy as np

패키지를 불러온다.

 

f = open("data/Sentences_AllAgree.txt","r", encoding='UTF-8')
original_data = f.readlines()
f.close()
original_data = list(map(lambda x: x.strip().split("@"), original_data))
original_data = pd.DataFrame(original_data)
original_data.columns = ["Text","Label"]

original_data[["Label_n"]] = 0

for i in range(len(original_data)):
    if original_data["Label"][i] == "neutral":
        original_data["Label_n"][i] = 1
    elif original_data["Label"][i] == "negative":
        original_data["Label_n"][i] = 0
    else : original_data["Label_n"][i] = 2

데이터를 정리하고, 라벨링을 한다.

함수를 쓰지않고 for문을 사용한 이유는 결과가 0이나 1로 나오지 않고, Label_0, Label_1로 나왔다. 그 이유가 함수를 사용해서인가싶어 저렇게 지정을 하였고, 결과는 동일하게 나왔다. 어쩔 수 없나보다.

 

tokenizer = BertTokenizer.from_pretrained("yiyanghkust/finbert-pretrain", do_lower_case=True)

finBERT를 사용하였다. 이에 대한 내용은

https://huggingface.co/yiyanghkust/finbert-pretrain

 

yiyanghkust/finbert-pretrain · Hugging Face

FinBERT is a BERT model pre-trained on financial communication text. The purpose is to enhance financial NLP research and practice. It is trained on the following three financial communication corpus. The total corpora size is 4.9B tokens. Corporate Report

huggingface.co

이 링크를 들어가보면 자세히 알 수 있다.

 

Text = original_data["Text"].to_list()
Lable = original_data["Label_n"].to_list()

Train_Text, Test_Text, Train_Label, Test_Label = train_test_split(Text, Lable, test_size=0.3, random_state = 42)
Train_Text, Eval_Text, Train_Label, Eval_Label = train_test_split(Train_Text, Train_Label, test_size=0.1, random_state = 42)

Train_encoding = tokenizer(Train_Text, truncation=True, padding=True)
Test_encoding = tokenizer(Test_Text, truncation=True, padding=True)
Eval_encoding = tokenizer(Eval_Text, truncation=True, padding=True)

# trainset-set
Train_dataset = tf.data.Dataset.from_tensor_slices((
    dict(Train_encoding),
    Train_Label
))

# validation-set
Test_dataset = tf.data.Dataset.from_tensor_slices((
    dict(Test_encoding),
    Test_Label
))

Eval_dataset = tf.data.Dataset.from_tensor_slices((
    dict(Eval_encoding),
    Eval_Label
))

Train, Test, Evaluation 로 데이터를 구분한다. 비율은 train과 test를 7:3으로 나누고, train을 다시 train과 evaluation을 9:1로 나누었다.

 

model = TFBertForSequenceClassification.from_pretrained("yiyanghkust/finbert-pretrain", num_labels=3, from_pt=True)
optimizer = tf.keras.optimizers.Adam(learning_rate=0.0001)
loss = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True)
metric = tf.keras.metrics.SparseCategoricalAccuracy('accuracy')
model.compile(optimizer=optimizer, loss=loss, metrics=[metric])

학습률은 0.0001로 지정하였고 옵티마이저는 Adam을 사용하였다. 

 

callback_earlystop = EarlyStopping(
    monitor="val_accuracy", 
    min_delta=0.001, # the threshold that triggers the termination (acc should at least improve 0.001)
    patience=3)
model.fit(Train_dataset.shuffle(100).batch(16), epochs=5, batch_size=16, 
          validation_data=Eval_dataset.shuffle(100).batch(16),
          callbacks = [callback_earlystop])

그리고 배치사이즈는 16, 에폭은 5로 아까 지정한 것과 동일하게 지정해주었다.

그리고 결과가 나아지지 않으면 빨리 종료하도록 earlystop도 사용하였다.

시간은 2시간 30분 정도가 걸렸다. 시간을 줄여보려고 구글링을 해보았지만 BERT의 단점이 시간이 엄청 오래걸린다는 것이다.

모델의 복잡도를 줄여 학습을 좀 더 빨리 할 수 있는 모델이 있지만 finBERT를 사용해야하므로 사용하진 않았다.

 

text_classifier = TextClassificationPipeline(
    tokenizer=tokenizer, 
    model=model, 
    framework='tf',
    return_all_scores=True
)

predicted_label_list = []
i=0
for text in Test_Text:
    preds_list = text_classifier(text)[0]
    sorted_preds_list = sorted(preds_list, key=lambda x: x['score'], reverse=True)
    predicted_label_list.append(sorted_preds_list[0]["label"])
    
predicted_label_list = list(map(lambda x: int(x[len(x)-1:len(x)]), predicted_label_list))
sum(np.equal(predicted_label_list, Test_Label))/len(predicted_label_list)

test 데이터로 확인해본 결과 0.95가 나왔다.

 

f = open("data/NBC_News.txt","r", encoding = "UTF-8")
NBC_News = f.readlines()
f.close()


NBC_News = list(map(lambda x: x.split("%/%/%/%/%"), NBC_News))

NBC_News = pd.DataFrame(NBC_News)
sentence = NBC_News[1].to_list()
sentence = list(map(lambda x: x.strip(), sentence))
sentence_pred = []
i=0
for text in sentence:
    i+=1
    if i % 1000 == 0:
        print(i)
    preds_list = text_classifier(text)[0]
    sorted_preds_list = sorted(preds_list, key=lambda x: x['score'], reverse=True)
    sentence_pred.append(sorted_preds_list[0]["label"])
    
sentence_pred = list(map(lambda x: int(x[len(x)-1:len(x)]), sentence_pred))

NBC 뉴스의 감성점수를 뽑아보려했으나 코드가 돌아가지 않았다.

이유를 찾아보니 길이가 너무 길어서라고 했다.

이 글의 맨 위에 있는 코드로 실행을 하면 error가 생기지 않는데 어떤 이유인지는 정확히 나오지 않았다.

계속 수정을 시도할 것이고, BERT에 대해 공부할 것이다. 원인과 해결방법을 명확히 찾으면 다시 소개하도록 할 것이다.

 

 

구글링을 했을 때 BERT의 코드나 다양한 모델에 대한 정보, error을 해결할 수 있는 방법이 친절하게 나와있지는 않았다.

생소한 내용도 많았다. 그래서 매우 어려웠던 것 같다.

모델을 돌리는 것도 시간이 오래 걸려 여러번 돌려보고 결과를 비교해보고 하는 것이 쉽지는 않았다.

그래도 확실히 배운 것은 BERT는 자연어 처리를 하는데 굉장히 유용하게 쓰일 것 같다는 것이다.

BERT에 대해 아직 많은 정보를 알고 자유자재로 다룰 줄 아는 것은 아니다.

하지만 추후에 계속 공부해보고 싶다.

'논문 > Code' 카테고리의 다른 글

트위터 API  (0) 2023.05.18
Bert (1)  (0) 2023.05.07
Collect New york times news headlines using r  (0) 2023.02.01
news data preprocessing code modification  (0) 2023.01.15