Survey를 진행하다보면 주관식 문항에 대해 워드클라우드를 요청하는 고객사가 정말 많다.

워드클라우드는 해석에 그렇게 좋지 않은 그래프임에도 무언가 꽉찬 느낌에 화려하여 보고서에서 한 장 차지하기 딱 좋아보여서 그런 것 같다.

근래 가장 많은 도움을 받고 있는 책인 ‘Do it! 쉽게 배우는 R 데이터 분석(김영우, 2017)’에 따르면 워드클라우드는 분석 결과를 정확하게 표현하는데는 정확하지 않다고 한다. 본문 중 워드클라우드에 대한 구체적인 평가 내용은 아래와 같다.

워드 클라우드는 디자인이 아름다워서 자주 사용되지만 분석 결과를 정확하게 표현하는 데는 적합하지 않다. 단어 빈도를 크기와 색으로 표현하므로 ‘어떤 단어가 몇 번 사용 되었는지’ 정확히 알 수 없고, 단어 배치가 산만해서 ‘어떤 단어가 다른 단어보다 얼마나 더 많이 사용되었는지’ 비교가 어렵다. 텍스트를 아름답게 표현하는 게 아니라 분석 결과를 정확하게 표현하는 목적이라면 워드 클라우드보다는 막대 그래프를 이용하는게 좋다.

고객사에게 해석의 어려움에 대해 설명 드려도 일단 해달라는 요청도 많다. 그래서 어떻게든 조금의 해석이 이루어질 수 있도록 하려다보면 전처리에 굉장히 심혈을 기울여야 한다.

자연어처리 분야가 당연히 전처리를 꼼꼼히 잘 해야하는 것 아니냐”라고 묻는 분도 계시겠지만 워드클라우드를 일단 만들어 달라는 고객사의 요청에 부응하기 위해서는 인사이트의 실마리가 필요하다. 또 그 실마리를 찾기 위해서는 ‘진짜 정말로’ 전처리를 어떻게든 잘 해내야 한다.

그러다보니 데이터를 분석하면서 전처리에 평소보다 더 많은 시간을 할애하게 되었고 공부했다. 공부하며 얻게 된 지식의 일부를 명확하게 정리하고자 본 글을 쓰게 되었다.

(아 물론 이 글의 주제는 NLP의 매우 기본적인 내용이다)

주제는 Stemming & Lemmatization이다.

고객사에게 인사이트를 제공하기 위한 실마리를 찾는다는 것은 다른 말로 조사 참여자들의 응답 속에서 “의미를 찾는 것”이라 할 수 있다. 따라서 텍스트 안에서 객관적인 의미를 갖는 단어들을 추출해야 한다.

의미를 갖는 단어들을 최대한 일반화하여 요약하는 것을 정규화(normalization)라고 한다. 그 기법에는 크게 2가지가 있는데 그것이 바로 이 글의 주제인 Stemming(어간 추출)Lemmatization(표제어 추출)이다.

어간(stem)의 사전적 의미는 다음과 같다.

활용어가 활용할 때에 변하지 않는 부분. ‘보다’, ‘보니’, ‘보고’에서 ‘보-’와 ‘먹다’, ‘먹니’, ‘먹고’에서 ‘먹-’ 따위이다.

출처: 네이버 사전

자연어처리 분야의 관점에서 의미는 다음과 같다.

단어의 의미를 담고 있는 단어의 핵심 부분

출처: 딥러닝을 이용한 자연어 처리 입문

본 프로젝트는 통계 프로그램 R을 활용하였다. 어간 추출에 대한 정확한 개념을 모를 때는 구글링에서 얻은 코드를 막 썼다가 엉망진창인 워드클라우드가 나오기도 했다.

아래는 실제 사용했던 코드이다. 특정 목적에 따라 설계된 설문조사였기 때문에 각 문항을 별도로 분석해야했다. 따라서 1개의 문항을 별도의 객체로 생성하고 말뭉치(corpus)화 했다.

말뭉치란 “언어 연구를 위하여 컴퓨터가 텍스트를 가공·처리·분석할 수 있는 형태로 모아 놓은 자료의 집합”이다(한국민족문화대백과사전). R의 tm 라이브러리에서 문서를 관리하는 기본적인 단위이기도 하다.

