{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# default_exp models.mpm"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# MPM\n",
    "> Multi-Preferences Model.\n",
    "\n",
    "MPM tries to eliminate the effects of unexpected behaviors by first extracting the users’ instant preferences from their recent historical interactions by a fine-grained preferences module. Then an unexpected-behaviors detector is trained to judge whether these instant preferences are biased by unexpected behaviors. we also integrate user’s general preference in MPM. Finally, an output module is performed to eliminates the effects of unexpected behaviors and integrates all the information to make a final recommendation."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#hide\n",
    "from nbdev.showdoc import *\n",
    "from fastcore.nb_imports import *\n",
    "from fastcore.test 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\n",
    "import numpy as np\n",
    "\n",
    "from recohut.models.layers.tcn import TemporalConvNet"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#export\n",
    "class MPM(nn.Module):\n",
    "    def __init__(self, nb_users, nb_items, embed_dim, history_size):\n",
    "        super(MPM, self).__init__()\n",
    "\n",
    "        self.nb_users = nb_users\n",
    "        self.nb_items = nb_items\n",
    "        self.embed_dim = embed_dim\n",
    "        self.history_size = history_size\n",
    "\n",
    "        #user and item embedding\n",
    "        self.user_embed = nn.Embedding(self.nb_users, self.embed_dim)\n",
    "        self.item_embed = nn.Embedding(self.nb_items, self.embed_dim)\n",
    "        self.user_embed.weight.data.normal_(0., 0.01)\n",
    "        self.item_embed.weight.data.normal_(0., 0.01)\n",
    "\n",
    "        #TCN\n",
    "        nhid = self.embed_dim\n",
    "        level = 5\n",
    "        num_channels = [nhid] * (level - 1) + [embed_dim]\n",
    "        self.tcn = TemporalConvNet(num_inputs=self.embed_dim, num_channels=num_channels, kernel_size=3, dropout=0.25)\n",
    "\n",
    "        #MLP\n",
    "        mlp_layer_sizes = [self.embed_dim * 2, 128, 64, 32]\n",
    "        nb_mlp_layers = len(mlp_layer_sizes)\n",
    "        self.mlp = nn.ModuleList()\n",
    "        for i in range(1, nb_mlp_layers):\n",
    "            self.mlp.extend([nn.Linear(mlp_layer_sizes[i-1], mlp_layer_sizes[i])])\n",
    "\n",
    "        #Output Module\n",
    "        self.output_1 = nn.Linear(mlp_layer_sizes[-1] * (self.history_size + 1),128,bias=True)\n",
    "        self.output_2 = nn.Linear(128,64,bias=True)\n",
    "        self.output_3 = nn.Linear(64,32,bias=True)\n",
    "        self.output_4 = nn.Linear(32,1,bias=True)\n",
    "\n",
    "        def golorot_uniform(layer):\n",
    "            fan_in, fan_out = layer.in_features, layer.out_features\n",
    "            limit = np.sqrt(6. / (fan_in + fan_out))\n",
    "            layer.weight.data.uniform_(-limit, limit)\n",
    "\n",
    "        def lecunn_uniform(layer):\n",
    "            fan_in, fan_out = layer.in_features, layer.out_features  # noqa: F841, E501\n",
    "            limit = np.sqrt(3. / fan_in)\n",
    "            layer.weight.data.uniform_(-limit, limit)\n",
    "\n",
    "        for layer in self.mlp:\n",
    "            if type(layer) != nn.Linear:\n",
    "                continue\n",
    "            golorot_uniform(layer)\n",
    "\n",
    "        lecunn_uniform(self.output_1)\n",
    "        lecunn_uniform(self.output_2)\n",
    "        lecunn_uniform(self.output_3)\n",
    "        lecunn_uniform(self.output_4)\n",
    "\n",
    "    def forward(self, user, item, history,sigmoid=False):\n",
    "\n",
    "        item = self.item_embed(item)\n",
    "\n",
    "        #multi granularity preference module\n",
    "        xhistory = self.item_embed(history)\n",
    "\n",
    "        output_TCN = self.tcn(xhistory.transpose(1,2)).transpose(1,2)\n",
    "\n",
    "        predict_vectors = list()\n",
    "\n",
    "        for i in range(self.history_size):\n",
    "            preference = output_TCN[:, i, :]\n",
    "            output_mlp = torch.cat((preference,item),dim=1)\n",
    "            for j, layer in enumerate(self.mlp):\n",
    "                output_mlp = layer(output_mlp)\n",
    "                output_mlp = F.relu(output_mlp)\n",
    "\n",
    "            output_mlp = output_mlp.view(-1, 1, output_mlp.size()[-1])\n",
    "            predict_vectors.append(output_mlp)\n",
    "\n",
    "        predict_vectors_sum = torch.cat(predict_vectors, dim=1)\n",
    "\n",
    "        # general preference module\n",
    "        user = self.user_embed(user)\n",
    "        xmlp = torch.cat((user, item), dim=1)\n",
    "        for i, layer in enumerate(self.mlp):\n",
    "            xmlp = layer(xmlp)\n",
    "            xmlp = F.relu(xmlp)\n",
    "\n",
    "        #output module\n",
    "        xmlp = xmlp.view(-1,1,xmlp.size()[-1])\n",
    "        x = torch.cat((predict_vectors_sum,xmlp),dim=1)\n",
    "        x = x.view(x.size()[0],-1)\n",
    "        x = self.output_1(x)\n",
    "        x = F.relu(x)\n",
    "        x = self.output_2(x)\n",
    "        x = F.relu(x)\n",
    "        x = self.output_3(x)\n",
    "        x = F.relu(x)\n",
    "        x = self.output_4(x)\n",
    "\n",
    "        if sigmoid:\n",
    "            x = torch.sigmoid(x)\n",
    "        return x"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> **References:-**\n",
    "- https://arxiv.org/pdf/2112.11023v1.pdf\n",
    "- https://github.com/chenjie04/MPM/blob/master/MPM.py"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> **Tutorials:-**\n",
    "- [Training MPM Recommendation Model on ML-1m in PyTorch](https://nbviewer.org/gist/sparsh-ai/9d7b35e4a4dd7ef10a2fdc7f17b1943c)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Author: Sparsh A.\n",
      "\n",
      "Last updated: 2021-12-31 08:21:05\n",
      "\n",
      "recohut: 0.0.8\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",
      "PIL       : 7.1.2\n",
      "IPython   : 5.5.0\n",
      "torch     : 1.10.0+cu111\n",
      "matplotlib: 3.2.2\n",
      "numpy     : 1.19.5\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
}
