Tokenization using tidytext

자, 이제는 “tidy” 데이터가 무엇인지, 그 원리를 살펴보고 이를 기반으로 한 어휘 빈도수 분석에 효율적인 함수를 제공하는 “tidytext” 패키지를 소개해드리도록 하겠습니다. 특히, 이 패키지의 unnest_tokens() 함수는 텍스트 전처리에 매우 편리한 기능을 제공합니다. 특히, 트윗 텍스트의 경우에는 unnest_tweets() 함수로 편리하게 토큰화 과정이 가능합니다.

자 우선, tidytext 패키지와 텍스트 전처리에 필요한 stringr 패키지를 R세션에 설치해 봅시다.

### Corpus
library(tidyverse)
## -- Attaching packages ---------------------------------------------------------------------------------------------- tidyverse 1.3.0 --
## √ ggplot2 3.2.1     √ purrr   0.3.3
## √ tibble  2.1.3     √ dplyr   0.8.5
## √ tidyr   1.0.2     √ stringr 1.4.0
## √ readr   1.3.1     √ forcats 0.5.0
## -- Conflicts ------------------------------------------------------------------------------------------------- tidyverse_conflicts() --
## x dplyr::filter() masks stats::filter()
## x dplyr::lag()    masks stats::lag()
library(stringr)
library(future.apply)
## Loading required package: future
plan(multicore)

load("covid_frames_tweets_sample.RData")

covid_frames_tweets
## # A tibble: 5,000 x 11
##    user_id status_id created_at          text  favorite_count retweet_count
##    <chr>   <chr>     <dttm>              <chr>          <int>         <int>
##  1 107920~ 12434792~ 2020-03-27 10:05:35 "요즘 ~              1             0
##  2 221873~ 12466013~ 2020-04-05 00:51:47 "정교모~              0             0
##  3 241668~ 12416792~ 2020-03-22 10:53:01 "신천지~              1             1
##  4 108008~ 12467265~ 2020-04-05 09:09:17 "평생 ~              0             0
##  5 111337~ 12448302~ 2020-03-31 03:34:05 "방탄소~              3             1
##  6 258016~ 12440690~ 2020-03-29 01:08:59 "#순천~              0             0
##  7 114023~ 12454298~ 2020-04-01 19:16:25 "@xo~              0             0
##  8 403278~ 12463508~ 2020-04-04 08:16:16 "잠시 ~              0             0
##  9 118952~ 12438192~ 2020-03-28 08:36:31 "@ja~              0             0
## 10 813409~ 12428154~ 2020-03-25 14:07:50 "코로나~              0             0
## # ... with 4,990 more rows, and 5 more variables: lang <chr>, date <dttm>,
## #   tweet <chr>, hashtag <chr>, nouns <chr>

앞서 우리는 텍스트 분석을 위한 전처리 과정을 거쳐왔습니다. 예를 들어, 문자열에서 HTML tags, URLs, 그리고 구두점, 숫자 등을 정규 표현을 이용해서 매칭하고 지워주거나 바꿔주는 전처리 작업을 했었죠. 그리고 이렇게 전처리 과정을 거친 텍스트를 단어 단위로 토큰화하기 위해 문자열을 쪼개는 작업을 해야 합니다.

tidytext 패키지의 unnest_tokens() 함수는 이 과정을 편리하게 수행할 수 있도록 해주는 기능을 수행합니다. apostrophe의 축약형을 제외한 텍스트의 구두점은 자동적으로 삭제하고, 빈칸을 기준으로 단어 단위로 텍스트를 쪼개서 그 결과값을 ‘tidy’ 데이터로 만들어 줍니다. 결국, 텍스트를 토큰 단위로 쪼개서 한 행에 하나의 토큰, 여기에선 한 단어씩 위치하게하는 데이터 프레임 형식으로 변환하는 기능을 수행하는 거죠.

library(tidytext)
tibble(text="텍스트 분석 난이도 초보자")
## # A tibble: 1 x 1
##   text                     
##   <chr>                    
## 1 텍스트 분석 난이도 초보자
unnest_tokens(tibble(text="텍스트 분석 난이도 초보자"), word, text)
## # A tibble: 4 x 1
##   word  
##   <chr> 
## 1 텍스트
## 2 분석  
## 3 난이도
## 4 초보자

tidy 데이터란 각 행에 하나의 토큰(여기에선 하나의 단어)만 위치하도록 구성된 테이블을 말합니다. 결국 tidy한 데이터를 만든다는 것은 토큰의 단위를 정하고 텍스트 데이터를 토큰화하여 각 행이 하나의 토큰으로 구성되게 만든다는 것이죠. 이렇게 tidy 데이터를 구성한다는 것은 각 행이 하나의 단어씩 가지고 있기 때문에 이후 기계 학습을 위한 텍스트 수치 변환 작업을 수행할 때 편리하게 연계 작업할 수 있다는 장점이 있습니다.

자, 다음의 R코드는 unnest_tokens() 함수를 이용해서 covid_frames_date라는 트윗 데이터를 토큰화한 과정을 보여주는데요.

