1 단어주머니 기반 지식

본 학습내용은 Ted Kwartler, “Text Mining: Bag of Words”, DataCamp의 내용을 바탕으로 재작성한 것을 밝혀둡니다. 또한, 한국어 자연어처리에 대한 내용은 싸이그래머 밋업에서 박조은님이 발표한 “텍스트 데이터 전처리로 시작하는 NLP”에서 가져왔다.

1.1 언어기술 로드맵

스탠포드 Dan Jurfsky 교수가 정의한 언어기술(Language Technology)에 따라 거의 해결된 기술과 상당한 진전을 이루는 기술, 그리고 여전히 어려운 문제로 구분했다.

  • 상당부분 해결 문제
    • Spam Detection
    • Part-of-speech (POS) tagging
    • Named entity recognition (NER)
  • 성과를 내고 있는 문제
    • Sentiment analysis
    • Coreference resolution
    • Word sense disambiguation (WSD)
    • Parsing
    • Machine translation (MT)
    • Information extraction (IE)
  • 여전히 어려운 문제
    • Question answering (QA)
    • Paraphrase
    • Summarization
    • Dialog

언어기술

1.2 단어주머니 접근법

단어 주머니(bag of words) 모형은 전통적인 자연어 처리(Natural Language Processing) 혹은 정보 검색(Information Retrieval)에서 사용되는 간략화한 표현으로 볼 수 있다. 문법 무시, 어순 무시 등 전통적인 텍스트 분석에 사용되는 것과 달리, 각 단어가 출현된 회수를 Feature로 추출하여 문서 분류, 감정분석 등에 활용한다.

BOW(bag of words)를 간단히 정리하면 다음과 같다.

  • 가장 간단하지만 효과적이라 널리쓰이는 방법
  • 장, 문단, 문장, 서식과 같은 입력 텍스트의 구조를 제외하고 각 단어가 해당 말뭉치에 얼마나 많이 나타나는지만 헤아림
  • 구조와 상관없이 단어의 출현횟수만 세기 때문에 텍스트를 담는 가방(bag)으로 생각할 수 있음
  • BOW는 단어의 순서가 완전히 무시 된다는 단점, 예를 들어 의미가 완전히 반대인 두 문장을 보자
    • it’s bad, not good at all.
    • it’s good, not bad at all.
    • 위 두 문장은 의미가 전혀 반대지만 완전히 동일하게 반환
  • 이를 보완하기 위해 n-gram을 사용하는 데 BOW는 하나의 토큰을 사용하지만 n-gram은 n개의 토큰을 사용

텍스트 데이터 분석 접근방법 비교

1.3 텍스트 데이터 분석 작업 흐름

텍스트 문자 데이터 분석 작업 흐름도 일반적인 데이터 분석 과정과 크게 차이가 나지는 않는다.

  1. 문제 정의 및 목표설정: 적용분야 전문지식 습득 필요
  2. 수집 텍스트 데이터 식별: 목표달성을 위한 정보가 담긴 데이터 발굴
  3. 데이터 분석 및 모형 개발 과정
    • 텍스트 데이터 전처리 및 구조화
    • 데이터 정제 및 Feature 공학을 통한 Feature 추출
    • 데이터 분석 및 모형 개발 : 탐색적 데이터 분석 포함
  4. 분석결과 정리: 보고서, 추천, 통찰 도출

텍스트 데이터 분석 작업흐름

1.3.1 텍스트 데이터 전처리 1 2 3

데이터 정제 및 전처리는 기계가 텍스트를 이해할 수 있도록 텍스트를 정제하여 신호와 소음을 구분하는 과정이다. 이를 위해서 HTML 태그, 특수문자, 이모티콘을 제거하고 정규표현식도 동원하고, 불용어(Stopword),어간추출(Stemming), 음소표기법(Lemmatizing)을 동원한다.

  1. 토큰화(Tokenization)
  2. 정제(Cleaning): 소문자, 공백(Whitespace) 제거, 구두점 소거 등
  3. 어간 추출(Stemming), 음소표기법(Lemmatization), Collocation
  4. 필터(Filtering): 불용어 제거
  • 정규화 normalization (입니닼ㅋㅋ -> 입니다 ㅋㅋ, 샤릉해 -> 사랑해) *예시: 한국어를 처리하는 예시입니닼ㅋㅋㅋㅋㅋ -> 한국어를 처리하는 예시입니다 ㅋㅋ
  • 토큰화 tokenization
    • 예시: 한국어를 처리하는 예시입니다 ㅋㅋ -> 한국어Noun, 를Josa, 처리Noun, 하는Verb, 예시Noun, 입Adjective, 니다Eomi ㅋㅋKoreanParticle
  • 어근화 stemming (입니다 -> 이다)
    • 예시: 한국어를 처리하는 예시입니다 ㅋㅋ -> 한국어Noun, 를Josa, 처리Noun, 하다Verb, 예시Noun, 이다Adjective, ㅋㅋKoreanParticle
  • 어구 추출 phrase extraction
    • 예시: 한국어를 처리하는 예시입니다 ㅋㅋ -> 한국어, 처리, 예시, 처리하는 예시
