計量政治学でユーザーの行動パターンを可視化せよ：NLPを超えた構造トピックモデルの活用

## はじめに

トピックモデルを抽象的に考えると、アイテムを分類するモデルであり、そのアイテムが単語であろうとユーザーが見た映画であろうと本質的な違いはない。トピックモデルと言ったら、普通の人はまず有名なLDA（Blei et al. 2003）を思いつくが、実は政治学では、このLDAの進化版にあたる構造トピックモデル（STM, structural topic model）が提案された。本記事ではまず、LDAとSTMの違いを紹介した上で、ユーザー行動の可視化（グルーピング、トピック分け）という、トピックモデルの元の目的ではない場面での活用法を説明する。

## モデル説明

LDAとSTMの違いはどこなのかというと、豊富なメタデータをモデルに入れることができるところが一番わかりやすい。STMでは、prevalence共変量（機械学習界隈の人にとって特徴量という表現の方がピンとくるかもしれない）とcontent共変量という二種類のメタデータを定義できる。

prevalence共変量とは、例えば子育て中の議員は保育園トピックを語りがちで、農家出身の議員は農業トピックを語りがちと言ったような、そもそもどのトピックが語られるかの判断材料として使う変数である。

これに対して、content共変量は、例えばA国の増税反対党と増税賛成党が税金トピックでそもそも利用する単語が違うときに、その違いを可視化する際に利用できる。例えば増税反対党の議員が税金トピックについて語る際に、「財産権」、「無駄」、「浪費」などを使うのに対して、増税賛成党は「公平性」、「建設」、「未来」などを使うのであろう。ここでcontent共変量を使わなければ、モデルが「増税反対党版の税金トピック」と「増税賛成党版の税金トピック」を分けて作ってしまう可能性があるが、content共変量を使ってモデルに二つの政党があるよと教えれば、増税反対党と増税賛成党の税金トピックが同じトピックに分類され、かつ二つの党が利用する単語の違いも可視化される。

私のビジネスとアカデミアでの実務経験でいうと、prevalence共変量を生かす場面は結構あるが、content共変量は活用できる場面があったら強い武器になれるが、登場する場面があまりないので、まず前者の概念をしっかり掴めた方がいいと思われる。

他にもトピック同士の相関が出せるといったLDAとの違いがあるが、詳細はRobertsらの論文（Roberts et al. 2016）やその中の参考文献を参照してください。

本記事では、ユーザーが閲覧した映画を文書に、映画名を単語にして、ユーザー行動のグルーピング（トピック分け）を行う。

コードは主に下記の解説論文を参照する：

https://cran.r-project.org/web/packages/stm/vignettes/stmVignette.pdf

## 分析

ここでは、STMでテキストデータを分析するという元の用途ではなく、ユーザーの行動解析での活用を説明する。実はLDAの論文（Blei et al. 2003）の中でも、トピックモデルをユーザーの行動解析に利用する事例が紹介されたので（p. 1014あたり）、新しいアイデアに見えるが、実はトピックモデルの初期から検討されている利用法といえよう。

まず、Rのrecommenderlabというパッケージ経由で、有名なmovielensというデータセットをダウンロードする。

https://grouplens.org/datasets/movielens/


```r
library(tidyverse)
library(recommenderlab)
library(quanteda)
library(stm)
data('MovieLense')

MovieLense_matrix <- as(MovieLense, "matrix")
```
ここで、movielensのデータを行列（行：ユーザー、列：映画）に変換したが、ユーザーが見た映画を単語に変換したほうがSTMに入れやすいので、変換を行う。

```r
#colnames(MovieLense_matrix)で映画名を取得し、間の空白を_に変換する
movie_names <- str_replace_all(colnames(MovieLense_matrix), " ", "_")

movie_sentence <- c()

for (i in 1:nrow(MovieLense_matrix)){
  #ユーザーが評価をつけた映画をそのユーザーが見た映画とする。ユーザーがつけた点数は考慮しない
  movie_sentence[i] <- str_c(movie_names[which(MovieLense_matrix[i,] > 0)], collapse = " ")
}
```
ここで中身を見てみよう。