covid_frames_tweets %>% arrange(created_at) # dplyr의 arrange() 함수는 해당 변수를 순차적으로 정렬 (작은 것부터); 시간의 경우 오래된 데이터부터 정렬
## # A tibble: 5,000 x 11
##    user_id status_id created_at          text  favorite_count retweet_count
##    <chr>   <chr>     <dttm>              <chr>          <int>         <int>
##  1 102014~ 12415455~ 2020-03-22 02:01:38 "대구와~              0             0
##  2 569707~ 12415475~ 2020-03-22 02:09:42 "교회도~              2             0
##  3 112777~ 12415630~ 2020-03-22 03:11:19 "신천지~              1             1
##  4 631022~ 12415789~ 2020-03-22 04:14:23 "신천지~              0             1
##  5 332861~ 12415887~ 2020-03-22 04:53:23 "신천지~              4             4
##  6 241044~ 12415931~ 2020-03-22 05:10:49 "신천지~              1             1
##  7 241044~ 12415934~ 2020-03-22 05:11:55 "신천지~              1             1
##  8 711645~ 12415941~ 2020-03-22 05:14:37 "<U+2757><U+FE0F>부~              1             3
##  9 115584~ 12416331~ 2020-03-22 07:49:46 "3월2~              0             0
## 10 812722~ 12416541~ 2020-03-22 09:13:11 "#우한~              0             0
## # ... with 4,990 more rows, and 5 more variables: lang <chr>, date <dttm>,
## #   tweet <chr>, hashtag <chr>, nouns <chr>
covid_frames_tweets %>% arrange(created_at) %>% unnest_tokens(word, nouns, token = "words") %>% slice(1:30)
## # A tibble: 30 x 11
##    user_id status_id created_at          text  favorite_count retweet_count
##    <chr>   <chr>     <dttm>              <chr>          <int>         <int>
##  1 102014~ 12415455~ 2020-03-22 02:01:38 대구와 ~              0             0
##  2 102014~ 12415455~ 2020-03-22 02:01:38 대구와 ~              0             0
##  3 102014~ 12415455~ 2020-03-22 02:01:38 대구와 ~              0             0
##  4 102014~ 12415455~ 2020-03-22 02:01:38 대구와 ~              0             0
##  5 102014~ 12415455~ 2020-03-22 02:01:38 대구와 ~              0             0
##  6 102014~ 12415455~ 2020-03-22 02:01:38 대구와 ~              0             0
##  7 102014~ 12415455~ 2020-03-22 02:01:38 대구와 ~              0             0
##  8 102014~ 12415455~ 2020-03-22 02:01:38 대구와 ~              0             0
##  9 102014~ 12415455~ 2020-03-22 02:01:38 대구와 ~              0             0
## 10 569707~ 12415475~ 2020-03-22 02:09:42 교회도,~              2             0
## # ... with 20 more rows, and 5 more variables: lang <chr>, date <dttm>,
## #   tweet <chr>, hashtag <chr>, word <chr>
covid_frames_tweets %>% arrange(created_at) %>% unnest_tokens(word, nouns, token = "tweets") %>% slice(1:30) # dplyr의 slice() 함수는 특정 범위의 행(들)만 추출해서 보여주는 기능을 함
## Using `to_lower = TRUE` with `token = 'tweets'` may not preserve URLs.
## # A tibble: 30 x 11
##    user_id status_id created_at          text  favorite_count retweet_count
##    <chr>   <chr>     <dttm>              <chr>          <int>         <int>
##  1 102014~ 12415455~ 2020-03-22 02:01:38 대구와 ~              0             0
##  2 102014~ 12415455~ 2020-03-22 02:01:38 대구와 ~              0             0
##  3 102014~ 12415455~ 2020-03-22 02:01:38 대구와 ~              0             0
##  4 102014~ 12415455~ 2020-03-22 02:01:38 대구와 ~              0             0
##  5 102014~ 12415455~ 2020-03-22 02:01:38 대구와 ~              0             0
##  6 102014~ 12415455~ 2020-03-22 02:01:38 대구와 ~              0             0
##  7 102014~ 12415455~ 2020-03-22 02:01:38 대구와 ~              0             0
##  8 102014~ 12415455~ 2020-03-22 02:01:38 대구와 ~              0             0
##  9 102014~ 12415455~ 2020-03-22 02:01:38 대구와 ~              0             0
## 10 569707~ 12415475~ 2020-03-22 02:09:42 교회도,~              2             0
## # ... with 20 more rows, and 5 more variables: lang <chr>, date <dttm>,
## #   tweet <chr>, hashtag <chr>, word <chr>
covid_frames_tweets %>% arrange(created_at) %>% unnest_tweets(word, nouns) %>% slice(1:30)
## Using `to_lower = TRUE` with `token = 'tweets'` may not preserve URLs.
## # A tibble: 30 x 11
##    user_id status_id created_at          text  favorite_count retweet_count
##    <chr>   <chr>     <dttm>              <chr>          <int>         <int>
##  1 102014~ 12415455~ 2020-03-22 02:01:38 대구와 ~              0             0
##  2 102014~ 12415455~ 2020-03-22 02:01:38 대구와 ~              0             0
##  3 102014~ 12415455~ 2020-03-22 02:01:38 대구와 ~              0             0
##  4 102014~ 12415455~ 2020-03-22 02:01:38 대구와 ~              0             0
##  5 102014~ 12415455~ 2020-03-22 02:01:38 대구와 ~              0             0
##  6 102014~ 12415455~ 2020-03-22 02:01:38 대구와 ~              0             0
##  7 102014~ 12415455~ 2020-03-22 02:01:38 대구와 ~              0             0
##  8 102014~ 12415455~ 2020-03-22 02:01:38 대구와 ~              0             0
##  9 102014~ 12415455~ 2020-03-22 02:01:38 대구와 ~              0             0
## 10 569707~ 12415475~ 2020-03-22 02:09:42 교회도,~              2             0
## # ... with 20 more rows, and 5 more variables: lang <chr>, date <dttm>,
## #   tweet <chr>, hashtag <chr>, word <chr>

이 코드는 covid_frames_tweets 데이터에 unnest_tokens()함수를 적용하여, nouns라는 변수의 명사 문자열을 단어 단위로 쪼개서 word라는 새로운 변수에 저장해주라는 명령을 수행합니다. 여기에서 토큰화 단위는 즉, 문자열을 쪼개고자 하는 기본 단위는 token이라는 인자를 “tweets”로 설정했죠. 이를 통해 단어 단위로 문자열을 토큰화하라는 설정이 취해진 것입니다.

unnest_tokens() 함수의 기능은 문자열을 단어 단위로 토큰화하라는 것 이외에 정규 표현식을 이용해서 텍스트를 토큰화하도록 할 수 있다는 점인데요. 따라서 텍스트를 쪼개는 토큰의 단위를 다양하게 설정할 수 있는 장점이 있습니다. 예를 들어서, 우리가 다루는 트윗 데이터에 경우, 텍스트에 우물 정자로 표시되는 해시태그나 골뱅이 모양으로 표시되는 트위터 핸들이 많이 포함되어 있는데요. 이러한 기호들은 트윗에서 그 자체로서 특별한 의미를 지니고 있기 때문에, 다른 구두점이나 기호들과는 차별해서 처리해 줘야 하는 경우가 있습니다. 따라서 해시태그와 트위터 핸들의 표시는 지우지 않는 토큰화 방법이 필요하고, 이 때 unnest_tweets()는 유용한 기능을 합니다.

자, 이러한 tidytext 방식의 토큰화를 거치면, 트윗 텍스트가 어휘 단위로 구분된 새로운 데이터를 얻게 되는데요. 이를 이용하면 어휘 빈도수 분석이 매우 용이합니다.

covid_word_count <- covid_frames_tweets %>% 
  unnest_tweets(word, nouns) %>% 
  count(word, sort = TRUE) 
## Using `to_lower = TRUE` with `token = 'tweets'` may not preserve URLs.
covid_word_count
## # A tibble: 8,115 x 2
##    word       n
##    <chr>  <int>
##  1 코로나  3467
##  2 문      2030
##  3 주세요  1976
##  4 알바    1975
##  5 만      1205
##  6 분      1090
##  7 시간    1073
##  8 저희    1006
##  9 남      1005
## 10 직원    1000
## # ... with 8,105 more rows
# covid_word_count는 데이터 프레임의 형식을 갖추고 있다. 변수는 두개. 관측값은 8,115. 즉, 트윗에서 나타나는 고유 단어의 수

