{"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":"# Factorization Machine for H&M RecSys Challenge\n### by Arseniy, Vladislav and Noah\n\n## Preperation\n- load packages and data","metadata":{}},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2022-03-25T14:34:30.834079Z","iopub.execute_input":"2022-03-25T14:34:30.834454Z","iopub.status.idle":"2022-03-25T14:34:30.868738Z","shell.execute_reply.started":"2022-03-25T14:34:30.834369Z","shell.execute_reply":"2022-03-25T14:34:30.868024Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Load the data\ntransactions_df = pd.read_csv(\"../input/h-and-m-personalized-fashion-recommendations/transactions_train.csv\")\ncustomers_df = pd.read_csv('../input/h-and-m-personalized-fashion-recommendations/customers.csv')\narticles_df = pd.read_csv('../input/h-and-m-personalized-fashion-recommendations/articles.csv')","metadata":{"execution":{"iopub.status.busy":"2022-03-25T14:34:30.986376Z","iopub.execute_input":"2022-03-25T14:34:30.987855Z","iopub.status.idle":"2022-03-25T14:35:59.241828Z","shell.execute_reply.started":"2022-03-25T14:34:30.987780Z","shell.execute_reply":"2022-03-25T14:35:59.240328Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Filter data","metadata":{}},{"cell_type":"code","source":"def get_most_bought_articles(data, num_articles=5):\n    # Create dataframe that contains the number of times each article has been bought\n    articles_counts = data[['article_id', 't_dat']].groupby('article_id').count().reset_index().rename(columns={'t_dat': 'count'})\n    articles_counts = articles_counts.sort_values(by='count', ascending=False)\n        \n    most_bought_articles = articles_counts.loc[articles_counts['count'] >= num_articles]['article_id'].values\n    \n    return most_bought_articles","metadata":{"execution":{"iopub.status.busy":"2022-03-25T14:35:59.244735Z","iopub.execute_input":"2022-03-25T14:35:59.245076Z","iopub.status.idle":"2022-03-25T14:35:59.254494Z","shell.execute_reply.started":"2022-03-25T14:35:59.245035Z","shell.execute_reply":"2022-03-25T14:35:59.252981Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Create training dataset with positive examples.\n# The training data will contain all transactions starting from 01/07/2020.\n# Only items that have been bought at least 10 times will be kept. Also, we\n# are only going to compute the information for the customers that appear\n# in these transactions.\nstart_date = pd.to_datetime('2020-07-01')\nend_date = pd.to_datetime('2020-09-22')\n\n# Filter transactions by date\ntransactions_df.t_dat = pd.to_datetime(transactions_df.t_dat)\ntransactions_df = transactions_df.loc[transactions_df.t_dat >= start_date]\ntransactions_df = transactions_df.loc[transactions_df.t_dat < end_date]\n\n# Get articles with at least num_articles purchases and remove the rest from the\n# transactions\nmost_bought_articles = get_most_bought_articles(transactions_df, num_articles=10)\ntransactions_df = transactions_df.loc[transactions_df.article_id.isin(most_bought_articles)]\n\narticle_ids = articles_df['article_id'].values\ncustomer_ids = customers_df['customer_id'].values\n\nnum_articles = len(article_ids)\nnum_customers = len(customer_ids)\n\n# Create dictionaries with mapping keys\narticles_id_to_idx = dict(zip(article_ids, range(num_articles)))\ncustomers_id_to_idx = dict(zip(customer_ids, range(num_customers)))\n\ntrain_df = transactions_df.copy()\ntrain_df = train_df[['customer_id', 'article_id']]\n\nnum_transactions = train_df.shape[0]\n\ntrain_df['bought'] = np.ones(num_transactions)\n\ntrain_df","metadata":{"execution":{"iopub.status.busy":"2022-03-25T14:35:59.255807Z","iopub.execute_input":"2022-03-25T14:35:59.256084Z","iopub.status.idle":"2022-03-25T14:36:04.798430Z","shell.execute_reply.started":"2022-03-25T14:35:59.256043Z","shell.execute_reply":"2022-03-25T14:36:04.797549Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Generate negative training samples\n# Generate negative examples\nnp.random.seed(47)\n\nnegative_data = pd.DataFrame(\n    {\n        'article_id': np.random.permutation(train_df.article_id.values),\n        'customer_id': train_df.customer_id.values,\n        'bought': np.zeros(num_transactions)\n    }\n)\n\ntrain_df = pd.concat([train_df, negative_data])\ntrain_df = train_df.sample(frac=1).reset_index(drop=True)\n\ntrain_df","metadata":{"execution":{"iopub.status.busy":"2022-03-25T14:36:04.800356Z","iopub.execute_input":"2022-03-25T14:36:04.800735Z","iopub.status.idle":"2022-03-25T14:36:08.976669Z","shell.execute_reply.started":"2022-03-25T14:36:04.800709Z","shell.execute_reply":"2022-03-25T14:36:08.975603Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Pre-processing and encoding features in sparse matrix for training","metadata":{}},{"cell_type":"code","source":"from scipy.sparse import csr_matrix, hstack\nimport numpy as np\nimport pandas as pd\n\ndef preprocess_customers_df(customers_df, transactions_df):\n    # Check if active and have club member status\n    customers_df[['Active']] = customers_df[['Active']].fillna(value=0) \n    customers_df['club_member_status'] = customers_df['club_member_status'] == 'ACTIVE'\n\n    # 9% of all values have one specific postal code, we will encode this as a feature\n    customers_df['common_postal_code'] = customers_df['postal_code'] == '2c29ae653a9282cce4151bd87643c907644e09541abc28ae87dea0d1f6603b1c'\n\n    # Replacing missing age with mean value\n    customers_df[['age']] = customers_df[['age']].fillna(value=customers_df['age'].mean())\n\n    # Drop columns\n    customers_df = customers_df.drop(['FN', 'fashion_news_frequency', 'postal_code'], axis=1)\n\n    # Replace boolean with 1/0\n    customers_df['club_member_status'] = customers_df['club_member_status'].astype(int)\n    customers_df['common_postal_code'] = customers_df['common_postal_code'].astype(int)\n    \n    customers_df = customers_df.merge(transactions_df[['customer_id', 'article_id']], on=\"customer_id\")\n    \n    # Computer average price per item bought by customer\n    # needed_df = transactions_df.merge(articles_df[['article_id', 'colour_group_code','index_group_no']], on ='article_id', how='left')\n    # avg_price = needed_df.groupby('customer_id').mean()['price']\n    # customers_df = customers_df.merge(avg_price, on='customer_id', how='left')\n    \n    return customers_df\n\ndef create_sparse_matrix(transactions_df, articles_df, customers_df, articles_to_idx, customers_to_idx): \n    customers_df = preprocess_customers_df(customers_df, transactions_df)\n    \n    # Create one hot encoded customers matrix\n    cols = np.array([customers_to_idx[customer] for customer in customers_df['customer_id']])\n    rows = np.array(list(range(0, len(customers_df))))\n    data = np.ones(len(rows))\n    csr_customers = csr_matrix((data, (rows, cols)), shape=(len(rows), len(customers_to_idx)))\n    \n    # Create one hot encoded bought articles matrix\n    rows = np.array(list(range(0, len(customers_df))))\n    cols = np.array([articles_to_idx[article] for article in customers_df['article_id']])\n    data = np.ones(len(rows))\n    csr_articles = csr_matrix((data, (rows, cols)), shape=(len(rows), len(articles_to_idx)))\n    \n    # Concatonate\n    sparse_matrix = hstack((csr_customers, csr_articles), format='csr')\n    # Age\n    sparse_matrix = hstack((sparse_matrix, customers_df['age'].values[:,None]), format='csr')\n    # Active\n    sparse_matrix = hstack((sparse_matrix, customers_df['Active'].values[:,None]), format='csr')\n    # Club member status\n    sparse_matrix = hstack((sparse_matrix, customers_df['club_member_status'].values[:,None]), format='csr')\n    # Common postal code\n    sparse_matrix = hstack((sparse_matrix, customers_df['common_postal_code'].values[:,None]), format='csr')\n    # Average price spent\n    # sparse_matrix = hstack((sparse_matrix, customers_df['price'].values[:,None]))\n    \n    return sparse_matrix\n","metadata":{"execution":{"iopub.status.busy":"2022-03-25T14:36:08.979362Z","iopub.execute_input":"2022-03-25T14:36:08.980162Z","iopub.status.idle":"2022-03-25T14:36:09.100759Z","shell.execute_reply.started":"2022-03-25T14:36:08.980116Z","shell.execute_reply":"2022-03-25T14:36:09.099711Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from scipy import sparse\n\nsparse_matrix = create_sparse_matrix(train_df, articles_df, customers_df, articles_id_to_idx, customers_id_to_idx)","metadata":{"execution":{"iopub.status.busy":"2022-03-25T14:36:09.102555Z","iopub.execute_input":"2022-03-25T14:36:09.103210Z","iopub.status.idle":"2022-03-25T14:36:28.805637Z","shell.execute_reply.started":"2022-03-25T14:36:09.103175Z","shell.execute_reply":"2022-03-25T14:36:28.804732Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Implement the factorization machine","metadata":{}},{"cell_type":"code","source":"from tqdm import tqdm\nfrom numba import njit\nimport numba as nb\n\n\n@njit\ndef log_loss(bought, pred):\n    # Using the log loss prevents the exploting gradient problem\n    return np.log(np.exp(-pred * bought) + 1.0)\n\n\n@njit\ndef predict_single(data, indices, indptr, i, latent_factors, w0, w, V):\n    # The sum of the interactions will be saved so that it can be used later on\n    sum_factors = np.zeros(latent_factors)\n    summed_squared = np.zeros(latent_factors)\n\n    # Initialize prediction with w0\n    prediction = w0\n\n    # Linear product w * x\n    for index in range(indptr[i], indptr[i + 1]):\n        feature = indices[index]\n        prediction += w[feature] * data[index]\n\n    # Interactions product\n    for factor in range(latent_factors):\n        # squared_sum = 0\n\n        for index in range(indptr[i], indptr[i + 1]):\n            feature = indices[index]\n            product = V[factor, feature] * data[index]\n            sum_factors[factor] += product\n            summed_squared[factor] += product * product\n\n        prediction += 0.5 * (sum_factors[factor] * sum_factors[factor] - summed_squared[factor])\n\n    return prediction, sum_factors\n\n\n@njit\ndef SGD(data, indices, indptr, latent_factors, w0, w, V, learning_rate, bought, reg_w, reg_v):\n    loss = 0\n\n    for i in range(len(bought)):\n        prediction, sum_factors = predict_single(data, indices, indptr, i, latent_factors, w0, w, V)\n        loss += log_loss(bought[i], prediction)\n        loss_gradient = -bought[i] / (np.exp(bought[i] * prediction) + 1.0)\n\n        # Update bias term\n        w0 -= learning_rate * loss_gradient\n\n        # Update features bias\n        for index in range(indptr[i], indptr[i + 1]):\n            feature = indices[index]\n            w[feature] -= learning_rate * (loss_gradient * data[index] + 2 * reg_w * w[feature])\n\n        # Update latent factors\n        for factor in range(latent_factors):\n            for index in range(indptr[i], indptr[i + 1]):\n                feature = indices[index]\n                term = sum_factors[factor] - V[factor, feature] * data[index]\n                v_gradient = loss_gradient * data[index] * term\n                V[factor, feature] -= learning_rate * (v_gradient + 2 * reg_v * V[factor, feature])\n\n        # if i % 100000 == 0:\n        #    print('Loss at step ', i, ': ', loss)\n\n    loss /= data.shape[0]\n\n    return loss, w0, w, V\n\n\nclass FactorizationMachine:\n    def __init__(self, transactions_matrix, bought, latent_factors):\n        self.transactions_matrix = transactions_matrix\n        self.bought = bought\n        self.latent_factors = latent_factors\n\n        self.n_samples, self.n_variables = self.transactions_matrix.shape\n\n    def fit(self, n_epochs=10, learning_rate=0.001, lmbda=0.1, reg_w=0.01, reg_latent=0.01, verbose=True):\n        \"\"\" We decompose the R matrix into to submatrices using the training data \"\"\"\n        self.verbose = verbose\n        self.learning_rate = learning_rate\n        self.lmbda = lmbda\n\n        np.random.seed(47)\n        # Initialize training variables\n        self.w0 = 0\n\n        self.reg_w = reg_w\n        self.reg_latent = reg_latent\n\n        self.w = np.random.normal(0, 1, self.n_variables)\n        self.V = np.random.normal(0, scale=1 / np.sqrt(self.latent_factors),\n                                  size=(self.latent_factors, self.n_variables))\n\n        data = self.transactions_matrix.data\n        indices = self.transactions_matrix.indices\n        indptr = self.transactions_matrix.indptr\n        bought = self.bought\n        self.history_ = []\n        for epoch in range(n_epochs):\n            print('Epoch: {}'.format(epoch))\n            \n            loss, self.w0, self.w, self.V = SGD(\n                data=data, indices=indices, indptr=indptr, latent_factors=self.latent_factors,\n                w0=self.w0, w=self.w, V=self.V, learning_rate=self.learning_rate,\n                bought=bought, reg_w=self.reg_w, reg_v=self.reg_latent\n            )\n\n            print(f'Loss: {loss}')\n            self.history_.append(loss)\n\n    def predict_proba(self, data):\n        \"\"\"\n        Probability estimates. The returned estimates for\n        all classes are ordered by the label of classes.\n\n        Paramters\n        ---------\n        X : scipy sparse csr_matrix, shape [n_samples, n_features]\n            Data in sparse matrix format.\n\n        Returns\n        -------\n        proba : 2d ndarray, shape [n_samples, n_classes]\n            The probability of the sample for each class in the model.\n        \"\"\"\n        pred = self.predict_all_sample(data)\n        pred_proba = 1.0 / (1.0 + np.exp(-pred))\n        proba = np.vstack((1 - pred_proba, pred_proba)).T\n        return proba\n\n    def predict_all_sample(self, data):\n        \"\"\"Predict vectorized for all samples\"\"\"\n        linear_output = data * self.w\n        v = self.V.T\n        term = (data * v) ** 2 - (data.power(2) * (v ** 2))\n        factor_output = 0.5 * np.sum(term, axis=1)\n        return self.w0 + linear_output + factor_output\n\n    def predict(self, data):\n        \"\"\"\n        Predict class labels for samples in X.\n\n        Parameters\n        ----------\n        X : scipy sparse csr_matrix, shape [n_samples, n_features]\n            Data in sparse matrix format.\n\n        Returns\n        -------\n        Predicted class label per sample.\n        \"\"\"\n        pred_proba = self.predict_proba(data)[:, 1]\n        return pred_proba.round().astype(np.int)\n\n    def __plot_learning_curves__(self):\n        # change default style figure and font size\n        plt.rcParams['figure.figsize'] = 8, 6\n        plt.rcParams['font.size'] = 12\n\n        # one quick way to check that we've implemented\n        # the gradient descent is to ensure that the loss\n        # curve is steadily decreasing\n        plt.plot(self.history_)\n        plt.title('Loss Curve Per Iteration')\n        plt.xlabel('Iterations')\n        plt.ylabel('Loss')\n        plt.show()","metadata":{"execution":{"iopub.status.busy":"2022-03-25T14:36:28.806913Z","iopub.execute_input":"2022-03-25T14:36:28.807093Z","iopub.status.idle":"2022-03-25T14:36:29.824652Z","shell.execute_reply.started":"2022-03-25T14:36:28.807067Z","shell.execute_reply":"2022-03-25T14:36:29.823862Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Training","metadata":{}},{"cell_type":"code","source":"fm = FactorizationMachine(transactions_matrix = sparse_matrix, bought = train_df.bought.values, latent_factors = 20)\nfm.fit(learning_rate=0.001)","metadata":{"execution":{"iopub.status.busy":"2022-03-25T14:36:29.826283Z","iopub.execute_input":"2022-03-25T14:36:29.826526Z","iopub.status.idle":"2022-03-25T14:38:54.311802Z","shell.execute_reply.started":"2022-03-25T14:36:29.826501Z","shell.execute_reply":"2022-03-25T14:38:54.310076Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"fm.__plot_learning_curves__()","metadata":{"execution":{"iopub.status.busy":"2022-03-25T14:38:54.313411Z","iopub.execute_input":"2022-03-25T14:38:54.313623Z","iopub.status.idle":"2022-03-25T14:38:54.520152Z","shell.execute_reply.started":"2022-03-25T14:38:54.313595Z","shell.execute_reply":"2022-03-25T14:38:54.519199Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Prepare for predictions","metadata":{}},{"cell_type":"code","source":"import pandas as pd\nimport os \nimport matplotlib.pyplot as plt\nimport matplotlib.image as mpimg\n\nidx_to_train_article_id = {i: article for i, article in enumerate(np.unique(train_df.article_id))}\n\n# Generate 12 suggestions for a user-products matrix\ndef generate_suggestions(model, matrix, n = 12):\n    proba = model.predict_proba(matrix)\n    index = pd.DataFrame(proba, columns=['No', 'Yes']).sort_values('Yes', ascending = False)[0:n]['Yes'].index\n    value = pd.DataFrame(proba, columns=['No', 'Yes']).sort_values('Yes', ascending = False)[0:n]['Yes'].values\n    #return index, value\n    return [''.join('0' + str(idx_to_train_article_id[i])) for i in index]\n\n\n# Generate submission\ndef submission(model, res_article, res_customer, customer_matrix):\n    submission = {}\n    for i in tqdm(range(len(res_customer))):\n        customer_id = res_customer[i]\n        customer_matrix = get_user_matrix(i)\n        recommendations = generate_suggestions(model, res_article, customer_matrix, n = 12)\n        submission[customer_id] = ' '.join('0' + str(x) for x in recommendations)  \n    return submission\n\ndef display_articles(articles):\n    w = 10\n    h = 10\n    fig = plt.figure(figsize=(8, 8))\n    columns = 4\n    rows = 3\n    for i, article in enumerate(articles[0:12]):\n        path = '../input/h-and-m-personalized-fashion-recommendations/images/' + article[0:3] + '/' + article + '.jpg'\n        if not os.path.isfile(path):\n            continue\n        fig.add_subplot(rows, columns, i + 1)\n        img = mpimg.imread(path)\n        plt.imshow(img)\n    plt.show()\n\ndef get_user_matrix(customer_id, customers_df, transactions_df):\n    n_rows = len(np.unique(train_df.article_id))\n    \n    customers_df = preprocess_customers_df(customers_df, transactions_df)\n    \n    # Create CSR matrix for customers\n    rows = np.arange(n_rows)\n    cols = [customers_id_to_idx[customer_id]] * n_rows\n    data = np.ones(n_rows)\n    csr_customers = sparse.csr_matrix((data, (rows, cols)), shape=(n_rows, len(customers_id_to_idx)))\n\n    # Create CSR matrix for articles\n    rows = np.arange(n_rows)\n    cols = [articles_id_to_idx[article] for article in np.unique(train_df.article_id)]\n    data = np.ones(n_rows)\n    csr_articles = sparse.csr_matrix((data, (rows, cols)), shape=(n_rows, len(articles_id_to_idx)))\n\n    customer_data = customers_df[customers_df['customer_id'] == customer_id].iloc[0]        \n    \n    # Concatenate matrices in CSR format\n    sparse_matrix = sparse.hstack((csr_customers, csr_articles), format='csr')\n    # Age\n    sparse_matrix = hstack((sparse_matrix, [[customer_data['age']]] * n_rows), format='csr')\n    # Active\n    sparse_matrix = hstack((sparse_matrix, [[customer_data['Active']]] * n_rows), format='csr')\n    # Club member status\n    sparse_matrix = hstack((sparse_matrix, [[customer_data['club_member_status']]] * n_rows), format='csr')\n    # Common postal code\n    sparse_matrix = hstack((sparse_matrix, [[customer_data['common_postal_code']]] * n_rows), format='csr')\n    \n    return sparse_matrix\n","metadata":{"execution":{"iopub.status.busy":"2022-03-25T14:38:54.522034Z","iopub.execute_input":"2022-03-25T14:38:54.522201Z","iopub.status.idle":"2022-03-25T14:38:54.982401Z","shell.execute_reply.started":"2022-03-25T14:38:54.522180Z","shell.execute_reply":"2022-03-25T14:38:54.981328Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"sample = pd.read_csv('../input/h-and-m-personalized-fashion-recommendations/sample_submission.csv')","metadata":{"execution":{"iopub.status.busy":"2022-03-25T14:38:54.984226Z","iopub.execute_input":"2022-03-25T14:38:54.984528Z","iopub.status.idle":"2022-03-25T14:39:00.983384Z","shell.execute_reply.started":"2022-03-25T14:38:54.984496Z","shell.execute_reply":"2022-03-25T14:39:00.982541Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Lets get some sample recommendations","metadata":{}},{"cell_type":"code","source":"preprocessed_customers_df = preprocess_customers_df(customers_df, transactions_df)\n\ndef get_random_recommendations(n=5):\n    counter = 0\n    while counter < n:\n        customer_id = customers_df.iloc[np.random.randint(0, len(customers_df))]['customer_id']\n        customer_info = preprocessed_customers_df.loc[preprocessed_customers_df.customer_id == customer_id]\n        if len(customer_info) == 0:\n            continue\n        print(\"Customer info:\")\n        print(customer_info.iloc[0])\n        user_matrix = get_user_matrix(customer_id, customers_df, transactions_df)\n        recommendations = generate_suggestions(fm, user_matrix)\n        print(\"Previously bought\")\n        display_articles(['0' + str(article) for article in train_df.loc[train_df.customer_id == customer_id]['article_id'].values])\n        print(\"Recommendations\")\n        display_articles(recommendations)\n        counter += 1","metadata":{"execution":{"iopub.status.busy":"2022-03-25T14:39:00.984738Z","iopub.execute_input":"2022-03-25T14:39:00.985009Z","iopub.status.idle":"2022-03-25T14:39:04.120119Z","shell.execute_reply.started":"2022-03-25T14:39:00.984973Z","shell.execute_reply":"2022-03-25T14:39:04.118684Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"get_random_recommendations()","metadata":{"execution":{"iopub.status.busy":"2022-03-25T14:39:04.121702Z","iopub.execute_input":"2022-03-25T14:39:04.121970Z","iopub.status.idle":"2022-03-25T14:40:17.137004Z","shell.execute_reply.started":"2022-03-25T14:39:04.121940Z","shell.execute_reply":"2022-03-25T14:40:17.136101Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"#### Doesn't look too bad, right? Let's continue!","metadata":{}},{"cell_type":"markdown","source":"## Predictions for all users\n\n### Time decaying baseline model for default recommendations\nAs in our previous notebook, we will use the time decaying baseline model for customers where we dont have any info","metadata":{}},{"cell_type":"code","source":"data = pd.read_csv(\"../input/h-and-m-personalized-fashion-recommendations/transactions_train.csv\", dtype={'article_id':str})","metadata":{"execution":{"iopub.status.busy":"2022-03-25T14:40:53.869474Z","iopub.execute_input":"2022-03-25T14:40:53.869761Z","iopub.status.idle":"2022-03-25T14:41:50.392030Z","shell.execute_reply.started":"2022-03-25T14:40:53.869729Z","shell.execute_reply":"2022-03-25T14:41:50.390802Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import datetime\n\ndata[\"t_dat\"] = pd.to_datetime(data[\"t_dat\"])\ntrain1 = data.loc[(data[\"t_dat\"] >= datetime.datetime(2020,9,16)) & (data['t_dat'] < datetime.datetime(2020,9,23))]\ntrain2 = data.loc[(data[\"t_dat\"] >= datetime.datetime(2020,9,8)) & (data['t_dat'] < datetime.datetime(2020,9,16))]\ntrain3 = data.loc[(data[\"t_dat\"] >= datetime.datetime(2020,8,31)) & (data['t_dat'] < datetime.datetime(2020,9,8))]\ntrain4 = data.loc[(data[\"t_dat\"] >= datetime.datetime(2020,8,23)) & (data['t_dat'] < datetime.datetime(2020,8,31))]\n\n# List of all purchases per user (has repetitions)\npositive_items_per_user1 = train1.groupby(['customer_id'])['article_id'].apply(list)\npositive_items_per_user2 = train2.groupby(['customer_id'])['article_id'].apply(list)\npositive_items_per_user3 = train3.groupby(['customer_id'])['article_id'].apply(list)\npositive_items_per_user4 = train4.groupby(['customer_id'])['article_id'].apply(list)\n\ntrain = pd.concat([train1, train2], axis=0)\ntrain['pop_factor'] = train['t_dat'].apply(lambda x: 1/(datetime.datetime(2020,9,23) - x).days)\npopular_items_group = train.groupby(['article_id'])['pop_factor'].sum()\n\n_, popular_items = zip(*sorted(zip(popular_items_group, popular_items_group.keys()))[::-1])","metadata":{"execution":{"iopub.status.busy":"2022-03-25T14:42:29.571889Z","iopub.execute_input":"2022-03-25T14:42:29.572351Z","iopub.status.idle":"2022-03-25T14:42:45.529576Z","shell.execute_reply.started":"2022-03-25T14:42:29.572323Z","shell.execute_reply":"2022-03-25T14:42:45.528751Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from collections import Counter\n\npopular_items = list(popular_items)\n\ndef get_popularity_based_prediction(user):\n    user_output = []\n    if user in positive_items_per_user1.keys():\n        most_common_items_of_user = {k:v for k, v in Counter(positive_items_per_user1[user]).most_common()}\n        user_output += list(most_common_items_of_user.keys())[:12]\n    if user in positive_items_per_user2.keys():\n        most_common_items_of_user = {k:v for k, v in Counter(positive_items_per_user2[user]).most_common()}\n        user_output += list(most_common_items_of_user.keys())[:12]\n    if user in positive_items_per_user3.keys():\n        most_common_items_of_user = {k:v for k, v in Counter(positive_items_per_user3[user]).most_common()}\n        user_output += list(most_common_items_of_user.keys())[:12]\n    if user in positive_items_per_user4.keys():\n        most_common_items_of_user = {k:v for k, v in Counter(positive_items_per_user4[user]).most_common()}\n        user_output += list(most_common_items_of_user.keys())[:12]\n    \n    user_output += list(popular_items[:12 - len(user_output)])\n    return user_output","metadata":{"execution":{"iopub.status.busy":"2022-03-25T14:42:49.197564Z","iopub.execute_input":"2022-03-25T14:42:49.197789Z","iopub.status.idle":"2022-03-25T14:42:49.207898Z","shell.execute_reply.started":"2022-03-25T14:42:49.197765Z","shell.execute_reply":"2022-03-25T14:42:49.207029Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Combine models and get predictions","metadata":{}},{"cell_type":"code","source":"preprocessed_customers_df = preprocess_customers_df(customers_df, transactions_df)\n\ndef get_predictions(customers):\n    all_recommendations = []\n    for customer_id in tqdm(customers):\n        customer_info = preprocessed_customers_df.loc[preprocessed_customers_df.customer_id == customer_id]\n        if len(customer_info) == 0:\n            recommendations = get_popularity_based_prediction(customer_id)\n        else:\n            user_matrix = get_user_matrix(customer_id, customers_df, transactions_df)\n            recommendations = generate_suggestions(fm, user_matrix)\n        all_recommendations.append(\" \".join(recommendations))\n    return pd.DataFrame({'customer_id': customers, 'prediction': all_recommendations})\n        ","metadata":{"execution":{"iopub.status.busy":"2022-03-25T14:45:06.733346Z","iopub.execute_input":"2022-03-25T14:45:06.733642Z","iopub.status.idle":"2022-03-25T14:45:09.968356Z","shell.execute_reply.started":"2022-03-25T14:45:06.733610Z","shell.execute_reply":"2022-03-25T14:45:09.967445Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"predictions = get_predictions(sample['customer_id'].values[:500])","metadata":{"execution":{"iopub.status.busy":"2022-03-25T14:49:25.710005Z","iopub.execute_input":"2022-03-25T14:49:25.710543Z","iopub.status.idle":"2022-03-25T15:03:46.632191Z","shell.execute_reply.started":"2022-03-25T14:49:25.710504Z","shell.execute_reply":"2022-03-25T15:03:46.631018Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"predictions","metadata":{"execution":{"iopub.status.busy":"2022-03-25T15:04:05.578778Z","iopub.execute_input":"2022-03-25T15:04:05.579109Z","iopub.status.idle":"2022-03-25T15:04:05.595045Z","shell.execute_reply.started":"2022-03-25T15:04:05.579075Z","shell.execute_reply":"2022-03-25T15:04:05.593445Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"predictions.to_csv('submission.csv', index=False)","metadata":{"execution":{"iopub.status.busy":"2022-03-25T15:04:11.869275Z","iopub.execute_input":"2022-03-25T15:04:11.869730Z","iopub.status.idle":"2022-03-25T15:04:11.889102Z","shell.execute_reply.started":"2022-03-25T15:04:11.869698Z","shell.execute_reply":"2022-03-25T15:04:11.888310Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Conclusion\n\nIn this notebook, we have shown how to implement a factorization machine with some additional encoded features. At this point, we want to note that we have also encoded the last 5 bought articles for each customer which even seemed to have a lower loss (Version 3 of this notebook) but we had to leave it out due to time constraints. During training, we have noticed that the training loss did not decrease a lot and thus this issue could be further explored. We have visualized recommendations and previously bought items for a random sample of customers. We could conclude that subjectivly, the predictions looked good, although some items like socks were predicted every time. Unfortuanetly, our algorithm is not very performant for inference and thus we had to cap the number of predicitons to 500. ","metadata":{}},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]}]}