차밍이

[R, kaggle실습] 영화 평점 감성 분석 - 텍스트 전처리에서 베이지안 필터기 적용까지 본문

R/데이터 사이언스

[R, kaggle실습] 영화 평점 감성 분석 - 텍스트 전처리에서 베이지안 필터기 적용까지

2020. 2. 14. 20:45
반응형

R을 사용한 감성분석

  • 케글의 영화 평점 분석 데이터를 사용하여 감성 분석을 진행하겠습니다. 텍스트 전처리, 데이터 전처리, 자연어 처리(NLP), 텍스트 마이닝을 진행한 후 정제된 데이터를 바탕으로 베이지안 필터기와 의사결정나무 모델을 사용해서 데이터를 분류해보겠습니다. 그 결과 어느 정도의 정확도를 보여줄 것인지 확인해보겠습니다.

  • 작업환경은 구글의 코랩(Colab)에서 진행하였습니다. 데이터의 크기가 커서 좀 더 나은 환경을 위해서 코랩에서 진행했습니다. 집 컴퓨터의 메모리와 cpu가 좋다면 로컬에서 작업하시는 것이 좋을 수도 있습니다.

(Colab에서 R 사용하기)

라이브러리 및 환경설정

# 파이썬에서 R 사용하는 환경설정
import rpy2
%load_ext rpy2.ipython

# 구글 드라이브와 연동하기
from google.colab import drive
drive.mount('/content/gdrive')

# 폴더 이동
cd /content/gdrive/My Drive/R_class

# 필요한 패키지 설치
%%R
install.packages('readr')
install.packages("tidytext")
install.packages("stringr")
install.packages("tm")
install.packages("tidyr")
install.packages("SnowballC")
install.packages("textdata")
install.packages('e1071')
install.packages('gmodels')
install.packages('stats')
install.packages('arules')
install.packages('C50')
# 라이브러리 불러오기
library(readr)
library(gmodels)
library(e1071)
library(stringr)
library(tm)
library(tidytext)
library(tidyr)
library(textdata)
library(SnowballC)
library(tidytext)
library(arules)
library(stats)
library(C50)

# 데이터 불러오기
imdb <- read_csv("IMDB Dataset.csv")

# 데이터가 너무 많아 10000개만 랜덤 추출해서 진행
set.seed(123)
num <- 10000
db_index <- sample(nrow(imdb), num)
imdb <- imdb[db_index,]
# label을 범주화 Negative와 Positive로 구분
imdb$sentiment <- factor(imdb$sentiment)

원본 데이터가 50,000개 정도 되는데 전부 돌리게 되면 속도도 매우 느린 데다가 중간에 메모리 부족으로 뻑나기도 합니다. 20,000개 까지 진행해봤는데 나중에 필터기 돌릴 때, 진짜 느려요. 연습 삼아하는 것이므로 5,000~10,000개 정도가 좋은 것 같습니다.

데이터 전처리

데이터 전처리는 2번에 나눠서 진행했습니다.

  1. 텍스트 자체에서 기본적인 전처리를 진행
  2. 벡터화와 Corpus(말뭉치) 단위로 나눈 후 전처리 진행

텍스트 자체 데이터 전처리

%%R
imdb$review <- sapply(imdb$review, FUN = function(x){
  # 축약어 전처리
  x <- str_replace_all(x,"'ll",' will')
  x <- str_replace_all(x,"'m",' am')
  x <- str_replace_all(x,"n't",' not')
  x <- str_replace_all(x,"'ve",' have')
  x <- str_replace_all(x,"'s",' ')
  # Be 동사 어원처리
  x <- str_replace_all(x,"( am)|( are)|( is)|( were)|( been)|( was)",' be')
  # br태그 제거
  x <- str_replace_all(x, "<[[:graph:]]{0,} /{0,}>", " ")
  x <- tolower(x)
  # 이상글자 제거
  x <- str_replace_all(x, "\\u0096", " ")
  x <- str_replace_all(x, "\\u0085", " ")
})
  • 축약어 제거