covid_word_count %>% filter(str_length(word)>1)
## # A tibble: 7,375 x 2
##    word       n
##    <chr>  <int>
##  1 코로나  3467
##  2 주세요  1976
##  3 알바    1975
##  4 시간    1073
##  5 저희    1006
##  6 직원    1000
##  7 카톡     997
##  8 상대     996
##  9 비용     990
## 10 라인     987
## # ... with 7,365 more rows

dplyr의 count() 함수는 해당 변수 즉 벡터를 구성하는 값들의 빈도수를 계산합니다. sort 인자는 빈도수를 내림차순을 정렬할 수 있게 합니다.

Docuemnt-Term Matrix(DTM) or Docuemnt-Feature Matrix(DFM)

문서 분류 또는 군집화를 위한 기계학습 알고리즘은 행이 각 문서를 열은 각 특성(어휘)을 숫자로 표시하도록 구성된 2차원의 행렬에서 작동한다. 결국, 텍스트의 분류 또는 군집화를 위해서는 각 문서가 벡터로서 표현(representation)되도록 변환(transformation)하는 작업이 필요하고 우리는 이를 특성 추출(feature extraction) 또는 간략하게 벡터화(vectorization)라 부른다. 이 단계가 곧 텍스트 마이닝의 시작이다.

벡터? 모든 원소가 같은 데이터 속성을 갖는 1차원 데이터 구조

텍스트의 특성을 추출하는 작업은 대부분 문서(document) 단위로 이뤄진다. 각 문서의 내용(content)은 물론 길이, 저자, 출처, 게시 날짜 등의 메타 정보가 특성을 구성한다. 이러한 복수의 특성을 바탕으로 문서의 분류 및 군집화가 이뤄진다.

이러한 관점에서 언어는 일련의 어휘들이 조합이라기 보다는 고차원의 의미(semantic) 공간을 구성하는 접점들이다. 공간의 접점들은 촘촘하게 떼를 짓기도 띄엄띄엄 퍼져 있기도, 또는 일정하게 고루 분포되어 있을 수도 있다. 결국, 의미 공간에서 가깝게 표시되는 문서들은 비슷한 의미를 띄고 있고, 멀리 떨어져 위치하면 상당히 다른 의미를 보이는 것으로 이해될 수 있다. 의미 공간에 각 문서의 특성을 위치시키는 작업이 필요하고 이를 위해서는 의미 공간의 인코딩 작업이 필요하다.

의미 공간(semantic space)의 인코딩(부호화) 작업 중 가장 간단한 방법은 단어주머니(bag-of-words, BOW) 모델로서, 단어들에 의해 문서의 의미와 유사도가 측정될 수 있음을 보여준다. 즉, 비슷한 의미를 전단하는 문서들은 결국 비슷한 단어들로 구성되어 있따는 전제로서 의미 공간을 도출하는 것이다. 또한, 전혀 다른 주제(의미)를 표현하는 문서들과는 겹치는 단어들이 적을 것이다. 따라서 단어주머니 모델은 간단하지만 꽤 효과적인 의미 공간을 표현한다(=벡터화).

Words in Space

BOW 방식의 벡터화는 해당 코퍼스의 모든 문서에 등장하는 모든 단어들을 모아 리스트로 만들어 각 문서가 어떤 단어들로 구성되어 있는지 파악하여, 각 문서별로 -> 각 단어의 출현 빈도수를 측정하는 것. 그리고 벡터 인코딩(벡터화)은 frequency(BOW), one-hot, TF-IDF 및 숫자로 표현(distributed representation) 등의 방법으로 산출 가능하다.

Encoding documents as vectors

Encoding documents as vectors

그럼 각 벡터의 숫자는 어떻게 정해지는가?

  1. Frequency
  2. One-hot encoding
  3. TF-IDF
  4. Distributed representations

각 문서의 벡터화를 위해서는 다음의 텍스트 전처리 과정이 필요하다. 1) 문자 표준화 (대문자, 영어 vs 한국어) 2) 구두점 제거 3) Stemming 또는 Lemmatization (벡터 단순화) 4) 불용어(stopwords) 제거

대부분의 자연어처리 패키지들은 (NLTK, Scikit-Learn, Gensim) 전처리 작업에 필요한 함수와 사전을 제공함.

의미 공간 인코딩 작업을 차례대로 살펴보자

load("covid_frames_tweets_sample_420.RData")
library(quanteda)
## Package version: 2.0.1
## Parallel computing: 2 of 6 threads used.
## See https://quanteda.io for tutorials and examples.
## 
## Attaching package: 'quanteda'
## The following object is masked from 'package:utils':
## 
##     View
txt <- covid_frames_tweets_sample$nouns

head(txt)
##                                                                                                                                                                                               언제 뵐까 각만 재고 있습니다..(*약속한 건 절대 잊지 않음!) 코로나 땜에 옴싹달싹하다 이케 됐네요 ㅠㅁㅠ @binu_4_lvlz 
##                                                                                                                                                                                                                                                                                            "각 약속 건 코로나 땜" 
##         @bornfreeonekiss 재중씨는 일본 호우재해 뒤 자원봉사로 와륵이나 흙부대를 나르는 노동을 정말 더운 중에 해 주셨고 코로나에 관해서도,조심하세요!라고 항상 경고해 주시는 것을 알고 있어요.싸다니는 사람들에게 분개해서 만우절 임펙트를 사용하는 형이 되었지만 우리들을 걱정해 주시는 마음만이라고 알고 있어요. 
##                                                                                                                                                                                   "재중 씨 일본 호우 재해 뒤 자원 봉사 와륵 흙 부대 노동 중 코로나 조심 경고 것 사람 분개 만우절 임 펙 트 사용 형 우리 걱정 마음" 
##                                                                                                                                                                                                          귀여워. 코로나 얼릉 꺼지시게.\n니놈들도 꺼지라\n#punish_nthroom\n#arrest_nthroom https://t.co/xShoMuDuBq 
##                                                                                                                                                                                                                                                                                                     "코로나 니놈" 
##                                                                                                                                       싸강개같다진짜코로나이자식언제잠잠해지냐\n#SAYNOTO_NTHROOM\n1학기연장하자니까이놈의대학교는눈치보면서일주일씩찔끔찔끔늘리는거보소조나빡치네제발한번에좀늘려이망할ㄷㅅㅇ대야 
##                                                                                                                                                                                                                                         "강개 코 나이 자식 학기 연장 이놈 대학교 일 주일 거보 소조 번 이망 ㄷ ㅅ" 
## #세종 #대전\n저희  스마일 밤 출장 샵 에서\n남 녀 섹 알바 직원 구함니다\n외로운 분들 상대로 \n알바 비용은 2시간 80만\n카톡:wn115문의 주세요\n라인:sk12238문의 주세요\n세종 대전 #확진자 #만남 #미녀 #대출 \n#코로나  #조건 #비키니 \n#야동사이트 #아가씨\n#ㅇㅕ성흥분ㅈㅔ\n#ㅂㅣㅇㅏ그ㄹㅏ https://t.co/XdHtnsz1TU 
##                                                                                                                                                                                                "저희 스마일 밤 출장 샵 남 녀 섹 알바 직원 구함 분 상대 알바 비용 시간 만 카톡 문 주세요 라인 문 주세요 세종 대전" 
##                                                                                                                                                                                                                             와아 교수님도 학교 오는 거 싫으셨나 봐 코로나 덕분에 &lt; 이렇게 말하심ㅋㅋㅋㅋㅋㅋㅋ 
##                                                                                                                                                                                                                                                                                     "교수 학교 거 코로나 덕분 말"
names(txt) <- 1:5000

