본문 바로가기
정리/텐서플로와 머신러닝으로 시작하는 자연어처리

Chap03. 자연어처리 개요_데이터 이해하기

by 스꼬맹이브로 2021. 7. 13.
728x90
반응형
SMALL

자연어 처리를 해결하면서 우리는 모델을 만들고 훈련 후에 성능을 평가하고, 생각보다 성능이 안 나온다면 모델에 문제가 있다고 판단하고 다른 모델을 사용한다.

이처럼 모델에 문제가 있는 경우도 있지만 우선적으로 해당 문제를 잘 해결하기 위해서는 데이터 이해가 선행되어야 하며 이러한 과정 속에서 생각하지 못한 데이터의 여러 패턴이나 잠재적인 문제점 등을 발견할 수 있다.

 

위와 같은 과정을 탐색적 데이터 분석(EDA : Exploratory Data Analysis)이라 하며, 데이터에 대한 선입견 없이 데이터가 보여주는 수치만으로 분석을 진행해야 한다.

그리고 이러한 데이터 분석 과정은 모델링 과정과 서로 상호 작용하면서 결과적으로 성능에 영향을 주기 때문에 매우 중요한 작업이다. 

탐색적 데이터 분석의 전체 흐름도

간단한 실습을 통해 데이터 불러오기부터 데이터 분석에 대해 알아보자.

실습에 활용할 데이터는 영화 리뷰 데이터로 리뷰와 리뷰에 대한 감정(긍정/부정) 값을 갖고 있다.

 

1. 데이터 불러오기

import os

import pandas as pd
import tensorflow as tf
from tensorflow.keras import utils

data_set = tf.keras.utils.get_file(
      fname="imdb.tar.gz", 
      origin="http://ai.stanford.edu/~amaas/data/sentiment/aclImdb_v1.tar.gz", 
      extract=True)

실습에 필요한 라이브러리를 불러온 후, 텐서플로 케라스 모듈의 get_file 함수를 통해 IMDB 데이터를 가져온다.

get_file함수의 인자 설명은 다음과 같다.

  • fname : 다운로드한 파일의 이름 재지정
  • origin : 다운로드할 데이터의 URL
  • extract : 다운로드한 압축 파일의 압축 해제 여부

압축을 해제하면 모든 데이터가 txt파일 형태로 되어있음을 확인할 수 있다.

때문에 데이터프레임을 만들기 위해서 변환 작업을 해주기 위해 함수를 생성한다.

def directory_data(directory):
    data = {}
    data["review"] = []
    for file_path in os.listdir(directory):
        with open(os.path.join(directory, file_path), "r", encoding='utf-8') as file:
            data["review"].append(file.read())
            
    return pd.DataFrame.from_dict(data)

위 함수는 파일에서 리뷰 텍스트를 불러오는 함수이다.

인자로는 데이터를 가져올 디렉터리를 받는데, 그 디렉터리 안에 있는 파일들을 하나씩 가져와 내용을 읽어서 data["review"]배열에 하나씩 넣는다.

후에 딕셔너리를 판다스 데이터프레임으로 만들어서 반환한다.

def data(directory):
    pos_df = directory_data(os.path.join(directory, "pos"))
    neg_df = directory_data(os.path.join(directory, "neg"))
    pos_df["sentiment"] = 1
    neg_df["sentiment"] = 0
    
    return pd.concat([pos_df, neg_df])

위 함수는 각 리뷰에 해당하는 레이블 값을 가져오는 함수이다.

먼저 폴더 이름을 지정하면 앞에서 설명한 directory_data함수를 호출하는데 이때 pos(긍정) 폴더에 접근할지 neg(부정)폴더에 접근할지를 통해 각각의 데이터프레임을 반환받는다.

이 값들은 pos_df와 neg_df에 담긴다.

다음으로 긍정은 1, 부정은 0으로 만들고 데이터프레임을 통해 연동한다.

 

위의 두 함수를 호출해서 데이터프레임을 반환받는 코드는 다음과 같다.

train_df = data(os.path.join(os.path.dirname(data_set), "aclImdb", "train"))
test_df = data(os.path.join(os.path.dirname(data_set), "aclImdb", "test"))

train_df.head()