만화가 김충원 개발하는 데이터 분석가 박은정
사람의 생김새 를 결정짓는 것은 골격과 피부의 미묘한 변화에서 비롯되는 차이 점이고, 그 차이점을 없애 버린다면 모든 사람의 생김새는 똑같을 것입니다. 데이터를 결정짓는 것은 행과 열의 미묘한 변화에서 비롯되는 차이 점이고, 그 차이점을 없애 버린다면 모든 데이터는 똑같을 것입니다.
  • 불용어(Stopword)는 일반적으로 코퍼스에서 자주 나타나는 단어로 실제 학습이나 예측 프로세스에 실제로 기여하지 않는 것으로 나타난다.
    • 예를 들어, 한국어 불용어로 조사, 접미사 - 나, 너, 은, 는, 이, 가, 하다, 합니다 등이 있다.
  • 어간추출(Stemming)는 단어를 축약형으로 바꿔준다.
    • 예를 들어, 새로운 (like new), 새로울 (will be new) → 새롭다 (new), 먹었다 (ate), 먹을 (will eat), 먹을지도 모르는(may be eating) → 먹다 (eat)
  • 음소표기법(Lemmatization)은 품사정보가 보존된 형태의 기본형으로 변환하는 것이다.
    • 예를 들어, 1) 배가 맛있다. 2) 배를 타는 것이 재미있다. 3) 평소보다 두 배로 많이 먹어서 배가 아프다.
    • 영어에서 meet는 meeting으로 쓰였을 때 회의를 뜻하지만 meet 일 때는 만나다는 뜻을 갖는데 그 단어가 명사로 쓰였는지 동사로 쓰였는지에 따라 적합한 의미를 갖도록 추출하는 것

1.3.2 문서단어행렬(DTM), 단어문서행렬(TDM)

0과 1밖에 모르는 기계에게 인간의 언어 알려줘야 하는데, 이유는 컴퓨터는 숫자만 인식할 수 있기 때문에 바이너리 코드로 처리해 줘야한다. 텍스트 데이터 즉, 순서가 없는 범주형 데이터를 수치형 데이터로 변환한다. 벡터에서 해당되는 하나의 데이터만 1로 변경해 주고 나머지는 0으로 채워주는 방식으로 원핫 인코딩(One-Hot Encoding), 통계학에서 가변수(Dummy Variable)처리라고 부른다.

TF(단어 빈도, term frequency)는 특정한 단어가 문서 내에 얼마나 자주 등장하는지를 나타내는 값으로 이 값이 높을수록 문서에서 중요하다고 생각할 수 있지만, 단어 자체가 문서군 내에서 자주 사용되는 경우,이것은 그 단어가 흔하게 등장한다는 것을 의미도 된다.

이것을 DF(문서 빈도, document frequency)라고 하며, 이 값의 역수를 IDF(역문서 빈도, inverse document frequency)라고함 TF-IDF는 TF와 IDF를 곱한 값이 된다.

단어문서행렬(Term Document Matrix)을 전치(Transpose)하게 되면 문서단어행렬(DTM)이 된다. 단어문서행렬은 다음과 같은 형태를 갖는다.

\(문서_1\) \(문서_1\) \(문서_1\) \(\cdots\) \(문서_n\)
\(단어_1\) 0 0 0 0 0
\(단어_2\) 1 1 0 0 0
\(단어_3\) 1 0 0 0 0
\(\cdots\) 0 0 2 1 1
\(단어_m\) 0 0 0 1 0

문서단어행렬은 단서문서행렬을 전치하여 다음과 같은 형태를 갖는다.

\(단어_1\) \(단어_1\) \(단어_1\) \(\cdots\) \(단어_n\)
\(문서_1\) 0 1 1 0 0
\(문서_2\) 0 1 0 0 0
\(문서_3\) 0 0 0 2 0
\(\cdots\) 0 0 0 1 1
\(문서_m\) 0 0 0 1 0

qdap 팩키지 wfm() 함수를 사용해서 말뭉치(Corpus) 대신 텍스트에서 바로 단어 빈도수를 산출하는 것도 가능하다.

