{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Copyright (c) Recommenders contributors. \n",
"\n",
"Licensed under the MIT License. "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": "# SLi_Rec: Adaptive User Modeling with Long and Short-Term Preferences for Personalized Recommendation\n\nThis notebook gives a quick example of how to train and evaluate the [SLi_Rec model](https://www.microsoft.com/en-us/research/uploads/prod/2019/07/IJCAI19-ready_v1.pdf) \\[1\\].\nSLi_Rec \\[1\\] is a deep learning-based sequential recommendation model that captures both long and short-term user preferences: it takes the sequence of the user behaviors as context and predicts the items that the user will interact in a short time (in an extreme case, the item that the user will interact next). To summarize, SLi_Rec has the following key properties:\n\n* It adopts the attentive \"Asymmetric-SVD\" paradigm for long-term modeling;\n* It takes both time irregularity and semantic irregularity into consideration by modifying the gating logic in LSTM.\n* It uses an attention mechanism to dynamic fuse the long-term component and short-term component.\n\nIn this notebook, we test SLi_Rec on a subset of the public dataset: [Amazon_reviews](http://snap.stanford.edu/data/amazon/productGraph/categoryFiles/reviews_Movies_and_TV_5.json.gz) and [Amazon_metadata](http://snap.stanford.edu/data/amazon/productGraph/categoryFiles/meta_Movies_and_TV.json.gz)"
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 0. Global Settings and Imports"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"execution": {
"iopub.execute_input": "2026-07-21T14:42:59.217961Z",
"iopub.status.busy": "2026-07-21T14:42:59.217407Z",
"iopub.status.idle": "2026-07-21T14:43:06.729279Z",
"shell.execute_reply": "2026-07-21T14:43:06.724860Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"System version: 3.11.14 (main, Jan 14 2026, 19:35:32) [Clang 21.1.4 ]\n",
"PyTorch version: 2.13.0.dev20260521+cu132\n"
]
}
],
"source": [
"import os\n",
"import sys\n",
"import torch\n",
"\n",
"from recommenders.utils.timer import Timer\n",
"from recommenders.utils.constants import SEED\n",
"from recommenders.datasets.amazon_reviews import download_and_extract, data_preprocessing\n",
"from recommenders.models.deeprec.models.sequential.pytorch.sli_rec import SLiRecModel as SeqModel\n",
"from recommenders.utils.notebook_utils import store_metadata\n",
"\n",
"print(f\"System version: {sys.version}\")\n",
"print(f\"PyTorch version: {torch.__version__}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Parameters"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {
"execution": {
"iopub.execute_input": "2026-07-21T14:43:06.787079Z",
"iopub.status.busy": "2026-07-21T14:43:06.786331Z",
"iopub.status.idle": "2026-07-21T14:43:06.795261Z",
"shell.execute_reply": "2026-07-21T14:43:06.791967Z"
},
"tags": [
"parameters"
]
},
"outputs": [],
"source": [
"EPOCHS = 10\n",
"BATCH_SIZE = 400\n",
"RANDOM_SEED = SEED # Set None for non-deterministic result\n",
"\n",
"data_path = os.path.join(\"..\", \"..\", \"tests\", \"resources\", \"deeprec\", \"slirec\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 1. Input data format\n",
"The input data contains 8 columns, i.e., ` ` columns are seperated by `\"\\t\"`. item_id and category_id denote the target item and category, which means that for this instance, we want to guess whether user user_id will interact with item_id at timestamp. `` columns record the user behavior list up to ``, elements are separated by commas. `` is a binary value with 1 for positive instances and 0 for negative instances. One example for an instance is: \n",
"\n",
"`1 A1QQ86H5M2LVW2 B0059XTU1S Movies 1377561600 B002ZG97WE,B004IK30PA,B000BNX3AU,B0017ANB08,B005LAIHW2 Movies,Movies,Movies,Movies,Movies 1304294400,1304812800,1315785600,1316304000,1356998400` \n",
"\n",
"In data preprocessing stage, we have a script to generate some ID mapping dictionaries, so user_id, item_id and category_id will be mapped into interager index starting from 1. And you need to tell the input iterator where is the ID mapping files are. (For example, in the next section, we have some mapping files like user_vocab, item_vocab, and cate_vocab). The data preprocessing script is at [recommenders/dataset/amazon_reviews.py](../../recommenders/dataset/amazon_reviews.py), you need to call the `_create_vocab(train_file, user_vocab, item_vocab, cate_vocab)` function. Note that ID vocabulary only creates from the train_file, so the new IDs in valid_file or test_file will be regarded as unknown IDs and assigned with a defualt 0 index.\n",
"\n",
"SLi_Rec is time-aware, so it makes use of the `` and `` columns to model the time irregularity of the user behaviors.\n",
"\n",
"We use Softmax to the loss function. In training and evalution stage, we group 1 positive instance with `num_ngs` negative instances. Pair-wise ranking can be regarded as a special case of softmax ranking, where `num_ngs` is set to 1. \n",
"\n",
"More specifically, for training and evalation, you need to organize the data file such that each one positive instance is followed by `num_ngs` negative instances. Our program will take `1+num_ngs` lines as a unit for Softmax calculation. `num_ngs` is a parameter you pass to `fit` and `run_eval`. `train_num_ngs` in `fit` denotes the number of negative instances for training, where a recommended number is 4. `valid_num_ngs` and `num_ngs` in `fit` and `run_eval` denote the number in evaluation. In evaluation, the model calculates metrics among the `1+num_ngs` instances. For the `predict` function, since we only need to calcuate a score for each individual instance, there is no need for `num_ngs` setting. More details and examples will be provided in the following sections.\n",
"\n",
"For training, you can provide positive instances only and pass `train_num_ngs` to `fit`; the model dynamically samples `train_num_ngs` negatives per positive in each mini-batch.\n",
"\n",
"### Amazon dataset\n",
"Now let's start with a public dataset containing product reviews and metadata from Amazon, which is widely used as a benchmark dataset in recommemdation systems field."
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"execution": {
"iopub.execute_input": "2026-07-21T14:43:06.800873Z",
"iopub.status.busy": "2026-07-21T14:43:06.800531Z",
"iopub.status.idle": "2026-07-21T14:43:06.812787Z",
"shell.execute_reply": "2026-07-21T14:43:06.808913Z"
}
},
"outputs": [],
"source": [
"\n",
"# for test\n",
"train_file = os.path.join(data_path, r'train_data')\n",
"valid_file = os.path.join(data_path, r'valid_data')\n",
"test_file = os.path.join(data_path, r'test_data')\n",
"user_vocab = os.path.join(data_path, r'user_vocab.pkl')\n",
"item_vocab = os.path.join(data_path, r'item_vocab.pkl')\n",
"cate_vocab = os.path.join(data_path, r'category_vocab.pkl')\n",
"output_file = os.path.join(data_path, r'output.txt')\n",
"MODEL_DIR = os.path.join(data_path, \"model\")\n",
"\n",
"reviews_name = 'reviews_Movies_and_TV_5.json'\n",
"meta_name = 'meta_Movies_and_TV.json'\n",
"reviews_file = os.path.join(data_path, reviews_name)\n",
"meta_file = os.path.join(data_path, meta_name)\n",
"train_num_ngs = 4 # number of negative instances with a positive instance for training\n",
"valid_num_ngs = 4 # number of negative instances with a positive instance for validation\n",
"test_num_ngs = 9 # number of negative instances with a positive instance for testing\n",
"sample_rate = 0.01 # sample a small item set for training and testing here for fast example\n",
"\n",
"input_files = [reviews_file, meta_file, train_file, valid_file, test_file, user_vocab, item_vocab, cate_vocab]\n",
"\n",
"if not os.path.exists(train_file):\n",
" download_and_extract(reviews_name, reviews_file)\n",
" download_and_extract(meta_name, meta_file)\n",
" data_preprocessing(*input_files, sample_rate=sample_rate, valid_num_ngs=valid_num_ngs, test_num_ngs=test_num_ngs)\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### 1.1 Set model parameters\n",
"All parameters are passed explicitly to the model constructor (architecture) and to `fit` (training). `need_sample` is implied: the training file holds positives only and `train_num_ngs` negatives are sampled in-batch. `train_num_ngs`, `valid_num_ngs` and `num_ngs` (in `fit`/`run_eval`) set the 1-positive-to-N-negatives grouping used by the softmax loss and the ranking metrics."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 2. Create model\n",
"When both hyper-parameters and data iterator are ready, we can create a model:"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {
"execution": {
"iopub.execute_input": "2026-07-21T14:43:06.816836Z",
"iopub.status.busy": "2026-07-21T14:43:06.816499Z",
"iopub.status.idle": "2026-07-21T14:43:12.506439Z",
"shell.execute_reply": "2026-07-21T14:43:12.502583Z"
}
},
"outputs": [],
"source": [
"model = SeqModel(\n",
" user_vocab=user_vocab,\n",
" item_vocab=item_vocab,\n",
" cate_vocab=cate_vocab,\n",
" item_embedding_dim=32,\n",
" cate_embedding_dim=8,\n",
" user_embedding_dim=16,\n",
" hidden_size=40,\n",
" attention_size=40,\n",
" max_seq_length=50,\n",
" layer_sizes=[100, 64],\n",
" att_fcn_layer_sizes=[80, 40],\n",
" dropout=[0.3, 0.3],\n",
" seed=RANDOM_SEED,\n",
")\n",
"\n",
"## to load a pre-trained model instead of training from scratch:\n",
"# model.load_model(os.path.join(MODEL_DIR, \"best_model\"))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now let's see what is the model's performance at this point (without starting training):"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {
"execution": {
"iopub.execute_input": "2026-07-21T14:43:12.510941Z",
"iopub.status.busy": "2026-07-21T14:43:12.510566Z",
"iopub.status.idle": "2026-07-21T14:44:26.415108Z",
"shell.execute_reply": "2026-07-21T14:44:26.411404Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{'auc': 0.4802, 'logloss': 0.6931, 'mean_mrr': 0.2768, 'ndcg@2': 0.1402, 'ndcg@4': 0.2322, 'ndcg@6': 0.3037, 'group_auc': 0.481}\n"
]
}
],
"source": [
"# test_num_ngs is the number of negative lines after each positive line in your test_file\n",
"print(model.run_eval(test_file, num_ngs=test_num_ngs)) "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"AUC=0.5 is a state of random guess. We can see that before training, the model behaves like random guessing.\n",
"\n",
"#### 2.1 Train model\n",
"Next we want to train the model on a training set, and check the performance on a validation dataset. Training the model is as simple as a function call:"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {
"execution": {
"iopub.execute_input": "2026-07-21T14:44:26.419088Z",
"iopub.status.busy": "2026-07-21T14:44:26.418742Z",
"iopub.status.idle": "2026-07-21T14:49:51.485199Z",
"shell.execute_reply": "2026-07-21T14:49:51.480611Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"step 20 , total_loss: 1.6126, data_loss: 1.6126\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"step 40 , total_loss: 1.6064, data_loss: 1.6064\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"eval valid at epoch 1: {'auc': 0.5065, 'logloss': 0.6911, 'mean_mrr': 0.4645, 'ndcg@2': 0.3369, 'ndcg@4': 0.5208, 'ndcg@6': 0.5957, 'group_auc': 0.5093}\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"step 20 , total_loss: 1.5540, data_loss: 1.5540\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"step 40 , total_loss: 1.4192, data_loss: 1.4192\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"eval valid at epoch 2: {'auc': 0.6538, 'logloss': 0.7135, 'mean_mrr': 0.575, 'ndcg@2': 0.4955, 'ndcg@4': 0.6475, 'ndcg@6': 0.6808, 'group_auc': 0.6554}\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"step 20 , total_loss: 1.3389, data_loss: 1.3389\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"step 40 , total_loss: 1.2803, data_loss: 1.2803\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"eval valid at epoch 3: {'auc': 0.6666, 'logloss': 0.7382, 'mean_mrr': 0.5855, 'ndcg@2': 0.5105, 'ndcg@4': 0.6553, 'ndcg@6': 0.6887, 'group_auc': 0.6648}\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"step 20 , total_loss: 1.2426, data_loss: 1.2426\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"step 40 , total_loss: 1.1857, data_loss: 1.1857\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"eval valid at epoch 4: {'auc': 0.7349, 'logloss': 0.642, 'mean_mrr': 0.6656, 'ndcg@2': 0.6112, 'ndcg@4': 0.7254, 'ndcg@6': 0.7491, 'group_auc': 0.736}\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"step 20 , total_loss: 1.1948, data_loss: 1.1948\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"step 40 , total_loss: 1.1307, data_loss: 1.1307\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"eval valid at epoch 5: {'auc': 0.7384, 'logloss': 0.6804, 'mean_mrr': 0.6618, 'ndcg@2': 0.6084, 'ndcg@4': 0.7223, 'ndcg@6': 0.7463, 'group_auc': 0.7333}\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"step 20 , total_loss: 1.1604, data_loss: 1.1604\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"step 40 , total_loss: 1.1245, data_loss: 1.1245\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"eval valid at epoch 6: {'auc': 0.7482, 'logloss': 0.6321, 'mean_mrr': 0.6669, 'ndcg@2': 0.6134, 'ndcg@4': 0.7263, 'ndcg@6': 0.7501, 'group_auc': 0.7372}\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"step 20 , total_loss: 1.1237, data_loss: 1.1237\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"step 40 , total_loss: 1.1063, data_loss: 1.1063\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"eval valid at epoch 7: {'auc': 0.7525, 'logloss': 0.6682, 'mean_mrr': 0.6741, 'ndcg@2': 0.6208, 'ndcg@4': 0.7327, 'ndcg@6': 0.7555, 'group_auc': 0.742}\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"step 20 , total_loss: 1.1639, data_loss: 1.1639\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"step 40 , total_loss: 1.0613, data_loss: 1.0613\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"eval valid at epoch 8: {'auc': 0.7521, 'logloss': 0.594, 'mean_mrr': 0.6721, 'ndcg@2': 0.6206, 'ndcg@4': 0.7304, 'ndcg@6': 0.7541, 'group_auc': 0.7416}\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"step 20 , total_loss: 1.0752, data_loss: 1.0752\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"step 40 , total_loss: 1.1010, data_loss: 1.1010\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"eval valid at epoch 9: {'auc': 0.7581, 'logloss': 0.6643, 'mean_mrr': 0.6821, 'ndcg@2': 0.6318, 'ndcg@4': 0.7394, 'ndcg@6': 0.7615, 'group_auc': 0.749}\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"step 20 , total_loss: 1.0915, data_loss: 1.0915\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"step 40 , total_loss: 1.0858, data_loss: 1.0858\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"eval valid at epoch 10: {'auc': 0.7613, 'logloss': 0.6053, 'mean_mrr': 0.6831, 'ndcg@2': 0.6349, 'ndcg@4': 0.7397, 'ndcg@6': 0.7623, 'group_auc': 0.7506}\n",
"best epoch: 10\n",
"Time cost for training is 5.76 mins\n"
]
}
],
"source": [
"with Timer() as train_time:\n",
" model = model.fit(\n",
" train_file,\n",
" valid_file,\n",
" epochs=EPOCHS,\n",
" batch_size=BATCH_SIZE,\n",
" learning_rate=0.001,\n",
" train_num_ngs=train_num_ngs,\n",
" valid_num_ngs=valid_num_ngs,\n",
" embed_l2=0.0,\n",
" layer_l2=0.0,\n",
" show_step=20,\n",
" save_model=True,\n",
" model_dir=MODEL_DIR,\n",
" )\n",
"\n",
"# valid_num_ngs is the number of negative lines after each positive line in valid_file\n",
"# we evaluate on valid_file every epoch\n",
"print('Time cost for training is {0:.2f} mins'.format(train_time.interval/60.0))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### 2.2 Evaluate model\n",
"\n",
"Again, let's see what is the model's performance now (after training):"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {
"execution": {
"iopub.execute_input": "2026-07-21T14:49:51.492071Z",
"iopub.status.busy": "2026-07-21T14:49:51.490915Z",
"iopub.status.idle": "2026-07-21T14:50:52.482174Z",
"shell.execute_reply": "2026-07-21T14:50:52.478338Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{'auc': 0.736, 'logloss': 0.6175, 'mean_mrr': 0.5045, 'ndcg@2': 0.4205, 'ndcg@4': 0.5204, 'ndcg@6': 0.5694, 'group_auc': 0.7205}\n"
]
}
],
"source": [
"res_syn = model.run_eval(test_file, num_ngs=test_num_ngs)\n",
"print(res_syn)\n"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {
"execution": {
"iopub.execute_input": "2026-07-21T14:50:52.487466Z",
"iopub.status.busy": "2026-07-21T14:50:52.487046Z",
"iopub.status.idle": "2026-07-21T14:50:52.514442Z",
"shell.execute_reply": "2026-07-21T14:50:52.510418Z"
}
},
"outputs": [
{
"data": {
"application/notebook_utils.json+json": {
"data": 0.736,
"encoder": "json",
"name": "auc"
}
},
"metadata": {
"notebook_utils": {
"data": true,
"display": false,
"name": "auc"
}
},
"output_type": "display_data"
},
{
"data": {
"application/notebook_utils.json+json": {
"data": 0.6175,
"encoder": "json",
"name": "logloss"
}
},
"metadata": {
"notebook_utils": {
"data": true,
"display": false,
"name": "logloss"
}
},
"output_type": "display_data"
},
{
"data": {
"application/notebook_utils.json+json": {
"data": 0.5045,
"encoder": "json",
"name": "mean_mrr"
}
},
"metadata": {
"notebook_utils": {
"data": true,
"display": false,
"name": "mean_mrr"
}
},
"output_type": "display_data"
},
{
"data": {
"application/notebook_utils.json+json": {
"data": 0.4205,
"encoder": "json",
"name": "ndcg@2"
}
},
"metadata": {
"notebook_utils": {
"data": true,
"display": false,
"name": "ndcg@2"
}
},
"output_type": "display_data"
},
{
"data": {
"application/notebook_utils.json+json": {
"data": 0.5204,
"encoder": "json",
"name": "ndcg@4"
}
},
"metadata": {
"notebook_utils": {
"data": true,
"display": false,
"name": "ndcg@4"
}
},
"output_type": "display_data"
},
{
"data": {
"application/notebook_utils.json+json": {
"data": 0.5694,
"encoder": "json",
"name": "ndcg@6"
}
},
"metadata": {
"notebook_utils": {
"data": true,
"display": false,
"name": "ndcg@6"
}
},
"output_type": "display_data"
},
{
"data": {
"application/notebook_utils.json+json": {
"data": 0.7205,
"encoder": "json",
"name": "group_auc"
}
},
"metadata": {
"notebook_utils": {
"data": true,
"display": false,
"name": "group_auc"
}
},
"output_type": "display_data"
}
],
"source": [
"# Record results for tests - ignore this cell\n",
"store_metadata(\"auc\", res_syn[\"auc\"])\n",
"store_metadata(\"logloss\", res_syn[\"logloss\"])\n",
"store_metadata(\"mean_mrr\", res_syn[\"mean_mrr\"])\n",
"store_metadata(\"ndcg@2\", res_syn[\"ndcg@2\"])\n",
"store_metadata(\"ndcg@4\", res_syn[\"ndcg@4\"])\n",
"store_metadata(\"ndcg@6\", res_syn[\"ndcg@6\"])\n",
"store_metadata(\"group_auc\", res_syn[\"group_auc\"])\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"If we want to get the full prediction scores rather than evaluation metrics, we can do this:"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {
"execution": {
"iopub.execute_input": "2026-07-21T14:50:52.518207Z",
"iopub.status.busy": "2026-07-21T14:50:52.517880Z",
"iopub.status.idle": "2026-07-21T14:51:27.536439Z",
"shell.execute_reply": "2026-07-21T14:51:27.532985Z"
}
},
"outputs": [],
"source": [
"model = model.predict(test_file, output_file)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 3. Loading Trained Models\n",
"In this section, we provide a simple example to illustrate how we can use the trained model to serve for production demand.\n",
"\n",
"Suppose we are in a new session. First let's load a previous trained model:"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {
"execution": {
"iopub.execute_input": "2026-07-21T14:51:27.552540Z",
"iopub.status.busy": "2026-07-21T14:51:27.551952Z",
"iopub.status.idle": "2026-07-21T14:51:27.633127Z",
"shell.execute_reply": "2026-07-21T14:51:27.630301Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"loading saved model in ../../tests/resources/deeprec/slirec/model/best_model\n"
]
},
{
"data": {
"text/plain": [
"SLiRecModel(\n",
" (user_lookup): Embedding(3950, 16)\n",
" (item_lookup): Embedding(480, 32)\n",
" (cate_lookup): Embedding(9, 8)\n",
" (asvd_attention): Attention()\n",
" (cell): Time4LSTMCell()\n",
" (attention_fcn): AttentionFcn(\n",
" (fcn): FcnNet(\n",
" (linears): ModuleList(\n",
" (0): Linear(in_features=160, out_features=80, bias=True)\n",
" (1): Linear(in_features=80, out_features=40, bias=True)\n",
" )\n",
" (bns): ModuleList(\n",
" (0): BatchNorm1d(80, eps=0.0001, momentum=0.05, affine=True, bias=True, track_running_stats=True)\n",
" (1): BatchNorm1d(40, eps=0.0001, momentum=0.05, affine=True, bias=True, track_running_stats=True)\n",
" )\n",
" (dropouts): ModuleList(\n",
" (0-1): 2 x Dropout(p=0.3, inplace=False)\n",
" )\n",
" (out): Linear(in_features=40, out_features=1, bias=True)\n",
" )\n",
" )\n",
" (fcn_alpha): FcnNet(\n",
" (linears): ModuleList(\n",
" (0): Linear(in_features=121, out_features=80, bias=True)\n",
" (1): Linear(in_features=80, out_features=40, bias=True)\n",
" )\n",
" (bns): ModuleList(\n",
" (0): BatchNorm1d(80, eps=0.0001, momentum=0.05, affine=True, bias=True, track_running_stats=True)\n",
" (1): BatchNorm1d(40, eps=0.0001, momentum=0.05, affine=True, bias=True, track_running_stats=True)\n",
" )\n",
" (dropouts): ModuleList(\n",
" (0-1): 2 x Dropout(p=0.3, inplace=False)\n",
" )\n",
" (out): Linear(in_features=40, out_features=1, bias=True)\n",
" )\n",
" (logit_fcn): FcnNet(\n",
" (linears): ModuleList(\n",
" (0): Linear(in_features=80, out_features=100, bias=True)\n",
" (1): Linear(in_features=100, out_features=64, bias=True)\n",
" )\n",
" (bns): ModuleList(\n",
" (0): BatchNorm1d(100, eps=0.0001, momentum=0.05, affine=True, bias=True, track_running_stats=True)\n",
" (1): BatchNorm1d(64, eps=0.0001, momentum=0.05, affine=True, bias=True, track_running_stats=True)\n",
" )\n",
" (dropouts): ModuleList(\n",
" (0-1): 2 x Dropout(p=0.3, inplace=False)\n",
" )\n",
" (out): Linear(in_features=64, out_features=1, bias=True)\n",
" )\n",
")"
]
},
"execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"model_best_trained = SeqModel(\n",
" user_vocab=user_vocab,\n",
" item_vocab=item_vocab,\n",
" cate_vocab=cate_vocab,\n",
" item_embedding_dim=32,\n",
" cate_embedding_dim=8,\n",
" user_embedding_dim=16,\n",
" hidden_size=40,\n",
" attention_size=40,\n",
" max_seq_length=50,\n",
" layer_sizes=[100, 64],\n",
" att_fcn_layer_sizes=[80, 40],\n",
" dropout=[0.3, 0.3],\n",
" seed=RANDOM_SEED,\n",
")\n",
"path_best_trained = os.path.join(MODEL_DIR, \"best_model\")\n",
"print('loading saved model in {0}'.format(path_best_trained))\n",
"model_best_trained.load_model(path_best_trained)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let's see if we load the model correctly. The testing metrics should be close to the numbers we have in the training stage."
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {
"execution": {
"iopub.execute_input": "2026-07-21T14:51:27.636332Z",
"iopub.status.busy": "2026-07-21T14:51:27.635980Z",
"iopub.status.idle": "2026-07-21T14:52:50.594672Z",
"shell.execute_reply": "2026-07-21T14:52:50.590961Z"
}
},
"outputs": [
{
"data": {
"text/plain": [
"{'auc': 0.736,\n",
" 'logloss': 0.6175,\n",
" 'mean_mrr': 0.5045,\n",
" 'ndcg@2': 0.4205,\n",
" 'ndcg@4': 0.5204,\n",
" 'ndcg@6': 0.5694,\n",
" 'group_auc': 0.7205}"
]
},
"execution_count": 12,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"model_best_trained.run_eval(test_file, num_ngs=test_num_ngs)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"And we make predictions using this model. In the next step, we will make predictions using a serving model. Then we can check if the two result files are consistent."
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {
"execution": {
"iopub.execute_input": "2026-07-21T14:52:50.600007Z",
"iopub.status.busy": "2026-07-21T14:52:50.599467Z",
"iopub.status.idle": "2026-07-21T14:53:26.050910Z",
"shell.execute_reply": "2026-07-21T14:53:26.047691Z"
}
},
"outputs": [
{
"data": {
"text/plain": [
"SLiRecModel(\n",
" (user_lookup): Embedding(3950, 16)\n",
" (item_lookup): Embedding(480, 32)\n",
" (cate_lookup): Embedding(9, 8)\n",
" (asvd_attention): Attention()\n",
" (cell): Time4LSTMCell()\n",
" (attention_fcn): AttentionFcn(\n",
" (fcn): FcnNet(\n",
" (linears): ModuleList(\n",
" (0): Linear(in_features=160, out_features=80, bias=True)\n",
" (1): Linear(in_features=80, out_features=40, bias=True)\n",
" )\n",
" (bns): ModuleList(\n",
" (0): BatchNorm1d(80, eps=0.0001, momentum=0.05, affine=True, bias=True, track_running_stats=True)\n",
" (1): BatchNorm1d(40, eps=0.0001, momentum=0.05, affine=True, bias=True, track_running_stats=True)\n",
" )\n",
" (dropouts): ModuleList(\n",
" (0-1): 2 x Dropout(p=0.3, inplace=False)\n",
" )\n",
" (out): Linear(in_features=40, out_features=1, bias=True)\n",
" )\n",
" )\n",
" (fcn_alpha): FcnNet(\n",
" (linears): ModuleList(\n",
" (0): Linear(in_features=121, out_features=80, bias=True)\n",
" (1): Linear(in_features=80, out_features=40, bias=True)\n",
" )\n",
" (bns): ModuleList(\n",
" (0): BatchNorm1d(80, eps=0.0001, momentum=0.05, affine=True, bias=True, track_running_stats=True)\n",
" (1): BatchNorm1d(40, eps=0.0001, momentum=0.05, affine=True, bias=True, track_running_stats=True)\n",
" )\n",
" (dropouts): ModuleList(\n",
" (0-1): 2 x Dropout(p=0.3, inplace=False)\n",
" )\n",
" (out): Linear(in_features=40, out_features=1, bias=True)\n",
" )\n",
" (logit_fcn): FcnNet(\n",
" (linears): ModuleList(\n",
" (0): Linear(in_features=80, out_features=100, bias=True)\n",
" (1): Linear(in_features=100, out_features=64, bias=True)\n",
" )\n",
" (bns): ModuleList(\n",
" (0): BatchNorm1d(100, eps=0.0001, momentum=0.05, affine=True, bias=True, track_running_stats=True)\n",
" (1): BatchNorm1d(64, eps=0.0001, momentum=0.05, affine=True, bias=True, track_running_stats=True)\n",
" )\n",
" (dropouts): ModuleList(\n",
" (0-1): 2 x Dropout(p=0.3, inplace=False)\n",
" )\n",
" (out): Linear(in_features=64, out_features=1, bias=True)\n",
" )\n",
")"
]
},
"execution_count": 13,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"model_best_trained.predict(test_file, output_file)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## References\n",
"\\[1\\] Zeping Yu, Jianxun Lian, Ahmad Mahmoody, Gongshen Liu, Xing Xie. Adaptive User Modeling with Long and Short-Term Preferences for Personailzed Recommendation. In Proceedings of the 28th International Joint Conferences on Artificial Intelligence, IJCAI’19, Pages 4213-4219. AAAI Press, 2019."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"celltoolbar": "Tags",
"interpreter": {
"hash": "3a9a0c422ff9f08d62211b9648017c63b0a26d2c935edc37ebb8453675d13bb5"
},
"kernelspec": {
"display_name": "Python 3.7.11 64-bit ('tf2': conda)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.14"
}
},
"nbformat": 4,
"nbformat_minor": 2
}