티스토리 뷰

본 포스팅은 『텐서플로2와 머신러닝으로 시작하는 자연어처리』를 참고하여 만들어졌습니다.
https://wikibook.co.kr/nlp-tf2/

 

영어로 이루어진 영화 리뷰 데이터를 분류하겠다. 우리가 접할 수 있는 영화 리뷰 분류 문제들이 많은데, 그 중에서 캐글 대회의 데이터를 활용해 실습을 해보자.

 

 

Bag of Words Meets Bags of Popcorn

Use Google's Word2Vec for movie reviews

www.kaggle.com

1. 문제소개

영어 텍스트 분류 문제중 캐글의 대회인 워드 팝콘 문제를 활용할 것이다. 이 문제를 해결하면서 텍스트 분류 기술을 알아보겠다. 먼저 워드 팝콘이 어떤 문제인지 알아보자.

 

1) 워드 팝콘

워드 팝콘은 인터넷 데이터베이스(IMDB)에서 나온 영화 평점 데이터를 활용한 캐글 문제다. 영화 평점 데이터이므로 각 데이터는 영화 리뷰 텍스트와 평점에 따른 감정 값(긍정 혹은 부정)으로 구성돼 있다. 이 데이터는 보통 감정 분석(sentiment analysis) 문제에서 자주 활영된다. 그럼 이 데이터를 어떻게 분류할지에 대한 목표를 알아보자.

 

2) 목표 

여기서는 크게 3가지 과정을 거칠 것이다. 첫 번째는 데이터를 불러오는 것과 정제되지 않은 데이터를 활용하기 쉽게 전처리 하는 과정이다. 그 다음은 데이터를 분석하는 과정이다. 데이터가 어떻게 구성돼 있는지 확인하고 그에 따라 어떻게 문제를 풀어가야 할지 알아 보겠다. 마지막으로 실제로 문제를 해결하기 위한 알고리즘을 모델링하는 과정을 밟을 것이다. 이때 한가지 방법으로만 하는 것이 아니라 여러 방법을 직접 구현해보고 결과를 비교하면서 어떤 방법이 좋을지에 대해서도 알아본다.

 

2. 데이터 분석 및 전처리

이번에 사용할 영화 리뷰 데이터는 텍스트 분류에서 가장 기본적으로 사용되는 데이터로 여기서는 이 데이터를 분류할 수 있는 모델을 학습시킬 것이다. 모델을 학습시키기 전에 데티어를 전처리하는 과정을 거쳐야 한다. 전처리는 데이터를 모델에 적용하기에 적합하도록 데이터를 정제하는 과정이다. 그전에 데이터를 불러오고 분석하는 과정을 선행할 것이다. 

 

우선, 데이터 분석에 필요한 라이브러리를 불러오고, 데이터 경로를 지정해주자.

import numpy as np
import pandas as pd
import os
import matplotlib.pyplot as plt
import seaborn as sns

%matplotlib inline 

data_path = '데이터가 들어있는 경로'
file_list = ['labeledTrainData.tsv','unlabeledTrainData.tsv','testData.tsv']

 

판다스의 read_csv 함수를 이용하여 파일을 읽어와 data frame을 만들자. 이때 첫번째 행이 항목명임으로 header 인자를 0으로 설정해주고, comma( , )로 데이터가 나눠져있는 것이 아니라 tab(\t)을 기준을호 구분돼 있으므로 delimeter에 teb을 설정해준다. 그리고 쌍띠움표를 무시하기 위해 quoting 인자에 3을 설정한다.

 

train_data = pd.read_csv(data_path+'\/'+file_list[0], header = 0, delimiter='\t', quoting=3)
train_data.head()  #데이터 윗 부분을 출력

 

데이터는 'id', 'sentiment', 'review'로 구분돼 있으며, 각 리뷰에 대한 감정이 긍정(1) 혹은 부정(0)인지 나와 있다. 이제 본격적으로 해당 데이터를 분석해보자.

 

1) 데이터의 크기

# 데이터의 파일 크기

print("파일 크기 : ")
for file in os.listdir(data_path):
    if 'tsv' in file :
        print(file.ljust(30), str(round(os.path.getsize(data_path+'/'+file)/1000000,2))+'MB')