I've been there. I'll be there later soon. I'm so sorry. Please Don't be upset. It's not my fault. 이러한 문장이 있다고 할 때, 그냥 특수문자를 제거하면 I've -> Ive 또는 I ve형태가 되어 의미 없는 garbage들을 만들어냅니다. 따라서 이런 축약된 부분들을 모두 풀어서 쓸 수 있도록 전처리를 했습니다. 이후 Be동사의 어원으로 만들어 주었습니다. 사실 Be동사 자체에는 큰 의미가 없으므로 제거해도 무방할 것이라? 조심스럽게 예측해봅니다. 아마 어차피 불용어 처리할 때, stopwords에서 걸러집니다.

  • <br />태그 제거

데이터가 파싱 해온 데이터인 것으로 생각됩니다. html 태그들이 같이 달려있는 것이 있어서 제거해주었습니다. 다른 태그들이 들어오는 경우도 있으니 일반화해서 제거할 수도 있겠습니다.

  • 이상 글자 제거

인코딩 문제 같은데 계속 해결하는데 결국 실패했습니다. 스페인어 같은 데서 나오는 문자 위에 점찍어진 단어들 혹시 아시나요? Résumé *요론 단어들, *café 이런 단어들이 읽어올 때 펣, 쉪, 팩 이런 식으로 깨져서 나오는 경우가 있습니다. 저는 결국 해결하지 못하고 해당 단어들이 출력되는 방식을 찾아서 직접 제거했습니다.

말뭉치 단위 전처리

%%R
# 벡터화 후 코퍼스 생성
corpus <- VCorpus(VectorSource(imdb.c$review))

# 2차 전처리에 필요한 함수
replacePunctuation <- function(x){
  x <- gsub(pattern = "[[:punct:]]+", " ", x)
}
replaceNumber <- function(x){
  x <- gsub(pattern = "[[:digit:]]+", " ", x)
}
# tm_map 함수로 각 말뭉치들에 함수 적용
# 숫자 제거
corpus <- tm_map(corpus,content_transformer(replaceNumber))
# 특수문자 제거
corpus <- tm_map(corpus,content_transformer(replacePunctuation))
# 불용어 제거
corpus <- tm_map(corpus,removeWords, stopwords('en'))
# 불필요 빈칸 제거
corpus <- tm_map(corpus, stripWhitespace)
# 어근 동일화
corpus <- tm_map(corpus, stemDocument)

# DTM 생성
dtm <- DocumentTermMatrix(corpus)
  • 특수문자 제거, 숫자 제거

tm_map에서 removePunctuation 등의 함수를 사용할 수 있지만, 그런 경우에 Umm....No good이런 단어가 UmmNo good이런 방식으로 붙는 경우가 발생합니다. 그래서 공백으로 대체했습니다.

  • DTM 생성

전처리를 모두 마친 후 DocumentTermMatrix를 생성합니다. DTM을 만들면 Column에 각 단어들이 모두 들어가고 Matrix의 데이터 값으로는 해당 열(해당 문장)에 해당 열의 단어가 몇 번 쓰였는지가 들어갑니다. 이로써 준비는 끝났습니다.

데이터셋 분류

%%R
# sample 비율 70%
smp_ratio <- 0.7
smp_size <- smp_ratio * num

# train/test data set 분리
set.seed(123)
train_index <- sample(num, smp_size)
train <- dtm[train_index,]
test <- dtm[-train_index,]

# 빈도수 10회 이상인 단어들로 제한
freq_words <- findFreqTerms(train,10)
train <- train[,freq_words]
test <- test[,freq_words]

# label 설정
train_labels <- imdb[train_index,]$sentiment
test_labels <- imdb[-train_index,]$sentiment

# 분류기 적용을 위한 카테고리화
convert_counts<- function(x){
  x<-ifelse(x>0, 'Y', "N")}
train <- apply(train,MARGIN = 2,FUN = convert_counts)
test <- apply(test,MARGIN = 2,FUN = convert_counts)
  • Train_data_split

sampling을 통해서 70%의 데이터를 뽑아 train데이터로 사용하고 나머지 30%를 결과를 확인하는 test데이터로 사용하였습니다.

  • 빈도수 10회 제한

