텍스트마이닝

토픽 모델링 #1 LDA

yul_S2 2022. 12. 20. 09:28
반응형

토픽 모델링 

텍스트의 핵심 주제를 찾아 비슷한 내용끼리 분류하는 분석 방법

다량의 텍스트를 분석할 때 유용

 

 (1) 토픽 모델을 이용하면

  • 단어가 어떤 토픽에 등장할 확률이 더 높은지 알 수 있다
  • 단어 등장 확률을 보고 토픽의 핵심 단어를 알 수 있다
  • 문서가 어떤 토픽에 등장할 확률이 높은지 알 수 있다
  • 확률을 이용해 문서를 토픽별로 분류할 수 있다 → 다량의 문서 분석할 때 특히 유용
  • 문서가 어떤 주제로 구성되는지 파악할 수 있다

 

 

 

 (2) LDA 모델 

LDA(Latent Dirichlet Allocation, 잠재 디리클레 할당)

가장 널리 사용되는 토픽 모델링 알고리즘

 

< LDA 모델의 가정 > 

  • 1. 토픽은 여러 단어의 혼합으로 구성된다

한 토픽에 여러 단어가 서로 다른 확률로 포함

같은 단어가 여러 토픽에 서로 다른 확률로 포함

 

  • 2. 문서는 토픽들의 혼합으로 구성된다

문서에는 여러 토픽의 단어가 서로 다른 비율로 들어 있음

단어 확률이 더 높은 쪽으로 문서 분류

 

 

 LDA 모델 만들기 

1. 기본적인 전처리

중복 문서 제거하기:

dplyr::distinct() 중복 문서가 있으면 계산량 늘어나 모델 만드는 시간 오래 걸림

한 토픽에 내용이 똑같은 문서가 여러 개 들어 있는 문제 생김

짧은 문서 제거하기:

토픽 모델은 여러 문서에 공통으로 사용된 단어를 이용해 만듦

짧은 문서는 다른 문서와 공통으로 사용된 단어가 적어 모델 만드는 데 적합하지 않음

 

library(multilinguer) 
library(KoNLP)
useNIADic()
library(readr)
library(dplyr)
library(stats)
library(stringr)
library(textclean)

 

 

# 1-1 데이터 불러오기

raw_news_comment <- read_csv("C:/news_comment_parasite.csv") %>%
  mutate(id = row_number())

row_number() : 각 행의 행번호. 각 행에 일련번호를 붙일 때 유용.

문서를 토픽별로 분류하는 작업을 할 때 문서 구분 기준이 필요하므로 댓글 고유 번호 부여

 

 

#1-2 기본 전처리

news_comment <- raw_news_comment %>%
  mutate(reply = str_replace_all(reply , "[^가-힣]"," "),
         reply = str_squish(reply)) %>%

 

#1-3 중복 댓글 제거

distinct(reply, .keep_all = T) %>%

distinct : 해당 데이터 프레임의 모든 열에서 동일 값을 갖는 행 데이터를 제거 

.keep_all = T :  다른 컬럼을 남긴다는 의미

 

 

#1-4 짧은 문서 제거

3단어 이상 추출

news_comment <- raw_news_comment %>%
  mutate(reply = str_replace_all(reply , "[^가-힣]"," "),
         reply = str_squish(reply)) %>%
  distinct(reply, .keep_all = T) %>%
  filter(str_count(reply, boundary("word") >= 3))

 

 

 

 

#2. 명사 추출하기

문서의 주제는 명사로 결정되므로 명사 추출해 모델 만드는 경우가 많음

댓글에 중복 사용된 단어 제거: 문서에 같은 단어 여러 번 사용되면 내용 관계없이 사용 빈도 때문 에 특정 토픽으로 분류될 가능성 높음

 

#2-1 명사추출

comment <- news_comment %>%
  unnest_tokens(input = reply,
                output = word,
                token = extractNoun,
                drop = F) %>%
  filter(str_count(word) > 1) %>%

 

 