Frequency Vector

간단한 인코딩 방식. 코퍼스 단어 리스트 중 각 문서에 등장하는 단어들의 횟수를 표현.

Token frequency as vector encoding

Token frequency as vector encoding

mydfm <- dfm(txt)
mydfm
## Document-feature matrix of: 5,000 documents, 7,996 features (99.9% sparse).
##     features
## docs 각 약속 건 코로나 땜 재중 씨 일본 호우 재해
##    1  1    1  1      1  1    0  0    0    0    0
##    2  0    0  0      1  0    1  1    1    1    1
##    3  0    0  0      1  0    0  0    0    0    0
##    4  0    0  0      0  0    0  0    0    0    0
##    5  0    0  0      0  0    0  0    0    0    0
##    6  0    0  0      1  0    0  0    0    0    0
## [ reached max_ndoc ... 4,994 more documents, reached max_nfeat ... 7,986 more features ]

One-Hot Encoding

BOW 방식은 문법(단어 순서)을 무시하고 문서 내의 등장 횟수만 고려한다. 그러나 Frequency 인코딩은 분포의 불균형 문제를 야기함 (long tail, power law distribution, Zipf’s law). 왜? 자주 쓰는 소수의 단어군이 정해져 있음.

하지만 대부분의 통계 모형은 정규 분포를 요구함.

One-hot encoding 이 대안이 될 수 있음. 이 방식은 단어의 등장 횟수는 무시하고 등장 여부(presence or absence / 1 or 0)만 표시하는 방식.

One-hot encoding

One-hot encoding

dfm_weight(mydfm, scheme="boolean")
## Document-feature matrix of: 5,000 documents, 7,996 features (99.9% sparse).
##     features
## docs 각 약속 건 코로나 땜 재중 씨 일본 호우 재해
##    1  1    1  1      1  1    0  0    0    0    0
##    2  0    0  0      1  0    1  1    1    1    1
##    3  0    0  0      1  0    0  0    0    0    0
##    4  0    0  0      0  0    0  0    0    0    0
##    5  0    0  0      0  0    0  0    0    0    0
##    6  0    0  0      1  0    0  0    0    0    0
## [ reached max_ndoc ... 4,994 more documents, reached max_nfeat ... 7,986 more features ]
mydfm_boolean <- dfm_weight(mydfm, scheme="boolean")
mydfm_boolean[1:10,1:10]
## Document-feature matrix of: 10 documents, 10 features (83.0% sparse).
##     features
## docs 각 약속 건 코로나 땜 재중 씨 일본 호우 재해
##    1  1    1  1      1  1    0  0    0    0    0
##    2  0    0  0      1  0    1  1    1    1    1
##    3  0    0  0      1  0    0  0    0    0    0
##    4  0    0  0      0  0    0  0    0    0    0
##    5  0    0  0      0  0    0  0    0    0    0
##    6  0    0  0      1  0    0  0    0    0    0
## [ reached max_ndoc ... 4 more documents ]

One-hot encoding 은 문서 차원에서의 유사도를 측정하기에 유용한 방식. 하지만 단어 차원에서 유사도를 측정하기는 비효율적. 다시 말해, 아주 다른 의미 단어라도 문서에 자주 함께 등장하기만 한다면 가까운 의미로 해석될 여지.

Term Frequency-Inverse Document Frequency(TF-IDF)

BOW 방식의 벡터화는 코퍼스 맥락을 고려하는데 한계가 있음. 즉, 영화 리뷰 코퍼스라면 각 문서마다 “movie” “actor” “scene” “epic” 등의 단어들이 당연히 많이 등장할 개연성 높음. 그런데, 사실 이러한 단어들은 문서의 의미를 파악하는데 그리 큰 도움이 되지 않음. 오히려 잘 등장하지 않는 “good” “bad” 이런 단어가 리뷰의 의미를 포착하는데 유용함.

TF-IDF 방식은 이러한 문제의식에서 단어 빈도수에 가중치를 부여하여, 모든 문서에 자주 등장하는 단어들의 빈도수는 낮추고, 잘 등장하지 않는 단어의 빈도수는 높임.

TF-IDF encoding

TF-IDF encoding

mydfm
## Document-feature matrix of: 5,000 documents, 7,996 features (99.9% sparse).
##     features
## docs 각 약속 건 코로나 땜 재중 씨 일본 호우 재해
##    1  1    1  1      1  1    0  0    0    0    0
##    2  0    0  0      1  0    1  1    1    1    1
##    3  0    0  0      1  0    0  0    0    0    0
##    4  0    0  0      0  0    0  0    0    0    0
##    5  0    0  0      0  0    0  0    0    0    0
##    6  0    0  0      1  0    0  0    0    0    0
## [ reached max_ndoc ... 4,994 more documents, reached max_nfeat ... 7,986 more features ]
dfm_tfidf(mydfm)
## Document-feature matrix of: 5,000 documents, 7,996 features (99.9% sparse).
##     features
## docs       각     약속       건     코로나       땜 재중       씨     일본
##    1 3.221849 2.251812 1.598599 0.03914885 1.489455    0 0        0       
##    2 0        0        0        0.03914885 0           3 2.119186 1.801343
##    3 0        0        0        0.03914885 0           0 0        0       
##    4 0        0        0        0          0           0 0        0       
##    5 0        0        0        0          0           0 0        0       
##    6 0        0        0        0.03914885 0           0 0        0       
##     features
## docs    호우    재해
##    1 0       0      
##    2 3.69897 3.39794
##    3 0       0      
##    4 0       0      
##    5 0       0      
##    6 0       0      
## [ reached max_ndoc ... 4,994 more documents, reached max_nfeat ... 7,986 more features ]
mydfm_tfidf <- dfm_tfidf(mydfm)
mydfm_tfidf
## Document-feature matrix of: 5,000 documents, 7,996 features (99.9% sparse).
##     features
## docs       각     약속       건     코로나       땜 재중       씨     일본
##    1 3.221849 2.251812 1.598599 0.03914885 1.489455    0 0        0       
##    2 0        0        0        0.03914885 0           3 2.119186 1.801343
##    3 0        0        0        0.03914885 0           0 0        0       
##    4 0        0        0        0          0           0 0        0       
##    5 0        0        0        0          0           0 0        0       
##    6 0        0        0        0.03914885 0           0 0        0       
##     features
## docs    호우    재해
##    1 0       0      
##    2 3.69897 3.39794
##    3 0       0      
##    4 0       0      
##    5 0       0      
##    6 0       0      
## [ reached max_ndoc ... 4,994 more documents, reached max_nfeat ... 7,986 more features ]

