본문 바로가기
IT & 데이터 사이언스/데이터 분석 실습

[DACON] 영화 리뷰 감성분석 경진대회(Basic)

by 바른 호랑이 2022. 1. 22.
728x90
반응형

※ DACON 링크

 

영화 리뷰 감성분석 경진대회 - DACON

좋아요는 1분 내에 한 번만 클릭 할 수 있습니다.

dacon.io

※ DACON 공유 코드

 

영화 리뷰 감성분석 경진대회 - DACON

사이킷런 tfidf, stacking [public score: 0.8795]

dacon.io

해당 경진대회는 데이터 분석에 관심이 있는 사람들의 학습을 돕고, 실제 데이터 분석능력을 함양할 수 있게 해주는 DACON에서 진행중인 온라인 기반 경진대회였다. Basic에 해당되어 분석할 때 참고할 수 있는 Baseline코드를 제공해주고 있으며, 해당코드를 활용하여 시작할 수 있다. 추가적으로 코드를 다른 사람들과 공유하면 순위권에 들지 않아도 상품을 제공해주고 있기에 다른 사람들도 다양한분석코드를 올려주니 해당 내용도 참고하여 분석을 진행할 수 있어서 학습을 위한 목적에서 참가하였다.

개인적으로 리뷰 감성분석은 좋은 결과를 얻기 위해서는 자연어 처리와 딥러닝에 대한 기본적인 지식이 있어야 하기에 초심자가 접하기에는 생소하고 어려운 분야라고 생각한다. 다행히도 나와 비슷한 걱정을 지닌 사람들도 지식과 기술을 체득할 수있게 많은 사람들이 지식과 기술을 공유해주고 있어서 많은 것들을 배울 수 있었다. 다만 자연어 처리는 CountVectorizer나 Tfidf를 활용한 방법들은 이미 DACON에서 코드 공유를 해주는 사람들이 많았기에 다른 방법으로 분석을 진행해보았다.

 

# 분석을 위한 기본적인 패키지 임포트 및 로드
import tensorflow as tf
import matplotlib.pyplot as plt
import numpy as np
import os
import pandas as pd

print(train.isna().sum().sum()) # 결측값 확인 - 결측값 없음
print(train.document.nunique()) # 중복 여부 확인 - 중복값 없음
print(train.columns)

# label 분포도 확인 - 균등
labels = list(train['label'].unique())
sizes = train['label'].value_counts()
colors = ['red', 'lightskyblue']
explode = (0, 0.1)
plt.title('Label Distribution')
plt.pie(sizes, explode = explode, labels = labels, colors = colors,
        autopct = '%1.1f%%', shadow = True, startangle = 90)
plt.axis('equal')
plt.show()

 

분석에 사용할 기본적인 패키지들을 로드하고 리뷰데이터의 결측값과 중복값을 확인해본 후, label열의 값 분포를 인해보았다. 훈련데이터 상에는 결측값과 중복값이 없고, 2가지의 값이 균등하게 분포하고 있는 것을 확인하였디.

 

# 정규표현식을 사용하여 한글과 영어를 제외하고 전부 삭제
df_train['document'] = df_train['document'].str.replace('[^ㄱ-하-ㅣ가-힣A-Za-z ]', ' ')
df_train.head()

# 한글 처리
from konlpy.tag import Okt
okt = Okt()
word_dict = dict()

# 토큰화 작업후 단어의 개수 세기
for review in df_train.document:
    word_list = okt.morphs(review)
    for word in word_list:
        if word not in word_dict:
            word_dict[word] = 1
        else:
            word_dict[word] += 1
            
# # 빈도수가 높은 단어만 사용
# for i in range(5000):
#     word = words[i][0]
#     word_index[word] = cnt
#     cnt += 1

# operator를 사용한 단어사전 정렬(빈도수 기준 내림차순 정렬)
import operator

# key=operator.itemgetter(0) - 키 값으로 정렬 / key=operator.itemgetter(1) value 값으로 정렬
words = sorted(word_dict.items(), key=operator.itemgetter(1), reverse = True)
# print(len(words))
# 각 단어별로 인덱스 번호 부여
word_index = dict()
word_index['NA'] = 0
cnt = 1
# 전부 사용
for word in words:
    word = word[0]
    word_index[word] = cnt
    cnt += 1

