1 감성분석 및 의견분석

현재의 트렌드를 분석하는 것은 쉬운일이 아닙니다. 기존의 트렌드 분석은 주로 대형 포털에서 제공하는 서비스에 의존하여 분석하는 것이 대부분이었습니다. 그런데, 세상이 바뀌었습니다. 1차적으로 뉴스를 접하는 곳은 비전통적 채널에서 확인합니다.. 그리고, 댓글을 통해 여론의 추이를 바라게 됩니다.. 아래 페이스북의 한 게시물을 보도록 합니다.

facebook_comment

위 게시물은 비전통적 언론사에서 제작하여 비전통적 채널이 페이스북에서 배포한 게시글이다. 사람들은 여기에 반응을 합니다. 댓글을 달고, 좋아요(👍)와 같은 이모티콘을 통해서도 사람들은 반응합니다. 문제는 이러한 댓글들은 특정 게시물에 대해 긍정과 부정 그리고 중립 크게 3가지로 반응을 한다는 점입니다. 또한, 좋아하는 단어, 싫어하는 단어, 중립적인 단어 등을 조합하여 반응한다는 점입니다. 이러한 댓글을 어떻게 분석할 것인가가 최근에 대두가 되기 시작하였고, 1차원적인 빈도분석부터 시작하여 감정분석, 이러한 분석을 통한 다양한 활용사례까지 나오기 시작했습니다.

본 개인프로젝트도 댓글을 수집하는 웹크롤링부터 시작하여 오픈소스 패키지를 활용한 분석, 그리고 구글 클라우드 API를 활용한 방법을 안내하는 패키지로 쓰려고 합니다.

1.1 참고한 자료들

블로그 형태의 글이기 때문에 개별적인 주석은 생략하였습니다. 이 글이 향후 논문형태로 씌여지기를 작게나마 기대합니다.

2 기본 패키지 설치

2.1 rtweet 패키지

rtweet 패키지를 활용하면 별도의 인증키 없이 트위터 데이터를 보다 쉽게 가져올 수 있습니다.

2.1.1 사용법

트위터 ID와 패스워드만 알고 있으면 됩니다. 가장 큰 장점은 트위터 API를 사용하기 위해서 트위터 개발자 아이디와 별도의 트위터 앱이 필요없습니다.

2.1.2 주요함수

모든 사용자가 반드시 인증을 받아야 합니다만, 단순하게 패키지내 함수들(search_tweets(), get_timeline(), get_followers(), get_favorites())을 사용하면 인증키 관련 입력란이 활성화됩니다. 아래 그림을 참고하시기를 바랍니다.

twitter_account

2.1.3 샘플코드

위 결과가 증명하는 것처럼, 매우 쉽게 양질의 트위터 데이터를 확보할 수 있는 것을 볼 수가 있습니다.

2.2 tidyverse 패키지

tidyverse 패키지는 데이터 전처리와 시각화 특화된 패키지입니다. 자세한 설명은 Garrett Grolemund & Hadley Wickham이 펴낸 R for Data Science를 참고하시기를 바랍니다.

2.3 stringr 패키지

stringr 패키지는 문자열 전처리에 특화된 패키지입니다. stringr 패키지는 정규표현식과 같이 사용될 때, 효과가 극대화 될 수 있습니다. 해당 패키지에 관한 자세한 내용은 공식 문서에서 확인하시기를 바랍니다.

2.4 googleLanguageR 패키지

위 패키지는 Google Cloud Platform에서 제공하는 자연어 처리 API 인터페이스 패키지입니다. 위 패키지를 사용하면 보다 쉽게 감정분석을 할 수 있습니다. 해당 패키지는 추후 감정분석 때 다시한번 설명을 할 예정입니다.

2.5 KoNLP 패키지

KoNLP(Korean Natural Language Processing)패키지는 국내 대표적인 자연어 분석 패키지입니다. 한국어를 분석할 수 있는 총 27개의 함수가 들어 있습니다. 이 부분 역시 감정분석 때 다시한번 다룰 예정입니다.

2.6 RcppMeCab & RmecabKo 패키지

KoNLP와 마찬가지로 일종의 형태소 분석 엔진입니다. 다만, 이 분석을 하려면 자바, 패키지 외 별도 패키지를 터미널을 활용해서 설치해야 하는 번거로움이 있습니다. 이 부분도 다시한번 다룰 예정입니다.

