pranit144 commited on
Commit
95cb9a1
·
verified ·
1 Parent(s): 0e46fbb

Upload 6 files

Browse files
Files changed (6) hide show
  1. app.py +157 -0
  2. mlb.pkl +3 -0
  3. nn_model.pkl +3 -0
  4. templates/index.html +143 -0
  5. train_df.pkl +3 -0
  6. train_genre_features.pkl +3 -0
app.py ADDED
@@ -0,0 +1,157 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, request, render_template, jsonify
2
+ import pickle
3
+ import pandas as pd
4
+ from sklearn.neighbors import NearestNeighbors
5
+ import numpy as np
6
+
7
+ app = Flask(__name__)
8
+
9
+ # ---------------------------
10
+ # Load saved model components
11
+ # ---------------------------
12
+ with open("train_df.pkl", "rb") as f:
13
+ train_df = pickle.load(f)
14
+ with open("mlb.pkl", "rb") as f:
15
+ mlb = pickle.load(f)
16
+ with open("train_genre_features.pkl", "rb") as f:
17
+ train_genre_features = pickle.load(f)
18
+ # (Optional) Loading nn_model if needed:
19
+ # with open("nn_model.pkl", "rb") as f:
20
+ # nn_model = pickle.load(f)
21
+
22
+ # If train_df does not have a list of genres, create it from the 'genres' column
23
+ if "genre_list" not in train_df.columns:
24
+ train_df["genre_list"] = train_df["genres"].apply(lambda x: x.split("|"))
25
+
26
+ # Prepare a list of all genres (for the dropdown options)
27
+ all_genres = sorted({genre for sublist in train_df["genre_list"] for genre in sublist})
28
+
29
+ # Prepare rating options (0 to 5 in increments of 0.5)
30
+ rating_options = [str(i / 2) for i in range(0, 11)]
31
+ # Prepare recommendation number options (1 to 10)
32
+ recommendation_options = [str(i) for i in range(1, 11)]
33
+
34
+
35
+ # ---------------------------
36
+ # Recommendation function using the training model
37
+ # ---------------------------
38
+ def recommend_movies_train(input_genres, min_rating, max_rating, n_recommendations=5, use_filter=True):
39
+ """
40
+ Recommend movies using the training set model.
41
+
42
+ Parameters:
43
+ input_genres (str): Comma-separated string of genres (e.g., "Comedy, Drama")
44
+ min_rating (float): Minimum average rating.
45
+ max_rating (float): Maximum average rating.
46
+ n_recommendations (int): Number of recommendations.
47
+ use_filter (bool): Whether to filter the training set by genre string matching.
48
+
49
+ Returns:
50
+ pd.DataFrame: Recommended movies from the training set.
51
+ """
52
+ # Clean and process the input genres: replace any "|" with commas and split
53
+ cleaned_input = input_genres.replace("|", ",")
54
+ input_genre_list = [g.strip() for g in cleaned_input.split(',') if g.strip()]
55
+
56
+ if not input_genre_list:
57
+ return pd.DataFrame()
58
+
59
+ # Filter training movies by the given rating range
60
+ filtered_train = train_df[(train_df['avg_rating'] >= min_rating) & (train_df['avg_rating'] <= max_rating)]
61
+
62
+ # Optionally filter training movies to keep those that have one of the input genres
63
+ if use_filter:
64
+ genre_pattern = '|'.join(input_genre_list)
65
+ filtered_train = filtered_train[filtered_train['genres'].str.contains(genre_pattern, case=False, na=False)]
66
+
67
+ if filtered_train.empty:
68
+ return pd.DataFrame()
69
+
70
+ # Get indices of the filtered training data relative to the full training set
71
+ filtered_indices = filtered_train.index.to_numpy()
72
+
73
+ # Obtain the corresponding genre features from the training set features
74
+ filtered_features = train_genre_features[[list(train_df.index).index(i) for i in filtered_indices]]
75
+
76
+ # Create the input vector using the same MultiLabelBinarizer
77
+ input_vector = mlb.transform([input_genre_list])
78
+
79
+ # Fit a temporary nearest neighbors model on the filtered training data
80
+ nn_filtered = NearestNeighbors(metric='cosine')
81
+ nn_filtered.fit(filtered_features)
82
+
83
+ n_neighbors = min(n_recommendations, len(filtered_train))
84
+ distances, indices = nn_filtered.kneighbors(input_vector, n_neighbors=n_neighbors)
85
+
86
+ # Map relative indices back to the original training DataFrame indices
87
+ recommended_indices = filtered_indices[indices[0]]
88
+
89
+ return train_df.loc[recommended_indices][['movieId', 'title', 'avg_rating', 'genres']]
90
+
91
+
92
+ # ---------------------------
93
+ # Routes
94
+ # ---------------------------
95
+ @app.route("/", methods=["GET", "POST"])
96
+ def index():
97
+ if request.method == "POST":
98
+ # Get form inputs from dropdowns
99
+ selected_genres = request.form.getlist("genres")
100
+ # Join selected genres into a comma-separated string
101
+ input_genres = ", ".join(selected_genres)
102
+
103
+ try:
104
+ min_rating = float(request.form.get("min_rating", 0))
105
+ max_rating = float(request.form.get("max_rating", 5))
106
+ n_recommendations = int(request.form.get("n_recommendations", 5))
107
+ except ValueError:
108
+ return render_template("index.html", error="Invalid rating or recommendation number.",
109
+ all_genres=all_genres, rating_options=rating_options,
110
+ recommendation_options=recommendation_options)
111
+
112
+ if min_rating > max_rating:
113
+ return render_template("index.html", error="Minimum rating cannot be greater than maximum rating.",
114
+ all_genres=all_genres, rating_options=rating_options,
115
+ recommendation_options=recommendation_options)
116
+
117
+ # Get recommendations from the model
118
+ recommendations = recommend_movies_train(input_genres, min_rating, max_rating, n_recommendations)
119
+
120
+ if recommendations.empty:
121
+ message = "No movies found for the given criteria."
122
+ return render_template("index.html", message=message,
123
+ all_genres=all_genres, rating_options=rating_options,
124
+ recommendation_options=recommendation_options)
125
+ else:
126
+ # Convert DataFrame to HTML table for display
127
+ rec_html = recommendations.to_html(classes="table table-striped", index=False)
128
+ return render_template("index.html", recommendations=rec_html,
129
+ all_genres=all_genres, rating_options=rating_options,
130
+ recommendation_options=recommendation_options)
131
+
132
+ # GET request: pass dropdown options to template
133
+ return render_template("index.html", all_genres=all_genres, rating_options=rating_options,
134
+ recommendation_options=recommendation_options)
135
+
136
+
137
+ @app.route("/api/recommend", methods=["POST"])
138
+ def api_recommend():
139
+ data = request.get_json()
140
+ input_genres = data.get("genres", "")
141
+ try:
142
+ min_rating = float(data.get("min_rating", 0))
143
+ max_rating = float(data.get("max_rating", 5))
144
+ n_recommendations = int(data.get("n_recommendations", 5))
145
+ except ValueError:
146
+ return jsonify({"error": "Invalid rating or recommendation number."}), 400
147
+
148
+ recommendations = recommend_movies_train(input_genres, min_rating, max_rating, n_recommendations)
149
+ if recommendations.empty:
150
+ return jsonify({"message": "No movies found for the given criteria."})
151
+
152
+ result = recommendations.to_dict(orient="records")
153
+ return jsonify(result)
154
+
155
+
156
+ if __name__ == "__main__":
157
+ app.run(debug=True)
mlb.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:128c83e1bf2c6cb5555f256109c1ecbdd84e3acc31c208c0aaea2e8a9a2aa79c
3
+ size 585
nn_model.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b9d9eb8d14a4ef1c7492505c2b347537ce3d350700d79270bb36e5257a98ecc4
3
+ size 7558421
templates/index.html ADDED
@@ -0,0 +1,143 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>CineSage - Movie Recommendation System</title>
7
+
8
+ <!-- Bootstrap 5 CSS -->
9
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
10
+
11
+ <!-- Google Fonts -->
12
+ <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600&display=swap" rel="stylesheet">
13
+
14
+ <style>
15
+ body {
16
+ background-color: #f4f6f9;
17
+ font-family: 'Poppins', sans-serif;
18
+ }
19
+ .recommendation-container {
20
+ background-color: white;
21
+ border-radius: 12px;
22
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
23
+ padding: 30px;
24
+ margin-top: 20px;
25
+ }
26
+ .form-control {
27
+ border-radius: 8px;
28
+ transition: all 0.3s ease;
29
+ }
30
+ .form-control:focus {
31
+ box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25);
32
+ border-color: #0d6efd;
33
+ }
34
+ .btn-primary {
35
+ background-color: #0d6efd;
36
+ border: none;
37
+ border-radius: 8px;
38
+ transition: all 0.3s ease;
39
+ }
40
+ .btn-primary:hover {
41
+ background-color: #0b5ed7;
42
+ transform: translateY(-2px);
43
+ }
44
+ .movie-recommendations {
45
+ display: grid;
46
+ grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
47
+ gap: 20px;
48
+ }
49
+ .movie-card {
50
+ background-color: #f8f9fa;
51
+ border-radius: 12px;
52
+ padding: 15px;
53
+ transition: transform 0.3s ease;
54
+ }
55
+ .movie-card:hover {
56
+ transform: scale(1.03);
57
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
58
+ }
59
+ .rating-badge {
60
+ background-color: #28a745;
61
+ color: white;
62
+ padding: 5px 10px;
63
+ border-radius: 20px;
64
+ }
65
+ </style>
66
+ </head>
67
+ <body>
68
+ <div class="container py-5">
69
+ <div class="row justify-content-center">
70
+ <div class="col-lg-8">
71
+ <div class="recommendation-container">
72
+ <h1 class="text-center mb-4">CineSage 🎬 Movie Recommender</h1>
73
+
74
+ <!-- Error and Message Alerts -->
75
+ {% if error %}
76
+ <div class="alert alert-danger alert-dismissible fade show" role="alert">
77
+ {{ error }}
78
+ <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
79
+ </div>
80
+ {% endif %}
81
+
82
+ {% if message %}
83
+ <div class="alert alert-info alert-dismissible fade show" role="alert">
84
+ {{ message }}
85
+ <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
86
+ </div>
87
+ {% endif %}
88
+
89
+ <!-- Recommendation Form -->
90
+ <form method="POST" action="/">
91
+ <div class="row g-3">
92
+ <div class="col-md-6">
93
+ <label for="genres" class="form-label">Movie Genres</label>
94
+ <input type="text" class="form-control" name="genres" id="genres"
95
+ placeholder="Comedy, Drama, Sci-Fi" required>
96
+ <small class="form-text text-muted">Separate genres with commas</small>
97
+ </div>
98
+
99
+ <div class="col-md-3">
100
+ <label for="min_rating" class="form-label">Min Rating</label>
101
+ <input type="number" class="form-control" name="min_rating" id="min_rating"
102
+ min="0" max="5" step="0.5" required>
103
+ </div>
104
+
105
+ <div class="col-md-3">
106
+ <label for="max_rating" class="form-label">Max Rating</label>
107
+ <input type="number" class="form-control" name="max_rating" id="max_rating"
108
+ min="0" max="5" step="0.5" required>
109
+ </div>
110
+
111
+ <div class="col-12">
112
+ <label for="n_recommendations" class="form-label">Number of Recommendations</label>
113
+ <input type="number" class="form-control" name="n_recommendations"
114
+ id="n_recommendations" min="1" max="20" value="5" required>
115
+ </div>
116
+
117
+ <div class="col-12">
118
+ <button type="submit" class="btn btn-primary w-100 mt-3">
119
+ Discover Movies 🔍
120
+ </button>
121
+ </div>
122
+ </div>
123
+ </form>
124
+ </div>
125
+
126
+ <!-- Recommendations Section -->
127
+ {% if recommendations %}
128
+ <div class="mt-5">
129
+ <h2 class="text-center mb-4">Your Movie Recommendations</h2>
130
+ <div class="movie-recommendations">
131
+ {{ recommendations|safe }}
132
+ </div>
133
+ </div>
134
+ {% endif %}
135
+ </div>
136
+ </div>
137
+ </div>
138
+
139
+ <!-- Bootstrap 5 JS and Popper.js -->
140
+ <script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.6/dist/umd/popper.min.js"></script>
141
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.min.js"></script>
142
+ </body>
143
+ </html>
train_df.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:706077a31bac5b3d7da5eacf1f3c527ac3bf203dea5facc59bac57bde4b96d60
3
+ size 3629725
train_genre_features.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:08cfe0ced4914ac06c73fca723136d1c3f3ec4da94babe1d3bb7fd0b40ccad2a
3
+ size 7558083