\(문서_1\)
\(단어_1\) 0
\(단어_2\) 1
\(단어_3\) 1
\(\cdots\) 0
\(단어_m\) 0

1.4 텍스트 분석 R 팩키지

자연어 처리가 중요한 일상이기도 하고 최근 챗봇 등으로 인해 자연어에 대한 관심도 많아 졌다. 관련하여 텍스트 분석 및 자연어 처리에 대한 다양한 팩키지도 새로 등장하고 있다.

2 텍스트 분석 상세 작업흐름도

텍스트 데이터를 말뭉치(Corpus)로 변환하여 데이터를 분석하는 것도 가능하지만, qdap 팩키지와 정규표현식의 기능을 적극 활용하여 가능하면 텍스트에서 전처리를 하고 나서, 말뭉치로 변환한 후에 tm 팩키지 정제기능을 활용하는 것도 가능하다. 물론 중복되는 부분도 있을 수 있으나 빨래를 할 때 세탁기로 넣어 한 번 돌리고, 손빨래로 두번째 빨래를 깔끔하게 마무리하는 것으로 봐도 좋을 듯 하다.

깔끔한 말뭉치로 텍스트 데이터가 정제되고 나면 행렬로 변화시켜 다양한 통계 분석을 시작한다.

텍스트에서 말뭉치로 변환

2.1 qdap, tm 텍스트 자료구조 비교

텍스트 마이닝(Text Mining)을 위한 R의 대표적인 팩키지가 qdap tm 이다.

팩키지명 원 텍스트 단어 빈도수(word counts)
qdap 데이터프레임(Dataframe) 단어 빈도 행렬(Word Frequency Matrix)
tm 말뭉치 (Corpus) 단어 문서행렬(Term Document Matrix)/문서 단어행렬(Document Term Matrix)

qdap은 원 텍스트 데이터를 데이터프레임 형태로 저장하는 반면에, tm 팩키지는 Corpus 말뭉치 형태로 원 텍스트 데이터를 저장한다는 점에서 차이가 난다.

두 팩키지 모두 공통으로 사용하는 단어/용어 빈도수에는 행렬(matrix)을 사용한다. 이를 그림을 표현하면 다음과 같다.

qdap, tm 비교

  • qdap 텍스트 원문 qdap_datqview(qdap_dat)
  • tm 텍스트 원문 tm_dat → inspect(tm_dat)
  • qdap 단어 빈도수 qdap_wfm → summary(qdap_wfm)
  • tm 단어 빈도수 tm_tdm → inspect(tm_tdm)

2.2 tm, qdap 데이터 정제 기능

tm, qdap 전처리 기능

단어 주머니 기법을 활용하여 텍스트를 분석할 때, 데이터 정제를 통해 단어를 합산하는데 큰 도움이 된다. 영어 단어 예를 들어, statistics, statistical, stats 등은 모두 통계라는 한 단어로 정리되면 좋다.

tm 팩키지 및 base 팩키지에 내장된 데이터 정제 기능은 다음과 같다.

  • tolower(): base에 포함된 함수로 모든 문자를 소문자로 변환.
  • removePunctuation(): tm에 포함된 함수로 모든 구두점을 제거.
  • removeNumbers(): tm에 포함된 함수로 숫자를 제거
  • stripWhitespace(): tm에 포함된 함수로 공백(whitespace)을 제거

qdap에는 좀더 다양한 텍스트 정제 함수가 지원된다.

  • bracketX(): 괄호 내 모든 텍스트 제거
    • “It’s (very) nice” → “It’s nice”
  • replace_number(): 아라비아 숫자를 대응되는 영어문자로 변환
    • “7” → “seven”
  • replace_abbreviation(): 축약어를 대응되는 전체 문자로 풀어냄
    • “Jan” → “Janunary”
  • replace_contraction(): 단어 축약을 원래 상태로 되돌림
    • “can’t” → “can not”
  • replace_symbol(): 일반 기호를 대응되는 단어로 교체
    • “$” → “dollar”

텍스트가 너무 자주 출현하여 거의 정보를 제공하지 않는 단어를 불용어(stop words) 라고 부른다. tm 팩키지에는 영어기준으로 174개 불용어가 등재되어 있다. 또한, 관심있는 주제로 문서를 모았다면 수집된 거의 모든 문서에 특정 단어가 포함되어 있어 이것도 도움이 되지 않아 불용어에 등록하여 텍스트 분석을 수행한다.

> removeWords(text, stopwords("english"))
> stop_words_lst <- c("rstudio", "statistics", stopwords("english"))
> removeWords(text, stop_words_lst)