결과 :

 

데이터프레임에 review와 sentiment가 잘 들어가 있는 것을 확인했으므로 리뷰 문장 리스트를 만들고 데이터 분석을 진행해보자.

reviews = list(train_df['review'])

 

2. 데이터 분석

reviews의 각 문장을 단어로 토크나이징하고(1) 문장마다 토크나이징된 단어의 수를 저장하고(2) 그 단어들을 붙여 알파벳의 전체 개수를 저장하는(3) 부분을 만들어 보자.

# 문자열 문장 리스트를 토크나이즈
tokenized_reviews = [r.split() for r in reviews]

# 토크나이즈 된 리스트에 대한 각 길이를 저장
review_len_by_token = [len(t) for t in tokenized_reviews]

# 토크나이즈 된 것을 붙여서 음절의 길이를 저장
review_len_by_eumjeol = [len(s.replace(' ', '')) for s in reviews]

(1)에 대한 결과로 하나만 보면 다음과 같다.

tokenized_reviews = [['Bromwell', 'High', 'is', 'a', 'cartoon', 'comedy.', 'It', 'ran', 'at', 'the', 'same', 'time', 'as', 'some', 'other', 'programs', 'about', 'school', 'life,', 'such', 'as', '"Teachers".', 'My', '35', 'years', 'in', 'the', 'teaching', 'profession', 'lead', 'me', 'to', 'believe', 'that', 'Bromwell', "High's", 'satire', 'is', 'much', 'closer', 'to', 'reality', 'than', 'is', '"Teachers".', 'The', 'scramble', 'to', 'survive', 'financially,', 'the', 'insightful', 'students', 'who', 'can', 'see', 'right', 'through', 'their', 'pathetic', "teachers'", 'pomp,', 'the', 'pettiness', 'of', 'the', 'whole', 'situation,', 'all', 'remind', 'me', 'of', 'the', 'schools', 'I', 'knew', 'and', 'their', 'students.', 'When', 'I', 'saw', 'the', 'episode', 'in', 'which', 'a', 'student', 'repeatedly', 'tried', 'to', 'burn', 'down', 'the', 'school,', 'I', 'immediately', 'recalled', '.........', 'at', '..........', 'High.', 'A', 'classic', 'line:', 'INSPECTOR:', "I'm", 'here', 'to', 'sack', 'one', 'of', 'your', 'teachers.', 'STUDENT:', 'Welcome', 'to', 'Bromwell', 'High.', 'I', 'expect', 'that', 'many', 'adults', 'of', 'my', 'age', 'think', 'that', 'Bromwell', 'High', 'is', 'far', 'fetched.', 'What', 'a', 'pity', 'that', 'it', "isn't!"]]

하나의 문장을 띄어쓰기 단위로 모두 자른 것을 볼 수 있다.

 

(2)에 대한 결과로 5개만 보면 다음과 같다.

review_len_by_token = [140, 428, 147, 124, 120]

배열 하나에 들어있는 갯수를 저장한 것을 볼 수 있다.

 

(3)에 대한 결과로 5개만 보면 다음과 같다.

review_len_by_eumjeol = [667, 1939, 695, 540, 528]

배열 하나에 들어있는 전체 알파벳의 개수가 저장되어있음을 예상할 수 있다.

 

위와 같이 만드는 이유는 문장에 포함된 단어와 알파벳의 개수에 대한 데이터 분석을 수월하게 하기 위해서다.

 

이제 위의 데이터로 문장을 구성하는 단어의 개수와 알파벳 개수를 히스토그램으로 알아보자.

import matplotlib.pyplot as plt

# 그래프에 대한 이미지 사이즈 선언
# figsize: (가로, 세로) 형태의 튜플로 입력
plt.figure(figsize=(12, 5))
# 히스토그램 선언
# bins: 히스토그램 값들에 대한 버켓 범위
# range: x축 값의 범위
# alpha: 그래프 색상 투명도
# color: 그래프 색상
# label: 그래프에 대한 라벨
plt.hist(review_len_by_token, bins=50, alpha=0.5, color= 'r', label='word')
plt.hist(review_len_by_eumjeol, bins=50, alpha=0.5, color='b', label='alphabet')
plt.yscale('log', nonposy='clip')
# 그래프 제목
plt.title('Review Length Histogram')
# 그래프 x 축 라벨
plt.xlabel('Review Length')
# 그래프 y 축 라벨
plt.ylabel('Number of Reviews')