즉, TF-IDF 방식은 각 문서에 등장하는 어휘 등장 횟수를 코퍼스 내 다른 문서들의 단어 구성을 고려하여 표시함. 결국, 문서 고유 의미를 파악하는 유용.

TF-IDF 계산법: https://www.tidytextmining.com/tfidf.html

TF-IDF의 장점 중 하나는, stopwords (불용어) 문제를 해결함. 매우 유용한 방식의 벡터 인코딩.

Distributed Representation

위의 세 방식은, 같은 단어를 포함하는 문서들의 유사도 정도를 측정하는데 유용한 벡터 인코딩 방식임. 근데, 만약에 비교하고자 하는 두 문서가 공유하는 단어가 하나도 없다면? 두 문서의 벡터는 0으로만 채워지고, 이런 경우 유사도 측정 불가.

이러한 문제점 때문에 Distributed representation 방식이 등장.

벡터의 원소(숫자)는 단어의 등장 여부 또는 횟수에 기초하는 방식이 아닌 코퍼스를 구성하는 단어들의 연관관계에 기초하여 각 문서를 의미 공간에 위치시키는 숫자들로 배열하는 방식. (복잡한 개념)

따라서 각 행은 문서를 나타내지만, 각 열은 단어가 아니라 특성(feature)을 나타내고 각 특성에 대해 문서들의 위치 정보가 표시된다. 그리고 이 특성 공간은 각 단어들의 위치를 나타냄으로서 단어들로 구성되는 문서의 유사 정도를 측정할 수 있게 한다.

이 특성 공간은 문서들의 단어 배열과 빈도수 정보를 바탕으로 기계 학습한 결과임.

Distributed representation

Distributed representation

Word2vec (구글의 Mikolov 개발)은 이러한 벡터화를 구현하는 word embedding 모델을 제시. 각 단어는 의미 공간에서의 위치를 가지고 이 위치는 그 단어가 어떠한 단어들과 가까이 혹은 멀리 쓰였는지를 통해 측정한다. (단어는 그 같은 맥락에서 또는 같은 문장에서 특정 단어들과 함께 자주 쓰인다면 비슷한 의미를 가지는 것으로 볼 수 있다)

Latent Semantic Analysis(LSA) 방식은 Word2Vec의 전신격. 단어들간의 복잡한 연관 관계를 정해진 차원으로 분해하여 보다 간단한 행렬 구조로 압축 변환하는 작업.

LSA는 정보 검색 시 사용자가 의미는 같지만 다른 단어를 사용해도 비슷한 검색 결과를 찾아주는 알고리즘 개발에 중요한 역할을 하고 있음.

주어진 키워드가 등장하지는 않지만 비슷한 의미를 표현하는 문서를 어떻게 찾아낼 수 있을까?
문서 매칭

문서 매칭

LSA 방식은 의미가 가까운 단어-문서, 단어-단어, 문서-문서를 찾을 수 있게 해줌. 어떻게? 단어들의 co-occurrence 패턴을 바탕으로.

‘타다’라는 동사가 ’커피’라는 명사와 등장할 때는 ’자동차’라는 명사와 쓰일 때 의미가 달라진다. ’중국집’이라는 단어는 ’메뉴’ 뿐만 아니라 ‘짜장면’ ‘짬뽕’ 등의 단어들과 자주 함께 등장하기 때문에 가까운 의미임을 유추할 수 있다.

이 유추를 위해서 LSA는 SVD(Sigular Value Decomposition)을 이용한다.

단어-문서 행렬 A

단어-문서 행렬 A

SVD는 단어-문서 행렬 A 를 아래 3개의 행렬로 분해(Decomposite)한다.

행렬 A의 SVD

행렬 A의 SVD

각 단어와 문서는 5차원 공간에서 위치 정보값으로 표현됨.

실제는 5차원이 아닌 수천 수만의 차원이 존재할 수 있음. 하지만, 대체적으로 100차원 정도로 고정함.

즉, 1) 100차원 공간에 몇천 몇만의 단어들을 의미 벡터로 위치 정보 값으로 포현하고, 2) 100차원 공간에 몇천 몇만의 문서를 의미 벡터로 위치 정보 값으로 표현하고, 3) 각 차원은 그 중요도를 표현.

그리고 LSA는 주요 정보를 제공하는 차원으로 축소한다. 가령 5차원에서 2차원으로.
SVD 차원 축소

SVD 차원 축소

각 행렬은 1. 단어-단어 간의 유사도를 차원 가중치가 부여된 행 값 간의 유사도로 계산하는데 사용 2. 문서-문서 유사도를 차원 가중치가 부여된 각 열 값들의 유사도로 계산

차원 X 문서 유사도 행렬

차원 X 문서 유사도 행렬

차원 가중치가 부여된 차원-문서 행렬 결과; 문서1과 문서2의 유사도는 행렬의 1번째 열과 1전째 열의 cosine 유사도를 구하면 됨. 모든 문서 쌍에 대한 유사도 계산 가능.

문서 X 문서 유사도 행렬

문서 X 문서 유사도 행렬

문서4-문서5가 가장 의미가 가까운 문서로, 문서3-문서6이 가장 먼 문서로 판명. 흥미롭게도, 문서2와 문서3은 공유하는 단어가 없음에도 높은 유사도를 보임. 단어 co-occurrence 패턴 정보와 차원 축소를 이용한 LSA 결과.

#install.packages("quanteda.textmodels")
library(quanteda.textmodels)
library(tidyverse)

?textmodel_lsa
## starting httpd help server ... done
mylsa <- textmodel_lsa(mydfm, nd=100)

