Spaces:
Runtime error
Runtime error
| import gradio as gr | |
| import pandas as pd | |
| import numpy as np | |
| from scipy.sparse.linalg import svds | |
| from sklearn.metrics.pairwise import cosine_similarity | |
| import plotly.express as px | |
| import plotly.graph_objects as go | |
| from collections import Counter | |
| # Global variables for models | |
| movies = None | |
| ratings = None | |
| users = None | |
| train_user_item_matrix = None | |
| user_similarity_df = None | |
| svd_predicted_ratings = None | |
| alpha = 0.6 | |
| models_loaded = False | |
| def load_datasets(): | |
| """Load CSV datasets with multiple encoding support""" | |
| global movies, ratings, users | |
| try: | |
| encodings = ['utf-8', 'latin-1', 'iso-8859-1', 'cp1252'] | |
| delimiters = [',', '::', '\t', '|', ';'] | |
| movies = None | |
| ratings = None | |
| users = None | |
| # Load movies | |
| for enc in encodings: | |
| for delim in delimiters: | |
| try: | |
| movies = pd.read_csv('movies.csv', encoding=enc, sep=delim, | |
| engine='python', on_bad_lines='skip') | |
| if len(movies.columns) >= 2: | |
| break | |
| except: | |
| continue | |
| if movies is not None and len(movies.columns) >= 2: | |
| break | |
| # Load ratings | |
| for delim in delimiters: | |
| try: | |
| ratings = pd.read_csv('ratings.csv', sep=delim, engine='python', | |
| on_bad_lines='skip') | |
| if len(ratings.columns) >= 3: | |
| break | |
| except: | |
| continue | |
| # Load users | |
| for delim in delimiters: | |
| try: | |
| users = pd.read_csv('users.csv', sep=delim, engine='python', | |
| on_bad_lines='skip') | |
| if len(users.columns) >= 2: | |
| break | |
| except: | |
| continue | |
| if movies is None or ratings is None or users is None: | |
| return "Failed to load datasets. Check file formats." | |
| # Normalize column names | |
| movies.columns = movies.columns.str.strip().str.lower() | |
| ratings.columns = ratings.columns.str.strip().str.lower() | |
| users.columns = users.columns.str.strip().str.lower() | |
| if 'genres' in movies.columns: | |
| movies['genres'] = movies['genres'].fillna('Unknown') | |
| return f"Loaded: {len(movies)} movies, {len(ratings)} ratings, {len(users)} users" | |
| except Exception as e: | |
| return f"Error: {str(e)}" | |
| def train_models(): | |
| """Train recommendation models""" | |
| global train_user_item_matrix, user_similarity_df, svd_predicted_ratings, models_loaded | |
| if movies is None or ratings is None: | |
| return "Please load datasets first!" | |
| try: | |
| # Create train split | |
| train_data = [] | |
| for user_id in ratings['userid'].unique(): | |
| user_ratings = ratings[ratings['userid'] == user_id] | |
| if 'timestamp' in ratings.columns: | |
| user_ratings = user_ratings.sort_values('timestamp') | |
| n_ratings = len(user_ratings) | |
| if n_ratings >= 5: | |
| split_idx = int(n_ratings * 0.8) | |
| train_data.append(user_ratings.iloc[:split_idx]) | |
| train_ratings = pd.concat(train_data, ignore_index=True) | |
| # Create user-item matrix | |
| train_user_item_matrix = train_ratings.pivot_table( | |
| index='userid', | |
| columns='movieid', | |
| values='rating' | |
| ).fillna(0) | |
| # Train User-Based CF | |
| user_similarity = cosine_similarity(train_user_item_matrix) | |
| user_similarity_df = pd.DataFrame( | |
| user_similarity, | |
| index=train_user_item_matrix.index, | |
| columns=train_user_item_matrix.index | |
| ) | |
| # Train SVD | |
| n_factors = min(100, min(train_user_item_matrix.shape) - 1) | |
| R = train_user_item_matrix.values | |
| user_ratings_mean = np.mean(R, axis=1) | |
| R_demeaned = R - user_ratings_mean.reshape(-1, 1) | |
| U, sigma, Vt = svds(R_demeaned, k=n_factors) | |
| sigma = np.diag(sigma) | |
| predicted_ratings = np.dot(np.dot(U, sigma), Vt) + user_ratings_mean.reshape(-1, 1) | |
| svd_predicted_ratings = pd.DataFrame( | |
| predicted_ratings, | |
| index=train_user_item_matrix.index, | |
| columns=train_user_item_matrix.columns | |
| ) | |
| models_loaded = True | |
| return "Models trained successfully!" | |
| except Exception as e: | |
| return f"Error training models: {str(e)}" | |
| def load_and_train(): | |
| """Load datasets and train models""" | |
| msg1 = load_datasets() | |
| if "Loaded:" not in msg1: | |
| return msg1, None, None | |
| msg2 = train_models() | |
| # Get dataset stats | |
| stats_html = f""" | |
| <div style='background: #f0f2f6; padding: 20px; border-radius: 10px; margin: 10px 0;'> | |
| <h3 style='color: #FF4B4B; margin-bottom: 15px;'>Dataset Statistics</h3> | |
| <div style='display: grid; grid-template-columns: repeat(4, 1fr); gap: 15px;'> | |
| <div style='background: white; padding: 15px; border-radius: 8px; text-align: center;'> | |
| <div style='font-size: 24px; font-weight: bold; color: #FF4B4B;'>{len(movies):,}</div> | |
| <div style='color: #666; font-size: 14px;'>Movies</div> | |
| </div> | |
| <div style='background: white; padding: 15px; border-radius: 8px; text-align: center;'> | |
| <div style='font-size: 24px; font-weight: bold; color: #FF4B4B;'>{len(users):,}</div> | |
| <div style='color: #666; font-size: 14px;'>Users</div> | |
| </div> | |
| <div style='background: white; padding: 15px; border-radius: 8px; text-align: center;'> | |
| <div style='font-size: 24px; font-weight: bold; color: #FF4B4B;'>{len(ratings):,}</div> | |
| <div style='color: #666; font-size: 14px;'>Ratings</div> | |
| </div> | |
| <div style='background: white; padding: 15px; border-radius: 8px; text-align: center;'> | |
| <div style='font-size: 24px; font-weight: bold; color: #FF4B4B;'>{ratings['rating'].mean():.2f}</div> | |
| <div style='color: #666; font-size: 14px;'>Avg Rating</div> | |
| </div> | |
| </div> | |
| </div> | |
| """ | |
| # Create rating distribution chart | |
| rating_dist = ratings['rating'].value_counts().sort_index() | |
| fig = px.bar(x=rating_dist.index, y=rating_dist.values, | |
| labels={'x': 'Rating', 'y': 'Count'}, | |
| title='Rating Distribution', | |
| color=rating_dist.values, | |
| color_continuous_scale='Viridis') | |
| return f"{msg1}\n{msg2}", stats_html, fig | |
| def recommend_movies(user_id, num_recommendations): | |
| """Generate movie recommendations""" | |
| if not models_loaded: | |
| return "Please load and train models first!", None, None | |
| try: | |
| user_id = int(user_id) | |
| num_recommendations = int(num_recommendations) | |
| if user_id not in train_user_item_matrix.index: | |
| return f"User {user_id} not found in training data", None, None | |
| # CF recommendations | |
| similar_users = user_similarity_df[user_id].sort_values(ascending=False)[1:51] | |
| user_ratings = train_user_item_matrix.loc[user_id] | |
| watched_movies = user_ratings[user_ratings > 0].index | |
| cf_recommendations = {} | |
| for sim_user, similarity in similar_users.items(): | |
| sim_user_ratings = train_user_item_matrix.loc[sim_user] | |
| for movie_id, rating in sim_user_ratings.items(): | |
| if rating > 0 and movie_id not in watched_movies: | |
| if movie_id not in cf_recommendations: | |
| cf_recommendations[movie_id] = 0 | |
| cf_recommendations[movie_id] += similarity * rating | |
| cf_top = sorted(cf_recommendations.items(), key=lambda x: x[1], reverse=True)[:num_recommendations*2] | |
| cf_movies = [movie_id for movie_id, _ in cf_top] | |
| # SVD recommendations | |
| user_pred_ratings = svd_predicted_ratings.loc[user_id] | |
| unwatched_predictions = user_pred_ratings.drop(watched_movies) | |
| svd_movies = unwatched_predictions.sort_values(ascending=False).head(num_recommendations*2).index.tolist() | |
| # Combine | |
| combined_scores = {} | |
| for i, movie_id in enumerate(cf_movies): | |
| combined_scores[movie_id] = combined_scores.get(movie_id, 0) + alpha * (len(cf_movies) - i) | |
| for i, movie_id in enumerate(svd_movies): | |
| combined_scores[movie_id] = combined_scores.get(movie_id, 0) + (1 - alpha) * (len(svd_movies) - i) | |
| top_movies = sorted(combined_scores.items(), key=lambda x: x[1], reverse=True)[:num_recommendations] | |
| movie_ids = [movie_id for movie_id, _ in top_movies] | |
| # Get movie details | |
| recommendations = [] | |
| for i, movie_id in enumerate(movie_ids, 1): | |
| movie_info = movies[movies['movieid'] == movie_id] | |
| if not movie_info.empty: | |
| title = movie_info.iloc[0]['title'] | |
| genres = movie_info.iloc[0].get('genres', 'Unknown') | |
| recommendations.append({ | |
| 'Rank': i, | |
| 'Title': title, | |
| 'Genres': genres | |
| }) | |
| # Create HTML output | |
| html_output = f""" | |
| <div style='background: #f8f9fa; padding: 20px; border-radius: 10px;'> | |
| <h2 style='color: #FF4B4B; margin-bottom: 20px;'>Top {num_recommendations} Recommendations for User {user_id}</h2> | |
| """ | |
| for rec in recommendations: | |
| html_output += f""" | |
| <div style='background: white; padding: 15px; margin: 10px 0; border-radius: 8px; border-left: 4px solid #FF4B4B;'> | |
| <h3 style='color: #1f1f1f; margin: 0 0 10px 0;'>{rec['Rank']}. {rec['Title']}</h3> | |
| <p style='color: #666; margin: 0;'><strong>Genres:</strong> {rec['Genres']}</p> | |
| </div> | |
| """ | |
| html_output += "</div>" | |
| # Create visualizations | |
| user_ratings_data = ratings[ratings['userid'] == user_id] | |
| # Rating distribution | |
| rating_dist = user_ratings_data['rating'].value_counts().sort_index() | |
| fig1 = px.bar(x=rating_dist.index, y=rating_dist.values, | |
| labels={'x': 'Rating', 'y': 'Count'}, | |
| title=f'User {user_id} Rating Distribution', | |
| color=rating_dist.values, | |
| color_continuous_scale='Blues') | |
| # Genre preferences | |
| user_movies = user_ratings_data.merge(movies[['movieid', 'genres']], on='movieid') | |
| genres_list = [] | |
| for genres in user_movies['genres']: | |
| if pd.notna(genres) and genres != 'Unknown': | |
| genres_list.extend(genres.split('|')) | |
| if genres_list: | |
| genre_counts = Counter(genres_list) | |
| top_genres = dict(sorted(genre_counts.items(), key=lambda x: x[1], reverse=True)[:8]) | |
| fig2 = px.pie(values=list(top_genres.values()), names=list(top_genres.keys()), | |
| title=f'User {user_id} Genre Preferences', | |
| color_discrete_sequence=px.colors.qualitative.Set3) | |
| else: | |
| fig2 = None | |
| return html_output, fig1, fig2 | |
| except Exception as e: | |
| return f"Error: {str(e)}", None, None | |
| def get_dataset_insights(): | |
| """Generate dataset insights""" | |
| if movies is None or ratings is None: | |
| return "Please load datasets first!", None, None | |
| # Genre analysis | |
| all_genres = [] | |
| for genres in movies['genres']: | |
| if pd.notna(genres) and genres != 'Unknown': | |
| all_genres.extend(genres.split('|')) | |
| genre_counts = Counter(all_genres) | |
| top_genres = dict(sorted(genre_counts.items(), key=lambda x: x[1], reverse=True)[:15]) | |
| fig1 = px.bar(x=list(top_genres.values()), y=list(top_genres.keys()), | |
| orientation='h', | |
| labels={'x': 'Number of Movies', 'y': 'Genre'}, | |
| title='Top 15 Genres by Movie Count', | |
| color=list(top_genres.values()), | |
| color_continuous_scale='Teal') | |
| # User activity | |
| user_activity = ratings.groupby('userid').size() | |
| fig2 = px.histogram(user_activity, nbins=50, | |
| labels={'value': 'Number of Ratings', 'count': 'Number of Users'}, | |
| title='User Activity Distribution', | |
| color_discrete_sequence=['coral']) | |
| stats = f""" | |
| <div style='background: #f0f2f6; padding: 20px; border-radius: 10px;'> | |
| <h3 style='color: #FF4B4B;'>Insights</h3> | |
| <p><strong>Most Popular Genre:</strong> {list(top_genres.keys())[0]}</p> | |
| <p><strong>Average User Activity:</strong> {user_activity.mean():.1f} ratings</p> | |
| <p><strong>Most Active User:</strong> {user_activity.max()} ratings</p> | |
| <p><strong>Total Unique Movies Rated:</strong> {ratings['movieid'].nunique()}</p> | |
| </div> | |
| """ | |
| return stats, fig1, fig2 | |
| # Create Gradio Interface | |
| with gr.Blocks(title="DataSynthis Movie Recommender", theme=gr.themes.Soft()) as app: | |
| gr.Markdown(""" | |
| # DataSynthis Movie Recommendation System | |
| ### Powered by Hybrid Collaborative Filtering & Matrix Factorization | |
| """) | |
| with gr.Tabs(): | |
| # Tab 1: Setup | |
| with gr.Tab("Setup & Load Data"): | |
| gr.Markdown("### Step 1: Load Datasets and Train Models") | |
| gr.Markdown("Click the button below to load your CSV files and train the recommendation models.") | |
| load_btn = gr.Button("Load Datasets & Train Models", variant="primary", size="lg") | |
| status_output = gr.Textbox(label="Status", lines=2) | |
| stats_output = gr.HTML(label="Dataset Statistics") | |
| chart_output = gr.Plot(label="Rating Distribution") | |
| load_btn.click( | |
| fn=load_and_train, | |
| outputs=[status_output, stats_output, chart_output] | |
| ) | |
| # Tab 2: Recommendations | |
| with gr.Tab("Get Recommendations"): | |
| gr.Markdown("### Generate Personalized Movie Recommendations") | |
| with gr.Row(): | |
| with gr.Column(scale=2): | |
| user_id_input = gr.Number(label="Enter User ID", value=1, precision=0) | |
| with gr.Column(scale=1): | |
| num_recs_input = gr.Slider(minimum=5, maximum=20, value=10, step=1, | |
| label="Number of Recommendations") | |
| recommend_btn = gr.Button("Generate Recommendations", variant="primary", size="lg") | |
| recommendations_output = gr.HTML(label="Recommendations") | |
| with gr.Row(): | |
| rating_chart = gr.Plot(label="User Rating Distribution") | |
| genre_chart = gr.Plot(label="Genre Preferences") | |
| recommend_btn.click( | |
| fn=recommend_movies, | |
| inputs=[user_id_input, num_recs_input], | |
| outputs=[recommendations_output, rating_chart, genre_chart] | |
| ) | |
| # Tab 3: Insights | |
| with gr.Tab("Dataset Insights"): | |
| gr.Markdown("### Explore Dataset Analytics") | |
| insights_btn = gr.Button("Generate Insights", variant="primary") | |
| insights_stats = gr.HTML(label="Statistics") | |
| with gr.Row(): | |
| genre_plot = gr.Plot(label="Popular Genres") | |
| activity_plot = gr.Plot(label="User Activity") | |
| insights_btn.click( | |
| fn=get_dataset_insights, | |
| outputs=[insights_stats, genre_plot, activity_plot] | |
| ) | |
| # Tab 4: About | |
| with gr.Tab("About"): | |
| gr.Markdown(""" | |
| ## DataSynthis Movie Recommendation System | |
| This intelligent recommendation system uses advanced machine learning algorithms to provide | |
| personalized movie suggestions based on user preferences and viewing history. | |
| ### Features: | |
| - **Hybrid Approach**: Combines User-Based Collaborative Filtering and SVD Matrix Factorization | |
| - **High Accuracy**: Trained on comprehensive movie rating datasets | |
| - **Real-Time Predictions**: Instant recommendations for any user | |
| - **Interactive Visualizations**: Understand user behavior and preferences | |
| ### Algorithms Used: | |
| 1. **User-Based Collaborative Filtering**: Finds similar users and recommends movies they enjoyed | |
| 2. **SVD Matrix Factorization**: Discovers latent patterns in rating data | |
| 3. **Hybrid Ensemble**: Weighted combination (60% CF, 40% SVD) for optimal results | |
| ### Technology Stack: | |
| - Python, Gradio, Scikit-learn, Pandas, NumPy, Plotly | |
| --- | |
| **Developed for DataSynthis ML Job Task** | |
| """) | |
| gr.Markdown(""" | |
| --- | |
| <div style='text-align: center; color: #666;'> | |
| <p>DataSynthis Movie Recommendation System | Deployed on Hugging Face Spaces</p> | |
| <p>Built with Gradio</p> | |
| </div> | |
| """) | |
| # Launch the app | |
| if __name__ == "__main__": | |
| app.launch() |