Skip to content

Commit

Permalink
readme update
Browse files Browse the repository at this point in the history
  • Loading branch information
oyounis19 committed Jun 3, 2024
1 parent 6f44cab commit 951fc4d
Show file tree
Hide file tree
Showing 2 changed files with 29 additions and 25 deletions.
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
# NCF Recommender System with PyTorch

Welcome to the NCF Recommender System with PyTorch! In this project, An end-to-end Recommender System using an adjusted version of Neural Collaborative Filtering (NCF) with PyTorch. the model is trained on the [MovieLens-1M](https://grouplens.org/datasets/movielens/1m/) dataset 🎥, served using FastAPI, hosted on Heroku 🚀.
Welcome to the NCF Recommender System with PyTorch! In this project, An end-to-end Recommender System using an adjusted version of Neural Collaborative Filtering (NCF) with PyTorch. the model is trained on the [MovieLens-1M](https://grouplens.org/datasets/movielens/1m/) dataset 🎥, served using FastAPI, hosted on Streamlit 🚀.

## 🌟 Try it out!

- Website: [https://ncf-recommender.herokuapp.com/](https://ncf-recommender.herokuapp.com/)
- API: [https://ncf-recommender.herokuapp.com/docs](https://ncf-recommender.herokuapp.com/docs)
- Website: [https://ncf-recsys.streamlit.app/](https://ncf-recsys.streamlit.app/)

## 📓 Notebook on Kaggle

Expand All @@ -17,10 +16,11 @@ Welcome to the NCF Recommender System with PyTorch! In this project, An end-to-e

- Our adjusted architecture of NCF enables the input of the user/item features besides the user/item IDs.

- A recommender system is not just a ranking model, but a pipeline: Items Retrieval, Filtering, Ranking, and Ordering. (Detailed explanation in the notebook)
- Quick Reminder: A recommender system is not just a ranking model, but a pipeline consisting of: Items Retrieval, Filtering, Ranking, and Ordering. (Detailed explanation in the notebook)

## 📚 Project Structure

- `streamlit.py`: Streamlit app to interact with the model.
- `app/`:

- **main.py**: The FastAPI app to serve the model.
Expand All @@ -33,7 +33,7 @@ Welcome to the NCF Recommender System with PyTorch! In this project, An end-to-e
- `data/`: Processed data for inference.
- `weights/`: Pretrained models weights for inference.

# References
# 📖 References

- [Neural collaborative filtering Paper](https://arxiv.org/abs/1708.05031)
- [Medium: Recommender Systems, Not Just a Recommender Models](https://medium.com/nvidia-merlin/recommender-systems-not-just-recommender-models-485c161c755e)
Expand Down
44 changes: 24 additions & 20 deletions app/model/main.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@
},
"outputs": [],
"source": [
"\n",
"import pandas as pd\n",
"from IPython.display import display\n",
"from sklearn.model_selection import train_test_split\n",
Expand Down Expand Up @@ -909,7 +910,10 @@
")\n",
"\n",
"early_stopping = EarlyStopping(patience=3, delta=0.0002, path='weights/explicit.pth') # early stops after 2 consecutive epochs with minor loss decrease/increase\n",
"scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(model.optimizer, mode='min', factor=0.1, patience=0) # reduces learning rate by factor of 0.1 when no improvement is seen"
"scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(model.optimizer, mode='min', factor=0.1, patience=0) # reduces learning rate by factor of 0.1 when no improvement is seen\n",
"\n",
"# create weights folder\n",
"!mkdir -p weights"
]
},
{
Expand Down Expand Up @@ -938,26 +942,26 @@
"output_type": "stream",
"text": [
"Epoch 1/12\n",
"391/391 - loss: 0.0355 - Val Loss: 0.0325 - R2: 0.3527 - MAE: 0.1425 - MSE: 0.0325 - RMSE: 0.1803 - lr: 0.001\n",
"391/391 - loss: 0.0356 - Val Loss: 0.0327 - R2: 0.3487 - MAE: 0.1442 - MSE: 0.0327 - RMSE: 0.1809 - lr: 0.001\n",
"Epoch 2/12\n",
"391/391 - loss: 0.0317 - Val Loss: 0.0311 - R2: 0.3809 - MAE: 0.1398 - MSE: 0.0311 - RMSE: 0.1763 - lr: 0.001\n",
"Epoch 3/12\n",
"391/391 - loss: 0.0301 - Val Loss: 0.0303 - R2: 0.3977 - MAE: 0.1369 - MSE: 0.0303 - RMSE: 0.1739 - lr: 0.001\n",
"Epoch 4/12\n",
"391/391 - loss: 0.0291 - Val Loss: 0.0299 - R2: 0.4050 - MAE: 0.1368 - MSE: 0.0299 - RMSE: 0.1729 - lr: 0.001\n",
"Epoch 5/12\n",
"391/391 - loss: 0.0284 - Val Loss: 0.0295 - R2: 0.4128 - MAE: 0.1352 - MSE: 0.0295 - RMSE: 0.1717 - lr: 0.001\n",
"Epoch 6/12\n",
"391/391 - loss: 0.0278 - Val Loss: 0.0294 - R2: 0.4150 - MAE: 0.1348 - MSE: 0.0294 - RMSE: 0.1714 - lr: 0.001\n",
"Epoch 7/12\n",
"391/391 - loss: 0.0274 - Val Loss: 0.0292 - R2: 0.4183 - MAE: 0.1355 - MSE: 0.0292 - RMSE: 0.1709 - lr: 0.001\n",
"Epoch 8/12\n",
"391/391 - loss: 0.0269 - Val Loss: 0.0292 - R2: 0.4182 - MAE: 0.1340 - MSE: 0.0292 - RMSE: 0.1709 - lr: 0.0001\n",
"Epoch 9/12\n",
"391/391 - loss: 0.0247 - Val Loss: 0.0292 - R2: 0.4183 - MAE: 0.1337 - MSE: 0.0292 - RMSE: 0.1709 - lr: 1e-05\n",
"Epoch 10/12\n",
"391/391 - loss: 0.0241 - Val Loss: 0.0292 - R2: 0.4179 - MAE: 0.1336 - MSE: 0.0292 - RMSE: 0.1710 - lr: 1.0000000000000002e-06\n",
"Early stopping\n"
"136/391 - loss: 0.0319\r"
]
},
{
"ename": "KeyboardInterrupt",
"evalue": "",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mKeyboardInterrupt\u001b[0m Traceback (most recent call last)",
"Cell \u001b[0;32mIn[7], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m history \u001b[38;5;241m=\u001b[39m \u001b[43mmodel\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mfit\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 2\u001b[0m \u001b[43m \u001b[49m\u001b[43mX\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43m[\u001b[49m\u001b[43my_ratings_train\u001b[49m\u001b[43m[\u001b[49m\u001b[43m:\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m0\u001b[39;49m\u001b[43m]\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43my_ratings_train\u001b[49m\u001b[43m[\u001b[49m\u001b[43m:\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m1\u001b[39;49m\u001b[43m]\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mX_users_train\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mX_items_train\u001b[49m\u001b[43m]\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 3\u001b[0m \u001b[43m \u001b[49m\u001b[43my\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43my_ratings_train\u001b[49m\u001b[43m[\u001b[49m\u001b[43m:\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m2\u001b[39;49m\u001b[43m]\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 4\u001b[0m \u001b[43m \u001b[49m\u001b[43mX_val\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43m[\u001b[49m\u001b[43my_ratings_val\u001b[49m\u001b[43m[\u001b[49m\u001b[43m:\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m0\u001b[39;49m\u001b[43m]\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43my_ratings_val\u001b[49m\u001b[43m[\u001b[49m\u001b[43m:\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m1\u001b[39;49m\u001b[43m]\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mX_users_val\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mX_items_val\u001b[49m\u001b[43m]\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 5\u001b[0m \u001b[43m \u001b[49m\u001b[43my_val\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43my_ratings_val\u001b[49m\u001b[43m[\u001b[49m\u001b[43m:\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m2\u001b[39;49m\u001b[43m]\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 6\u001b[0m \u001b[43m \u001b[49m\u001b[43mepochs\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;241;43m12\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[1;32m 7\u001b[0m \u001b[43m \u001b[49m\u001b[43mbatch_size\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;241;43m2048\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[1;32m 8\u001b[0m \u001b[43m \u001b[49m\u001b[43mearly_stopping\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mearly_stopping\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 9\u001b[0m \u001b[43m \u001b[49m\u001b[43mscheduler\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mscheduler\u001b[49m\n\u001b[1;32m 10\u001b[0m \u001b[43m)\u001b[49m\n",
"File \u001b[0;32m/media/omar/New Volume/Programming/AI Projects/NCF-Recommender-System/app/model/utils/model.py:176\u001b[0m, in \u001b[0;36mNCF.fit\u001b[0;34m(self, X, y, epochs, batch_size, X_val, y_val, k, scheduler, early_stopping)\u001b[0m\n\u001b[1;32m 173\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m i, batch \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28menumerate\u001b[39m(dataloader):\n\u001b[1;32m 174\u001b[0m X_user_id, X_item_id, X_user, X_item, y \u001b[38;5;241m=\u001b[39m batch\n\u001b[0;32m--> 176\u001b[0m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43moptimizer\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mzero_grad\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m \u001b[38;5;66;03m# Zero the gradients\u001b[39;00m\n\u001b[1;32m 177\u001b[0m output \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m(X_user_id, X_item_id, X_user, X_item) \u001b[38;5;66;03m# Forward pass\u001b[39;00m\n\u001b[1;32m 178\u001b[0m loss \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mcriterion(output, y) \u001b[38;5;66;03m# Compute the loss\u001b[39;00m\n",
"File \u001b[0;32m~/.local/lib/python3.10/site-packages/torch/_compile.py:24\u001b[0m, in \u001b[0;36m_disable_dynamo.<locals>.inner\u001b[0;34m(*args, **kwargs)\u001b[0m\n\u001b[1;32m 20\u001b[0m \u001b[38;5;129m@functools\u001b[39m\u001b[38;5;241m.\u001b[39mwraps(fn)\n\u001b[1;32m 21\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21minner\u001b[39m(\u001b[38;5;241m*\u001b[39margs, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs):\n\u001b[1;32m 22\u001b[0m \u001b[38;5;28;01mimport\u001b[39;00m \u001b[38;5;21;01mtorch\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01m_dynamo\u001b[39;00m\n\u001b[0;32m---> 24\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mtorch\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_dynamo\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mdisable\u001b[49m\u001b[43m(\u001b[49m\u001b[43mfn\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mrecursive\u001b[49m\u001b[43m)\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n",
"File \u001b[0;32m~/.local/lib/python3.10/site-packages/torch/_dynamo/eval_frame.py:489\u001b[0m, in \u001b[0;36m_TorchDynamoContext.__call__.<locals>._fn\u001b[0;34m(*args, **kwargs)\u001b[0m\n\u001b[1;32m 487\u001b[0m dynamo_config_ctx\u001b[38;5;241m.\u001b[39m\u001b[38;5;21m__enter__\u001b[39m()\n\u001b[1;32m 488\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m--> 489\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mfn\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 490\u001b[0m \u001b[38;5;28;01mfinally\u001b[39;00m:\n\u001b[1;32m 491\u001b[0m set_eval_frame(prior)\n",
"File \u001b[0;32m~/.local/lib/python3.10/site-packages/torch/optim/optimizer.py:815\u001b[0m, in \u001b[0;36mOptimizer.zero_grad\u001b[0;34m(self, set_to_none)\u001b[0m\n\u001b[1;32m 812\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[1;32m 813\u001b[0m per_device_and_dtype_grads \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[0;32m--> 815\u001b[0m \u001b[38;5;28;01mwith\u001b[39;00m torch\u001b[38;5;241m.\u001b[39mautograd\u001b[38;5;241m.\u001b[39mprofiler\u001b[38;5;241m.\u001b[39mrecord_function(\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_zero_grad_profile_name):\n\u001b[1;32m 816\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m group \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mparam_groups:\n\u001b[1;32m 817\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m p \u001b[38;5;129;01min\u001b[39;00m group[\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mparams\u001b[39m\u001b[38;5;124m'\u001b[39m]:\n",
"File \u001b[0;32m~/.local/lib/python3.10/site-packages/torch/autograd/profiler.py:605\u001b[0m, in \u001b[0;36mrecord_function.__enter__\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 604\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21m__enter__\u001b[39m(\u001b[38;5;28mself\u001b[39m):\n\u001b[0;32m--> 605\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mrecord \u001b[38;5;241m=\u001b[39m \u001b[43mtorch\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mops\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mprofiler\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_record_function_enter_new\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 606\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mname\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43margs\u001b[49m\n\u001b[1;32m 607\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 608\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m\n",
"File \u001b[0;32m~/.local/lib/python3.10/site-packages/torch/_ops.py:755\u001b[0m, in \u001b[0;36mOpOverloadPacket.__call__\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 750\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21m__call__\u001b[39m(\u001b[38;5;28mself\u001b[39m, \u001b[38;5;241m*\u001b[39margs, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs):\n\u001b[1;32m 751\u001b[0m \u001b[38;5;66;03m# overloading __call__ to ensure torch.ops.foo.bar()\u001b[39;00m\n\u001b[1;32m 752\u001b[0m \u001b[38;5;66;03m# is still callable from JIT\u001b[39;00m\n\u001b[1;32m 753\u001b[0m \u001b[38;5;66;03m# We save the function ptr as the `op` attribute on\u001b[39;00m\n\u001b[1;32m 754\u001b[0m \u001b[38;5;66;03m# OpOverloadPacket to access it here.\u001b[39;00m\n\u001b[0;32m--> 755\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_op\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43mkwargs\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;129;43;01mor\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43m{\u001b[49m\u001b[43m}\u001b[49m\u001b[43m)\u001b[49m\u001b[43m)\u001b[49m\n",
"\u001b[0;31mKeyboardInterrupt\u001b[0m: "
]
}
],
Expand Down

0 comments on commit 951fc4d

Please sign in to comment.