```r
[1] "Toy_Story_(1995) Richard_III_(1995) Mighty_Aphrodite_(1995) Postino,_Il_(1994) Antonia's_Line_(1995) Birdcage,_The_(1996) Star_Wars_(1977) Fargo_(1996) Truth_About_Cats_&_Dogs,_The_(1996) Godfather,_The_(1972) Jerry_Maguire_(1996) Kolya_(1996) Shall_We_Dance?_(1996) My_Best_Friend's_Wedding_(1997) Men_in_Black_(1997) Contact_(1997) Full_Monty,_The_(1997) Good_Will_Hunting_(1997) Heat_(1995) Sabrina_(1995) Sense_and_Sensibility_(1995) Leaving_Las_Vegas_(1995) Restoration_(1995) Bed_of_Roses_(1996) Once_Upon_a_Time..._When_We_Were_Colored_(1995) Up_Close_and_Personal_(1996) River_Wild,_The_(1994) Time_to_Kill,_A_(1996) Emma_(1996) Tin_Cup_(1996) Secrets_&_Lies_(1996) English_Patient,_The_(1996) Marvin's_Room_(1996) Scream_(1996) Evita_(1996) Fierce_Creatures_(1997) Absolute_Power_(1997) Rosewood_(1997) Donnie_Brasco_(1997) Liar_Liar_(1997) Breakdown_(1997) Promesse,_La_(1996) Ulee's_Gold_(1997) Face/Off_(1997) Hoodlum_(1997) Air_Force_One_(1997) In_&_Out_(1997) L.A._Confidential_(1997) Fly_Away_Home_(1996) Ice_Storm,_The_(1997) Mrs._Brown_(Her_Majesty,_Mrs._Brown)_(1997) Devil's_Advocate,_The_(1997) FairyTale:_A_True_Story_(1997) Deceiver_(1997) Rainmaker,_The_(1997) Wings_of_the_Dove,_The_(1997) Midnight_in_the_Garden_of_Good_and_Evil_(1997) Titanic_(1997) 3_Ninjas:_High_Noon_At_Mega_Mountain_(1998) Apt_Pupil_(1998) As_Good_As_It_Gets_(1997)"
```

映画同士がスペース区切りになっていることが確認できたので、次はQuantedaを利用して文書行列を作ってからSTMに入れやすい形式に変換する。ここで重要なのは、トークン化を行う前に、phrase()という関数を一回噛ませることで、なぜかというと、phrase()を噛ませないと、tokens()関数が勝手にこちらがスペース区切りで綺麗に分割した単語（映画名）をもう一回分割するからである。

```r
#文書行列の作成
movie_dfm <- movie_sentence %>%
  phrase() %>%
  tokens() %>%
  dfm()

#STMのデータ形式の変換
dfm2stm <- convert(movie_dfm, to = "stm")
```
ここではmovie_dfmの中身を少し見てみよう。

```r
> movie_dfm
Document-feature matrix of: 943 documents, 1,664 features (93.67% sparse) and 0 docvars.
       features
docs    toy_story_(1995) goldeneye_(1995) four_rooms_(1995) get_shorty_(1995) copycat_(1995)
  text1                1                1                 1                 1              1
  text2                1                0                 0                 0              0
  text3                0                0                 0                 0              0
  text4                0                0                 0                 0              0
  text5                1                1                 0                 0              0
  text6                1                0                 0                 0              0
       features
docs    shanghai_triad_(yao_a_yao_yao_dao_waipo_qiao)_(1995) twelve_monkeys_(1995) babe_(1995)
  text1                                                    1                     1           1
  text2                                                    0                     0           0
  text3                                                    0                     0           0
  text4                                                    0                     0           0
  text5                                                    0                     0           0
  text6                                                    0                     1           1
       features
docs    dead_man_walking_(1995) richard_iii_(1995)
  text1                       1                  1
  text2                       0                  1
  text3                       0                  0
  text4                       0                  0
  text5                       0                  0
  text6                       1                  0
[ reached max_ndoc ... 937 more documents, reached max_nfeat ... 1,654 more features ]
```
予想通りの結果になっているので次はSTMに入れるステップに入るが、STMはトピックモデルなのでまずトピック数を決めないといけない。ここで、STMのパッケージの中で用意されたsearchKという便利な関数を利用してトピック数を決める。上述のprevalence共変量として、ユーザー年齢と性別を入れた。これは要するに、年齢と性別はユーザーがどんなトピック（グルーピング）の映画を見るかに影響すると仮定する意味である。

```r
m_search_k <- searchK(dfm2stm$documents, dfm2stm$vocab, K = c(5,10,15,20,25),
                      prevalence = ~ s(age) + as.factor(sex), 
                      data = MovieLenseUser)
plot(m_search_K)
```
![searchK.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2257087/745445d1-63be-6fcc-e5f6-daaf1bc30ec8.png)
searchKの結果を確認すると、トピック数（K）が15の時、検証データに対する尤度（Held-Out Likelihood）が最大化され、残差（Residuals）が最小化されているように見えるので、トピック数は一旦15にする。

次に、トピック数15のSTMモデルを推定する。

```r
m_stm <- stm(dfm2stm$documents, dfm2stm$vocab, K = 15,
  prevalence = ~ s(age) + as.factor(sex), 
  data = MovieLenseUser)

plot(m_stm)
```
![plot_m_stm.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2257087/7dc79562-a59c-f3e1-9c1d-90d8580267fd.png)

STMモデルの可視化から、各トピックの代表的な映画が出てくる。

これだけ見ても正直なかなか示唆が得られないので、前述したSTMとLDAの違いの一つであるトピック同士の相関を出してみよう。

```r
plot(topicCorr(m_stm))
```
![topiccor.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2257087/4e372466-5248-a5c5-fdc8-3fc3be39f1dd.png)

ここで注目してほしいのは、トピック10とトピック8が他のトピックから孤立している点である。トピック10とトピック8にどんな映画があるのかというと、エアフォース・ワン（トピック8）、タイタニック（トピック10）グッド・ウィル・ハンティング/旅立ち（トピック10）など、なかなか有名な映画が多いので、トピック10とトピック8のユーザーはあまり有名でない映画を見ない仮説が出てくる。

次に、年齢とトピックの関連性を可視化するが、全てのトピックを出すと長くなるため、面白い傾向が見られたトピック14に絞って解釈を行う。

```r
prep <- estimateEffect(1:15 ~ s(age) + as.factor(sex), m_stm,
                      meta = MovieLenseUser, uncertainty = "Global")

#日本語の文字化け対策
par(family= "HiraKakuProN-W3")
plot(prep, "age", method = "continuous", topics = 14,
     xlab = "年齢")
```
![topic14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2257087/5a0bab3a-94b9-7cd7-60e3-3655b9ebe217.png)

この結果からわかるのは、20代から30代がトピック14の映画を好む傾向があるということだが、そもそもトピック14に何が入っているのかというと、レザボア・ドッグスやユージュアル・サスペクツ、トレインスポッティングなどのサスペンスや刺激的な内容の映画である。

ここで仮説として、20代から30代の年齢層のユーザーがこのような刺激性の強い映画を嗜好することが言えて、追加検証などで似たような結果が得られたら、マーケティング戦略に活かせる。

## 結論

モデルの推定結果を完全に網羅するととんでもない分量になるため、本記事は、STMモデルから得られたユーザーの行動に関する示唆を完全に抽出して解釈を加えたわけではない。ただ、上述した簡単な内容からもわかるように、STMなどのトピックモデルは解釈可能性が豊富な仮説群を抽出でき、これがまさにGrimmerらが最近出した本（Grimmer et al. 2022）のp. 151あたりで述べたように、一つのトピックモデルの結果が一連の研究につながる可能性がある。また、政治学では一つのトピックモデルの深掘りが一冊の専門書になったケースもある（Catalinac 2016）ので、ぜひトピックモデル、特にLDAの進化版のSTMをビジネスとアカデミアでどんどん活用してください。

## 参考文献
Blei, David M., Andrew Y. Ng, and Michael I. Jordan. "Latent dirichlet allocation." Journal of Machine Learning Research 3.Jan (2003): 993-1022.

Catalinac, Amy. Electoral reform and national security in Japan: From pork to foreign policy. Cambridge University Press, 2016.

Grimmer, Justin, Margaret E. Roberts, and Brandon M. Stewart. Text as data: A new framework for machine learning and the social sciences. Princeton University Press, 2022.

Roberts, Margaret E., Brandon M. Stewart, and Edoardo M. Airoldi. "A model of text for experimentation in the social sciences." Journal of the American Statistical Association 111.515 (2016): 988-1003.