{
 "cells": [
  {
   "cell_type": "raw",
   "metadata": {},
   "source": [
    "---\n",
    "execute:\n",
    "  enabled: false\n",
    "---"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<!-- d2l:prose id=ranking-md-1 fw=all -->\n",
    "\n",
    "# Personalized Ranking for Recommender Systems\n",
    "\n",
    "In the former sections, only explicit feedback was considered and models were trained and tested on observed ratings.  There are two demerits of such methods: First, most feedback is not explicit but implicit in real-world scenarios, and explicit feedback can be more expensive to collect.  Second, non-observed user-item pairs which may be predictive for users' interests are totally ignored, making these methods unsuitable for cases where ratings are not missing at random but because of users' preferences.  Non-observed user-item pairs are a  mixture of real negative feedback (users are not interested in the items) and missing values (the user might interact with the items in the future). We simply ignore the non-observed pairs in matrix factorization and AutoRec. Clearly, these models are incapable of distinguishing between observed and non-observed pairs and are usually not suitable for personalized ranking tasks.\n",
    "\n",
    "To this end, a class of recommendation models targeting at generating ranked recommendation lists from implicit feedback have gained popularity. In general, personalized ranking models can be optimized with pointwise, pairwise or listwise approaches. Pointwise approaches consider a single interaction at a time and train a classifier or a regressor to predict individual preferences. Matrix factorization and AutoRec are optimized with pointwise objectives. Pairwise approaches consider a pair of items for each user and aim to approximate the optimal ordering for that pair. Usually, pairwise approaches are more suitable for the ranking task because predicting relative order is reminiscent to the nature of ranking. Listwise approaches approximate the ordering of the entire list of items, for example, direct optimizing the ranking measures such as Normalized Discounted Cumulative Gain ([NDCG](https://en.wikipedia.org/wiki/Discounted_cumulative_gain)). However, listwise approaches are more complex and compute-intensive than pointwise or pairwise approaches. In this section, we will introduce two pairwise objectives/losses, Bayesian Personalized Ranking loss and Hinge loss, and their respective implementations.\n",
    "\n",
    "## Bayesian Personalized Ranking Loss and its Implementation\n",
    "\n",
    "Bayesian personalized ranking (BPR) [@Rendle.Freudenthaler.Gantner.ea.2009] is a pairwise personalized ranking loss that is derived from the maximum posterior estimator. It has been widely used in many existing recommendation models. The training data of BPR consists of both positive and negative pairs (missing values). It assumes that the user prefers the positive item over all other non-observed items.\n",
    "\n",
    "In formal, the training data is constructed by tuples in the form of $(u, i, j)$, which represents that the user $u$ prefers the item $i$ over the item $j$. The Bayesian formulation of BPR which aims to maximize the posterior probability is given below:\n",
    "\n",
    "$$\n",
    "p(\\Theta \\mid >_u )  \\propto  p(>_u \\mid \\Theta) p(\\Theta)\n",
    "$$\n",
    "\n",
    "Where $\\Theta$ represents the parameters of an arbitrary recommendation model, $>_u$ represents the desired personalized total ranking of all items for user $u$. We can formulate the maximum posterior estimator to derive the generic optimization criterion for the personalized ranking task.\n",
    "\n",
    "$$\n",
    "\\begin{aligned}\n",
    "\\textrm{BPR-OPT} : &= \\ln p(\\Theta \\mid >_u) \\\\\n",
    "         & \\propto \\ln p(>_u \\mid \\Theta) p(\\Theta) \\\\\n",
    "         &= \\ln \\prod_{(u, i, j) \\in D} \\sigma(\\hat{y}_{ui} - \\hat{y}_{uj}) p(\\Theta) \\\\\n",
    "         &= \\sum_{(u, i, j) \\in D} \\ln \\sigma(\\hat{y}_{ui} - \\hat{y}_{uj}) + \\ln p(\\Theta) \\\\\n",
    "         &= \\sum_{(u, i, j) \\in D} \\ln \\sigma(\\hat{y}_{ui} - \\hat{y}_{uj}) - \\lambda_\\Theta \\|\\Theta \\|^2\n",
    "\\end{aligned}\n",
    "$$\n",
    "\n",
    "\n",
    "where $D \\stackrel{\\textrm{def}}{=} \\{(u, i, j) \\mid i \\in I^+_u \\wedge j \\in I \\backslash I^+_u \\}$ is the training set, with $I^+_u$ denoting the items the user $u$ liked, $I$ denoting all items, and $I \\backslash I^+_u$ indicating all other items excluding items the user liked. $\\hat{y}_{ui}$ and $\\hat{y}_{uj}$ are the predicted scores of the user $u$ to item $i$ and $j$, respectively. The prior $p(\\Theta)$ is a normal distribution with zero mean and variance-covariance matrix $\\Sigma_\\Theta$. Here, we let $\\Sigma_\\Theta = \\lambda_\\Theta I$.\n",
    "\n",
    "![Illustration of Bayesian Personalized Ranking](../img/rec-ranking.svg)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<!-- d2l:prose id=ranking-md-2 fw=pytorch -->\n",
    "\n",
    "We will subclass `nn.Module` and implement the BPR loss in its `forward` method."
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "id": "ranking-bayesian-personalized-ranking-loss-and-its-implementation-1"
   },
   "source": [
    "import torch\n",
    "from torch import nn"
   ],
   "execution_count": 1,
   "outputs": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<!-- d2l:prose id=ranking-md-3 fw=all -->\n",
    "\n",
    "The implementation of BPR loss is as follows."
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "id": "ranking-bayesian-personalized-ranking-loss-and-its-implementation-2"
   },
   "source": [
    "\n",
    "class BPRLoss(nn.Module):\n",
    "    def __init__(self):\n",
    "        super(BPRLoss, self).__init__()\n",
    "\n",
    "    def forward(self, positive, negative):\n",
    "        distances = positive - negative\n",
    "        loss = -torch.sum(torch.log(torch.sigmoid(distances)), dim=0,\n",
    "                          keepdim=True)\n",
    "        return loss"
   ],
   "execution_count": 2,
   "outputs": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<!-- d2l:prose id=ranking-md-4 fw=all -->\n",
    "\n",
    "## Hinge Loss and its Implementation\n",
    "\n",
    "The Hinge loss for ranking has a different form from the standard hinge loss that is often used in classifiers such as SVMs.  The loss used for ranking in recommender systems has the following form.\n",
    "\n",
    "$$\n",
    " \\sum_{(u, i, j) \\in D} \\max( m - \\hat{y}_{ui} + \\hat{y}_{uj}, 0)\n",
    "$$\n",
    "\n",
    "where $m$ is the safety margin size. It aims to push negative items away from positive items. Similar to BPR, it aims to optimize for relevant distance between positive and negative samples instead of absolute outputs, making it well suited to recommender systems."
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "id": "ranking-hinge-loss-and-its-implementation"
   },
   "source": [
    "\n",
    "class HingeLossbRec(nn.Module):\n",
    "    def __init__(self):\n",
    "        super(HingeLossbRec, self).__init__()\n",
    "\n",
    "    def forward(self, positive, negative, margin=1):\n",
    "        distances = positive - negative\n",
    "        loss = torch.sum(torch.clamp(-distances + margin, min=0))\n",
    "        return loss"
   ],
   "execution_count": 3,
   "outputs": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<!-- d2l:prose id=ranking-md-5 fw=all -->\n",
    "\n",
    "These two losses are interchangeable for personalized ranking in recommendation.\n",
    "\n",
    "## Summary\n",
    "\n",
    "- There are three types of ranking losses available for the personalized ranking task in recommender systems, namely, pointwise, pairwise and listwise methods.\n",
    "- The two pairwise losses, Bayesian personalized ranking loss and hinge loss, can be used interchangeably.\n",
    "\n",
    "## Exercises\n",
    "\n",
    "- Are there any variants of BPR and hinge loss available?\n",
    "- Can you find any recommendation models that use BPR or hinge loss?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<!-- d2l:prose id=ranking-md-6 fw=pytorch -->\n",
    "\n",
    "[Discussions](https://d2l.discourse.group/t/402)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "name": "d2l-pytorch",
   "display_name": "d2l (PyTorch)",
   "language": "python"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
