From 7a93f74640a5cb282bee7eeadf45430c3fe76128 Mon Sep 17 00:00:00 2001 From: harmandeep2993 Date: Tue, 28 Apr 2026 01:58:18 +0200 Subject: [PATCH 1/2] chore: project name changed and requirements.txt file added --- README.md | 12 ++++++------ frontend/app.py | 8 ++++---- requirements.txt | Bin 0 -> 9142 bytes 3 files changed, 10 insertions(+), 10 deletions(-) create mode 100644 requirements.txt diff --git a/README.md b/README.md index d25beae..447c52d 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ -# CinePick 🎬 +# CineMatch 🎬 > Your personal AI-powered movie recommendation engine -CinePick is an end-to-end machine learning system that delivers personalized movie recommendations using SVD collaborative filtering, trained on the MovieLens 1M dataset. Built with MLOps best practices from data versioning and experiment tracking to containerized cloud deployment. +CineMatch is an end-to-end machine learning system that delivers personalized movie recommendations using SVD collaborative filtering, trained on the MovieLens 1M dataset. Built with MLOps best practices from data versioning and experiment tracking to containerized cloud deployment. --- @@ -42,7 +42,7 @@ SVD Matrix Factorization (50 latent factors) ↓ FastAPI REST API ↓ -CinePick Streamlit UI +CineMatch Streamlit UI ``` --- @@ -95,7 +95,7 @@ movie-recommendation-mlops-end-to-end/ │ └── services.py → API business logic │ ├── frontend/ -│ └── app.py → CinePick Streamlit UI +│ └── app.py → CineMatch Streamlit UI │ ├── tests/ │ ├── test_data.py → data pipeline tests @@ -266,8 +266,8 @@ UI available at: `http://localhost:8501` ### Run with Docker ```bash -docker build -t cinepick . -docker run -p 8000:8000 cinepick +docker build -t CineMatch . +docker run -p 8000:8000 CineMatch ``` ### View MLflow experiments diff --git a/frontend/app.py b/frontend/app.py index 408a392..cf9e693 100644 --- a/frontend/app.py +++ b/frontend/app.py @@ -1,5 +1,5 @@ """ -Streamlit frontend for CinePick - Movie Recommendation System. +Streamlit frontend for CineMatch - Movie Recommendation System. """ import os @@ -15,7 +15,7 @@ TMDB_IMAGE_URL = "https://image.tmdb.org/t/p/w500" st.set_page_config( - page_title="CinePick", + page_title="CineMatch", page_icon="🍿", layout="wide", initial_sidebar_state="expanded" @@ -157,7 +157,7 @@ def get_genres(tmdb_data: dict, max_genres: int = 3) -> str: st.markdown("""

🍿

-

CinePick

+

CineMatch

discover what to watch next

""", unsafe_allow_html=True) @@ -342,7 +342,7 @@ def get_genres(tmdb_data: dict, max_genres: int = 3) -> str: st.markdown("""

🍿

-

Welcome to CinePick

+

Welcome to CineMatch

Your personal AI-powered movie discovery engine

diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..3ee33efd07f5a778a23f4b2ac120c19d3389633a GIT binary patch literal 9142 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 Date: Tue, 28 Apr 2026 02:16:48 +0200 Subject: [PATCH 2/2] chore: untrack notebooks and update .gitignore file --- .gitignore | 1 + notebooks/01_EDA.ipynb | 458 ------------------------------ notebooks/02_preprocess.ipynb | 324 --------------------- notebooks/03_train_model.ipynb | 502 --------------------------------- 4 files changed, 1 insertion(+), 1284 deletions(-) delete mode 100644 notebooks/01_EDA.ipynb delete mode 100644 notebooks/02_preprocess.ipynb delete mode 100644 notebooks/03_train_model.ipynb diff --git a/.gitignore b/.gitignore index ae2ed8f..268c11e 100644 --- a/.gitignore +++ b/.gitignore @@ -77,6 +77,7 @@ target/ # Jupyter Notebook .ipynb_checkpoints +notebooks/ # IPython profile_default/ diff --git a/notebooks/01_EDA.ipynb b/notebooks/01_EDA.ipynb deleted file mode 100644 index b2a91e5..0000000 --- a/notebooks/01_EDA.ipynb +++ /dev/null @@ -1,458 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "47069228", - "metadata": {}, - "source": [ - "## Exploratory Data Analysis (EDA) for Movie Recommendation System" - ] - }, - { - "cell_type": "markdown", - "id": "321a39af", - "metadata": {}, - "source": [ - "The dataset used in this project is the MovieLens 1M dataset, which contains 1 million ratings from 6000 users on 4000 movies. The dataset is available for download at [MovieLens](https://grouplens.org/datasets/movielens/1m/)." - ] - }, - { - "cell_type": "markdown", - "id": "0ee394e3", - "metadata": {}, - "source": [ - "## Importing Libraries and Loading Data\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "9b054d0e", - "metadata": {}, - "outputs": [], - "source": [ - "import os \n", - "import sys\n", - "\n", - "import pandas as pd\n", - "import matplotlib.pyplot as plt\n", - "import seaborn as sns" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "69630397", - "metadata": {}, - "outputs": [], - "source": [ - "os.chdir(\"../\")\n", - "sys.path.append(os.path.abspath(\"..\"))" - ] - }, - { - "cell_type": "markdown", - "id": "c0b6f1de", - "metadata": {}, - "source": [ - "> We have three main files in the dataset:\n", - "- `users.dat`: Contains user information\n", - "- `movies.dat`: Contains movie information\n", - "- `ratings.dat`: Contains user ratings for movies\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "361c77f5", - "metadata": {}, - "outputs": [], - "source": [ - "movies_path = os.path.join(\"data\", \"raw\", \"ml-1m\", \"movies.dat\")\n", - "rating_path = os.path.join(\"data\", \"raw\", \"ml-1m\", \"ratings.dat\")\n", - "users_path = os.path.join(\"data\", \"raw\", \"ml-1m\", \"users.dat\")\n", - "\n", - "\n", - "print(f\"movie path: {movies_path}\")\n", - "print(f\"rating path: {rating_path}\")\n", - "print(f\"user path: {users_path}\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "400e8662", - "metadata": {}, - "outputs": [], - "source": [ - "ratings_df = pd.read_csv(rating_path, sep=\"::\", engine=\"python\", header=None, names=[\"user_id\", \"movie_id\", \"rating\", \"timestamp\"])\n", - "ratings_df.head()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "10b05898", - "metadata": {}, - "outputs": [], - "source": [ - "movies_df = pd.read_csv(movies_path, sep=\"::\", engine=\"python\", header=None, names=[\"movieid\", \"title\", \"genres\"], encoding=\"latin-1\")\n", - "movies_df.head()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b1976848", - "metadata": {}, - "outputs": [], - "source": [ - "user_df = pd.read_csv(users_path, sep=\"::\", engine= \"python\", header= None, names=[\"user_id\",\"gender\",\"age\",\"occupation\", \"zipcode\"])\n", - "user_df.head()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "46a141e0", - "metadata": {}, - "outputs": [], - "source": [ - "print(\"ratings_df shape:\", ratings_df.shape)\n", - "print(\"movies_df shape:\", movies_df.shape)\n", - "print(\"user_df shape:\", user_df.shape)" - ] - }, - { - "cell_type": "markdown", - "id": "37039690", - "metadata": {}, - "source": [ - "- From the shape we can see that there are 1 million ratings, 6040 users and 3706 movies in the dataset.\n", - "- The ratings dataset has 4 columns: `UserID`, `MovieID`, `Rating`, and `Timestamp`. The movies dataset has 3 columns: `MovieID`, `Title`, and `Genres`. The users dataset has 5 columns: `UserID`, `Gender`, `Age`, `Occupation`, and `Zip-code`.\n", - "\n", - "## CHECKING NULL VALUES AND DUPLICATES" - ] - }, - { - "cell_type": "markdown", - "id": "634a1bef", - "metadata": {}, - "source": [ - "### NULL VALUES" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "00cb136d", - "metadata": {}, - "outputs": [], - "source": [ - "print(\"null values in ratings_df:\\n\", ratings_df.isnull().sum())\n", - "print(\"null values in movies_df:\\n\", movies_df.isnull().sum())\n", - "print(\"null values in user_Df: \\n\", user_df.isnull().sum())" - ] - }, - { - "cell_type": "markdown", - "id": "840956d2", - "metadata": {}, - "source": [ - "### DUPLICATES" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "23916fbb", - "metadata": {}, - "outputs": [], - "source": [ - "print(\"duplicates in ratings_df:\", ratings_df.duplicated().sum())\n", - "print(\"duplicates in movies_df:\", movies_df.duplicated().sum())\n", - "print(\"duplicates in user_df:\", user_df.duplicated().sum())" - ] - }, - { - "cell_type": "markdown", - "id": "83abfa25", - "metadata": {}, - "source": [ - "* No null values found in any of the datasets.\n", - "* No duplicates found in any of the datasets." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "e4bc6eaf", - "metadata": {}, - "outputs": [], - "source": [ - "print(ratings_df.dtypes)\n", - "print(\"\\n\")\n", - "print(movies_df.dtypes)\n", - "print(\"\\n\")\n", - "print(user_df.dtypes)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "11f9893e", - "metadata": {}, - "outputs": [], - "source": [ - "ratings_df[\"timestamp\"] = pd.to_datetime(ratings_df[\"timestamp\"] , unit=\"s\")\n", - "ratings_df.head()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "6418d2f5", - "metadata": {}, - "outputs": [], - "source": [ - "ratings_df.head()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "5505293b", - "metadata": {}, - "outputs": [], - "source": [ - "ratings_df.describe().round(2)" - ] - }, - { - "cell_type": "markdown", - "id": "7032f75f", - "metadata": {}, - "source": [ - "* average rating is 3.53 with a standard deviation of 0.98. The minimum rating is 1 and the maximum rating is 5.\n", - "* this means users generally tend to give higher ratings to movies, with a significant number of ratings being 4 or 5. The distribution of ratings is likely skewed towards the higher end of the scale, indicating that users are more inclined to rate movies positively." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b8a710f2", - "metadata": {}, - "outputs": [], - "source": [ - "plt.figure(figsize=(8, 5))\n", - "sns.histplot(ratings_df[\"rating\"], bins=5, kde=False)\n", - "plt.title(\"Distribution of Movie Ratings\") \n", - "plt.xlabel(\"Rating\")\n", - "plt.ylabel(\"Frequency\")" - ] - }, - { - "cell_type": "markdown", - "id": "5a2a1042", - "metadata": {}, - "source": [ - "* left skewed distribution of ratings, with a higher frequency of ratings around 4 and 5, and fewer ratings around 1 and 2. This suggests that users tend to rate movies positively, which is common in movie rating datasets." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "1ae04d68", - "metadata": {}, - "outputs": [], - "source": [ - "# Rating per user\n", - "ratings_per_user = ratings_df.groupby(\"user_id\")[\"rating\"].count()\n", - "ratings_per_user.sort_values(ascending=False)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "899a5bbe", - "metadata": {}, - "outputs": [], - "source": [ - "print(ratings_per_user.describe())" - ] - }, - { - "cell_type": "markdown", - "id": "0a7bd45b", - "metadata": {}, - "source": [ - "**Insight from ratings per user:**\n", - "- The average number of ratings per user is approximately 166.5, with a standard deviation of 200.5 which indicates a wide variation in the number of ratings given by different users.\n", - "- The minimum number of ratings given by a user is 20, while on other hands side maximum is 2314. \n", - "- This indicates that while some users are very active in rating movies, there is a wide variation in user engagement, with many users providing only a few ratings and a few users providing a large number of ratings.\n", - "- Good enough to make a recommendation system." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "e52abc70", - "metadata": {}, - "outputs": [], - "source": [ - "ratings_per_movie = ratings_df.groupby(\"movie_id\")[\"rating\"].count()\n", - "print(ratings_per_movie.describe())" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "1e734157", - "metadata": {}, - "outputs": [], - "source": [ - "ratings_per_movie.sort_values(ascending=False)" - ] - }, - { - "cell_type": "markdown", - "id": "9e98d05f", - "metadata": {}, - "source": [ - "* average ratings per movie is approximately 270 ratings per movie with std. of 384 which indicates a wide variation in the number of ratings received by different movies.\n", - "* minimum ratings is 1 and maximum is 3428 which indicates that while some movies are very popular and receive a large number of ratings, there are many movies that receive only a few ratings. \n", - "* This suggests that there may be a long tail distribution in the number of ratings per movie, with a small number of movies receiving a large proportion of the total ratings.\n", - "* 3883 movies in the dataseet but we have ratings for 3706 movies which means that there are 177 movies that have not received any ratings. * No rating means hard to recommend these movies based on user ratings. \n", - "\n", - "* problem with \"Popularity bias\" in recommendation system. \n", - " - Popular movies are more likely to be recommended, \n", - " - while less popular movies may be overlooked. \n", - "\n", - "* This can lead to a feedback loop where popular movies become even more popular, while less popular movies struggle to gain visibility and ratings. To mitigate this bias, recommendation systems can incorporate techniques such as diversity promotion, which encourages the system to recommend a wider range of movies, including those that are less popular but may still be of interest to users based on their preferences and viewing history." - ] - }, - { - "cell_type": "code", - "execution_count": 32, - "id": "6deb8da9", - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "plt.figure(figsize=(8, 5))\n", - "ratings_per_movie.plot(kind=\"hist\", bins=50)\n", - "plt.title(\"Number of Ratings per Movie Distribution\")\n", - "plt.xlabel(\"Number of Ratings\")\n", - "plt.ylabel(\"Number of Movies\")\n", - "plt.show()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "33f7f8e1", - "metadata": {}, - "outputs": [], - "source": [ - "genres = movies_df[\"genres\"].str.split(\"|\").explode()\n", - "print(genres.value_counts())" - ] - }, - { - "cell_type": "code", - "execution_count": 31, - "id": "3f1af11b", - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAA9oAAAJOCAYAAABIl3+mAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjgsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvwVt1zgAAAAlwSFlzAAAPYQAAD2EBqD+naQAAimlJREFUeJzt3QeYlNXZ+P8D0hQFVCJFEUuMLbaAUWyxELBEJWKM0Sj6YnlVbFiJiiUajb23xN5jXntBFGvsYoy9E8UCGAsoClLmf33PL2f+z467CPLszjO73891rbgzs7szTzvnPvd9ztOqVCqVgiRJkiRJykXrfH6NJEmSJEky0JYkSZIkKWdmtCVJkiRJypGBtiRJkiRJOTLQliRJkiQpRwbakiRJkiTlyEBbkiRJkqQcGWhLkiRJkpQjA21JkiRJknJkoC1JUg5atWoVjjvuOLfl99htt93Cwgsv7HaSJDVrBtqSpJp15ZVXxgCXr3/84x/feb5UKoVevXrF53/1q1+FWjNr1qxwxRVXhI033jgstthioX379mGZZZYJu+++e3juuedCEbz66qtxgOHf//53KJrZs2eHq6++Ovzyl78MXbt2DW3btg1LLLFEGDBgQLj00kvD9OnTq/0WJUnNVJtqvwFJkuZXhw4dwvXXXx822GCDOo8/8sgj4YMPPogBamP75ptvQps2bXL9fdttt10YNWpU2GijjcIf/vCHGGwT0P7tb38LV111VXj//ffDUkstFaodaB9//PFxMIBBgKJg+/36178O9913X1hvvfXCoYceGrp16xY+++yzeFzsu+++4emnnw6XXXZZtd+qJKkZMtCWJNW8LbfcMtx8883h3HPPrRPsEnz36dMn/Oc//2mSYD9Phx12WAyyzzrrrHDQQQfVee7YY4+Nj6thBx98cAyyzz777HDggQfWee6QQw4Jb731Vrj//vubfBNOnTo1dOzYscn/riSpaVk6Lkmqeb/73e/Cp59+Widw+vbbb8Pf//73sNNOOzUY8BBwUVpOxnvFFVcMp59+eiw3T37605+GTTbZpN6S5CWXXDJsv/32c5yj/eGHH4b/+Z//iZlU/saqq64aLr/88u/9PGThL7nkkljyXBlkY4EFFogZ2mw2+5///GfYYostQqdOneIc6M022yw89dRTdX6O98f7bKgEP1v+TXaacntK8n/+85/HgYTlllsulmJnf+43v/lN/H+2Uyrjf/jhh7/3M7777rth4MCBMejs2bNnOOGEE8rbnn/5+9tuu+13fm7atGmhc+fOYe+9927wd48fPz789a9/DZtvvvl3guxkhRVWiFntyv1KYM5+4vOy3/g7n3/+eZ3Xzc22SduH7ZEy6JStZ/fZvffeGzbccMO4DRZZZJGw1VZbhVdeeeV7t50kqfgMtCVJNY/Ap1+/fuGGG26oE8RMnjw57Ljjjt95PYHcNttsE7PCBGNnnnlmDLTJIg8fPrz8ut/+9rfh0UcfDRMmTKjz8wRYH330Ub2/O5k4cWJYd911wwMPPBCGDRsWzjnnnPDjH/84DB06NAZzc8J7nzlzZthll13m6vMTnBGw/etf/wqHH354OOaYY8K4ceNiOTfl0T/U22+/HQcTCPjPOOOMsOiii8bFzFIwSEn7AQccEP+f0vZrrrkmfq288srfO/ec7U4ge+qpp8aqA7L0fIHg9Pe//33cDpR6Z915551hypQp8fmG8HP8jTm9pj4E1RwD66+/ftxfzIW/7rrr4oDAjBkz5mnbZBFkU2I/cuTIcOSRR8bH2E4E1gyK/PnPf477jNcw/aGI890lSfOoJElSjbriiitIgZaeffbZ0vnnn19aZJFFSl9//XV87je/+U1pk002if/fu3fv0lZbbVX+udtuuy3+3Iknnljn922//falVq1ald5+++34/RtvvBFfd95559V53b777ltaeOGFy38LvO7YY48tfz906NBSjx49Sv/5z3/q/OyOO+5Y6ty5c52frXTwwQfH3/fPf/5zrrbDoEGDSu3atSu988475cc++uijuD022mij8mO8v/qa/rQdx40bV36MbcZjjz76aPmxSZMmldq3b1865JBDyo/dfPPN8XUPPfTQXL3XIUOGxNfvv//+5cdmz54d9w+f4ZNPPqmz7S+66KI6P7/NNtuUlllmmfgz37f9XnjhhTqPT58+Pf7+9JXdN4899lj8meuuu67Oz4waNeo7j8/ttknbdYMNNijNnDmz/PiXX35Z6tKlS2nPPfes87cmTJgQj43KxyVJtceMtiSpWdhhhx3iAlh33XVX+PLLL+O/DZWN33PPPbH8OmVjE0rJiZnJiOInP/lJWHPNNcNNN91Ufg2ZUkrSt95667DgggvW+/v5Hf/3f/8XX8P/M0c8fZEdJdP+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wIC83pskSZIkSS0j0L7iiivyfyeSJEmSJLXUQDsZO3ZseO211+L/r7rqqmGttdbK631JkiRJktRyAu1JkyaFHXfcMTz88MOhS5cu8bEvvvgibLLJJuHGG28MP/rRj/J+n5IkSZIkNd9Vx/fff//w5ZdfhldeeSV89tln8evll18OU6ZMCQcccEBub27WrFlxcbVll102LLjggmH55ZcPf/zjH+Piawn/P3LkyNCjR4/4mv79+4e33nqrzu/h/e28886hU6dOcWBg6NCh3u9bkiRJklScQHvUqFHhwgsvDCuvvHL5sVVWWSVccMEF4d57783tzf35z38OF110UTj//PNjiTrfn3rqqeG8884rv4bvzz333HDxxReHp59+OnTs2DEMHDgwTJs2rfwagmwGBe6///5w1113hUcffTTstddeub1PSZIkSZLmq3R89uzZoW3btt95nMd4Li9PPPFE2HbbbcNWW20Vv19mmWXCDTfcEJ555plyNvvss88ORx99dHwdrr766tCtW7dw2223xfJ2AnQGBp599tnQt2/f+BoC9S233DKcfvrpoWfPnrm9X0mSJEmSflBGe9NNNw0HHnhg+Oijj8qPffjhh+Hggw8Om222WW5bdb311gtjxowJb775Zvz+X//6V/jHP/4Rtthii/j9uHHjwoQJE2K5ePbWY+uss0548skn4/f8S7l4CrLB67klGRnwhkyfPj2Wwme/JEmSJElqlIw2pdzbbLNNzDD36tUrPjZ+/Pjw05/+NFx77bUhL0ceeWQMcFdaaaWwwAILxDnbJ510UiwFB0E2yGBn8X16jn+XWGKJOs+3adMmLLbYYuXX1Ofkk08Oxx9/fG6fRZIkSZLUMvygQJvg+vnnnw8PPPBAeP311+NjzNfOZpbz8Le//S1cd9114frrr4+3D3vhhRfCQQcdFMu9hwwZEhrTiBEjwvDhw8vfE/CnQQVJkiRJknIJtB988MEwbNiw8NRTT8UVvH/5y1/GL0yePDkGwyxKtuGGG4Y8HHbYYTGrzVxrrLbaauG9996L2WYC7e7du8fHJ06cGFcdT/h+zTXXjP/Pa7gdWdbMmTPjSuTp5+vTvn37+CVJkiRJUqPN0WbhsT333DMG2ZWYG7333nuHM888M+Tl66+/jnOpsyghTwuucdsvgmXmcWczz8y97tevX/yef7nH99ixY+sM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/vWvf4Xtt98+DgA2d2l/3HrrrWHw4MHhqquuCp9//nkoknS+Umnw4YcfhhkzZoRZs2aFokrv9/333w977bVXWGKJJUKnTp2q/bYK7aWXXgq77LJLOdhuagsvvHAMsnv27BkH26688sqaDrbTtfSiiy4KN9xwQ2hO0v748ssv6wSxRU4SpMGkv/3tb4VPKM3Ntv/kk09i3705VOu0atUqjBkzJmyzzTbhvffey+U4MtCu0QObA4CMw5tvvlk+OIp8YZlbCy64YBg2bFi49957w5133hmKiu3N+9t4441jmTVB6eabbx6eeuqpcme11hpkGqpTTz01juC/8847oU2bNrFBaA7HVdonDzzwQDjyyCPD1ltvHQOn1157rXCNXDpupk6dGhvioUOHxs55c8S2v+WWW2KjRiDC8bb//vuH3XbbLTbc1QiyCayp5iB4a9euXTwPllxyyTjokTrczRX746677go77bRTHPQ48MADw6KLLhqKdh7ffvvt8Xq7wQYbhF/84hfhuOOOCxMnTgxFxPt98MEHw0MPPRTP5eY2gNkYunbtGn784x+HsWPHxu+bui3lWkCQfc4558SKFrLbtR5sf/bZZ+G2224LTz/9dPy+yINTP+SaNWjQoLDJJpuE0047LVYgFb3vMn78+Hg9IElTy9ueY2rLLbcMffr0iX2r5557LtSi0n/P6QkTJsT4g2ljv/71r8uDIvP7y1UjZs+eHf+99dZbS2ussUapZ8+epfXWW6+0zz77lF8za9asUq349ttvG/yce+yxR2nXXXctffXVV+XPXQTpvXz22WelwYMHly6//PLSzJkzSxMnTiytssoqpfXXX7/0j3/8o/y6Ir33uTF27NjSzjvvXOrUqVPpL3/5S00eVw255ZZbSgsttFDp0EMPjefMRhttVFprrbVKEyZMKBXNww8/XNp8881Lv/71r0vvv/9+qbl69913Sz/+8Y9L5557bvz+ww8/LC266KKlYcOG1XldY59H6fh+4YUXSgsuuGDpD3/4Q53nx40bV/r9738fr7dXXHFFqTlInzl7bn/++eelLbbYonTyySfH77n+so/OOuus2O7MmDGjVG333XdfqWPHjqXTTz89nrv7779/aZFFFin93//9X6mo7dxvf/vbUqtWrWL7QHuh0ve2LbSt7Nd//etfVdlc6ZrDNYnrMO1F9tyvtbYdf/3rX0sLL7xwPKebi2eeeabUoUOH0lFHHVXafvvt4zk2aNCg0r///e9C9124tv7ud78r7bXXXjV7PL300kulLl26lP785z/HftUvf/nL0i9+8YvSo48+WqoFV111Venpp58uf0/7v/LKK8c+ydVXX53bfjHQrjH33ntvvFCed955pXfeead0xhlnxAZ8p512Kr+mqBeW5I9//GNp0qRJ5e//9Kc/lc4888zSiy++WOcE+NGPflR6++23C/eZHnjggdLAgQNLW265Zem1114rP/7JJ5+UfvrTn8bO+OOPP174C2d20IAO9rRp0+L3b731VmnHHXeMn+Oaa64pv75I+2BejR8/vrTmmmuWLrzwwvK+ooE45JBDSkXDdma7L7XUUqWuXbuWpkyZEh8vQpCTt1deeSUOduC9994rLbnkkqW99967/PyTTz7ZZO+F62nbtm1Lxx13XJ3j/fzzz48DUG+++WazC7YZxLnkkktKH330Ufkz01FloIPj7oADDihtuOGGpeWWW660wAILxOC2WnhvBK0MwB522GHl87h3796l/fbbr/y66dOnl4q4nffdd98YEDz00EM1fz1tDC+//HIMPhKOSTru6ZhrigGK1CZW7hvaD4K3Wgm2K99X+jxff/113KZHHHFEsxjwof912mmnlU455ZTyY7Sdm2yySWnrrbcuTLDd0N8fNWpUqU2bNjUTmFYG2SeddFLp2GOPLT92//33x/Nkgw02KPRnmj17drzebLzxxuVjJNlll11iTMX1OvW95peBdg1h9H6rrbaKQSkIVnv16hWDPrLbjJon1b6wNOSxxx4r/eQnP4lB6n/+85/4GBd9goqf//zn8SAnewSCPb6K1pgR/DPSzsnIhQXpPfKZCOjIbj/11FOlokrv94477ij169cvVkissMIKpSuvvDJ2VF999dU4eEOn+7rrrivVOgYP+HzsH44vzps999yzzuDJp59+WioKBj+uv/76mN39zW9+U3681jtH6bgjK8mxR3DL4BT/v8wyy8TR/TSgwMDbtttuW2cArrFwvTz77LNLiy++eOnoo48uP05HonPnzqVHHnmkPDBAsE2H+6KLLirVOjqlbP8LLrggZu5AYMM1ul27djGTlwKLgw8+OHbSG6pEairbbbddzK7THtLupYwQbr/99nIgWy0NtVcEjrRntB1k4eb02pYg20e5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EwegKLdcZJFFwnrrrReGDh0aB6akPBx11FHhr3/9ayztJTCimiX55S9/GTPdyy67bCwZZ74tGW/KybOBumQbIuXDQFuSmok0L5PgmnmXf/7zn2M2NTsfWNU1a9asOvNkpbyQbWQdBs77rbbaqt4Bz5NPPjmWixNYk7WkTNwgW4ltiJQvS8clqZlIwXSfPn1i55lVhAm0DbKLI5s1dABEefrkk0/CRx99VC4FT8cXQfY333wT52GzRkOWC58pyzZEypd1QpLUzHTr1i0ce+yx8XYZlCurOLKDHg6AKA8MqqFz586hS5cudRbco4ICLIR42223fednXfhM9bENkfJhoC1JzRCL1ay99touRic108C6skqCxakWXXTReG/sl19+OT7GNAWy1jfeeGO49957q/J+VZtsQ6T55xxtSWqmuD2Lt1+Rmo/sfGoWPGMxs08//TQMHjw4bLfdduHdd9+NC1n9+Mc/jovtcd92bsnEvbSff/55M9iaJ7Yh0vwxoy1JzZRBttS8pCD7sMMOiyuMs7DZl19+GRdB23///eP9jbnjwHLLLRfuvvvucPXVV8dMN+s1UCaeSsmluWEbIs0fM9qSJEk1gntf77jjjuH222+P00PSHOx99tkn7LnnnuGUU06JK42Df7lnNlz4TJKalquOS5Ik1Yivv/46riBOppoMNVnuHXbYIZb57rHHHmGnnXYKq6++enxtuq0XK5C78JkkNS1LxyVJkgpowoQJcR72tddeGxc4mzx5clhiiSXCuHHj4u28WOwsZa+32WabuPjhW2+99Z3f4wr3ktT0zGhLkiQVzC233BIuu+yyuIgZWewZM2aEX/7yl+GII44Ie+21V/j9738fX8PCZyDgJoPtvFpJKgbnaEuSJBUIK4oTULPg2Zprrhn69OkTzjvvvLiCOGXgu+66a3j99dfjfO2TTz45ZqyvueaamAF/5plnYqZbklRdBtqSJEkFCrKHDRsWbrjhhnjLrqybbropnH766WGhhRYKBxxwQBgzZky4+eabQ69evWLZ+K233hratm0b524bbEtSdRloS5IkFcDDDz8cNt1003DccceFkSNHxuw1CJzTYmbnnntuOOaYY8IVV1wRA/EPPvggdOrUKSyyyCIxs+3q4pJUDC6GJkmSVADcB3uDDTaI87Ife+yxGDjzRZA9e/bs+Boy2UsvvXR44IEH4vfdu3ePgTav4zWuLi5JxWCgLUmSVAArrLBCXABt+vTp4aSTTgr/+Mc/vrNy+JQpU+KtvHr06BG/zwbW3OpLklQMXpElSZIKFGxTHk5gfeKJJ4bHH3+8zvPvvvtuvIf2uuuuG79P5eWSpGJxjrYkSVLBcD9sysQJpFl9fMMNN4zzr7fddtuYub799tvNYEtSgRloS5IkFTjYJrD+wx/+EM4888x4W68XXnghri7OnGzLxSWpmAy0JUmSChxsH3zwwWH06NFhueWWCy+99FIMsl1dXJKKzUBbkiSpwMhiX3jhhTGjzeJnBtmSVHwG2pIkSTXCIFuSaoOBtiRJkiRJOfL2XpIkSZIk5chAW5IkSZKkHBloS5IkSZKUIwNtSZIkSZJyZKAtSZIkSVKODLQlSZIkScqRgbYkSZIkSTky0JYkqRmbMGFCOPDAA8OPf/zj0KFDh9CtW7ew/vrrh4suuih8/fXX1X57kiQ1S22q/QYkSVLjePfdd2NQ3aVLl/CnP/0prLbaaqF9+/bhpZdeCpdeemlYcsklwzbbbNMof/vbb78N7dq1a5TfLUlS0ZnRliSpmdp3331DmzZtwnPPPRd22GGHsPLKK4flllsubLvttuHuu+8OW2+9dXzdF198EfbYY4/wox/9KHTq1Clsuumm4V//+lf59xx33HFhzTXXDNdcc01YZpllQufOncOOO+4Yvvzyy/JrNt544zBs2LBw0EEHha5du4aBAwfGx19++eWwxRZbhIUXXjhm03fZZZfwn//8pwpbQ5KkpmOgLUlSM/Tpp5+G0aNHh/322y907Nix3te0atUq/vub3/wmTJo0Kdx7771h7Nix4Wc/+1nYbLPNwmeffVZ+7TvvvBNuu+22cNddd8WvRx55JJxyyil1ft9VV10Vs9iPP/54uPjii2MAT9C+1lprxWB/1KhRYeLEiTHolySpObN0XJKkZujtt98OpVIprLjiinUeJ9s8bdq0+P8E4WS1n3nmmRhoU1aO008/PQbVf//738Nee+0VH5s9e3a48sorwyKLLBK/JzM9ZsyYcNJJJ5V/9worrBBOPfXU8vcnnnhiDLIpW08uv/zy0KtXr/Dmm2+Gn/zkJ428FSRJqg4DbUmSWhCCaoLmnXfeOUyfPj2WiH/11Vdh8cUXr/O6b775JmaxE0rGU5CNHj16xOA8q0+fPnW+53c/9NBDsWy8Er/bQFuS1FwZaEuS1Ayxyjil4W+88Uadx5mjjQUXXDD+S5BN0Pzwww9/53ewiFrStm3bOs/xuwnYsypL1PndZMz//Oc/f+d38zclSWquDLQlSWqGyFD/8pe/DOeff37Yf//9G5ynzXxsbgHGomlkrfPE7/6///u/+Hv5/ZIktRQuhiZJUjN14YUXhpkzZ4a+ffuGm266Kbz22msxw33ttdeG119/PSywwAKhf//+oV+/fmHQoEFx8bR///vf4YknnghHHXVUXMBsfjAHnAXVfve734Vnn302lovfd999Yffddw+zZs3K7XNKklQ0Di9LktRMLb/88uGf//xnXIxsxIgR4YMPPogLnq2yyirh0EMPjbf/ogT8nnvuiYE1AfAnn3wSunfvHjbaaKN4O6750bNnz7gC+RFHHBEGDBgQ54T37t07bL755qF1a8f6JUnNV6sSS5JKkiRJkqRcOJwsSZIkSVKODLQlSZIkScqRgbYkSZIkSTky0JYkSZIkKUcG2pIkSZIk5chAW5IkSZKkHBloS5IkSZKUIwNtSZIkSZJyZKAtSZIkSVKODLQlSZIkScqRgbYkSZIkSTky0JYkSZIkKeTn/wPLkn1Kh4g+4wAAAABJRU5ErkJggg==", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "plt.figure(figsize=(10, 6))\n", - "genres.value_counts().plot(kind=\"bar\", color=\"#1898f4\")\n", - "plt.title(\"Movie Count by Genre\")\n", - "plt.xlabel(\"Genre\")\n", - "plt.ylabel(\"Count\")\n", - "plt.xticks(rotation=45)\n", - "plt.tight_layout()\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "id": "ca99e3b7", - "metadata": {}, - "source": [ - "**Insight from movie genres:**\n", - "\n", - "* ``Drama`` and ``Comedy`` dominate\n", - "* Model will naturally recommend these more\n", - "* Another form of popularity bias\n", - "\n", - "* One movie can have multiple genres such as ``Action``, ``Adventure``, ``Sci-Fi``\n", - "*That is why total genre count > total movies" - ] - }, - { - "cell_type": "markdown", - "id": "a9f36156", - "metadata": {}, - "source": [ - "* Drama and Comedy dominate the dataset.\n", - "* This can cause genre bias in recommendations.\n", - "* Model will tend to recommend Drama and Comedy more often.\n", - "\n", - "#### EDA Summary So Far\n", - "\n", - "We have discovered these important things:\n", - "\n", - "- Dataset is clean — no nulls, no duplicates\n", - "- 1M ratings, 6040 users, 3883 movies\n", - "- Positive bias — users rate mostly 4 and 5\n", - "- No user cold start — min 20 ratings per user\n", - "- Movie cold start — some movies have 1 rating\n", - "- Long tail problem — few movies dominate\n", - "- Genre bias — Drama and Comedy dominate" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "movie-recommendation-system-mlops", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.12.10" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/notebooks/02_preprocess.ipynb b/notebooks/02_preprocess.ipynb deleted file mode 100644 index 79746b4..0000000 --- a/notebooks/02_preprocess.ipynb +++ /dev/null @@ -1,324 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "32c6db7c", - "metadata": {}, - "source": [ - "# PREPROCESSING AND EXPLORATORY DATA ANALYSIS (EDA)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "f60eed5b", - "metadata": {}, - "outputs": [ - { - "ename": "", - "evalue": "", - "output_type": "error", - "traceback": [ - "\u001b[1;31mThe kernel failed to start due to the missing module 'wcwidth'. Consider installing this module.\n", - "\u001b[1;31mClick here for more info." - ] - } - ], - "source": [ - "# Load necessary libraries\n", - "\n", - "import os \n", - "import sys\n", - "\n", - "import pandas as pd\n", - "import numpy as np\n", - "import scipy.sparse as sp\n", - "import json" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "cd39a653", - "metadata": {}, - "outputs": [], - "source": [ - "os.chdir(\"../\")\n", - "sys.path.append(os.path.abspath(\"..\"))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "e0660e90", - "metadata": {}, - "outputs": [], - "source": [ - "movies_path = os.path.join(\"data\", \"raw\", \"ml-1m\", \"movies.dat\")\n", - "rating_path = os.path.join(\"data\", \"raw\", \"ml-1m\", \"ratings.dat\")\n", - "users_path = os.path.join(\"data\", \"raw\", \"ml-1m\", \"users.dat\")\n", - "\n", - "ratings_df = pd.read_csv(rating_path, sep=\"::\", engine=\"python\", header=None, names=[\"user_id\", \"movie_id\", \"rating\", \"timestamp\"])\n", - "movies_df = pd.read_csv(movies_path, sep=\"::\", engine=\"python\", header=None, names=[\"movieid\", \"title\", \"genres\"], encoding=\"latin-1\")\n", - "user_df = pd.read_csv(users_path, sep=\"::\", engine= \"python\", header= None, names=[\"user_id\",\"gender\",\"age\",\"occupation\", \"zipcode\"])" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "09fa6d8a", - "metadata": {}, - "outputs": [], - "source": [ - "ratings_df.head()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "141c30bc", - "metadata": {}, - "outputs": [], - "source": [ - "movies_df.head()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "fac3000a", - "metadata": {}, - "outputs": [], - "source": [ - "user_df.head()" - ] - }, - { - "cell_type": "markdown", - "id": "d32bf63e", - "metadata": {}, - "source": [ - "## Rating Transformation" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "253c75ca", - "metadata": {}, - "outputs": [], - "source": [ - "ratings_df.head()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a94f1687", - "metadata": {}, - "outputs": [], - "source": [ - "ratings_count = ratings_df.groupby(\"rating\")[\"movie_id\"].count()\n", - "ratings_count" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "4ce2d71d", - "metadata": {}, - "outputs": [], - "source": [ - "movie_count = ratings_df.groupby(\"movie_id\")[\"rating\"].count()\n", - "movie_count" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "72ec7cc9", - "metadata": {}, - "outputs": [], - "source": [ - "print(f\"Ratings shape before filtering: {ratings_df.shape}\")\n", - "print(f\"Unique movies before filtering: {ratings_df['movie_id'].nunique()}\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "dd76212a", - "metadata": {}, - "outputs": [], - "source": [ - "valid_movies = movie_count[movie_count >= 10].index\n", - "ratings_df = ratings_df[ratings_df[\"movie_id\"].isin(valid_movies)]\n", - "\n", - "print(f\"Ratings shape after filtering: {ratings_df.shape}\")\n", - "print(f\"Unique movies after filtering: {ratings_df['movie_id'].nunique()}\")" - ] - }, - { - "cell_type": "markdown", - "id": "640ffaba", - "metadata": {}, - "source": [ - "## SPILT DATSET INTO TRAIN AND TEST" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "5b7f3ec1", - "metadata": {}, - "outputs": [], - "source": [ - "from sklearn.model_selection import train_test_split\n", - "\n", - "train_data, test_data = train_test_split(\n", - " ratings_df,\n", - " test_size=0.2,\n", - " random_state=42,\n", - " stratify=ratings_df[\"user_id\"]\n", - ")\n", - "\n", - "print(\"Train size:\", train_data.shape)\n", - "print(\"Test size:\", test_data.shape)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "4ac3381a", - "metadata": {}, - "outputs": [], - "source": [ - "user_ids = train_data[\"user_id\"].unique()\n", - "movie_ids = train_data[\"movie_id\"].unique()\n", - "\n", - "user_map = {uid: idx for idx, uid in enumerate(user_ids)}\n", - "item_map = {mid: idx for idx, mid in enumerate(movie_ids)}\n", - "\n", - "print(\"Total users:\", len(user_map))\n", - "print(\"Total movies:\", len(item_map))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "bf80ed9f", - "metadata": {}, - "outputs": [], - "source": [ - "type(user_map), type(item_map)" - ] - }, - { - "cell_type": "markdown", - "id": "8d6c6d9f", - "metadata": {}, - "source": [ - "## Build Matrix with User and Item Indexes" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c7f66480", - "metadata": {}, - "outputs": [], - "source": [ - "from scipy.sparse import csr_matrix\n", - "\n", - "row = train_data[\"user_id\"].map(user_map)\n", - "col = train_data[\"movie_id\"].map(item_map)\n", - "values = train_data[\"rating\"].values\n", - "\n", - "train_matrix = csr_matrix(\n", - " (values, (row, col)),\n", - " shape=(len(user_map), len(item_map))\n", - ")\n", - "\n", - "print(\"Matrix shape:\", train_matrix.shape)\n", - "print(\"Total ratings stored:\", train_matrix.nnz)\n", - "print(\"Sparsity:\", round(1 - train_matrix.nnz / (train_matrix.shape[0] * train_matrix.shape[1]), 4))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "796676ab", - "metadata": {}, - "outputs": [], - "source": [ - "total_matrix_size = len(user_map) * len(item_map)\n", - "print(f\"Total possible ratings: {total_matrix_size}\")\n", - "\n", - "total_ratings_stored = train_matrix.nnz\n", - "print(f\"Total ratings stored: {total_ratings_stored}\")\n", - "\n", - "empty_cells = total_matrix_size - total_ratings_stored\n", - "print(f\"Empty cells: {empty_cells}\")\n", - "\n", - "filled_percentage = total_ratings_stored / total_matrix_size * 100\n", - "print(f\"Filled percentage: {filled_percentage:.4f}%\")\n", - "\n", - "sparsity = (1 - train_matrix.nnz / total_matrix_size) * 100\n", - "print(f\"Sparsity: {sparsity:.4f}%\")" - ] - }, - { - "cell_type": "markdown", - "id": "46edb958", - "metadata": {}, - "source": [ - "* Matrix sparsity is 95.94% — only 4% of cells have ratings.\n", - "* This is the core challenge of recommendation systems." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "31a98bb5", - "metadata": {}, - "outputs": [], - "source": [ - "from scipy.sparse import save_npz\n", - "\n", - "os.makedirs(\"../data/processed\", exist_ok=True)\n", - "os.makedirs(\"../data/processed/mappings\", exist_ok=True)\n", - "\n", - "# save the sparse matrix\n", - "save_npz(\"../data/processed/train_matrix.npz\", train_matrix)\n", - "\n", - "# save test data\n", - "test_data.to_csv(\"../data/processed/test_data.csv\", index=False)\n", - "\n", - "# save mappings\n", - "pd.DataFrame(list(user_map.items()), columns=[\"user_id\", \"user_idx\"]).to_csv(\"../data/processed/mappings/user_map.csv\", index=False)\n", - "pd.DataFrame(list(item_map.items()), columns=[\"movie_id\", \"item_idx\"]).to_csv(\"../data/processed/mappings/item_map.csv\", index=False)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "movie-recommendation-system-mlops", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.12.10" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/notebooks/03_train_model.ipynb b/notebooks/03_train_model.ipynb deleted file mode 100644 index 4eba3b3..0000000 --- a/notebooks/03_train_model.ipynb +++ /dev/null @@ -1,502 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "576bc673", - "metadata": {}, - "outputs": [], - "source": [ - "import os\n", - "os.chdir(\"../\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "685c12ca", - "metadata": {}, - "outputs": [], - "source": [ - "import numpy as np\n", - "import pandas as pd\n", - "from scipy.sparse import load_npz\n", - "import mlflow\n", - "import mlflow.sklearn\n", - "from sklearn.neighbors import NearestNeighbors" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "10015a84", - "metadata": {}, - "outputs": [], - "source": [ - "# load train matrix\n", - "train_matrix = load_npz(\"../data/processed/train_matrix.npz\")\n", - "\n", - "# load mappings\n", - "user_map = pd.read_csv(\"../data/processed/mappings/user_map.csv\")\n", - "item_map = pd.read_csv(\"../data/processed/mappings/item_map.csv\")\n", - "\n", - "# load test data\n", - "test_data = pd.read_csv(\"../data/processed/test_data.csv\")\n", - "\n", - "print(\"Train matrix shape:\", train_matrix.shape)\n", - "print(\"Test data shape:\", test_data.shape)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "eea4693b", - "metadata": {}, - "outputs": [], - "source": [ - "if not os.path.exists(\"mlflow\"):\n", - " os.makedirs(\"mlflow\")\n", - "\n", - "mlflow.set_tracking_uri(\"sqlite:///mlflow.db\")\n", - "mlflow.set_experiment(\"movie-recommendation\")\n", - "\n", - "print(\"MLflow tracking URI set!\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c333818c", - "metadata": {}, - "outputs": [], - "source": [ - "from sklearn.metrics import mean_squared_error\n", - "\n", - "# convert item_map to dictionary for fast lookup\n", - "item_to_idx = dict(zip(item_map[\"movie_id\"], item_map[\"item_idx\"]))\n", - "idx_to_item = dict(zip(item_map[\"item_idx\"], item_map[\"movie_id\"]))\n", - "user_to_idx = dict(zip(user_map[\"user_id\"], user_map[\"user_idx\"]))\n", - "\n", - "# try different K values\n", - "k_values = [10, 20, 50]\n", - "\n", - "for k in k_values:\n", - " with mlflow.start_run(run_name=f\"ItemKNN_K{k}\"):\n", - " \n", - " # train model\n", - " model = NearestNeighbors(n_neighbors=k, metric=\"cosine\", algorithm=\"brute\")\n", - " model.fit(train_matrix.T)\n", - " \n", - " # log parameter\n", - " mlflow.log_param(\"k\", k)\n", - " mlflow.log_param(\"metric\", \"cosine\")\n", - " \n", - " print(f\"K={k} trained and logged to MLflow.\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "78ff0a03", - "metadata": {}, - "outputs": [], - "source": [ - "from sklearn.metrics import root_mean_squared_error\n", - "\n", - "for k in k_values:\n", - " with mlflow.start_run(run_name=f\"ItemKNN_K{k}_eval\"):\n", - " \n", - " # train model\n", - " model = NearestNeighbors(n_neighbors=k, metric=\"cosine\", algorithm=\"brute\")\n", - " model.fit(train_matrix.T)\n", - " \n", - " # log parameters\n", - " mlflow.log_param(\"k\", k)\n", - " mlflow.log_param(\"metric\", \"cosine\")\n", - " \n", - " # evaluate on test data\n", - " actuals = []\n", - " predictions = []\n", - " \n", - " for _, row in test_data.head(1000).iterrows():\n", - " user_idx = user_to_idx.get(row[\"user_id\"])\n", - " item_idx = item_to_idx.get(row[\"movie_id\"])\n", - " \n", - " if user_idx is None or item_idx is None:\n", - " continue\n", - " \n", - " # get similar items\n", - " distances, indices = model.kneighbors(\n", - " train_matrix.T[item_idx], \n", - " n_neighbors=k\n", - " )\n", - " \n", - " # predict as weighted average\n", - " similar_ratings = train_matrix[user_idx, indices[0]].toarray().flatten()\n", - " weights = 1 - distances[0]\n", - " \n", - " if weights.sum() > 0:\n", - " pred = np.average(similar_ratings, weights=weights)\n", - " else:\n", - " pred = train_matrix[user_idx].mean()\n", - " \n", - " actuals.append(row[\"rating\"])\n", - " predictions.append(pred)\n", - " \n", - " # calculate RMSE\n", - " rmse = root_mean_squared_error(actuals, predictions)\n", - " mlflow.log_metric(\"rmse\", rmse)\n", - " \n", - " print(f\"K={k} → RMSE: {rmse:.4f}\")" - ] - }, - { - "cell_type": "markdown", - "id": "f712fabc", - "metadata": {}, - "source": [ - "### SVD" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "1ee41391", - "metadata": {}, - "outputs": [], - "source": [ - "from scipy.sparse.linalg import svds\n", - "\n", - "with mlflow.start_run(run_name=\"SVD\"):\n", - " \n", - " # number of latent factors\n", - " n_factors = 50\n", - " \n", - " # train SVD\n", - " U, sigma, Vt = svds(train_matrix.astype(float), k=n_factors)\n", - " \n", - " # convert sigma to diagonal matrix\n", - " sigma = np.diag(sigma)\n", - " \n", - " # reconstruct full predictions matrix\n", - " predicted_ratings = np.dot(np.dot(U, sigma), Vt)\n", - " \n", - " # log parameter\n", - " mlflow.log_param(\"n_factors\", n_factors)\n", - " mlflow.log_param(\"model\", \"SVD\")\n", - " \n", - " print(f\"SVD trained and logged to MLflow.\")\n", - " print(f\"Predicted ratings matrix shape: {predicted_ratings.shape}\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "75b09a54", - "metadata": {}, - "outputs": [], - "source": [ - "\n", - "with mlflow.start_run(run_name=\"SVD_eval\"):\n", - " \n", - " mlflow.log_param(\"n_factors\", 50)\n", - " mlflow.log_param(\"model\", \"SVD\")\n", - " \n", - " actuals = []\n", - " predictions = []\n", - " \n", - " for _, row in test_data.head(1000).iterrows():\n", - " user_idx = user_to_idx.get(row[\"user_id\"])\n", - " item_idx = item_to_idx.get(row[\"movie_id\"])\n", - " \n", - " if user_idx is None or item_idx is None:\n", - " continue\n", - " \n", - " pred = predicted_ratings[user_idx, item_idx]\n", - " actuals.append(row[\"rating\"])\n", - " predictions.append(pred)\n", - " \n", - " rmse = root_mean_squared_error(actuals, predictions)\n", - " mlflow.log_metric(\"rmse\", rmse)\n", - " \n", - " print(f\"SVD → RMSE: {rmse:.4f}\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3ebfcff2", - "metadata": {}, - "outputs": [], - "source": [ - "import joblib\n", - "import os\n", - "\n", - "# train final model with best K\n", - "best_model = NearestNeighbors(n_neighbors=50, metric=\"cosine\", algorithm=\"brute\")\n", - "best_model.fit(train_matrix.T)\n", - "\n", - "# save model\n", - "os.makedirs(\"models\", exist_ok=True)\n", - "joblib.dump(best_model, \"models/itemknn_k50.joblib\")\n", - "\n", - "# save predicted ratings matrix\n", - "np.save(\"models/train_matrix_dense.npy\", train_matrix.toarray())\n", - "\n", - "print(\"Best model saved!\")" - ] - }, - { - "cell_type": "markdown", - "id": "3f562a3e", - "metadata": {}, - "source": [ - "### Predicted ratings matrix" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "762fbad2", - "metadata": {}, - "outputs": [], - "source": [ - "movies = pd.read_csv(\"data/raw/ml-1m/movies.dat\", sep='::', engine='python', header=None, names=['movie_id', 'title', 'genres'], encoding='latin-1')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "10ca36cf", - "metadata": {}, - "outputs": [], - "source": [ - "def recommend_movies(user_id, n_recommendations=10):\n", - " \n", - " # get user index\n", - " user_idx = user_to_idx.get(user_id)\n", - " if user_idx is None:\n", - " print(f\"User {user_id} not found!\")\n", - " return\n", - " \n", - " # get user ratings from train matrix\n", - " user_ratings = train_matrix[user_idx].toarray().flatten()\n", - " \n", - " # find movies user already watched\n", - " watched_movies = np.where(user_ratings > 0)[0]\n", - " \n", - " # find movies user has NOT watched\n", - " unwatched_movies = np.where(user_ratings == 0)[0]\n", - " \n", - " # predict ratings for unwatched movies\n", - " predicted_scores = []\n", - " \n", - " for item_idx in unwatched_movies:\n", - " # get similar movies to this unwatched movie\n", - " distances, indices = best_model.kneighbors(\n", - " train_matrix.T[item_idx],\n", - " n_neighbors=50\n", - " )\n", - " \n", - " # get user ratings for similar movies\n", - " similar_ratings = user_ratings[indices[0]]\n", - " weights = 1 - distances[0]\n", - " \n", - " if weights.sum() > 0 and similar_ratings.sum() > 0:\n", - " pred = np.average(similar_ratings, weights=weights)\n", - " else:\n", - " pred = 0\n", - " \n", - " predicted_scores.append((item_idx, pred))\n", - " \n", - " # sort by predicted score\n", - " predicted_scores.sort(key=lambda x: x[1], reverse=True)\n", - " \n", - " # get top N recommendations\n", - " top_n = predicted_scores[:n_recommendations]\n", - " \n", - " # convert back to movie titles\n", - " print(f\"\\nTop {n_recommendations} recommendations for User {user_id}:\\n\")\n", - " for item_idx, score in top_n:\n", - " movie_id = idx_to_item.get(item_idx)\n", - " title = movies[movies[\"movie_id\"] == movie_id][\"title\"].values\n", - " if len(title) > 0:\n", - " print(f\"→ {title[0]} (predicted score: {score:.2f})\")\n" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "id": "827aa342", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "Top 10 recommendations for User 1:\n", - "\n", - "→ Lion King, The (1994) (predicted score: 1.67)\n", - "→ Jungle Book, The (1967) (predicted score: 1.36)\n", - "→ Pinocchio (1940) (predicted score: 1.36)\n", - "→ Mary Poppins (1964) (predicted score: 1.31)\n", - "→ Prince of Egypt, The (1998) (predicted score: 1.30)\n", - "→ Sleeping Beauty (1959) (predicted score: 1.29)\n", - "→ Fantasia 2000 (1999) (predicted score: 1.28)\n", - "→ Lady and the Tramp (1955) (predicted score: 1.27)\n", - "→ Anastasia (1997) (predicted score: 1.26)\n", - "→ 101 Dalmatians (1961) (predicted score: 1.25)\n" - ] - } - ], - "source": [ - "# test with user 1\n", - "recommend_movies(1)" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "id": "bc572b0b", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "User 1 watched 42 movies:\n", - "\n", - "→ Airplane! (1980) (rated: 4)\n", - "→ Princess Bride, The (1987) (rated: 3)\n", - "→ Mulan (1998) (rated: 4)\n", - "→ E.T. the Extra-Terrestrial (1982) (rated: 4)\n", - "→ Ferris Bueller's Day Off (1986) (rated: 4)\n", - "→ Wizard of Oz, The (1939) (rated: 4)\n", - "→ Toy Story (1995) (rated: 5)\n", - "→ Bug's Life, A (1998) (rated: 5)\n", - "→ Back to the Future (1985) (rated: 5)\n", - "→ Erin Brockovich (2000) (rated: 4)\n" - ] - } - ], - "source": [ - "# see what user 1 already watched\n", - "user_idx = user_to_idx.get(1)\n", - "user_ratings = train_matrix[user_idx].toarray().flatten()\n", - "watched = np.where(user_ratings > 0)[0]\n", - "\n", - "print(f\"User 1 watched {len(watched)} movies:\\n\")\n", - "for item_idx in watched[:10]:\n", - " movie_id = idx_to_item.get(item_idx)\n", - " title = movies[movies[\"movie_id\"] == movie_id][\"title\"].values\n", - " rating = user_ratings[item_idx]\n", - " if len(title) > 0:\n", - " print(f\"→ {title[0]} (rated: {rating})\")" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "id": "22328c6e", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "Top 10 recommendations for User 100:\n", - "\n", - "→ Braveheart (1995) (predicted score: 1.52)\n", - "→ Star Trek: The Wrath of Khan (1982) (predicted score: 1.39)\n", - "→ Glory (1989) (predicted score: 1.38)\n", - "→ Star Trek IV: The Voyage Home (1986) (predicted score: 1.34)\n", - "→ Dances with Wolves (1990) (predicted score: 1.33)\n", - "→ Fugitive, The (1993) (predicted score: 1.29)\n", - "→ Thelma & Louise (1991) (predicted score: 1.28)\n", - "→ Die Hard (1988) (predicted score: 1.28)\n", - "→ Hunt for Red October, The (1990) (predicted score: 1.27)\n", - "→ L.A. Confidential (1997) (predicted score: 1.26)\n" - ] - } - ], - "source": [ - "recommend_movies(100)" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "id": "89644cae", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "User 100 watched 61 movies:\n", - "\n", - "→ Shawshank Redemption, The (1994) (rated: 4)\n", - "→ Star Wars: Episode VI - Return of the Jedi (1983) (rated: 4)\n", - "→ Princess Bride, The (1987) (rated: 4)\n", - "→ Star Wars: Episode V - The Empire Strikes Back (1980) (rated: 4)\n", - "→ Meet the Parents (2000) (rated: 3)\n", - "→ Fargo (1996) (rated: 3)\n", - "→ Conspiracy Theory (1997) (rated: 2)\n", - "→ GoodFellas (1990) (rated: 4)\n", - "→ Men in Black (1997) (rated: 2)\n", - "→ Indiana Jones and the Temple of Doom (1984) (rated: 3)\n" - ] - } - ], - "source": [ - "# see what user 1 already watched\n", - "user_idx = user_to_idx.get(100)\n", - "user_ratings = train_matrix[user_idx].toarray().flatten()\n", - "watched = np.where(user_ratings > 0)[0]\n", - "\n", - "print(f\"User 100 watched {len(watched)} movies:\\n\")\n", - "for item_idx in watched[:10]:\n", - " movie_id = idx_to_item.get(item_idx)\n", - " title = movies[movies[\"movie_id\"] == movie_id][\"title\"].values\n", - " rating = user_ratings[item_idx]\n", - " if len(title) > 0:\n", - " print(f\"→ {title[0]} (rated: {rating})\")\n" - ] - }, - { - "cell_type": "markdown", - "id": "ed625297", - "metadata": {}, - "source": [ - "* Model is working correctly!\n", - "* User 1 likes animated/family movies\n", - "* Model correctly recommends similar animated movies\n", - "* Predicted scores are low due to no rating normalization\n", - "* But ranking order is correct which is what matters" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "movie-recommendation-system-mlops", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.12.10" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -}