본문 바로가기
논문/Code

Bert (1)

by p-jiho 2023. 5. 7.

논문의 주요 포인트는 뉴스 감성분석이다.

현재 NBC 뉴스와 NYT 뉴스를 NLTK와 TextBlob으로 감성분석해 생성한 점수를 변수로 사용하고 있다.

분석 방법에 따라 점수가 다르고, 그에 따른 예측 결과도 달라지므로 여러가지 감성분석을 시도하려고 노력하고 있다.

그 중 가장 욕심이 나고 주의깊게 보았던 것이 Bert이다.

Bert는 transpormer 계열 중 하나로 구글에서 만든 사전훈련 모델이다.

Bert에 대한 설명은

https://wikidocs.net/115055

 

17-02 버트(Bidirectional Encoder Representations from Transformers, BERT)

* 트랜스포머 챕터에 대한 사전 이해가 필요합니다. ![](https://wikidocs.net/images/page/35594/BERT.PNG) BERT(Bidire…

wikidocs.net

나 또한 이곳에서 처음 bert를 접했고, 이 책을 기반으로 공부했으니 개념은 여기서보면 된다.

이번에는 bert에 대한 설명 대신 bert code를 생성하고 정확도를 높이는 과정에 대해서 이야기 해볼 것이다.

 

먼저, 나는 pytorch 보다 tensorflow가 더 익숙하다. 딥러닝 모델을 처음 배울 때 tensorflow로 접했고, code를 보기에 개인적으로는 tensorflow가 더 깔끔했기 때문이다.

그래서 bert도 tensorflow를 먼저 시도했다.

https://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

코드는 위 링크를 상당히 많이 참고했고, 나의 상황에 맞게 수정해야할 코드가 아니면 그대로 사용하였다.

 

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

먼저, 필요한 패키지를 불러온다.

 

f = open("Sentences_50Agree.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"]

그리고 사전학습할 데이터를 불러온다.

이 데이터는 영어 뉴스에 대한 긍정, 부정, 중립의 라벨을 전문가들이 하나하나 직접 입력한 데이터이다.

데이터 다운로드는 https://www.researchgate.net/publication/251231364_FinancialPhraseBank-v10 여기서 했다.

이 데이터로 사전학습을 하는 일은 나에게는 굉장히 매력적인 일이었다.

왜냐하면 NLTK나 TextBlob으로 감성분석을 하는 것은 평소 우리의 말에 대한 것이기 때문이다.

즉, 경제 뉴스 또는 주식 뉴스와 관련되어있지 않을수도 있다는 것이다.

그래서 뉴스에 최적화된 감성분석을 해야한다고 생각했다. 그것을 실현 시켜줄 방법 중 하나가 Bert였고, 실현시켜줄 것이라고 기대했다.

데이터를 다운로드 받으면 Text와 label로 구성이 되어있는데 @를 기준으로 분리가 되어있다. 그러므로 split 함수를 이용해 분리해주고 dataframe으로 변경해 데이터를 구성해준다.

 

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

그리고 Label 변수는 neutral, negative, positive로 구성이 되어있으므로 이것을 0, 1, 2로 지정해준다.

원래는 from sklearn.preprocessing import LabelEncoder를 사용해 라벨링을 했지만 나중에 결과가 0, 1이 아닌 Label_1, Label_2로 나왔다. 그래서 하나씩 입력해주는 방법으로 바꾸었다.

 

tokenizer = BertTokenizer.from_pretrained("bert-base-cased", do_lower_case=True)

그리고 토큰화를 위해 준비를 해준다. "bert-base-cased" 부분에서 다양한 사전학습 모델을 불러올 수 있다.

이에 대한 정보는 https://huggingface.co/transformers/v2.5.1/pretrained_models.html 를 보면 된다.

bert-base-cased를 사용한 이유는 내가 참고한 논문 https://koreascience.kr/article/JAKO202016151585665.pdf 에서 사용을 동일한 파라미터의 모델을 사용하기도 했고, 추후에 입력할 NBC 뉴스는 대소문자를 구분하도록 되어있다. 그래서 해당 모델을 사용하였다. 이에 대한 정보는 https://metatext.io/models/bert-base-uncased를 보면 된다.

 

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)

print(Counter(Train_Label),Counter(Test_Label),Counter(Eval_Label))
# Counter({1: 1838, 2: 839, 0: 375}) Counter({1: 847, 2: 428, 0: 179}) Counter({1: 194, 2: 96, 0: 50})

변수를 각각 list로 만들어주고 train_test_split 함수를 이용해 일정 비율로 데이터를 나누어주었다.

Counter 함수를 이용해 빈도를 보면 나름 적절하게 분배가 된 것 같아 seed=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)

Train_dataset = tf.data.Dataset.from_tensor_slices((
    dict(Train_encoding),
    Train_Label
))

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
))

토큰화를 진행해주고 dataset 형식으로 만들어준다.

이유는 실은 잘 모르겠으나 모든 tensorflow 코드가 dataset을 사용하고 있었다. 아마 bert를 사용하려면 data가 dataset 형태로 있어야하는 것 같다.

 

model = TFBertForSequenceClassification.from_pretrained("bert-base-cased", num_labels=3, hidden_dropout_prob = 0.3)

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

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(8), epochs=5, batch_size=8, 
          validation_data=Eval_dataset.shuffle(100).batch(8),
          callbacks = [callback_earlystop])

텍스트 분류를 해야하므로 해당 함수를 사용하였다.

그리고 label을 3종류이므로 3으로 지정하고, 참고한 논문에서 dropoout을 0.3으로 지정하였으므로 동일하게 지정해보았다.