각 단어들의 빈도수가 최소 10회 이상 사용되는 단어들로 제한하였습니다. 그렇지 않으면 생긴 단어들의 수만큼의 열의 수가 증가합니다. 그만큼 차원의 수가 증가하는 것입니다. 단어 자체가 10번도 나오지 않았다는 것은 10000개의 행에서 0.001% 의 확률도 안 되는 것이므로 모델에 영향을 미치지 않으면서도 전체적인 성능을 높일 것이라 생각했습니다.

  • 카테고리화

나이브 베이즈 필터를 적용하기 전 데이터를 펙터화, 카테고리화 해야 하니다. train과 test에 있는 데이터는 dtm으로부터 발생된 데이터로 value들은 모두 int 형태의 숫자입니다. 해당 단어가 몇 번 나왔는지 말해주는 값입니다. 분류기에서는 해당 값이 몇 인 지보 다는 해당 단어가 나왔을 때 그 문장이 positive였는지 혹은 negative였는지가 중요하죠. 이것이 베이즈 이론의 핵심이기 때문입니다. 따라서, 펙터화를 위한 함수를 만들고 Y or N로 구분하였습니다.

나이브 베이즈 분류기 적용

# 나이브 베이즈 분류기 모델 생성
NB.classifier <- naiveBayes(train, train_labels, laplace = 1)
# test set에 적용해서 예측
pred <- predict(NB.classifier, test)
# 예측 결과 확인
sum(pred == test_labels)*100/length(test_labels)
# CrossTable 확인
CrossTable(pred, test_labels, prop.t=FALSE, prop.r = FALSE, dnn=c('predicted','actual'))
  • 모델 생성

train데이터와 train_labels정답을 가지고 분류기를 생성합니다. laplace = 1로 설정합니다. 만약 특정 단어가 만약 한 번도 등장하지 않았을 경우를 생각해보겠습니다. 모델에서 해당 단어가 나올 확률을 0으로 확정 지어버릴 경우 test 데이터에 적용할 때, 그 단어가 나오면 무조건 0으로 판단하게 될 것입니다. 각 단어들 간의 사건이 독립이라고 생각하면 각 확률들이 곱해질 것입니다. 그러면 0이 있으면 무조건 결과값이 0이 나오겠죠. 따라서, 기본적으로 모든 단어가 1건은 있었다는 전제를 두고 들어가는 것입니다. 이를 두고 라플라스 추정량이라고 합니다.

  • 모델 적용 및 결과 확인

나이브 베이지안 분류기가 만들어지면 해당 모델을 test데이터에 적용한 후 결과를 얻습니다. 이후 test_labels와 결과값을 비교하여 Accuracy를 확인합니다.

 

 

정확도는 83.6%로 측정되었습니다. negative를 negative로 판단하는 정답률은 85.7%, positive를 positive로 판단하는 정답률은 81.5%의 정답률을 보였습니다. 부정적인 결과를 더 잘 파악하는 것을 확인할 수 있었습니다. 더욱 성능을 높이기 위해서는 전처리 과정이 더 많이 필요할 것으로 생각됩니다. 형태소 분석기가 python에서 사용하는 것 만큼의 성능이 나오지 않는 것같습니다. dtm 내부의 columns를 살펴보면 어떤 단어들로 구성되어 있는지 확인할 수 있었는데, 인터넷 상의 언어이다보니 오타가 많은데 이런 경우를 잘잡지 못하는 것 같습니다.

 

지금까지 베이즈 필터를 사용해서 영화 댓글의 긍정과 부정을 판별하는 필터기를 만들었습니다. 다음으로는 negative와 positive를 넘어서 감성분석 사전을 활용해서 더 자세한 텍스트 감성분석을 진행해보겠습니다.

 

 

베이지안 필터 적용 실습 사례

2020/02/11 - [R 프로그래밍] - [R] 나이브 베이즈를 활용한 스팸메일 분류와 텍스트 마이닝

2020/02/11 - [R 프로그래밍] - [R] 나이브 베이즈 분류(Naive Bayes Classifier) 활용 데이터 분석 및 실습 - 독버섯 분류하기

 

반응형

관련된 글 보기

Comments