#2-2 댓글 내 중복 제거

  group_by(id) %>%
  distinct(word, .keep_all = T) %>%
  ungroup() %>%
  select(id, word)
comment

# A tibble: 21,835 x 2 
# id word      
#    <int> <chr>     
# 1     1 우리      
# 2     1 행복      
# 3     2 시국      
# 4     2 감사      
# 5     2 진심      
# 6     3 우리나라  
# 7     3 영화감독  
# 8     3 영감      
# 9     3 봉감독님  
# 10     3 공동각본쓴
# # ... with 21,825 more rows
# # i Use `print(n = ...)` to see more rows

 

#3. 빈도 높은 단어 제거하기

빈도가 매우 높은 단어가 포함된 상태로 토픽 모델을 만들면 대부분의 토픽에 똑같은 단어가 주요 단어로 등장해 토픽의 특징을 파악하기 어려우므로 제거

 

count_word <- comment %>%
  add_count(word) %>%
  filter(n <= 200) %>%
  select(-n)

count_word
# # A tibble: 18,239 x 2
# id word      
# <int> <chr>     
#   1     1 우리      
# 2     1 행복      
# 3     2 시국      
# 4     2 감사      
# 5     2 진심      
# 6     3 우리나라  
# 7     3 영화감독  
# 8     3 영감      
# 9     3 봉감독님  
# 10     3 공동각본쓴
# # ... with 18,229 more rows
# # i Use `print(n = ...)` to see more rows

 

 

 

#4. 불용어 제거 & 유의어 처리

#4-1 불용어, 유의어 확인

불용어(Stop word): 단어 텍스트 해석에 도움이 되지 않으므로 제거해야 함

count_word %>%
  count(word, sort = T) %>%
  print(n = 200)

# A tibble: 6,179 x 2
# word             n
# <chr>        <int>
#   1 조국           186
# 2 블랙리스트     174
# 3 대박           170
# 4 한국           165
# 5 세계           142
# 6 수상           137
# 7 미국           128
# 8 들이           126
# 9 역사           106
# 10 정치           106
# # ... with 6,169 more rows

빈도 높은 단어 추출해 불용어 확인, 표현은 다르지만 의미가 비슷한 유의어가 있는지 확인

 

 

 

#4-2 불용어 목록 만들기

stopword <- c("들이", "하다", "하게", "하면", "해서", "이번", "하네",
              "해요", "이것", "니들", "하기", "하지", "한거", "해주",
              "그것", "어디", "여기", "까지", "이거", "하신", "만큼")

분석에서 제외할 단어 "들이" , "하다" , "하게" 처럼 의미를 알 수 없는 단어들

stop word에 불용어 넣기 

 

 

#4-3 불용어 제거하고 유의어 수정하기

count_word <- count_word %>%
  filter(!word %in% stopword) %>%
  mutate(word = recode(word,
                       "자랑스럽습니" = "자랑",
                       "자랑스럽" = "자랑",
                       "자한" = "자유한국당",
                       "문재" = "문재인",
                       "한국의" = "한국",
                       "그네" = "박근혜",
                       "추카" = "축하",
                       "정경" = "정경심",
                       "방탄" = "방탄소년단"))

dplyr::recode() : 특정 값을 다른 값으로 수정

!word %in% stopword :  word가 아닌것  stopword안에서

 

 

 

#5 DTM 만들기

DTM(Document-Term Matrix, 문서 단어 행렬): 행은 문서, 열은 단어로 구성해 빈도를 나타낸 행렬

 

#5-1 문서별 단어 빈도 구하기

count_word_doc <- count_word %>%
  count(id, word, sort = T)

count_word_doc

# A tibble: 17,785 x 3
# id word           n
#              
# 1    35  한국                2
# 2  1173 한국                2
# 3  1599 한국                2
# 4  1762 한국                2
# 5  2240 한국                2
# 6  2307 방탄소년단     2
# 7  2733 한국                2
# 8  2984 박근혜            2
# 9     1 우리                  1
# 10     1 행복                1
# # ... with 17,775 more rows
# # i Use `print(n = ...)` to see more rows

 

