{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# default_exp models.prod2vec"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Prod2Vec\n",
    "> Implementation of Prod2vec model.\n",
    "\n",
    "> References\n",
    "1. [https://nbviewer.org/github/sparsh-ai/stanza/blob/S543002/2021-07-19-session-based-prod2vec-coveo.ipynb](https://nbviewer.org/github/sparsh-ai/stanza/blob/S543002/2021-07-19-session-based-prod2vec-coveo.ipynb)\n",
    "2. [https://nbviewer.org/github/sparsh-ai/stanza/blob/S543002/2021-06-11-recostep-session-based-recommender-using-word2vec.ipynb](https://nbviewer.org/github/sparsh-ai/stanza/blob/S543002/2021-06-11-recostep-session-based-recommender-using-word2vec.ipynb)\n",
    "3. [https://github.com/mquad/sars_tutorial/blob/master/recommenders/Prod2VecRecommender.py](https://github.com/mquad/sars_tutorial/blob/master/recommenders/Prod2VecRecommender.py)\n",
    "4. [https://nbviewer.org/github/sparsh-ai/stanza/blob/S543002/2021-04-24-rec-medium-word2vec.ipynb](https://nbviewer.org/github/sparsh-ai/stanza/blob/S543002/2021-04-24-rec-medium-word2vec.ipynb)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#hide\n",
    "from nbdev.showdoc import *"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#export\n",
    "import logging\n",
    "import gensim\n",
    "import numpy as np"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#export\n",
    "class Prod2Vec(object):\n",
    "    \"\"\"\n",
    "    Implementation of the Prod2Vec skipgram model from\n",
    "    Grbovic Mihajlo, Vladan Radosavljevic, Nemanja Djuric, Narayan Bhamidipati, Jaikit Savla, Varun Bhagwan, and Doug Sharp.\n",
    "    \"E-commerce in your inbox: Product recommendations at scale.\"\n",
    "    In Proceedings of the 21th ACM SIGKDD International Conference on Knowledge Discovery and Data Mining,\n",
    "    pp. 1809-1818. ACM, 2015.\n",
    "    \"\"\"\n",
    "\n",
    "    logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')\n",
    "    logger = logging.getLogger()\n",
    "\n",
    "    def __init__(self, min_count=2, negative=5, size=100, window=5, decay_alpha=0.9):\n",
    "        \"\"\"\n",
    "        :param min_count: (optional) the minimum item frequency. Items less frequent that min_count will be pruned\n",
    "        :param negative: (optional) the minimum negative samples\n",
    "        :param size: (optional) the size of the embeddings\n",
    "        :param window: (optional) the size of the context window\n",
    "        :param decay_alpha: (optional) the exponential decay factor used to discount the similarity scores for items\n",
    "                back in the user profile. Lower values mean higher discounting of past user interactions. Allows values in [0-1].\n",
    "        \"\"\"\n",
    "        super(Prod2Vec, self).__init__()\n",
    "        self.min_count = min_count\n",
    "        self.negative = negative\n",
    "        self.size = size\n",
    "        self.window = window\n",
    "        self.decay_alpha = decay_alpha\n",
    "\n",
    "    def __str__(self):\n",
    "        return 'Prod2Vec(min_count={min_count}, ' \\\n",
    "               'size={size}, ' \\\n",
    "               'window={window}, ' \\\n",
    "               'decay_alpha={decay_alpha})'.format(**self.__dict__)\n",
    "\n",
    "    def fit(self, train_data):\n",
    "        self.model = gensim.models.Word2Vec(train_data,\n",
    "                                            min_count=self.min_count,\n",
    "                                            negative=self.negative,\n",
    "                                            window=self.window,\n",
    "                                            hs=1,\n",
    "                                            size=self.size,\n",
    "                                            sg=1,\n",
    "                                            workers=-1)\n",
    "        self.model.train(train_data, total_examples = self.model.corpus_count, \n",
    "                         epochs=10, report_delay=1)\n",
    "        # As we do not plan to train the model any further, we are calling\n",
    "        # init_sims(), which will make the model much more memory-efficient\n",
    "        self.model.init_sims(replace=True)\n",
    "\n",
    "    def aggregate_vectors(self, products):\n",
    "        product_vec = []\n",
    "        for i in products:\n",
    "            try:\n",
    "                product_vec.append(self.model[i])\n",
    "            except KeyError:\n",
    "                continue\n",
    "            \n",
    "        return np.mean(product_vec, axis=0)\n",
    "\n",
    "    def recommend(self, user_profile, topk=5):\n",
    "        \"\"\"\n",
    "        Given the user profile return a list of recommendation\n",
    "\n",
    "        Args:\n",
    "            user_profile: list of item ids visited/interacted by the user\n",
    "            topk: (optional) top-k recommendations\n",
    "        \"\"\"\n",
    "        rec = []\n",
    "        try:\n",
    "            vec = self.aggregate_vectors(user_profile)\n",
    "            # extract most similar products for the input vector\n",
    "            rec = self.model.wv.similar_by_vector(vec, topn= topk+1)[1:]\n",
    "        except KeyError:\n",
    "            rec = []\n",
    "\n",
    "        return rec"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Online Retail.xlsx  100%[===================>]  22.62M  10.7MB/s    in 2.1s    \n"
     ]
    }
   ],
   "source": [
    "import pandas as pd\n",
    "\n",
    "!wget -q --show-progress https://archive.ics.uci.edu/ml/machine-learning-databases/00352/Online%20Retail.xlsx\n",
    "\n",
    "df = pd.read_excel('Online Retail.xlsx')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "c67c3bc470394dbc9374345f69804966",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "  0%|          | 0/3935 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "a99361b1142540848001669faaa0d38c",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "  0%|          | 0/437 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# remove missing values\n",
    "df.dropna(inplace=True)\n",
    "\n",
    "# Convert the StockCode to string datatype\n",
    "df['StockCode']= df['StockCode'].astype(str)\n",
    "\n",
    "# Check out the number of unique customers in our dataset\n",
    "customers = df[\"CustomerID\"].unique().tolist()\n",
    "\n",
    "# shuffle customer ID's\n",
    "import random\n",
    "random.shuffle(customers)\n",
    "\n",
    "# extract 90% of customer ID's\n",
    "customers_train = [customers[i] for i in range(round(0.9*len(customers)))]\n",
    "\n",
    "# split data into train and validation set\n",
    "train_df = df[df['CustomerID'].isin(customers_train)]\n",
    "validation_df = df[~df['CustomerID'].isin(customers_train)]\n",
    "\n",
    "# list to capture purchase history of the customers\n",
    "purchases_train = []\n",
    "\n",
    "# populate the list with the product codes\n",
    "from tqdm.notebook import tqdm\n",
    "for i in tqdm(customers_train):\n",
    "    temp = train_df[train_df[\"CustomerID\"] == i][\"StockCode\"].tolist()\n",
    "    purchases_train.append(temp)\n",
    "\n",
    "# list to capture purchase history of the customers\n",
    "purchases_val = []\n",
    "\n",
    "# populate the list with the product codes\n",
    "for i in tqdm(validation_df['CustomerID'].unique()):\n",
    "    temp = validation_df[validation_df[\"CustomerID\"] == i][\"StockCode\"].tolist()\n",
    "    purchases_val.append(temp)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Build word2vec Embeddings for Products\n",
    "# train word2vec model\n",
    "model = Prod2Vec(window=10, negative=5, size=100, min_count=2)\n",
    "model.fit(purchases_train)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import warnings\n",
    "warnings.filterwarnings('ignore')\n",
    "\n",
    "products = train_df[[\"StockCode\", \"Description\"]]\n",
    "\n",
    "# remove duplicates\n",
    "products.drop_duplicates(inplace=True, subset='StockCode', keep=\"last\")\n",
    "\n",
    "# create product-ID and product-description dictionary\n",
    "products_dict = products.groupby('StockCode')['Description'].apply(list).to_dict()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['SET OF 3 BABUSHKA STACKING TINS']\n",
      " \n",
      "['EDWARDIAN HEART PHOTO FRAME', 0.3702189028263092]\n",
      "['SET OF 6 VINTAGE NOTELETS KIT', 0.34610092639923096]\n",
      "['FRENCH STYLE STORAGE JAR JAM', 0.3301945626735687]\n",
      "['BAG 500g SWIRLY MARBLES', 0.3177795708179474]\n",
      "['SPOTTY BUNTING', 0.30998745560646057]\n"
     ]
    }
   ],
   "source": [
    "random_sample = products.sample(1).values\n",
    "recommendations = [[products_dict[a][0], b] for a,b in model.recommend(user_profile=random_sample[:,0])]\n",
    "\n",
    "print(random_sample[:,1])\n",
    "print(' ')\n",
    "for rec in recommendations: print(rec)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['SET OF 5 LUCKY CAT MAGNETS ' 'HEARTS GIFT TAPE'\n",
      " 'PAINTED YELLOW WOODEN DAISY' 'COLOURFUL FLOWER FRUIT BOWL'\n",
      " 'TUSCAN VILLA BIRD FEEDER']\n",
      " \n",
      "['PAINTED YELLOW WOODEN DAISY', 0.498282253742218]\n",
      "['HEARTS GIFT TAPE', 0.4829496443271637]\n",
      "['TUSCAN VILLA BIRD FEEDER', 0.34984883666038513]\n",
      "['STRAWBERRY RAFFIA FOOD COVER', 0.3352939486503601]\n",
      "['IVORY PAPER CUP CAKE CASES ', 0.3215782642364502]\n"
     ]
    }
   ],
   "source": [
    "random_sample = products.sample(5).values\n",
    "recommendations = [[products_dict[a][0], b] for a,b in model.recommend(user_profile=random_sample[:,0])]\n",
    "\n",
    "print(random_sample[:,1])\n",
    "print(' ')\n",
    "for rec in recommendations: print(rec)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Author: Sparsh A.\n",
      "\n",
      "Last updated: 2021-12-26 08:07:18\n",
      "\n",
      "recohut: 0.0.7\n",
      "\n",
      "Compiler    : GCC 7.5.0\n",
      "OS          : Linux\n",
      "Release     : 5.4.144+\n",
      "Machine     : x86_64\n",
      "Processor   : x86_64\n",
      "CPU cores   : 2\n",
      "Architecture: 64bit\n",
      "\n",
      "pandas    : 1.1.5\n",
      "seaborn   : 0.11.2\n",
      "torch     : 1.10.0+cu111\n",
      "IPython   : 5.5.0\n",
      "logging   : 0.5.1.2\n",
      "numpy     : 1.19.5\n",
      "gensim    : 3.6.0\n",
      "matplotlib: 3.2.2\n",
      "\n"
     ]
    }
   ],
   "source": [
    "#hide\n",
    "%reload_ext watermark\n",
    "%watermark -a \"Sparsh A.\" -m -iv -u -t -d -p recohut"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "name": "python3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 0
}