2.7 SentimentAnalysis 패키지

SentimentAnalysis 패키지는 R에서 원문의 감정분석을 보다 용이하게 도와주는 패키지입니다. 기존의 등록된 사전들(예: QDAP, Harvard IV, and Loughran-McDonald)와 같은 사전을 이용하거나 또는 사용자 정의된 사전을 이용할 수 있습니다. 이 부분은 실제 감정분석을 사용할 때, 군산대에서 제공하는 감성사전을 등록한 이후 다시 다룰 예정입니다.

2.8 주요 패키지 설치 코드

  • 기존에 설치된 패키지는 require()를 활용하여 불러옵니다.
  • 신규 패키지는 이미 설치된 installed.packages()에서 실제로 없는 것을 추출하여 new_pkgs 벡터 형태로 반환합니다. 그리고, 새로 설치 진행합니다.
install_pkgs <- function(pkgs) {
  
  # 신규 패키지 설치 
  new_pkgs <- pkgs[!(pkgs %in% installed.packages()[, "Package"])]
  if (length(new_pkgs))
      install.packages(new_pkgs, dependencies = TRUE)
  
  # 기존 패키지 library 불러오기
  sapply(pkgs, require, character.only = TRUE)
}

# 패키지 설치
packages <- c("tidyverse", "stringr", "rtweet", "SentimentAnalysis", "rtweet", "stringr")

install_pkgs(packages)
##         tidyverse           stringr            rtweet SentimentAnalysis 
##              TRUE              TRUE              TRUE              TRUE 
##            rtweet           stringr 
##              TRUE              TRUE
  • 위 소스코드는 개인적으로 매우 좋아하는 코드 이기 때문에 별도의 함수로 저장하는 것을 추천합니다.
  • 추가적으로 install_github()에서 내려 받아야 하는 코드를 추가적으로 작성하는 것도 또한 추천합니다.
  • 형태소 엔진인 googleLanguageR, KoNLP, RcppMeCab & RmecabKo 패키지 설치 방법을 안내하도록 하겠습니다.

3 형태소 엔진 패키지 설치

3.1 googleLanguageR

3.1.1 기본 개념 설명

googleLanguageR 패키지는 구글 클라우드에서 제공하는 자연어처리 API1 입니다. 패키지에 관한 자세한 설명은 googleLanguageR에서 자세하게 살펴보시면 좋을 것 같습니다. 쉽게 설명하면 API를 호출하기 위해서는 google cloud platform에서 제공하는 언어 관련 API를 호출해야 사용할 수 있습니다.

해당 패키지와 API를 활용해서 할 수 있는 것은 기본적으로 NLP API를 통해서는 감정분석, 문법 분석, 분류분석등을 지원합니다. 또한, 번역 API를 통해서는 번역 작업을 수행할 수 있습니다. 스피치 API를 통해서는 녹음한 음성을 텍스트로 바꿔주는 서비스를 제공하고 있습니다.

3.1.2 설치 방법 1. Google Cloud 플랫폼

  1. 우선 프로젝트 만들기 방법에 관한 자세한 설명은 구글의 공식문서 프로젝트 만들기 및 관리를 참고 하시기를 바랍니다.

  2. 프로젝트를 생성하면 메뉴에서 APIs & Servies 항목을 클릭하시기를 바랍니다.

  3. 왼쪽 메뉴에서 Library를 클릭하시기를 바랍니다.

  4. 검색창에서 Cloud Natural Language API를 클릭하여 Enable을 클릭하면 아래와 같이 API를 관리하는 화면으로 이동하게 됩니다.

  5. 마지막으로 Acount Credential을 지정합니다. 2019년 10월 기준 [Credentials in APIs & Services] - [Create Credentials] - [Service Account Key] - [Service Account] - [Key Type = JSON] - [Create] 순으로 지정하면 최종적으로 .JSON 파일을 다운로드 받을 수 있습니다.

3.1.3 설치방법 2. R 패키지 설치

우선 저장된 .json 파일의 경로를 기억합니다. 또는 원하는 파일 경로를 지정합니다. 가급적 중간 경로에 한글 경로가 없도록 유의 바랍니다. 인증키 값 확인을 위해서는 크게 두가지 방법이 있습니다.