stopwords("english") 영어불용어 사전에 “rstudio”, “statistics” 단어를 더해서 불용어 사전을 완성하고 나서 removeWords() 함수로 새로 갱신된 사전에 맞춰 불용어를 정리한다.

2.3 텍스트에서 말뭉치(Corpus)로 변환

텍스트 마이닝을 통해 데이터를 분석하려면 우선 원데이터가 텍스트 형태로 되어 있고 이를 말뭉치로 변환하여야 한다. 데이터가 벡터 형태(연설문 등) 혹은 데이터프레임 형태(트위터 트윗 등)에 따라 VectorSource(), DataframeSource() 함수를 적용하여 Source 객체로 변형을 시킨다. 그리고 나서 VCorpus() 함수를 활용하여 말뭉치(Corpus)로 저장한다. 휘발성 말뭉치(Volatile Corpus, VCorpus)와 영구저장 말뭉치(Permanent Corpus, PCorpus)로 변환이 가능하다. 주로 VCorpus를 메모리에 적재하여 텍스트 데이터 분석에 활용한다.

텍스트에서 말뭉치로 변환

2.4 말뭉치 정제

텍스트를 말뭉치 객체로 저장시켜 놓으면, 말뭉치 정제 과정을 거치야 된다. 기본적으로 어떤 말뭉치 정제에 사용되는 기본 정제함수는 tm 팩키지를 활용하고, 추가적으로 qdap 팩키지 특수 말뭉치 정제 함수도 적극 활용한다.

말뭉치 정제

2.5 단어문서행렬(Term Document Matrix, TDM)

단어문서행렬을 통한 방법이 일반적으로 많이 사용된다. 이를 위해 입력값이 데이터프레임인 경우 DataframeSource, 벡터인 경우 VectorSource를 사용하여 말뭉치(Corpus)로 변환하고, 이를 TermDocumentMatrix 함수에 넣어 단어문서행렬을 생성한다. 물론 텍스트를 바로 넣어 wfm 단어빈도행렬(Word Frequency Matrix)을 생성시켜 분석을 하기도 하지만 일반적인 방식은 아니다.

TermDocumentMatrix() 함수를 활용하여 단어가 문서에 출현하는 빈도수를 행렬로 저장한다. 행렬형태 데이터를 저장하게 되면 고급 통계 분석이 가능하게 된다. DocumentTermMatrix()TermDocumentMatrix() 함수로 통해 나온 행렬을 전치(transpose)시킨 것이다.

3 텍스트 데이터 시각화 4 5

오바마 퇴임 연설문과 트럼프 취임 연설문을 통해 텍스트 데이터를 시각화한다.

3.1 환경설정

tm, qdap 텍스트 데이터 전처리를 위한 팩키지를 불러오고, 텍스트 시각화를 위한 wordcloud, plotrix 팩키지도 가져오고, 데이터 전처리 함수를 두개 생성한다. 하나는 텍스트 수준에서 텍스트 데이터를 전처리하는 함수로 qdap에서 불러오고, 또 다른 함수는 tm에서 지원하는 함수로 중복되는 부분도 있다. 상황에 따라 편리한 함수를 활용한다.

> # 0. 환경설정 -------------------------------------------------------
> library(tm)
> library(tidytext)
> library(qdap)
> library(tidyverse)
> library(wordcloud)
> library(plotrix)
> 
> clean_text <- function(text){
+   text <- tolower(text)
+   # text <- removeNumbers(text)
+   # text <- bracketX(text)
+   text <- replace_number(text)
+   text <- replace_abbreviation(text)
+   text <- replace_contraction(text)
+   text <- replace_symbol(text)
+   text <- removePunctuation(text)
+   text <- stripWhitespace(text)
+   text <- str_replace_all(text, "americans", "america")
+   
+   indexes <- which(text == "")
+   if(length(indexes) > 0){
+     text <- text[-indexes]
+   } 
+   return(text)
+ }
> 
> clean_corpus <- function(corpus){
+   corpus <- tm_map(corpus, content_transformer(replace_abbreviation))
+   corpus <- tm_map(corpus, stripWhitespace)
+   corpus <- tm_map(corpus, removePunctuation)
+   corpus <- tm_map(corpus, removeNumbers)
+   corpus <- tm_map(corpus, removeWords, c(stopwords("en"), "Top200Words"))
+   corpus <- tm_map(corpus, content_transformer(tolower))
+   return(corpus)
+ }

3.2 데이터 불러오기

오바마 퇴임식 연설문과 트럼프 취임식 연설문을 불러온다.

