Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -8,43 +8,36 @@ import os
|
|
| 8 |
from scipy.sparse import csr_matrix
|
| 9 |
|
| 10 |
class ItemBasedCF:
|
| 11 |
-
def __init__(self):
|
|
|
|
|
|
|
| 12 |
self.user_item_matrix = None
|
| 13 |
-
self.similarity_matrix = None
|
| 14 |
-
|
| 15 |
-
def predict(self, user_idx, movie_idx):
|
| 16 |
-
if self.user_item_matrix is None or self.similarity_matrix is None:
|
| 17 |
-
return 3.0
|
| 18 |
|
|
|
|
| 19 |
user_ratings = self.user_item_matrix[user_idx].toarray().flatten()
|
| 20 |
-
|
| 21 |
|
| 22 |
-
if
|
| 23 |
-
return
|
| 24 |
|
| 25 |
-
similarities = self.
|
| 26 |
-
ratings = user_ratings[rated_items]
|
| 27 |
|
| 28 |
-
|
| 29 |
-
|
|
|
|
| 30 |
|
| 31 |
-
prediction =
|
| 32 |
return np.clip(prediction, 1, 5)
|
| 33 |
|
| 34 |
|
| 35 |
class SVDRecommender:
|
| 36 |
-
def __init__(self):
|
|
|
|
| 37 |
self.user_factors = None
|
| 38 |
self.item_factors = None
|
| 39 |
self.global_mean = 3.5
|
| 40 |
|
| 41 |
def predict(self, user_idx, movie_idx):
|
| 42 |
-
if self.user_factors is None or self.item_factors is None:
|
| 43 |
-
return self.global_mean
|
| 44 |
-
|
| 45 |
-
if user_idx >= len(self.user_factors) or movie_idx >= len(self.item_factors):
|
| 46 |
-
return self.global_mean
|
| 47 |
-
|
| 48 |
prediction = self.global_mean + np.dot(self.user_factors[user_idx], self.item_factors[movie_idx])
|
| 49 |
return np.clip(prediction, 1, 5)
|
| 50 |
|
|
@@ -82,71 +75,71 @@ class NeuralCF(nn.Module):
|
|
| 82 |
|
| 83 |
|
| 84 |
class HybridRecommender:
|
| 85 |
-
def __init__(self):
|
|
|
|
|
|
|
| 86 |
self.item_cf = None
|
| 87 |
self.svd = None
|
| 88 |
self.ncf = None
|
| 89 |
-
self.weights =
|
| 90 |
-
|
|
|
|
|
|
|
|
|
|
| 91 |
|
| 92 |
def predict(self, user_idx, movie_idx):
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
predictions.append(self.ncf.predict(user_idx, movie_idx, self.device))
|
| 103 |
-
|
| 104 |
-
if not predictions:
|
| 105 |
-
return 3.5
|
| 106 |
-
|
| 107 |
-
weights = self.weights[:len(predictions)]
|
| 108 |
-
weight_sum = sum(weights)
|
| 109 |
-
weighted_pred = sum(p * w for p, w in zip(predictions, weights)) / weight_sum
|
| 110 |
|
| 111 |
-
return np.clip(
|
| 112 |
|
| 113 |
-
def recommend_movies(self, user_id, N, user_id_map, reverse_movie_map, movies_df):
|
| 114 |
-
if
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
return []
|
| 121 |
|
| 122 |
-
|
| 123 |
-
unrated_indices = np.where(user_ratings == 0)[0]
|
| 124 |
|
| 125 |
-
|
| 126 |
-
|
|
|
|
|
|
|
|
|
|
| 127 |
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
pred_rating = self.predict(user_idx, movie_idx)
|
| 131 |
-
predictions.append((movie_idx, pred_rating))
|
| 132 |
-
|
| 133 |
-
predictions.sort(key=lambda x: x[1], reverse=True)
|
| 134 |
-
top_predictions = predictions[:N]
|
| 135 |
|
| 136 |
recommendations = []
|
| 137 |
-
for movie_idx,
|
| 138 |
-
|
| 139 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 140 |
|
| 141 |
-
|
| 142 |
-
title = movie_info['title'].values[0]
|
| 143 |
-
recommendations.append((original_movie_id, title, pred_rating))
|
| 144 |
|
| 145 |
return recommendations
|
| 146 |
|
| 147 |
|
| 148 |
class MovieLensDataLoader:
|
| 149 |
-
def __init__(self):
|
|
|
|
|
|
|
| 150 |
self.user_id_map = {}
|
| 151 |
self.movie_id_map = {}
|
| 152 |
self.reverse_user_map = {}
|
|
|
|
| 8 |
from scipy.sparse import csr_matrix
|
| 9 |
|
| 10 |
class ItemBasedCF:
|
| 11 |
+
def __init__(self, n_neighbors=20):
|
| 12 |
+
self.n_neighbors = n_neighbors
|
| 13 |
+
self.item_similarity = None
|
| 14 |
self.user_item_matrix = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
|
| 16 |
+
def predict(self, user_idx, movie_idx):
|
| 17 |
user_ratings = self.user_item_matrix[user_idx].toarray().flatten()
|
| 18 |
+
rated_mask = user_ratings > 0
|
| 19 |
|
| 20 |
+
if not rated_mask.any():
|
| 21 |
+
return 2.5
|
| 22 |
|
| 23 |
+
similarities = self.item_similarity[movie_idx].toarray().flatten()
|
|
|
|
| 24 |
|
| 25 |
+
weights = similarities * rated_mask
|
| 26 |
+
if weights.sum() == 0:
|
| 27 |
+
return 2.5
|
| 28 |
|
| 29 |
+
prediction = (weights * user_ratings).sum() / weights.sum()
|
| 30 |
return np.clip(prediction, 1, 5)
|
| 31 |
|
| 32 |
|
| 33 |
class SVDRecommender:
|
| 34 |
+
def __init__(self, n_factors=50):
|
| 35 |
+
self.n_factors = n_factors
|
| 36 |
self.user_factors = None
|
| 37 |
self.item_factors = None
|
| 38 |
self.global_mean = 3.5
|
| 39 |
|
| 40 |
def predict(self, user_idx, movie_idx):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
prediction = self.global_mean + np.dot(self.user_factors[user_idx], self.item_factors[movie_idx])
|
| 42 |
return np.clip(prediction, 1, 5)
|
| 43 |
|
|
|
|
| 75 |
|
| 76 |
|
| 77 |
class HybridRecommender:
|
| 78 |
+
def __init__(self, n_users, n_movies):
|
| 79 |
+
self.n_users = n_users
|
| 80 |
+
self.n_movies = n_movies
|
| 81 |
self.item_cf = None
|
| 82 |
self.svd = None
|
| 83 |
self.ncf = None
|
| 84 |
+
self.weights = {
|
| 85 |
+
'item_cf': 0.3,
|
| 86 |
+
'svd': 0.4,
|
| 87 |
+
'ncf': 0.3
|
| 88 |
+
}
|
| 89 |
|
| 90 |
def predict(self, user_idx, movie_idx):
|
| 91 |
+
cf_pred = self.item_cf.predict(user_idx, movie_idx)
|
| 92 |
+
svd_pred = self.svd.predict(user_idx, movie_idx)
|
| 93 |
+
ncf_pred = self.ncf.predict(user_idx, movie_idx)
|
| 94 |
+
|
| 95 |
+
prediction = (
|
| 96 |
+
self.weights['item_cf'] * cf_pred +
|
| 97 |
+
self.weights['svd'] * svd_pred +
|
| 98 |
+
self.weights['ncf'] * ncf_pred
|
| 99 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 100 |
|
| 101 |
+
return np.clip(prediction, 1, 5)
|
| 102 |
|
| 103 |
+
def recommend_movies(self, user_id, N=10, user_id_map=None, reverse_movie_map=None, movies_df=None):
|
| 104 |
+
if user_id_map is not None:
|
| 105 |
+
if user_id not in user_id_map:
|
| 106 |
+
return []
|
| 107 |
+
user_idx = user_id_map[user_id]
|
| 108 |
+
else:
|
| 109 |
+
user_idx = user_id
|
|
|
|
| 110 |
|
| 111 |
+
rated_movies = set(np.where(self.item_cf.user_item_matrix[user_idx].toarray().flatten() > 0)[0])
|
|
|
|
| 112 |
|
| 113 |
+
scores = []
|
| 114 |
+
for movie_idx in range(self.n_movies):
|
| 115 |
+
if movie_idx not in rated_movies:
|
| 116 |
+
score = self.predict(user_idx, movie_idx)
|
| 117 |
+
scores.append((movie_idx, score))
|
| 118 |
|
| 119 |
+
scores.sort(key=lambda x: x[1], reverse=True)
|
| 120 |
+
top_recommendations = scores[:N]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 121 |
|
| 122 |
recommendations = []
|
| 123 |
+
for movie_idx, score in top_recommendations:
|
| 124 |
+
if reverse_movie_map is not None:
|
| 125 |
+
original_movie_id = reverse_movie_map[movie_idx]
|
| 126 |
+
else:
|
| 127 |
+
original_movie_id = movie_idx
|
| 128 |
+
|
| 129 |
+
if movies_df is not None:
|
| 130 |
+
title = movies_df[movies_df['movie_id'] == original_movie_id]['title'].values[0]
|
| 131 |
+
else:
|
| 132 |
+
title = f"Movie {original_movie_id}"
|
| 133 |
|
| 134 |
+
recommendations.append((original_movie_id, title, score))
|
|
|
|
|
|
|
| 135 |
|
| 136 |
return recommendations
|
| 137 |
|
| 138 |
|
| 139 |
class MovieLensDataLoader:
|
| 140 |
+
def __init__(self, ratings_path=None, movies_path=None):
|
| 141 |
+
self.ratings_path = ratings_path
|
| 142 |
+
self.movies_path = movies_path
|
| 143 |
self.user_id_map = {}
|
| 144 |
self.movie_id_map = {}
|
| 145 |
self.reverse_user_map = {}
|