{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Tensorflow Recommenders:\n\nIn this notebook we peek into the possibility of using Tensorflow recommender system (tfrs) -  Retrieval models for H&M product recommendations.\n\nRetrieval models have typically query and candidate models in which features are embedded. Affinity score is calculated by a factorized retrieval model.The retrieval task is for selecting an initial set of candidates from all possible candidates.\n\nTensorflow has easy to implement modules such as *tfrs.tasks.Retrieval* along with metrics such as *tfrs.metrics.FactorizedTopK* for retrieval task.\n\nTensorflow *ScaNN* library can be used to retrieve the best candidates for a given query. In our case we can get the 12 recommendations required using this library.","metadata":{"papermill":{"duration":0.02234,"end_time":"2022-04-30T17:34:06.627711","exception":false,"start_time":"2022-04-30T17:34:06.605371","status":"completed"},"tags":[]}},{"cell_type":"code","source":"!pip install -q tensorflow-recommenders\n!pip install -q scann","metadata":{"_kg_hide-output":true,"execution":{"iopub.execute_input":"2022-04-30T17:34:06.670389Z","iopub.status.busy":"2022-04-30T17:34:06.670047Z","iopub.status.idle":"2022-04-30T17:35:57.154484Z","shell.execute_reply":"2022-04-30T17:35:57.153372Z"},"papermill":{"duration":110.509082,"end_time":"2022-04-30T17:35:57.157317","exception":false,"start_time":"2022-04-30T17:34:06.648235","status":"completed"},"tags":[]},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport tensorflow as tf\nimport tensorflow_recommenders as tfrs\n\nfrom pathlib import Path\nfrom typing import Dict, Text","metadata":{"_kg_hide-output":true,"execution":{"iopub.execute_input":"2022-04-30T17:35:57.201557Z","iopub.status.busy":"2022-04-30T17:35:57.200888Z","iopub.status.idle":"2022-04-30T17:36:02.141281Z","shell.execute_reply":"2022-04-30T17:36:02.140090Z"},"papermill":{"duration":4.964267,"end_time":"2022-04-30T17:36:02.144124","exception":false,"start_time":"2022-04-30T17:35:57.179857","status":"completed"},"tags":[]},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# The Dataset","metadata":{"papermill":{"duration":0.020329,"end_time":"2022-04-30T17:36:02.185079","exception":false,"start_time":"2022-04-30T17:36:02.164750","status":"completed"},"tags":[]}},{"cell_type":"code","source":"data_dir = Path('../input/h-and-m-personalized-fashion-recommendations')\ntrain0 = pd.read_csv(data_dir/'transactions_train.csv')\ntrain0 = train0[train0['t_dat'] >='2020-09-01']\n\n# add 0 in article_id column (string)\ntrain0['article_id'] = train0['article_id'].astype(str)\ntrain0['article_id'] = train0['article_id'].apply(lambda x: x.zfill(10))\ntrain0.head()","metadata":{"execution":{"iopub.execute_input":"2022-04-30T17:36:02.226179Z","iopub.status.busy":"2022-04-30T17:36:02.225882Z","iopub.status.idle":"2022-04-30T17:37:23.796817Z","shell.execute_reply":"2022-04-30T17:37:23.795832Z"},"papermill":{"duration":81.61501,"end_time":"2022-04-30T17:37:23.819752","exception":false,"start_time":"2022-04-30T17:36:02.204742","status":"completed"},"tags":[]},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"customer_df = pd.read_csv(data_dir/'customers.csv')\ncustomer_df.head()","metadata":{"execution":{"iopub.execute_input":"2022-04-30T17:37:23.864013Z","iopub.status.busy":"2022-04-30T17:37:23.863738Z","iopub.status.idle":"2022-04-30T17:37:30.210556Z","shell.execute_reply":"2022-04-30T17:37:30.209362Z"},"papermill":{"duration":6.373194,"end_time":"2022-04-30T17:37:30.213215","exception":false,"start_time":"2022-04-30T17:37:23.840021","status":"completed"},"tags":[]},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"article_df = pd.read_csv(data_dir/'articles.csv')\n\n# add 0 in article_id column (string) similar to train0\narticle_df['article_id'] = article_df['article_id'].astype(str)\narticle_df['article_id'] = article_df['article_id'].apply(lambda x: x.zfill(10))\narticle_df.head()","metadata":{"execution":{"iopub.execute_input":"2022-04-30T17:37:30.260571Z","iopub.status.busy":"2022-04-30T17:37:30.259575Z","iopub.status.idle":"2022-04-30T17:37:31.769178Z","shell.execute_reply":"2022-04-30T17:37:31.768165Z"},"papermill":{"duration":1.537539,"end_time":"2022-04-30T17:37:31.772457","exception":false,"start_time":"2022-04-30T17:37:30.234918","status":"completed"},"tags":[]},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"We select only two features for training. Also generate data for embedding in both query and candidate models.\n","metadata":{"papermill":{"duration":0.02202,"end_time":"2022-04-30T17:37:31.822183","exception":false,"start_time":"2022-04-30T17:37:31.800163","status":"completed"},"tags":[]}},{"cell_type":"code","source":"#get data for embedding and task\n\nunique_customer_ids = customer_df.customer_id.unique()\nunique_article_ids = article_df.article_id.unique()\n\narticle_ds = tf.data.Dataset.from_tensor_slices(dict(article_df[['article_id']]))\narticles = article_ds.map(lambda x: x['article_id'])\n","metadata":{"_kg_hide-output":true,"execution":{"iopub.execute_input":"2022-04-30T17:37:31.874863Z","iopub.status.busy":"2022-04-30T17:37:31.873682Z","iopub.status.idle":"2022-04-30T17:37:32.685270Z","shell.execute_reply":"2022-04-30T17:37:32.681891Z"},"papermill":{"duration":0.842681,"end_time":"2022-04-30T17:37:32.688151","exception":false,"start_time":"2022-04-30T17:37:31.845470","status":"completed"},"tags":[]},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Query, Candidate and H&M model ","metadata":{"papermill":{"duration":0.027553,"end_time":"2022-04-30T17:37:32.744151","exception":false,"start_time":"2022-04-30T17:37:32.716598","status":"completed"},"tags":[]}},{"cell_type":"code","source":"embedding_dimension = 64\n\n# Query Model\ncustomer_model = tf.keras.Sequential([\n  tf.keras.layers.StringLookup(\n      vocabulary=unique_customer_ids, mask_token=None),  \n  tf.keras.layers.Embedding(len(unique_customer_ids) + 1, embedding_dimension)\n])","metadata":{"execution":{"iopub.execute_input":"2022-04-30T17:37:32.795157Z","iopub.status.busy":"2022-04-30T17:37:32.794149Z","iopub.status.idle":"2022-04-30T17:37:34.256278Z","shell.execute_reply":"2022-04-30T17:37:34.255217Z"},"papermill":{"duration":1.490855,"end_time":"2022-04-30T17:37:34.258838","exception":false,"start_time":"2022-04-30T17:37:32.767983","status":"completed"},"tags":[]},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Candidate Model\narticle_model = tf.keras.Sequential([\n  tf.keras.layers.StringLookup(\n      vocabulary=unique_article_ids, mask_token=None),\n  tf.keras.layers.Embedding(len(unique_article_ids) + 1, embedding_dimension)\n])","metadata":{"execution":{"iopub.execute_input":"2022-04-30T17:37:34.310374Z","iopub.status.busy":"2022-04-30T17:37:34.310098Z","iopub.status.idle":"2022-04-30T17:37:34.359740Z","shell.execute_reply":"2022-04-30T17:37:34.358524Z"},"papermill":{"duration":0.078002,"end_time":"2022-04-30T17:37:34.362335","exception":false,"start_time":"2022-04-30T17:37:34.284333","status":"completed"},"tags":[]},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Retrieval Model\n\nclass HandMModel(tfrs.Model):\n    \n    def __init__(self, customer_model, article_model):\n        super().__init__()\n        self.article_model: tf.keras.Model = article_model\n        self.customer_model: tf.keras.Model = customer_model\n        self.task = tfrs.tasks.Retrieval(\n        metrics=tfrs.metrics.FactorizedTopK(\n            candidates=articles.batch(128).map(self.article_model),            \n            ),\n        )        \n\n    def compute_loss(self, features: Dict[str, tf.Tensor], training=False) -> tf.Tensor:\n    \n        customer_embeddings = self.customer_model(features[\"customer_id\"])    \n        article_embeddings = self.article_model(features[\"article_id\"])\n\n        # The task computes the loss and the metrics.\n        return self.task(customer_embeddings, article_embeddings,compute_metrics=not training)","metadata":{"execution":{"iopub.execute_input":"2022-04-30T17:37:34.408777Z","iopub.status.busy":"2022-04-30T17:37:34.408481Z","iopub.status.idle":"2022-04-30T17:37:34.416918Z","shell.execute_reply":"2022-04-30T17:37:34.416208Z"},"papermill":{"duration":0.033981,"end_time":"2022-04-30T17:37:34.418863","exception":false,"start_time":"2022-04-30T17:37:34.384882","status":"completed"},"tags":[]},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Train & Validate","metadata":{"papermill":{"duration":0.022256,"end_time":"2022-04-30T17:37:34.466155","exception":false,"start_time":"2022-04-30T17:37:34.443899","status":"completed"},"tags":[]}},{"cell_type":"code","source":"model = HandMModel(customer_model, article_model)\nmodel.compile(optimizer=tf.keras.optimizers.Adagrad(learning_rate=0.1))","metadata":{"execution":{"iopub.execute_input":"2022-04-30T17:37:34.513314Z","iopub.status.busy":"2022-04-30T17:37:34.513004Z","iopub.status.idle":"2022-04-30T17:37:34.885391Z","shell.execute_reply":"2022-04-30T17:37:34.884307Z"},"papermill":{"duration":0.399598,"end_time":"2022-04-30T17:37:34.888252","exception":false,"start_time":"2022-04-30T17:37:34.488654","status":"completed"},"tags":[]},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train = train0[train0['t_dat']<='2020-09-15']\ntest = train0[train0['t_dat'] >='2020-09-15']\n\ntrain_ds = tf.data.Dataset.from_tensor_slices(dict(train[['customer_id','article_id']])).shuffle(100_000).batch(256).cache()\ntest_ds = tf.data.Dataset.from_tensor_slices(dict(test[['customer_id','article_id']])).batch(256).cache()\n\nnum_epochs = 5\n\n'''\n\nhistory = model.fit(\n    train_ds, \n    validation_data = test_ds,\n    validation_freq=5,\n    epochs=num_epochs,\n    verbose=1)\n\n'''\n","metadata":{"_kg_hide-output":true,"execution":{"iopub.execute_input":"2022-04-30T17:37:34.936667Z","iopub.status.busy":"2022-04-30T17:37:34.936315Z","iopub.status.idle":"2022-04-30T17:37:35.417899Z","shell.execute_reply":"2022-04-30T17:37:35.416785Z"},"papermill":{"duration":0.508485,"end_time":"2022-04-30T17:37:35.420230","exception":false,"start_time":"2022-04-30T17:37:34.911745","status":"completed"},"tags":[]},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"**A word on metrics:**\n\nCalculation of factorized top K metric is highly time intensive. Even with the option 'compute_metrics=not training' and \ncomputing validation metrics only every 5 epochs, it still takes a lot of time. You can check this by running above model. Another option \nmay be by reducing the number of retrievals from standard 100.(may cost accuracy?)\n\nself.task = tfrs.tasks.Retrieval(\n        metrics=tfrs.metrics.FactorizedTopK(\n        candidates=articles.batch(128).map(self.article_model),\n        k = (any value less than 100)\n        )","metadata":{"papermill":{"duration":0.024439,"end_time":"2022-04-30T17:37:35.470960","exception":false,"start_time":"2022-04-30T17:37:35.446521","status":"completed"},"tags":[]}},{"cell_type":"markdown","source":"# Retrieve & Submit","metadata":{"papermill":{"duration":0.025245,"end_time":"2022-04-30T17:37:35.521089","exception":false,"start_time":"2022-04-30T17:37:35.495844","status":"completed"},"tags":[]}},{"cell_type":"code","source":"# train without validation\n\ntrain_ds = tf.data.Dataset.from_tensor_slices(dict(train0[['customer_id','article_id']])).shuffle(100_000).batch(256).cache()\n\nnum_epochs = 5\n\nhistory = model.fit(\n    train_ds,    \n    epochs=num_epochs,\n    verbose=1)","metadata":{"execution":{"iopub.execute_input":"2022-04-30T17:37:35.574105Z","iopub.status.busy":"2022-04-30T17:37:35.573517Z","iopub.status.idle":"2022-04-30T17:39:10.765325Z","shell.execute_reply":"2022-04-30T17:39:10.764220Z"},"papermill":{"duration":95.220725,"end_time":"2022-04-30T17:39:10.767906","exception":false,"start_time":"2022-04-30T17:37:35.547181","status":"completed"},"tags":[]},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"scann_index = tfrs.layers.factorized_top_k.ScaNN(model.customer_model, k = 12 )\nscann_index.index_from_dataset(\n  tf.data.Dataset.zip((articles.batch(100), articles.batch(100).map(model.article_model)))\n)","metadata":{"_kg_hide-output":true,"execution":{"iopub.execute_input":"2022-04-30T17:39:12.119495Z","iopub.status.busy":"2022-04-30T17:39:12.118571Z","iopub.status.idle":"2022-04-30T17:39:19.020642Z","shell.execute_reply":"2022-04-30T17:39:19.019491Z"},"papermill":{"duration":7.599396,"end_time":"2022-04-30T17:39:19.023044","exception":false,"start_time":"2022-04-30T17:39:11.423648","status":"completed"},"tags":[]},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"sub = pd.read_csv(data_dir/'sample_submission.csv')\n_,articles = scann_index(sub.customer_id.values)\npreds = articles.numpy().astype(str)\npreds = pd.Series(map(' '.join, preds,))\nsub['prediction'] = preds\nsub.to_csv('submission.csv',index=False)","metadata":{"_kg_hide-output":true,"execution":{"iopub.execute_input":"2022-04-30T17:39:20.312955Z","iopub.status.busy":"2022-04-30T17:39:20.312004Z","iopub.status.idle":"2022-04-30T17:40:11.148621Z","shell.execute_reply":"2022-04-30T17:40:11.147645Z"},"papermill":{"duration":51.485014,"end_time":"2022-04-30T17:40:11.151296","exception":false,"start_time":"2022-04-30T17:39:19.666282","status":"completed"},"tags":[]},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"This notebook is based on recommender models in the Tensorflow official site. This model can be further refined by adding more features, deep layers and with different model hyperparameters. Examples can be referred at https://www.tensorflow.org/recommenders\n\n**Thank you for your time!**\n","metadata":{"papermill":{"duration":0.667152,"end_time":"2022-04-30T17:40:12.469097","exception":false,"start_time":"2022-04-30T17:40:11.801945","status":"completed"},"tags":[]}}]}