sort =T :  오름차순 정렬

sort =(decreasing = TRUE) : 내림차순 정렬

  cf)  sort()는 정렬된 값을 순서대로 보여주는 반면에, order()는 데이터 크기의 색인을 제공

 

#5-2 DTM 만들기

tidytext::cast_dtm()

document : 문서 구분 기준

term : 단어

value : 단어 빈도

library(tm)
library(tidytext)   # cast_dtm 함수 사용하기 위한 라이브러리


dtm_comment <- count_word_doc %>%
  cast_dtm(document = id, term = word, value = n)

dtm_comment

# <></documenttermmatrix (documents: 3563, terms: 6157)>
# Non-/sparse entries: 17785/21919606
# Sparsity           : 100%
# Maximal term length: 35
# Weighting          : term frequency (tf)

cast_dtm()

어떤 함수들은 tidy format이 아닌 dtm format을 input으로 필요로 하는 케이스가 있을 수도 있다.

tidytext 라이브러리에서는 tidy format을 dtm format으로 변환해주는 함수 

 

  cf)   tidy()

tidytext 라이브러리는 DTM 객체를 직접 사용할 수 없지만 이를 tidy data frame 형태로 변환해주는 함수를 제공 

 

 

 

#6. LDA

topicmodels::LDA()

k : 토픽 수

method : 샘플링 방법. // 일반적으로 깁스 샘플링( "Gibbs" ) 가장 많이 사용

control = list(seed =         )) : 난수 고정

 

# 6-1 토픽 모델 만들기

library(topicmodels)

lda_model <- LDA(dtm_comment,
                 k = 8,
                 method = "Gibbs",
                 control = list(seed = 1234))

토픽 수에는 정해진 정답이 없기 때문에 k값을 바꿔가며 여러 모델을 만든 다음 결과를 비교해 결정

 

 

 

# 6-2 모델 내용 확인

glimpse(lda_model)

## Formal class 'LDA_Gibbs' [package "topicmodels"] with 16 slots
## ..@ seedwords : NULL
## ..@ z : int [1:17604] 8 8 4 3 7 4 3 1 1 1 ...
## ..@ alpha : num 6.25
## ..@ call : language LDA(x = dtm_comment, k = 8, method = "Gibbs", control =
list(seed = 1234))
## ..@ Dim : int [1:2] 3563 6157
## ..@ control :Formal class 'LDA_Gibbscontrol' [package "topicmodels"] with 14
slots
## ..@ k : int 8
## ..@ terms : chr [1:6157] "한국" "자랑" "방탄소년단" "박근혜" ...
## ..@ documents : chr [1:3563] "35" "206" "566" "578" ...
## ..@ beta : num [1:8, 1:6157] -7.81 -10.22 -10.25 -5.83 -10.25 ...
## ..@ gamma : num [1:3563, 1:8] 0.151 0.15 0.11 0.114 0.11 ...
## ..@ wordassignments:List of 5
## .. ..$ i : int [1:17592] 1 1 1 1 1 1 1 1 1 1 ...
## .. ..$ j : int [1:17592] 1 98 99 100 101 102 103 104 105 106 ...
## .. ..$ v : num [1:17592] 8 4 3 7 4 3 7 2 8 6 ...
## .. ..$ nrow: int 3563
## .. ..$ ncol: int 6157
## .. ..- attr(*, "class")= chr "simple_triplet_matrix"
## ..@ loglikelihood : num -128790

@ beta : num [1:8, 1:6157] : 단어가 각 토픽에 등장할 확률/ 6157개 단어로 모델

@ gamma : num [1:3563, 1:8] :  문서가 각 토픽에 등장할 확률/ 3563개 문서로 모델 생성

 

 

 

 

반응형