Rijeet commited on
Commit
0ab6605
·
verified ·
1 Parent(s): 59825da

Upload comprehensive_recommendation_system.py

Browse files
comprehensive_recommendation_system.py ADDED
@@ -0,0 +1,760 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Comprehensive Movie Recommendation System
3
+ ========================================
4
+
5
+ This system implements multiple recommendation approaches:
6
+ 1. User-based Collaborative Filtering
7
+ 2. Item-based Collaborative Filtering
8
+ 3. Matrix Factorization (SVD)
9
+ 4. Neural Embeddings (Deep Learning)
10
+
11
+ Author: DataSynthis ML Team
12
+ Date: 2024
13
+ """
14
+
15
+ import pandas as pd
16
+ import numpy as np
17
+ import pickle
18
+ import os
19
+ from typing import List, Tuple, Dict, Optional
20
+ from sklearn.metrics.pairwise import cosine_similarity
21
+ from sklearn.decomposition import TruncatedSVD
22
+ from sklearn.neural_network import MLPRegressor
23
+ from sklearn.preprocessing import StandardScaler
24
+ from sklearn.model_selection import train_test_split
25
+ from sklearn.metrics import mean_squared_error, mean_absolute_error
26
+ import matplotlib.pyplot as plt
27
+ import seaborn as sns
28
+ import warnings
29
+
30
+ warnings.filterwarnings('ignore')
31
+
32
+ class ComprehensiveRecommendationSystem:
33
+ """
34
+ Comprehensive recommendation system implementing multiple approaches
35
+ """
36
+
37
+ def __init__(self, data_path: str = None):
38
+ """
39
+ Initialize the recommendation system
40
+
41
+ Args:
42
+ data_path: Path to the MovieLens dataset directory
43
+ """
44
+ self.data_path = data_path
45
+ self.ratings = None
46
+ self.movies = None
47
+ self.user_item_matrix = None
48
+ self.user_similarity_matrix = None
49
+ self.item_similarity_matrix = None
50
+ self.svd_model = None
51
+ self.neural_model = None
52
+ self.scaler = None
53
+ self.user_means = None
54
+ self.item_means = None
55
+
56
+ def load_data(self, ratings_file: str, movies_file: str):
57
+ """
58
+ Load and preprocess the MovieLens dataset
59
+
60
+ Args:
61
+ ratings_file: Path to ratings file
62
+ movies_file: Path to movies file
63
+ """
64
+ print("Loading MovieLens dataset...")
65
+
66
+ # Load ratings data - handle both CSV and tab-separated formats
67
+ if ratings_file.endswith('.csv'):
68
+ self.ratings = pd.read_csv(ratings_file)
69
+ # Rename columns to standard format
70
+ if 'userId' in self.ratings.columns:
71
+ self.ratings = self.ratings.rename(columns={'userId': 'user_id', 'movieId': 'movie_id'})
72
+ else:
73
+ # Handle tab-separated format (ml-100k)
74
+ self.ratings = pd.read_csv(
75
+ ratings_file,
76
+ sep="\t",
77
+ names=["user_id", "movie_id", "rating", "timestamp"]
78
+ )
79
+
80
+ # Load movies data - handle both CSV and pipe-separated formats
81
+ if movies_file.endswith('.csv'):
82
+ self.movies = pd.read_csv(movies_file)
83
+ # Rename columns to standard format
84
+ if 'movieId' in self.movies.columns:
85
+ self.movies = self.movies.rename(columns={'movieId': 'movie_id'})
86
+ # Select relevant columns for movies
87
+ if 'title' in self.movies.columns:
88
+ self.movies = self.movies[['movie_id', 'title']]
89
+ else:
90
+ # Fallback to first two columns if title column not found
91
+ self.movies = self.movies.iloc[:, [0, 1]]
92
+ self.movies.columns = ["movie_id", "title"]
93
+ else:
94
+ # Handle pipe-separated format (ml-100k)
95
+ self.movies = pd.read_csv(
96
+ movies_file,
97
+ sep="|",
98
+ encoding="latin-1",
99
+ header=None
100
+ )
101
+ # Select relevant columns for movies
102
+ self.movies = self.movies[[0, 1]]
103
+ self.movies.columns = ["movie_id", "title"]
104
+
105
+ print(f"Ratings shape: {self.ratings.shape}")
106
+ print(f"Movies shape: {self.movies.shape}")
107
+ print(f"Unique users: {self.ratings['user_id'].nunique()}")
108
+ print(f"Unique movies: {self.movies['movie_id'].nunique()}")
109
+ print(f"Rating range: {self.ratings['rating'].min()} - {self.ratings['rating'].max()}")
110
+
111
+ # Create user-item matrix
112
+ self._create_user_item_matrix()
113
+
114
+ def _create_user_item_matrix(self):
115
+ """Create user-item rating matrix"""
116
+ print("Creating user-item matrix...")
117
+ self.user_item_matrix = self.ratings.pivot_table(
118
+ index="user_id",
119
+ columns="movie_id",
120
+ values="rating"
121
+ ).fillna(0)
122
+
123
+ print(f"User-Item Matrix shape: {self.user_item_matrix.shape}")
124
+ print(f"Sparsity: {(1 - (self.user_item_matrix != 0).sum().sum() / self.user_item_matrix.size) * 100:.2f}%")
125
+
126
+ def train_user_based_cf(self):
127
+ """Train user-based collaborative filtering"""
128
+ print("Training user-based collaborative filtering...")
129
+
130
+ # Compute user similarity matrix
131
+ self.user_similarity_matrix = cosine_similarity(self.user_item_matrix)
132
+
133
+ # Calculate user means for bias correction
134
+ self.user_means = self.user_item_matrix.mean(axis=1)
135
+
136
+ print("User-based CF trained successfully!")
137
+
138
+ def train_item_based_cf(self):
139
+ """Train item-based collaborative filtering"""
140
+ print("Training item-based collaborative filtering...")
141
+
142
+ # Compute item similarity matrix
143
+ self.item_similarity_matrix = cosine_similarity(self.user_item_matrix.T)
144
+
145
+ # Calculate item means for bias correction
146
+ self.item_means = self.user_item_matrix.mean(axis=0)
147
+
148
+ print("Item-based CF trained successfully!")
149
+
150
+ def train_svd(self, n_components: int = 50):
151
+ """Train SVD matrix factorization"""
152
+ print(f"Training SVD with {n_components} components...")
153
+
154
+ # Create a copy of the matrix for SVD
155
+ matrix_for_svd = self.user_item_matrix.copy()
156
+
157
+ # Replace 0s with user means for proper SVD
158
+ user_means = matrix_for_svd.mean(axis=1)
159
+ for user_id in matrix_for_svd.index:
160
+ user_ratings = matrix_for_svd.loc[user_id]
161
+ zero_mask = user_ratings == 0
162
+ matrix_for_svd.loc[user_id, zero_mask] = user_means[user_id]
163
+
164
+ # Apply SVD
165
+ self.svd_model = TruncatedSVD(n_components=n_components, random_state=42)
166
+ self.svd_model.fit(matrix_for_svd)
167
+
168
+ print("SVD trained successfully!")
169
+
170
+ def train_neural_embeddings(self, hidden_layers: Tuple = (100, 50), max_iter: int = 100):
171
+ """Train neural network for embeddings"""
172
+ print("Training neural embeddings...")
173
+
174
+ # Prepare data for neural network
175
+ X = []
176
+ y = []
177
+
178
+ for user_id in self.user_item_matrix.index:
179
+ for movie_id in self.user_item_matrix.columns:
180
+ rating = self.user_item_matrix.loc[user_id, movie_id]
181
+ if rating > 0: # Only use actual ratings
182
+ X.append([user_id, movie_id])
183
+ y.append(rating)
184
+
185
+ X = np.array(X)
186
+ y = np.array(y)
187
+
188
+ # Scale features
189
+ self.scaler = StandardScaler()
190
+ X_scaled = self.scaler.fit_transform(X)
191
+
192
+ # Train neural network
193
+ self.neural_model = MLPRegressor(
194
+ hidden_layer_sizes=hidden_layers,
195
+ max_iter=max_iter,
196
+ random_state=42
197
+ )
198
+
199
+ self.neural_model.fit(X_scaled, y)
200
+
201
+ print("Neural embeddings trained successfully!")
202
+
203
+ def recommend_movies_user_based(self, user_id: int, N: int = 10, top_k: int = 10) -> pd.DataFrame:
204
+ """
205
+ Generate recommendations using user-based collaborative filtering
206
+
207
+ Args:
208
+ user_id: ID of the user to recommend for
209
+ N: Number of movies to recommend
210
+ top_k: Number of similar users to consider
211
+
212
+ Returns:
213
+ DataFrame with recommended movies
214
+ """
215
+ if user_id not in self.user_item_matrix.index:
216
+ return pd.DataFrame(columns=['movie_id', 'title', 'predicted_rating'])
217
+
218
+ # Get user's ratings
219
+ user_ratings = self.user_item_matrix.loc[user_id]
220
+ rated_movies = set(user_ratings[user_ratings > 0].index)
221
+
222
+ # Get all movies
223
+ all_movies = set(self.user_item_matrix.columns)
224
+ unrated_movies = all_movies - rated_movies
225
+
226
+ if not unrated_movies:
227
+ return pd.DataFrame(columns=['movie_id', 'title', 'predicted_rating'])
228
+
229
+ # Get similar users
230
+ user_idx = list(self.user_item_matrix.index).index(user_id)
231
+ similarities = self.user_similarity_matrix[user_idx]
232
+ similar_users_idx = np.argsort(similarities)[::-1][1:top_k+1]
233
+
234
+ # Predict ratings for unrated movies
235
+ predictions = []
236
+ for movie_id in unrated_movies:
237
+ predicted_rating = self._predict_rating_user_based(user_id, movie_id, similar_users_idx)
238
+ if predicted_rating > 0:
239
+ predictions.append({
240
+ 'movie_id': movie_id,
241
+ 'predicted_rating': predicted_rating
242
+ })
243
+
244
+ if not predictions:
245
+ return pd.DataFrame(columns=['movie_id', 'title', 'predicted_rating'])
246
+
247
+ # Sort by predicted rating and get top-N
248
+ predictions_df = pd.DataFrame(predictions)
249
+ predictions_df = predictions_df.sort_values('predicted_rating', ascending=False).head(N)
250
+
251
+ # Merge with movie information
252
+ recommendations = predictions_df.merge(
253
+ self.movies[['movie_id', 'title']],
254
+ on='movie_id',
255
+ how='left'
256
+ )
257
+
258
+ return recommendations[['movie_id', 'title', 'predicted_rating']]
259
+
260
+ def recommend_movies_item_based(self, user_id: int, N: int = 10, top_k: int = 10) -> pd.DataFrame:
261
+ """
262
+ Generate recommendations using item-based collaborative filtering
263
+
264
+ Args:
265
+ user_id: ID of the user to recommend for
266
+ N: Number of movies to recommend
267
+ top_k: Number of similar items to consider
268
+
269
+ Returns:
270
+ DataFrame with recommended movies
271
+ """
272
+ if user_id not in self.user_item_matrix.index:
273
+ return pd.DataFrame(columns=['movie_id', 'title', 'predicted_rating'])
274
+
275
+ # Get user's ratings
276
+ user_ratings = self.user_item_matrix.loc[user_id]
277
+ rated_movies = set(user_ratings[user_ratings > 0].index)
278
+
279
+ # Get all movies
280
+ all_movies = set(self.user_item_matrix.columns)
281
+ unrated_movies = all_movies - rated_movies
282
+
283
+ if not unrated_movies:
284
+ return pd.DataFrame(columns=['movie_id', 'title', 'predicted_rating'])
285
+
286
+ # Predict ratings for unrated movies
287
+ predictions = []
288
+ for movie_id in unrated_movies:
289
+ predicted_rating = self._predict_rating_item_based(user_id, movie_id, top_k)
290
+ if predicted_rating > 0:
291
+ predictions.append({
292
+ 'movie_id': movie_id,
293
+ 'predicted_rating': predicted_rating
294
+ })
295
+
296
+ if not predictions:
297
+ return pd.DataFrame(columns=['movie_id', 'title', 'predicted_rating'])
298
+
299
+ # Sort by predicted rating and get top-N
300
+ predictions_df = pd.DataFrame(predictions)
301
+ predictions_df = predictions_df.sort_values('predicted_rating', ascending=False).head(N)
302
+
303
+ # Merge with movie information
304
+ recommendations = predictions_df.merge(
305
+ self.movies[['movie_id', 'title']],
306
+ on='movie_id',
307
+ how='left'
308
+ )
309
+
310
+ return recommendations[['movie_id', 'title', 'predicted_rating']]
311
+
312
+ def recommend_movies_svd(self, user_id: int, N: int = 10) -> pd.DataFrame:
313
+ """
314
+ Generate recommendations using SVD matrix factorization
315
+
316
+ Args:
317
+ user_id: ID of the user to recommend for
318
+ N: Number of movies to recommend
319
+
320
+ Returns:
321
+ DataFrame with recommended movies
322
+ """
323
+ if user_id not in self.user_item_matrix.index:
324
+ return pd.DataFrame(columns=['movie_id', 'title', 'predicted_rating'])
325
+
326
+ # Get user's ratings
327
+ user_ratings = self.user_item_matrix.loc[user_id]
328
+ rated_movies = set(user_ratings[user_ratings > 0].index)
329
+
330
+ # Get all movies
331
+ all_movies = set(self.user_item_matrix.columns)
332
+ unrated_movies = all_movies - rated_movies
333
+
334
+ if not unrated_movies:
335
+ return pd.DataFrame(columns=['movie_id', 'title', 'predicted_rating'])
336
+
337
+ # Predict ratings for unrated movies
338
+ predictions = []
339
+ for movie_id in unrated_movies:
340
+ predicted_rating = self._predict_rating_svd(user_id, movie_id)
341
+ if predicted_rating > 0:
342
+ predictions.append({
343
+ 'movie_id': movie_id,
344
+ 'predicted_rating': predicted_rating
345
+ })
346
+
347
+ if not predictions:
348
+ return pd.DataFrame(columns=['movie_id', 'title', 'predicted_rating'])
349
+
350
+ # Sort by predicted rating and get top-N
351
+ predictions_df = pd.DataFrame(predictions)
352
+ predictions_df = predictions_df.sort_values('predicted_rating', ascending=False).head(N)
353
+
354
+ # Merge with movie information
355
+ recommendations = predictions_df.merge(
356
+ self.movies[['movie_id', 'title']],
357
+ on='movie_id',
358
+ how='left'
359
+ )
360
+
361
+ return recommendations[['movie_id', 'title', 'predicted_rating']]
362
+
363
+ def recommend_movies_neural(self, user_id: int, N: int = 10) -> pd.DataFrame:
364
+ """
365
+ Generate recommendations using neural embeddings
366
+
367
+ Args:
368
+ user_id: ID of the user to recommend for
369
+ N: Number of movies to recommend
370
+
371
+ Returns:
372
+ DataFrame with recommended movies
373
+ """
374
+ if user_id not in self.user_item_matrix.index:
375
+ return pd.DataFrame(columns=['movie_id', 'title', 'predicted_rating'])
376
+
377
+ # Get user's ratings
378
+ user_ratings = self.user_item_matrix.loc[user_id]
379
+ rated_movies = set(user_ratings[user_ratings > 0].index)
380
+
381
+ # Get all movies
382
+ all_movies = set(self.user_item_matrix.columns)
383
+ unrated_movies = all_movies - rated_movies
384
+
385
+ if not unrated_movies:
386
+ return pd.DataFrame(columns=['movie_id', 'title', 'predicted_rating'])
387
+
388
+ # Predict ratings for unrated movies
389
+ predictions = []
390
+ for movie_id in unrated_movies:
391
+ predicted_rating = self._predict_rating_neural(user_id, movie_id)
392
+ if predicted_rating > 0:
393
+ predictions.append({
394
+ 'movie_id': movie_id,
395
+ 'predicted_rating': predicted_rating
396
+ })
397
+
398
+ if not predictions:
399
+ return pd.DataFrame(columns=['movie_id', 'title', 'predicted_rating'])
400
+
401
+ # Sort by predicted rating and get top-N
402
+ predictions_df = pd.DataFrame(predictions)
403
+ predictions_df = predictions_df.sort_values('predicted_rating', ascending=False).head(N)
404
+
405
+ # Merge with movie information
406
+ recommendations = predictions_df.merge(
407
+ self.movies[['movie_id', 'title']],
408
+ on='movie_id',
409
+ how='left'
410
+ )
411
+
412
+ return recommendations[['movie_id', 'title', 'predicted_rating']]
413
+
414
+ def recommend_movies(self, user_id: int, N: int = 10, method: str = "ensemble") -> pd.DataFrame:
415
+ """
416
+ Main recommendation function with the required signature
417
+
418
+ Args:
419
+ user_id: ID of the user to recommend for
420
+ N: Number of movies to recommend
421
+ method: Recommendation method ('user_based', 'item_based', 'svd', 'neural', 'ensemble')
422
+
423
+ Returns:
424
+ DataFrame with recommended movies
425
+ """
426
+ if method == "user_based":
427
+ return self.recommend_movies_user_based(user_id, N)
428
+ elif method == "item_based":
429
+ return self.recommend_movies_item_based(user_id, N)
430
+ elif method == "svd":
431
+ return self.recommend_movies_svd(user_id, N)
432
+ elif method == "neural":
433
+ return self.recommend_movies_neural(user_id, N)
434
+ elif method == "ensemble":
435
+ return self._ensemble_recommendations(user_id, N)
436
+ else:
437
+ raise ValueError("Method must be 'user_based', 'item_based', 'svd', 'neural', or 'ensemble'")
438
+
439
+ def _ensemble_recommendations(self, user_id: int, N: int) -> pd.DataFrame:
440
+ """Combine recommendations from all methods"""
441
+ # Get recommendations from all methods
442
+ user_recs = self.recommend_movies_user_based(user_id, N*2)
443
+ item_recs = self.recommend_movies_item_based(user_id, N*2)
444
+ svd_recs = self.recommend_movies_svd(user_id, N*2)
445
+ neural_recs = self.recommend_movies_neural(user_id, N*2)
446
+
447
+ # Combine and average scores
448
+ all_recs = []
449
+ movie_scores = {}
450
+
451
+ for recs in [user_recs, item_recs, svd_recs, neural_recs]:
452
+ for _, rec in recs.iterrows():
453
+ movie_id = rec['movie_id']
454
+ score = rec['predicted_rating']
455
+
456
+ if movie_id not in movie_scores:
457
+ movie_scores[movie_id] = []
458
+ movie_scores[movie_id].append(score)
459
+
460
+ # Average scores for each movie
461
+ for movie_id, scores in movie_scores.items():
462
+ avg_score = np.mean(scores)
463
+ all_recs.append({
464
+ 'movie_id': movie_id,
465
+ 'predicted_rating': avg_score
466
+ })
467
+
468
+ # Sort by average score and get top-N
469
+ all_recs_df = pd.DataFrame(all_recs)
470
+ all_recs_df = all_recs_df.sort_values('predicted_rating', ascending=False).head(N)
471
+
472
+ # Merge with movie information
473
+ recommendations = all_recs_df.merge(
474
+ self.movies[['movie_id', 'title']],
475
+ on='movie_id',
476
+ how='left'
477
+ )
478
+
479
+ return recommendations[['movie_id', 'title', 'predicted_rating']]
480
+
481
+ def _predict_rating_user_based(self, user_id: int, movie_id: int, similar_users_idx: np.ndarray) -> float:
482
+ """Predict rating using user-based collaborative filtering"""
483
+ user_idx = list(self.user_item_matrix.index).index(user_id)
484
+ movie_idx = list(self.user_item_matrix.columns).index(movie_id)
485
+
486
+ numerator = 0.0
487
+ denominator = 0.0
488
+
489
+ for similar_user_idx in similar_users_idx:
490
+ similar_user_id = self.user_item_matrix.index[similar_user_idx]
491
+ rating = self.user_item_matrix.iloc[similar_user_idx, movie_idx]
492
+
493
+ if rating > 0:
494
+ similarity = self.user_similarity_matrix[user_idx, similar_user_idx]
495
+ user_mean = self.user_means[similar_user_id]
496
+
497
+ numerator += similarity * (rating - user_mean)
498
+ denominator += abs(similarity)
499
+
500
+ if denominator == 0:
501
+ return 0.0
502
+
503
+ user_mean = self.user_means[user_id]
504
+ predicted_rating = user_mean + (numerator / denominator)
505
+
506
+ return max(0.0, min(5.0, predicted_rating))
507
+
508
+ def _predict_rating_item_based(self, user_id: int, movie_id: int, top_k: int) -> float:
509
+ """Predict rating using item-based collaborative filtering"""
510
+ user_ratings = self.user_item_matrix.loc[user_id]
511
+ movie_idx = list(self.user_item_matrix.columns).index(movie_id)
512
+
513
+ # Get similar items
514
+ similarities = self.item_similarity_matrix[movie_idx]
515
+ similar_items_idx = np.argsort(similarities)[::-1][1:top_k+1]
516
+
517
+ numerator = 0.0
518
+ denominator = 0.0
519
+
520
+ for similar_item_idx in similar_items_idx:
521
+ similar_movie_id = self.user_item_matrix.columns[similar_item_idx]
522
+ rating = user_ratings[similar_movie_id]
523
+
524
+ if rating > 0:
525
+ similarity = similarities[similar_item_idx]
526
+ item_mean = self.item_means[similar_movie_id]
527
+
528
+ numerator += similarity * (rating - item_mean)
529
+ denominator += abs(similarity)
530
+
531
+ if denominator == 0:
532
+ return 0.0
533
+
534
+ item_mean = self.item_means[movie_id]
535
+ predicted_rating = item_mean + (numerator / denominator)
536
+
537
+ return max(0.0, min(5.0, predicted_rating))
538
+
539
+ def _predict_rating_svd(self, user_id: int, movie_id: int) -> float:
540
+ """Predict rating using SVD"""
541
+ user_idx = list(self.user_item_matrix.index).index(user_id)
542
+ movie_idx = list(self.user_item_matrix.columns).index(movie_id)
543
+
544
+ # Transform user and movie to latent space
545
+ user_latent = self.svd_model.transform(self.user_item_matrix.iloc[user_idx:user_idx+1])[0]
546
+ movie_latent = self.svd_model.components_[:, movie_idx]
547
+
548
+ # Predict rating
549
+ predicted_rating = np.dot(user_latent, movie_latent)
550
+
551
+ return max(0.0, min(5.0, predicted_rating))
552
+
553
+ def _predict_rating_neural(self, user_id: int, movie_id: int) -> float:
554
+ """Predict rating using neural network"""
555
+ # Prepare input
556
+ X = np.array([[user_id, movie_id]])
557
+ X_scaled = self.scaler.transform(X)
558
+
559
+ # Predict
560
+ predicted_rating = self.neural_model.predict(X_scaled)[0]
561
+
562
+ return max(0.0, min(5.0, predicted_rating))
563
+
564
+ def evaluate_model(self, test_users: List[int], method: str = "ensemble", N: int = 10) -> Dict:
565
+ """
566
+ Evaluate model performance using precision@K, recall@K, and NDCG@K
567
+
568
+ Args:
569
+ test_users: List of user IDs to evaluate
570
+ method: Recommendation method to evaluate
571
+ N: Number of recommendations to generate
572
+
573
+ Returns:
574
+ Dictionary with evaluation metrics
575
+ """
576
+ print(f"Evaluating {method} method on {len(test_users)} users...")
577
+
578
+ precision_scores = []
579
+ recall_scores = []
580
+ ndcg_scores = []
581
+
582
+ for user_id in test_users:
583
+ try:
584
+ # Get recommendations
585
+ recommendations = self.recommend_movies(user_id, N=N, method=method)
586
+ recommended_movies = set(recommendations['movie_id'].values)
587
+
588
+ # Get actual liked movies (rating >= 4)
589
+ user_ratings = self.ratings[
590
+ (self.ratings['user_id'] == user_id) &
591
+ (self.ratings['rating'] >= 4)
592
+ ]
593
+ liked_movies = set(user_ratings['movie_id'].values)
594
+
595
+ if len(recommended_movies) == 0:
596
+ precision_scores.append(0.0)
597
+ recall_scores.append(0.0)
598
+ ndcg_scores.append(0.0)
599
+ continue
600
+
601
+ # Calculate precision@K
602
+ precision = len(recommended_movies & liked_movies) / len(recommended_movies)
603
+ precision_scores.append(precision)
604
+
605
+ # Calculate recall@K
606
+ if len(liked_movies) > 0:
607
+ recall = len(recommended_movies & liked_movies) / len(liked_movies)
608
+ else:
609
+ recall = 0.0
610
+ recall_scores.append(recall)
611
+
612
+ # Calculate NDCG@K
613
+ ndcg = self._calculate_ndcg(list(recommendations['movie_id']), liked_movies, N)
614
+ ndcg_scores.append(ndcg)
615
+
616
+ except Exception as e:
617
+ print(f"Error evaluating user {user_id}: {str(e)}")
618
+ precision_scores.append(0.0)
619
+ recall_scores.append(0.0)
620
+ ndcg_scores.append(0.0)
621
+
622
+ return {
623
+ 'precision': np.mean(precision_scores),
624
+ 'recall': np.mean(recall_scores),
625
+ 'ndcg': np.mean(ndcg_scores),
626
+ 'std_precision': np.std(precision_scores),
627
+ 'std_recall': np.std(recall_scores),
628
+ 'std_ndcg': np.std(ndcg_scores),
629
+ 'num_users': len(test_users)
630
+ }
631
+
632
+ def _calculate_ndcg(self, recommended: List[int], relevant: set, k: int = None) -> float:
633
+ """Calculate Normalized Discounted Cumulative Gain"""
634
+ if k is None:
635
+ k = len(recommended)
636
+
637
+ recommended = recommended[:k]
638
+
639
+ if len(relevant) == 0:
640
+ return 0.0
641
+
642
+ # Calculate DCG
643
+ dcg = 0.0
644
+ for i, movie_id in enumerate(recommended):
645
+ if movie_id in relevant:
646
+ dcg += 1.0 / np.log2(i + 2)
647
+
648
+ # Calculate IDCG
649
+ idcg = 0.0
650
+ for i in range(min(len(relevant), len(recommended))):
651
+ idcg += 1.0 / np.log2(i + 2)
652
+
653
+ if idcg == 0:
654
+ return 0.0
655
+
656
+ return dcg / idcg
657
+
658
+ def train_all_models(self):
659
+ """Train all recommendation models"""
660
+ print("Training all recommendation models...")
661
+
662
+ # Train user-based CF
663
+ self.train_user_based_cf()
664
+
665
+ # Train item-based CF
666
+ self.train_item_based_cf()
667
+
668
+ # Train SVD
669
+ self.train_svd(n_components=50)
670
+
671
+ # Train neural embeddings
672
+ self.train_neural_embeddings(hidden_layers=(100, 50), max_iter=50)
673
+
674
+ print("All models trained successfully!")
675
+
676
+ def save_model(self, filepath: str):
677
+ """Save the trained model"""
678
+ model_data = {
679
+ 'user_item_matrix': self.user_item_matrix,
680
+ 'user_similarity_matrix': self.user_similarity_matrix,
681
+ 'item_similarity_matrix': self.item_similarity_matrix,
682
+ 'svd_model': self.svd_model,
683
+ 'neural_model': self.neural_model,
684
+ 'scaler': self.scaler,
685
+ 'user_means': self.user_means,
686
+ 'item_means': self.item_means,
687
+ 'movies': self.movies,
688
+ 'ratings': self.ratings
689
+ }
690
+
691
+ with open(filepath, 'wb') as f:
692
+ pickle.dump(model_data, f)
693
+ print(f"Model saved to {filepath}")
694
+
695
+ def load_model(self, filepath: str):
696
+ """Load a trained model"""
697
+ with open(filepath, 'rb') as f:
698
+ model_data = pickle.load(f)
699
+
700
+ self.user_item_matrix = model_data['user_item_matrix']
701
+ self.user_similarity_matrix = model_data['user_similarity_matrix']
702
+ self.item_similarity_matrix = model_data['item_similarity_matrix']
703
+ self.svd_model = model_data['svd_model']
704
+ self.neural_model = model_data['neural_model']
705
+ self.scaler = model_data['scaler']
706
+ self.user_means = model_data['user_means']
707
+ self.item_means = model_data['item_means']
708
+ self.movies = model_data['movies']
709
+ self.ratings = model_data['ratings']
710
+
711
+ print(f"Model loaded from {filepath}")
712
+
713
+
714
+ def main():
715
+ """Main function to demonstrate the comprehensive recommendation system"""
716
+ print("Comprehensive Movie Recommendation System")
717
+ print("=" * 50)
718
+
719
+ # Initialize the system
720
+ rec_system = ComprehensiveRecommendationSystem()
721
+
722
+ # Load data
723
+ rec_system.load_data("MovieLensDataset_small/ml-100k/u.data", "MovieLensDataset_small/ml-100k/u.item")
724
+
725
+ # Train all models
726
+ rec_system.train_all_models()
727
+
728
+ # Test recommendations for a specific user
729
+ test_user = 1
730
+ print(f"\nRecommendations for User {test_user}:")
731
+ print("-" * 40)
732
+
733
+ # Test different methods
734
+ methods = ["user_based", "item_based", "svd", "neural", "ensemble"]
735
+
736
+ for method in methods:
737
+ print(f"\n{method.upper()} Method:")
738
+ recommendations = rec_system.recommend_movies(test_user, N=5, method=method)
739
+ print(recommendations)
740
+
741
+ # Evaluate models
742
+ print("\nEvaluating models...")
743
+ print("-" * 40)
744
+
745
+ # Select a sample of users for evaluation
746
+ all_users = rec_system.ratings['user_id'].unique()
747
+ test_users = np.random.choice(all_users, min(20, len(all_users)), replace=False)
748
+
749
+ for method in methods:
750
+ results = rec_system.evaluate_model(test_users, method=method, N=10)
751
+ print(f"{method:12} | Precision: {results['precision']:.4f} | Recall: {results['recall']:.4f} | NDCG: {results['ndcg']:.4f}")
752
+
753
+ # Save model
754
+ rec_system.save_model("comprehensive_recommendation_model.pkl")
755
+
756
+ return rec_system
757
+
758
+
759
+ if __name__ == "__main__":
760
+ rec_system = main()