{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<a href=\"https://colab.research.google.com/github/RecoHut-Projects/recohut/blob/master/tutorials/modeling/T936914_siren_ml1m_torch_gpu.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# SiReN on ML-1m in PyTorch"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## **Step 1 - Setup the environment**"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### **1.1 Install libraries**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Looking in links: https://pytorch-geometric.com/whl/torch-1.10.0+cu111.html\n",
      "Collecting torch-scatter\n",
      "  Downloading https://data.pyg.org/whl/torch-1.10.0%2Bcu113/torch_scatter-2.0.9-cp37-cp37m-linux_x86_64.whl (7.9 MB)\n",
      "\u001b[K     |████████████████████████████████| 7.9 MB 4.9 MB/s \n",
      "\u001b[?25hInstalling collected packages: torch-scatter\n",
      "Successfully installed torch-scatter-2.0.9\n",
      "Looking in links: https://pytorch-geometric.com/whl/torch-1.10.0+cu111.html\n",
      "Collecting torch-sparse\n",
      "  Downloading https://data.pyg.org/whl/torch-1.10.0%2Bcu113/torch_sparse-0.6.12-cp37-cp37m-linux_x86_64.whl (3.5 MB)\n",
      "\u001b[K     |████████████████████████████████| 3.5 MB 4.3 MB/s \n",
      "\u001b[?25hRequirement already satisfied: scipy in /usr/local/lib/python3.7/dist-packages (from torch-sparse) (1.4.1)\n",
      "Requirement already satisfied: numpy>=1.13.3 in /usr/local/lib/python3.7/dist-packages (from scipy->torch-sparse) (1.19.5)\n",
      "Installing collected packages: torch-sparse\n",
      "Successfully installed torch-sparse-0.6.12\n",
      "Looking in links: https://pytorch-geometric.com/whl/torch-1.10.0+cu111.html\n",
      "Collecting torch-cluster\n",
      "  Downloading https://data.pyg.org/whl/torch-1.10.0%2Bcu113/torch_cluster-1.5.9-cp37-cp37m-linux_x86_64.whl (2.3 MB)\n",
      "\u001b[K     |████████████████████████████████| 2.3 MB 4.7 MB/s \n",
      "\u001b[?25hInstalling collected packages: torch-cluster\n",
      "Successfully installed torch-cluster-1.5.9\n",
      "Looking in links: https://pytorch-geometric.com/whl/torch-1.10.0+cu111.html\n",
      "Collecting torch-spline-conv\n",
      "  Downloading https://data.pyg.org/whl/torch-1.10.0%2Bcu113/torch_spline_conv-1.2.1-cp37-cp37m-linux_x86_64.whl (747 kB)\n",
      "\u001b[K     |████████████████████████████████| 747 kB 5.4 MB/s \n",
      "\u001b[?25hInstalling collected packages: torch-spline-conv\n",
      "Successfully installed torch-spline-conv-1.2.1\n",
      "Collecting torch-geometric\n",
      "  Downloading torch_geometric-2.0.2.tar.gz (325 kB)\n",
      "\u001b[K     |████████████████████████████████| 325 kB 5.4 MB/s \n",
      "\u001b[?25hRequirement already satisfied: numpy in /usr/local/lib/python3.7/dist-packages (from torch-geometric) (1.19.5)\n",
      "Requirement already satisfied: tqdm in /usr/local/lib/python3.7/dist-packages (from torch-geometric) (4.62.3)\n",
      "Requirement already satisfied: scipy in /usr/local/lib/python3.7/dist-packages (from torch-geometric) (1.4.1)\n",
      "Requirement already satisfied: networkx in /usr/local/lib/python3.7/dist-packages (from torch-geometric) (2.6.3)\n",
      "Requirement already satisfied: scikit-learn in /usr/local/lib/python3.7/dist-packages (from torch-geometric) (1.0.1)\n",
      "Requirement already satisfied: requests in /usr/local/lib/python3.7/dist-packages (from torch-geometric) (2.23.0)\n",
      "Requirement already satisfied: pandas in /usr/local/lib/python3.7/dist-packages (from torch-geometric) (1.1.5)\n",
      "Collecting rdflib\n",
      "  Downloading rdflib-6.1.1-py3-none-any.whl (482 kB)\n",
      "\u001b[K     |████████████████████████████████| 482 kB 36.6 MB/s \n",
      "\u001b[?25hRequirement already satisfied: googledrivedownloader in /usr/local/lib/python3.7/dist-packages (from torch-geometric) (0.4)\n",
      "Requirement already satisfied: jinja2 in /usr/local/lib/python3.7/dist-packages (from torch-geometric) (2.11.3)\n",
      "Requirement already satisfied: pyparsing in /usr/local/lib/python3.7/dist-packages (from torch-geometric) (3.0.6)\n",
      "Collecting yacs\n",
      "  Downloading yacs-0.1.8-py3-none-any.whl (14 kB)\n",
      "Requirement already satisfied: PyYAML in /usr/local/lib/python3.7/dist-packages (from torch-geometric) (3.13)\n",
      "Requirement already satisfied: MarkupSafe>=0.23 in /usr/local/lib/python3.7/dist-packages (from jinja2->torch-geometric) (2.0.1)\n",
      "Requirement already satisfied: python-dateutil>=2.7.3 in /usr/local/lib/python3.7/dist-packages (from pandas->torch-geometric) (2.8.2)\n",
      "Requirement already satisfied: pytz>=2017.2 in /usr/local/lib/python3.7/dist-packages (from pandas->torch-geometric) (2018.9)\n",
      "Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.7/dist-packages (from python-dateutil>=2.7.3->pandas->torch-geometric) (1.15.0)\n",
      "Collecting isodate\n",
      "  Downloading isodate-0.6.1-py2.py3-none-any.whl (41 kB)\n",
      "\u001b[K     |████████████████████████████████| 41 kB 525 kB/s \n",
      "\u001b[?25hRequirement already satisfied: importlib-metadata in /usr/local/lib/python3.7/dist-packages (from rdflib->torch-geometric) (4.8.2)\n",
      "Requirement already satisfied: setuptools in /usr/local/lib/python3.7/dist-packages (from rdflib->torch-geometric) (57.4.0)\n",
      "Requirement already satisfied: zipp>=0.5 in /usr/local/lib/python3.7/dist-packages (from importlib-metadata->rdflib->torch-geometric) (3.6.0)\n",
      "Requirement already satisfied: typing-extensions>=3.6.4 in /usr/local/lib/python3.7/dist-packages (from importlib-metadata->rdflib->torch-geometric) (3.10.0.2)\n",
      "Requirement already satisfied: chardet<4,>=3.0.2 in /usr/local/lib/python3.7/dist-packages (from requests->torch-geometric) (3.0.4)\n",
      "Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.7/dist-packages (from requests->torch-geometric) (2021.10.8)\n",
      "Requirement already satisfied: urllib3!=1.25.0,!=1.25.1,<1.26,>=1.21.1 in /usr/local/lib/python3.7/dist-packages (from requests->torch-geometric) (1.24.3)\n",
      "Requirement already satisfied: idna<3,>=2.5 in /usr/local/lib/python3.7/dist-packages (from requests->torch-geometric) (2.10)\n",
      "Requirement already satisfied: joblib>=0.11 in /usr/local/lib/python3.7/dist-packages (from scikit-learn->torch-geometric) (1.1.0)\n",
      "Requirement already satisfied: threadpoolctl>=2.0.0 in /usr/local/lib/python3.7/dist-packages (from scikit-learn->torch-geometric) (3.0.0)\n",
      "Building wheels for collected packages: torch-geometric\n",
      "  Building wheel for torch-geometric (setup.py) ... \u001b[?25l\u001b[?25hdone\n",
      "  Created wheel for torch-geometric: filename=torch_geometric-2.0.2-py3-none-any.whl size=535570 sha256=f2b6129519927decbbb2a89678f562023aa5da4190f94359813eb9cb9d2aa95c\n",
      "  Stored in directory: /root/.cache/pip/wheels/3f/08/13/2321517088bb2e95bfd0e45033bb9c923189e5b2078e0be4ef\n",
      "Successfully built torch-geometric\n",
      "Installing collected packages: isodate, yacs, rdflib, torch-geometric\n",
      "Successfully installed isodate-0.6.1 rdflib-6.1.1 torch-geometric-2.0.2 yacs-0.1.8\n"
     ]
    }
   ],
   "source": [
    "# torch geometric\n",
    "try: \n",
    "    import torch_geometric\n",
    "except ModuleNotFoundError:\n",
    "    # Installing torch geometric packages with specific CUDA+PyTorch version. \n",
    "    # See https://pytorch-geometric.readthedocs.io/en/latest/notes/installation.html for details \n",
    "    import torch\n",
    "    TORCH = torch.__version__.split('+')[0]\n",
    "    CUDA = 'cu' + torch.version.cuda.replace('.','')\n",
    "\n",
    "    !pip install torch-scatter     -f https://pytorch-geometric.com/whl/torch-{TORCH}+{CUDA}.html\n",
    "    !pip install torch-sparse      -f https://pytorch-geometric.com/whl/torch-{TORCH}+{CUDA}.html\n",
    "    !pip install torch-cluster     -f https://pytorch-geometric.com/whl/torch-{TORCH}+{CUDA}.html\n",
    "    !pip install torch-spline-conv -f https://pytorch-geometric.com/whl/torch-{TORCH}+{CUDA}.html\n",
    "    !pip install torch-geometric \n",
    "    import torch_geometric\n",
    "import torch_geometric.nn as geom_nn\n",
    "import torch_geometric.data as geom_data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "  Building wheel for recohut (setup.py) ... \u001b[?25l\u001b[?25hdone\n"
     ]
    }
   ],
   "source": [
    "!pip install -q -U git+https://github.com/RecoHut-Projects/recohut.git -b v0.0.4"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### **1.2 Download datasets**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "!git clone -q --branch v2 https://github.com/RecoHut-Datasets/movielens_1m.git"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### **1.3 Import libraries**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import torch\n",
    "from torch import optim\n",
    "from torch.utils.data import DataLoader\n",
    "\n",
    "import pandas as pd\n",
    "import numpy as np\n",
    "import time\n",
    "from tqdm.notebook import tqdm\n",
    "\n",
    "import os\n",
    "import pickle\n",
    "\n",
    "import warnings\n",
    "warnings.filterwarnings('ignore')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# layers\n",
    "from recohut.models.layers.message_passing import LightGConv, LRGCCF\n",
    "\n",
    "# models\n",
    "from recohut.models.siren import SiReN\n",
    "\n",
    "# transforms\n",
    "from recohut.transforms.bipartite import BipartiteDataset"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### **1.4 Set params**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Args:\n",
    "    dataset = 'ML-1M' # Dataset\n",
    "    version = 1 # Dataset version\n",
    "    batch_size = 1024 # Batch size\n",
    "    dim = 64 # Dimension\n",
    "    lr = 5e-3 # Learning rate\n",
    "    offset = 3.5 # Criterion of likes/dislikes\n",
    "    K = 40 # The number of negative samples\n",
    "    num_layers = 4 # The number of layers of a GNN model for the graph with positive edges\n",
    "    MLP_layers = 2 # The number of layers of MLP for the graph with negative edges\n",
    "    epoch = 4 # The number of epochs\n",
    "    reg = 0.05 # Regularization coefficient"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## **Step 2 - Data preparation**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Data_loader():\n",
    "    def __init__(self,dataset,version):\n",
    "        self.dataset=dataset; self.version=version\n",
    "        self.sep='::'\n",
    "        self.names=['userId','movieId','rating','timestemp'];\n",
    "        self.path_for_whole='./movielens_1m/ratings.dat'\n",
    "        self.path_for_train='./movielens_1m/train_1m%s.dat'%(version)\n",
    "        self.path_for_test='./movielens_1m/test_1m%s.dat'%(version)\n",
    "        self.num_u=6040; self.num_v=3952;\n",
    "        \n",
    "    def data_load(self):\n",
    "        self.whole_=pd.read_csv(self.path_for_whole, names = self.names, sep=self.sep, engine='python').drop('timestemp',axis=1).sample(frac=1,replace=False,random_state=self.version)\n",
    "        self.train_set = pd.read_csv(self.path_for_train,engine='python',names=self.names).drop('timestemp',axis=1)\n",
    "        self.test_set = pd.read_csv(self.path_for_test,engine='python',names=self.names).drop('timestemp',axis=1)            \n",
    "        return self.train_set, self.test_set"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def deg_dist(train, num_v):\n",
    "    uni, cou = np.unique(train['movieId'].values-1,return_counts=True)\n",
    "    cou = cou**(0.75)\n",
    "    deg = np.zeros(num_v)\n",
    "    deg[uni] = cou\n",
    "    return torch.tensor(deg)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def gen_top_K(data_class,emb,train,directory_):\n",
    "    no_items = np.array(list(set(np.arange(1,data_class.num_v+1))-set(train['movieId'])))\n",
    "    total_users = set(np.arange(1,data_class.num_u+1))\n",
    "    reco = dict()\n",
    "    pbar = tqdm(desc = 'top-k recommendation...',total=len(total_users),position=0)\n",
    "    for j in total_users:\n",
    "        pos = train[train['userId']==j]['movieId'].values-1\n",
    "        embedding_ = emb[j-1].view(1,len(emb[0])).mm(emb[data_class.num_u:].t()).detach();\n",
    "        embedding_[0][no_items-1]=-np.inf;\n",
    "        embedding_[0][pos]=-np.inf;\n",
    "        reco[j]=torch.topk(embedding_[0],300).indices.cpu().numpy()+1\n",
    "        pbar.update(1)\n",
    "    pbar.close()\n",
    "    return reco"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "data loading...\n",
      "loading complete! time :: 9.222385168075562\n",
      "generate negative candidates...\n",
      "complete ! time : 0.08198142051696777\n"
     ]
    }
   ],
   "source": [
    "args = Args()\n",
    "data_class=Data_loader(args.dataset,args.version)\n",
    "threshold = round(args.offset) # To generate ground truth set \n",
    "\n",
    "print('data loading...'); st=time.time()\n",
    "train,test = data_class.data_load();\n",
    "train = train.astype({'userId':'int64', 'movieId':'int64'})\n",
    "print('loading complete! time :: %s'%(time.time()-st))\n",
    "\n",
    "print('generate negative candidates...'); st=time.time()\n",
    "neg_dist = deg_dist(train,data_class.num_v)\n",
    "print('complete ! time : %s'%(time.time()-st))    "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## **Step 3 - Training & Evaluation**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "class evaluate():\n",
    "    def __init__(self,reco,train,test,threshold,num_u,num_v,N=[5,10,15,20,25],ratings=[20,50]):\n",
    "        '''\n",
    "        train : training set\n",
    "        test : test set\n",
    "        threshold : To generate ground truth set from test set\n",
    "        '''\n",
    "        self.reco = reco\n",
    "        self.num_u = num_u;\n",
    "        self.num_v = num_v;\n",
    "        self.N=N\n",
    "        self.p=[]\n",
    "        self.r=[]\n",
    "        self.NDCG=[]\n",
    "        self.p_c1=[]; self.p_c2=[]; self.p_c3=[]\n",
    "        self.r_c1=[]; self.r_c2=[]; self.r_c3=[]\n",
    "        self.NDCG_c1=[]; self.NDCG_c2=[]; self.NDCG_c3=[]\n",
    "        self.tr = train; self.te = test;\n",
    "        self.threshold = threshold;\n",
    "        self.gen_ground_truth_set()\n",
    "        self.ratings = ratings\n",
    "        self.partition_into_groups_(self.ratings)\n",
    "        print('\\nevaluating recommendation accuracy....')\n",
    "        self.precision_and_recall_G(self.group1,1)\n",
    "        self.precision_and_recall_G(self.group2,2)\n",
    "        self.precision_and_recall_G(self.group3,3)\n",
    "        self.Normalized_DCG_G(self.group1,1)\n",
    "        self.Normalized_DCG_G(self.group2,2)\n",
    "        self.Normalized_DCG_G(self.group3,3)\n",
    "        self.metric_total()\n",
    "\n",
    "    def gen_ground_truth_set(self):\n",
    "        result = dict()\n",
    "        GT = self.te[self.te['rating']>=self.threshold];\n",
    "        U = set(GT['userId'])\n",
    "        for i in U:\n",
    "            result[i] = list(set([j for j in GT[GT['userId']==i]['movieId']]))#-set(self.TOP))\n",
    "            if len(result[i])==0:\n",
    "                del(result[i])\n",
    "        self.GT = result\n",
    "\n",
    "    def precision_and_recall(self):\n",
    "        user_in_GT=[j for j in self.GT];\n",
    "        for n in self.N:\n",
    "            p=0; r=0;\n",
    "            for i in user_in_GT:\n",
    "                topn=self.reco[i][:n]\n",
    "                num_hit=len(set(topn).intersection(set(self.GT[i])));\n",
    "                p+=num_hit/n; r+=num_hit/len(self.GT[i]);\n",
    "            self.p.append(p/len(user_in_GT)); self.r.append(r/len(user_in_GT));\n",
    "                \n",
    "    def Normalized_DCG(self):\n",
    "        maxn=max(self.N);\n",
    "        user_in_GT=[j for j in self.GT];\n",
    "        ndcg=np.zeros(maxn);\n",
    "        for i in user_in_GT:\n",
    "            idcg_len = min(len(self.GT[i]), maxn)\n",
    "            temp_idcg = np.cumsum(1.0 / np.log2(np.arange(2, maxn + 2)))\n",
    "            temp_idcg[idcg_len:] = temp_idcg[idcg_len-1]\n",
    "            temp_dcg=np.cumsum([1.0/np.log2(idx+2) if item in self.GT[i] else 0.0 for idx, item in enumerate(self.reco[i][:maxn])])\n",
    "            ndcg+=temp_dcg/temp_idcg;\n",
    "        ndcg/=len(user_in_GT);\n",
    "        for n in self.N:\n",
    "            self.NDCG.append(ndcg[n-1])\n",
    "            \n",
    "    def metric_total(self):\n",
    "        self.p = self.len1 * np.array(self.p_c1) + self.len2 * np.array(self.p_c2) + self.len3 * np.array(self.p_c3);\n",
    "        self.p/= self.len1 + self.len2 + self.len3\n",
    "        self.p = list(self.p)\n",
    "        self.r = self.len1 * np.array(self.r_c1) + self.len2 * np.array(self.r_c2) + self.len3 * np.array(self.r_c3);\n",
    "        self.r/= self.len1 + self.len2 + self.len3\n",
    "        self.r = list(self.r)\n",
    "        self.NDCG = self.len1 * np.array(self.NDCG_c1) + self.len2 * np.array(self.NDCG_c2) + self.len3 * np.array(self.NDCG_c3);\n",
    "        self.NDCG/= self.len1 + self.len2 + self.len3\n",
    "        self.NDCG = list(self.NDCG)\n",
    "\n",
    "    def partition_into_groups_(self,ratings=[20,50]):\n",
    "        unique_u, counts_u = np.unique(self.tr['userId'].values,return_counts=True)\n",
    "        self.group1 = unique_u[np.argwhere(counts_u<ratings[0])]\n",
    "        temp = unique_u[np.argwhere(counts_u<ratings[1])]\n",
    "        self.group2 = np.setdiff1d(temp,self.group1)\n",
    "        self.group3 = np.setdiff1d(unique_u,temp)\n",
    "        self.cold_groups = ratings\n",
    "        self.group1 = list(self.group1.reshape(-1))\n",
    "        self.group2 = list(self.group2.reshape(-1))\n",
    "        self.group3 = list(self.group3.reshape(-1))\n",
    "    \n",
    "    def precision_and_recall_G(self,group,gn):\n",
    "        user_in_GT=[j for j in self.GT];\n",
    "        leng = 0 ; maxn = max(self.N) ; p = np.zeros(maxn); r = np.zeros(maxn);\n",
    "        for i in user_in_GT:\n",
    "            if i in group:\n",
    "                leng+=1\n",
    "                hit_ = np.cumsum([1.0 if item in self.GT[i] else 0.0 for idx, item in enumerate(self.reco[i][:maxn])])\n",
    "                p+=hit_ / np.arange(1,maxn+1); r+=hit_/len(self.GT[i])\n",
    "        p/= leng; r/=leng;\n",
    "        for n in self.N:\n",
    "            if gn == 1 :\n",
    "                self.p_c1.append(p[n-1])\n",
    "                self.r_c1.append(r[n-1])\n",
    "                self.len1 = leng;\n",
    "            elif gn == 2 :\n",
    "                self.p_c2.append(p[n-1])\n",
    "                self.r_c2.append(r[n-1])\n",
    "                self.len2 = leng;\n",
    "            elif gn == 3 :\n",
    "                self.p_c3.append(p[n-1])\n",
    "                self.r_c3.append(r[n-1])\n",
    "                self.len3 = leng;\n",
    "            \n",
    "    def Normalized_DCG_G(self,group,gn):\n",
    "        maxn=max(self.N);\n",
    "        user_in_GT=[j for j in self.GT];\n",
    "        ndcg=np.zeros(maxn);\n",
    "        leng = 0\n",
    "        for i in user_in_GT:\n",
    "            if i in group:\n",
    "                leng+=1\n",
    "                idcg_len = min(len(self.GT[i]), maxn)\n",
    "                temp_idcg = np.cumsum(1.0 / np.log2(np.arange(2, maxn + 2)))\n",
    "                temp_idcg[idcg_len:] = temp_idcg[idcg_len-1]\n",
    "                temp_dcg=np.cumsum([1.0/np.log2(idx+2) if item in self.GT[i] else 0.0 for idx, item in enumerate(self.reco[i][:maxn])])\n",
    "                ndcg+=temp_dcg/temp_idcg;\n",
    "        ndcg/=leng\n",
    "        for n in self.N:\n",
    "            if gn == 1 :\n",
    "                self.NDCG_c1.append(ndcg[n-1])\n",
    "            elif gn == 2 :\n",
    "                self.NDCG_c2.append(ndcg[n-1])\n",
    "            elif gn == 3 :\n",
    "                self.NDCG_c3.append(ndcg[n-1])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "device = torch.device(\"cuda:0\" if torch.cuda.is_available() else \"cpu\")\n",
    "\n",
    "args.user_col = 'userId'\n",
    "args.item_col = 'movieId'\n",
    "args.feedback_col = 'rating'\n",
    "\n",
    "model= SiReN(train,\n",
    "             data_class.num_u,\n",
    "             data_class.num_v,\n",
    "             offset=args.offset,\n",
    "             num_layers=args.num_layers,\n",
    "             MLP_layers=args.MLP_layers,\n",
    "             dim=args.dim,\n",
    "             device=device,\n",
    "             reg=args.reg,\n",
    "            graph_enc = 'lightgcn',\n",
    "            user_col = args.user_col,\n",
    "            item_col = args.item_col,\n",
    "            rating_col = args.feedback_col)\n",
    "\n",
    "model.data_p.to(device)\n",
    "model.to(device)\n",
    "optimizer = optim.Adam(model.parameters(), lr = args.lr)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "Training on cuda:0...\n",
      "\n",
      "negative sampling for next epochs...\n"
     ]
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "db4ec0a687d04355a3ddc2c266b4fb02",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "negative sampling for next epochs...:   0%|          | 0/6040 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "complete ! 44.3044056892395\n"
     ]
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "da00ae1a6d844879920d15450bd70087",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Version : 1 Epoch 1/4:   0%|          | 0/782 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "c4cc38955e754b56935444f7ea1683df",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Version : 1 Epoch 2/4:   0%|          | 0/782 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "83eaf9d1b71d4dc08c4df7e2dbe623b5",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "top-k recommendation...:   0%|          | 0/6040 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "evaluating recommendation accuracy....\n",
      "\n",
      "***************************************************************************************\n",
      " /* Recommendation Accuracy */\n",
      "Precision at [10, 15, 20] ::  [0.1977101788400488, 0.17380355451557442, 0.15696139060671854]\n",
      "Recall at [10, 15, 20] ::  [0.11815263807718367, 0.15323209109049143, 0.1826288867835281]\n",
      "NDCG at [10, 15, 20] ::  [0.23763163291675646, 0.23046875513769544, 0.22915729283611078]\n",
      "***************************************************************************************\n",
      "negative sampling for next epochs...\n"
     ]
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "72f93c8f098e436d991b864139676f10",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "negative sampling for next epochs...:   0%|          | 0/6040 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "complete ! 48.64898443222046\n"
     ]
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "f4d5f46afaec44f396088c241b8014de",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Version : 1 Epoch 3/4:   0%|          | 0/782 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "0f6d8285f502433eb842ea01c78f7de8",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Version : 1 Epoch 4/4:   0%|          | 0/782 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "3377ec386afb4a4a82e8fe17730bafc6",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "top-k recommendation...:   0%|          | 0/6040 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "evaluating recommendation accuracy....\n",
      "\n",
      "***************************************************************************************\n",
      " /* Recommendation Accuracy */\n",
      "Precision at [10, 15, 20] ::  [0.22543874310546613, 0.19848459524207768, 0.17864783553401203]\n",
      "Recall at [10, 15, 20] ::  [0.13995855076046487, 0.1812022261733025, 0.21449465241229854]\n",
      "NDCG at [10, 15, 20] ::  [0.269092620151712, 0.2622437530495195, 0.261196524578161]\n",
      "***************************************************************************************\n"
     ]
    }
   ],
   "source": [
    "print(\"\\nTraining on {}...\\n\".format(device))\n",
    "model.train()\n",
    "training_dataset = BipartiteDataset(args, train, neg_dist, args.offset, \n",
    "                                    data_class.num_u, data_class.num_v, args.K)\n",
    "\n",
    "for EPOCH in range(1,args.epoch+1):\n",
    "    if EPOCH%2-1==0:training_dataset.negs_gen_EP(2)\n",
    "    LOSS=0\n",
    "    training_dataset.edge_4 = training_dataset.edge_4_tot[:,:,EPOCH%2-1]\n",
    "    ds = DataLoader(training_dataset,batch_size=args.batch_size,shuffle=True)\n",
    "    q=0\n",
    "    pbar = tqdm(desc = 'Version : {} Epoch {}/{}'.format(args.version,EPOCH,args.epoch),total=len(ds),position=0)\n",
    "    for u,v,w,negs in ds:\n",
    "        q+=len(u)\n",
    "        st=time.time()\n",
    "        optimizer.zero_grad()\n",
    "        loss = model(u,v,w,negs,device) # original\n",
    "        loss.backward()                \n",
    "        optimizer.step()\n",
    "        LOSS+=loss.item() * len(ds)\n",
    "        pbar.update(1);\n",
    "        pbar.set_postfix({'loss':loss.item()})\n",
    "    pbar.close()\n",
    "\n",
    "    if EPOCH%2==0 :\n",
    "        directory = os.getcwd() + '/results/%s/SiReN/epoch%s_batch%s_dim%s_lr%s_offset%s_K%s_num_layers%s_MLP_layers%s_threshold%s_reg%s/'%(args.dataset,EPOCH,args.batch_size,args.dim,args.lr,args.offset,args.K,args.num_layers,args.MLP_layers,threshold,args.reg)\n",
    "        if not os.path.exists(directory):\n",
    "            os.makedirs(directory)\n",
    "        model.eval()\n",
    "        emb = model.aggregate();\n",
    "        top_k_list = gen_top_K(data_class,emb,train,directory+'r%s_reco.pickle'%(args.version)) \n",
    "        eval_ = evaluate(top_k_list,train,test,threshold,data_class.num_u,data_class.num_v,N=[10,15,20],ratings=[20,50])\n",
    "        print(\"\\n***************************************************************************************\")\n",
    "        print(\" /* Recommendation Accuracy */\")\n",
    "        print('Precision at [10, 15, 20] :: ',eval_.p)\n",
    "        print('Recall at [10, 15, 20] :: ',eval_.r)\n",
    "        print('NDCG at [10, 15, 20] :: ',eval_.NDCG)\n",
    "        print(\"***************************************************************************************\")\n",
    "        directory_ = directory+'r%s_reco.pickle'%(args.version)\n",
    "        with open(directory_,'wb') as fw:\n",
    "            pickle.dump(eval_,fw)\n",
    "        model.train()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## **Closure**"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "For more details, you can refer to https://github.com/RecoHut-Stanzas/S138006."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<a href=\"https://github.com/RecoHut-Stanzas/S138006/blob/main/reports/S138006_Report.ipynb\" alt=\"S138006_Report\"> <img src=\"https://img.shields.io/static/v1?label=report&message=active&color=green\" /></a> <a href=\"https://github.com/RecoHut-Stanzas/S138006\" alt=\"S138006\"> <img src=\"https://img.shields.io/static/v1?label=code&message=github&color=blue\" /></a>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Author: Sparsh A.\n",
      "\n",
      "Last updated: 2021-12-20 06:30:14\n",
      "\n",
      "recohut: 0.0.4\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_geometric: 2.0.2\n",
      "IPython        : 5.5.0\n",
      "pandas         : 1.1.5\n",
      "torch          : 1.10.0+cu111\n",
      "numpy          : 1.19.5\n",
      "\n"
     ]
    }
   ],
   "source": [
    "!pip install -q watermark\n",
    "%reload_ext watermark\n",
    "%watermark -a \"Sparsh A.\" -m -iv -u -t -d -p recohut"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**END**"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "name": "python3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 0
}
