{"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":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\n\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2023-10-16T13:01:08.161488Z","iopub.execute_input":"2023-10-16T13:01:08.161817Z","iopub.status.idle":"2023-10-16T13:01:08.541687Z","shell.execute_reply.started":"2023-10-16T13:01:08.161788Z","shell.execute_reply":"2023-10-16T13:01:08.540806Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"!pip install --upgrade implicit","metadata":{"execution":{"iopub.status.busy":"2023-10-16T13:01:08.543460Z","iopub.execute_input":"2023-10-16T13:01:08.543822Z","iopub.status.idle":"2023-10-16T13:01:21.288446Z","shell.execute_reply.started":"2023-10-16T13:01:08.543796Z","shell.execute_reply":"2023-10-16T13:01:21.287041Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport implicit\nfrom scipy.sparse import coo_matrix\nfrom implicit.evaluation import mean_average_precision_at_k","metadata":{"execution":{"iopub.status.busy":"2023-10-16T13:01:21.289994Z","iopub.execute_input":"2023-10-16T13:01:21.290347Z","iopub.status.idle":"2023-10-16T13:01:21.538436Z","shell.execute_reply.started":"2023-10-16T13:01:21.290313Z","shell.execute_reply":"2023-10-16T13:01:21.537482Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"%%time\n\nbase_path = '../input/h-and-m-personalized-fashion-recommendations/'\ncsv_train = f'{base_path}transactions_train.csv'\ncsv_sub = f'{base_path}sample_submission.csv'\ncsv_users = f'{base_path}customers.csv'\ncsv_items = f'{base_path}articles.csv'\n\ndf = pd.read_csv(csv_train, dtype={'article_id': str}, parse_dates=['t_dat'])\ndf_sub = pd.read_csv(csv_sub)\ndfu = pd.read_csv(csv_users)\ndfi = pd.read_csv(csv_items, dtype={'article_id': str})","metadata":{"execution":{"iopub.status.busy":"2023-10-16T13:01:21.539522Z","iopub.execute_input":"2023-10-16T13:01:21.540023Z","iopub.status.idle":"2023-10-16T13:03:06.248033Z","shell.execute_reply.started":"2023-10-16T13:01:21.539997Z","shell.execute_reply":"2023-10-16T13:03:06.247215Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Trying with less data:\n# https://www.kaggle.com/tomooinubushi/folk-of-time-is-our-best-friend/notebook\ndf = df[df['t_dat'] > '2020-08-21']\ndf.shape","metadata":{"execution":{"iopub.status.busy":"2023-10-16T13:03:06.250081Z","iopub.execute_input":"2023-10-16T13:03:06.250773Z","iopub.status.idle":"2023-10-16T13:03:07.208862Z","shell.execute_reply.started":"2023-10-16T13:03:06.250742Z","shell.execute_reply":"2023-10-16T13:03:07.208177Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df['t_dat'].max()","metadata":{"execution":{"iopub.status.busy":"2023-10-16T13:03:07.210439Z","iopub.execute_input":"2023-10-16T13:03:07.210819Z","iopub.status.idle":"2023-10-16T13:03:07.221091Z","shell.execute_reply.started":"2023-10-16T13:03:07.210782Z","shell.execute_reply":"2023-10-16T13:03:07.219722Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"ALL_USERS = dfu['customer_id'].unique().tolist()\nALL_ITEMS = dfi['article_id'].unique().tolist()\n\nuser_ids = dict(list(enumerate(ALL_USERS)))\nitem_ids = dict(list(enumerate(ALL_ITEMS)))\n\nuser_map = {u: uidx for uidx, u in user_ids.items()}\nitem_map = {i: iidx for iidx, i in item_ids.items()}\n\ndf['user_id'] = df['customer_id'].map(user_map)\ndf['item_id'] = df['article_id'].map(item_map)\n\ndel dfu, dfi","metadata":{"execution":{"iopub.status.busy":"2023-10-16T13:03:07.223106Z","iopub.execute_input":"2023-10-16T13:03:07.223594Z","iopub.status.idle":"2023-10-16T13:03:10.106856Z","shell.execute_reply.started":"2023-10-16T13:03:07.223546Z","shell.execute_reply":"2023-10-16T13:03:10.105892Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"row = df['user_id'].values\ncol = df['item_id'].values\ndata = np.ones(df.shape[0])\ncoo_train = coo_matrix((data, (row, col)), shape=(len(ALL_USERS), len(ALL_ITEMS)))\ncoo_train","metadata":{"execution":{"iopub.status.busy":"2023-10-16T13:03:10.108037Z","iopub.execute_input":"2023-10-16T13:03:10.108332Z","iopub.status.idle":"2023-10-16T13:03:10.124546Z","shell.execute_reply.started":"2023-10-16T13:03:10.108307Z","shell.execute_reply":"2023-10-16T13:03:10.123707Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"%%time\nmodel = implicit.als.AlternatingLeastSquares(factors=10, iterations=2)\nmodel.fit(coo_train)","metadata":{"execution":{"iopub.status.busy":"2023-10-16T13:03:10.125798Z","iopub.execute_input":"2023-10-16T13:03:10.126672Z","iopub.status.idle":"2023-10-16T13:03:19.047011Z","shell.execute_reply.started":"2023-10-16T13:03:10.126639Z","shell.execute_reply":"2023-10-16T13:03:19.045454Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def to_user_item_coo(df):\n    \"\"\" Turn a dataframe with transactions into a COO sparse items x users matrix\"\"\"\n    row = df['user_id'].values\n    col = df['item_id'].values\n    data = np.ones(df.shape[0])\n    coo = coo_matrix((data, (row, col)), shape=(len(ALL_USERS), len(ALL_ITEMS)))\n    return coo\n\n\ndef split_data(df, validation_days=7):\n    \"\"\" Split a pandas dataframe into training and validation data, using <<validation_days>>\n    \"\"\"\n    validation_cut = df['t_dat'].max() - pd.Timedelta(validation_days)\n\n    df_train = df[df['t_dat'] < validation_cut]\n    df_val = df[df['t_dat'] >= validation_cut]\n    return df_train, df_val\n\ndef get_val_matrices(df, 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 (users x items)\n            csr_train: training data in CSR sparse format and as (users x items)\n            csr_val:  validation data in CSR sparse format and as (users x items)\n    \n    \"\"\"\n    df_train, df_val = split_data(df, validation_days=validation_days)\n    coo_train = to_user_item_coo(df_train)\n    coo_val = to_user_item_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=42)\n    model.fit(coo_train, show_progress=show_progress)\n    \n    # The MAPK by implicit doesn't allow to calculate allowing repeated items, which is the case.\n    # TODO: change MAP@12 to a library that allows repeated items 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":"2023-10-16T13:03:19.054738Z","iopub.execute_input":"2023-10-16T13:03:19.058844Z","iopub.status.idle":"2023-10-16T13:03:19.087414Z","shell.execute_reply.started":"2023-10-16T13:03:19.058764Z","shell.execute_reply":"2023-10-16T13:03:19.085899Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"matrices = get_val_matrices(df)","metadata":{"execution":{"iopub.status.busy":"2023-10-16T13:03:19.099449Z","iopub.execute_input":"2023-10-16T13:03:19.104673Z","iopub.status.idle":"2023-10-16T13:03:19.329023Z","shell.execute_reply.started":"2023-10-16T13:03:19.104576Z","shell.execute_reply":"2023-10-16T13:03:19.328156Z"},"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}\")","metadata":{"execution":{"iopub.status.busy":"2023-10-16T13:03:19.333032Z","iopub.execute_input":"2023-10-16T13:03:19.333786Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"del matrices","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"coo_train = to_user_item_coo(df)\ncsr_train = coo_train.tocsr()","metadata":{},"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=42)\n    model.fit(coo_train, show_progress=show_progress)\n    return model","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"model = train(coo_train, **best_params)","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def submit(model, csr_train, submission_name=\"submissions.csv\"):\n    preds = []\n    batch_size = 2000\n    to_generate = np.arange(len(ALL_USERS))\n    for startidx in range(0, len(to_generate), batch_size):\n        batch = to_generate[startidx : startidx + batch_size]\n        ids, scores = model.recommend(batch, csr_train[batch], N=12, filter_already_liked_items=False)\n        for i, userid in enumerate(batch):\n            customer_id = user_ids[userid]\n            user_items = ids[i]\n            article_ids = [item_ids[item_id] for item_id in user_items]\n            preds.append((customer_id, ' '.join(article_ids)))\n\n    df_preds = pd.DataFrame(preds, columns=['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\n","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df_preds = submit(model, csr_train);\n","metadata":{},"execution_count":null,"outputs":[]}]}