3.1.3.1 .Renviron

R 해당 프로젝트가 있는 쪽에 텍스트 에디터를 활용하여 확장자명 .Renviron 파일을 만들고 아래 내용을 입력합니다.

#.Renviron
GL_AUTH=location_of_json_file.json

이때 경로는 예를 들면 GL_AUTH = textMiningR/location_of_json_file.json와 같은 형태로 지정 하시면 됩니다.

이렇게 한번 지정을 해두면 패키지를 불러올 때 자동적으로 인증이 완료됩니다.

library(googleLanguageR)

3.1.3.2 gl_auth()

만약 .Renviron 파일로 하는 것을 원치 않는다면 R script 상에서 직접 gl_auth()를 활용하여 인증할 수 있습니다.

library(googleLanguageR)
gl_auth("location_of_json_file.json")

인증이 완료가 되면 각 API 함수를 사용할 수 있습니다. - gl_nlp() - Natural Language API - gl_speech() - Cloud Speech API - gl_translate() - Cloud Translation API

3.1.4 Sample 코드

  • gl_nlp()에 관한 자세한 함수 사용법은 help(gl_nlp)를 클릭하셔서 확인하시기를 바랍니다.
library(googleLanguageR)

# random text form wikipedia
texts <- c("Norma is a small constellation in the Southern Celestial Hemisphere between Ara and Lupus, one of twelve drawn up in the 18th century by French astronomer Nicolas Louis de Lacaille and one of several depicting scientific instruments.", 
         "Solomon Wariso (born 11 November 1966 in Portsmouth) is a retired English sprinter who competed primarily in the 200 and 400 metres.")


nlp_result <- gl_nlp(texts, nlp_type = "analyzeSentiment", language = "en")

3.2 KoNLP 패키지 설치하기

3.2.1 자바 설치하기

KoNLP 패키지를 설치하려면 Java(>=1.6) 필요합니다. 자바 설치 할 때 보다 쉽게 할 수 있는 방법이 나와서 같이 소개하오니 참고 바랍니다.

# install.packages("remotes")
# remotes::install_github("mrchypark/multilinguer")
library(multilinguer)
has_java()
## [1] TRUE

3.2.2 KoNLP 설치 및 샘플코드

자바가 설치 되었기 때문에

library(KoNLP)

# 사전 등록
useSejongDic()
## Backup was just finished!
## 370957 words dictionary was built.
sentence <- "아버지가 가방에 스르륵 들어가신다"
extractNoun(sentence)
## [1] "아버지" "가방"   "스르륵"

3.3 RmecabKo 패키지 설치

은전한닢 프로젝트는 일본어 형태소 분석기인 mecab-ko를 기반으로 나온 패키지입니다. 오픈소스 형태이기 때문에 무료로 사용 가능합니다. 이 패키지의 장점은 C++에서 직접 동작하므로 다른 형태소 분석기에 비해 상당히 빠르다고 합니다. 자세한 설명은 RmecabKo에서 확인하시기를 바랍니다.

RmecabKo 소개에 나와 있는 것처럼 먼저 은전한닢 프로젝트에서 “MeCab”과 함께 한국어 사전인 ’MeCab-Ko-Dic’를 설치해야 합니다.

순서는 mecab & mecab-ko-dic 설치 순으로 진행하시면 됩니다. 다만, 본 포스트에서는 MacOS 사용자 대상으로 진행한 것이기 때문에 Windows 사용자분들은 RcppMeCab에서 참고하시기를 바랍니다.

3.3.1 MeCab 설치

먼저 터미널을 열고 아래와 같이 입력합니다. 다른 버전을 사용하고 싶다면 bitbucket MeCab 싸이트에서 MeCab 압축파일을 다운로드 받아 압축을 풀고 설치를 진행합니다. 그러나, 별도로 다운받을 필요는 없습니다.

$ wget https://bitbucket.org/eunjeon/mecab-ko/downloads/mecab-0.996-ko-0.9.2.tar.gz
$ tar xzvf mecab-0.996-ko-0.9.2.tar.gz
$ cd mecab-0.996-ko-0.9.2
$ ./configure
$ make
$ sudo make install

