Skip to content

Commit

Permalink
Fix BPMF MCMC parameter update (#13)
Browse files Browse the repository at this point in the history
* fix BPMF param sampling error

* clean up debug messages
  • Loading branch information
chyikwei committed Apr 20, 2017
1 parent cfcb577 commit 3bb3f2d
Show file tree
Hide file tree
Showing 2 changed files with 47 additions and 63 deletions.
2 changes: 1 addition & 1 deletion recommend/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = '0.1.0'
__version__ = '0.1.1'
108 changes: 46 additions & 62 deletions recommend/bpmf.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@ class BPMF(ModelBase):
"""

def __init__(self, n_user, n_item, n_feature, beta=2.0, beta_user=2.0,
df_user=None, beta_item=2.0, df_item=None, converge=1e-5,
seed=None, max_rating=None, min_rating=None):
df_user=None, mu0_user=0., beta_item=2.0, df_item=None,
mu0_item=0., converge=1e-5, seed=None, max_rating=None,
min_rating=None):

super(BPMF, self).__init__()

Expand All @@ -47,13 +48,13 @@ def __init__(self, n_user, n_item, n_feature, beta=2.0, beta_user=2.0,
self.WI_user = np.eye(n_feature, dtype='float64')
self.beta_user = beta_user
self.df_user = int(df_user) if df_user is not None else n_feature
self.mu_user = np.zeros((n_feature, 1), dtype='float64')
self.mu0_user = np.repeat(mu0_user, n_feature).reshape(n_feature, 1)

# Inv-Whishart (item features)
self.WI_item = np.eye(n_feature, dtype='float64')
self.beta_item = beta_item
self.df_item = int(df_item) if df_item is not None else n_feature
self.mu_item = np.zeros((n_feature, 1), dtype='float64')
self.mu0_item = np.repeat(mu0_item, n_feature).reshape(n_feature, 1)

# Latent Variables
self.mu_user = np.zeros((n_feature, 1), dtype='float64')
Expand Down Expand Up @@ -90,7 +91,7 @@ def fit(self, ratings, n_iters=50):
self._update_item_params()
self._update_user_params()

# update item & user_features
# update item & user features
self._udpate_item_features()
self._update_user_features()

Expand Down Expand Up @@ -128,119 +129,102 @@ def predict(self, data):

def _update_item_params(self):
N = self.n_item
X_bar = np.mean(self.item_features, 0)
X_bar = np.reshape(X_bar, (self.n_feature, 1))
X_bar = np.mean(self.item_features, 0).reshape((self.n_feature, 1))
# print 'X_bar', X_bar.shape
S_bar = np.cov(self.item_features.T)
# print 'S_bar', S_bar.shape

norm_X_bar = X_bar - self.mu_item
# print 'norm_X_bar', norm_X_bar.shape
diff_X_bar = self.mu0_item - X_bar

WI_post = inv(inv(self.WI_item) + N * S_bar + \
np.dot(norm_X_bar, norm_X_bar.T) * \
(N * self.beta_item) / (self.beta_item + N))
# print 'WI_post', WI_post.shape
# W_{0}_star
WI_post = inv(inv(self.WI_item) + \
N * S_bar + \
np.dot(diff_X_bar, diff_X_bar.T) * \
(N * self.beta_item) / (self.beta_item + N))

# Not sure why we need this...
# Note: WI_post and WI_post.T should be the same.
# Just make sure it is symmertic here
WI_post = (WI_post + WI_post.T) / 2.0
df_post = self.df_item + N

# update alpha_item
df_post = self.df_item + N
self.alpha_item = wishart.rvs(df_post, WI_post, 1, self.rand_state)

# update mu_item
mu_temp = (self.beta_item * self.mu_item + N * X_bar) / \
mu_mean = (self.beta_item * self.mu0_item + N * X_bar) / \
(self.beta_item + N)
# print "mu_temp", mu_temp.shape
lam = cholesky(inv(np.dot(self.beta_item + N, self.alpha_item)))
mu_var = cholesky(inv(np.dot(self.beta_item + N, self.alpha_item)))
# print 'lam', lam.shape
self.mu_item = mu_temp + np.dot(lam, self.rand_state.randn(self.n_feature, 1))
self.mu_item = mu_mean + np.dot(mu_var, self.rand_state.randn(self.n_feature, 1))
# print 'mu_item', self.mu_item.shape

def _update_user_params(self):
# same as _update_user_params
N = self.n_user
X_bar = np.mean(self.user_features, 0).T
X_bar = np.reshape(X_bar, (self.n_feature, 1))

# print 'X_bar', X_bar.shape
X_bar = np.mean(self.user_features, 0).reshape((self.n_feature, 1))
S_bar = np.cov(self.user_features.T)
# print 'S_bar', S_bar.shape

norm_X_bar = X_bar - self.mu_user
# print 'norm_X_bar', norm_X_bar.shape

WI_post = inv(inv(self.WI_user) + N * S_bar + \
np.dot(norm_X_bar, norm_X_bar.T) * \
(N * self.beta_user) / (self.beta_user + N))
# print 'WI_post', WI_post.shape
# mu_{0} - U_bar
diff_X_bar = self.mu0_user - X_bar

# Not sure why we need this...
# W_{0}_star
WI_post = inv(inv(self.WI_user) + \
N * S_bar + \
np.dot(diff_X_bar, diff_X_bar.T) * \
(N * self.beta_user) / (self.beta_user + N))
# Note: WI_post and WI_post.T should be the same.
# Just make sure it is symmertic here
WI_post = (WI_post + WI_post.T) / 2.0
df_post = self.df_user + N

# update alpha_user
df_post = self.df_user + N
# LAMBDA_{U} ~ W(W{0}_star, df_post)
self.alpha_user = wishart.rvs(df_post, WI_post, 1, self.rand_state)

# update mu_item
mu_temp = (self.beta_user * self.mu_user + N * X_bar) / \
(self.beta_user + N)
# print 'mu_temp', mu_temp.shape
lam = cholesky(inv(np.dot(self.beta_user + N, self.alpha_user)))
# print 'lam', lam.shape
self.mu_user = mu_temp + np.dot(lam, self.rand_state.randn(self.n_feature, 1))
# print 'mu_user', self.mu_user.shape
# update mu_user
# mu_{0}_star = (beta_{0} * mu_{0} + N * U_bar) / (beta_{0} + N)
mu_mean = (self.beta_user * self.mu0_user + N * X_bar) / \
(self.beta_user + N)

# decomposed inv(beta_{0}_star * LAMBDA_{U})
mu_var = cholesky(inv(np.dot(self.beta_user + N, self.alpha_user)))
# sample multivariate gaussian
self.mu_user = mu_mean + np.dot(mu_var, self.rand_state.randn(self.n_feature, 1))

def _udpate_item_features(self):
# Gibbs sampling for item features
for item_id in xrange(self.n_item):
indices = self._ratings_csc[:, item_id].indices
# print 'vec', vec.shape
# if vec.shape[0] == 0:
# continue
features = self.user_features[indices, :]
# print 'features', features.shape
rating = self._ratings_csc[:, item_id].data - self._mean_rating
rating = np.reshape(rating, (rating.shape[0], 1))

# print 'rating', rating.shape
covar = inv(
self.alpha_item + self.beta * np.dot(features.T, features))
# print 'covar', covar.shape
lam = cholesky(covar)

temp = self.beta * \
np.dot(features.T, rating) + np.dot(
self.alpha_item, self.mu_item)
# print 'temp', temp.shape
mean = np.dot(covar, temp)
# print 'mean', mean.shape
temp_feature = mean + np.dot(lam, self.rand_state.randn(self.n_feature, 1))
temp_feature = np.reshape(temp_feature, (self.n_feature,))
self.item_features[item_id, :] = temp_feature
self.item_features[item_id, :] = temp_feature.reshape((self.n_feature,))

def _update_user_features(self):
# Gibbs sampling for user features
for user_id in xrange(self.n_user):
indices = self._ratings_csr[user_id, :].indices
# print len(vec)
# if vec.shape[0] == 0:
# continue
# print "item_feature", self.item_features.shape
features = self.item_features[indices, :]
rating = self._ratings_csr[user_id, :].data - self._mean_rating
rating = np.reshape(rating, (rating.shape[0], 1))

# print 'rating', rating.shape
covar = inv(
self.alpha_user + self.beta * np.dot(features.T, features))
lam = cholesky(covar)
temp = self.beta * \
np.dot(features.T, rating) + np.dot(
self.alpha_user, self.mu_user)
# aplha * sum(V_j * R_ij) + LAMBDA_U * mu_u
temp = self.beta * np.dot(features.T, rating) + \
np.dot(self.alpha_user, self.mu_user)
# mu_i_star
mean = np.dot(covar, temp)
# print 'mean', mean.shape
temp_feature = mean + np.dot(lam, self.rand_state.randn(self.n_feature, 1))
temp_feature = np.reshape(temp_feature, (self.n_feature,))
self.user_features[user_id, :] = temp_feature
self.user_features[user_id, :] = temp_feature.reshape((self.n_feature,))

0 comments on commit 3bb3f2d

Please sign in to comment.