mylsa$docs[1:10,1:10]
##            [,1]         [,2]          [,3]          [,4]         [,5]
## 1  0.0033154106  0.012350925 -0.0055143147  0.0073353714 -0.002392431
## 2  0.0044558327  0.016540237 -0.0034951057 -0.0037420541  0.051587836
## 3  0.0030958085  0.011542588 -0.0073724356  0.0068995686 -0.004565452
## 4  0.0004728437  0.001662984  0.0013225345 -0.0048790113  0.006616425
## 5  0.0637789071 -0.018696613 -0.0001262884  0.0002052393 -0.000410228
## 6  0.0039280515  0.014572602  0.0258495445  0.0086679179 -0.006569254
## 7  0.0032662651  0.012163869 -0.0073352366  0.0062903271 -0.002285266
## 8  0.0036180999  0.013442528 -0.0043333897  0.0048509950  0.000231532
## 9  0.0051705864  0.019086454  0.0010660825 -0.0028829918  0.037187291
## 10 0.0031622869  0.011793724 -0.0075203962  0.0073271540 -0.004216984
##             [,6]          [,7]          [,8]          [,9]         [,10]
## 1   0.0028220257 -0.0021439715 -0.0025496154 -0.0009845971  0.0003002921
## 2  -0.0081772275 -0.0102355613 -0.0209492640  0.0292697414 -0.0331789952
## 3   0.0043609806 -0.0037430837 -0.0006656441  0.0015037841  0.0019957680
## 4  -0.0053107301  0.0240965977  0.0123314230 -0.0162571841  0.0111479792
## 5  -0.0003010782 -0.0003355122 -0.0002532822  0.0002612241 -0.0003386141
## 6   0.0077036617 -0.0065605476  0.0004964865  0.0010546232  0.0021376266
## 7   0.0052206989 -0.0006567559  0.0011867893  0.0002319324 -0.0002456903
## 8   0.0018783040 -0.0004061834 -0.0028982260  0.0014062073 -0.0017932632
## 9  -0.0453314326  0.0750075826  0.0410325506 -0.0419810327 -0.0623238931
## 10  0.0045870959 -0.0035501626 -0.0012561115  0.0021905279  0.0025213678
library(lsa)
## Loading required package: SnowballC
lsa::cosine(mylsa$docs[1,],mylsa$docs[3,])
##           [,1]
## [1,] 0.1062292
lsa::cosine(mylsa$docs[1,],mylsa$docs[2,])
##             [,1]
## [1,] -0.07500481
cosine(mylsa$docs)[1:10,1:10]
##                [,1]          [,2]          [,3]          [,4]          [,5]
##  [1,]  1.000000e+00  9.070029e-17  1.347308e-16 -1.664928e-17 -8.278222e-17
##  [2,]  9.070029e-17  1.000000e+00 -7.928906e-17  8.093569e-17 -5.140474e-17
##  [3,]  1.347308e-16 -7.928906e-17  1.000000e+00 -1.480275e-17  2.689025e-16
##  [4,] -1.664928e-17  8.093569e-17 -1.480275e-17  1.000000e+00  9.114075e-17
##  [5,] -8.278222e-17 -5.140474e-17  2.689025e-16  9.114075e-17  1.000000e+00
##  [6,]  1.906620e-16 -1.017863e-16 -4.157543e-16  4.264099e-16 -4.042414e-16
##  [7,] -4.344263e-17 -6.997170e-17 -1.580936e-16  4.193152e-17  2.153022e-16
##  [8,]  6.360032e-17 -8.571973e-17  5.468445e-18  2.074451e-16 -3.887000e-16
##  [9,]  1.079967e-17 -9.042246e-17  1.519459e-16  2.143688e-16  6.128673e-16
## [10,] -1.166873e-17  6.700369e-17 -3.386709e-16 -3.962623e-16 -7.219431e-16
##                [,6]          [,7]          [,8]          [,9]         [,10]
##  [1,]  1.906620e-16 -4.344263e-17  6.360032e-17  1.079967e-17 -1.166873e-17
##  [2,] -1.017863e-16 -6.997170e-17 -8.571973e-17 -9.042246e-17  6.700369e-17
##  [3,] -4.157543e-16 -1.580936e-16  5.468445e-18  1.519459e-16 -3.386709e-16
##  [4,]  4.264099e-16  4.193152e-17  2.074451e-16  2.143688e-16 -3.962623e-16
##  [5,] -4.042414e-16  2.153022e-16 -3.887000e-16  6.128673e-16 -7.219431e-16
##  [6,]  1.000000e+00 -1.598487e-16 -5.713237e-17 -2.509809e-16 -7.924840e-17
##  [7,] -1.598487e-16  1.000000e+00 -7.654501e-16  2.880471e-16 -8.069175e-17
##  [8,] -5.713237e-17 -7.654501e-16  1.000000e+00  7.570645e-16 -2.570576e-16
##  [9,] -2.509809e-16  2.880471e-16  7.570645e-16  1.000000e+00  6.612380e-16
## [10,] -7.924840e-17 -8.069175e-17 -2.570576e-16  6.612380e-16  1.000000e+00
library(text2vec)
sim2(mylsa$docs, method="cosine", norm="l2")[1:10,1:10]
##               1            2           3            4            5            6
## 1   1.000000000 -0.075004811  0.10622923 -0.042269858  0.005045853 -0.069296435
## 2  -0.075004811  1.000000000 -0.08563110  0.006111831 -0.004560466 -0.046415136
## 3   0.106229230 -0.085631104  1.00000000 -0.059112863 -0.003799020  0.087444129
## 4  -0.042269858  0.006111831 -0.05911286  1.000000000  0.002851247 -0.031072200
## 5   0.005045853 -0.004560466 -0.00379902  0.002851247  1.000000000 -0.006506251
## 6  -0.069296435 -0.046415136  0.08744413 -0.031072200 -0.006506251  1.000000000
## 7  -0.020019016 -0.051826219  0.09526460 -0.014843984  0.011090349  0.025682275
## 8   0.006013154 -0.094632237  0.05646580 -0.057517218 -0.007039800 -0.042586667
## 9  -0.042393338 -0.021586837 -0.04918236  0.000953299 -0.013817059 -0.045534876
## 10  0.018276077 -0.031150729  0.34258680  0.063810896 -0.015906671  0.074918584
##               7            8            9          10
## 1  -0.020019016  0.006013154 -0.042393338  0.01827608
## 2  -0.051826219 -0.094632237 -0.021586837 -0.03115073
## 3   0.095264598  0.056465801 -0.049182365  0.34258680
## 4  -0.014843984 -0.057517218  0.000953299  0.06381090
## 5   0.011090349 -0.007039800 -0.013817059 -0.01590667
## 6   0.025682275 -0.042586667 -0.045534876  0.07491858
## 7   1.000000000  0.008281659 -0.021552508  0.08643167
## 8   0.008281659  1.000000000 -0.006796688 -0.02201814
## 9  -0.021552508 -0.006796688  1.000000000 -0.04891996
## 10  0.086431670 -0.022018138 -0.048919964  1.00000000
plot_words <- function(words, data){
  # empty plot
  plot(0, 0, xlim=c(-2.5, 2.5), ylim=c(-2.5,2.5), type="n",
       xlab="First dimension", ylab="Second dimension")
  for (word in words){
    # extract first two dimensions
    vector <- as.numeric(data[data$word==word, 2:3])
    # add to plot
    text(vector[1], vector[2], labels=word)
  }
}

covid_frames_tweets_sample %>% 
  sample_n(20) %>% 
  pull(nouns)
