본문 바로가기
AI/자연어처리

GPT 를 이용한 영화 리뷰 분류 (38)

by 바다의 공간 2025. 1. 10.

GPT를 이용한 네이버 영화 리뷰 분류

!pip install tensorflow==2.12.0

tensorflow를 다운그레이드하게되고 2.12.0으로 합니다.

import tensorflow as tf
tf.__version__

버전 확인까지 한 번 다시해주고


GPT를 이용한 네이버 영화 리뷰 분류

import pandas as pd
import numpy as np
import urllib.request
import os
from tqdm import tqdm
import tensorflow as tf
from transformers import AutoTokenizer, TFGPT2Model
from tensorflow.keras.preprocessing.sequence import pad_sequences

텐서플러우 허그 뭐시기에 들어있는것이고 GPT2모델을 가져온것을 확인할 수 있습니다.

urllib.request.urlretrieve("https://raw.githubusercontent.com/e9t/nsmc/master/ratings_train.txt", filename="ratings_train.txt")
urllib.request.urlretrieve("https://raw.githubusercontent.com/e9t/nsmc/master/ratings_test.txt", filename="ratings_test.txt")
train_data = pd.read_table('ratings_train.txt')
test_data = pd.read_table('ratings_test.txt')
print('훈련용 리뷰 개수 :',len(train_data)) # 훈련용 리뷰 개수 출력
print('테스트용 리뷰 개수 :',len(test_data)) # 테스트용 리뷰 개수 출력

로보면 훈련 리뷰와 테스트용 리뷰를 볼 수 있는데

 

훈련용 리뷰 개수 : 150000
테스트용 리뷰 개수 : 50000 입니다.

train_data.head()

train_data.drop_duplicates(subset=['document'], inplace=True)
train_data = train_data.dropna(how='any')

print('훈련데이터 개수: ', len(train_data))

중복데이터가 있는것이 있으면 제거하기 위해서 drop_duplicates를 하겠습니다. document행안에서 말이에용

그리고 train_data.dropna(how='any')로 하겠습니다.

 

으로 보면 훈련데이터 개수:  146182입니다.

test_data = test_data.dropna(how = 'any')
print('테ㅡ트 데이터 개수: ', len(test_data))

테ㅡ트 데이터 개수:  49997

로 볼 수 있습니다. 3개정도밖에 안빠진것같습니다.


GPT의 입력

tokenizer = AutoTokenizer.from_pretrained('skt/kogpt2-base-v2', bos_token='</s>', eos_token='</s>', pad_token='<pad>')
print(tokenizer.tokenize("보는내내 그대로 들어맞는 예측 카리스마 없는 악역"))
['▁보는', '내', '내', '▁그대로', '▁들어', '맞', '는', '▁예측', '▁카', '리스', '마', '▁없는', '▁악', '역']

print(tokenizer.encode("보는내내 그대로 들어맞는 예측 카리스마 없는 악역"))
[11867, 7071, 7071, 10554, 9359, 7498, 7162, 15305, 9488, 10191, 7487, 9712, 9868, 8031]

tokenizer.decode(tokenizer.encode("보는내내 그대로 들어맞는 예측 카리스마 없는 악역"))
보는내내 그대로 들어맞는 예측 카리스마 없는 악역

 

일부 문장을 가져와서 koGPT-2에서 이해할 수 있는 토큰 단위로 분리하려고 합니다.

koGPT-2모델은 문장을 단어대신 서브워드 단위로 처리합니다.

 

또한 _ 기호는 단어의 시작 부분을 나타냅니다.

 

두번쨰로는 정수인코딩을 해준것이고 

토큰화된 단어를 모델이 처리할 수 있는 정수 인덱스로 변환하겠죠? koGPT-2 모델은 정수로 표현된 데이터를 입력으로 받구요.

 

마지막으로 인코딩된 데이터를가지고 다시 원래 문장으로 복원할 수 있는지를 확인했습니다.

 

KoGPT-2 모델은 텍스트를 "토큰 단위 → 정수 인코딩 → 출력 벡터" 순서로 처리합니다.

 

잘 처리되는것을 볼 수 있죠?

 

max_seq_len = 128

encoded_result = tokenizer.encode("전율을 일으키는 영화. 다시 보고 싶은 영화",
                                  max_length=max_seq_len, pad_to_max_length=True)
print(encoded_result)
print('길이:', len(encoded_result))

 

다음은 pad, max_seq_len까지 입력해서 encoded_result를 해봅니다.