다만, 이때 주의해야 할 것은 현재 경로가 mecab-0.996-ko-0.92에 지정되고 있기 때문에 cd .. 명령어를 입력하여 경로를 수정하고 한국어 사전 설치 진행하시면 됩니다.

3.3.2 MeCab 한국어 사전 설치

한국어 사전을 설치하려면 bitbucket MeCab 한국어 사전 싸이트에서 한국어 사전 압축파일을 다운로드 받아 압축을 팔고 설치를 진행합니다.

$ wget https://bitbucket.org/eunjeon/mecab-ko-dic/downloads/mecab-ko-dic-2.1.1-20180720.tar.gz
$ tar xzvf mecab-ko-dic-2.1.1-20180720.tar.gz
$ cd mecab-ko-dic-2.1.1-20180720
$ ./configure
$ make
$ sudo make install

3.3.3 RcppMeCab & RmecabKo 설치 및 샘플코드

  • 형태소 분석을 위해 pos() 함수를 사용해서 각 문서에 형태소 분석을 수행하고 명사만을 추출한다.
# install.packages("RcppMeCab")
# install.packages("RmecabKo")

library(RcppMeCab)
library(RmecabKo)

sample_text <- c("부실 대출로 인해서 은행은 벌금을 지불하는데 동의했다",
                 "은행에 대출을 늦게 갚은 경우, 은행에서 지연에 대해 이자를 물릴 것이다.", 
                 "시내에 새로운 식당이 생겼습니다.",
                 "테헤란로에 맛집 식당이 있습니다.",
                 "새로 개장하려고 하는 식당 대출을 어떻게 상환할 계획입니까?")

sample_df <- tibble::tibble(
  document = paste0("문서", 1:5),
  text = sample_text
)

ds_df <- sample_df %>% 
  mutate(text_pos = pos(text)) %>% 
  unnest(text_pos)

ds_df %>% 
  DT::datatable()
ds_noun_df <- ds_df %>% 
  filter(str_detect(text_pos, "/NNG")) %>% 
  mutate(text_no_pos = str_replace_all(text_pos, "/NNG", ""))

ds_noun_df %>% 
  DT::datatable()

ds_df에서는 다양한 형태의 글이 분류 되는 것을 볼 수 있지만, ds_noun_df에서는 명사만 분류된 것을 다시 확인할 수 있습니다.

4 텍스트 마이닝 미니 프로젝트 - 오픈소스 기반

4.1 트위터 특정 키워드 별 텍스트 데이터 수집

트위터 데이터 수집에는 rtweet 패키지를 활용할 것입니다. 패키지를 활용하면 매우 쉽게 트위터 데이터를 가져올 수 있다는 장점이 있지만, 대용량의 트위터 데이터를 가져올 시에는 제한이 있으니 참고바랍니다. 참고: Rate Limiting

library(rtweet)
library(stringr)
# tweets_df <- search_tweets(q = "패션", n = 10000, type = "mixed")
# writexl::write_xlsx(tweets_df, "../data/tweets.xlsx")

df <- readxl::read_excel("../data/tweets.xlsx")
DT::datatable(head(df, n = 500), 
              class = 'cell-border stripe', 
              options = list(pageLength = 5, autoWidth = TRUE, scrollX = TRUE))

트위터에서 제공하는 데이터는 위와 같이, 총 90개의 변수로 이루어져 있는 것을 확인 할 수 있습니다.

4.2 텍스트 데이터 문자열 전처리

library(stringr)
doc <- df$text
docs <- str_replace_all(doc, "[^0-9a-zA-Zㄱ-ㅎㅏ-ㅣ가-힣[:space:]]", " ")
docs <- str_replace_all(docs, "[\n\t]", " ")
docs <- str_trim(docs)
docs <- str_replace_all(docs, "\\s+", " ")

위 소스코드는 트위터의 문자열 데이터(예: 불필요한 공백 제거)를 처리하는 과정을 보여드렸습니다. 불필요한 공백은 제거 하였습니다.

4.3 텍스트 데이터 Tokenizer

library(tm)
corp <- VCorpus(VectorSource(docs))
tdm <- TermDocumentMatrix(corp, 
                          control = list(wordLengths = c(1, Inf), 
                                         tokenize = function(x) {
                                           ngram_tokenize(x, char = F)
                                         }))

