Rijeet commited on
Commit
535daaa
·
verified ·
1 Parent(s): 4f11e18

Update comprehensive_recommendation_system.py

Browse files
Files changed (1) hide show
  1. comprehensive_recommendation_system.py +760 -760
comprehensive_recommendation_system.py CHANGED
@@ -1,760 +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()
 
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("u.data", "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()