{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# default_exp models.embedding"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Embedding\n",
    "> Embedding Models."
   ]
  },
  {
   "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 torch\n",
    "from itertools import zip_longest\n",
    "from torch import nn"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#export\n",
    "class EmbeddingNet(torch.nn.Module):\n",
    "    \"\"\"\n",
    "    Creates a dense network with embedding layers.\n",
    "    \n",
    "    Args:\n",
    "    \n",
    "        n_users:            \n",
    "            Number of unique users in the dataset.\n",
    "        n_items: \n",
    "            Number of unique items in the dataset.\n",
    "        n_factors: \n",
    "            Number of columns in the embeddings matrix.\n",
    "        embedding_dropout: \n",
    "            Dropout rate to apply right after embeddings layer.\n",
    "        hidden:\n",
    "            A single integer or a list of integers defining the number of \n",
    "            units in hidden layer(s).\n",
    "        dropouts: \n",
    "            A single integer or a list of integers defining the dropout \n",
    "            layers rates applyied right after each of hidden layers.\n",
    "            \n",
    "    \"\"\"\n",
    "    def __init__(self, n_users, n_items,\n",
    "                 n_factors=50, embedding_dropout=0.02, \n",
    "                 hidden=10, dropouts=0.2):\n",
    "        \n",
    "        super().__init__()\n",
    "        hidden = get_list(hidden)\n",
    "        dropouts = get_list(dropouts)\n",
    "        n_last = hidden[-1]\n",
    "        \n",
    "        def gen_layers(n_in):\n",
    "            \"\"\"\n",
    "            A generator that yields a sequence of hidden layers and \n",
    "            their activations/dropouts.\n",
    "            \n",
    "            Note that the function captures `hidden` and `dropouts` \n",
    "            values from the outer scope.\n",
    "            \"\"\"\n",
    "            nonlocal hidden, dropouts\n",
    "            assert len(dropouts) <= len(hidden)\n",
    "            \n",
    "            for n_out, rate in zip_longest(hidden, dropouts):\n",
    "                yield torch.nn.Linear(n_in, n_out)\n",
    "                yield torch.nn.ReLU()\n",
    "                if rate is not None and rate > 0.:\n",
    "                    yield torch.nn.Dropout(rate)\n",
    "                n_in = n_out\n",
    "            \n",
    "        self.u = torch.nn.Embedding(n_users, n_factors)\n",
    "        self.m = torch.nn.Embedding(n_items, n_factors)\n",
    "        self.drop = torch.nn.Dropout(embedding_dropout)\n",
    "        self.hidden = torch.nn.Sequential(*list(gen_layers(n_factors * 2)))\n",
    "        self.fc = torch.nn.Linear(n_last, 1)\n",
    "        self._init()\n",
    "        \n",
    "    def forward(self, users, items, minmax=None):\n",
    "        features = torch.cat([self.u(users), self.m(items)], dim=1)\n",
    "        x = self.drop(features)\n",
    "        x = self.hidden(x)\n",
    "        out = torch.sigmoid(self.fc(x))\n",
    "        if minmax is not None:\n",
    "            min_rating, max_rating = minmax\n",
    "            out = out*(max_rating - min_rating + 1) + min_rating - 0.5\n",
    "        return out\n",
    "    \n",
    "    def _init(self):\n",
    "        \"\"\"\n",
    "        Setup embeddings and hidden layers with reasonable initial values.\n",
    "        \"\"\"\n",
    "        \n",
    "        def init(m):\n",
    "            if type(m) == torch.nn.Linear:\n",
    "                torch.nn.init.xavier_uniform_(m.weight)\n",
    "                m.bias.data.fill_(0.01)\n",
    "                \n",
    "        self.u.weight.data.uniform_(-0.05, 0.05)\n",
    "        self.m.weight.data.uniform_(-0.05, 0.05)\n",
    "        self.hidden.apply(init)\n",
    "        init(self.fc)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#export\n",
    "def get_list(n):\n",
    "    if isinstance(n, (int, float)):\n",
    "        return [n]\n",
    "    elif hasattr(n, '__iter__'):\n",
    "        return list(n)\n",
    "    raise TypeError('layers configuraiton should be a single number or a list of numbers')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# import torch\n",
    "# import torch.nn as nn\n",
    "# import torch.nn.functional as F\n",
    "\n",
    "# from recohut.datasets.synthetic import Synthetic\n",
    "# from recohut.transforms.split import chrono_split\n",
    "# from recohut.transforms.encode import label_encode as le\n",
    "\n",
    "# # generate synthetic implicit data\n",
    "# synt = Synthetic()\n",
    "# df = synt.implicit()\n",
    "\n",
    "# # drop duplicates\n",
    "# df = df.drop_duplicates()\n",
    "\n",
    "# # chronological split\n",
    "# df_train, df_valid = chrono_split(df)\n",
    "# print(f\"Train set:\\n\\n{df_train}\\n{'='*100}\\n\")\n",
    "# print(f\"Validation set:\\n\\n{df_valid}\\n{'='*100}\\n\")\n",
    "\n",
    "# # label encoding\n",
    "# df_train, uid_maps = le(df_train, col='USERID')\n",
    "# df_train, iid_maps = le(df_train, col='ITEMID')\n",
    "# df_valid = le(df_valid, col='USERID', maps=uid_maps)\n",
    "# df_valid = le(df_valid, col='ITEMID', maps=iid_maps)\n",
    "\n",
    "# # an Embedding module containing 10 user or item embedding size 3\n",
    "# # embedding will be initialized at random\n",
    "# embed = nn.Embedding(10, 2)\n",
    "\n",
    "# # given a list of ids we can \"look up\" the embedding corresponing to each id\n",
    "# ids = [1,2,0,4,5,1]\n",
    "# a = torch.LongTensor([ids])\n",
    "# print(f\"Randomly initialized Embeddings of a list of ids {ids}:\\n\\n{embed(a)}\\n{'='*100}\\n\")\n",
    "\n",
    "# # initializing and multiplying users, items embeddings for the sample dataset\n",
    "# emb_size = 2\n",
    "# user_emb = nn.Embedding(df_train.USERID.nunique(), emb_size)\n",
    "# item_emb = nn.Embedding(df_train.ITEMID.nunique(), emb_size)\n",
    "# users = torch.LongTensor(df_train.USERID.values)\n",
    "# items = torch.LongTensor(df_train.ITEMID.values)\n",
    "# U = user_emb(users)\n",
    "# V = item_emb(items)\n",
    "# print(f\"User embeddings of length {emb_size}:\\n\\n{U}\\n{'='*100}\\n\")\n",
    "# print(f\"Item embeddings of length {emb_size}:\\n\\n{V}\\n{'='*100}\\n\")\n",
    "# print(f\"Element-wise multiplication of user and item embeddings:\\n\\n{U*V}\\n{'='*100}\\n\")\n",
    "# print(f\"Dot product per row:\\n\\n{(U*V).sum(1)}\\n{'='*100}\\n\")\n",
    "\n",
    "\n",
    "# \"\"\"\n",
    "# Train set:\n",
    "#     USERID  ITEMID     EVENT   TIMESTAMP\n",
    "# 0        1       1     click  2000-01-01\n",
    "# 2        1       2     click  2000-01-02\n",
    "# 5        2       1     click  2000-01-01\n",
    "# 6        2       2  purchase  2000-01-01\n",
    "# 7        2       1       add  2000-01-03\n",
    "# 8        2       2  purchase  2000-01-03\n",
    "# 10       3       3     click  2000-01-01\n",
    "# 11       3       3     click  2000-01-03\n",
    "# 12       3       3       add  2000-01-03\n",
    "# 13       3       3  purchase  2000-01-03\n",
    "# ====================================================================================================\n",
    "# Validation set:\n",
    "#     USERID  ITEMID     EVENT   TIMESTAMP\n",
    "# 4        1       2  purchase  2000-01-02\n",
    "# 9        2       3  purchase  2000-01-03\n",
    "# 14       3       1     click  2000-01-04\n",
    "# ====================================================================================================\n",
    "# Randomly initialized Embeddings of a list of ids [1, 2, 0, 4, 5, 1]:\n",
    "# tensor([[[-0.4989, -0.0017],\n",
    "#          [ 0.2724,  0.1308],\n",
    "#          [-0.3845,  1.0548],\n",
    "#          [ 0.0951, -0.7816],\n",
    "#          [-1.2381,  0.4325],\n",
    "#          [-0.4989, -0.0017]]], grad_fn=<EmbeddingBackward>)\n",
    "# ====================================================================================================\n",
    "# User embeddings of length 2:\n",
    "# tensor([[-0.7574, -1.1494],\n",
    "#         [-0.7574, -1.1494],\n",
    "#         [ 1.3911,  1.0157],\n",
    "#         [ 1.3911,  1.0157],\n",
    "#         [ 1.3911,  1.0157],\n",
    "#         [ 1.3911,  1.0157],\n",
    "#         [ 0.0271, -1.2206],\n",
    "#         [ 0.0271, -1.2206],\n",
    "#         [ 0.0271, -1.2206],\n",
    "#         [ 0.0271, -1.2206]], grad_fn=<EmbeddingBackward>)\n",
    "# ====================================================================================================\n",
    "# Item embeddings of length 2:\n",
    "# tensor([[ 0.0406,  0.4805],\n",
    "#         [-0.7570, -1.6676],\n",
    "#         [ 0.0406,  0.4805],\n",
    "#         [-0.7570, -1.6676],\n",
    "#         [ 0.0406,  0.4805],\n",
    "#         [-0.7570, -1.6676],\n",
    "#         [-0.9237,  1.2666],\n",
    "#         [-0.9237,  1.2666],\n",
    "#         [-0.9237,  1.2666],\n",
    "#         [-0.9237,  1.2666]], grad_fn=<EmbeddingBackward>)\n",
    "# ====================================================================================================\n",
    "# Element-wise multiplication of user and item embeddings:\n",
    "# tensor([[-0.0308, -0.5522],\n",
    "#         [ 0.5733,  1.9167],\n",
    "#         [ 0.0565,  0.4880],\n",
    "#         [-1.0530, -1.6937],\n",
    "#         [ 0.0565,  0.4880],\n",
    "#         [-1.0530, -1.6937],\n",
    "#         [-0.0251, -1.5460],\n",
    "#         [-0.0251, -1.5460],\n",
    "#         [-0.0251, -1.5460],\n",
    "#         [-0.0251, -1.5460]], grad_fn=<MulBackward0>)\n",
    "# ====================================================================================================\n",
    "# Dot product per row:\n",
    "# tensor([-0.5830,  2.4900,  0.5445, -2.7467,  0.5445, -2.7467, -1.5711, -1.5711,\n",
    "#         -1.5711, -1.5711], grad_fn=<SumBackward1>)\n",
    "# ====================================================================================================\n",
    "# \"\"\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#export\n",
    "class GroupEmbedding(nn.Module):\n",
    "    \"\"\"\n",
    "    Embedding Network\n",
    "    \"\"\"\n",
    "\n",
    "    def __init__(self, embedding_size: int, user_num: int, item_num: int):\n",
    "        \"\"\"\n",
    "        Initialize Embedding\n",
    "        :param embedding_size: embedding size\n",
    "        :param user_num: number of users\n",
    "        :param item_num: number of items\n",
    "        \"\"\"\n",
    "        super(GroupEmbedding, self).__init__()\n",
    "        self.user_embedding = nn.Embedding(user_num + 1, embedding_size)\n",
    "        self.item_embedding = nn.Embedding(item_num + 1, embedding_size)\n",
    "        self.user_attention = nn.Sequential(\n",
    "            nn.Linear(embedding_size, embedding_size),\n",
    "            nn.ReLU(),\n",
    "            nn.Linear(embedding_size, 1)\n",
    "        )\n",
    "        self.user_softmax = nn.Softmax(dim=-1)\n",
    "\n",
    "    def forward(self, group_members, history):\n",
    "        \"\"\"\n",
    "        Forward\n",
    "        :param group_members: group members\n",
    "        :param history: browsing history of items\n",
    "        :return: embedded state\n",
    "        \"\"\"\n",
    "        embedded_group_members = self.user_embedding(group_members)\n",
    "        group_member_attentions = self.user_softmax(self.user_attention(embedded_group_members))\n",
    "        embedded_group = torch.squeeze(torch.inner(group_member_attentions.T, embedded_group_members.T))\n",
    "        embedded_history = torch.flatten(self.item_embedding(history), start_dim=-2)\n",
    "        embedded_state = torch.cat([embedded_group, embedded_history], dim=-1)\n",
    "        return embedded_state"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor([ 1.4833,  1.4087,  0.9842, -1.9608, -0.5692,  0.9200,  1.1108,  1.2899,\n",
       "        -1.0670,  1.1149, -0.1407,  0.8058, -0.1740, -0.6787,  0.9383,  0.4889],\n",
       "       grad_fn=<CatBackward0>)"
      ]
     },
     "execution_count": null,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "torch.random.manual_seed(0)\n",
    "group_embeddings = GroupEmbedding(embedding_size=4,\n",
    "                      user_num=5,\n",
    "                      item_num=5)\n",
    "\n",
    "group_embeddings.forward(group_members = torch.tensor((2,3,1)),\n",
    "                         history = torch.tensor((2,5,1)))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Author: Sparsh A.\n",
      "\n",
      "Last updated: 2021-12-19 10:30:22\n",
      "\n",
      "Compiler    : GCC 7.5.0\n",
      "OS          : Linux\n",
      "Release     : 5.4.104+\n",
      "Machine     : x86_64\n",
      "Processor   : x86_64\n",
      "CPU cores   : 2\n",
      "Architecture: 64bit\n",
      "\n",
      "torch  : 1.10.0+cu111\n",
      "IPython: 5.5.0\n",
      "\n"
     ]
    }
   ],
   "source": [
    "#hide\n",
    "!pip install -q watermark\n",
    "%reload_ext watermark\n",
    "%watermark -a \"Sparsh A.\" -m -iv -u -t -d"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "name": "python3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 0
}