tail(Terms(tdm))
## [1] "ㅡㅡ 근데 결ㅈㅔ가" "ㅡㅡ 나"            "ㅡㅡ 나 안경"      
## [4] "ㅣㅏ"               "ㅣㅏ https"         "ㅣㅏ https t"
head(Terms(tdm))
## [1] "0"            "0 3세"        "0 3세 달퐁"   "0 3세 아라칸"
## [5] "00"           "00 13"
  • Tokenization이란 문자열을 여러개의 조각, 즉 여러 개의 Token(토큰)들로 쪼개는 것을 말합니다. Token은 문자열의 한 조각으로 하나의 단어가 하나의 토큰이라고 할 수 있습니다.
  • N-gram Tokenizer() 함수는 SentimentAnalysis 패키지에 있으며, tm패키지의 TermDocumentMatrix() 함수와 같이 사용됩니다. 가장 큰 장점은 언어 중, 비 라틴계 언어도 토큰화 할 수 있다는 장점이 있습니다.

4.4 빈도 그래프

library(tidyverse)

# 희소단어 제거
tdm_r <- removeSparseTerms(tdm, sparse = 0.95)
head(Terms(tdm_r), 20)
##  [1] "191024"                     "191024 exo"                
##  [3] "191024 exo sehun"           "191024 exo suho"           
##  [5] "23일"                       "23일 현지시간"             
##  [7] "23일 현지시간 공개된"       "airport"                   
##  [9] "airport hoodie"             "airport hoodie sweat"      
## [11] "airport pet"                "airport pet wappen"        
## [13] "beyond"                     "beyond closet"             
## [15] "beyond closet 비욘드클로젯" "bfz6qyrgkh"                
## [17] "blue"                       "bsecr7xri7"                
## [19] "bsecr7xri7 exo"             "bsecr7xri7 exo sehun"
wordFreq <- slam::row_sums(tdm_r)
wordFreq_df <- data.frame(words = names(wordFreq), 
                          freq  = wordFreq)

remove_chars <- c("t", "co", "t co", "https", "https t", "https t co")
wordFreq_df2 <- wordFreq_df %>% 
  filter(!(words %in% remove_chars))

# Plot
theme_set(theme_bw(base_family = "AppleGothic"))
ggplot(wordFreq_df2 %>% filter(freq > 1000), aes(reorder(words, freq), freq)) + 
  geom_bar(stat="identity", width = 0.5, fill="tomato2") + 
      coord_flip() + 
      theme(axis.text.x = element_text(angle=65, vjust=0.6))

#패션 이라는 해쉬 태그를 이용하여 데이터를 수집한 결과 트위터에서는 특이하게 아이돌이 많이 등장하는 것을 볼 수 있습니다. - 이렇게 나오는 빈도표를 활용하여 어떻게 활용할 수 있을지 업계 현직자와 많은 대화를 하다보면 좀 더 많은 아이디어가 도출 되지 않을까 싶습니다.

5 감정분석의 여러 방법

5.1 사전기반의 감정분석

5.1.1 한국어 감성사전 등록

군산대학교 소프트웨어융합공학과에서 미리 개발된 감성사전을 활용하였습니다. KNU 한국어 감성사전에 대해 자세히 알고 싶다면 관련 링크를 클릭하셔서 살펴보시기를 바랍니다. - 군산대학교 KNU 한국어 감성사전

senti_words_kr <- readr::read_delim("../data/SentiWord_Dict.txt", delim='\t', col_names=c("term", "score"))
head(senti_words_kr)
## # A tibble: 6 x 2
##   term  score
##   <chr> <dbl>
## 1 (-;       1
## 2 (;_;)    -1
## 3 (^^)      1
## 4 (^-^)     1
## 5 (^^*      1
## 6 (^_^)     1
dim(senti_words_kr)
## [1] 14855     2
table(senti_words_kr$term)[1:10]
## 
## -_-^    ;  ;-)   ;) ;ㅅ;  :-(  :-)  :-D  :-P :'-( 
##    1    1    1    1    1    1    1    1    1    1

이번에 준비한 감성 사전에는 총 14855의 개수와 2개의 변수가 확인되었습니다.