##  [1] "이부 프로펜 아스피린 류 진통제 코로나 악화 양성 검사 무증상 양성 머리 어제 잠 라떼 직 빵"              
##  [2] "나 코로나 직격탄 이상 방식 직 격타 트위터 얘기 곤란 설명 상황 나 이해 상황 이해 당사자 이해 거"        
##  [3] "회사 통근 버스 날 일 대중교통 출근 솔 코로나 회사 탓 마음"                                             
##  [4] "디 레터 포기 내 기지 디 어유 부끄부끄 쓰 느낌 눈물 코로나"                                             
##  [5] "긴급 코로나 법 추가 개정 필요 듯 법 시행 일자 처벌 수위 조정 필요 듯"                                  
##  [6] "저희 출장 샵 남 녀 섹 알바 직원 구함 분 상대 알바 비용 시간 만 카톡 문 주세요 라인 문 주세요 마산 울산"
##  [7] "해외 입국 사람 자 격리 일 기독교 예배 사람 문제 코로나 지속 타격 건 정규직 고용 형태 거 생각"          
##  [8] "김재중 코로나 바이러스 거짓말 비난 오버 금물"                                                          
##  [9] "완전 최고 당장 오프 노래"                                                                              
## [10] "코로나 땜 보드 등록 영주 시바 염병 개짱 나 스트레스"                                                   
## [11] "구 댕구 댕구 코로나"                                                                                   
## [12] "코로나 타 바이러스 중국 사망자 발생"                                                                   
## [13] "저희 출장 샵 남 녀 섹 알바 직원 구함 분 상대 알바 비용 시간 만 카톡 문 주세요 라인 문 주세요 안성 평택"
## [14] "저희 출장 샵 남 녀 섹 알바 직원 구함 분 상대 알바 비용 시간 만 카톡 문 주세요 라인 문 주세요 김포 고양"
## [15] "벗 꽃 만개 봄 옴 말 듯 코로나 꽃구경 줄 원진 씨 벗 꽃 꽃 표정 글 사진 선희"                            
## [16] "네 이번 타이밍 코 줄 일 건데 말"                                                                       
## [17] "챠 베이비 코로나 조심 이탈리아 사람 데 걱정 챠 라 곳 너 뮤"                                            
## [18] "나 적 첨 당황 나 병원 점심 통 ㅅ ㅂ 코로나 나 생각"                                                    
## [19] "코로나 바이러스"                                                                                       
## [20] "수출 코로나 진단 키트 이름 독도 대한민국 청와대"
plot_words(c("신종", "코로나", "바이러스"), mylsa$features %>% as_tibble() %>% mutate(word = rownames(mylsa$features)))
## Warning: `as_tibble.matrix()` requires a matrix with column names or a `.name_repair` argument. Using compatibility `.name_repair`.
## This warning is displayed once per session.

querydfm <- dfm(c("신종 코로나 바이러스")) %>%
    dfm_select(pattern = mydfm)