옵티마이저는 adam인데 이것은 내가 임의로 지정하였다. 이유는 다른 참고 코드들이 다들 adam을 사용하고 있기도 했고, adam이 성능이 좋았던 경우가 많아 지정을 하였다.

학습률, 에폭, 배치사이즈는 논문에 나온 그대로 사용하였다.

그리고 earlystopping은 참고한 코드를 그대로 가져온 것인데 3정도가 적당해보여 3으로 지정하였다.

그리고 이 코드에서 중요한 부분 중 하나는 원래 loss를

model.compute_loss

이것으로 지정하였다. 그런데 오류가 나서 구글링을 해보니 현재 이 코드를 사용할 수 없다고 했다.

그래서 keras로 loss를 생성하여 지정해주었다.

그리고 여기서 tensorflow와 transpormer을 같이 사용할 때 문제가 있었다. 구글링을 해보니 최신 버전 tensorflow는 지원을 안해주는 것 같았다. https://stackoverflow.com/questions/74586892/no-module-named-keras-saving-hdf5-format 이 곳을 참고해보면 패키지의 파일을 수정하거나 아니면 tensorflow의 버전을 낮추는 수 밖에 없었다. 나는 버전을 낮추는 법을 선택하였다.

 

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

그리고 test 데이터로 예측을 해보기위해 이 함수를 지정해주었다.

 

predicted_label_list = []
i=0
for text in Test_Text:
    preds_list = text_classifier(text)[0]
    print(i)
    i+=1
    sorted_preds_list = sorted(preds_list, key=lambda x: x['score'], reverse=True)
    predicted_label_list.append(sorted_preds_list[0]["label"])

그리고 예측을 한 후 결과를 보았는데 53%의 정확도를 보였다.

이대로는 쓸 수 없을 것 같다.

 

문제는 두가지였다.

첫번째, 너무 오래걸린다. 구글링을 해보니 원래 Bert가 굉장히 복잡한 모델이어서 아무리 빠르게 해도 오래걸리고 코드를 잘못 짜면 하루종일 이 코드만 돌리고 있어야할 판이었다. DistilBert라고 좀 더 적은 파라미터로 학습시간을 개선한 모델이 나왔길래 시도를 해보았으나 코드에 대한 정보가 많이 없고 하나씩 찾으며 정보를 모아 코드를 하나하나 생성해보기엔 아직 BERT를 다루는 내 실력이 부족했다...

두번째, GPU가 안된다. 왜 안되는지 구글링을 해보았는데 dataset 형식을 GPU가 지원을 안해주는 모양이다. 정답인지는 모르겠으나 모델은 GPU로 실행이 되는데 데이터는 GPU가 아닌 CPU를 타고 있어서 일수도 있다. 그래서 GPU로 실행하도록 온갖 방법을 써보았으나 실패했다. 이에 대한 해결책은 pytorch였다. tensorflow는 dataset을 안쓰고 모델을 돌리는 법도 dataset을 GPU로 돌리는 법도 모르기 때문에 pytorch로 실행을 해보는 것이었다.

 

아직 pytorch에 대한 지식이 부족해 추후에 공부를 더 해볼 예정이었는데 어쩔 수 없이 pytorch에 대한 지식이 많이 없는 채로 코드를 돌려보게 되었다.

나의 코드는 

https://sophieeunajang.wordpress.com/2021/01/15/%ed%95%9c%ea%b5%ad%ec%96%b4-bert-%ec%82%ac%ec%9a%a9-%eb%b0%a9%eb%b2%95/

 

한국어 BERT 사용 방법

BERT의 아키텍쳐를 조금 알고 계신다고 생각 하고 설명 하겠습니다! BERT는 GPU가 필요 하니까 ~ 저처럼 GPU가 없으신 분들은 Colab으로 접속해 주시구요! Colab은 Jupyter과 많이 비슷해요 :) 들어오셔서

sophieeunajang.wordpress.com

https://velog.io/@seolini43/%EC%9D%BC%EC%83%81%EC%97%B0%EC%95%A0-%EC%A3%BC%EC%A0%9C%EC%9D%98-%ED%95%9C%EA%B5%AD%EC%96%B4-%EB%8C%80%ED%99%94-BERT%EB%A1%9C-%EC%9D%B4%EC%A7%84-%EB%B6%84%EB%A5%98-%EB%AA%A8%EB%8D%B8-%EB%A7%8C%EB%93%A4%EA%B8%B0%ED%8C%8C%EC%9D%B4%EC%8D%ACColab-%EC%BD%94%EB%93%9C

 

[파이썬]일상/연애 주제의 한국어 대화 'BERT'로 이진 분류 모델 만들기 - 코드

BERT를 이용한 프로젝트 - 코딩편 입니다!

velog.io

이 두 링크를 결합하고 또 나의 데이터를 결합하여 코드를 생성하였다.

사실 거의 그대로 가져다 쓴거라 나의 블로그엔 따로 쓰지 않겠다.

결과는 gpu를 사용했으므로 시간이 확 줄어들길 기대했으나 그러지 않았고, 결과도 좋지 못했다.

혹시 모델이 잘못일까 싶어 위 링크에 있는 모델도 동일하게 사용해보았으나 똑같이 결과가 좋지 못했다.

위 링크에 있는 데이터로 코드를 실행했을 땐 정확도가 80%가 넘어 꽤 잘 나왔다.

아마도 데이터가 문제인 것 같은데 데이터를 새로 찾거나 이 데이터를 가지고 bert를 실행한 예시를 찾아보는 것이 좋겠다.

꼭 bert로 결과를 내서 주가 예측을 해볼 수 있으면 좋겠다.

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

BERT (2)  (0) 2023.05.24
트위터 API  (0) 2023.05.18
Collect New york times news headlines using r  (0) 2023.02.01
news data preprocessing code modification  (0) 2023.01.15