# 학습 데이터 갯수

print("전체 학습 데이터의 갯수 : {}".format(len(train_data)))

 

이제 각 데이터의 문자의 길이를 알아보자. 'review'열에 각 데이터의 리뷰가 들어가 있다. 우선 각 리뷰의 길이를 새로운 변수로 정의하고 head 함수를 사용해 몇 개의 데이터만 확인해보자.

# 데이터의 문자열 길이

train_length = train_data['review'].apply(len)  #데이터 길이를 변수로 새로운 dataframe 생성
train_length.head()

해당 변수에는 각 리뷰의 길이가 담겨 있다. 이 변수를 사용해 히스토그램을 그려보자. matplotlib을 사용해 히스토그램을 그린다.

우선 figure 함수를 사용해서 그릴 그래프의 크기를 설정한다. 그리고 hist 함수를 사용해 히스토그램을 그리는데 필요한 인자를 설정한다. 또한 이때 y축의 scale을 log의 크기로 설정하는데, 이는 10의 거듭제곱 단위의 값을 한 단위값으로 갖게 된다는 의미이다.

# 그래프에 대한 이미지 크기 선언
# figsize : (가로, 세로) 형태의 튜플로 입력
plt.figure(figsize=(12,5))

# 히스토그램 선언
# bins : 히스토그램 값에 대한 버킷 범위
# range : x축 값의 범위
# alpah : 그래프 색상 투명도
# color : 그래프 색상
# label : 그래프에 대한 라벨
plt.hist(train_length, bins=200, alpha=0.5, color='r', label='word')
plt.yscale('log', nonposy='clip')

#그래프 제목
plt.title("Log-Histogram of length of review")
#그래프 x축 라벨
plt.xlabel('Length of review')
#그래프 y축 라벨
plt.ylabel('Number of review')

분포를 보면 각 리뷰의 문자 길이가 대부분 6000이하이고 대부분 2000이하에 분포돼 있음을 알 수 있다. 그리고 일부 데이터의 경우 이상치로 10000 이상의 값을 가지고 있다. 길이에 대한 몇 가지 통계값도 확인해보자.

print('리뷰 길이 최댓값 : {}'.format(np.max(train_length)))
print('리뷰 길이 최솟값 : {}'.format(np.min(train_length)))
print('리뷰 길이 평균값 : {:.2f}'.format(np.mean(train_length)))
print('리뷰 길이 표준편차 : {:.2f}'.format(np.std(train_length)))
print('리뷰 길이 중간값 : {:.2f}'.format(np.median(train_length)))

#사분위는 0~100 스케일로 되어 있음
print('리뷰 길이 제1사분위: {}'.format(np.percentile(train_length,25)))
print('리뷰 길이 제3사분위: {}'.format(np.percentile(train_length,75)))

 

리뷰의 길이가 히스토그램에서 확인했던 것과 비슷하게 평균이 1300 정도이고, 쵀댓값이 13000이라는 것을 알 수 있다. 이제 이 값을 가지고 박스 플롯을 그려보자.

plt.figure(figsize=(12,5))

# 박스 플롯 생성
# 첫 번째 인자 : 여러 분포에 대한 데이터 리스트를 입력
# label : 입력한 데이터에 대한 라벨
# showmeans : 평균값을 마크함

plt.boxplot(train_length, labels=['counts'], showmeans=True)

박스 플롯 그래프를 통해 데이터를 살펴보면 우선 데이터의 길이가 대부분 2000이하로 평균이 1500 이하인데, 길이가 4000 이상인 이상치 데이터도 많이 분포돼 있는 것을 확인할 수 있다. 

워드 클라우드를 이용해서 리뷰에 많이 사용된 단어로 어떤 것이 있는지 알아보자.

 

from wordcloud import WordCloud

cloud = WordCloud(width=800, height=600).generate(" ".join(train_data['review']))

plt.figure(figsize=(12,5))
plt.imshow(cloud)
plt.axis('off')

워드클라우드를 통해 그린 그림을 살펴보면 데이터에서 가장 많이 사용된 단어는 br이라는 것을 확인할 수 있다. br은 우리가 분석하고자 하는 데이터의 의미를 나타내는 것이 아니라 HTML의 테그가 정제되지 않은 것이다. 이후 전처리 작업을 통해서 제거해줘야 한다.

 