Q1 <- df$Q101 #Q1문항을 별도의 객체로 생성
docs < - Corpus(VectorSource(text_101)) #말뭉치 생성

어간추출과 직접적으로 관련된 함수는 tm 라이브러리의 tm_map 함수이다. tm_map 함수를 사용하여 단어의 원형이 되는 어간을 추출하였다.

tm_map(처리하고자 하는 텍스트, 함수)

tm_map 함수 내 텍스트를 처리하는 함수는 ‘stemDocument’를 사용하였는데 이 함수는 Poter의 어간 추출 알고리즘을 사용한다. 해당 알고리즘의 자세한 내용은 아래 논문을 참고하길 바란다.

원문: Porter, Martin F. "An algorithm for suffix stripping." Program (1980).

toSpace <- content_transformer(function (x , pattern ) gsub(pattern, " ", x))

docs <- tm_map(docs, toSpace, "/")
docs <- tm_map(docs, toSpace, "@")
docs <- tm_map(docs, toSpace, "\\|")
docs <- tm_map(docs, toSpace, "-")
docs <- tm_map(docs, toSpace, ":")

#library(dplyr)
docs <- docs %>%
  tm_map(removeNumbers) %>% #숫자 제거
  tm_map(removePunctuation) %>% #구두점 제거
  tm_map(stripWhitespace) #공백 제거

docs <- tm_map(docs, content_transformer(tolower)) #소문자로 변환 -> 사전(Dictionary)의 내용과 대조 목적

docs <- tm_map(docs, removeWords, stopwords("english")) #띄어쓰기와 시제 제거

#원하는 단어 직접 제거
docs <- tm_map(docs, removeWords, c("직접 제거할 단어")) 

# Text stemming: 어간 추출 추출하는 작업
docs_101 <- tm_map(docs, stemDocument)
tm_map(docs, stemDocument)

#함수 정의
stemCompletion_mod <- function(x,dict=dictCorpus) {
  PlainTextDocument(stripWhitespace(paste(stemCompletion(unlist(strsplit(as.character(x)," ")),dictionary=dict, type="shortest"),sep="", collapse=" ")))
}

docs <- lapply(docs_101, stemCompletion_mod, dict=docs)
docs <- VCorpus(VectorSource(docs))

Term Document Matrix(이하 TDM)란 다수의 문서에 등장하는 각 단어들의 빈도를 행렬로 나타낸 것을 의미한다. TDM을 matrix로 변환한 뒤 모든 문서, 즉 모든 열에 카운팅된 각 키워드의 빈도를 합산하여 내림차순으로 정렬하였다. 이후 word와 freq로 명명된 2개의 변수가 있는 ‘df101’이라는 빈도 순위표를 생성하였다.

어간 추출을 했음에도 키워드들이 딱 떨어지지 않아 보다 더 명확하게 정규화하기 위해 어간추출로 나온 테이블에 ‘표제어추출’을 적용하였다.

표제어 추출은 R패키지 texstem의 lemmatize_word 함수를 사용하였다. dictionary 옵션에는 lexicon 외에도 많은 사전이 존재하니 도움말을 참고해보길 바란다.

dtm <- TermDocumentMatrix(docs) #TDM으로 변환
matrix <- as.matrix(dtm) #Matrix로 인식하도록 변환
words <- sort(rowSums(matrix),decreasing=TRUE) #1:n 열을 합산한 뒤 내림차순 정렬
df101 <- data.frame(word = names(words),freq=words) #변수명 설정

#표제어 추출
df101 <- df101 %>% 
  mutate(word101=lemmatize_words(df101$word, dictionary = lexicon::available_data())) %>%
  group_by(word=word101) %>%
  summarise(freq=sum(freq)) %>%
  arrange(-freq)

dtm$dimnames$Terms <-lemmatize_words(dtm$dimnames$Terms)

본 글에서는 어간추출, 표제어 추출의 개념을 간략하게 설명하고 바로 적용해볼 수 있는 코드를 소개하였다.

매우 기본적인 과정이지만 개념과 R코드를 일목요연하게 정리해놓은 글은 거의 보이지 않아 직접 글을 작성하게 되었다.

주관식 문항의 데이터를 처리 과정에서 고통 받는 리서치 연구원에게 1이라도 도움이 되길 희망한다.

profile
안녕하세요.

0개의 댓글