5.1.2 감성어 사전 준비

SentimentAnalysis

x <- duplicated(senti_words_kr$term)
senti_words_kr2 <- senti_words_kr[!x, ]
senti_dic_kr <- SentimentDictionaryWeighted(words = senti_words_kr2$term, 
                                            scores = senti_words_kr2$score)
senti_dic_kr <- SentimentDictionary(senti_words_kr2$term[senti_words_kr2$score > 0], 
                                    senti_words_kr2$term[senti_words_kr2$score < 0])

summary(senti_dic_kr)
## Dictionary type:  binary (positive / negative)
## Total entries:    14699
## Positive entries: 4871 (33.14%)
## Negative entries: 9828 (66.86%)
  • 우선 등록된 사전 중, 중복된 값이 없는지 사전 점검합니다.
senti_words_kr$term[duplicated(senti_words_kr$term)]
## [1] "버릇없이"   "울컥하다"   "적극적이다"

위 3개의 단어는 중복적으로 기재된 것으로 확인하였고, 그 후에 제거를 했습니다.

  • SentimentDictionaryWeighted() 함수는 크게 두개의 데이터 셋으로 구성이 됩니다. words & scores. 여기에서 scores 각 단어당 긍정 및 부정에 관한 가중치를 도메인 영역에 맞게 주시면 됩니다.

  • SentimentDictionary()는 입력에 따라서, 새로운 감성 사전을 만들어 낼 수 있습니다. 여기에서는 binary 형태의 (긍정 및 부정)으로 구성이 되어 있고, 전체 14699개의 데이터 중에서 긍정어 비율은 약 33.14% / 부정어 비율은 약 66.86%으로 확인이 되었습니다.

  • 감성사전을 어떻게 정의하냐에 따라서 감정분석의 방향성이 정해지는 만큼 이 부분에서 도메인 영역, 회사의 마케팅 기획 및 분석 기획이 매우 중요한 요소중의 하나가 됩니다.

5.1.3 감성분석 실시

5.1.3.1 데이터 값

res_sentiment <- analyzeSentiment(corp, #대신에 corpus,
                              language="korean",
                              rules=list("KoreanSentiment"=list(ruleSentiment, senti_dic_kr)),
                              removeStopwords = F, stemming = F)

df2 <- data.frame(round(res_sentiment, 3), df)

theme_set(theme_minimal(base_family = "AppleGothic"))
df3 <- df2 %>% 
  mutate(pos_neg = if_else(KoreanSentiment >= 0, "PositiveTweet", "NegativeTweet")) %>%
  select(pos_neg, everything())

DT::datatable(head(df3 %>% select(screen_name, text, favorite_count, retweet_count, KoreanSentiment, pos_neg), n = 500), 
              class = 'cell-border stripe', 
              options = list(pageLength = 5, autoWidth = TRUE, scrollX = TRUE))
  • 감정분석사전을 활용하여 감장점수 0을 기준으로 긍정트윗(PositiveTweet)과 부정트윗(NegativeTweet)으로 나누었습니다.
  • 감정분석과 관련된 주요변수들을 재 추출하였습니다.

5.1.3.2 트위터 데이터의 긍정 및 부정어 시각화

ggplot(df3, aes(x = factor(pos_neg))) + 
  geom_bar(stat = "count", width = 0.7, fill = "steelblue") + 
  theme_minimal()

  • 위 내용을 보면 #패션이라는 키워드로 감정분석을 실시한 결과 대부분 긍정트윗으로 확인되었습니다.
  • 특정 도메인의 감정사전은 다룰 수 있지만, 만약 추가적인 감정사전을 만드려면 각 도메인(여기서는 패션업)에 맞는 긍정어 부정어를 찾는것이 중요한 과제로 보여집니다.

  1. API는 응용 프로그램에서 보다 쉽게 사용자가 사용할 수 있도록, 운영 체제나 프로그래밍 언어가 제공하는 기능을 제어할 수 있게 만들어주는 인터페이스를 뜻합니다. 즉, 각 구글과 같은 클라우드 업체들은 특화된 개별 서비스를 만들어 API 형태로 제공하며, 사용량 만큼 요금을 부과하는 형태로 서비스를 제공하고 있습니다.