해밨더니 

Truncation was not explicitly activated but `max_length` is provided a specific value, please use `truncation=True` to explicitly truncate examples to max length. Defaulting to 'longest_first' truncation strategy. If you encode pairs of sequences (GLUE-style) with the tokenizer you can select this strategy more precisely by providing a specific strategy to `truncation`.
[9034, 13555, 16447, 10584, 389, 9427, 10056, 22386, 10584, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3]
길이: 128
/usr/local/lib/python3.10/dist-packages/transformers/tokenization_utils_base.py:2673: FutureWarning: The `pad_to_max_length` argument is deprecated and will be removed in a future version, use `padding=True` or `padding='longest'` to pad to the longest sequence in the batch, or use `padding='max_length'` to pad to a max length. In this case, you can give a specific length with `max_length` (e.g. `max_length=45`) or leave max_length to None to pad to the maximal input size of the model (e.g. 512 for Bert).
  warnings.warn(

 

의 결과가 나옵니다. 길이는 128개로 보이는것을 확인할 수 있겠죠?

 

koGPT-2의 시작/종료 토큰을 앞뒤에 </s>로 부착합니다.

정해진 최대 길이인 128로 패딩하구요.

def convert_examples_to_features(examples, labels, max_seq_len, tokenizer):
  input_ids, data_labels = [], []

  for example, label in tqdm(zip(examples, labels), total=len(examples)):

    bos_token = [tokenizer.bos_token]
    eos_token = [tokenizer.eos_token]
    # 시작/종료 토큰을 앞뒤에 부착
    tokens = bos_token + tokenizer.tokenize(example) + eos_token
    input_id = tokenizer.convert_tokens_to_ids(tokens)
    # 패딩
    input_id = pad_sequences([input_id], maxlen=max_seq_len,
                             value=tokenizer.pad_token_id, padding='post')[0]

    input_ids.append(input_id)
    data_labels.append(label)

  input_ids = np.array(input_ids, dtype=int)
  data_labels = np.asarray(data_labels, dtype=np.int32)

  return input_ids, data_labels

📚 정수인코딩 변환

train, test에 있는 데이터들을 정수인코딩으로 변환하려고합니다.

train_X, train_y = convert_examples_to_features(train_data['document'], train_data['label'],
                             max_seq_len=max_seq_len, tokenizer=tokenizer)
                             

test_X, test_y = convert_examples_to_features(test_data['document'], test_data['label'],
                             max_seq_len=max_seq_len, tokenizer=tokenizer)

 

📚 인코딩 된 첫번째 샘플 확인

input_id = train_X[0]
label = train_y[0]
print('정수인코딩:', input_id)
print('인코딩 길이', len(input_id))
print('인코딩 복원', tokenizer.decode(input_id))
print('레이블', label)


정수인코딩: [    1  9050  9267  7700  9705 23971 12870  8262  7055  7098  8084 48213
     1     3     3     3     3     3     3     3     3     3     3     3
     3     3     3     3     3     3     3     3     3     3     3     3
     3     3     3     3     3     3     3     3     3     3     3     3
     3     3     3     3     3     3     3     3     3     3     3     3
     3     3     3     3     3     3     3     3     3     3     3     3
     3     3     3     3     3     3     3     3     3     3     3     3
     3     3     3     3     3     3     3     3     3     3     3     3
     3     3     3     3     3     3     3     3     3     3     3     3
     3     3     3     3     3     3     3     3     3     3     3     3
     3     3     3     3     3     3     3     3]
인코딩 길이 128
인코딩 복원 </s> 아 더빙.. 진짜 짜증나네요 목소리</s><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad><pad>
레이블 0

 

📚 GPT출력

model = TFGPT2Model.from_pretrained('skt/kogpt2-base-v2', from_pt=True)

 

이제 GPT-2모델을 가지고 오려고합니다. MODEL이라는 변수에 담아주겠습니다.

 

max_seq_len = 128

input_ids_layer = tf.keras.layers.Input(shape=(max_seq_len,), dtype=tf.int32)
outputs = model([input_ids_layer])

outputs[0] 는 (batch_size, 128, 768)
텍스트 분류 문제를 다룰 경우 koGPT-2 의 마지막 예측에 해당하는 벡터를 사용해야 합니다.

마지막 출력 벡터는 

print(outputs[0][:, -1])

로 뽑아볼 수 있겠죠


📚 GPT를 이용한 텍스트 분류 모델 만들기

서브클래싱 구현 방식으로 구현한 텍스트 분류 모델은 다음과 같습니다.
GPT의 출력 중 outputs[0][:, -1]. 즉, 마지막 출력 벡터를
시그모이드 함수가 활성화 함수로 설정된 출력층으로 연결합니다.

class TFGPT2ForSequenceClassification(tf.keras.Model):
  def __init__(self, model_name):
    super(TFGPT2ForSequenceClassification, self).__init__()

    self.gpt = TFGPT2Model.from_pretrained(model_name, from_pt=True)
    self.dropout = tf.keras.layers.Dropout(0.2)
    self.classifier = tf.keras.layers.Dense(1,
                                            kernel_initializer=tf.keras.initializers.TruncatedNormal(0.02),
                                            activation='sigmoid',
                                            name='classifier')

  def call(self, inputs):
    outputs = self.gpt(input_ids = inputs)
    cls_token = outputs[0][:, -1]
    cls_token = self.dropout(cls_token)
    prediction = self.classifier(cls_token)

    return prediction
model = TFGPT2ForSequenceClassification('skt/kogpt2-base-v2')
optimizer = tf.keras.optimizers.Adam(5e-5)
loss = tf.keras.losses.BinaryCrossentropy()
model.compile(optimizer=optimizer, loss=loss, metrics=['accuracy'])

 

optimizer, loss를 입력해준 후 compile해줍니다.

이렇게 좀 튜닝해준 후 fit하려고하는데 굉장히 오래걸립니다.

model.fit(train_X, train_y, epochs=2, batch_size=32, validation_split=0.2)
results = model.evaluate(test_X, test_y, batch_size=1024)
print('test loss, test accuracy:', results)

📚 리뷰예측

def sentiment_predict(new_sentence):
  bos_token = [tokenizer.bos_token]
  eos_token = [tokenizer.eos_token]
  tokens = bos_token + tokenizer.tokenize(new_sentence) + eos_token
  input_id = tokenizer.convert_tokens_to_ids(tokens)
  input_id = pad_sequences([input_id], maxlen=max_seq_len,
                           value=tokenizer.pad_token_id, padding='post')[0]

  input_id = np.array([input_id])
  score = model.predict(input_id)[0][0]

  if(score > 0.5):
    print("{:.2f}% 확률로 긍정 리뷰입니다.\n".format(score * 100))
  else:
    print("{:.2f}% 확률로 부정 리뷰입니다.\n".format((1 - score) * 100))

이건 50%이상이면 긍정이라고 확인해볼 수 있겠죠

bert내에서 test했던 것인데 실행시켜보면 긍정, 부정을 나눌 수 있는 리뷰 예측이 됩니다.

sentiment_predict('보던거라 계속보고있는데 전개도 느리고 주인공인 은희는 한두컷 나오면서 소극적인모습에 ')
sentiment_predict("스토리는 확실히 실망이였지만 배우들 연기력이 대박이였다 특히 이제훈 연기 정말 ... 이 배우들로 이렇게밖에 만들지 못한 영화는 아쉽지만 배우들 연기력과 사운드는 정말 빛났던 영화. 기대하고 극장에서 보면 많이 실망했겠지만 평점보고 기대없이 집에서 편하게 보면 괜찮아요. 이제훈님 연기력은 최고인 것 같습니다")
sentiment_predict("남친이 이 영화를 보고 헤어지자고한 영화. 자유롭게 살고 싶다고 한다. 내가 무슨 나비를 잡은 덫마냥 나에겐 다시 보고싶지 않은 영화.")
sentiment_predict("이 영화 존잼입니다 대박")
sentiment_predict('이 영화 개꿀잼 ㅋㅋㅋ')
sentiment_predict('이 영화 핵노잼 ㅠㅠ')
sentiment_predict('이딴게 영화냐 ㅉㅉ')
sentiment_predict('감독 뭐하는 놈이냐?')
sentiment_predict('와 개쩐다 정말 세계관 최강자들의 영화다')

'AI > 자연어처리' 카테고리의 다른 글

Anaconda 아나콘다 & streamlit(1)  (0) 2025.01.22
Hello Langchain (39)  (0) 2025.01.13
GPT (37)  (0) 2025.01.09
한국어 BERT 를 이용한 네이버 영화 리뷰 분류 (36)  (0) 2025.01.08
BERT 의 MLM, NSP (35)  (0) 2025.01.07