Data Analyst KIM

[DL/NLP] 문서 분류 모델 만들기(영화 리뷰 감성 분석) 본문

데이터 분석/ML&DL&NLP

[DL/NLP] 문서 분류 모델 만들기(영화 리뷰 감성 분석)

김두연 2023. 11. 6. 20:37
반응형

자연어 처리를 이용한 영화 리뷰 감성분석 모델 만들기

 


<문서 분류 모델 학습하기>

Step1. 코랩 노트북 초기화하기

  • 오른쪽 상단의 링크 접속, [내 드라이브에 복사] 진행
  • [런타임 → 런타임 유형 변경]을 클릭하고 [GPU]나 [TPU] 둘 중 하나 선택 (GPU 사용 권장)

Step2. 각종 설정하기

  • 패키지 설치
  • 구글 드라이브 연동
  • 모델 환경 설정
  • 랜덤 시드 고정
  • 로거 설정
# 의존성 패키지 설치
!pip install ratsnlp

# 구글드라이브와 연결
from google.colab import drive
drive.mount('/gdrive', force_remount=True)

# 모델 환경 설정
import torch
from ratsnlp.nlpbook.classification import ClassificationTrainArguments
args = ClassificationTrainArguments(
    pretrained_model_name="beomi/kcbert-base",
    downstream_corpus_name="nsmc",
    downstream_model_dir="/gdrive/My Drive/nlpbook/checkpoint-doccls",
    batch_size=32 if torch.cuda.is_available() else 4,
    learning_rate=5e-5,
    max_seq_length=128,
    epochs=3,
    tpu_cores=0 if torch.cuda.is_available() else 8,
    seed=7,
)

# 랜덤 시드 고정
from ratsnlp import nlpbook
nlpbook.set_seed(args)

# 로거 설정
nlpbook.set_logger(args)

 

Step3. 말뭉치 내려받기

# 말뭉치 내려받기
from Korpora import Korpora
Korpora.fetch(
    corpus_name=args.downstream_corpus_name,
    root_dir=args.downstream_corpus_root_dir,
    force_download=True,
)

 

Step4. 토크나이저 준비하기

# 토크나이저 준비
from transformers import BertTokenizer
tokenizer = BertTokenizer.from_pretrained(
    args.pretrained_model_name,
    do_lower_case=False,
)

Step5. 데이터 전처리하기

  • 학습 데이터 구축
  • 학습 데이터 로더 구축
  • 테스트 데이터 구축
# 학습데이터 구축
from torch.utils.data import DataLoader, SequentialSampler, RandomSampler
from ratsnlp.nlpbook.classification import NsmcCorpus, ClassificationDataset
corpus = NsmcCorpus()
train_dataset = ClassificationDataset(
    args=args,
    corpus=corpus,
    tokenizer=tokenizer,
    mode="train",
)
train_dataloader = DataLoader(
    train_dataset,
    batch_size=args.batch_size,
    sampler=RandomSampler(train_dataset, replacement=False),
    collate_fn=nlpbook.data_collator,
    drop_last=False,
    num_workers=args.cpu_workers,
)


# 학습 데이터 로더 구축
from torch.utils.data import DataLoader, RandomSampler
train_dataloader = DataLoader(
    train_dataset,
    batch_size=args.batch_size,
    sampler=RandomSampler(train_dataset, replacement=False),
    collate_fn=nlpbook.data_collator,
    drop_last=False,
    num_workers=args.cpu_workers,
)


# 테스트 데이터 구축
from torch.utils.data import SequentialSampler
val_dataset = ClassificationDataset(
    args=args,
    corpus=corpus,
    tokenizer=tokenizer,
    mode="test",
)
val_dataloader = DataLoader(
    val_dataset,
    batch_size=args.batch_size,
    sampler=SequentialSampler(val_dataset),
    collate_fn=nlpbook.data_collator,
    drop_last=False,
    num_workers=args.cpu_workers,
)

Step6. 모델 불러오기

# 모델 초기화
from transformers import BertConfig, BertForSequenceClassification
pretrained_model_config = BertConfig.from_pretrained(
    args.pretrained_model_name,
    num_labels=corpus.num_labels,
)


model = BertForSequenceClassification.from_pretrained(
        args.pretrained_model_name,
        config=pretrained_model_config,
)

Step7. 모델 학습시키기

  • 학습 준비
  • 학습
# Task 정의
from ratsnlp.nlpbook.classification import ClassificationTask
task = ClassificationTask(model, args)

# Trainer 정의
trainer = nlpbook.get_trainer(args)

# 학습개시
trainer.fit(
    task,
    train_dataloaders=train_dataloader,
    val_dataloaders=val_dataloader,
)


<학습 마친 모델을 실전 투입하기>

  • 영화 리뷰 감성 분석 웹 서비스 만들기

