{"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":"# H&M - Implicit ALS model\n\n[Implicit](https://github.com/benfred/implicit/) is a library for recommender models.\n\nIn this notebook we are going to use ALS (Alternating Least Squares).\n\n# Please, upvote if you find this useful!\n\n\n# Note :\n1) We will be using latest version of implicit library (released just few days back).\n\n2) for cold start / unseen customers we will use [Heng Zheng](https://www.kaggle.com/hengzheng)'s [time is our best friend v2](https://www.kaggle.com/hengzheng/time-is-our-best-friend-v2/).","metadata":{}},{"cell_type":"code","source":"# Installing latest implicit library for ALS\n\n!pip install --upgrade implicit","metadata":{"execution":{"iopub.status.busy":"2022-02-17T12:32:37.693731Z","iopub.execute_input":"2022-02-17T12:32:37.69445Z","iopub.status.idle":"2022-02-17T12:54:44.432423Z","shell.execute_reply.started":"2022-02-17T12:32:37.694352Z","shell.execute_reply":"2022-02-17T12:54:44.431369Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Importing required libraries \n\nimport os\nimport pandas as pd\nimport numpy as np\nfrom scipy.sparse import coo_matrix\nimport implicit\nfrom implicit.evaluation import mean_average_precision_at_k\n\n","metadata":{"execution":{"iopub.status.busy":"2022-02-17T12:54:52.491342Z","iopub.execute_input":"2022-02-17T12:54:52.492342Z","iopub.status.idle":"2022-02-17T12:54:53.011538Z","shell.execute_reply.started":"2022-02-17T12:54:52.492303Z","shell.execute_reply":"2022-02-17T12:54:53.010544Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"%%time\n\n# Importing data\n\ntransactions = pd.read_csv('../input/h-and-m-personalized-fashion-recommendations/transactions_train.csv', dtype={'article_id': str}, parse_dates=['t_dat'])\nsample_submission = pd.read_csv('../input/h-and-m-personalized-fashion-recommendations/sample_submission.csv')\ncustomers = pd.read_csv('../input/h-and-m-personalized-fashion-recommendations/customers.csv')\narticles = pd.read_csv('../input/h-and-m-personalized-fashion-recommendations/articles.csv', dtype={'article_id': str})\n","metadata":{"execution":{"iopub.status.busy":"2022-02-17T12:55:10.568629Z","iopub.execute_input":"2022-02-17T12:55:10.568933Z","iopub.status.idle":"2022-02-17T12:56:33.870249Z","shell.execute_reply.started":"2022-02-17T12:55:10.568892Z","shell.execute_reply":"2022-02-17T12:56:33.869204Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"articles","metadata":{"execution":{"iopub.status.busy":"2022-02-17T12:56:39.057429Z","iopub.execute_input":"2022-02-17T12:56:39.057718Z","iopub.status.idle":"2022-02-17T12:56:39.195425Z","shell.execute_reply.started":"2022-02-17T12:56:39.057686Z","shell.execute_reply":"2022-02-17T12:56:39.194347Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"customers","metadata":{"execution":{"iopub.status.busy":"2022-02-17T12:56:44.401613Z","iopub.execute_input":"2022-02-17T12:56:44.401916Z","iopub.status.idle":"2022-02-17T12:56:44.43024Z","shell.execute_reply.started":"2022-02-17T12:56:44.401885Z","shell.execute_reply":"2022-02-17T12:56:44.429092Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"transactions","metadata":{"execution":{"iopub.status.busy":"2022-02-17T12:56:47.765237Z","iopub.execute_input":"2022-02-17T12:56:47.766006Z","iopub.status.idle":"2022-02-17T12:56:47.784466Z","shell.execute_reply.started":"2022-02-17T12:56:47.765969Z","shell.execute_reply":"2022-02-17T12:56:47.783472Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Trying with less data:\n\ntransactions = transactions[transactions['t_dat'] > '2020-09-14']\ntransactions.shape","metadata":{"execution":{"iopub.status.busy":"2022-02-17T12:57:03.61608Z","iopub.execute_input":"2022-02-17T12:57:03.616415Z","iopub.status.idle":"2022-02-17T12:57:03.862923Z","shell.execute_reply.started":"2022-02-17T12:57:03.616385Z","shell.execute_reply":"2022-02-17T12:57:03.862012Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# For validation : 3 weeks of training and 1 week for validation\n# For submission : 4 weeks of training\n\ntransactions['t_dat'].max()","metadata":{"execution":{"iopub.status.busy":"2022-02-17T12:57:19.140075Z","iopub.execute_input":"2022-02-17T12:57:19.140407Z","iopub.status.idle":"2022-02-17T12:57:19.153808Z","shell.execute_reply.started":"2022-02-17T12:57:19.140375Z","shell.execute_reply":"2022-02-17T12:57:19.152311Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Assigning incremental ids to customers and articles\n\nall_customers = customers['customer_id'].unique().tolist()\nall_articles = articles['article_id'].unique().tolist()\n\ncustomer_ids = dict(list(enumerate(all_customers)))\narticle_ids = dict(list(enumerate(all_articles)))\n\ntransactions['customer_id'] = transactions['customer_id'].map({u: uidx for uidx, u in customer_ids.items()})\ntransactions['article_id'] = transactions['article_id'].map({i: iidx for iidx, i in article_ids.items()})\n\ndel customers, articles","metadata":{"execution":{"iopub.status.busy":"2022-02-17T12:57:22.827604Z","iopub.execute_input":"2022-02-17T12:57:22.828137Z","iopub.status.idle":"2022-02-17T12:57:26.850456Z","shell.execute_reply.started":"2022-02-17T12:57:22.828074Z","shell.execute_reply":"2022-02-17T12:57:26.848025Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Creating coo_matrix (customer x article) and csr matrix (customer x article)\n\nrow = transactions['customer_id'].values\ncol = transactions['article_id'].values\ndata = np.ones(transactions.shape[0])\ncoo_train = coo_matrix((data, (row, col)), shape=(len(all_customers), len(all_articles)))\ncoo_train","metadata":{"execution":{"iopub.status.busy":"2022-02-17T12:58:03.227814Z","iopub.execute_input":"2022-02-17T12:58:03.228178Z","iopub.status.idle":"2022-02-17T12:58:03.248202Z","shell.execute_reply.started":"2022-02-17T12:58:03.228108Z","shell.execute_reply":"2022-02-17T12:58:03.246961Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"%%time\n\nmodel = implicit.als.AlternatingLeastSquares(factors=10, iterations=2, use_gpu=True, calculate_training_loss=True, random_state=7)\nmodel.fit(coo_train)","metadata":{"execution":{"iopub.status.busy":"2022-02-17T12:58:14.52617Z","iopub.execute_input":"2022-02-17T12:58:14.526743Z","iopub.status.idle":"2022-02-17T12:58:15.979324Z","shell.execute_reply.started":"2022-02-17T12:58:14.526707Z","shell.execute_reply":"2022-02-17T12:58:15.978291Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def to_customer_article_coo(transactions):\n    \"\"\" Turn a dataframe with transactions into a COO sparse articles x customers matrix\"\"\"\n    row = transactions['customer_id'].values\n    col = transactions['article_id'].values\n    data = np.ones(transactions.shape[0])\n    coo = coo_matrix((data, (row, col)), shape=(len(all_customers), len(all_articles)))\n    return coo\n\n\ndef split_data(transactions, validation_days=7):\n    \"\"\" Split a pandas dataframe into training and validation data, using <<validation_days>>\n    \"\"\"\n    validation_cut = transactions['t_dat'].max() - pd.Timedelta(validation_days)\n\n    df_train = transactions[transactions['t_dat'] < validation_cut]\n    df_val = transactions[transactions['t_dat'] >= validation_cut]\n    return df_train, df_val\n\ndef get_val_matrices(transactions, validation_days=7):\n    \"\"\" Split into training and validation and create various matrices\n        \n        Returns a dictionary with the following keys:\n            coo_train: training data in COO sparse format and as (customers x articles)\n            csr_train: training data in CSR sparse format and as (customers x articles)\n            csr_val:  validation data in CSR sparse format and as (customers x articles)\n    \n    \"\"\"\n    df_train, df_val = split_data(transactions, validation_days=validation_days)\n    coo_train = to_customer_article_coo(df_train)\n    coo_val = to_customer_article_coo(df_val)\n\n    csr_train = coo_train.tocsr()\n    csr_val = coo_val.tocsr()\n    \n    return {'coo_train': coo_train,\n            'csr_train': csr_train,\n            'csr_val': csr_val\n          }\n\n\ndef validate(matrices, factors=200, iterations=20, regularization=0.01, show_progress=True):\n    \"\"\" Train an ALS model with <<factors>> (embeddings dimension) \n    for <<iterations>> over matrices and validate with MAP@12\n    \"\"\"\n    coo_train, csr_train, csr_val = matrices['coo_train'], matrices['csr_train'], matrices['csr_val']\n    \n    model = implicit.als.AlternatingLeastSquares(factors=factors, \n                                                 iterations=iterations, \n                                                 regularization=regularization, \n                                                 random_state=7,\n                                                 use_gpu=True)\n    model.fit(coo_train, show_progress=show_progress)\n    \n    # The MAPK by implicit doesn't allow to calculate allowing repeated articles, which is the case.\n    # TODO: change MAP@12 to a library that allows repeated articles in prediction\n    map12 = mean_average_precision_at_k(model, csr_train, csr_val, K=12, show_progress=show_progress, num_threads=4)\n    print(f\"Factors: {factors:>3} - Iterations: {iterations:>2} - Regularization: {regularization:4.3f} ==> MAP@12: {map12:6.5f}\")\n    return map12","metadata":{"execution":{"iopub.status.busy":"2022-02-17T12:58:27.144774Z","iopub.execute_input":"2022-02-17T12:58:27.145059Z","iopub.status.idle":"2022-02-17T12:58:27.160838Z","shell.execute_reply.started":"2022-02-17T12:58:27.145027Z","shell.execute_reply":"2022-02-17T12:58:27.159836Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"matrices = get_val_matrices(transactions)","metadata":{"execution":{"iopub.status.busy":"2022-02-17T12:58:35.526986Z","iopub.execute_input":"2022-02-17T12:58:35.527303Z","iopub.status.idle":"2022-02-17T12:58:35.688294Z","shell.execute_reply.started":"2022-02-17T12:58:35.527256Z","shell.execute_reply":"2022-02-17T12:58:35.687333Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"%%time\nbest_map12 = 0\nfor factors in [40, 50, 60, 100, 200, 500, 1000]:\n    for iterations in [3, 12, 14, 15, 20]:\n        for regularization in [0.01]:\n            map12 = validate(matrices, factors, iterations, regularization, show_progress=False)\n            if map12 > best_map12:\n                best_map12 = map12\n                best_params = {'factors': factors, 'iterations': iterations, 'regularization': regularization}\n                print(f\"Best MAP@12 found. Updating: {best_params}\")\n\n\ndel matrices","metadata":{"execution":{"iopub.status.busy":"2022-02-17T12:58:38.337664Z","iopub.execute_input":"2022-02-17T12:58:38.338184Z","iopub.status.idle":"2022-02-17T13:12:00.600814Z","shell.execute_reply.started":"2022-02-17T12:58:38.338126Z","shell.execute_reply":"2022-02-17T13:12:00.59921Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"best_params","metadata":{"execution":{"iopub.status.busy":"2022-02-17T13:12:07.337984Z","iopub.execute_input":"2022-02-17T13:12:07.33841Z","iopub.status.idle":"2022-02-17T13:12:07.347442Z","shell.execute_reply.started":"2022-02-17T13:12:07.338366Z","shell.execute_reply":"2022-02-17T13:12:07.346334Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#Training over the full dataset\n\ncoo_train = to_customer_article_coo(transactions)\ncsr_train = coo_train.tocsr()","metadata":{"execution":{"iopub.status.busy":"2022-02-17T13:12:13.17829Z","iopub.execute_input":"2022-02-17T13:12:13.17888Z","iopub.status.idle":"2022-02-17T13:12:13.262874Z","shell.execute_reply.started":"2022-02-17T13:12:13.178843Z","shell.execute_reply":"2022-02-17T13:12:13.261897Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def train(coo_train, factors=200, iterations=15, regularization=0.01, show_progress=True):\n    model = implicit.als.AlternatingLeastSquares(factors=factors, \n                                                 iterations=iterations, \n                                                 regularization=regularization, \n                                                 random_state=7,\n                                                 use_gpu=True)\n    model.fit(coo_train, show_progress=show_progress)\n    return model\n\nbest_params","metadata":{"execution":{"iopub.status.busy":"2022-02-17T13:12:17.653871Z","iopub.execute_input":"2022-02-17T13:12:17.6542Z","iopub.status.idle":"2022-02-17T13:12:17.664617Z","shell.execute_reply.started":"2022-02-17T13:12:17.654122Z","shell.execute_reply":"2022-02-17T13:12:17.663563Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"model = train(coo_train, **best_params)","metadata":{"execution":{"iopub.status.busy":"2022-02-17T13:12:24.870226Z","iopub.execute_input":"2022-02-17T13:12:24.870526Z","iopub.status.idle":"2022-02-17T13:12:26.432667Z","shell.execute_reply.started":"2022-02-17T13:12:24.870494Z","shell.execute_reply":"2022-02-17T13:12:26.431681Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Submission\n\nheng_df = pd.read_csv('../input/heng-zhengs-time-is-our-best-friend-v2-submission/not_so_fancy_but_fast_benchmark.csv')\n\n\ndef submit(model, csr_train, custs, heng_df, submission_name=\"submissions.csv\"):\n    preds = []\n    batch_size = 2000\n\n    for startidx in range(0, len(custs), batch_size):\n        \n        batch = custs[startidx : startidx + batch_size]\n        ids, scores = model.recommend(batch, csr_train[batch], N=12, filter_already_liked_items=False)\n        \n        for i, customerid in enumerate(batch):\n            customer_id = customer_ids[customerid]\n            customer_articles = ids[i]\n            articleids = [article_ids[article_id] for article_id in customer_articles]\n            preds.append((customer_id, ' '.join(articleids)))\n\n    df_preds = pd.DataFrame(preds, columns=['customer_id', 'prediction'])\n            \n    # Fill with better base model than ALS cold-start rec\n    df_preds = pd.merge(heng_df, df_preds, how='left', on='customer_id', suffixes=('_fill', '_als'))\n    df_preds.loc[~df_preds['prediction_als'].isnull(), 'prediction'] = df_preds['prediction_als']\n    df_preds.loc[df_preds['prediction_als'].isnull(), 'prediction'] = df_preds['prediction_fill']\n    df_preds = df_preds[['customer_id', 'prediction']]\n    df_preds.to_csv(submission_name, index=False)\n    \n    display(df_preds.head())\n    print(df_preds.shape)\n    \n    return df_preds","metadata":{"execution":{"iopub.status.busy":"2022-02-17T14:13:33.866439Z","iopub.execute_input":"2022-02-17T14:13:33.866724Z","iopub.status.idle":"2022-02-17T14:13:36.376552Z","shell.execute_reply.started":"2022-02-17T14:13:33.866691Z","shell.execute_reply":"2022-02-17T14:13:36.375546Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"transactions_customers = transactions['customer_id'].unique().tolist()\n\nlen(transactions_customers)","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"%%time\n\ndf_preds = submit(model, csr_train, transactions_customers, heng_df)","metadata":{"execution":{"iopub.status.busy":"2022-02-17T14:13:41.490203Z","iopub.execute_input":"2022-02-17T14:13:41.490483Z","iopub.status.idle":"2022-02-17T14:14:59.59114Z","shell.execute_reply.started":"2022-02-17T14:13:41.490451Z","shell.execute_reply":"2022-02-17T14:14:59.590189Z"},"trusted":true},"execution_count":null,"outputs":[]}]}