> # 1. 데이터 불러오기 -------------------------------------------------------
> obama <- readLines("data/obama_farewell.txt", encoding = "UTF-8")
> trump <- readLines("data/trump_inauguration.txt", encoding = "UTF-8")

3.3 데이터 전처리

텍스트를 넣어 qdap 팩키지 전처리 함수를 조합한 clean_text 함수를 통해 텍스트 데이터를 정제하고 나서 말뭉치(Corpus)를 생성시키고 나서 이를 또다시 clean_corpus 함수로 정제한다. 그리고 나서, 단어구름 시각화를 위한 데이터프레임 형태로 변환시킨다.

> # 2. 데이터 전처리 -------------------------------------------------------
> source("code/clean_fun.R")
> 
> make_corpus <- function(text) {
+   text_clean <- clean_text(text)
+   text_source <- VectorSource(text_clean)
+   text_corpus <- VCorpus(text_source)
+   corpus <- clean_corpus(text_corpus)
+ }
> 
> obama_corpus <- make_corpus(obama)
> trump_corpus <- make_corpus(trump)
> 
> # 3. 말뭉치를 데이터프레임으로 변환 --------------------------------------
> 
> word_freq <- function(corpus) {
+     doc_tdm <- TermDocumentMatrix(corpus)
+     doc_m <- as.matrix(doc_tdm)
+     doc_term_freq <- rowSums(doc_m)
+     doc_word_freqs <- data.frame(term = names(doc_term_freq),
+                              num = doc_term_freq) %>% arrange(desc(num))
+     return(doc_word_freqs)
+ }
> 
> obama_word_freqs <- word_freq(obama_corpus)
> trump_word_freqs <- word_freq(trump_corpus)

3.4 텍스트 데이터 시각화

텍스트 데이터 시각화로 가장 많이 사용되는 단어구름(wordcloud)을 활용하여 트럼프 취임 연설문과 오바마 퇴임 연설문을 시각화한다. 오바마 퇴임 연설문, 트럼프 취임 연설문 각각에 대한 단어구름을 생성시키고 나서, 두 연설문의 공통된 단어 및 두 연설문에 공통적으로 나타나지 않는 차이나는 단어를 시각화한다.

마지막으로 두 연설문의 공통적으로 나타나는 단어만 뽑아 피라미드 그래프를 통해 공통적으로 언급하고 있으나 강도에 대한 부분을 시각화한다.

> # 4. 시각화 --------------------------------------------------------------
> ## 4.1. 단어구름----------------------------------------------------------
> par(mfrow=c(1,2))
> blues <- brewer.pal(8, "Blues")[-(1:2)]
> wordcloud(obama_word_freqs$term, obama_word_freqs$num, max.words=100, colors=blues)
> 
> reds <- brewer.pal(8, "Reds")[-(1:2)]
> wordcloud(trump_word_freqs$term, trump_word_freqs$num, max.words=100, colors=reds)

> ## 4.2. 공통 단어구름
> 
> all_obama <- paste(obama, collapse = " ")
> all_trump <- paste(trump, collapse = " ")
> 
> obama_trump <- c(all_obama, all_trump)
> 
> obama_trump_corpus <- make_corpus(obama_trump)
> obama_trump_word_freqs <- word_freq(obama_trump_corpus)
> 
> obama_trump_tdm <- TermDocumentMatrix(obama_trump_corpus)
> obama_trump_m <- as.matrix(obama_trump_tdm)
> 
> commonality.cloud(obama_trump_m,  colors ="steelblue1", max.words=100)
> 
> ## 4.3. 비교 단어구름
> colnames(obama_trump_tdm) <- c("Obama", "Trump")
> obama_trump_df <- as.matrix(obama_trump_tdm) %>% as.data.frame()
> 
> comparison.cloud(obama_trump_df, colors = c("blue", "red"), max.words = 50)

> ## 4.4. 피라미드 그래프
> par(mfrow=c(1,1))
> 
> common_words_25 <- obama_trump_df %>% 
+   mutate(label = rownames(obama_trump_df)) %>% 
+   dplyr::filter(Obama > 0 & Trump >0) %>% 
+   mutate(diff = abs(Obama - Trump)) %>% 
+   arrange(desc(diff)) %>% slice(1:25)
> 
> plotrix::pyramid.plot(common_words_25$Obama, common_words_25$Trump,
+              labels = common_words_25$label, gap = 8,
+              top.labels = c("Obama", "Words", "Trump"),
+              main = "Words in Common", laxlab = NULL, 
+              raxlab = NULL, unit = NULL)

[1] 5.1 4.1 4.1 2.1