Step1. 코랩 노트북 초기화하기

Step2. 환경 설정하기

  • 패키지 설치
  • 구글 드라이브 연동하기
  • 각종 설정

Step3. 토크나이저 및 모델 불러오기

  • 모델 로딩

Step4. 모델 출력값 만들고 후처리하기

  • 인퍼런스 함수 선언

Step5. 웹 서비스 시작하기

  • 웹서비스 만들기 준비
  • 웹서비스 개시
## Step2. 환경 설정하기  ## 
# 의존성 패키지 설치
!pip install ratsnlp

# 구글 드라이브 연동
from google.colab import drive
drive.mount('/gdrive', force_remount=True)

# 인퍼런스 설정
from ratsnlp.nlpbook.classification import ClassificationDeployArguments
args = ClassificationDeployArguments(
    pretrained_model_name="beomi/kcbert-base",
    downstream_model_dir="/gdrive/My Drive/nlpbook/checkpoint-doccls",
    max_seq_length=128,
)

## Step3. 토크나이저 및 모델 불러오기 ## 
# 토크나이저 로드
from transformers import BertTokenizer
tokenizer = BertTokenizer.from_pretrained(
    args.pretrained_model_name,
    do_lower_case=False,
)

# 체크포인트 로드
import torch
from transformers import BertConfig, BertForSequenceClassification
fine_tuned_model_ckpt = torch.load(
    args.downstream_model_checkpoint_fpath,
    map_location=torch.device("cpu")
)

# BERT 설정 로드
pretrained_model_config = BertConfig.from_pretrained(
    args.pretrained_model_name,
    num_labels=fine_tuned_model_ckpt['state_dict']['model.classifier.bias'].shape.numel(),
)

# BERT 모델 초기화
model = BertForSequenceClassification(pretrained_model_config)

# 체크포인트 주입하기
model.load_state_dict({k.replace("model.", ""): v for k, v in fine_tuned_model_ckpt['state_dict'].items()})

# 평가 모드로 전환
model.eval()

## Step4. 모델 출력값 만들고 후처리하기 ##
# 인퍼런스 함수
def inference_fn(sentence):
    inputs = tokenizer(
        [sentence],
        max_length=args.max_seq_length,
        padding="max_length",
        truncation=True,
    )
    with torch.no_grad():
        outputs = model(**{k: torch.tensor(v) for k, v in inputs.items()})
        prob = outputs.logits.softmax(dim=1)
        positive_prob = round(prob[0][1].item(), 4)
        negative_prob = round(prob[0][0].item(), 4)
        pred = "긍정 (positive)" if torch.argmax(prob) == 1 else "부정 (negative)"
    return {
        'sentence': sentence,
        'prediction': pred,
        'positive_data': f"긍정 {positive_prob}",
        'negative_data': f"부정 {negative_prob}",
        'positive_width': f"{positive_prob * 100}%",
        'negative_width': f"{negative_prob * 100}%",
    }

## Step5. 웹 서비스 시작하기 ## 
!mkdir /root/.ngrok2 && echo "authtoken: test111" > /root/.ngrok2/ngrok.yml
!mkdir /root/.ngrok2 && echo "authtoken: 2V2vAP8ftksxuHsG5827SL2SksF_5TtQ9Bu7u57om6kH2XwMy" > /root/.ngrok2/ngrok.yml

# 웹 서비스
from ratsnlp.nlpbook.classification import get_web_service_app
app = get_web_service_app(inference_fn)
app.run()


IMDB 리뷰 감성 분류하기(IMDB Movie Review Sentiment Analysis)

import numpy as np
import matplotlib.pyplot as plt
from tensorflow.keras.datasets import imdb

(X_train, y_train), (X_test, y_test) = imdb.load_data()

print('훈련용 리뷰 개수 : {}'.format(len(X_train)))
print('테스트용 리뷰 개수 : {}'.format(len(X_test)))
num_classes = len(set(y_train))
print('카테고리 : {}'.format(num_classes))
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/imdb.npz
17464789/17464789 [==============================] - 2s 0us/step
훈련용 리뷰 개수 : 25000
테스트용 리뷰 개수 : 25000
카테고리 : 2
# 레이블 분포 확인
unique_elements, counts_elements = np.unique(y_train, return_counts=True)
print("각 레이블에 대한 빈도수:")
print(np.asarray((unique_elements, counts_elements)))
각 레이블에 대한 빈도수:
[[      0          1]
[12500 12500]]
word_to_index = imdb.get_word_index()
index_to_word = {}
for key, value in word_to_index.items():
    index_to_word[value+3] = key
    
    
for index, token in enumerate(("<pad>", "<sos>", "<unk>")):
  index_to_word[index] = token

print(' '.join([index_to_word[index] for index in X_train[0]]))

