1
+ import numpy as np
2
+ from scipy .special import gammaln , psi
3
+
4
+ eps = 1e-10
5
+
6
+ class rtm :
7
+ """ implementation of relational topic model by Chang and Blei (2009)
8
+ I implemented the exponential link probability function in here
9
+ """
10
+
11
+ def __init__ (self , num_topic , num_doc , num_voca , doc_ids , doc_cnt , doc_links , rho ):
12
+ self .D = num_doc
13
+ self .K = num_topic
14
+ self .V = num_voca
15
+
16
+ self .alpha = 1.
17
+
18
+ self .gamma = np .random .gamma (100. , 1. / 100 , [self .D , self .K ])
19
+ self .beta = np .random .dirichlet ([5 ]* self .V , self .K )
20
+
21
+ self .nu = 0
22
+ self .eta = np .random .normal (0. ,1 , self .K )
23
+
24
+ self .phi = list ()
25
+ self .pi = np .zeros ([self .D , self .K ])
26
+
27
+ for di in xrange (self .D ):
28
+ unique_word = len (doc_ids [di ])
29
+ cnt = doc_cnt [di ]
30
+ self .phi .append (np .random .dirichlet ([10 ]* self .K , unique_word ).T ) # list of KxW
31
+ self .pi [di ,:] = np .sum (cnt * self .phi [di ],1 )/ np .sum (cnt * self .phi [di ])
32
+
33
+ self .doc_ids = doc_ids
34
+ self .doc_cnt = doc_cnt
35
+ self .doc_links = doc_links
36
+ self .rho = rho #regularization parameter
37
+
38
+ def posterior_inference (self , max_iter ):
39
+ for iter in xrange (max_iter ):
40
+ self .variation_update ()
41
+ self .parameter_estimation ()
42
+ #print self.compute_elbo()
43
+ if iter == max_iter - 1 :
44
+ print self .link_prediction ()
45
+
46
+ def compute_elbo (self ):
47
+ """ compute evidence lower bound for trained model
48
+ """
49
+ elbo = 0
50
+
51
+ e_log_theta = psi (self .gamma ) - psi (np .sum (self .gamma , 1 ))[:,np .newaxis ] # D x K
52
+ log_beta = np .log (self .beta )
53
+
54
+ for di in xrange (self .D ):
55
+ words = self .doc_ids [di ]
56
+ cnt = self .doc_cnt [di ]
57
+
58
+ elbo += np .sum (cnt * (self .phi [di ] * log_beta [:,words ])) # E_q[log p(w_{d,n}|\beta,z_{d,n})]
59
+ elbo += np .sum ((self .alpha - 1. )* e_log_theta [di ,:]) # E_q[log p(\theta_d | alpha)]
60
+ elbo += np .sum (self .phi [di ].T * e_log_theta [di ,:]) # E_q[log p(z_{d,n}|\theta_d)]
61
+
62
+ elbo += - gammaln (np .sum (self .gamma [di ,:])) + np .sum (gammaln (self .gamma [di ,:])) \
63
+ - np .sum ((self .gamma [di ,:] - 1. )* (e_log_theta [di ,:])) # - E_q[log q(theta|gamma)]
64
+ elbo += - np .sum (cnt * self .phi [di ] * np .log (self .phi [di ])) # - E_q[log q(z|phi)]
65
+
66
+ for adi in self .doc_links [di ]:
67
+ elbo += np .dot (self .eta , self .pi [di ]* self .pi [adi ]) # E_q[log p(y_{d1,d2}|z_{d1},z_{d2},\eta,\nu)]
68
+
69
+ return elbo
70
+
71
+ def variation_update (self ):
72
+ #update phi, gamma
73
+ e_log_theta = psi (self .gamma ) - psi (np .sum (self .gamma , 1 ))[:,np .newaxis ]
74
+
75
+ new_beta = np .zeros ([self .K , self .V ])
76
+
77
+ for di in xrange (self .D ):
78
+ words = self .doc_ids [di ]
79
+ cnt = self .doc_cnt [di ]
80
+ doc_len = np .sum (cnt )
81
+
82
+ new_phi = np .log (self .beta [:,words ]) + e_log_theta [di ,:][:,np .newaxis ]
83
+
84
+ gradient = np .zeros (self .K )
85
+ for adi in self .doc_links [di ]:
86
+ gradient += self .eta * self .pi [adi ,:] / doc_len
87
+
88
+ new_phi += gradient [:,np .newaxis ]
89
+ new_phi = np .exp (new_phi )
90
+ new_phi = new_phi / np .sum (new_phi ,0 )
91
+
92
+ self .phi [di ] = new_phi
93
+
94
+ self .pi [di ,:] = np .sum (cnt * self .phi [di ],1 )/ np .sum (cnt * self .phi [di ])
95
+ self .gamma [di ,:] = np .sum (cnt * self .phi [di ], 1 ) + self .alpha
96
+ new_beta [:, words ] += (cnt * self .phi [di ])
97
+
98
+ self .beta = new_beta / np .sum (new_beta , 1 )[:,np .newaxis ]
99
+
100
+ def parameter_estimation (self ):
101
+ #update eta, nu
102
+ pi_sum = np .zeros (self .K )
103
+
104
+ num_links = 0.
105
+
106
+ for di in xrange (self .D ):
107
+ for adi in self .doc_links [di ]:
108
+ pi_sum += self .pi [di ,:]* self .pi [adi ,:]
109
+ num_links += 1
110
+
111
+ num_links /= 2. # divide by 2 for bidirectional edge
112
+ pi_sum /= 2.
113
+
114
+ pi_alpha = np .zeros (self .K ) + self .alpha / (self .alpha * self .K )* self .alpha / (self .alpha * self .K )
115
+
116
+ self .nu = np .log (num_links - np .sum (pi_sum )) - np .log (self .rho * (self .K - 1 )/ self .K + num_links - np .sum (pi_sum ))
117
+ self .eta = np .log (pi_sum ) - np .log (pi_sum + self .rho * pi_alpha ) - self .nu
118
+
119
+ def link_prediction (self ):
120
+
121
+ prediction = []
122
+ '''
123
+ for v in test_docs:
124
+ prediction_sub = dict()
125
+ for v2 in range(self.D):
126
+ prediction_sub[v2] = np.exp(self.eta.dot(self.pi[v]*self.pi[v2]) + self.nu)
127
+ sorted_prediction_sub = sorted(prediction_sub.items(), key = operator.itemgetter(1))[::-1]
128
+ sorted_prediction_sub_dict = {k[0] : idx for idx, k in enumerate(sorted_prediction_sub)}
129
+ linked_rankings = []
130
+ for v3 in doc_links_unremoved[v]:
131
+ linked_rankings.append(sorted_prediction_sub_dict[v3])
132
+ prediction.append(np.mean(linked_rankings))
133
+ '''
134
+
135
+ for v in test_docs :
136
+ predicted_likelihood = np .exp (self .eta .dot ((self .pi [v ]* self .pi ).T ) + self .nu )
137
+ sorted_p = predicted_likelihood .argsort ()[::- 1 ]
138
+ linked_rankings = []
139
+ for v2 in doc_links_unremoved [v ]:
140
+ linked_rankings .append (list (sorted_p ).index (v2 ))
141
+ prediction .append (np .mean (linked_rankings ))
142
+
143
+ return np .mean (prediction )
144
+
145
+ def main ():
146
+ rho = 1
147
+ num_topic = 2
148
+ num_voca = 6
149
+ num_doc = 2
150
+ doc_ids = [[0 ,1 ,4 ],[2 ,3 ,5 ]]
151
+ doc_cnt = [[2 ,2 ,3 ],[1 ,3 ,1 ]]
152
+ doc_links = [[1 ],[0 ]] #bidirectional link
153
+ model = rtm (num_topic , num_doc , num_voca , doc_ids , doc_cnt , doc_links , rho )
154
+ model .posterior_inference (10 )
155
+
156
+ if __name__ == '__main__' :
157
+ main ()
0 commit comments