# # 빈도수가 높은 단어만 사용
# for i in range(5000):
#     word = words[i][0]
#     word_index[word] = cnt
#     cnt += 1

# 훈련데이터 셋 최대 문장길이 확인
df_train['token'] = df_train.document.apply(lambda x: okt.morphs(x))
# max_length = 0
# for token_list in df_train.token:
#     sample = len(token_list)
#     if max_length < sample:
#         max_length = sample
# max_length
max_length = 12

# 벡터화 및 패딩 작업 진행
def tk_vect(token_list, max_length):
    vector_list = []
    for token in token_list:
        if token in word_index:
            vector_list.append(word_index[token])
        else:
            vector_list.append(word_index['NA'])
    if len(vector_list) < max_length:
        for _ in range(len(vector_list), max_length):
            vector_list.append(word_index['NA'])
    if len(vector_list) > max_length:
            vector_list = vector_list[:max_length] 
    return vector_list

df_train['vectorization'] = df_train['token'].apply(lambda x: tk_vect(x, max_length))

 

그 후에는 정규표현식을 활용하여 리뷰데이터에서 한글과 영어를 제외한 모든 값들을 제거한 후 Konlpy의 가장 기본적인 토크나이저인 OKT로 자연어 처리를 진행해보았다. 각각의 리뷰 데이터에 대해 토큰으로 분할 후 단어(형태소)사전을 만들고, 빈도 수를 기준으로 사전을 정렬하였다. 컴퓨터는 사람의 언어를 직접적으로 인식할 수 없기에 0과 1로 이루어진 데이터로 변환하는 과정이 필요하며, 딥러닝을 위해서는 반드시 입력데이터의 형태를 맞춰야 하기에 리뷰데이터의 최대 길이를 확인후 각각의 데이터에 대해 패딩 작업을 진행하였다.

 

from sklearn.linear_model import LogisticRegression #모델 불러오기
from sklearn.metrics import accuracy_score 

lr = LogisticRegression(max_iter = 500) #객체에 모델 할당
lr.fit(list(df_train.vectorization), list(df_train.label)) #모델 학습
pred = lr.predict(list(df_train.vectorization))
accuracy_score(df_train.label, pred)

그 후 테스트 차원에서 로지스틱 회귀 모델을 활용하여 분류모델을 작성해봤는데, 정확도가 0.54정도로 좋지 않은 것으로 확인하였다. 성능 향상을 위해 임베딩부터 딥러닝 분석을 진행하기로 생각하였고, 임베딩부터 진행하기로 하였다.

 

# 딥러닝 모델을 사용하기 위해서는 넘파이 구조로 자료형을 변환해주어야 합니다.
deep_train = df_train[['vectorization', 'label']].copy()
for i in range(max_length):
    deep_train[f'{i}'] = deep_train.vectorization.apply(lambda x: x[i]) 
deep_train.drop(['vectorization'], axis = 1, inplace = True)

# 분석에 사용할 데이터를 훈련 데이터와 평가 데이터로 분할 - 여기서는 평가데이터를 검증 데이터로 사용할 예정입니다.
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(deep_train.drop(['label'], axis = 1), deep_train[['label']], \
                                                    test_size = 0.2, stratify =deep_train[['label']], random_state = 7)
                                                    
# 학습데이터로 사용하기 위해 pandas파일을 numpy파일로 변환
print(X_train.shape, X_train.to_numpy().shape, X_test.shape, X_test.to_numpy().shape)
X_train = X_train.to_numpy()
X_test = X_test.to_numpy()
y_train = y_train.label.to_numpy()
y_test = y_test.label.to_numpy()

from tensorflow.keras.utils import to_categorical

# quality가 범주형이므로 범주형으로 변환
y_train = to_categorical(y_train)
y_test = to_categorical(y_test)

from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Embedding, Flatten

model = Sequential()
# input_length와 입력데이터의 차원을 일치시켜줘야함
# output_dim은 임베딩 벡터의 수와 일치 - 32개의 텐서로 하나의 토큰을 설명 -> 토큰간 유사도 분석
model.add(Embedding(input_dim = len(word_index), output_dim = 32, input_length = max_length))
# Flatten()층은 다차원의 배열을 1차원으로 펼쳐주는 층임.
model.add(Flatten())
# 마지막 출력층은 반드시 label의 value의 개수와 일치시켜줄 것
model.add(Dense(2, activation = 'sigmoid'))
model.compile(optimizer = 'adam', loss = 'binary_crossentropy', metrics = ['acc'])
model.summary()
history = model.fit(X_train, y_train, batch_size = 64, epochs = 30, validation_data = (X_test, y_test))

history_dict = history.history

ax1 = plt.subplot(2, 1, 1)
loss = history_dict['loss']
val_loss = history_dict['val_loss']
epochs = range(1, len(loss)+1)
plt.plot(epochs, loss, 'r', label = 'Training loss')
plt.plot(epochs, val_loss, 'b', label = 'Validation loss')
plt.title('Training and validation loss')
plt.xlabel('Epochs')
plt.ylabel('loss')
plt.legend()
plt.show()

ax2 = plt.subplot(2, 1, 2)
acc = history_dict['acc']
val_acc = history_dict['val_acc']
plt.plot(epochs, acc, 'r', label = 'Training acc')
plt.plot(epochs, val_acc, 'b', label = 'Validation acc')
plt.title('Training and validation accuracy')
plt.xlabel('Epochs')
plt.ylabel('Accuracy')
plt.legend()
plt.show()

plt.tight_layout()
plt.show()

 

 

텐서플로우를 활용하기 위해 데이터의 구조를 일부 변형한 후 numpy형태로 데이터 구조를 변환하였다. 변환 후 임베딩을 할 수 있는 모델을 쌓은 후 분석을 진행해보았고, 약 0.81정도의 정확도가 나오는 것을 확인할 수 있었다. 임베딩을 활용한 분석은 토큰을 n개의 층으로 나누어 값의 의미를 저장하여 토큰 간의 유사도 분석은 가능하나 문맥상의 의미를 반영하는 것은 어렵기에 문맥까지도 고려할 수 있는 대표적이면서 좋은 성능을 가진 LSTM모델을 활용해보기로 하였다.

 

from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import LSTM, Dense, Embedding
model = Sequential()
model.add(Embedding(input_dim = len(word_index), output_dim = 64, input_length = max_length))
model.add(LSTM(32, return_sequences = True))
model.add(LSTM(16))
model.add(Dense(2, activation = 'sigmoid'))

model.compile(optimizer = 'adam', loss = 'binary_crossentropy', metrics = ['acc'])
model.summary()
history = model.fit(X_train, y_train, batch_size = 64, epochs = 10, validation_data = (X_test, y_test))

LSTM을 활용한 분석을 진행해본 결과, 정확도에 큰 변화는 없어 아쉬움이 남기는 했으나, 리뷰데이터에 대한 분석을 개인적으로 진행해보며 많은 것을 배울 수 있었다. DACON에서 제공해준 코드를 보고 개인적으로 학습하며 많은 것을 배울 수 있었던 것 같다. 데이터 분석 및 AI를 활용하고 싶지만 지식이 부족하여 고민하고 있는 사람이라면 DACON을 활용해보는 것도 좋을 것 같다.

 

P.S 더 나은 개발자가 되기위해 공부중입니다. 잘못된 부분을 댓글로 남겨주시면 학습하는데 큰 도움이 될 거 같습니다.

세부코드가 궁금하신 분들은 아래 GitHub를 참고해주시기 바랍니다.

 

※ Github

 

GitHub - Jeong-Beom/TIL: 교육받은 내용을 기록하기 위한 레파지토리입니다.

교육받은 내용을 기록하기 위한 레파지토리입니다. Contribute to Jeong-Beom/TIL development by creating an account on GitHub.

github.com

728x90
반응형

댓글