GRU로 IMDB 리뷰 감성 분류하기

  • 단어 집합의 크기를 10,000으로 제한하고, 리뷰 최대 길이는 500으로 제한하여 패딩을 진행
import re
from tensorflow.keras.datasets import imdb
from tensorflow.keras.preprocessing.sequence import pad_sequences
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, GRU, Embedding
from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint
from tensorflow.keras.models import load_model

vocab_size = 10000
max_len = 500

(X_train, y_train), (X_test, y_test) = imdb.load_data(num_words=vocab_size)

X_train = pad_sequences(X_train, maxlen=max_len)
X_test = pad_sequences(X_test, maxlen=max_len)

모델 적합(약 1시간 10분)

하이퍼파라미터인 임베딩 벡터의 차원은 100, 은닉 상태의 크기는 128
모델은 다 대 일 구조의 GRU를 사용(이진 분류 문제를 수행하는 모델)
이진 분류 문제의 경우, 출력층에 로지스틱 회귀를 사용(활성화 함수로는 시그모이드 함수를 사용)
손실 함수로 크로스 엔트로피 함수를 사용
하이퍼파라미터인 배치 크기는 64이며, 15 에포크를 수행
EarlyStopping(monitor='val_loss', mode='min', verbose=1, patience=4): 검증 데이터 손실(val_loss)이 증가하면, 과적합 징후므로 검증 데이터 손실이 4회 증가하면 정해진 에포크가 도달하지 못하였더라도 학습을 조기 종료(Early Stopping)
ModelCheckpoint를 사용하여 검증 데이터의 정확도(val_acc)가 이전보다 좋아질 경우에만 모델을 저장validation_split=0.2을 사용하여 훈련 데이터의 20%를 검증 데이터로 분리해서 사용
검증 데이터는 기계가 훈련 데이터에 과적합되고 있지는 않은지 확인하기 위한 용도로 사용
embedding_dim = 100
hidden_units = 128

model = Sequential()
model.add(Embedding(vocab_size, embedding_dim))
model.add(GRU(hidden_units))
model.add(Dense(1, activation='sigmoid'))

es = EarlyStopping(monitor='val_loss', mode='min', verbose=1, patience=4)
mc = ModelCheckpoint('GRU_model.h5', monitor='val_acc', mode='max', verbose=1, save_best_only=True)

model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['acc'])
history = model.fit(X_train, y_train, epochs=15, callbacks=[es, mc], batch_size=64, validation_split=0.2)
loaded_model = load_model('GRU_model.h5')
print("\n 테스트 정확도: %.4f" % (loaded_model.evaluate(X_test, y_test)[1]))

 

테스트 정확도 : 0.8728


리뷰의 긍, 부정을 예측

def sentiment_predict(new_sentence):
  # 알파벳과 숫자를 제외하고 모두 제거 및 알파벳 소문자화
  new_sentence = re.sub('[^0-9a-zA-Z ]', '', new_sentence).lower()
  encoded = []

  # 띄어쓰기 단위 토큰화 후 정수 인코딩
  for word in new_sentence.split():
    try :
      # 단어 집합의 크기를 10,000으로 제한.
      if word_to_index[word] <= 10000:
        encoded.append(word_to_index[word]+3)
      else:
      # 10,000 이상의 숫자는 <unk> 토큰으로 변환.
        encoded.append(2)
    # 단어 집합에 없는 단어는 <unk> 토큰으로 변환.
    except KeyError:
      encoded.append(2)

  pad_sequence = pad_sequences([encoded], maxlen=max_len)
  score = float(loaded_model.predict(pad_sequence)) # 예측

  if(score > 0.5):
    print("{:.2f}% 확률로 긍정 리뷰입니다.".format(score * 100))
  else:
    print("{:.2f}% 확률로 부정 리뷰입니다.".format((1 - score) * 100))
test_input = "This movie was just way too overrated. The fighting was not professional and in slow motion. I was expecting more from a 200 million budget movie. The little sister of T.Challa was just trying too hard to be funny. The story was really dumb as well. Don't watch this movie if you are going because others say its great unless you are a Black Panther fan or Marvels fan."

sentiment_predict(test_input)

84.14% 확률로 부정 리뷰입니다.

test_input = " I was lucky enough to be included in the group to see the advanced screening in Melbourne on the 15th of April, 2012. And, firstly, I need to say a big thank-you to Disney and Marvel Studios. \
Now, the film... how can I even begin to explain how I feel about this film? It is, as the title of this review says a 'comic book triumph'. I went into the film with very, very high expectations and I was not disappointed. \
Seeing Joss Whedon's direction and envisioning of the film come to life on the big screen is perfect. The script is amazingly detailed and laced with sharp wit a humor. The special effects are literally mind-blowing and the action scenes are both hard-hitting and beautifully choreographed."

sentiment_predict(test_input)

99.40% 확률로 긍정 리뷰입니다.

반응형