{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# default_exp models.factorization"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Factorization\n",
    "> Matrix Factorization algorithmic modules for recommender systems."
   ]
  },
  {
   "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",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#export\n",
    "class MF(nn.Module):\n",
    "    def __init__(self, num_users, num_items, emb_size=100):\n",
    "        super(MF, self).__init__()\n",
    "        self.user_emb = nn.Embedding(num_users, emb_size)\n",
    "        self.item_emb = nn.Embedding(num_items, emb_size)\n",
    "        self.user_emb.weight.data.uniform_(0, 0.05)\n",
    "        self.item_emb.weight.data.uniform_(0, 0.05)\n",
    "        \n",
    "    def forward(self, u, v):\n",
    "        u = self.user_emb(u)\n",
    "        v = self.item_emb(v)\n",
    "        return (u*v).sum(1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#export\n",
    "class BiasedMF(nn.Module):\n",
    "    def __init__(self, num_users, num_items, emb_size=100):\n",
    "        super(BiasedMF, self).__init__()\n",
    "        self.user_emb = nn.Embedding(num_users, emb_size)\n",
    "        self.user_bias = nn.Embedding(num_users, 1)\n",
    "        self.item_emb = nn.Embedding(num_items, emb_size)\n",
    "        self.item_bias = nn.Embedding(num_items, 1)\n",
    "        self.user_emb.weight.data.uniform_(0,0.05)\n",
    "        self.item_emb.weight.data.uniform_(0,0.05)\n",
    "        self.user_bias.weight.data.uniform_(-0.01,0.01)\n",
    "        self.item_bias.weight.data.uniform_(-0.01,0.01)\n",
    "        \n",
    "    def forward(self, u, v):\n",
    "        U = self.user_emb(u)\n",
    "        V = self.item_emb(v)\n",
    "        b_u = self.user_bias(u).squeeze()\n",
    "        b_v = self.item_bias(v).squeeze()\n",
    "        return (U*V).sum(1) +  b_u  + b_v"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#export\n",
    "class NeuMF(nn.Module):\n",
    "    def __init__(self, args, num_users, num_items):\n",
    "        super(NeuMF, self).__init__()\n",
    "        self.num_users = num_users\n",
    "        self.num_items = num_items\n",
    "        self.factor_num_mf = args.factor_num\n",
    "        self.factor_num_mlp =  int(args.layers[0]/2)\n",
    "        self.layers = args.layers\n",
    "        self.dropout = args.dropout\n",
    "\n",
    "        self.embedding_user_mlp = nn.Embedding(num_embeddings=self.num_users, embedding_dim=self.factor_num_mlp)\n",
    "        self.embedding_item_mlp = nn.Embedding(num_embeddings=self.num_items, embedding_dim=self.factor_num_mlp)\n",
    "\n",
    "        self.embedding_user_mf = nn.Embedding(num_embeddings=self.num_users, embedding_dim=self.factor_num_mf)\n",
    "        self.embedding_item_mf = nn.Embedding(num_embeddings=self.num_items, embedding_dim=self.factor_num_mf)\n",
    "\n",
    "        self.fc_layers = nn.ModuleList()\n",
    "        for idx, (in_size, out_size) in enumerate(zip(args.layers[:-1], args.layers[1:])):\n",
    "            self.fc_layers.append(torch.nn.Linear(in_size, out_size))\n",
    "            self.fc_layers.append(nn.ReLU())\n",
    "\n",
    "        self.affine_output = nn.Linear(in_features=args.layers[-1] + self.factor_num_mf, out_features=1)\n",
    "        self.logistic = nn.Sigmoid()\n",
    "        self.init_weight()\n",
    "\n",
    "    def init_weight(self):\n",
    "        nn.init.normal_(self.embedding_user_mlp.weight, std=0.01)\n",
    "        nn.init.normal_(self.embedding_item_mlp.weight, std=0.01)\n",
    "        nn.init.normal_(self.embedding_user_mf.weight, std=0.01)\n",
    "        nn.init.normal_(self.embedding_item_mf.weight, std=0.01)\n",
    "        \n",
    "        for m in self.fc_layers:\n",
    "            if isinstance(m, nn.Linear):\n",
    "                nn.init.xavier_uniform_(m.weight)\n",
    "                \n",
    "        nn.init.xavier_uniform_(self.affine_output.weight)\n",
    "\n",
    "        for m in self.modules():\n",
    "            if isinstance(m, nn.Linear) and m.bias is not None:\n",
    "                m.bias.data.zero_()\n",
    "\n",
    "    def forward(self, user_indices, item_indices):\n",
    "        user_embedding_mlp = self.embedding_user_mlp(user_indices)\n",
    "        item_embedding_mlp = self.embedding_item_mlp(item_indices)\n",
    "\n",
    "        user_embedding_mf = self.embedding_user_mf(user_indices)\n",
    "        item_embedding_mf = self.embedding_item_mf(item_indices)\n",
    "\n",
    "        mlp_vector = torch.cat([user_embedding_mlp, item_embedding_mlp], dim=-1)\n",
    "        mf_vector =torch.mul(user_embedding_mf, item_embedding_mf)\n",
    "\n",
    "        for idx, _ in enumerate(range(len(self.fc_layers))):\n",
    "            mlp_vector = self.fc_layers[idx](mlp_vector)\n",
    "\n",
    "        vector = torch.cat([mlp_vector, mf_vector], dim=-1)\n",
    "        logits = self.affine_output(vector)\n",
    "        rating = self.logistic(logits)\n",
    "        return rating.squeeze()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#export\n",
    "class GMF(nn.Module):\n",
    "    def __init__(self, args, num_users, num_items):\n",
    "        super(GMF, self).__init__()\n",
    "        self.num_users = num_users\n",
    "        self.num_items = num_items\n",
    "        self.factor_num = args.factor_num\n",
    "\n",
    "        self.embedding_user = nn.Embedding(num_embeddings=self.num_users, embedding_dim=self.factor_num)\n",
    "        self.embedding_item = nn.Embedding(num_embeddings=self.num_items, embedding_dim=self.factor_num)\n",
    "\n",
    "        self.affine_output = nn.Linear(in_features=self.factor_num, out_features=1)\n",
    "        self.logistic = nn.Sigmoid()\n",
    "\n",
    "    def forward(self, user_indices, item_indices):\n",
    "        user_embedding = self.embedding_user(user_indices)\n",
    "        item_embedding = self.embedding_item(item_indices)\n",
    "        element_product = torch.mul(user_embedding, item_embedding)\n",
    "        logits = self.affine_output(element_product)\n",
    "        rating = self.logistic(logits)\n",
    "        return rating\n",
    "\n",
    "    def init_weight(self):\n",
    "        pass"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# import torch\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",
    "# from recohut.models.dnn import CollabFNet\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",
    "# # event implicit to rating conversion\n",
    "# event_weights = {'click':1, 'add':2, 'purchase':4}\n",
    "# event_maps = dict({'EVENT_TO_IDX':event_weights})\n",
    "# df_train = le(df_train, col='EVENT', maps=event_maps)\n",
    "# df_valid = le(df_valid, col='EVENT', maps=event_maps)\n",
    "# print(f\"Processed Train set:\\n\\n{df_train}\\n{'='*100}\\n\")\n",
    "# print(f\"Processed Validation set:\\n\\n{df_valid}\\n{'='*100}\\n\")\n",
    "\n",
    "# # get number of unique users and items\n",
    "# num_users = len(df_train.USERID.unique())\n",
    "# num_items = len(df_train.ITEMID.unique())\n",
    "# print(f\"There are {num_users} users and {num_items} items.\\n{'='*100}\\n\")\n",
    "\n",
    "# # training and testing related helper functions\n",
    "# def train_epocs(model, epochs=10, lr=0.01, wd=0.0, unsqueeze=False):\n",
    "#     optimizer = torch.optim.Adam(model.parameters(), lr=lr, weight_decay=wd)\n",
    "#     model.train()\n",
    "#     for i in range(epochs):\n",
    "#         users = torch.LongTensor(df_train.USERID.values) # .cuda()\n",
    "#         items = torch.LongTensor(df_train.ITEMID.values) #.cuda()\n",
    "#         ratings = torch.FloatTensor(df_train.EVENT.values) #.cuda()\n",
    "#         if unsqueeze:\n",
    "#             ratings = ratings.unsqueeze(1)\n",
    "#         y_hat = model(users, items)\n",
    "#         loss = F.mse_loss(y_hat, ratings)\n",
    "#         optimizer.zero_grad()\n",
    "#         loss.backward()\n",
    "#         optimizer.step()\n",
    "#         print(loss.item()) \n",
    "#     test_loss(model, unsqueeze)\n",
    "\n",
    "# def test_loss(model, unsqueeze=False):\n",
    "#     model.eval()\n",
    "#     users = torch.LongTensor(df_valid.USERID.values) #.cuda()\n",
    "#     items = torch.LongTensor(df_valid.ITEMID.values) #.cuda()\n",
    "#     ratings = torch.FloatTensor(df_valid.EVENT.values) #.cuda()\n",
    "#     if unsqueeze:\n",
    "#         ratings = ratings.unsqueeze(1)\n",
    "#     y_hat = model(users, items)\n",
    "#     loss = F.mse_loss(y_hat, ratings)\n",
    "#     print(\"test loss %.3f \" % loss.item())\n",
    "\n",
    "# # training MF model\n",
    "# model = MF(num_users, num_items, emb_size=100) # .cuda() if you have a GPU\n",
    "# print(f\"Training MF model:\\n\")\n",
    "# train_epocs(model, epochs=10, lr=0.1)\n",
    "# print(f\"\\n{'='*100}\\n\")\n",
    "\n",
    "# # training MF with bias model\n",
    "# model = MF_bias(num_users, num_items, emb_size=100) #.cuda()\n",
    "# print(f\"Training MF+bias model:\\n\")\n",
    "# train_epocs(model, epochs=10, lr=0.05, wd=1e-5)\n",
    "# print(f\"\\n{'='*100}\\n\")\n",
    "\n",
    "# # training MLP model\n",
    "# model = CollabFNet(num_users, num_items, emb_size=100) #.cuda()\n",
    "# print(f\"Training MLP model:\\n\")\n",
    "# train_epocs(model, epochs=15, lr=0.05, wd=1e-6, unsqueeze=True)\n",
    "# print(f\"\\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",
    "# Processed Train set:\n",
    "#     USERID  ITEMID  EVENT   TIMESTAMP\n",
    "# 0        0       0      1  2000-01-01\n",
    "# 2        0       1      1  2000-01-02\n",
    "# 5        1       0      1  2000-01-01\n",
    "# 6        1       1      4  2000-01-01\n",
    "# 7        1       0      2  2000-01-03\n",
    "# 8        1       1      4  2000-01-03\n",
    "# 10       2       2      1  2000-01-01\n",
    "# 11       2       2      1  2000-01-03\n",
    "# 12       2       2      2  2000-01-03\n",
    "# 13       2       2      4  2000-01-03\n",
    "# ====================================================================================================\n",
    "# Processed Validation set:\n",
    "#     USERID  ITEMID  EVENT   TIMESTAMP\n",
    "# 4        0       1      4  2000-01-02\n",
    "# 9        1       2      4  2000-01-03\n",
    "# 14       2       0      1  2000-01-04\n",
    "# ====================================================================================================\n",
    "# There are 3 users and 3 items.\n",
    "# ====================================================================================================\n",
    "# Training MF model:\n",
    "# 5.836816787719727\n",
    "# 1.993103265762329\n",
    "# 4.549840450286865\n",
    "# 1.5779536962509155\n",
    "# 1.285771131515503\n",
    "# 1.926152229309082\n",
    "# 2.242276191711426\n",
    "# 2.270019054412842\n",
    "# 2.3635096549987793\n",
    "# 2.272618055343628\n",
    "# test loss 9.208 \n",
    "# ====================================================================================================\n",
    "# Training MF+bias model:\n",
    "# 5.8399200439453125\n",
    "# 3.7661311626434326\n",
    "# 1.8716331720352173\n",
    "# 1.6015545129776\n",
    "# 1.5306222438812256\n",
    "# 1.2995147705078125\n",
    "# 1.1046849489212036\n",
    "# 1.1331274509429932\n",
    "# 1.2109991312026978\n",
    "# 1.2451963424682617\n",
    "# test loss 5.625 \n",
    "# ====================================================================================================\n",
    "# Training MLP model:\n",
    "# 4.953568458557129\n",
    "# 9.649873733520508\n",
    "# 1.1805670261383057\n",
    "# 2.54287052154541\n",
    "# 2.6113314628601074\n",
    "# 2.1839144229888916\n",
    "# 1.4144573211669922\n",
    "# 0.8893814086914062\n",
    "# 0.7603365182876587\n",
    "# 1.240354061126709\n",
    "# 1.1316341161727905\n",
    "# 0.8014519810676575\n",
    "# 0.7997692823410034\n",
    "# 0.8474739789962769\n",
    "# 0.9691768884658813\n",
    "# test loss 5.207 \n",
    "# ====================================================================================================\n",
    "# \"\"\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Author: Sparsh A.\n",
      "\n",
      "Last updated: 2021-12-18 06:19: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
}
