ノイズ有り対応トピックモデルの実装

# 本記事を読むための事前知識
 本記事では通常のトピックモデルについての説明は省きます.
通常のトピックモデルについては,Qiitaでは以下の記事が理解しやすいと思います.
 [数式をなるべく使わずにトピックモデルの解説にチャレンジ](http://qiita.com/GushiSnow/items/8156d440540b0a11dfe6)

実装まで挑戦したいという方には,以下のブログがとても参考になります(私も参考にさせて頂きました).
 [Latent Dirichlet Allocations の Python 実装 (Mi manca qualche giovedi`?)](http://d.hatena.ne.jp/n_shuyo/20110214/lda)

# ノイズ有り対応トピックモデル
 トピックモデルの拡張として,補助情報についてもトピックを区分する[対応トピックモデル](https://www.google.co.jp/url?sa=t&rct=j&q=&esrc=s&source=web&cd=1&cad=rja&uact=8&ved=0ahUKEwj_6LSQlOvNAhVPw2MKHYpwAO8QFggmMAA&url=http%3A%2F%2Fwww.cs.columbia.edu%2F~blei%2Fpapers%2FBleiJordan2003.pdf&usg=AFQjCNGsM3SjXGrj6_OVEh73bFp_U3swRw)が2003年に発表されました.
補助情報とは,記事のタグや商品のカテゴリーなど,文書の内容に対して補助的に与えられる情報のことです.
 対応トピックモデルでは,全ての補助情報がトピックに割り当てられますが,文書の内容と関係しない一般補助情報も同様にトピックが割り当てられます.一般補助情報とは,例えばSNSでは「いいね」や「後で読む」,Amazonでは「☆☆☆☆☆」(評価点)といった記事や商品の内容とは関係しない語彙のことです.一般補助情報をノイズとして,トピックとは別に区分するトピックモデルを[ノイズ有り対応トピックモデル](http://ieeexplore.ieee.org/xpl/login.jsp?tp=&arnumber=6193102&url=http%3A%2F%2Fieeexplore.ieee.org%2Fiel5%2F69%2F6517838%2F06193102.pdf%3Farnumber%3D6193102)といいます[^1]. 
[^1]: 正確には,ノイズ有り対応トピックモデルは,対応トピックモデルの拡張です.

### 生成過程
 ノイズ有り対応トピックモデルの生成過程では，通常のトピックモデルに補助情報分布，トピック毎の補助情報の生成過程が加えています．
**トピック毎の補助情報分布は，文書$d$内の単語$m$を生成したトピック$k$の割合($N_{dk}/N_{d}$)に応じて割り当てられ，また，補助情報と一般補助情報を区別する二値の潜在変数($r_{dm} ∈\\{0,1\\}$)によって，補助情報が補助情報分布から生成されるのか($r_{dm} = 1$のとき)，一般補助情報分布から生成されるのか($r_{dm} = 0$のとき)**を決定することが，ノイズ有り対応トピックモデルの特徴です．

以下の観測変数$\\{(w_{d},t_{d})\\}^{D}_{d=1}$の生成過程によって,モデルを生成します.

> 1\. 関係性分布を生成 $λ$ ~ $Beta(η)$

> 2\. 一般補助情報分布を生成 $Ψ_{0}$~$Dirichlet(γ)$

> 3\. For トピック$k=1,...,K$

>> (a) 単語分布を生成 $φ_{k}$~$Dirichlet(β)$ 

>> (b) 補助情報分布を生成 $Ψ_{k}$~$Dirichlet(γ)$ 

> 4\. For 文書$d=1,...,D$

>> (a) 単語トピック分布を生成 $Θ_{d}$~$Dirichlet(α)$

>> (b) For 単語数 $n=1,...,N_{d}$

>>> i. トピック$z_{dn}$~$Categorical(Θ_{d})$

>>> ii. 単語$w_{dn}$~$Categorical(φ_{zdn})$

>> (c) For 補助情報数 $m=1,...,M_{d}$

>>> i. トピック $c_{dm}$~$Categorical( \\{ \frac{N_{kd}}{N_{d}} \\}^{K}_{k=1})$

>>> ii. 関係性 $r_{dm}$~$Bernoulli(λ)$

>>> iii. 補助情報 $t_{dm}$

>>> $\space Categorical(Ψ_{0})\space$if $rdm = 0$

>>> $\space Categorical(Ψ_{cdm})\space$otherwise

### グラフィカルモデル表現
ノイズ有り対応トピックモデルのグラフィカルモデル表現は以下の図で示されます.補助情報ではトピック数(K) + 1 回の繰り返しによって,補助情報のトピックと一般補助情報を区分します.

![graphical_model.jpg](https://qiita-image-store.s3.amazonaws.com/0/63906/4585d3cd-ddc2-9f57-8b79-35ad6c2301ea.jpeg)

| 記号 | 説明 | 
|:-----:|:-----:|
|$w$| 単語(観測変数) |
|$t$| 補助情報(観測変数) |
|$z$| 単語トピック |
|$θ$| 単語トピック分布 |
|$Φ$| 単語分布 |
|$c$| 補助情報トピック |
|$ψ$| 補助情報分布 |
|$r$| 関係性 |
|$λ$| 関係性分布 |
|$α$| $θ$を生成するディリクレ分布のパラメータ |
|$β$| $Φ$を生成するディリクレ分布のパラメータ |
|$γ$| $ψ$を生成するディリクレ分布のパラメータ |
|$η$| $λ$を生成するベータ分布のパラメータ |
|$D$| 文書数 |
|$N$| ある文書の中で現れる単語数 |
|$M$| ある文書の中で現れる補助情報数 |
|$K$| トピック数 |
|$V$| 全文書の中で現れる単語の種類数(語彙数) |

### サンプリング確率
グラフィカルモデル表現で示される，未知変数$z$，$c$，$r$の集合$Z$，$C$，$R=(r_{11},...,r_{DN_{d}})$のサンプリング確率は，崩壊型ギプスサンプリングによって以下の式で示されます($r_{DN_{d}}$は文書$D$にある$N_{d}$番目の単語の関係性)．

```math
p(z_{dn}=k|W,X,Z_{\dn},C,R,α,β,γ,η)
∝(N_{dk\dn}+α)\frac{N_{kw_{dn}\dn}+β}{N_{k\dn}+βV} \left( \frac{N_{dk\dn}+1}{N_{dk\dn}} \right)^{M_{dk}}
```
```math
p(c_{dm}=k|W,X,Z,C_{\dm},R,α,β,γ,η)
∝N_{dk}\frac{M_{kx_{dm}\dm}+γ} {M_{k\dm}+γS}
```
```math
p(r_{dm}=0|W,X,Z,C,R_{\dm},α,β,γ,η)
∝(M_{0\dm}+η)\frac{M_{0x_{dm}\dm}+γ} {M_{0\dm}+γS}
```
```math
p(r_{dm}=1|W,X,Z,C,R_{\dm},α,β,γ,η)
∝(M_{1\dm}+η)\frac{M_{c_{dm}x_{dm}\dm}+γ} {M_{c_{dm}\dm}+γS}
```
| 記号 | 説明 | 
|:-----:|:-----:|
|$N_{dk}$|文書$d$でトピック$k$が割り当てられた単語数|
|$N_{kv}$|文書集合全体で語彙$v$にトピック$k$が割り当てられた単語数|
|$N_{k}$|文書集合全体でトピック$k$が割り当てられた単語数|
|$M_{0}$($M_{1}$)|内容と関係ない(ある)補助情報の数|
|$M_{0s}$|内容と関係ない補助情報$s$の数|
|$M_{dk}$|文書$d$でトピック$k$が割り当てられた補助情報の数|
|$M_{ks}$|内容と関係があり，かつトピック$k$が割り当てられた補助情報$s$の数|
|$M_{k}$|文書集合全体でトピック$k$が割り当てられた補助情報の数|
※\dn：文書$d$の$n$番目の単語を除いた時の単語数を指します．例えば，$N_{dk\dn}$は$n$番目の単語を除いた時の文書$d$でトピック$k$が割り当てられた単語数となります．
※\dm：文書$d$の$m$番目の補助情報を除いた時の補助情報の数を指します．

# データセットについて
今回，Qiitaの最新記事9999本(2016/7/17時点)から抽出した単語と，補助情報にタグを使用したデータセットを作成しました．

Qiitaの記事とタグ取得については，以下の記事を参考にさせて頂きました．
[Qiitaの投稿をガーッと取得するバッチ処理用QiitaAPI Pythonラッパー](http://qiita.com/tag1216/items/7e23630d97293e35ea4c)

MeCabを使用した形態素解析では，以下の記事を参考にさせて頂きました．
[PythonでWebスクレイピングと形態素解析。](http://fixxman.hatenablog.com/entry/2015/11/27/035847)

形態素解析の結果より，1本の記事の中で頻出回数の多い上位30位の単語を使用します．

今回のデータセットはソースコードと共に[GitHub](https://github.com/yh0sh/CorrLDA)にアップロードしています[^2] [^3]．


[^2]: データセットのファイル構成は[UCI Machine Learning Repository: Bag of Words Data Set](http://archive.ics.uci.edu/ml/datasets/Bag+of+Words)に準じています．

[^3]: 驚くほど拙い英文のREADMEが付いていますので失笑しながらご覧ください．．．

# 実装
 Python 2.7を使用して,ノイズ有り対応トピックモデルを実装しました.モジュールはNumpyのみで行えます.
解法アルゴリズムとしては,トピックモデルの解法ではお馴染みのGibbs Samplingを用います.
なお，Numpyのみでは，配列要素探索に時間がかかりすぎてしまうため，Cython+Numpyを用いてチューニングしています．

```python:corr_lda.pyx
# distutils: language=c++
# -*- coding: utf-8 -*-

import os
import math
from scipy import special 
from d_profile import profile

import numpy as np
cimport numpy as np
from libcpp.vector cimport vector

DTYPE = np.int
FTYPE = np.float
ctypedef np.int_t DTYPE_t
ctypedef np.float_t FTYPE_t

cdef class CorrLDA:
  # MAIN VARIABLES
  cdef int K, D, V
  cdef double alpha, beta
  cdef np.ndarray Nk
  cdef np.ndarray Ndk
  cdef np.ndarray Nkv
  cdef np.ndarray Nd
  cdef np.ndarray zdn # topic sequence for word n in document d
  cdef np.ndarray vdn # vocaburary sequence for word n in document d
  # SUPPLY INFORMATION VARIABLES
  cdef int S
  cdef double M0, M1
  cdef double gamma, eta, lam
  cdef np.ndarray Mk
  cdef np.ndarray Mdk
  cdef np.ndarray M0s
  cdef np.ndarray Mks
  cdef np.ndarray Md
  cdef np.ndarray xdm # topic sequence for supply m in document d
  cdef np.ndarray sdm # vocaburary sequence for supply m in document d
  cdef np.ndarray rdm # relevance sequence for supply m in document d

  def __init__(self, K, alpha, beta, eta, lam):
    self.K = K
    self.alpha = alpha
    self.beta = beta
    self.eta = eta
    self.lam = lam
    self.gamma = np.random.beta(self.lam, self.lam)

  # make document dictionary with a key of document id
  def make_dict(self, vfile):
    vdict = {}
    fvoc = open(vfile)
    for num,line in enumerate(fvoc):
      vdict[num] = line[:-1]
    fvoc.close()
    return vdict

  # count up Nk, Ndk, Nkv, and Nd
  def set_corpus(self, dfile, prof = False):
    self.Nk = np.zeros(self.K,dtype=FTYPE)
    fdoc = open(dfile)
    pre_doc = 0
    cdef vector[int] zdn
    cdef vector[int] vdn
    cdef int count
    for num, line in enumerate(fdoc):
      if num == 0:
        self.D = int(line[:-1])
        self.Ndk = np.zeros([self.D, self.K],dtype=FTYPE)
        self.Nd = np.zeros(self.D,dtype=DTYPE)
        continue
      elif num == 1:
        self.V = int(line[:-1])
        self.Nkv = np.zeros([self.K, self.V],dtype=FTYPE)
        continue
      elif num == 2:
        continue
      else:
        d, v, c = line[:-1].split(' ')
        k = np.random.randint(self.K,size=1)[0]
        doc = int(d) - 1
        voc = int(v) - 1
        count = int(c)
        self.Ndk[doc, k] += count
        self.Nkv[k, voc] += count
        self.Nk[k] += count
        self.Nd[doc] += count
        for c in range(count):
          vdn.push_back(voc)
          zdn.push_back(k)
        if pre_doc != doc and prof:
          print pre_doc+1, self.Ndk[pre_doc], self.Nd[pre_doc]
          pre_doc = doc
    if prof:
      print pre_doc+1, self.Ndk[pre_doc], self.Nd[pre_doc]
    fdoc.close()
    self.Nk += self.beta * self.V
    #self.Ndk += self.alpha
    self.Nkv += self.beta
    self.zdn = np.array(list(zdn))
    self.vdn = np.array(list(vdn))
    return 0

  # count up M0, M1, Mk, Mdk, M0s, Mks, and Md
  def set_supply(self, dfile, prof = False):
    self.M0 = 0
    self.M1 = 0
    self.Mk = np.zeros(self.K + 1,dtype=FTYPE) # K + 1 includes the index of noisy topic
    self.Mdk = np.zeros([self.D, self.K],dtype=FTYPE)
    self.Md = np.zeros(self.D,dtype=DTYPE) # for numbering iteration only, not using calculation
    fdoc = open(dfile)
    pre_doc = 0
    cdef vector[int] xdm
    cdef vector[int] sdm
    cdef vector[int] rdm
    cdef int count, num_noisy
    for num, line in enumerate(fdoc):
      if num == 0 or num == 2:
        continue
      elif num == 1:
        self.S = int(line[:-1])
        self.Mks = np.zeros([self.K, self.S],dtype=FTYPE)
        self.M0s = np.zeros(self.S,dtype=FTYPE)
        continue
      else:
        r = np.random.binomial(1, self.gamma) # setting relevance randomly
        d, s, c = line[:-1].split(' ')
        doc = int(d) - 1
        supply = int(s) - 1
        count = int(c)
        if r == 1: # relevance
          k = np.random.multinomial(1,self.Ndk[doc] / self.Ndk[doc].sum()).argmax()
          self.Mdk[doc, k] += count
          self.Mks[k, supply] += count
          self.M1 += count
        else: # irrelevance
          k = self.K  
          self.M0s[supply] += count
          self.M0 += count
          num_noisy += count
        self.Mk[k] += count
        self.Md[doc] += count
        for c in range(count):
          sdm.push_back(supply)
          xdm.push_back(k)
          rdm.push_back(r)
        # if prof is true, print out below:
        # doc-id, word count of all topics(array), that of noisy topic, total count of words in the document with doc-id
        if pre_doc != doc and prof:
          print pre_doc+1, self.Mdk[pre_doc], num_noisy, self.Md[pre_doc]
          pre_doc = doc
          num_noisy = 0
    if prof:
      print pre_doc+1, self.Mdk[pre_doc], num_noisy, self.Md[pre_doc]
    fdoc.close()
    self.M0 += self.eta
    self.M1 += self.eta
    self.Mk += self.gamma * self.S
    self.Mks += self.gamma
    self.M0s += self.gamma
    self.xdm = np.array(list(xdm))
    self.sdm = np.array(list(sdm))
    self.rdm = np.array(list(rdm))
    return 0

  # if prof is true, print out the profile of the inference
  @profile
  def inference_prof(self):
    return self.inference()

  def inference(self):
    # MAIN VARIABLES
    cdef int t, v, new_t
    cdef int idx = 0 # index of topic for main
    cdef np.ndarray[FTYPE_t, ndim=1] theta = np.zeros(self.K,dtype=FTYPE)
    cdef np.ndarray[FTYPE_t, ndim=1] phi = np.zeros(self.K,dtype=FTYPE)
    cdef np.ndarray[FTYPE_t, ndim=1] p_z = np.zeros(self.K,dtype=FTYPE)
    # SUPPLY INFORMATION VARIABLES
    cdef int k, s, new_ts, ktemp = 0
    cdef int idxs = 0 # index of topic for supply information
    cdef double p_r0
    cdef np.ndarray[FTYPE_t, ndim=1] p_r1 = np.zeros(self.K,dtype=FTYPE)
    cdef np.ndarray[FTYPE_t, ndim=1] p_y = np.zeros(self.K,dtype=FTYPE)

    for d in range(len(self.Nd)):
      # MAIN ITERATION
      for n in range(self.Nd[d]):
        t = self.zdn[idx]
        v = self.vdn[idx]
        # REDUCE COUNT OF OLD TOPIC
        self.Ndk[d][t] -= 1
        self.Nkv[t][v] -= 1
        self.Nk[t] -= 1
        # INFERENCE
        theta = self.Ndk[d] + self.alpha
        phi = self.Nkv[:,v] / self.Nk
        p_z = theta * phi * ((self.Ndk[d] + 1.0) / self.Ndk[d])**self.Mdk[d]
        new_t = np.random.multinomial(1,p_z / p_z.sum()).argmax() # decide new topic as categorical dist
        # COUNT ADD TO NEW TOPIC
        self.zdn[idx] = new_t
        self.Ndk[d][new_t] += 1
        self.Nkv[new_t][v] += 1
        self.Nk[new_t] += 1
        idx += 1

      # ITERATION FOR SUPPLY INFOMATION
      for n_s in range(self.Md[d]):
        k = self.xdm[idxs] # old topic
        s = self.sdm[idxs]
        # REDUCE COUNT OF OLD TOPIC
        self.Mk[k] -= 1
        if self.rdm[idxs] == 1:
          self.Mdk[d, k] -= 1
          self.Mks[k, s] -= 1
          self.M1 -= 1
        else:
          self.M0s[s] -= 1
          self.M0 -= 1
        p_r1 = self.M1 * self.Mks[:, s] / self.Mk[:self.K]
        p_r0 = self.M0 * self.M0s[s] / self.Mk[self.K]
        r = np.random.binomial(1, p_r1.sum() / (p_r0 + p_r1.sum()))
        # COUNT ADD TO NEW TOPIC
        self.rdm[idxs] = r # replace to new rdm = {0,1}
        if r == 1:
          p_y = self.Ndk[d] * (self.Mks[:,s] + self.gamma) / self.Mk[k]
          new_ts = np.random.multinomial(1,p_y / p_y.sum()).argmax()
          self.xdm[idxs] = new_ts # replace to new topic
          self.Mdk[d, new_ts] += 1
          self.Mks[new_ts, s] += 1
          self.M1 += 1
        else:
          self.xdm[idxs] = self.K # replace to noisy topic
          self.M0s[s] += 1
          self.M0 += 1
        self.Mk[self.xdm[idxs]] += 1
        idxs += 1
    cdef double perplexity = self.__calc_perplexity()
    return perplexity

  # calculation perplexity per one iteration
  def __calc_perplexity(self):
    cdef int s, N, idx = 0
    cdef double log_per = 0.0
    cdef double Kalpha = self.K * self.alpha
    cdef np.ndarray[FTYPE_t, ndim=1] theta = np.zeros(self.K,dtype=FTYPE)
    cdef np.ndarray[FTYPE_t, ndim=2] phi = self.Nkv / self.Nk[:,np.newaxis]
    for d in range(len(self.Nd)):
      theta = self.Ndk[d,:] / (self.Nd[d] + Kalpha) * ((self.Ndk[d] + 1.0) / self.Ndk[d])**self.Mdk[d]
      for v in range(self.Nd[d]):
        v = self.vdn[idx]
        log_per -= np.log(np.inner(phi[:,v], theta))
        idx += 1
    N = idx + 1
    return np.exp(log_per / N)
```

# 結果
トピック数は50として，反復計算200回行ったときの結果を示します．

###トピック例
トピック1-5までの単語とタグ，そしてノイズとなったタグを下表に示します．
各トピックの中で確率の高い上位10位の単語とタグ(補助情報)，それらのトピック内の割合を順に並べています．

トピック毎に関連単語，関連タグが集まっていますが，同一トピック内の単語と補助情報の関連性については微妙です．
タグの種類が少ないため，ノイズトピックも含めて，複数のトピックに同じタグが存在しています．
ある一つのトピックだけに関連するタグは，あまり見られないということだと思います．


| topic | 単語 | タグ |
|:-----:|:-----:|:-----:|
|topic 1| com : 0.0143840966187 | iOS : 0.0137813506742 |
|| よう : 0.0118339832479 |JavaScript : 0.0104194960757 |
|| https : 0.0110616631985 |  Ruby : 0.0102093801633 | 
|| http : 0.00810353168844 |  Windows : 0.00915880060132 |
|| こと : 0.00746236032665 |  Mac : 0.00873856877652 | 
|| 場合 : 0.00708348634014 |  Android : 0.00852845286412 |
|| の : 0.00673375650643 | Rails : 0.00810822103931 |
|| 以下 : 0.00552427416487 |  Linux : 0.0072677573897 |
|| 設定 : 0.00542226963004 |  Swift : 0.00663740965249 |  
|| タスク : 0.00447508466376 |Python : 0.00642729374009 |
|topic 2| tf : 0.038923153859 |Python : 0.0185783855201 | 
|| train : 0.0312198497729 |  TensorFlow : 0.0176585006859 | 
|| test : 0.0220516550738 |DeepLearning : 0.0136723330711 | 
|| data : 0.0185914824188 |Chainer : 0.00692651095376 |  
|| model : 0.0179095505817 |  機械学習 : 0.00447348472926 |  
|| loss : 0.0171518485404 |MachineLearning : 0.00447348472926 |
|| np : 0.0124793526194 |  Keras : 0.0041668564512 |
|| step : 0.0115953669046 |深層学習 : 0.0041668564512 |  
|| accuracy : 0.00944854445438 | CNN : 0.00386022817314 |  
|| print : 0.00901917996434 | ConvolutionalNeuralNetworks : 0.00355359989508 |  
|topic 3| mysql : 0.0438792621576 |  MySQL : 0.0172747954451 |
|| el : 0.0398385043809 |  VBA : 0.0126760035933 |  
|| As : 0.0384579121405 |  Excel : 0.00479236041866 |  
|| 的 : 0.0315886239199 |  ExcelVBA : 0.00282144962501 |  
|| 中 : 0.0257968711066 |  access : 0.00216447936046 | 
|| Long : 0.0166378201459 |Xcode : 0.00150750909591 | 
|| MySQL : 0.0123613514988 |  VB.Net : 0.00150750909591 | 
|| remi : 0.0118225837952 |Azure : 0.00117902396363 | 
|| Sub : 0.0117552378323 | mecab : 0.00117902396363 |
|| Function : 0.0114858539805 |  mariadb : 0.00117902396363 |
|topic 4| stocks : 0.133028236329 |  WebAPI : 0.000907792530452 |
|| jp : 0.10110874249 | 努力1mm : 0.000907792530452 |
|| http : 0.0886481914183 |#slick-codegen : 0.000557195545988 |
|| 日 : 0.08622056503 | openssl : 0.000557195545988 |
|| code : 0.0815739363962 |AdobeAIR : 0.000557195545988 |
|| co : 0.0778755993203 |  mex : 0.000557195545988 |
|| 円 : 0.0719013625053 |  日時 : 0.000557195545988 |  
|| detail : 0.0682409570917 | zybo : 0.000557195545988 |
|| yahoo : 0.0671409388845 |  Carbon : 0.000557195545988 |
|| 株 : 0.0664961006251 |  Chrome : 0.000557195545988 |
|topic 5| data : 0.115147263212 | crystal : 0.00490350058429 | 
|| value : 0.0738510543501 |  Ruby : 0.00322298524599 |
|| key : 0.0632111293615 | riot : 0.00120636684003 | 
|| sample : 0.0343835825957 | ぐるナビ : 0.00120636684003 |
|| fa : 0.0293961177574 |  Ansible : 0.00120636684003 |
|| default : 0.0268691355726 |Qiita : 0.00120636684003 | 
|| size : 0.0263371393232 |PHP : 0.00120636684003 |
|| class : 0.0221144190933 |  Twitter : 0.00120636684003 |
|| option : 0.0123389880101 | websocket : 0.000870263772375 | 
|| chef : 0.0120729898854 |Parslet : 0.000870263772375 | 
|noisy topic||  Go : 0.000211416490486 |
|||  TensorFlow : 0.000211416490486 | 
|||  Vim : 0.000211416490486 |  
|||  C# : 0.000211416490486 |
|||  めざせライブラリマスター : 0.000211416490486 |  
|||  Ubuntu : 0.000211416490486 |  
|||  機械学習 : 0.000211416490486 |
|||  Windows : 0.000211416490486 | 
|||  PostgreSQL : 0.000211416490486 | 
|||  VirtualBox : 0.000211416490486 | 

### Perplexity
 毎回の反復計算でトピックモデルのperplexityを求めています．
50回前後でperplexityが一定となった確率モデルが生成されたことを示しています．

![perplexity2.png](https://qiita-image-store.s3.amazonaws.com/0/63906/3cfc0553-a0c2-60cd-57b3-14289ecea66c.png)

# ソースコード
全てのソースコード，サンプルデータはgithubにアップロードしています．

https://github.com/yh0sh/CorrLDA

# 参考文献
- [岩田 具治「トピックモデル」(講談社,2014年)](http://bookclub.kodansha.co.jp/product?isbn=9784061529045)
- [Modeling Noisy Annotated Data with Application to Social Annotation](http://ieeexplore.ieee.org/xpl/login.jsp?tp=&arnumber=6193102&url=http%3A%2F%2Fieeexplore.ieee.org%2Fiel5%2F69%2F6517838%2F06193102.pdf%3Farnumber%3D6193102)
- [Modeling Social Anotation Data with Content Relevance using a Topic Model](https://www.google.co.jp/url?sa=t&rct=j&q=&esrc=s&source=web&cd=5&ved=0ahUKEwj_6LSQlOvNAhVPw2MKHYpwAO8QFghNMAQ&url=http%3A%2F%2Fece.duke.edu%2F~lcarin%2FNIPS2009_0270.pdf&usg=AFQjCNE9LAoWl9Zv80iV-9KtkNWRQP4rLQ&cad=rja)