## Warning: pattern = dfm is deprecated; use dfm_match() instead
querydfm
## Document-feature matrix of: 1 document, 7,996 features (100.0% sparse).
##        features
## docs    각 약속 건 코로나 땜 재중 씨 일본 호우 재해
##   text1  0    0  0      1  0    0  0    0    0    0
## [ reached max_nfeat ... 7,986 more features ]
newq <- predict(mylsa, newdata = querydfm)
newq$docs_newspace
## 1 x 100 Matrix of class "dgeMatrix"
##              [,1]       [,2]         [,3]        [,4]         [,5]        [,6]
## text1 0.003193957 0.01189234 -0.007544839 0.008119712 -0.002370177 0.005515908
##               [,7]          [,8]        [,9]       [,10]        [,11]
## text1 -0.002492207 -0.0001500434 0.001848627 0.002637271 -0.002109913
##             [,12]       [,13]        [,14]      [,15]        [,16]        [,17]
## text1 0.002942615 -0.00243009 -0.001337883 -0.0045929 0.0005523338 -0.000972361
##              [,18]         [,19]         [,20]       [,21]         [,22]
## text1 0.0003967114 -0.0005061258 -0.0008126455 0.004917576 -0.0008891493
##               [,23]        [,24]        [,25]       [,26]        [,27]
## text1 -0.0006202815 0.0002723207 -0.008480857 0.003180756 -0.005555761
##             [,28]        [,29]      [,30]        [,31]       [,32]       [,33]
## text1 0.003171965 -0.002581977 0.01156452 -0.003326849 -0.02680253 -0.01111192
##             [,34]      [,35]       [,36]        [,37]       [,38]        [,39]
## text1 -0.01849034 0.02679452 -0.02611058 -0.003812199 -0.01698794 -0.002472685
##             [,40]      [,41]        [,42]     [,43]        [,44]      [,45]
## text1 -0.01382512 0.01910865 -0.002559583 -0.011537 -0.009619505 0.00101792
##            [,46]       [,47]        [,48]       [,49]        [,50]       [,51]
## text1 0.01819579 0.009507654 -0.007167466 -0.01576308 -0.007924756 0.003389031
##             [,52]       [,53]        [,54]        [,55]       [,56]      [,57]
## text1 0.003117049 0.004664057 -0.001503296 -0.009063957 0.006407607 0.00112477
##             [,58]        [,59]        [,60]       [,61]      [,62]       [,63]
## text1 0.003181614 -0.002738948 -0.003922563 0.004981721 0.01024722 0.006864028
##             [,64]        [,65]      [,66]        [,67]       [,68]        [,69]
## text1 0.002151453 -0.002357458 0.01080073 -0.004030827 0.008248725 0.0003810654
##              [,70]       [,71]         [,72]        [,73]         [,74]
## text1 -0.002792021 0.005633196 -0.0006662681 -0.004306621 -0.0003170858
##              [,75]        [,76]       [,77]        [,78]       [,79]
## text1 -0.006345991 6.416623e-05 -3.7451e-05 -0.002054528 0.001121889
##              [,80]        [,81]        [,82]      [,83]       [,84]       [,85]
## text1 -0.002547297 0.0008264431 -0.002175174 0.00105293 -0.00260712 0.003030696
##            [,86]       [,87]       [,88]       [,89]        [,90]        [,91]
## text1 0.00314178 0.004604757 0.001964524 0.002017858 -0.001229548 -0.006303859
##              [,92]       [,93]        [,94]        [,95]      [,96]
## text1 -0.001544348 0.001896988 -0.001585521 -0.002604684 0.00404328
##               [,97]        [,98]        [,99]        [,100]
## text1 -0.0005042013 0.0008909395 -0.003917177 -0.0008216151
class(mylsa$docs)
## [1] "matrix"
class(newq$docs_newspace)
## [1] "dgeMatrix"
## attr(,"package")
## [1] "Matrix"
mylsa$docs[1,]
##   [1]  0.0033154106  0.0123509253 -0.0055143147  0.0073353714 -0.0023924311
##   [6]  0.0028220257 -0.0021439715 -0.0025496154 -0.0009845971  0.0003002921
##  [11]  0.0017829391  0.0030180652 -0.0074160317 -0.0037204155 -0.0066391179
##  [16]  0.0024773329  0.0058606510 -0.0058519483 -0.0011568043  0.0024599803
##  [21] -0.0016343409 -0.0035566190 -0.0016891356  0.0193938419  0.0036059206
##  [26]  0.0059898108 -0.0176898323  0.0030402716  0.0142022670 -0.0143878188
##  [31] -0.0279234364 -0.0013859805  0.0296370240 -0.0337688270 -0.0378062571
##  [36]  0.0170661013 -0.0120352009  0.0219618015 -0.0265860191 -0.0147531169
##  [41]  0.0235395446  0.0186831054 -0.0193544474  0.0083001085  0.0143171362
##  [46]  0.0084201575  0.0187244213 -0.0007733815  0.0013113183  0.0100100256
##  [51]  0.0077178893  0.0011830207  0.0035267255 -0.0016860960 -0.0016318138
##  [56]  0.0035023928 -0.0146325768 -0.0037155382  0.0053195552  0.0009392436
##  [61]  0.0019453884 -0.0072755932  0.0094261614  0.0006612286 -0.0028445455
##  [66]  0.0155727693  0.0109204121  0.0023300517  0.0035154373  0.0034535095
##  [71]  0.0078810706 -0.0087654879 -0.0046971473 -0.0008276534  0.0021718152
##  [76]  0.0054735414  0.0033157847 -0.0057754084 -0.0004313924 -0.0071169824
##  [81] -0.0027852056 -0.0032516320 -0.0005020502  0.0001091970  0.0079596177
##  [86] -0.0013728855  0.0062244518 -0.0038614290 -0.0028121672 -0.0081432919
##  [91] -0.0032815578 -0.0021471201  0.0016032394  0.0109723017  0.0044700784
##  [96]  0.0029884554  0.0009359366 -0.0022789425  0.0022031357 -0.0026658370
newq$docs_newspace
## 1 x 100 Matrix of class "dgeMatrix"
##              [,1]       [,2]         [,3]        [,4]         [,5]        [,6]
## text1 0.003193957 0.01189234 -0.007544839 0.008119712 -0.002370177 0.005515908
##               [,7]          [,8]        [,9]       [,10]        [,11]
## text1 -0.002492207 -0.0001500434 0.001848627 0.002637271 -0.002109913
##             [,12]       [,13]        [,14]      [,15]        [,16]        [,17]
## text1 0.002942615 -0.00243009 -0.001337883 -0.0045929 0.0005523338 -0.000972361
##              [,18]         [,19]         [,20]       [,21]         [,22]
## text1 0.0003967114 -0.0005061258 -0.0008126455 0.004917576 -0.0008891493
##               [,23]        [,24]        [,25]       [,26]        [,27]
## text1 -0.0006202815 0.0002723207 -0.008480857 0.003180756 -0.005555761
##             [,28]        [,29]      [,30]        [,31]       [,32]       [,33]
## text1 0.003171965 -0.002581977 0.01156452 -0.003326849 -0.02680253 -0.01111192
##             [,34]      [,35]       [,36]        [,37]       [,38]        [,39]
## text1 -0.01849034 0.02679452 -0.02611058 -0.003812199 -0.01698794 -0.002472685
##             [,40]      [,41]        [,42]     [,43]        [,44]      [,45]
## text1 -0.01382512 0.01910865 -0.002559583 -0.011537 -0.009619505 0.00101792
##            [,46]       [,47]        [,48]       [,49]        [,50]       [,51]
## text1 0.01819579 0.009507654 -0.007167466 -0.01576308 -0.007924756 0.003389031
##             [,52]       [,53]        [,54]        [,55]       [,56]      [,57]
## text1 0.003117049 0.004664057 -0.001503296 -0.009063957 0.006407607 0.00112477
##             [,58]        [,59]        [,60]       [,61]      [,62]       [,63]
## text1 0.003181614 -0.002738948 -0.003922563 0.004981721 0.01024722 0.006864028
##             [,64]        [,65]      [,66]        [,67]       [,68]        [,69]
## text1 0.002151453 -0.002357458 0.01080073 -0.004030827 0.008248725 0.0003810654
##              [,70]       [,71]         [,72]        [,73]         [,74]
## text1 -0.002792021 0.005633196 -0.0006662681 -0.004306621 -0.0003170858
##              [,75]        [,76]       [,77]        [,78]       [,79]
## text1 -0.006345991 6.416623e-05 -3.7451e-05 -0.002054528 0.001121889
##              [,80]        [,81]        [,82]      [,83]       [,84]       [,85]
## text1 -0.002547297 0.0008264431 -0.002175174 0.00105293 -0.00260712 0.003030696
##            [,86]       [,87]       [,88]       [,89]        [,90]        [,91]
## text1 0.00314178 0.004604757 0.001964524 0.002017858 -0.001229548 -0.006303859
##              [,92]       [,93]        [,94]        [,95]      [,96]
## text1 -0.001544348 0.001896988 -0.001585521 -0.002604684 0.00404328
##               [,97]        [,98]        [,99]        [,100]
## text1 -0.0005042013 0.0008909395 -0.003917177 -0.0008216151
lsa::cosine(newq$docs_newspace[1,], mylsa$docs[1,])
##            [,1]
## [1,] 0.03091322

Text Normalization

SVD를 이용한 DTM의 Dimensionality 축소 방식 이전에, 간편하게 차원을 축소할 수 있는 방법? Text Normalization

  1. Stemming
  1. Simply remove word affixes (접사, 특히 suffixes, 접미사)

  2. Faster

  1. Lemmatization
  1. 원형 복원

  2. Slower, but effective

library(tidyverse)
library(tidytext)
library(textstem)
## Loading required package: koRpus.lang.en
## Loading required package: koRpus
## Loading required package: sylly
## For information on available language packages for 'koRpus', run
## 
##   available.koRpus.lang()
## 
## and see ?install.koRpus.lang()
## 
## Attaching package: 'koRpus'
## The following object is masked from 'package:lsa':
## 
##     query
## The following objects are masked from 'package:quanteda':
## 
##     tokens, types
## The following object is masked from 'package:readr':
## 
##     tokenize
stem_words(c("take","takes","took","taken","taking"))
## [1] "take"  "take"  "took"  "taken" "take"
lemmatize_words(c("take","takes","took","taken","taking"))
## [1] "take" "take" "take" "take" "take"
stop_words %>% filter(lexicon=="SMART")
## # A tibble: 571 x 2
##    word        lexicon
##    <chr>       <chr>  
##  1 a           SMART  
##  2 a's         SMART  
##  3 able        SMART  
##  4 about       SMART  
##  5 above       SMART  
##  6 according   SMART  
##  7 accordingly SMART  
##  8 across      SMART  
##  9 actually    SMART  
## 10 after       SMART  
## # ... with 561 more rows