결과 :

위의 데이터를 통해 보고자 하는 것은 문장에 대한 길이 분포이다.

빨간색 히스토그램은 단어 개수에 대한 것이며, 파란색은 알파벳 개수의 히스토그램이다.

 

다음으로 데이터 분포를 통계치로 수치화해보자.

import numpy as np

print('문장 최대길이: {}'.format(np.max(review_len_by_token)))
print('문장 최소길이: {}'.format(np.min(review_len_by_token)))
print('문장 평균길이: {:.2f}'.format(np.mean(review_len_by_token)))
print('문장 길이 표준편차: {:.2f}'.format(np.std(review_len_by_token)))
print('문장 중간길이: {}'.format(np.median(review_len_by_token)))
# 사분위의 대한 경우는 0~100 스케일로 되어있음
print('제 1 사분위 길이: {}'.format(np.percentile(review_len_by_token, 25)))
print('제 3 사분위 길이: {}'.format(np.percentile(review_len_by_token, 75)))

결과 : 

문장 최대길이: 2470

문장 최소길이: 10

문장 평균길이: 233.79

문장 길이 표준편차: 173.73

문장 중간길이: 174.0

제 1 사분위 길이: 127.0

제 3 사분위 길이: 284.0

 

단어 길이에 대한 통곗값은 전체적으로 7가지(전체, 최소, 평균, 표준편차, 중간, 1사분위, 3사분위) 로 볼 수 있다.

이러한 통곗값을 통해 수치적으로 데이터 문장 길이의 분포가 확인이 가능하다.

 

다음으로 박스 플롯을 통해 문장 내 단어 수와 알파벳 개수를 각각 시각화를 해보자.

박스 플롯은 직관적인 시각화를 제공한다.

박스 플롯이 나타내는 바는 다음과 같다.

plt.figure(figsize=(12, 5))
# 박스플롯 생성
# 첫번째 파라메터: 여러 분포에 대한 데이터 리스트를 입력
# labels: 입력한 데이터에 대한 라벨
# showmeans: 평균값을 마크함

plt.boxplot([review_len_by_token],
             labels=['token'],
             showmeans=True)

결과 :

plt.figure(figsize=(12, 5))
plt.boxplot([review_len_by_eumjeol],
             labels=['Eumjeol'], 
             showmeans=True)

결과 : 

 

다음으로 워드 클라우드로 데이터를 시각화해보자.

from wordcloud import WordCloud, STOPWORDS
import matplotlib.pyplot as plt
%matplotlib inline

wordcloud = WordCloud(stopwords = STOPWORDS, background_color = 'black', width = 800, height = 600).generate(' '.join(train_df['review']))

plt.figure(figsize = (15, 10))
plt.imshow(wordcloud)
plt.axis("off")
plt.show()

결과 :

워드 클라우드는 데이터에 포함된 단어의 등장 횟수에 따라 단어의 크기가 크게 나온다.

결과에서는 br이 엄청 크게 보여지는데, 이는 데이터에 <br>과 같은 HTML 태그가 포함되어 있기 때문이다.

이러한 부분은 학습에 도움이 되지 않기 때문에 전처리 단계에서 제거해야 한다.

 

마지막으로 긍정과 부정의 분포를 확인해보자.

import seaborn as sns
import matplotlib.pyplot as plt

sentiment = train_df['sentiment'].value_counts()
fig, axe = plt.subplots(ncols=1)
fig.set_size_inches(6, 3)
sns.countplot(train_df['sentiment'])

결과 :

긍정과 부정의 개수가 12000개로 같으며 이는 데이터의 균형이 아주 좋다는 것을 나타낸다.

데이터의 균형이 맞을 때가 가장 좋지만 모든 데이터가 좋은 균형을 유지하지는 않는다.

균형이 좋지 않은 데이터는 따로 처리를 해주어야하는데, 이는 다음 포스팅에서 자세히 다룰 것이다!

728x90
반응형
LIST