이제 각 라벨의 분포를 확인해 본다. 해당 데이터의 경우 긍정과 부정이라는 두 가지 라벨만 가지고 있다. 분포의 경우 또 다른 시각화 도구인 씨본을 사용해 시각화하겠다. 결과를 보면 긍정과 부정 각각 12500개의 데이터를 동등하게 가지고 있는 것을 볼 수 있다. 굉장히 균형이 잘 잡힌 데이터이고 실제 수집하는 데이터는 이렇게 균형잡혀있기 어렵다.

fig, axe = plt.subplots(nrows=1, ncols=1)
fig.set_size_inches(6, 3)

sns.countplot(train_data['sentiment'])

이제 각 리뷰를 단어 기준으로 나눠서 각 리뷰당 단어의 개수를 확인해본다. 단어는 띄어쓰기 기준으로 하나의 단어라 생각하고 갯수를 계산한다.(한글의 경우 KoNLPy를 이용해서 별도의 토크나이징이 필요하지만 영어 데이터의 단어의 단위와 띄어쓰기의 단위가 일치해서 다음과 같이 할 수 있는 것이다.)

 

#띄어쓰기 단위로 토크나이즈한 이후 토큰의 갯수로 데이터프레임을 만든다.
train_word_counts = train_data['review'].apply(lambda x:len(x.split(' ')))


#히스토그램
plt.figure(figsize=(15,10))
plt.hist(train_word_counts, bins=50, color='b', alpha=0.5, label='train')
plt.yscale('log',nonposy='clip')
plt.legend()  # 범례표시
plt.xlabel('Number of words',fontsize=15)
plt.ylabel('Number of reviews', fontsize=15)

대부분의 단어가 1000개 미만의 단어를 가지고 있고, 대부분 200개 정도의 단어를 가지고 있음을 확인할 수 있다. 앞서 했던 것과 같이 마지막으로 몇 가지 통계값을 확인해보자.

 

print('리뷰 단어 개수 최댓값: {}'.format(np.max(train_word_counts)))
print('리뷰 단어 개수 최솟값: {}'.format(np.min(train_word_counts)))
print('리뷰 단어 개수 평균값: {:.2f}'.format(np.mean(train_word_counts)))
print('리뷰 단어 개수 표준편차: {:.2f}'.format(np.std(train_word_counts)))
print('리뷰 단어 개수 중간값: {}'.format(np.median(train_word_counts)))

#  사분위의 경우 0~100스케일로 되어있음
print('리뷰 단어 개수 제1사분위: {}'.format(np.percentile(train_word_counts, 25)))
print('리뷰 단어 개수 제3사분위: {}'.format(np.percentile(train_word_counts, 75)))

단어 개수의 경우 평군 233개이고, 최댓값의 경우 2470개의 단어를 가지고 있다. 그리고 3사분위 값으로 284개로 리뷰의 75%가 300개 이하의 단어를 가지고 있음을 확인 할 수 있다.

 

마지막으로 각 리뷰에 대해 구두점과 대소문자 비율 값을 확인해보자.

qmarks = np.mean(train_data['review'].apply(lambda x:'?' in x))
fullstop = np.mean(train_data['review'].apply(lambda x:'.' in x))
capital_first = np.mean(train_data['review'].apply(lambda x: x[0].isupper())) #첫 번째 대문자
capitals = np.mean(train_data['review'].apply(lambda x:max([y.isupper() for y in x]))) # 대문자가 포함
numbers = np.mean(train_data['review'].apply(lambda x:max([y.isdigit() for y in x]))) #숫자가 포함

print('물음표가 있는 질문:{:.2f}%'.format(qmarks * 100))
print('마침표가 있는 질문:{:.2f}%'.format(fullstop * 100))
print('첫 글자가 대문자인 질문:{:.2f}%'.format(capital_first * 100))
print('대문자가 있는 질문:{:.2f}%'.format(capitals * 100))
print('숫자가 있는 질문:{:.2f}%'.format(numbers * 100))

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
글 보관함