From 00eaa884cd3437f3e8741d1a5ae82cde74b553b0 Mon Sep 17 00:00:00 2001 From: anettapik <120940816+anettapik@users.noreply.github.com> Date: Tue, 21 Nov 2023 12:43:04 +0300 Subject: [PATCH 1/7] =?UTF-8?q?=D0=92=D1=82=D0=BE=D1=80=D0=B0=D1=8F=20?= =?UTF-8?q?=D0=B4=D0=BE=D0=BC=D0=B0=D1=88=D0=BD=D1=8F=D1=8F=20=D1=80=D0=B0?= =?UTF-8?q?=D0=B1=D0=BE=D1=82=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hw_2.ipynb | 440 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 440 insertions(+) create mode 100644 hw_2.ipynb diff --git a/hw_2.ipynb b/hw_2.ipynb new file mode 100644 index 00000000..b4d81fa6 --- /dev/null +++ b/hw_2.ipynb @@ -0,0 +1,440 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 63, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import pandas as pd\n", + "\n", + "from pprint import pprint\n", + "\n", + "import copy\n", + "\n", + "from tqdm.auto import tqdm\n", + "\n", + "from implicit.nearest_neighbours import TFIDFRecommender, BM25Recommender\n", + "from implicit.als import AlternatingLeastSquares\n", + "\n", + "\n", + "from rectools import Columns\n", + "from rectools.dataset import Interactions, Dataset\n", + "from rectools.metrics import Precision, Recall, MeanInvUserFreq, Serendipity, calc_metrics, MAP, MRR\n", + "from rectools.models import ImplicitItemKNNWrapperModel, RandomModel, PopularModel\n", + "from rectools.model_selection import TimeRangeSplitter" + ] + }, + { + "cell_type": "code", + "execution_count": 123, + "metadata": {}, + "outputs": [], + "source": [ + "df = pd.read_csv('data_original/interactions.csv', parse_dates=['last_watch_dt'])\n", + "\n", + "df.rename(\n", + " columns={\n", + " 'last_watch_dt': Columns.Datetime,\n", + " 'total_dur': Columns.Weight\n", + " }, \n", + " inplace=True) \n", + "\n", + "interactions = Interactions(df)\n", + "\n", + "\n", + "users = pd.read_csv('data_original/users.csv')\n", + "items = pd.read_csv('data_original/items.csv')\n" + ] + }, + { + "cell_type": "code", + "execution_count": 124, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
user_idageincomesexkids_flg
373089666262age_65_infincome_20_40Ж0
\n", + "
" + ], + "text/plain": [ + " user_id age income sex kids_flg\n", + "373089 666262 age_65_inf income_20_40 Ж 0" + ] + }, + "execution_count": 124, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "users[users['user_id'] == 666262]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Функция для расчета метрик" + ] + }, + { + "attachments": { + "image.png": { + "image/png": "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AQQQQAABBBCInQCBBLGzpWcEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQSSnACBBEnuknHCCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIxE6AQILY2dIzAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACSU6AQIIkd8k4YQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBGInQCBB7GzpGQEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAgSQnQCBBkrtknDACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAKxEyCQIHa29IwAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggECSEyCQIMldMk4YAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQACB2AkQSBA7W3pGAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAgyQkQSJDkLhknjAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAQOwECGD6lGMAAEAASURBVCSInS09I4AAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgggkOQECCRIcpeME0YAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQCB2AgQSxM6WnhFAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEhyAgQSJLlLxgkjkDCBtGnTSokSJeT2YsXkg9Gj5c8//0xYh+yNAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAALJSuCiDSS47PLLJVPGjNbF/Ouvv2T//v1J6sLmyZNHKlWubJ3z4cOHZeKECUnq/DnZ2AqkTJlSChUuLKVLl5Frcl8jmTJlksyZs0jOnDkl59U55X//+591Ag/Uqydr166N7cnQOwIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIJCmBiy6QIEWKFNKqdWvp3KWLpEmTxrpYv/z8s1SpVClJXbhGjRtLr969rXP+559/5OabbkpS58/JxkZA7+9OnTtLo0aNJWu2rHEehECCOIlogAACCCCA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T80/ZgQhgTczBXVAIPoSwDsy+t479BwEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAE/qsEICT4r955Me7vZs6iBAkS0L8PH9KRI0do8+ZNtGTx4v8wEQwdcyJy5kDZcuVkiBG++u5du2junDmR0xFcFQRAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAAQEAQgJMA1AAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAAQ0AQgJNApkQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEICTAHAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABENAEICTQKJABARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARCAkABzAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAQBOAkECjQAYEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQABCAswBEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABTQBCAo0CGRAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAAQgJMAdAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAAQ0AQgJNApkQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEICTAHAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABENAEICTQKJABARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARCAkABzAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAQBOAkECjQAYEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQABCAswBEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABTQBCAo0CGRAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAAQgJMAdAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAAQ0AQgJNApkQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEICSIJnMgfvz4lDRpUtnb0NBQOnPmTMA9j4g2VadCQkLo7SJFKH369JQyVSpKkiQp3bh+nS5evECHDh6i9evX0Z07d1R1bEEABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABKIIAQgJosiNcNeN/AUKUL/+Ayj5S8l1tZLFitPZs/6LCSKiTe5c7Nix6YP69eXfSy+/rPtrZ27fuk2TJ02k4cOG0cOHD+3D2AcBEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEIgkAhASRBJ4by4bM2ZMatvuU2ry4YcUI8YzDqeUKlmSTp065VDmzU5EtKmumyhxYho9ZizlzpNbFXncbli/ntq3a0fXhbcCJBAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAgcgnACFB5N8Dpz1IkyYNDRw8hLJlz+b0uD9CgohoU3UuYcKENGfefEqfIb0qkttrV6/R7t276MqVK5Q6dRrKkiULxY0X16HOqZMn6aNmzejE8eMO5dgBARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARB48gQgJHjyzD1esWq1avRlt+4UL348l3V9FRJERJtm53p+9RXVrlNHFz24/4CGDR1K302fRjdv3tTlMWLEoMpVqlD3Hj0pTtw4uvzg/gNUtUplhDnQRJABARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAgcghACFB5HB3edXcefLQzNmzHY5v3rSJlixaTP0HDdTlvggJIqJN3RGRyZkzJ82aO4+eeRR94d69e9Tqk09o/bp1ZjWH/GuvvUZjxo2nlKlS6vJOn31GixYu1PvIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIPHkCEBI8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IJrcz/jx41PSpEllb0NDQ+nMmTMB9TwkJIQKv/025cyVi1566SWKFzceXbp8iU6dPEmrVq2iSxcvBtS+s5P5mm8XKULp06enlKlSUZIkSenG9et08eIFOnTwEK1fv47u3Lnj7FSUgQAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIPCECEBI8IdCBXCZ/gQLUr/8ASv5Sct1MyWLF6exZ/8QELB4YPmIkJU0WJkzQjT7KPAx9SGPHjqEhgwbZh/zajx07Nn1Qv778e+nll122cfvWbZo8aSINHzaMHj586LIeDoAACIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACEQcAQgJIo5twC3HjBmT2rb7lJp8+CHFiPGMQ3ulSpakU6dOOZR5s8MeAUaMHEVx4sbxWH3h/AX0RdcuxB4Q/E2JEiem0WPGUu48ub1uYsP69dS+XTu6LrwVIIEACIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACDxZAhASPFneXl8tTZo0NHDwEMqWPZvTc/wREhQvXoJGjBpFMZ+Nqdv84/Bh2rVzF927d5dyvJlTXC+7g2hh4YIF1Pnzz3V9XzIJEyakOfPmU/oM6R1Ou3b1Gu3evYuuXLlCqVOnoSxZslDceHEd6nCIhY+aNaMTx487lGMHBEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAgYglASBCxfP1qvWq1avRlt+4UL348l+f7KiR45plnaMWq1ZTh1Qy6zX7ffksTJ0zQ+5ypVLkyfduvvxYTPHz4L71booRfYRR6fvUV1a5TR7f/4P4DGjZ0KH03fRrdvHlTl8eIEYMqV6lC3Xv0dPCUcHD/AapapTLCHGhSyIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACIBAxBOAkCDiGft0hdx58tDM2bMdztm8aRMtWbSY+g8aqMt9FRJkyZqVFi5erM+fOH489evbV++bmU9ataLWbdrooi6dOtOC+fP0vjeZnDlz0qy580joF2S6d+8etfrkE1q/bp3L01977TUaM248pUyVUtfp9NlntGjhQr2PDAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiA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AAIgAAIgENkJQEgQ2e8g+g8CTABCAjwG3hJInjw5vVe0qKp+lOOB/7Rypbenot4TQgAGySfkRqAbIOADAby3PsBCVRAAARAAARAAARAAARAAARAAARAAARAAARAAgSeCAIQET8RtQCdAIDACJd9/n/IXKKAa+X3bNvphxozAGsTZIAACTyyBkJCY1OmLzhQ1alSi+0T9+n5DFy9efGL7i46BAAgQ4b3FUwACIAACIAACIAACIAACIAACIAACIAACIAACIBDZCEBIENnuGPoLAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAhFIAEKCCISLpkEABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAgshGAkCCy3TH0FwRAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAAQikACEBBEIF02DAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAQGQjACFBZLtj6C8IgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIRCABCAkiEC6aBgEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAIHIRgBCgsh2x9BfEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEIhAAhASRCBcNA0CIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACkY0AhASR7Y6hvyAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAQgQQgJIhAuGgaBEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABCIbAQgJItsdQ39BAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAIAIJQEgQgXDRNAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAhENgIQEkS2O4b+ggAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgEAEEoCQIALhomkQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQiGwEICSIbHcM/QUBEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEACBCCQAIUEEwkXTIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACIBDZCEBIENnuGPoLAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAhFIAEKCCISLpkEABEAABJ5NAu/mz0/f9O2rBr961Wrq2KG9C4iChQpR9x49KHr06LR27Vpq/9lnLscj+07MmDHplVfiqWFcvnyJrl696jKkNGnSUK7cuenndevor7/+cjmGHRAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAgcdPAEKCx38PvOpBrFixKEGCBKru3bt36fjx416d56mSGHrSp3+b3s7wNsXk9vft3Uu7d+2iU6dOeTrNq2PPPfccvfbaa6runTt36cQJ7/sbJUoUSp48uXUdGauMGQkEQAAEIguB3Hny0PiJE1V37/x3h/LkzkWXL12yuj946FAqUbKk2l8wbz61a9vGOuZvRubdTJkykVw7UaL/UdxX4tK/t/+l8xfO06GDB+mnlSvpktEHf6/jzXk9vvqKqlarpqqu+uknavzxx9ZpRd57j74bMULt379PVOfDWvTbr79ax5EBARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARB4/AQgJHj89yDcHsiqzW/69qNE/0tk1S1SsJBPxnnrRM7kzJWLvujShVKlTkNRojxnHlL5f/75h2bO+IEGDuhP9+7dC3PcmwK5xsTJk62q77Jh69y5c9a+p0z9Bg3o8w4drCpNmzShFcuXW/vIgAAIgMCTTiBGjBi0eds25XFA+jpx/Hjq+fXXdJ8t55kyZVbzY4yQGGoYHdu3p9mzZgU0pPTp09MgFieYIix7g/fu3qNx48ZSf/aUENHirK979qJKVSqrLqxZvYY+bviR1Z0hQ7+l4iVLWPszf/iBOnfsaO0jAwIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIg8PgJQEjw+O+B2x5EixaNWrZqTQ0aNgxj8C9WpAgdPXrU7bnuDlSvUVOJCKJGi+quilW+ds0aat2yJV27ds0q8zaTJ29eGjdhglW9QL58dObMGWvfXSZN2rQ0Z948y/gm9Vo0bUZLly5xdwrKQQAEQOCJJNCxU2eqU6+u1bej7MJfPAK8xUb/559/XpUf43m8SqVKJAIuf1OZMmXpy549KUaMF7xqQlb/t2rRgi5evOhVfX8qeRIStODvSpOmTa1m+/TqRWO//97al0yrNm0oP4eHkLSOwx8M7N9f5Z3+86Wu0/koAwEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQCEsAQoKwTJ6IEgkL0H/gIMqQMYNjf/wREtStV586dHJd9Xnm9Gnas3sP3bx1k1KnTs3/2EtB1CjWNf/84w9l5LIKvMz4IyQQw9rM2XPozbfedLkKhAQuOLADAiAQiQh0+uILql2njmOPjxw+QrVr1fTaW4tTIzVr1aIu3bq5HLpx/QYdOLCfDh8+TLFjx6a0adNRsuTJXOqcOnmKPmCvADdu3HApD9aOJyHBK6+8Qp+xF4bMmbPQpk2/Ud8+fejq1asulx45egwVLFRQldk9GrhU5B1f6trPxT4IgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgIAzAQgJnLk81tIKFSuy14CuFDNWTLf98EdIsHLVasuY9O+//1LPr76maVOnuFwjR44cNGz4CIodJ7ZVXufDD+nXX36x9r3J+CMkkFWlnzRuHKZ5CAnCIEEBCIBAJCKQ9Z13SOZW2Up4geMnjtOWzZtp44YNYQzovgwrJCQmrV63luLGjWudtnDBAurOwoIrV65YZZIpWqwYfcmhFcy6QwYPpmEcDiEikichgTfX80Uc4Etdb66NOiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAkQQEjxhT8E72bLR1OnTXXq1/uefaf7cedR3wEPXzr4KCbJkzUrTOQ61Tm1ataKFP/6od122hQoVphGjR1llA/r1p5Ejhlv73mR8FRJI/6ZOm+7iDUFfB0ICTQJbEAABEHhIIHeePDR+4kSrYO6cOdT+s8+sfXvm9RQpaMnSZdY8u3PHDqpYvry9WlD2ISQICkY0AgIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAKPjQCEBI8NvfOFc+TMSZOmhHoJ+O+//6h/3740ftw4EoPRuAkTrJN8FRK079CR6jWor86XWNz5cuemO3fuWO2ZmZgxY9Lv27dbRZMnTaIvu3e39r3J+CIkkFW1Cxb+SMk5nINTsgsJJARCterVKWrUaHT//n36YcYMunnT2T131KhRqWq16hQ9enTV9Jw5s+nK5cvWZWSFcMaMmdT+vn176ZeNG61j9kzGTJkoa9Z3VPH169dopiHM8KVPL774IlWsVJmee+45vgf/sVeIqXT37l2XywWrX2ajL7zwAmXnVdFp06altOnSUaJE/6Pjx49xaIvdtHvXbtqzZzeJpwpPKUuWLJSJ3ZF7k+7evUPTp00jeY51iohxJUmalLJnz67GlCZNGnWpA/v30969e+n3bdt4jMf15V22wepLtGjRqHqNGtbzOGvmD3T9+nVKkTIlFStWXIUMiZ8gAZ0/f46OsKv5eXPn0mkOKeJNih0nDpUuXZrSpElL/3v1VXr++Wh09sxZOnbsKC1auNDt2KRt85l0uta9e3dVPw4fOkSH+F94SYzQmfgdkOcnFYdAkfP37tlDu3btol07d9KZM2ccmzD78Sjf17czZKBs2bKrPh05cpjWrllj9c+XPlknGZkSJUqq+yFF639eRwcPHrSO+tK2zE/y7ESL9rw6f8P6nzkkwQGrrfAyMg92//JLq1qjjz5yGad1wMgMZg8EJUqWVCXyLcjF745OBQsVotdfT6F2N2/epO6rzM9ly5WldOneIAm5c4Pn2mPHjtG6tWs9eqrxJCSIFSsWVapcRc2BMk/IHCjfo1f5GS/ObCXVrVeXXk2cWOVPnzrF38HxKi//rVyxgufMO17XPcEeIOwpMbddukxZHu/rlOh//+MQD9dJwj1s3brlQfuuc7I+385oNz//afidyJUrl5pfY8V6UbEZP26sPsWrbbHixSlx4iRe1ZVKy5cv4/6edKwf6DxtbzRRokQk71PSpMnUPbMf3779T9q2dau92OO+t/Ov+a5Jg3Nmz1LeNiJy3pXrZOP3In/+ApQkSWKKFz8+X/MqyXMo35SVK1e4/ftJzjWThBYpXaaMmsPleZY5cM/uXbSDRTwi5Dl37pxZ3W1eRKYFChS0+nPyxEnauXOHamcff+vM76zbRnw4YN4fp9OuXbtGx47+Rbv57wfJh5f8/X6Y7Up4Lm+T/Zk0xxPI33rm9YP5zkq7/jLyZWz58uWj1Pz3hKQ/ft9Gf3D4MjMF8/vlS788/X1t9g95EAABEAABEAABEAABEAABEAABEAABEACBR08AQoJHz9zjFbWQ4Ohff1GrFi2UkU5OsBvmfRUSiFEzAxsCJJ1hI+amTZtU3um/+Pyj+YZff7UOSQiECePHWfveZOz9LcA/XrozNIohTAxiOk1gg1EdNiLpZBcSiOF4lWEULFe6jDKC6/rmVn7E38w//Ov0Yc2atOm33/Qu9f7mGypfoYLa/2nlSmryySfWMTMj7SxcvIQNTolUsbgnfzNd6I+xUuBLnzJnzkwzZs2yms/Loo4L589b+5IJVr90oxkyZqQ+3/RlA3AqXRRmu4fFBB991CBMX8yKHTp2orr165lFHvOFCxakkydOWHWCOS4RYtTisBttP/ucYsR4wbqGmRFhRA92826KPvTxYPXlf2yEXLt+vW6WqlSsRCXeL0m1a9ehaGz4t6c7/92hiRMnqLjw9+7dsx9W+1GiRKGWrVrze1DP7djYHkXLly2jTh3aO7rHtz+Tjhd6UPjzunXUvWtXR2GCiHCat2hJDdhIHSVqFMdm7t27rzjbQ6VIZXs/HsX7Ktft1Lkz1a5bV7K0mee7Wmyw18mXPulzzO3S5StYKBJqcO/ftx+NGjnCOuxL2x81bEjtPv/cOnfwwIH03bBh1n54GREk9ezdy6rWgJ8X8WDjKYk4JV68eKrKzRs3XOblaSzKEuOPpLHff6+4devew5r31AHjvz9+/52aN21KZ8+eNUpDs56EBGKUns2CGp3096FM2bLUt/9Dzzv6uH3buWNHun37ttd1zfdfBFVdunZT837UaFHtTat9ERR83q6t43fSzigai0H0c6YbW7xokfp+631vtuIxSDzzeJs68HMzZ/Zsx+qBztO6UQk1VLtOXTakhz4vuty+Dc8Thr2+7Hsz/4rIpluPHtbpYsivUqmSMr5HxLwrF0r3xhvUl70wpXsjnXVde+b8ufP0ZY/utGzpUvsha1++T82aN+d5syHFCIlhldszo0aMpIHsbcrdtyB16tTUr/8AejP9W/ZTrf3jx45T408+JhHQBSuZ98dTm5cvXaYhgweRCE6dUqDfD92mCDC32oze+pjT1v5MmuMJ5G8981rBemcDZeTt2KTv37MwOd+776phiNCz6xdfmEPy6Xsd3vfL236F9/e1SwexAwIgAAIgAAIgAAIgAAIgAAIgAAIgAAIg8MgJQEjwyJF7vqAYocQ4OmTQYJdV9nbDvK9CAs9XdT1as1Yt6sLGV53K82o6WXXmS7L3VxuK7G0UYEPzqDFjrGJZ2Vy3Th3abKxufBKEBL1696EKlSpa/XxShATh9Us6XPL992nAwEFujcDWoDgjqxzr161Df7GQxSmZPww7HbeX+Ssk8GZc3XuwAKXGQwGK/drmfl8WjIwZNcos8sqQJSeE1xe7QUuEKiIICi9JaBEJMWJPskr9OzYuFSxU0H7Icf/I4SP0Ya2adN62stVu0HY82Sg8yCvhK5Qrpwy0ulj6MnvOXI9GLF1XtsO/+44GDRhgFvlkmAiW8Ec68KQLCUTcNZ+fATFs6+SrkCBlylS0hFem6/QbC8A+adSIV9c7e2jR9dxtTSO5iATSv/228mzhrr6Ui9G9ZvVqdIqNvGZ6EoUE4m1HDGlaLGH2156XFd4t2RAs3g/MZDK6eOGio5HdHyHBkmXLKWWqlOalPOY9CQkCnadltf+XLCA0v3meOmM32nqqq4+ZfXQy7L7/wQfUX75bUZ5Tp1w4f0E9Z/rbFOx5Vy4if7d8N3wEhcQM0d10uxUhV59ePWnc2LCeJ0QIJp4/ZMW6N0m8pbRu2TLMyv4c7EFoOH+3xIgeXrpx/Qa1a9smzPMa3nnujpv3x10ds/yztm1p/rx5ZhF76Qn8+6Eb9PV7Zn8mzfE4PW/6OuF973U92QbjnQ0GI2/HJn0OlpDAm++Xt/3yhbmMAQkEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQODREoCQ4NHy9vtqdsN8RAkJqlarRl27dSe9UlNWqVcoX87tajl3A7L310lI8PLLL6tV/gkSJlDN3Lp1myqULaPcrZuhFR63kODd/PlpjM1Y8CQICbzplxg0li5fTq+x+26dNm/arNx3i5t9WZks99w8Pn0qr1Lr4rpKTZ87YuQoKlSksNqVlc92I1uKFCldvEn4IyTwZlwJEyak1WvXWSv+xUX7rB9m0pYtm+nChQuUJ09eatykCcWMFVP19fChw1SyeDE9DLX15kdub/piN2jpi4g3hM2/baLfWFggnPMXKGCtYtd12rVuQwsWzNe7aiurgFu1aWOVyWp/Edhs2LCeZAV5jpy52O12NhcD9OqfVtEnHzeyzpGM3fAyfuw4Ehf/ksR9cS72hJHv3fwuHg86cQgUCc2gU1kWFnzTr5/epb8vXqR17L1gB4c+uXnzlupH2bLlrPlCKuZlF+9yD3Sy9wMeCYiNo1Fo6vTpYVag+yokEMa/bt5McePG1bjZbfoe+nboEOVeP7xQJdZJDzKmkdw8tn/fPtWehOyQMDsS4sX0trF61WoWMDQ0TyF/hATyXhcuUkS107DRx5Q0WVKVP3H8BI0eNdJqXwyvEg7G27o6lMgX7HVDhHo63bxxU4Vn2LptK8m18+bN5+K1RVZby7xxkZ97nZwY3WEvIyK2+/PPP+gWvxe/s8twMVb6kn7heeKVB54iJk2cSIeMcBm6HXN1vichQaDz9EcsRmn32Wf6siTf5h95npIwKDdv3lTl9dlDSfLkyVXebrS1TvSQ8TT/yorpkaNGW8/YpX8usUeR6i5hP4I978bhv0cWL1lK8RPEt3ot35WNGzbQ9j+3UwoO7ZKXPSslS57MOi5/B1SpXEnNh1YhZ8R7y2ft25tF6r1cu3YNhwM4Rm+wt4MqVau5eCqwi7BeeuklWsT90V6QpLFrV6/Sz/zd/e3X31SYmzI892bIGOppSo7L8/xu3jyOHmrkuC/JvD8SyuT70aOt05MkSUpFixVz+Z6Jxyn5O89Mwfh+6PbSp09Pc+Y//FZKuC17SChPz6Q5HndCAm++97o/sg3GOxsMRt6MTfc7GEICb79f3vTLV+Z6HNiCAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAg8OgIQEjw61gFdyW6YD1RIICsOK1epyrG5o/K/aCq+fKZMmV2MKLLSuUa1qvT333/73Hd7f52EBGasbrnA1xziYOKECSSrRp8UIYGsBFzE7ovFaGGmxy0k8LZfYmgbPvKhAW4GGy+7sMt3M8n9nzp9BmXKnEkVHzp4iN4v4byS0nTl67TK/80336J5Py6wmvdVSODtuJqwO/UWvIJTkhjaxYvCLxs3WteVjP0H+iIFC9GJE8etOuH9yO1tX5wMWlevXFGr+8UAYybps/Rdp3Nnz1GBd/NZQh0RHKxhQ5G4Otap2aefqhAGel+2shpw1pw5LitV7W7tvTHgS0zmZcaK6ymTJ6sQBfpa8+YvsLwRyJiqVq5Mh9iYaKbChfkZM4y8sop7yeLFVhVv+qErPyseCSTWd4dOHfWwra0/QoLa7MGlk809tTQoRsc1q1ezaGibiuu+f/++MIY368IPMk5GcgkNIs+FmcSoN41d8ZveFOxhY/wREpjXGDl6jOWVY83qNfRxw4/Mwy55b+qmTZeOPUAstFa4C59qVaq4GKfFHf2gIUOoRMmSVvvyTZJvk052RiKu+ah+fSsMka7n63bXnr2W4bwqu++3xy6X9rayl4gX2cAsyZOQINB5ejwLGUQwIkm+/5U4/I8ZokbKvxsxgoq8955kKZhCgixZstD4iZMsI7vcp9os/hAxlZmCOe9Kux07dXYRwe3YvkP9/WMX4/T46islvtN9+ZPd7Uu4BZ1E1COhbsx34/N27WieEcpD6ooIQ55HLXaT5yg/G+LFE4akzzt0oPoNGqi8/CdiniocyuTmTVdvIyL8rMEeaXTyJxSVPtfchvd9lLr92ftMKfZYpVOu7NlJxBc6BeP7odsSkc/YCePVrghbMr2dXh+ytp6eyfDG4+333roYZ4LxzgaDUXhjM/scDCGBt9+v8PrlD3NzLMiDAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAg8GgIQEjwazgFfxW6YD1RI4PQjvNnJcd+PpTFjRtOF8+fNYq/z9v7ahQRlynAs7AH9rfY2btiojMH32V/wkyQk+KpnTxZcVLH6qTOPW0jgbb/EMC3hIyTJ6j1xp29fxSfH3italIYNHy5ZlXKzS2UnAYnpyrdj+w40e9ZMfYraBiok8HZcYkgXg5Okc+zS3ykufKpUqWjxsodu3z8oUYIOGqt8w/uR29u+2N8lcXktq7PFiGtPspJvDL9beVk8oFPN6tVpC68ql2SPCS7xs/v366urumzlnn373XBi26dKdmOetwZ8MXrJGCTZ28jHhq2EiRKpY3v37HEb4kQMYm+8+YaqN3XyFOreravKy3/e9kPqPi4hgRiU9zwI33Lnzh2Sf57S0uUrrNW4/fv2o1EjR1jVwxvv6+wdZP7CRS6eIPTJ/ggJ5Fx3hh3drmzlfV64YIG6x+5C1diN5GL8FCOoU6pUuQp9za7ddbLf9ydNSNCmbTtqxHHkJd29c5ca1K8XRnwkx8QAPGXadGult4h98ufLS/JtkmRnZJ9XVCUf/4sVKxZt+/NP66zibKDXLvytQs54KyQIZJ6W8W/ets0yhPf6uieNHxfWfb8no63ZZ3d5p/k3Tdq0NJXZx44TW50mK+zr16tL24xQR7q9YM67Mi+vW7+BtHckeVcqlC2rvCPp6+mteHOR50ML76TcFMyJhx8RG+g0edIkktXzTsk+33/K3mjEy48IWqQ/CRMlVKeJiKti+fJ09OjRMM2EhMSkBYsWWt4h9rIgpWzpUmHq+VrgdH/sbdjnAJOD1A3G90NfU0JdDBw8WO2eOX2aBXjv6kPW1tMzGd54vP3e64sF650NBqPwxqb7LNtAhQS+fL/C65evzM1xIA8CIAACIAACIAACIAACIAACIAACIAACIPDoCEBI8OhYB3Qlu2E+ooUEVy5f4VWok2jYt99aK+R8GYC9v6aQQAwAC3nF8kuxQ40Fcq3SH7xP4hpX0pMiJDBXwEm/FsybT2XKlZUsPU4hgS/9Up314j+70b3U++/Tgf37w5xpuvJt/PHHtOqnn1zqBCIkCPa4JDyAhAmQdPzYcXqvcCGXvnr6kduXvtgNWk5hBswLiwhCwk3oNIIFHAP7h4pqRrA770IP+ilu5LNnzeoo/NDnTmAjlYQokCQrQWVFqE7hGbSlnqzS/nHRIn2KiglvehOwDoSTkVXcJfmZkbRs6TJq3vRT6wxv+qErPy4hgb6+bMVefPbsGfrryBFayq7Ff5gxPcw98FdIIAbLSVOmqpAQcq2dO3ZQrFgvWqIEf4UE0laJEiWpWYvmlDpNGtn1mGQVtby/pst+OcE0kounj2xZMpM8h+7SvAU/0ptvvakO29+xJ01IIG7iU6dJrfpqX0VuH1/1GjWpW4+Hxt/SbMSUFeGSTEYHDxygDwzvBfZ2vN1PnCQJh2pZa1XPkS0bXb50ydrXGW+FBIHM0/b5qVWLFrTYmCN0XzwZbXUdT1v7/Csr6cWTgjbmiyeARhwiwO5pRrcZzHk3/dtv05x583TT1LdPHxpjuPK3DjzIZON5dsq0aVax6bXDnMMlzMU7/A65EyaJYKAge8rRoaQ2cXiLKywasPdnEK/8l9AH7pIYd/V7L+JPJ28W7s51V26/P00++SRM1SHfDqPiD7wXefJkFOZEW4Gn74euar6TEnKrHIfBsidPz6Sn8fjyvdfXDPY7q9t1t/XEyNPY7O0FIiTw9fvlqV/+MLePBfsgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAKPhgCEBI+Gc8BXsRvmAxUSyKrDps2aqx+wozwXRf14nzz5a/R2hgyW62fptKxuk9W6dne64Q3I3l8tJJAfzseOn0B5OI6vTm1atVIr5fW+r0KC2TNnOa7Uk/bEdapehSr7dvfb7n7olNVmi5YsoVcTJ5bT1EreFbwKefC3Q9X+4xIS+Nov1VnjPzlfjBSvxH2FhRwvqRXgEotZ7rvEqtXJnZBg9959ltFDnovfeeWqmfwVEgQ6ruLsbSBGjBgcpuN5tVI0NxvXk7/2mtW1IbyScdjQ0HunC4N17+0Gre9YfDN40CB9mTBb+TH+d3abHSPGC+qYCFTatbTVWc8AAEAASURBVG0TmueV6uk4frak8IydUsfujjsT39tbt27JoTCeAMRIfYANn5JCYoRQUo71XbVqVfrfq6+qsmvXrlE+5qZjoKtC238iOJF5wnx2RBAkMcF1Ck9IEKz3VValTmWjvLv0QalSFsvNmzZxjPUaVlW7uME64JARA7IY0Y4ffxgWw18hgRmGQFyYl2e34GIg0kbAQIQE0nWZXzNkzEgFChRUApO3+XmIERLDYVTE8e4PU93aH7Jo4qx13DSSH/3rLyr2wHW9VcGWMd+hO//dofQPvFJItSdNSLB91y5rlb2IRbp37WobzcPdd955h0obhkoJXfDzunWqgslo+tRp1LXLFw9P9DP31ltv0Vz2FiFJBBzpeQ64d+9emNa8FRIEMk/LCncJLcSPkkruvFJ4MtqG6bhDgfns6BAw4u5fkjxLTZs0odWrVzmcGVoUzHm3aLFi7N3loaHeHibG3gm74GnMqFEkoX4kiTBLBFqSJDxCpQrlVd6X/4oVL05Dhw2zThFBxdo1a6x9bzMy37Tgv6+8SZM4nIUIFnQy78/BAwdp0MDQY/L9euWVV9T8UpzFS/o5kW+efPs8JX++H7q9JhziR49lA3trkHBG9uTpmTTH89PKlWpOl/P9/dsj2O+sHos/jNyNTbdpbgMREvj6/XLXL3+Zm+NAHgRAAARAAARAAARAAARAAARAAARAAARA4NERgJDg0bEO6Ep2w3ygQgJ3nRHXwp053rZe5Sz17HHT3Z1rltv7q4UEH9auTZ27dLGqLlq4kFo/iHWvC30VEujzvNl6KyTozjGxq7HLeUkXL1yk93nVXa5cuR+7kMDXfmkm4kK5QsVKJIbFKFGj6GK3WychgYgytvJKZp2Ks2t9WbVtJn+FBP6OS64dNWpU2v1gtbDZF513ik8tx9z9yO1rX+wGrRZNm9HSpUv05R23svpVBB2STKOIuZJ4Jq/O7dyxo+P5utDuWroIr249cSLU4O2LsVwMXq1btiBt0NPtyzYxi2nasHv73LnzULz48cxDjvnwhASOJ7kp9PS+ujnFsTg8IYF4Q/nn73+UIT5BggRhxvnnH39S9apVLM8E/ggJxEj646LFlmFfrzIWwVKwhAT2wcu78Vb69FSoUCH2GPEBpUyV0qXK1i1bVRx4XWgaye33UdcxtyIg+ax9e6vIXEn/JAkJ7IZfq8NeZtp/9pkKCSHVTUZjv/+e+vTq5WUr7qvJ91a8i0i69M8lypk9m2Nlb4QEwZinzflJOjJ+7DgaOWK4S7gbT0Zbx87bCs3513aI2rE3mQXz59uLXfaDOe+aq93lIvnz5nUR2Lhc+MGOGRLGFFv8wqIlMbRL8mYOf9Ccy6ZGzVrUtXs3q8yb/liVjUyz5i2oafNmRon77HT2sNCV//bTydP90XX0dtYPM1U4G/EiYU+Bfj90ex06dqK69eupXQnRJAJUe/L0TJrjMYUEvn7v9TWD+c4Gysjd2HRfza2/QgJ/vl/u+uUvc3McyIMACIAACIAACIAACIAACIAACIAACIAACDw6AhASPDrWAV3JbpiPKCGBdFIM+bPnzrOMTrL6PnPGDHT79m2vx2DvrwgJYsaMpVZd6pXYZ8+cpVIc0uDK5csu7T5uIYH8QDx+4iRrpV0zXgm3fNky5Tr8cXok8KdfwvJrNnRJfGFfkpOQwG6YNo2Gum1/hAT+jEtfT7bhCQmOcVzpPr17q9jT5nlOP3L70xe7Qasui2XcuePW1xevHHk57rqkLZu3UM3q1VRe4qTLaj1J37Nr7W/YxbanZF+5asZrt98vT+2IC+7FixeRuOg2PRKIl4p+/QfQy3Ff9nS6yzG7AdqXfrg0xDuPSkhQrnQZ2rNnt3X5lClT8XvTk7LyynSdRNQhhkFJvgoJ9u7dwyENplD2HDnU+bt5dXylChWUMCEihQTqYg/+k/fkw9p16PMOHSyvM3fv3FWu1/U9N43k3hhBK1aqTD17PzSkmwbPJ0lIkChRIlq3YYOJw6e8ee9NRsESEkhYCv1tEXGWiLSckjdCAvv75s88nTNXLhozdixFjx7dpRsictDeibS3Hqkwd84cErGFL8mcf+3nmV5a7Mf0fjDn3XrscaK9IdrKwB4inIzi+tqyNT0PLGUxUItmoQZ7cw4f9/1Y6s3ziK/J3h/T04wvbYnXKQl34k2ye9fwdH/s7e1jT0UD+vejNatXuxwKxvdDN2j2R7wnfNWjhz5kbX0VEvjzvdcXC9Y7GwxGJhtTJKH7am79ERL4+/1y6lcgzM1xIA8CIAACIAACIAACIAACIAACIAACIAACIPDoCEBI8OhYB3Qlu2E+IoUE0tGq1apRj6++svosLrh3735oaLMOuMnY+ysrpcWFdwYWJEgS19If1atL69evD9OCr0KC0SNH0aFDB8O0IwWyErVj587WMU+GSfkBtm3r1rRw8RJ2C59EnSOxoSVGtCTzh+NHHdrA3341575/+sDAIWO4wy6jhfnBA/vp3LlzdJlFHBKTOS6HOjANgk5CAgl/MHvuXGlGud9+K11avo98I43kq5DA33EZl1SryOvUrUch7ML9OXa7HDduXErDceJzsgeJKFFC/XOLG3kx8G/ZvNk61f4jt799sRu0unXpStOmTrGu45RZtWat9YyZP/z/tHoNJU2WVJ2ycsUK+rRxY6fTrbKGHOe+LXsL0ClPzpxW3Hu7QVGe5RPHT6iq0Z6PRvHixaNs2bJb/ZAD6zhOe8MGDVQdCRWxaetWyx28FIooY/OmzSqUyKVL/6hn5yo/P11YgPAax+mWFJ6QIFjvq7je9xQ3XDyKZM6SRfUpPI8EdiGBnBTn5ZdpKQuIXmFOkn6YMYO+6NRJ5X0VEmR9J6tiJCfLO1ihfDnat3evautRCQnUxfi/dmzw/ahRI71LdT78kH795Re1bxrJ5T7XqhHqlcWqbMu0advOJXRMRvZ+oAVnT5KQQEL5SGgDnfbs3kNjx4zRu47bkJghdPPGTXVs4y8bSWLPSzIZBUtIYL7HEi5GwsY4JW+EBMGYp+Xar6dIQT3YM4+ICsJLgQoJDh08pC6RKnUq61KjRoyk/v36Wvv2TDDn3XLly1Ofvg+vVbZUaRLjqbukwtP8ud3yLjKNQ6x06xrqaWnlqtWUjMPGSJJwBBKWwNdUtlw5+qZfP+s0ER3t4HATvibxTpEsWWi4iPDOPXX6FF2+dMmqZn4fz587b3nkkPApql0eo3ipiRotqjpHhBf169SlzZs3qf1gfT90h0aOHkMFCxVUu06hiuSAL0ICf7/3qgP8XzDe2WAxMu+V+feE7qu59UdI4O/3y96vQJmb40AeBEAABEAABEAABEAABEAABEAABEAABEDg0RGAkODRsQ7oSnbDvC9CAvnht3Wbti4/eh8+HPrDvbtOFShYkEYZhhaJES4/UHqb7P0V17eVqlS2Tp/Mbpy/7N7d2jczvgoJnIyAuj27S+vwhATi4rxmrVrq9L///pve51jF//zzj9p/nEICf/s1c/Ycypgpo+r/6VOnqCaHODh5ItSYrBnJVoxFEydPtoqchATi1WDg4MGqjoR7yJMrp1VfZ3wVEvg7Ln09T1uJYSxjeonFJJLmzJ5NHT7/3DrF/iO3v32xG7RMo5J1MSMjHge2sqt8HVt6xvTp1OWB2GXGzJmW8VuM9kWLFDHODJvt268/lSlXVh0QcYvEqNex1e1CAqf3RAxiHXglbu26da3GSxYrTjI/5C9QgEaz63advh0ylIZ9O9RqX5fLdjLHis+eI7sqCk9I4NQP3Zav76vMS+5SJ2aqx+WPkEDaFW8MpcuWUZf44/ffqWrl0DnMFyGBxHr/pm8/ihkrpmpn6OAh9O3QISov/wUiJJC5SgQhkjZu3OgilFGFDv9ly56dprAbc506dehIs2aGelowjeRXLl+h7CyA8JRM4961a9foncyZrepPkpBAOmUa4d25Rrc67yFjMgqWkGAAx5f/oFQpdVVPRnlzDDKXyZxmT8GYp3Wb0aJFoy3bficRVeikV+qb3go89VmfZ9/a59+eX31NM2fPsoQ7Ut+TKCuY824+9pj0/fjxVhfbtebQCgvmW/v2jLh5X7FqlVVsGrbN50O+KeKNyddk//vJfEd9bcvf+vb74zTXpkiZUglrRLwnyRReBuv7ofu/ZNlyy0tWNw6PNW3qVH3I2voiJPD3e68vFox3NliMvLlXut++CgkC+X7Z+xUocz0GbEEABEAABEAABEAABEAABEAABEAABEAABB4tAQgJHi1vv69m/2HZFyGBrMb8nVfP6ZVjdhe2Tp2yx9atWqkS/fHHH05VHcvs/TUrHT50mMqzce7WrVtmsZV/XEKCa1evUqwXX7IMvC3YLfDSJYutfj0uIYG//XrppZd4Rfk2a1W+uAIWl8BO6TM2SjVo2NA65CQkEM8G4uFA0nZ+nipXrGDV1xlfhAT+jktfy5uteNUQ7xqSZBVyuTKlrdPMH7kD6YvdoCUr5cuWLu0SIsC6KGfEYChGCJ26d+1GU6eEiji6dutONWrVVIfu3btPEqrAnehH3pPFS5eSdjEu7vLLlw0VFUgD3ggJpF6ChAlpPRuhddLx4Nuzgbleg/qq2FPcdlmdumnLVmt+eZqEBP0HDKBS7I1FksnXFyGBGORjxwkVs+zds5cqsjeCO3fuqDblv0CEBMI9zstxVFs7d+zgtstb7brL1GHRiOmlRTxQiCcKSaYRVPZrslcH04uHlOkkxkPhoMNemCE6pE4whQQbN2ykenVq60uH2ZqCBnd1JbREDvbYIenkiZNU7L0iLvchTKNuCkxGwRISmM9TP14ZP3rkSMereyMkCMY8rS/eqk0b+sTwirJsyVJq3qypOuzJaKvP97Q151+9ijozC1EmTJ5COvyRiKOaftrEUcQYzHk3foIE9DM/Y9qDzcIFC6gNeydylz5k7zad2ZitU+NGH9OqVT+p3Y6dOlMd9rakk6e/nV5mrycSL14EG3d5rBIG4dTJkyTlG3/9zZpT5f0UzwZ2D0DWNfgbJy7yRRgm76s8l4Emp/vj1Gav3n2oQqWK6pC8V4ULFlD5YH0/pDEJy7J95y4STzqSzDlLFTz4z9MzaY4nkO+9vl4w3tlgMTLHpt8l3U/71lchQSDfL7NfwWBuHwv2QQAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEHg0BCAkeDeeAr2I3zDsJCRInSUKyGlFWMsuPiXfv3rWuO2fePEr/9ttq//r161SBXedKLGanJPGkp0ybbrnnlTjauXLmoCvsBt/bZO+vPk/aqlK5EonRy116XEICsz+mwUSXPy4hgb6+bH3plxh4N4uQIGoU1cQAXr0+csRwszmVf51d0k9i18wJEyW0jtmFBOLVYhEbkbTr6R/nL6C2bcIaWnwRElgX44wv4xKji/yIH+vBCu/Zs2ZTv77fmM1Z+aHDhlEx9iohaRu76a9etap1zPyR2yrkjC99kfPsBi0pcxfjW1ayzp0/n15kkYdOsmJVVupJsnuGEJfflSqUpxs3bujq1nbw0KFUomRJa99+f70VEpRi0UP/gQOtdmQF4orly+mz9u2pwQO33DJn5MyWjSREhD3ZRShPi5BAVvovXrrMMpSbq65NI1J/9jYwauQIC4uduz4gc58Y+vfs2a2L1DYQIYFpQJfGWjRtRkuXLnFp39yRMYmXjtQc+kOn9woVouPHj6td00guBWfPnFWir4sXL+rqaisGyzEc/z3vuw9XW/fp1cvFgBmokGAEh6wpVKSwut6xY8eoaOHQvEtHHux4U7cWh3D4omtX6/T5/E38nMOCOBlnJVRKazaiS7p77y4JI/FQI8lkFAwhQcqUqWgxh9DQHkpqs5eJ3379VV3L/l94QoJgzdNyXRFdTJg02TKuyxxVmv++0H8HeDLa2vvttG/Ov6bxU76zg3hu0zxu3bpNdVhcZRcyBnPelf6ZXlVk350XAPk7Sp4BEWhKEk8cuXPkIO2pIUvWrDT9h1APH3JcuEloKP38SJlOJkMpM721jB0/gfLmy6ur8jfOWWCSMVMmmjZ9hmVk9yYkjtWoh4y7+2M/xZwL9+3dR2VKfaCqBOv7IY3ZxXdOf/9KPZOnOV/LMXM8sq+Tr997OS9Y72ywGJljM98lPUZz66uQQJ/rz/fL7JduR7b+MDfPRx4EQAAEQAAEQAAEQAAEQAAEQAAEQAAEQODREoCQ4NHy9vtqdsO8/YdU+WFzEa9OtlbU/fgjtWnVyrpeZY65/FXPnta+xH7++qsvaQEbNXVMa1n1lZ1/EBd33gkSJrDqzps7VxlcrAIvMvb+6lNMF8C6zL593EICCWUgK8HtxrPHLSTwp1+mq3wxBstq85/XrVOr5cUgn4tDGvRjI7J2TazvhSkkiB0nDtWvX58af/qpPkyd2R3+TMNYog/4IyTwZ1yT2a2xPKs6iZtvMRxoo6AYOmvUrOliOJzKK127d3toSHT6kdufvjgZtKRf49jQOnvWTDpw4ACvsI2hDHMSnz5tunS627T6p1X0yceNrH15B+fMm09vcIgCnWQ16sgRI0hip4s4SM6vwoIIWRWrkxizShYrRufOndNF4XokCAmJSbnz5KZWvPJW90lWAOfMno2uXLlCdnff4rZ6MHtS0AIkWcXbpm1bqlAxdDWqvvDTICSQsBg92NV6howZ9LBUWAztSt40nnkrJPju228VP6vBB5lAhASFOfTFcNvqdZmvB7EnhdOnT1uXev755/l+vqu+AfETxLfKxWAthmudTCO5LpPnTuZtCQ8hQhJ5x8Uob4aqkbml2Hvv0YXz5/VpAXskML1zSKPCT8KAaNGNdSHOeFNXVnkvXbHCZa4T7zxT2BvIgf371dwhYUcacbiMhg0bWavBxYtE2dKhYQfkmiajQIUEMgePHD2a8r37rhqO9EPmXnfJk5AgmPN0HGa1YOFCJZKSvoh3lLq1P3QROHgy2rrrv1luzr924+dHjRqRzJU6ybxcjcOK/PXXX7rIUcAlB/2Zd+W84vzNH8LPmE4iDBAvPqs5hIHMqyLMy50nD4nHgcRJEutq9D2HgPqmd29rX8QcIiTInCWLVbaVPYdIOJNNv/2mvGCkYSFPBfbyVJ+9gej0C3uFqWvM6eJhYMzYsfowydz8TZ/etIz/zjvFYYrkWc2RIyd1+uILS/QplWuwd4KtW7ZY5/mb8XR/pM1UqVJRaRZImH8XmO9DML4f8j18++0M1IdFFClSplBDEXFTfkNgYY7P0zNpjkef48/3PpjvbDAYyVjMsdnfJT1WvfVXSODP98vsl76+P8z1udiCAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAg8HgIQEjwe7j5f1W6YtwsJ2rRtxwaQj6127/x3h7LxyribN0NXMssPsmJwKlCwoFVHMvLjtPw4f5vDDKRMncpaZacriRGlIbs3Nw2U+pinrb2/UvdPjgtfvWoVF08JTm08biFB65YtaREbUezJFyGBrD7UKzft7byeIvQHcV0uRrjzbKjYxgY7MYhLcvoB1p9+NeXwDM1aNNeXUlsRjojXimTJX7NcSLtU4B0tJGj48cfUllftmknG9j6v8pcfhO3JHyGBP+MqVKgwjRg9yuXyV9n4ffjwEWVofJMN8clfe806LgZQGZM2gsuBYDF2JySwLs4ZYa5XsOpyWWkrgpUTJ0JXg+tyMWLPmjPXMmTqcmkjevQXrJW6uly2TsIO+8p4+zMpHkzMOOfSzq+//EJ12EgsSforoTG0m3FVyP+dZiPW/fvkYkzTx2QbGYUEJhsRSIjR0EwSykPmLh2SwFchwf59+5QXGCePDoEICaSPA9nIL55o7ElcUsvcHhISg1KkSGmtWNb15Hn6sEZN+vPPhyFrTCO5rqe3Yli1Py/6mMS3nzB+nN5V20A9EtRkgUOXbt1c2pTn7p+/L1J7nifXrlljHfO2rt37hm5Avpcyn4nHBu3BRY7J97FRw4+U+ErXNRmZhlN93NutCPvy5y9Aif6XyOUUc45yOcA75rfjxvUb/F0+q1bCL1m8mFoYwkE5L5B5+tvvvqOiLEzSaTR7h7B7ffFktNXnedqa86+T8fPLr79WgindhnilEDGBFvgFe96V6wz5dhgLCorrS1pbWY0tSYeH0geO8vtVhkPV2MM0iZF9LodHsM/5cs9u/3vbRcwibclzXb9uHdq4YYNuWm1NRuYBMabHjx8/TH9EsFCj2kOvO+Y5vubt1zafy5gxY7l4MJK2ZQy1a9agTSw4khTo90P+Zt2y7XeK+cDzkGqU/xs6eIgSZeh9c+vpmbSPR87z9W+PYL6zlVmAJ8LHYHxj7WMz75XJR/LmHCL7uq6w2MXhkex/N0gdf79f9n5JW74yl3OQQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEHi8BCAkeL3+vr243zNuFBHZDyoXzF9SqLTO8gazSFmODGfPYUwfkx/02rVpbYgRPde3H7P29dfMWleX49PpHS3t9c/9xCgk8uQX2RUhgjsfbvAgtqjyINWz/Adbffsmq8wGDBpKsXPaUhnMIAHNloRYStOTV6o3Z1b2ZmrFnguXsitsp+Sok8HdcsuqzWfPm/Cw3CWNMsfdLjC7SZ9NgKnWCxdhu0Or1dU9q+1k7klXg7pIYlJo0/oRkFapTkhXKg4cMcQmB4FRPVgr35VWqYtC0JyeDgL2OuS99ad60qfJGoMvzFyhAg7gfsvrVXRJ31idPnrCescgoJHA3NinfsX2Higsvsct18kVIIEbIyvxe79q5U5/usg1USCDvgoSgaN2mbbjvgr6wPH/isUbHddflppF8+rRplCVLVkr3Rjp92HErK8D79O6ljGJmhUCFBCEhIbRw8RJKmiyp2azK24UzvtStUbMWdeZV3HajsP0ict/atW0TRlRmMgpESGC2Y7+2r/vSD3N1u5zv7zxdlVe09/jqK6sL8txW4dXzWkSjD3gy2uo6nrbm/OskJBBD8mgJn2GsPpd38UM2Vt+8eTOMR4JgzLvyvfymX18rHI6n/su8J95kzHnBrJ89ew4SQcbLcV82i8PkxZtMOw6jseqnn8IcE+GOvEdlypUNc8xeIN5tJNyQtBeMZN6f8NoTUZyECVm6ZLFL1UC+H3L/d7MAy0y72dAtBnj7s6jreHom7ePx52+PYL6z2VnoK55/AmGkx20fmy73ZfsRe50Sb1X2vxsC+X7Z++UPc1/GgLogAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIRQwBCgojhGvRWs7Cb3OkzZ6p2ZZVkoQL5XVw8i6FP4mVLPYmn3pvjVYtbdaf0XtGiaqWfuIyNFz9emCrHjx2nHzgG8Bhe8X3v3r0wx70pMPsr9bt37UZT2YW0N0ncwG/iePZ6NV9Ddv8r7t11SpAwIa37eb1aOSqr4EoUK+pWoCCihN/Yza9eSVupQgU2DG7XTbm4xL586TK9X7KEi3tuqyJn5Aff0Q8MtvJj/TuZM1uHzT5ZhT5mTCGB6ao7kH5JF7SApFSp0mGMcnt27aY+bIg+cvgwrV67TjEV43Tx94qQrADVQgLhvGvnDhUKY8L48W5HJqvdxMgq8a3lOc3PMdTF24JOwRyXtCmr9z/v0IHezpAhzCrykydOcjiArdSLV/5euHBBd8HaBqsvdiFBxfLl6drVqyTxjwsVLmKFG5ELCxOJYT+MY4AfPHjQ6otTJlmyZGwc/IjKli8XxpAv7axcuYLGjR1L2/hdcUrhPZPSxmG+72KcEZHFNA4XYQqPdJtvvPEmde7ShTIw4xi8ul0nWdE+acIEGjF8OLXjFeJigJRkD4Vi9iOY7+tCXvHbhoUu7lIrNs5p0ZSsXm/ExnadzD7pMr0Vw8nx48foyJG/2LvCEjUeHTJD15nLIWHeSp9e7Yr780kTJ+pDHBbm4fwkhcJnYP/+1nF7ZubsOZQxU0ZVLMbQ8eMeujO31/W0ny17dp7PullhKpzqSkibFcuXU382ljqFCDANZWKcHsKhLOozNxEq2MUk4q1mxPDvSFbDOyVxuV67Th11SOq0ZOGPTqlTp6aFS5aqeUJ45+MQG7KC3p4SMstKvAq9UuUqbOBKolbfy1xuFxLIeb7UlbjyMqaiRYuFERSIUfTHBfNpxHfDw3gLkeuMHTee8vK8JkneYwn74E8yWftzvnmOFhIEOk+LKGXd+g3WanNPAsD+HD6jFLu2l2QPG2P2zV3enH/dvcviGUTC86TmUAA69WM396PZu1JEzbvCoGy5chy+o7ZLaBN9/VMnT6m5cvKkiepvLV3utE2cODHV479fJPyL3cvJGQ49spNFGhIa5fDhQ06nW2UfsNeDOnXrUabMmawyycgcvH/fflq2bCmNGTUqjJjHpbKPO+b9cTpV/i7ZvXsX/9tNiziUlqxmd0r+fj9MIYF4V1m3dg0N5ffNkxjV0zNpjsffv6mC+c5qIYEw85eR5m2OTZf5utVCgmB+v8x++cvc13GgPgiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAQPAJQEgQfKaPtUUxnIvx3+5q112nxPjyJhtjlQGRrRCnTp0O90dtd22h3HcC1WvUoG5siJRkCgl8b8m7M1555RU2uCWikJghJG6ZnYx3ZktitBODjYRf0C6lzeNPUl6MNuKWXmIYi5HeXWiJYPfZyaC1c8cOdRkRxaRMmZKeY28gYrg/duyoYulLH8SzgYxNfuCX91rCkJzk1fEiGHqUSQw7adOmU8II6YcITZxc9T/KPuFaYQkkTZqMsvJqVxGJybz+4ksv0d8XL7JR/ARtZrfjYnx0l0xDmbnaXsRISZMmZTHBi8rV+HG+976Gu3F3TW/KxbgbN25ctRJZnkOnsCq6HV/qygr0JBzvPgZ7P5C5TlaXnz17NqgGWd0v+9ZkLSvSF7A4xdsUO3Zs2syhcHTKmysXybc/MszTus+BbiN63pX+yTMnIWAkyfMhYV3kufdVYClz+Kuvvqrm8Js8b59mEYGnZ1hd0OE/sz8y9x7i75yT+Mvh1Mde5M/3Q4eZkfnmSRhnMN9ZU0igb44/jPS5/mw3/vqbJSbWQgJ/2sE5IAACIAACIAACIAACIAACIAACIAACIAACTzcBCAme7vuL0T3hBB61kOAJxxEpu+fJoBUpB4ROP7METEOZKSR4ZoFE4MBN1oEKCZyMkhHY9Seiacy7T8RteKY68bS9sxASPFOPLwYLAiAAAiAAAiAAAiAAAiAAAiAAAiAAAn4TgJDAb3Q4EQQCJwAhQeAMH3cLMGg97juA6weLgGkog5AgWFSd2zFZQ0jgzMhTKeZdT3RwLCIIPG3vLIQEEfGUoE0QAAEQAAEQAAEQAAEQAAEQAAEQAAEQePoIQEjw9N1TjCgSEYCQIBLdLDddhUHLDRgURzoCpqEMQoKIvX0mawgJfGeNedd3ZjgjMAJP2zsLIUFgzwPOBgEQAAEQAAEQAAEQAAEQAAEQAAEQAIFnhQCEBM/KncY4n0gCyZMnp/eKFlV9O3r0KP20cuUT2U90yj0BGLTcs8GRyEXANJRBSBCx965w4SL0eorX1UWWLllCp06d8vqCEktdRGjRo0enf//9l6ZNnfpExJD3egBBqIh5NwgQ0YRPBJ62d7ZsuXIUL148xWDe3Ln0999/+8QDlUEABEAABEAABEAABEAABEAABEAABEAABJ4NAhASPBv3GaMEARCIIAIhITGp0xedSYx7dJ+oX99v6OLFixF0NTQLAhFHoE7duvTGm2+qCyxfuoxWr14VcRdDyyAQAAHMuwHAw6kgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIg4CUBCAm8BIVqIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACIPAsEICQ4Fm4yxgjCIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACHhJAEICL0GhGgiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAg8CwQgJHgW7jLGCAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAJeEoCQwEtQqAYCIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACzwIBCAmehbuMMYIACIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACICAlwQgJPASFKqBAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAwLNAAEKCZ+EuY4wgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIg4CUBCAm8BIVqIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACIPAsEICQ4Fm4yxgjCIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACHhJAEICL0GhGgiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAg8CwQgJHgW7jLGCAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAJeEoCQwEtQqAYCIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACzwIBCAmehbuMMYIACIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACICAlwQgJPASFKqBAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAwLNAAEKCZ+EuY4wgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIg4CUBCAm8BIVqIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACIAAC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CdzlidxAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAIEoTgJAgSl8+FB4EQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAE3CUAIYG7PJEbCIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACERpAhASROnLh8KDAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAgLsEICRwlydyAwEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAIEoTQBCgih9+VB4EAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEHCXAIQE7vJEbiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAQpQlASBClLx8KDwIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAALuEoCQwF2eyA0EQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEojQBCAmi9OVD4UEABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEDAXQIQErjLE7mBAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAQJQmACFBlL58KDwIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIuEsAQgJ3eSI3EAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEIjSBCAkiNKXD4UHARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAAXcJQEjgLk/kBgIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAJRmgCEBFH68qHwIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACIOAuAQgJ3OWJ3EAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAgShOAkCBKXz4UHgRAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAATcJQAhgbs8kRsIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIRGkCEBJE6cuHwoMACIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACICAuwQgJHCXJ3IDARCIIgRix45NuXPnphw5c9LkSZPozz//jCIlRzFBAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAIGIJRBshQcyYMalAwYL0Zo4clDx5cooTJw6dP3+eDh86RIsWLaJrV69GLOkAcn/iiScoTZo09FzSpBQ3blw6eeKE7dF/XrlCV8UfAgiAgD2BGDFi0KuvvUb58uWnVKlT0dNPP03PPJOQUqRIQSleTEFPPfWUPPDdatVox44d9pkgFgRAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAASiGYFoISR49dVXaeSYMVJAYHd9//nnH+rWpSvNmvmD3e5HHvdWnjw0eepUR+e9eeMmrV69iqZMnkxbt2xxdAwSgcDjToDFOK0//phq1apNiRIn8ltdCAn8IkICEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEACBaETgsRcSZH/zTRr37bf0dIIEfi9r/379aNzYsX7TRXSCfPnz0wRhaj2QcO/uPWrX9hNauGBBIIchLQg8dgSefPJJ6iue5XLlyzuq29YtW6lNq5bSQomjA5AIBEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABB5zAo+1kCB79uxiQH4yxY0X17qMvx08KGbub6V79+9Rjhw5KVPmTNY+XunetSt959AagMeBLm7YCQluXL9hnSFmrJiWSXYrUqywmKB9u7a0YP58PRrrIBBtCLALk3HjJ1C+/Pk86nz+3HnasmUzHTxwgK5fv04XLlykU6dO0gnhNiQyuTXxKDQ2QAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQOA/IvBYCwlYRKAPKA4bMpSGDxvqgbqNMH/e/KOPrLhzZ89R0cKF6O7du1bco14xhQSlihen48ePexTjuaRJKUOGDPThRy0oV+5c1j4W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/dPcuZQ5y6v0RJgRAusko0aMoMFff21tYwUEQMCeAIQE9lwQCwIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAJRhQCEBFHlSqGcIGBDoGKlSpQkSRK5Z85PP9Hly5dtUiEqUAKr1qylFC+m8DhsxvTp1L1rV7p7965HPDZAAATCE0iUKBFVqVpV7jh37hwtXLAgfCIfMVmzZaOcOXPKFFu2bKFdO3f6SP1odqG9fTSccRYQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAIHIQQBCgshxHVAKEACBSERg6rTplCBBArp/7x4dOnSI1q9fR3PnzIlEJURRQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQCDiCEBIEHFskTMIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIRDkCEBJEuUuGAoMACIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACIBAxBGAkCDi2CJnEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEIhyBCAkiHKXDAUGARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAgYgjACFBxLFFziAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAQ5QhASBDlLhkKDAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIRRwBCgohji5xBAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAIMoRgJAgyl0yFBgEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEIo4AhAQRxxY5gwAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgECUIwAhQZS7ZCgwCIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACIAACEQcAQgJIo4tcgYBEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEACBKEcAQoIod8lQYBAAARAAARAAARAAARAA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18amT3YSi3Znn3OOOeXUU80BBxxgdimxi/lm0zfm888+MzNnzjTfbNyYLIuMzp940knmhBNONAcffLA58KADbR5fffmVWb9+vVm1cqV59913MsoXF+21116mUuXK5pRTTjF77rmX2WXXXcz++5ey99pn331svvNff920bd0643vohYqAIpAbBNq0bWtu6d7dZj5zxgxzQ8eOubnRPzxXbSf/4RVAH18RcBDoSos3V7Zvb48uWbzEXN6sqZNCf/5TEMDc4A2qA3uU3MP89edfpl7dOuaD1av/KY+vz6kIFCkEsMB+0EF5BNvff/89JWIPxnglS5YMnmPTpk3m559/Dn4X5g7m5KPHjrW33PK/LaZypTMTiGr3Dx1qLqpZ056f9uxzpmuXzoVZPL2XIqAIKAKKgCKgCCgCioAioAgoAopAGggokSANsLZV0jMrVSJG/yBT6oBSQRGqV6lq1q3LjEwA8sDQBx8y++2fR0wIMs3fgfJwxIjh5r4hQ9xTGf+uW6+euaJlS3PCiSfG5gEywdgxY8yzzzxj/v7779i0fPLoo482/QbcbfPeaad/8eHQrRIJQmHRg4rANkVgXyJJzZ4zx+y222703RtzWYP6ZsXy5du0TDvazbWd3NHeqD6PIuAHARC4QOSCvLVsmWnauLGfjDWX7RKBa6+7znS68UZbdiWWbJevUAu9gyDQtFlz06tPb/s0//vf/8wJ5crFPtmRpUub8RMmWGI9J7xnwADz6COP8M9C3RYvXtwseest63EANx47erTp17evnd+XL3+yGfvkk6Z4ieK2TLd262amTJ5cqOXTmykCioAioAgoAoqAIqAIKAKKgCKgCKSOgBIJUseq0FMWK1bM3HDjTaZtu3bGXSC/sHp18/nnn6ddJrgZfPChh4OJe1wGUydPMbf1uNXAA0Km8p///IcW+QeYWrVrp5UFLJJv6drV/PHHH7HXVahY0QwbPsJaT8UmpJM//fijuf+++8wT+dYRydLreUVAESgcBPrffbep36CBvdkzU6eqe1PPsGs76RlQzU4R2IEQUCLBDvQyPTwKxu0zZ79oDj7kYJvbjZ06aUgwD7hqFopAughc3qKFuf2OO+xlIPmXO/aYyCz2239/M2HipOC7RcJZ5GEQ3+9ff/0VeV2uT9za4zbTsnWr4DbwfPgDhc857vjjKYTKv+3xL0if0ahh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n7WTAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQQFwRoCJBXN0ONoYESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAEUpYAFQlSlj9rJwESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIIG4IkBFgri6HWwMCZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACaQsASoSpCx/1k4CJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACcUWAigRxdTvYGBIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARJIWQJUJEhZ/qydBEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABOKKABUJ4up2sDEkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkkLIEqEiQsvxZOwmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAnEFQEqEsTV7WBjSIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESCBlCVCRIGX5s3YSIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESiCsCVCSIq9vBxpAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZBAyhKgIkHK8mftJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJBBXBKhIEFe3g40hARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIggZQlQEWClOXP2kmABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEggrghQkSCubgcbQwIkQAIkQAIkQAIkoAlcdtllUqxYMbmnSBGZPm2a/Pnnn/oUtyRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAjEkQEWCGML1sugMGTJI5syZnSJPnz4te/fujbr49OnTyx133Cl3FrhT0qvyt33/vWzdskV++eWXqMs2C8iaNaukS5fOjApr36vrDKsyJiIBEiABEiABEkgRAqlSpZI77rxTSpYsJTlz5ZQrrrhCrroqo2TLlk2yZc8ml19+udOuBvXqyddff50ibWSlJEACJEACJEACJEACJEACJEACJEACJEACJEACJHCxEaAiwQVwx4uXKCGDBg+RrNdndVtbsVx52bcvacoE9xYvLt179JA8eW+RVKkuccvUO1jtN+/NufLqsKFy5swZHZ3k7Ruz50iRokWSlD+a60xShcxEAiRAAiRAAiSQLAQuueQSad+xozRq1FgyXZ0pZJ1UJAiJiAlIgARIgARIgARIgARIgARIgARIgARIgARIgARIwDMCVCTwDKX3BaVJk0Y6dOwkLZ94IpHAv3LFirJ79+6IK23UuImjRJA6TeqQedeuWSOdOnSQI0eOhEwbLMHb77wj+fLnD5Yk4LmkXmfAAnmCBEiABEiABEggxQlceuml8sqgQVKjZs2w2vLlF19Kh3Zt5cCBA2GlZyISIAESIAESIAESIAESIAESIAESIAESIAESIAESIIHoCFCRIDp+Mct94403ytBXX5MCBQv4rSMpAvZmzVvICy+96FPe/l9/le+2fifHTxyXvHnzqj9lpSB1KjfNN8qEcH1lSjiasGbdOrlBmSdGOHPmrMhZ9RcgmHUjSVKuM0DRjCYBEiABEiABEogDAqlTp5aJk6dIyVIlfVpz4LcD8sUXnzuulo4ePSoHD/7uWF/as2ePHP7rL5+0PCABEiABEiABEiABEiABEiABEiABEiABEiABEiABEogtASoSxJZvkkqvU7eushrQU9JnSB8wf1IE7O99sNrxPYxC//nnH+nft5/MnvWGTx3FihWTUWPGylUZr3Ljmz76qHzy8cfucaQ7XyplBPg7RujaubMsWbw4YBElS5WSKdOmueeTcp1uZu6QAAmQAAmQAAnEHYG69R6W/q8McNv16SefyEsvvCB79ybNZZNbEHdIgARIgARIgARIgARIgARIgARIgARIgARIgARIgAQ8I0BFAs9QelPQPUWKyKw5c3wKW//hh7J44SIZPGyoGx+pgL3w3XfLnLlz3fydlU/ipW+/7R6bO+XLV5CxE8a7UcOGDJVxY8e4x5HspEqVSrZu+0GUG2QntHriSVm9+oOARVCRICAaniABEiABEiCB/wSBle++KzfdfLNzLd98/Y00e+xROXbs2H/i2ngRJEACJEACJEACJEACJEACJEACJEACJEACJEACJPBfIUBFgji7k8XuvVdmvHHOSsC///4rQwcPlqlTpkiJkiWjWqn//AsvSvOWLZyr/fPPP6V0iRJy6tQpv1efPn162bhpk3tu5owZ8nLv3u5xJDtXXXWVfP7VV26Wxg0bypdffOEe2ztJUSQoUrSolClTVrJnzybXXnedHD78t/z6yy+yUdX73nvvBrxO+Gdu2KiRpE6dxm5G0ONTp/5VlhxmyenTpwOmS2qbzALhiiLcsGnTN/LVl1+6yc1rO6tcScx98005fty/kAYmphs0bCSXXXaZk3/Bgrd8TEjffc89UrBgIefctm3fy8cffeTWY+8ULFRI7r77Hif66NEjMi9BeeWOO++UokWL2clDHn/33VbBSlU7ZMqUSWo/+KDkUe44sim3GadOnZZffvlZtmzeLO8sXSonTpyws/gcFy5cWArdVdgnLtDB6dOnZM7s2YLnUYdImOg8euvlvdFl6q0X/U6XFWgbLTu73KS22eRol+nv2OzXkdy/QH0adaRJk0YaNW7svEPwnM2fN1dgjv3m3LmlcuUqjruY6zJnVubZD8jOHTtk0cKF8qtyJ2OH+g0aSPr0GZzorzd+JV8rKy7BQrXq1SVr1uudJF4/IzbXI0eOONcVrD3mubLlysnNN+d2okwmZppg+3b9dtozZ047DHf89JP8pP78Ba/ui7+yk/rusa8r1lyzZs0qdxYoIDly5FSKfAmafMYF2d8M41RYu1dlzCg1a9aUW27JJ9ffcINcemka+W3/b7Jnz27nHRzMsgAUCKBIgPD34cNSoXx555sDRnDrhPIOq/hf1Hf894MH/bbnlnz5pFSp0s65s2fPyIzp05XrpDN+0yIS34nKVaq65+fMnuV8Jx6qU0cyZszkxK9atVJ++flnN425ky9/filZspQT9fPP++TdVavM04n2i6sxVunS9zlWoK66KqNzHfv27ZUVK1Y4bhsSZVARZh+Jh++2vzYiLtZ9K1C9jCcBEiABEiABEiABEiABEiABEiABEiABEiABEkh+AlQkSH7mQWvUigS7d+2Sju3by5YtW5z0SRGwmxVBsFVACRUQ9itB1meffWae9tm/TgnjNxjCW7hAmDZ1ik+acA+yZc8u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eMCCeYIESIAEPCDgpcC6jnrXDRg40KdVBw8clDatW8mmb77xiedBcAJe3pfgNfFsShNYvXadZMuezacZc2bPlj69esnp06d94nlAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiQQGQEqEkTGi6lJgARIgARIwCGQLl16eal7N0mdOrXIWZEhgwfJH3/8kSQ691eqJC916y6HDv0pf/7vT/nhhx9k6pTJsn///iSVdzFn8vK+XMwcL4Rrnzlrtlx55ZVy9swZ+fHHH2X9+g9l8aJFF0LT2UYSIAESIAESIAESIAESIAESIAESIAESIAESIAESiHsCVCSI+1vEBpIACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZBA8hGgIkHysWZNJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJBD3BKhIEPe3iA0kARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIggeQjQEWC5GPNmkiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEgg7glQkSDubxEbSAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQALJR4CKBMnHmjWRAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQQNwToCJB3N8iNpAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAEko8AFQmSjzVrIgESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIIG4J0BFgri/RWwgCZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACSQfASoSJB9r1kQCJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACcU+AigRxf4vYQBIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARJIPgJUJEg+1qyJBEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABOKeABUJ4v4WsYEkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkkHwEqEiQfKxZEwmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAnEPQEqEsT9LWIDSYAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESCD5CFCRIPlYsyYSIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESiHsCVCSI+1vEBpIACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZBA8hGgIkHysWZNJEACJEACJEACBoFb8uWTs2fOyPbt243Yc7t33Hmn/H34sOzZsyfROUaQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAnElgAVCWLL17PSM2TIIJkzZ3bKO336tOzdu9ezsnVB2XPkkEvTpNGHcuTIEfn999/dYy92UqdOLfeVKSO5c+eWHDlzynXXZZYjf/8tBw78Jt9/972sWbNaTpw44UVVLIMESIAESCCOCfTp21caNGzotHDKpMnyyoD+bmvHjBsnFSpWlLNnRYYMHiQTx493z3GHBEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEgg9gSoSBB7xlHXULxECRk0eIhkvT6rW1bFcuVl3z7vlAmK3XuvTJ/5hlxyiVuF7Nq5U6pUqnQ+Ioq9yy+/XB597DHn7/obbghY0vFjx2XK5EkycsQIOaNWqTKQAAmQAAn89whcdtllsvGbTZLm0nPKayeOn5BCBe50LjRLlizy4UcfuRe9b+8+qVi+nHvMHRIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIggdgToCJB7BknuYY0yjpAh46dpOUTT0iqVIaEX5VYWa3U3L17d5LLNjOmS5de3n7nHcmZK6cZ7ZiTrlShgk9cUg4yZsokY8aOk3uK3BN29rVr1kjnjh3lb2WtgIEESIAESOC/ReASpbW26r33JNeNNzoXBos0tWvWcPaheLZu/QbJdHUm53j9hx9Ky+bN/1sAeDUkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkEOcEqEgQpzfoRiVcGfrqa1KgYAG/LfRSkaBbjx6OpQC7IviljlaR4KqrrpI3582X3Hly+xR/+K/DsnHjV/LHH39Irlw3yh133CHp0qfzSbN71y556sknZeeOHT7xPCABEiABErjwCRQuXFgaNWniXMi0qVNly+bN7kWVLl1aaj/4kJw+c1rGK0W0HTt+cs9xhwRIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIIPYEqEgQe8YR11Cnbl3p3qOnpM+QPmBerxQJCt99t8x+c66PSwNdqReKBL1fflkaNmqki5RT/56SEcOHy8wZ0+Xo0aNufKpUqeTBhx6Snr16S9p0ad3477ZslToPPUg3By4R7pAACZAACZAAh0lPLgAAQABJREFUCZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZBAbAlQkSC2fCMu/Z4iRWTWnDk++WDWefHCRTJ42FA33gtFAvioXrTkbcmTN49T7v+UdYBFixZJi5YtneNoFQmw2nT23HmuksI///wjbZ9+WtasXu1eh71zyy23yNjxEyRHzhzuqeeffVYWLljgHnOHBEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEggdgSoSBA7tkkqudi998qMN95w8v77778ydPBgmTplipQoWVKmTJvmlumFIkH7Dh2kzTPPuGW2b9tOuRnIJZ27dnHiolUkGDJ0mNSsXcstf/CgQTJx/Hj3ONDO/ZUqyagxY9zTa1avkaeeeNw9tneKFC0qZcqUlezZs8m1110nhw//Lb/+8ots/Ooree+9d+XUqVN2Fsf6QaZMVzvxJ0+ekNmzZiVKoyOqVqsu119/vXN4+PBfsuCtt5z9NGnSSKPGjSV16jRy9uxZmT9vrmNl4ebcuaVy5SqSN29euS5zZjl48IDjnmHRwoXy66+/6mIDbuEfvGixYpIvXz7Jlz+/ZM16vezdu0e+27pVtioLDd99t1WglOEv3H3PPVKwYCHn1LZt38vHH33kL5kTd2eBAlKkSFFnf+fOHbJ2zZqAaaF0kv/WW+X222+XdOkSW8o4ffqUzJk9W9BnA4XiJUpI6dL3Sc5cOeWqqzLK7wcPyr59e2XFihWy7fvvA2UT85qQaP2H62T79u0B05snrsqYUerUqetG/fDDNvlowwb3ONTOpZde6ljUwD1GOHLkiHOfQ+XT58uWKyc333zOrYfZR/R5exsJI1jx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WjQ3qyD+gAgknPmFBLH0YZcV9jdq5Eh5ceDAcLuWKVOnSaEbC5lyCBgmTjgRTcBtK4i7bbRO3XrSu28fuygI41+0cGETAcHtN4ioUVnZ792zx6vrzpQqXdpETsE6tHHrTTe5xUnOu/sKqoz9r1u3Vv+tk1madmatRjeJxfLkySPNWzwgNe6pGZIKBm3iXvPhh/NNdIYvv/giyd1E01+SbMypsPrrNZIxU0azpnmTprJ06fF0H04VvUfGdm9w20qt83D3wXkSIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESOJUIUEiQzn5tjPLEaGw3hP/JdopX6Ej3vHnzmRzncCRlzJjRpDjYvOk7TWPwlYb9Vo9rlHbeeedJ7kuO50xHOoWfdVQ7coK7I9OjbDLJ6kFO2G/WrDHbZciQQc8xr5ym5wfH3tatPwpECMk1OKBz5brQOOKQluGXX35J7qYndb0LNJXBxblzm98YTnikbjhw4MBJfcxpfXBpySg1+3BaczsV94doL7n1eoLwAvf8o/pvh0ZegZjsv2Zp2e//a2x4vCRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiTwbxCgkODfoM59pgsCkZyw6eIEeRLpngD7cLr/iXmCJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJJAiAhQSpAgbNyIBCUxtYCMSkA8J/BcIUEjwX/iVeIwkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkkPYEKCRIe+bcYzohQCdsOvkhT+HTYB8+hX98njoJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJRCBAIUEEOCwigUgE6ISNRIdl/wUC7MP/hV+Jx0gCJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACaU+AQoK0Z849phMCGTNmkq7dnpYzzjhD5B+RAf1fkH379qWTs+NpnAoE2IdPhV+Z50gCJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEAC0ROgkCB6ZtyCBEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABNItAQoJ0u1PyxMjARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIggegJUEgQPTNuQQIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQALplgCFBOn2p+WJkQAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkED0BCgkiJ4ZtyABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiCBdEuAQoJ0+9PyxEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEggegIUEkTPjFuQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQQLolQCFBuv1peWIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkED0BCgmiZ8YtSIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESCDdEqCQIN3+tDwxEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEoieAIUE0TPjFiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiSQbglQSJBuf1qeGAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAlET4BCguiZcQsSIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESSLcEKCRItz8tT4wESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAEoidAIUH0zLgFCZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACaRbAhQSpNuflidGAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAtEToJAgembcggRIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgATSLQEKCdLtT8sTIwESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIIHoCVBIED0zbkECJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEAC6ZYAhQTp9qfliZEACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZBA9AQoJIieGbcgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIggXRLgEKCdPvT8sRIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIIHoCFBJEz4xbkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkEC6JUAhQbr9aXliJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJBA9AQoJomfGLUiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEgg3RKgkCDd/rQ8MRL47xC49NI8UrpMafn1119l9qxZ/50D55GSAAmcFAQyZswoF1yQwxzL73/8Lvt/++2kOC4eBAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAn8VwmcUkKCDBkySP4CBaRQoUKSJUtW+fbbjfLNmjXy008/nVS/32mnnSaXX3655MiZU+Ac2bZ1a+Dx/aqOEjpLAtFw5UlI4LzzzpNid9whN998s2TPfp5kypxJcuW6UC655BK5IMcF5oiXfPKJtGjW7CQ8eh4SCZDAyUygY6dO8kDLluYQV3y+Qho3bHAyH27gsZ199tmSO3fuwDK78sCBA0Zw9c8//9hV6XaaK1cuyZQpU9Tnt337djl27FjU23EDEiABEiCBUAJ4d8+WLZtZ+fPPP8vRo0dDK3CJBEiABEiABEiABEiABEiABEiABEgg3RM4JYQEV199tfTu208KFiwoZ5x5RqIf9ZdffpHXJk2WEcOHycnwcf72okXl1cmTEx1n0IrDhw7LokULZdKrr8oXK1cGVeE6EvhXCeD669PveSl4ww1y+umnRTwWCgki4mEhCZBAGAJPdu4szVu0MKVffvGFNLj33jA1T97VDRo2kp7P9EryAP/880/ZvWu3rFjxuUyd8o6sXLnipHh3SfLAo6yw/LPP5PwLjovMotm0/eOPy8wZM6LZhHVJgARIgAQCCEx+/Q0pXKSwKenbu49MnDA+oBZXkQAJkAAJkAAJkAAJkAAJkAAJkAAJpGcC6V5IUKJkSRkybJice+65Sf6OCKn+lI5q/LdHW9xRvLhMeOWVJI/XrfD3X39Lh/ZPyKyZM93VnCeBf5XAbYULy8hRoyVrtqxJHsfvOtJ2yODBRhSTZGVWIAESIAGHQHoQEjS+7z7p1qOHc1bJm9303SZ54vF2snHDhuRtkMJal1x6qQwbPlwQNQmiy9atWskOHf2fWvbZipWS/bzsUTffsX17mf7++1Fvxw1IgARIgARCCbzx1ltyy623mpUv9Osn48aODa3AJRIgARIgARIgARIgARIgARIgARIggXRPIF0LCRBGffyEiXL6Gad7P+S+vftk7dpv5I8//hCMlM531dUho6Q/nD9fHnvkEa/+vzETJCQ4dPCQdyiIqnDOOed4y3YGYoKOHdpzJJ4Fwum/SqBkqVIyYuTIkL567H/HzPX3hY4Y3rN7txw8eFD27Nkj33//vWz98Uf566+//tVj5s5JgAT+mwTSo5Dgr2Oh98OgiEr21zpy+Ih069pVpk9PPQc6ospMffddu0upfc89Jj2UtyLOM34hgZ9HuN21bvWYfPThh+GKuZ4ESIAESCCZBCgkSCYoViMBEiABEiABEiABEiABEiABEiCBdEwgXQsJEImgYqVK3s83ZvTL8uKggSHOSkQsGDZ8hMnXbitWrlBBNm/ebBfTfOoXEtxdvrz8qE5W13LkzCnXXHONPPpYKy/kJMohJqhbp3aqftx3j4PzJBBE4IwzzpB58z+UPJflMcUQEHR+6kn5YN48OXLkSNAmXEcCJEACKSaQ3oQEeJbnv/aaEB5nn322XHTRRZI3Xz65p1YtKX/nnXLWWWd5dbBNw/r3yqpVq7x18Zz5N4UEQ158UV4aMSKep8O2SIAESIAEkiBAIUESgFhMAiRAAiRAAiRAAiRAAiRAAiRAAqcAgXQrJMicObMs++xzyZDh+Mj9cWPGyAvPPx/4k9aqXVv6OmWdOnSQ9997L7BuWqxMjpDAPY4uXZ+WJs2aequQM7lL56e8Zc6QQFoTqFqtmgxUxw/s77//kfYadhupQ2gkQAIkkBoETgUhgZ9b3rz5ZOToUXLFlVd6RVu3bpUaVavKoUMnohh5hTHOUEgQI0BuTgIkQAL/MQIUEvzHfjAeLgmQAAmQAAmQAAmQAAmQAAmQAAmkAoF0KySoVLmyDB461ENW4a675AcNnx5kuXPnloUff+wVDejfX8aMHu0tp/VMtEICpDmYNWeuN/p7588/S2mNtBBk2bNnlxo1a2pKh6sE531MQyf/9NMOWfvNNzJr5sxkjRZHnuTChQvLNddea9JDYD/fffutbND8zKu+/FK2bdsWsut6994rmTJlNutWr/pSVq9eHVLuX8Bvd+GFF5nV69evk88+/TSkStNmzUOWIy18/fVX8qWG0beG0Zv1GzSQM8440+R4fltzfx4+HNnhcn3Bgnq+RWwTyZ4GHTs2htPnxhtvNBElkFrj77//kg3r12vI/7Xmd9i5c2eifcT7GBLtIGDFbfoblypVWi65JLdckCOHHDjwu/z800/mN/7ww/nad44FbHV8FUQEEBPAhg8dJsOGDjHzObQdnD9G1v6s/fSnHTvk6NGjpiw5/2XNlk2qabtXX32NXHTxxToa90zZtXOXbN36o+m//r7ntnnzLbco95vMqm+/3SjLli7VfnCGVK5SRQoWvEGuzHulieixbdtWWblypcz/4AP9bf52mwicx/VXuEgR83vimkDfRRvr162TdWvXCfrBn3/+mWhbcChTpqxZv2fPbnP8iSolrLi3fn3JmDGTWZo5c4bs1XQQ1pC7tlChG83ixo0bZPmyZbYo0bSQ9rtbbjme6/bgwT9kyttvh9Rx2wopSFhASpitP/4g6/TcMJ+UpaSvB7VZtFgxKVGipLnHZc2azZz/9u3bZO7cuRHzwvvPZ8knH8umTZuCdpFoHfparVq1vfW2z3grkjHj379/k2h5+rfHMu7pNe+pZYpwLb315hum36KvlypdWq66+irJkiWruXa/++47mTZtqhzYvz+oqZB1119/vRTQf4h8c2XevCYNCfrzunVrTcSbX3/9NaS+XShTtqxcccVxx/aKFZ+bexpG0eN+gvvoZZdfbo5l06bv5OPFi5P9e6AP4Hwuu+wyyaTXwg/aD3HfnDF9hrmHJ1dIkDVrVnMsOK+r9d952c+TzVs2e9crfuekrvucuXLJTTfdJLgvX3ddfo1ydMw8Azdu3Chf6L1jt6ZtSYk1vu8+6dajh9k0KCJBUJtZsmSRd6ZNCxET9O3dRyZOGB9UXVJy/vaZnDdfXsG9yNpbb74pWzZvMYv79u3V32K6LfKmKdmf3dhNbZDSiAT+/rhOn7P43YsWLWru25kzn2v6YRCveF4Dl2u/x7sd+vE552Qw/WXp0iXy6fLl9nTNFCm5btdjQ+otvKfg3Wz16lWmXyUlDsHzDFEqbr31Nrk0z6XmmbFr10758YcfNOXFdPO8DdlZFAvuvSzW50yk3cb7fRH7Qh+sVr26eW+4WN97//nnH73e18qaNWvMvSzc9Zpa7ww4pljfxe/WyGm5c1+CppJlH3wwL9HvH81vChHRbbcVNvv6/vstsnjRorD7xfvdtdddJwUKFPDeW9zKuF+++cYb8r///c9dHTKfXp/5ISfpLCT1t1Es777ObpI1e9ppp5m/8fIXuF6f15eZd+Tzzz/fbLvm6zWyZMknsmP7dlm6ZIn+/fhTstq0leJxHrH8DWiPA8/wKlWq2sUkp0m9o6f07wDs2L0OsZzW76nYJ40ESIAESIAESIAESIAESIAESIAEkiKQboUE+EgHpwMMzpV56nAKZ/ioPHP2bK+4bes2MnfOiWWvII1mohUS4LCe69NH6tarZ44QDoiCBeDcOJFfGR+GWrdpIy0eeFAyZMwQeCa//fqb9H7uWZn+fnCOZbQBR0eHTk96kR78DcFh+kzPnp6DEh+2l2tkiGzZs5mq+GjeRNsIZ3CWfvq5RpJIOMb3NB/zkx07etXPPfdc+SIJIYJXWWfeVQfLU506easggljgfACtWa26cfR6FQJmXAdVQHHYVfPmzpM2mqvZGj6utmnbTn+DB+T0M063q0OmGL0Pfm+8/lrI+ngdQ0ijYRbwAbj/gIH6IfjaMDVE9uzeI88+0yvsdfXpihVy3nnnGcd8+XJljcP80ccek5y5coa0eeTIUZk1Y4aMGfOyfL/luEMqpELCwumnny7tHn9CI280C9v31DdgUid01Wgcv//+e6JmevfpK3Xq1TXrFy1cJCOGD5O+/fqpo/XqRHWxAsKjdm3aRuwfNxQqJM+/0F+FOfkC28DK9ep8feCBFiHOf6xv0rSZdHm6K2b1A/9PUrZ0KTMf9N9Gx/n92COPyIfz53vV+r3wgglzjhXIC/7oww97Ze4MnCkzZ8+RCy+60KwOclS6bbnb+uf3/7Zfhg4ZLJMnTfIXmeVY+rrb4FUqeBowcJDkv76AuzpkHg4NRJH57bffQtZjwX8+CxcslIdbPpioXtAK9Ne2jz/uFaHPPPTgA95ycmb8+w+3TVI8w22H9f7nRUl1RD751FNSVR1nQXbo4CGT4ufVV14JKhZ86O/WrbtUr1kjsBwrDx86LG3btA50JrmjF8ePG2euo2eeey6wLaQ8GfnSS/pvRMjzyq18sQqGkKboRnXcB9nuXbvluWefkZtuvlmat2hhqkA81kAFbH4rV668PNu7t+TImcNf5C3jd26n53b48GFvnTtTu05d6dGrpzqDj0c7csswf/DgQXMPx7MrWkuJkAD7gMPz9TffktNPP83s8qvVX0k9TXHkt5Se//wFC4yAw9+euwxBV6kSxd1VktL92UbiISTw98cz9Z3k/qZN7S7MFNFyHm/b1lsX72sA71ZPdGjvte/OoJ907dxZsuvzsk/fvlK6TBm32JvfpCKgxg0bSjgBDxyvz6mAxKYT8jZMmMH9ftrUqdKrZ49AYZu/vn/ZvZfF+pzxt22X4/2+mJz3Xuz75VGjzT3RLyBKjXeG5BxTUu/iOOY3VQSI6z651vnJJ83v79ZP7m+Kbbo+/bR33azQd3T0Rb9B5PXCgAFyww2F5EwVeUayctrP4Yz226nyzPefN4QVPbp186/We3rs776JGo2wAkKxB1s+FPY+4m6Kv1cgXB3Y/wUjhHbL/PPxOo9Y/wa0x1W8eAkZ/8pEu5jkNNI7eix/B2DH7nWI5bR+T8U+aSRAAiRAAiRAAiRAAiRAAiRAAiSQFIF0KyRI6sTd8lYqHGjdto23qrh+kHVH/HoFaTTjdwzdXb68/PjjjxH33k4dXo+o48vabepUsc5UfMAZMGiQVNFwx8mxfipKmDA+8WjGXs88K/UbNkhOE9JfnZtjX37Z1O3Rs5c0bNzIzONjdok7ism+ffsC2/FHkmihH/yX6KgXa34hgF0fbnqyCAnwgXzqtHcjOkTdc4BzbbD+ZtbSSkiAvvfSyFGSMVNGu+uwUzjun++buK9ccMEFmlbkM7Pdpu82aSSD/WbETdiGtADOtw4qFFiw4KNE1cDuJf3QX6ZsmURlQSu+3/K93Kf9bY9vVLDrFIBTBlEWIHaIZH+oIKGZ9sGvv/oqUTX01UEvDg4rCnE32LF9hzRv2kR+0JGh1tJaSNC33/NSy3EuxiIksOcQlAYm1r5u275Z72Fj1BGdRQUQSRlEH43UseG/b/s/0KLPVq1UMclR8HASL9IoNedrX7aWmkICu48gnrYs3NT/vIBYC07FpGzokCEyQh30rmHU4fszZkquC3O5qwPn/9JoNk937ZLIOeU6bnEtYiR2OOGUbbiPCg1emTjRLnrT3JdcIm/oqHdEH4lkOJYNG9abCAGoFyQkeOTRR6XdE09EasYr+/qrr+XBFs0TiVPcZ5lXOcwMnn94DkZjKRUSYB+4Vqx4EstlVUiJiC/WYjn/lAgJYtmfPeZ4Cwn27d2n9/0T17TdjyskiPc1gGgAeS7TayBB5GH36U6RegtOYYxMjWQb1m9QgUy9RGkrEFUH4r+knLdoe+WKldKsyf1Riwnce2kkIUFynjORztG9xmJ5X8R7LwRIGLmfHIMg7Yl27UIi7cT7nSFe7+I4nznzPhBECEmupbaQABFaRuo9z45cT+q4goQEp+Iz33IKEhLE693X7iPSFCLu/gMHyF133x2pWmAZBOR9nuudSABtK8fzPGL9G9AeE+6ZL+o7UHItnJAg1r8DsH/33orltH5PxT5pJEACJEACJEACJEACJEACJEACJJAUgVNeSNBEnYSduz4tOtje2NIlS43DLylwqVnudwwlR0iAUbvVahwfgYoIDIU0JLW1Ro0bS/eePe2iCSeK8MufqbM3Y4aMxulU6MZCXjmcMrU0/QEcM9ZyaRjIhYs/9j5UY1TcO29P0RDwK2Tv3mEqcZUAAD6WSURBVL1yxx3FBY6DTJkzmU0Q7rhSheMfpBBOfYqOhLPWs3t3/eD0ul0MmQ4dNlwqqKMPhhGOZUqVDAkzjVDD05yICc/26pVoJGtzHfGPkVGweAgJEK7+Ft/IL3wsdUcrvzZ5snyrYa1dQ5hrpHqAIZ0ERmpZ+0WFFB+ro3LN11/ryNcjGmr7NqlRo6acceYZtooU1/DGYAuLxzF4DYeZyaZRPGZrigx3tC5+Z6QAgGPtSg3FX7xEiZCRSvjQX69uHXMetlk4RDBaLpwhdQMc+P4RvRidXKdWrUQRAB7WEfiPt2/vNYdRUAj3jLDQhzUPeJHbixp+bnsLP1ogDz/U0tsGM65TwC3Yvm27LF68yIRoRejeYuqEtRExUG/zps1SvWqVkFQOcAjM1dQHl19xhdfUis9XyBdfrDSRFSCmwMgut/zN13XEWfcTI87SUkhQslQpGesTByUlJECudTi4rF1yyaXmIzNSQFjDb1la+4RrsfZ1tIWP2rM1ikxuTathDWk1PvnkEzPCHWGWcc/Jfl52WyzztO+2ad3KW8aM/wMt1mFULpwqkayBihJ6PvNMSJVYhQQp5RlyEAEL/ueFrQJxDkIPr161SpBiAk5mpBiwhusIo9ZxD7KG6wzXmzVcGwhjjHDwMISJL6fCNmuIpHC73rsQKtyaKySw6+DAxTUGQU6GDBmkeo0aJm2CLf/9wAG5s1y5RI77l8eODRmhjXvEl19+YUZBZs6c2ZwT7o1+8wsJcG9bpM8v+3zCuSOtwlcaMh6hmS+9NI80bNQoxMnsFzdAnPGKE4EDURlGjXxJ+a1Rwcn5Jkx+NY1wY/eBaCsYob8/IFKG/3jtcixCAkRK6NOvr21KHn6wpSxcuMAsx3r++M3xDoCUSPc3aeLtA1EtNidES4E4EOlgYLHuz+4g3kIC2y76EdKzfPXVajmiz99VmnIJznFYal0DW1UMCj67NILGrSoYsO859pjsFO9OOJadO382z/3atet4712o4/6uWIbYBs9tV/wHAR+ufbw/4N0FfdctHzlihAzW9EPRmHsvDSckSO5zJtJ+4/W+iMhPnTQyi2vr160396GtP27VlCTXSr1764c86/0Czni+M+A44vEubs9nub6/W6HbpFdf9a5DW46p+wxLTSHBmWeeKZ/p3xQYLW7NhsDHe7y6RvX9Ma9GlGpqi8UvJDgVnvneySfM4FmLVBuwICFBvN59E3YXcTJQhcv+KEZwnkOY6ApQ8R6D6GFIU2SfdbZhf7Qsuz6e5xHr34D2mBo0bKTXRy+ziPecsRoVzW947llxXpCQIB5/B2Cf7r3VHkNavqfafXJKAiRAAiRAAiRAAiRAAiRAAiRAApEInFJCgnvUSYkPXcg7e606HxAK2R3Rg4+MjRs2CBmRFAleapX5HUNJCQngtJytYfStUw2jQCvefZc5PIQzX6TONzhdYBAJPKShvT9RJ7ZrGKkJIYC1pZ8skebOR79HW7WStjpaCwYnDEZX+3Ox+x2I5TX/+/bt28w27uipcOkNMmbMqGkNVngfdoNGdLrhKOGoubHgCcGE2ZH+99KoUSZPMJbjISSw7brTHDlzylInt/G9depoHuPVbpWQ+ffen+5FI4DT7N66dWXz5s0hdRAGeuTLo7117TQVxRwn5YZXkDAT7TH4t/cvd1FBjfuhFx+CG9a/N9HIRYQpd/Nkf6XnXU/P35o/qgTW47fq1aO7cQQjUgCc/rj+nlDHJabWcA3WrF7NLgr6NvovQuVba62RNz6YN88umilyuCNPuPshu4WmQVii21oLcgrgA/xzPocxHGbvTZ8R4lT0i1/wgXHk6BO/FXKFd9fQv67h4zrCjd94041mNQQJlStW8KqklZAATGapU951IuMgkhIShHMW+T84Fy1cOCTcdjz6uj/Cymeffqr3nKYhYg6EvJ8ydVpIuoz6el2tUse5taAPtMjLXE6d6uHyYuPj8Dx15Fkxkm0rViFBSnna/Yeb+p8XqAenJcRgNiqN3Xbw0KGC69OaG6L6rLPOMmllzs2SxRTDYVCvdu2Q3xYFDzz4oHR0hBjVNdLNxg0bbJPiFxLs/Plnk2bAzaWMEYoD1ZHpHgtScoCRNb/jHqMe62v6HoiIXPM751DmFxI80LKldHRS3PTVEPATJ4RG3YHze/rMmd51AqdvK+eZ2EnPuYWeu7WgtDhVq1Uz52Xr4N6Ce0xyLRYhQZ48eeTDhQu9XXXr2lXefustsxyP80dDEPBMdVI21L7nHpNj3ttpwky89pcaQgKI+B5o3jwwFHdqXQNIb9NI3y0h7rHmv46wHv22edNmKi48ZKtJxUqVzchZG9EAUUQQTcSaK77Euikq4nu6SxdbbKZwzk967XUvLRCEFKVLlvCEiiGVwyy499Kge1k0z5kwu/BWx/q+CKHiYhVSuOJCpMjypxvBPR4RWKxDFH2jlArj8IyAxfOdIV7v4hbSWo1OYSNQhHv//EKfhfZ+nppCgltvu03fdd60h2aikQ3o3z9EYJY/fwF5b8Z0r45fSHAqPfMthHETJkiJkiXNol9IEM93X7u/cNNqmgYJUeus4f4AUaZ9HrvP9Bc0Hdg4Ffjh/RZR6my6MGyLCF536bvxL7/8YpuK6zs8Go31b0B7YG7EHPydgL8X/JbUO3o8/g7APt17qz2GtHxPtfvklARIgARIgARIgARIgARIgARIgAQiETilhARrdASa65B0wYzWcO7jx41NNCLSrZNW837HUCQhAQQCgwYPCQn77o6qgngCHymsIQcnPlj5DXlb4WSqWKmSKYLg4I6it3s84KhF2FEYHHCug9as1P/y5csnsx0Hb5WKJ0KIP/TwI16O4HDhavHBfMiwobY5DUFeSb7TEPSuueEo4aAqnfARzq0TjZAAjqn12i9gx44dC3FUum3656N14pfQj9O5LrzQNLNh/XozGtLfJpbxUfu6/NeZotcnv2byGQfVw7pojyFcO1gP5+nHGo0jZ66cpho+BNbSUcM/K2O/wdny2htveg5ylLsfhWurqKCPfmy0BuFJm1aPeaNV7XpM8cH/7Xfekcs0/Lm1uuq8tKkE/CPDkct44ID+tmrI9M677pLhL430oov4RSR+pwCcNvfpKGT87n4rUqSITHx1khchwi9+wUdem8v6r7/+kpkzZiSKjIE2cUwjRo70mi+m7dqPrO5HSozYxvUWzjYmjPpFuX/Ul/sRMsjB85ymKqmr/dxvKRUS1KlbT3prSgtr7m+PdfHo6x8tXCSX5rnU7AIjdO/VqBcHVIDjt+sLFjSOa+swemXCROnT+zmvmsvGW6kziLTwwvPPu6u8ef99yBaklpAgKZ52/+Gm/ucFRljXqV1Lvvv220SbZMqUSQU37+rI8nymDNdmSU01YyOf4NwzJaQ1gchg27bjQjC3IdwrVmmEEhu1w+8sd50O2C7oPo71GEn90YKFXtoD66BAGaxnr2ekQaOGxxf0fzhH4SQNsufVaVVTndrW/EKCKzRyiA0b/8cffyQSItntWj70sLTv2MEsIk85oi1YQ+j46jVrmEWIwW7zRamx9fB7WNEORoXv2oURucmzWIQEEOKtXrPG29GQwYPlpeHDzXI8zh8NJVdIEK/9uUICOFZ2/rzTO79wM8OGDpH333vPK/b3R/e9xKvkzMT7GsB9tpQ67f2pdvDOhfPLlj2b2TuuxTtuL5JIuIPCmSoovPqaa0w9hOBvqaPtYXDeL9fr1L7XIqIWUnLgmeS3Cvo+NjShP6As3Lugfzu77N5LY33O2DbDTWN9X4TQEYJHa5M1kgiiVwWZ/x3Dfb7G850hXu/iOAe893/ppFuqcOed8sMPPyQ6vbQSErjp4RClppj2Y38fTEpIcCo98+0PFUlI4O+Xsbz72v2Fm+Id/EZNTQHD/epxFY3PnTPbq+7eQ93nNN4F/Gnznu/bV/+WHudtG+/ziPVvQHtgT3XuIs30XgmbOX26tA9Ie+S+owdFJIjH3wHYv3tvxbK1tHpPtfvjlARIgARIgARIgARIgARIgARIgAQiEaCQIIEOPn5BSDBGc3z6P4BFApgaZX7HED4aI8+uNYwEueTSS82H5WbNmoeEoofzAyO6rQN4mIawtTliD+w/IIVvvcU2k2h6u4bSf1VD9Ftr27pNyMckuz7c1A0JvG3rNg1VXdaritHDCzS0tB1V5x/hjYrIZ2uFDAilfY86sv3mhqPEKL+aCekc3HrRCAnc7RCde9eunSZ0+lwNFfz2W2+G7QvxdOK7x+COGJ6nUSbggA9n8TwGOGSnOc6X/upkHeuEtfcfw206Cv01R5DyTM+egvQOMP8I4Vc193lv56O+vy3/B2Y3EsWol8dI2YR+hNGchdV5F+n6ROhxjGSGIS0DRstb8zsFIJRYmxCy3dZxp4PUEVdFR1vDIKy59eabdKToYbdKkvN+cU1VHQ1uHbzVq9eQ/oMGem3cXKhQotzXtjClQgJ39Bbamv7e+54zNKVCgqHDR0iFhMgK/igL9niTMw3X1xGqfsasWV4T/hDzXkHCzGRNGVG4yPHfGSG9qySkRkFxuA+0cCYjJQOmfsOIazhM/ZZaQoJYefqfF5E+PuOcyqvTCfdIa506dAhxvNr1kabTZ86SazU0OMwVrmHZdTrs2L5DRUalsTrQFixarM+yS0wZ7h+4j1hzHUv+SCW2jp3CsbZy1Wrv+eIXEth6SU2RH3r4Sy951QpoX7T3G394ZozKHKyjOBEpIV4Wi5AAx7Buw0ZP/ARnDpw60Vik80c7yRUSJHefSe3PFRIkt82B/QfIy6NP9G+3P25SYWKVBLFkctsLVy+51wAiaCA6SJCN0d/Ihs6O1MddEYvbt/2iJ38UHv8+3XD4iOqD6D7JNfde6hcSRPucSWqfsb4vuu8NEFbh2R0kGMRxQNBRRqNn2bRSn2vKACtai+c7QzzfxSHCWqipWawV0YgAQSlU0kpI4L4ruWnN7PFh6n/PcwWIp9oz33KJJCRw+3Cs7752f0HTy1XE+8FHH3lFQVG63HuoKyTARogutED7ohXP+e9j8T6PWP8GtCfaV//GqaWiZVjQOWN9UkIC1EmORfo7ANu791a3vbR6T3X3yXkSIAESIAESIAESIAESIAESIAESCEfglBISPKofTRHmEx8Oc2lo+suvuNJ8GNdFz5DnvMl9jT3ngVeQhjN+x1Bydw0RQZP77pMNG9Z7myBEe/4C+b3lnt17ePP+mfM033jbxx/3Vvd5rre8MnGCt+zOYHQbcl2feeZZZmQ68sq7o8oRehcheF2bqCGei91xh1nlH+HtT2sQznmI39AeI0bfIcWC31IqJPC38+3GjYJw20GjcmN14uPD0mWXXS5ZsmYRhLzNov0yi06R19daWgoJ/A6dpBwSOOYVX35pD9WEsu2fEPnCn5sY4cFt7mxvA2cG1+NKbcuG4P1w/nwz6h5VXGeNP4WC04Q360/PcKMKJI4cOWLKXacAhAE33lDQC2HsNeDMuKOTsfqucuUE4d6DDI5MiDHOP+/8kN8UjjfkjbbmCgkKFCgg7+pIKGvhxBuI2vDpihW2WrIjEuCYZs2Z4+Xgxair+R/MlyHDj1+XSQkJ4JQf/OLxcLcYeXb++ecbkUaFipW8qA/uqGfvAH0z0fZ1v6Mb0R5W6sjdcNa9Rw9vVDtC697qpMpwP9Ainct11+X3om4E8XbFVPiIi3uMFU3EKiSIF08/B//zooOOrJvh9Ct/fYjQFuioZmtwhkMM4Dc4CJACCDmc3XtUlnOzeCP5sE0kIQFSsyBFSzhzRSBuaGfcE77RNCc2dLc/ukhQex9oWoTLr7jCFLnO1qC66MtwbGE0uL33YlpTHb4XqejNmisk8IfvRh30tw/mfSDLly/TPrpC3PQNto1oprEICRBtYtXXX3u7G6QRFEaPOhENxSvQmZScP7ZPqZAgpfuLt5DgTRUd9ejezUURcT4e18AbmlKgp6b1CTJXRBRuVCy2e7JzZ2neooVpwu3bDz70kHTQkP3W8N71y74TYcXteju1OcGx/NXqrzQl0XFHmi2PNHXvpa6QICXPmUj7sWWxvC9CiAbnNAwpmurUOhGtxLafnGk83xni+S7uvjsgksX1Kur6+++/E51SNEICRPh6XftqOIOo0orH3JQ4qO+mfUFY/KpVKsv3W7aENBVJSJBen/nudRICI2EhkpAgnu++Qfu268qULSujHdFw21atZe7cObbYTCMJCVDBFYX6U87F+zxi/RvQnhhSkyE1AWzYkKEyfNhQW+RNoxUSpOTvAOzMvbf+G++p3glzhgRIgARIgARIgARIgARIgARIgAQiEDilhARBHK6//nrpqXkeC91YyCuO9AHeq5SKM37HUHJ2FSQiwHbLPv0sJNd7ctqydcKF0sTH9XXqZA9nQbloUbeGOmleGDDAbAYnZgkNqb1Pc9LCKqpz0jo44eRFmQ0Bbyok/Ne5S1dp2ryZWYKDsb0jfLD1ohES7Ny5U3795VcjLsmp4pILclxgmzFTfGxvcG+9RMKSlAgJcqtDrr1+9C9W7I5E+wnZacJCWgoJ3FE+2H0pDc+dVEhu5CC2o5CQexi/O8x1hmH5Tv1QGSTGQJk1pEq4LSGMuCsYcEdQBuV+ttvbqT9MfHkdabh9+/Hw7K5TIDkjU8uWLSejxrxsmzb52Vc54gkUIGxrrdp1pKCKCE4/43SvbrgZV0gAIc4izc0KoQAMobshvoHzFSMic+W6UEqWLiX3399E02Lk8pp0Qy9jpfsR0v1w3evZZ6V+gwZmO6ROqKxRBIoWLeZdZ0kJCbwdhpl55+0pJvVG0IjsWPq6PxRumN2HXX2DCjTsMfnZrNZR6zZ0/a6du6R82TIhYpKx48d7wg+MOM+WNZuXAzhWIUHYA04oiMQz0rb+54Xbx4K2g5P+i9WrTXhslPtH4+GZ2LptOyms16MV9wS1Y9dFEhIkNSo+nCMlW/bs8vnKE+IR/yhIu2936o74dZ2ttg6eW488+phGvakoV119jSeGseVBU1dIgHI36k5QfdxXIHp4//33E4WyD6rvX+feO4OuT399d/mKK6+UeSrCstblqadkqoasthaP849GSBCP/blCAjzvX3/tNXs6YadIh3Rg/36v3HWCJdUf7UZpdQ24QoIZ70+XDu2fsIcQMg0nJOj69NNyf9OmIXWTu5BUtBB/O/57KUSWsJQ8Z/xtBy3H8r6IdA8Qr8CS894QtH+si+c7QzzfxRF1CdGXYP4ULGZlwn/RCAnc7ZKa9wsJ/O9KP2oEtZ4q8Pvs00+9d+dIQoL0+sx338eCmIZ7/qFuPN99g/Zt1+EdEdewNURqwt9Frrn30KBnsetwx3a3a4SM3377zTQR7/OI9W9Ae17u3xxIe4L0J35zzysotYGtH8vfAWjDf29N6/dUex6ckgAJkAAJkAAJkAAJkAAJkAAJkEAkAqe8kABwkOcQoyZy5MxhWO3Zvcc4siOBS80yv2MI+zp08FCiXeJDzY4dO2TRwgUyberUQMc7cllnypwp0bbJWRHuozscBJGEBFt//FGe79dPMLLctYwZM6mw4VPveNz0Bu6IloUfLZCHH2rpburNux9c/A4wWykaIUHNatVl/fp1dlPJmzefyf9u82mjICg3d7RCAoxKHzBwkGTXqA/JtbQUEjRr3lye0hzk1lxnrF3nn7qj/ubqyPe2rVubKtWqVzd5U2395LTlprVA6H84Q2HIA4xRPrCkQrajDtJ4wKFozc2H7ToFvlj5hTSsf6+tFji9WdMovOnkZG96//2yfNkyUxejf3tr2HDka43G/E7e2nXqSB+9VqKx5AgJ4GiY+Ookz1mKENYIZe0KdoIcle71ldQxbdQw6oMGDtD7z8KQqrH2dYy8heMspeamiHDPBx/1n+rUyYg3bJ/q/OST5t6JfV173XX6HJhpdgtRR/kyZaSNOtTr1Ktr1qW2kCAcT7PzCP/5nxclNOqLPx+7f/PFKmCxI+/ffust6da1q6lS7957pZs6gGzOdf92QcupISS48MIL5eOlS73ddXmqszrFp3jLQTPP9ekjdevVM0V+IUGuXLl01OQwufW2W4M2DbvOLyRARYRDhvDKFR/6G0D/QSQG/zPQX8+/HIuQwJ9S5gG9p2N0Iyxe559cIUG89ucKCYa8+KK85Nzb/ezCLbtOsHDvNO62aXkNxCokcPu8ew7JmYeQqlSJ4smpaur476UQEqT0OZOcncbyvui+N0wYN1769e2TnF0mqhPPd4Z4vou7z/Efvv9eKtx1V6Jjx4q0EhJgX+MnTJTiJUtg1jNEJ9i7d4/8o3nDEHHr3HPP9crc1Abp9Zkfi5DA7cOxvvt60ANm/O/+1+t7kD8NiHsPDRIS+N/5y+rfOzY6T7zPw70PpeRvQIsA0bquuvpqswhBOoRqfktKSBCvvwPcc/o33lP9581lEiABEiABEiABEiABEiABEiABEggiQCFBApV2Gg76EQ3Bbi1czlFbnppTv2Pobg2/+KM651NiCxd/LLkvyW02PXr0qDzd+YSzOKi9jJkyyuFDh03Rmm/WJApPigKMaMUHlowZM8hpGvIcI6qv1g8yt+to59NPP54nAo4UOF4R7tk194OJTW+AtAbLP/tcsG9YUGhN28boMWOlTNkyZjEofQIKYhESYHuMhp2rTtfzVWACcx1tZoX+F42QACPPP//iCznnnHPs5gKxBdJo4Hf97bdfzQj033UUenfNEW7Dc6elkKDmPffI8/37e8dXo2q1kBQZXkHCDELd48N4Bu0DMDd8MxzJGNltrbqGxN24YYNdDJy+p6Mx819fwJS5o93cPOluyoPARnSlP9TzHbff7kW9cJ0Cv2gkjGJaFsnuqVXLjBSydVzRSZu2beWxBOEEyvHBfIlGaNj03beye/du2a8jYhFV4DxNddCn34k85X4hAa6lLurEbaxRB+y1Y/cXbpqUkACh7WfOnuPlnp+tYZ4f1+OFuQ6IpIQEEFRhdDUMxwknQJ7L8piIGjafNEb+N2/SVFas+NzUi0dfh6MW+WutDdB+uevn0BFytsxM9ZaDewjuW0f/PGoEE3BcwNz7jf2o747uxQhy/CaoD6FPtRrVzXYQZkFk4PaZWIUEKeFpDiaJ//zPi/sbNzYjQcNtBlZf6rVr+9uY0S/LgP4viD+PL7ZHvmNECNm2bavp078f+F32H9ivIpVXveZTQ0iAe+XXa9d6+wgXHceroDNumgS/kMAVqmEbCPPgZN/y/RbZu2ePuVZxvd52W2G9h5wQsQUJCew+r7rqKimj6U4KFy4it956i3GU2TJMEVmnmabewajc5FosQoLJr78uhYsUMbvCtV28WFFPXBiv80+ukCBe+0trIUFaXwOxCgk6dOwU0l87degg/2io+3CGqDlIR/Wnvgv+8ss+88wKV9e/3n8vjeU542873LK7z2jeFz9csNA8q9Du4kWLpKWTMircvoLWu/f/WN8Z4vku7r7nIEpS/QQBlf8cohESIIoA7uXhDCPXb0pIG+S+o9n6EBjj/oXUY1aoZ8uCpq6QIL0+8+07R9D5Y12kiATxfPcNt3+s94sA6uh77xonRQ7qJCUk6KgCzQdannhuuuLheJ9HrH8D4nz8f7/gHXbp0iUoCrGkhATx+jvAvc/ZPpOW76khJ80FEiABEiABEiABEiABEiABEiABEghDIN0KCe6tX98bbbBER1/iY2Ik84fWTMqRGqmtWMv8jqFYhARvvzNVbrzpRnNI69euk5oJjrJYjzFoe+RtfXXyZM+hYp1xbl03B7l1dsD5MXT4cFPtwP4DxgFiQ5O722J+juajzpsvr1ntRjRw68UqJEBbrlNx9apVcm/d46OS7X6iERKUKl1axowbZzeV4ToydoTmqQ/Kaes6w9JSSFBCw5mOmzjRO8aOT7SX6dPf95b9M5dddpnMX7DAW+2KOi6//HL54KOPvLKk2sIHaOT2tkIL1/H91pQp3sdriC/uSshp6jXum+mvecGr16xh1qJ/XZ//Oo+z6xRABVdk4GvGLLof8rDCHek9Zeo0b0Tyzz/9JI00xcGO7dsTNeP2dxT6hQR2A0Q/wEf4fBoR48q8eQWOeowY3b59m4DHM889Z6tKUkIChKXF6GQY0oNU1igNv/76q1mORkhgP2iaDZ3/cHz4sGxTMri/Vzz6ul+I8ojmAV/g9CfnUJKcDfpAi3Qc+Lh95llnmu0fevBB+VajYHyokVDAHRqEqhr+ftOmTXEVEqSEZ5InqBX8z4veGqb41VdeCbup3xnct3cfmThhvDRt1lw6dz0uNAODTu2D7wEQlSxd/qmXniU1hAQ4eKRfsCNYkyPiWPHFl5I1W1Zz3q6QAPeXT1W0ZcsgjHhQHYv7E0Ivu6D8KV4iCQnc7bCPIipMeqxVK8+Zj/I3X39DenTv5laNOJ9SIYE/z/XSJUuluYoYYPE8f3/fqa0CtG/WrAk5p3juL62FBGl9DcQqJHAdXfgRMDIdI9RTw/z30lieM8k9Pvf5Gc37ouv4xHEiXHtKLJ7vDPF8Fx80eLBUUYEmDGI/RNoJsmiEBOGeT7ZdN41GkJDA1mvYqLH06NXTLgp+t2N/HUsU5cYVEpxqz3wLJ5KQIJ7vvnZ/QVMI4Sa/8bpXFPT+4F5PQREJ3L9b8M5ZLEHQhkbjfR6x/g2IY8I77NwPPsCsMbcv2nWYuvfXoNQG8fo7wH9vRbSXtHxPdc+Z8yRAAiRAAiRAAiRAAiRAAiRAAiQQjkC6FRK4f+BvWL9BalQ7/tEtHIg+fftJ7bp1vOLixYqZkYreijSc8TuGYhES9HpG86Q3PJ4nHaOm79BRikEOlHidHhyeEHHAMJq1ZvVqIU3DCbVw8WK5OPfxKAk9unXTSAZFvRDxb76hjhddF2RwUHz9zVrPAfighkD/WNvyWzyEBAMHDZKqGqIftk5Hxt5T47hz2u4rGiHBUxoFolmL5mbTSPls4TT7XEPu29HeaSkkwPl8snSZN0p55vTp0l5Htoez+zTaxNPdu3vFj7RUZ++CE+IBN3T69Pfel44d2nt1/TPFNBS7O8J5kIoBRo8aaar16NlLGjZuZOb/1pGWSFWwZctmfxNmGWFGZ8+d6/Ut/+/mdwognDuiTQTZWWedJdPee0+uufZaU7xv7z4jcDEherNk0QgTX3qsnnvmGZNnPqidTjqqvYU6qq2FExLY8nDTjerUthZJSPDH779L5nOzeCkN2rZuI3PnzLabRhWRIJJjoW+/56VWndqmXTfXdjz6ek4NQ4++qLcKY8kJR+6doG8m6AMtqiD6BqJwwBAZZP26tV6u8YU6mvXhlsd/M7fPJMeZbRp0/gu3f6eKmQ3H018vaNn/vFim7OBEtlEZ/Nu0btNWWrU5noYEZU1UwILRvi+PHSuly5Qx1SONckX++GnvnxAZpZaQwHVO4L5Z8e67PEGMOUjnv9sKF5bX9NlhzRUS3FCokLyTEFkD5eFGH6JslEZnKFu+HGaNJVdIYOtDDLVIIx3YaDbfbtwo1aJIf5ISIUFufZa+NeUdyXVhLnsY0uWppzQVxDtmOZ7n7xcSNNBUGGDtWjz3l9ZCgrS+BmIVEhRRh90kjURhLSgNky2Lderey2J9ziT3WFL6vtil69PSpFlTbzf3agqh1SogCrLsGoEKOeLPPPNM+Uud3kiD8JOmDIO5938sp/SdAdvG81187gfz1Rl6JZrVaDL9Zczo0Wbe/19aCwmQ0mS6Ch+tyBCRycB+rb5D589fQN6bMd07RNd5eyo+8wEikpAgnu++HvSAGaQQWfrpci+KBIRhjVUYe/jw8ch02CSSkAC/61uadsgKgefNmSttWrfy9hTP84jH34A4MIhwIMaBHTl8RG4qdEPg+1IkIUGWOP4d4N5b3ffutHpPNSD4HwmQAAmQAAmQAAmQAAmQAAmQAAkkQSDdCgkQIt6OygWDSKNar7jiCnlTRz7bj1/7f9svRXy5lPEBo5yOhr7iyitllubRth8ak+CbomK/YygWIYG/LeSGb9bkfkGaA7/BiTR02PHIAChrcl9j8/EVH1jx4TJz5kxmk6ka5QChsIMM+emRpx7mOnPcum4aCYwOveaaa720BvV15P8qjQAQZO7HH5SH4xKrkOACTWkwe+48yX5ednMYQSO+ohESdFKnTouE0LoHDx6U22+7TfCB1W9+p3NaCglwLK7jDstdVQDxzpS3MRti1xcsaD4s2g+Hf/zxhxmB5EaR8AtzwrWFXOjTVGiQI2cObx/u7+qOSESFzZs2S51a98ihQ4e8+nZmyLBhUrFSJbsoriABK/1OAXxArKfioaC0C/77x5S33xY4aWAQfGD0M8JEw/z7MSv1P9xXJr32eoiDL7WFBHbfmPo/6GJdPCISoB3XkbFxw0apXrUKVku8+rr74RojGh979JHAqAS4L78yaZLccEMhs//l6hC3IgCsCPeB9uprrpEZs2Z7YgWzccJ/DVUI9cXKlWbJ7TOpKSQIx9M9rnDz/ns86g1WIVRQmGqMmn/l1Ule30UEmKJFCqsD7S8Z9fIYKVuurNlNOPEdBDb4AG/v8aicWkICf+5mjLJ/oHkzL8KIOVD9L0eOHOYecuFFF9pVIc8e3K8gCrLWSlMYzXdGI9r1ECOMmzBRMmQ4x64SKyQ4++yz5V0VT2TNmk33/5cZBTz4xRe9eu6Mm6YlHEe3vjsfrZAAIiz8Hueff77XzNeatqJ+vbrmN8XKeJy/bdwvIumIqBWOqCTe+0trIUFaXwOxCgkQonup3vOscAXvF401BP26devsT+ZNs2bLJlNUXHLRRRebdW+oAAFO8+Saey91t0nJc8bdPqn5lLwvIsLPm/rMtoaoBPeoMBSjpf3mvi+izE1h5N7/UZbSdwZs679Pp+RdHO3kzZtPZmvqrdNOw5JIpFQ2aSkkgOhjgkbCwT3JWn9NUTR2zBizGElIgAqn2jMf5xxJSBDPd1/sK5I927u31FNRmDWIER968AGx7/Tub+NGJEAqmMkq4HOfPy2aNRNEAbQWz/OIx9+AOC43ogeEpI0TxPb2mO00kpAgnn8HuPdWV0iQVu+p9nw5JQESIAESIAESIAESIAESIAESIIFIBNKtkAAfrd7VUdX2YxtGM7+k4fPHjR3jOSHhhCquIU8Rxj5b9mwep1EjR8qLAwd6y5hxR6ijLYS+3rw5eGR0yIYpWPB/cHQdq9E2h3N8f8YMwQcJaws1hDecP998s8Y4GyAUgOiiTbt2XihpiClKlyypo1KOO2zd/MtoB/nD4WC3o17xQbtho0bSrUcPuxt5ffJr0qvniWVbADHGvPnz7aI3DRe6HudQsOANZhSxHYWFsO+lShT3tnVn3A/DfhHAJZdeKgsWLfKqux+NsRLpGZ55rrfcoCNUrOFckabBtWiEBP60AQgFP0SdPzYEMdpqr7mNkSfWtbQWElTQ0f42xQSOAx8RMdp+oaYw2L17t+kb+EiM0X65LzkeUQL1xulIZnxcdA2hQ2fNnuNFV4Bw4hkV9yxcuFD2aFsQIdyqggqcN0a5WsMHSHyItIbfHkKD6zRFgTVEoRg9apRg1DQcoIgagI+giJJgDeKGSnffbY7brvM7BbB+69at8nzfvrJs6VJzX7hCnf/VNfrEY61b283UcfiP1K1dKySEtxuuFc4bhBZGznWM4sL1VFSjbAxQR6MVJ9nG0kpIgFQGiN6wb98+u2szjVVIgA/HyKn7yGOPee26EQPi1dcrVa4syLNu7ciRo9KrR3cTgWTv3r1mNXLUd+3WXR00JxwXflFHuA+0aMAdfWz3409j4vaZ1BASJMXTHlekqf95gbros/2f7ydzNUIHRG9Zs2ZVTiWku96fL8hxgdfcOHXyvKDOHtj9TZoozxPRYHBdvzJhguzatcuU58mTR+BwcB1FKEgtIQHui3hO2PQG2NdUHXk/edKrsn79enOdIS0O8hTDceiaK2LDs2n5Z597wjCkInlSr9eVK1aY+0eGDBmMAAnRdKw4yrZlhQRYfmfauyHPhZ7dexihlRWF4bp/5NHHQqI9vK8CBuStT665QgJsU77McWGH3T5jpowm5HFevb8iokYBjQ7hGhydNTQKkH22oCxe54+24DBa/vnnmDX2naYE6denj0DAg3sxLJ77S2shQVpfA7EKCcAbzypcA9b27tkr3bs9LZ9/9pn8rhFqYBDJdNP+6j5HwwlqbDv+qXsvtWUpfc7Y7ZMzjfZ9EW3CqQ0hwU033+ztAo774cOGGi7Hjh2Tq6++WqPq1JHmGtnK2vJly6Sp8x7h3v9tnZS+M8TjXRz3mNF6zy6h7+YwXH94pwhnaSkkeKBlS+nopFhAlBuwtH8jJCUkOFWe+e5vFUlIEM93X3efQfP4uwgCOJv+B3Xw+72hQtjPPvtU8DfVLbfeajaFOOTDDz+UYhq179HHWoUIZXH9NG/aNETsF4/zQBvx+huwqB73hImveGLKtq1a63vSnCAsSaY2iNffAe691RUS4KDS4j018OS5kgRIgARIgARIgARIgARIgARIgAR8BNKtkADn2aFjJ3nwoZYhp4zcz9u2/igH/zgoea/Kl8hxsHLFSo1e0FIOHDjgbYeQ6RiBbHNqo2D0yFEyaOAAr048Z/yOoViEBDguhBp+Wx0wdgS1PVY4544eOSKZNNIARpq69myvXuqwmeStKlu2nIwa87K3jJnfldGWLd8LRnvlV0fvZZdf7pXDuYIPnK5DwyvUmbd1ZNyNN93krjLOdYg9XMMHpJVfrjLH6K4fNmSo+SjsrrPz0QgJMELtwP79ZlM4rlynFVZidGeDe+sJPjy7Fo2QAM4phMJ3R7qiLTi00B9dp7y7j7QWEmDfrmPDPZa/jh13Etm0C7bsxx9+0NHoVeWI9iO/uTl13TJEw8B5+3nAIV9VIwr8pFxcg7gDTjz/vtHO2Wef44mF3G2CQjwHOQXsNuiv/mvAlgWl22ilKQNat21jq5gpjgdimDyXXZ7o3GzFtBISPKGiIERO8Vs0QgJs616/mTJlDvlojHL8jvc3aiifJzgX49nXR2q4ZkSB8RvuW4cO/uGNwrXlcPDUUhGIdZ5hfaQPtBidP+m11+zmZupPG+H2mViFBNhBtDxDDi7Mgv954a8GUcE/f/+d6PrBfRtiGxvdw5832LaDazxz5nNDoobYMkxTS0iAtpuoU6LL009jNpFB6IRIAUHmCglQ7o5AtPUhNtr5806BeMh9tttyTF0hARz3CDXsGvrimq+/lj/12r/2uutCGOGeWbNGdUF6g+SaX0iQ3O1Qb8/uPdJWU1bYaBrutvE4f9vepyrA8AukDh08pOHLvzEhsVEvXvtLayFBWl8D7vN2hjryOrR/wmIOmT7ZubPn8Pb3bTy3EC3DFYtiY1z3EIL+rQKPLCokcg3vuffpfftvvS8k19x7qd0mpc8Zu31yp8l9X3Tbg0gLYmK/OAh99eifRxP1YTzLkBIGokJr7v3frrPTaN8ZsF0s7+LPqWCnVKnS4kZeQZvuMwXLrkGEYQ3nvXv3LiMQrauiVbyzub+p34Fpt7NT931uhT7vEf7eGsSgb709xbuPQohcrUplT4SGekkJCVAnvT3zk2IaSUgAHvF690VbSVm5cuVlhIro/X8n4rpQXU6StvPnn6VWzZqJxKvYMJbziNffgNddl1+GqJAIafXcewLeG3GPDLKLLs4d8i5vr7XX9b3xlYkTJV5/B0S6DtPiPTXo3LmOBEiABEiABEiABEiABEiABEiABPwE0rWQACdbu05dzX/6TFgnoQsEH3K7dH7KC+doy/AhY/EnSyRnrpx2lfgd7V5BHGb8jqFYhQQ4pDJly5qP+5kzZ07yCIfoSOqXNEWBaxjh1bpNG3n4kUcTOaTcephHtIDWOmL5q69W+4u85Qb6EbKnjna3ho9V5cuWkR3bt9tVZgr263yOmHWabxUfQv3OfbthNEICu03QdM3Xa0yuz6A0FtEICdB2qdKlzQjrSPwRIn7Hju2e8/TfEBIgX+oLA/qHhC4PYoN1ON6HVXQTxAfl+O2e691HR/2FRlpAmd/gBIMTN1yfwQi8ITpC/VzNSxrJ7EhsjJL3m+sUQPSAY+ros6Hc/XXtMkKgw1liQ7za9eA0aPCL3m9l1/unI/U6ckfvp4WQ4EMdxQ2WQRatkCCoDbsOTtQnO3aUuXNm21VmGq++DgHXoBcHh+SsD9mRs7Bt6zYN8dwokQgl0gdabO6OMv9+y/dSuWKFEOea22fiISRwDjnRbDieiSr6VvifF4j80a1Hz4gf/3GPhtNs06ZNIa3hvtxdt/U7E9xKCxcsNKPi8xfIb1anppAAzx2ky3BHDbvHYufXfvPNcdGXOkdhfmcr7tejX345JPqJ3dZO4Rh8VZ0DLR580K4KERJgNPATGl2gWfMWOuo+smcFTpXH9b6B44jGUiIkwD1v0cIF0k0FF3v37AncXTzO3zbsf3bb9W6UoHjtL62FBDiXtLwG4iEkwDEjN/0o7d9IY5GUIZIPov5AuBeNufdSbBfLcyaa/aKuv8+Fe1/0t1u4cBEZ/tJLXjQSf7ldhqgIaToWfPSRXWWm7v0/1ncG23BK38Xd8PK2rZROC2sEF4il3d80Kad3OCEBntPvqWDjchVkWcP7/weafsG15AgJ0tszPymmSQkJwC8e777u7xBpHlHCIAK76KKLIlVLVLZU/z7u2KF9oIjAVk7pecTrb0D/e5I9rpRM8Z7QWyMYxevvgKSuw9R+T00JA25DAiRAAiRAAiRAAiRAAiRAAiRw6hFI90IC/KQ3a3hThN0sqB9ZL7r4eH5Y96fGiITXJ0+WCePHu6tD5mtrCNQnn+qsIzMzy+rVq03+SHx8TA3D8b45ZYppGvnBy5YuZUb9x7ovjE6CQ6Z69RqCEMmuHfvfMVm0aKGM0DzzQfl1bV2MLMHoOIxA8o/e37F9h4ab/0L66sgpG37cbuefIl/vCsfJEi5PpfsRCfm8P168SIbpMdqRIf52seymofCnV8ipH9w/1o9eQY4yjCDdtm2rfP/9DzJPQ12+9+67XlhW/36yZc8uy5Z/6o3Awqj8jRs2+KuFLGNEzNPdu2s+9xskQ8YMXhlGsk/S3LJIqdFR0yjcqznaYdg/HLXhLCXHEK4tdz2cdzV0ZFHj++4PCeVt6/y04ydBfmWEGLejmW2Zf4q2KlepYpxvbroIW++3X3+TqVPfMdceUh5EMoRWb97iAalxT01zHbp1cZ18+OF80044553rFIBT+NGHHzIpETCqKEfOHG5zKmjZYdKgIBqBDdkdUkEXEMK77eOPS9Wq1eTSPJeGFK9fu06e19Dy32/ZIgsXf2z6Gxx+Fe4sb9IphFROxsJqFbXYa7Z5k6aydOkSb6sePXtJQ3WiwzASsLKmXQnnUISTf0yCyAL3r1t9UUHctrwdODNof926teYeMUtTpqxVUU+Qxauvg/E9tWqbtBXWce3uDw5biEbeevPNwKgY7vnMVGdH+ydCR/wiXC9G4sGWLVsaMhoV6xDqH+HOYXNmz5Z2KqaKxtz9B22XXJ5B29p1/g/kN2sEmquvvkae6tLZC0ds60KsME2jwYx8aURI2g9bjmnJUqWknfZr/IbuSH3cf7EdItWM19DAhYsUNpv500mMnzBRipcsYcrwPBk6ZIiZD/pvmApt7q5QwRRNGDc+bM72ipUqq4jtEfH3AThDkYN7vKZhaPnQQ15KEjg2mjdrGrLLjBkzytOaCqN0mTIhokBUWrpkqUnPghRHr+q7AAxpAm656cZE138RTafwuDodEYHAFYbBubn1xx8Ez7IXXnher8XfTDvR/If3jD6+NDH+7REJCOkmEFEC6RmQvgfzSVm8zh/3dNxHkE4GkYqwjPDleOa76Ybisb9FKvjCCFJYXxWlTZwQ/h0t3PlH0x9tG2l1DSDCBSJdwHAP6x4m+gZSF9h0O0F9G9uDN9JLNWjYSKPi5MGqENv03XcmJRAi1YR7poVs4Ftw72WxPmd8TSe5mNz3xaCGcmv/aabvvUgd5X9nxfPjGxUhDew/QKNrbU60ebzfGewOUvIunhpCAvc3DXo+2uPFFPc83INhixctkpYPPGDm6zdooGLtZ808/ntHIxN01WeP33DOcz+YbwRueF8rpc+IoHe+9PTMT4ppcp9/sb77+n+LSMu4RqpWq6bXSx25XlPnuO8A7nZ4f/zs00/l/+3dz4tVVRwA8AOG7VQSjBwQA9Hc5MaVUMKoQUKbXOUmN9EPyH1ZUNEPlGpV0Q/6tTFGdBOG/Q8Ohi1nF1PjL2hcGQ0MdL9Xrtx5zfjem3lzvefdz4XhvTdzf5zzOefeOe+e7z3n3NRUWR+qKSzq6/S+X00+RvUdsLed1Ju2YT5XgQSxzSi+B/Q7D9e7nTpM3q1LgAABAgQIECBAgAABAt0V6EQgQb14t27dWj61VQ1lODd3bdkbiPVtqvcxl3LcNOjXeVqt39bXGA46bq4+UljEk9bR4RHDyQ97czn2EU8expOa8WRrNUXAIPmOQIQLRUd5tZx6481yvunqc/01jhE3t2aLgI9h01jfT1vex42x3bv3lE+1xvCyEcgST8O2cYkhrLdPTJRJi3oT0zHcLDr8hxkSucpX7CvmYo1zKM6luIkf9W6lkSWq7XpfYyjnqHsRFBJ+MT3HX8U88P3Oy95OgZdfunsjPPYfT2DFteHhSFfRKRdpGyaPMXf4tm2Plp39MRR8TJlhuTsqxajqepRPdCrGDevovIxpJOpT0HTVu/cGeQQSVOdCXDd37dpVXufjGjM7O7tswMVydjH87+49e9JiMa1LdBrEOfagr79xzu/YsSP9c+dOmaY/ixFshjlPq3w+VgQUxvXooeJaEsE+9ekwqnX6vUYdjPq4edPmcoqFmWK+8hhKPodlFPmPfEbndfzEORlBHb0jt1QWozpetb+mXtt4DvTLe9TLGKEg/kcuFOd8BEj+UVwrc6mby+VvmPbictvH76LdEPUwriFx/bhW/I+fn59fafXy9+vZZogDDNMWrwcSxOgJPxfTWQy6bCqmtrhcjERRLdWIBNXntr76n7+0ZFbb9l26l8E/RVv97mgn39wL4vuxCBT8+qsv+waL3+8ow+ZjFN8B6+2k+M51ZHLyfkn839/qI3LUAwnqK/oeUNfwngABAgQIECBAgAABAgTGTaBzgQTjVoC55ufjTz5NzxXzR8cSnVRPHThwr/Mr1zxJd7sF7tcp0O6USx2BlQXqN8hjrXogwcpb+QsBAgTyEHhQ7cU2tRm6GEiQR+0c/1TW696ZYsScb4sRgHJb6u2k9QokyM1EegkQIECAAAECBAgQIECAwDACAgmG0bLumgTiSbl4wjWG8q3PeR1PuHz4wftr2reNCfQTaFOnQL+0+juBQQXqN8hjG4EEg8pZjwCBtgq0ob3YpjZDvTO3KyMStLVudi1d9bonkCCllUYk6Fq9kF8CBAgQIECAAAECBAgQ6JaAQIJulfcDy+2hw4fTZ59/Uc4VX09EzLv9TDFvfL8hZuvbeE9gNQJt6hRYTfptQ2A5AYEEy6n4HQECuQq0pb3YpjZDvTNXIEGuNTvPdNfrnkACgQR51mKpJkCAAAECBAgQIECAAIG1CggkWKug7QcSeP7YsfTR6dNL1r1181Z67dVX0u9Xry75vQ8E1kOgTZ0C65E/++ymgECCbpa7XBMYV4G2tBfb1GaYnDyUdj6+syzyXy9dSnNzcwMX/4YNG9ILx4+njRs3poWFhfTT2bNpcXFx4O2t2G2BcQgk2LJlS4rrSiw3btxIv1y8OFShPrlvX9q/f3+5zfT0tO+tQ+lZmQABAgQIECBAgAABAgTGQUAgwTiUYgZ5OHzkSDr11tvp9u35NP/3fJqZmUk/fP9dun79egapl8RxEHj26NH09MGDZVZ+u3IlnZuaGodsyUPHBbZPTKTXT54sFRb+XUjvvfuOTqKO1wnZJ5CzQFvai9oMOdciaR+VwIsnTqQn9u4td3fh/Pk0ffnyqHZtPwQIECBAgAABAgQIECBAgEAmAgIJMikoySRAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAk0ICCRoQtkxCBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIBAJgICCTIpKMkkQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQJNCAgkaELZMQgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAQCYCAgkyKSjJJECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECTQgIJGhC2TEIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgEAmAgIJMikoySRAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAk0ICCRoQtkxCBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIBAJgICCTIpKMkkQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQJNCAgkaELZMQgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAQCYCAgkyKSjJJECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECTQgIJGhC2TEIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgEAmAgIJMikoySRAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAk0ICCRoQtkxCBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIBAJgICCTIpKMkkQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQJNCAgkaELZMQgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAQCYCAgkyKSjJJECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECTQgIJGhC2TEIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgEAmAgIJMikoySRAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAk0ICCRoQtkxCBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIBAJgICCTIpKMkkQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQJNCAgkaELZMQgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAQCYCAgkyKSjJJECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECTQgIJGhC2TEIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgEAmAgIJMikoySRAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAk0I/AfN+6wQWxy+cwAAAABJRU5ErkJggg==" + } + }, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![image.png](attachment:image.png)" + ] + }, + { + "cell_type": "code", + "execution_count": 58, + "metadata": {}, + "outputs": [], + "source": [ + "# Модели: rectools.models.RandomModel(random_state=32), rectools.models.PopularModel() с параметрами по умолчанию\n", + "models = {\n", + " \"random\": RandomModel(random_state=32),\n", + " \"popular\": PopularModel()\n", + "}\n", + "\n", + "# Метрики: 2 ранжирующие, 2 классификационные, 2 beyond-accuracy. Считаем по порогам 1, 5, 10. MAP обязательно\n", + "metrics = {\n", + " # классификационные\n", + " \"prec@1\": Precision(k=1),\n", + " \"prec@10\": Precision(k=5),\n", + " \"prec@10\": Precision(k=10),\n", + " \"recall\": Recall(k=1),\n", + " \"recall\": Recall(k=5),\n", + " \"recall\": Recall(k=10),\n", + " # ранжирующие\n", + " \"MAP\": MAP(k=1),\n", + " \"MAP\": MAP(k=5),\n", + " \"MAP\": MAP(k=10),\n", + " # среднее значение обратного ранга\n", + " \"MRR\": MRR(k=1),\n", + " \"MRR\": MRR(k=5),\n", + " \"MRR\": MRR(k=10),\n", + " \"novelty\": MeanInvUserFreq(k=10),\n", + " \"serendipity\": Serendipity(k=10),\n", + "}\n", + "\n", + "# 3 фолда для кросс-валидации по неделе\n", + "n_splits = 3\n", + "test_size = \"14D\"\n", + "\n", + "# Инициализированный Splitter для кросс-валидации\n", + "cv = TimeRangeSplitter(\n", + " test_size= test_size,\n", + " n_splits=n_splits,\n", + " filter_already_seen=True,\n", + " filter_cold_items=True,\n", + " filter_cold_users=True,\n", + ")\n", + "\n", + "# Количество рекомендаций для генерации (K)\n", + "K_RECOS = 10" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dataset = Dataset.construct(\n", + " interactions_df=interactions,\n", + " user_features_df=None,\n", + " item_features_df=None,\n", + " )\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 103, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CPU times: user 5 µs, sys: 0 ns, total: 5 µs\n", + "Wall time: 32.2 µs\n" + ] + } + ], + "source": [ + "%%time\n", + "import time\n", + "\n", + "def evaluate_models(interactions, models, metrics, cv, K_RECOS):\n", + " results = []\n", + " trained_models = {}\n", + "\n", + " # n_splits = cv.get_n_splits()\n", + " fold_iterator = cv.split(interactions, collect_fold_stats=True)\n", + "\n", + " for train_ids, test_ids, fold_info in tqdm(fold_iterator, total=n_splits):\n", + " print(f\"\\n==================== Fold {fold_info['i_split']}\")\n", + " pprint(fold_info)\n", + "\n", + " df_train = interactions.df.iloc[train_ids]\n", + " # Создаем RecTools Dataset через метод construct на train взаимодействиях для каждого фолда\n", + " dataset = Dataset.construct(df_train)\n", + " # Определили test\n", + " df_test = interactions.df.iloc[test_ids] # Предполагается, что Columns.UserItem определено\n", + " test_users = np.unique(df_test[Columns.User])\n", + "\n", + " catalog = df_train[Columns.Item].unique() # Каталог для рекомендаций\n", + "\n", + " # Обучаем модель (не забываем сделать deepcopy), рекоменуем K айтемов для каждого юзера, считаем метрики на test\n", + " for model_name, model in models.items():\n", + " \n", + " model_copy = copy.deepcopy(model)\n", + " # время перед началом обучения\n", + " start_time = time.time()\n", + " model.fit(dataset)\n", + " recos = model.recommend(\n", + " users=test_users,\n", + " dataset=dataset,\n", + " k=K_RECOS,\n", + " filter_viewed=True,\n", + " )\n", + " metric_values = calc_metrics(\n", + " metrics,\n", + " reco=recos,\n", + " interactions=df_test,\n", + " prev_interactions=df_train,\n", + " catalog=catalog,\n", + " )\n", + " \n", + " # время обучения\n", + " training_time = time.time() - start_time\n", + "\n", + " res = {\"fold\": fold_info[\"i_split\"], \"model\": model_name, \"training_time\": training_time}\n", + " res.update(metric_values)\n", + " results.append(res)\n", + "\n", + " # Сохраняем обученную модель\n", + " if fold_info['i_split'] == n_splits - 1: # Последний фолд\n", + " trained_models[model_name] = model_copy\n", + " \n", + "\n", + " # Результат оборачиваем в pandas DataFrame и усредняем по фолдам\n", + " results_df = pd.DataFrame(results)\n", + " average_results = results_df.groupby('model').mean()\n", + " average_results = average_results.reset_index()\n", + " return average_results, trained_models\n", + "\n", + "# %%time\n", + "# df_rec, trained_models = evaluate_models(interactions, models, metrics, cv, K_RECOS)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 104, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + " 0%| | 0/3 [00:00 Date: Tue, 21 Nov 2023 12:46:08 +0300 Subject: [PATCH 2/7] hw_2 From 092e10ee66677c370328ff23f1d62f7d752a4259 Mon Sep 17 00:00:00 2001 From: anettapik <120940816+anettapik@users.noreply.github.com> Date: Tue, 21 Nov 2023 13:01:53 +0300 Subject: [PATCH 3/7] hw_2 From eb1f30e37deae39df0a01e955a39c1d026d5381f Mon Sep 17 00:00:00 2001 From: Anna Pikuleva Date: Mon, 27 Nov 2023 16:55:25 +0300 Subject: [PATCH 4/7] =?UTF-8?q?=D0=9F=D0=BE=D0=BF=D1=8B=D1=82=D0=BA=D0=B0?= =?UTF-8?q?=203=20=D0=B4=D0=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- HW-3.1.ipynb | 4027 ++++++++++++++++ notebooks/HW-3.1.ipynb | 4027 ++++++++++++++++ notebooks/HW-3.2-rectools-research.ipynb | 725 +++ notebooks/HW-3.3-rectools-cv.ipynb | 4387 ++++++++++++++++++ notebooks/HW-3.4-model-for-online-recs.ipynb | 1161 +++++ userknn.py | 112 + 6 files changed, 14439 insertions(+) create mode 100644 HW-3.1.ipynb create mode 100644 notebooks/HW-3.1.ipynb create mode 100644 notebooks/HW-3.2-rectools-research.ipynb create mode 100644 notebooks/HW-3.3-rectools-cv.ipynb create mode 100644 notebooks/HW-3.4-model-for-online-recs.ipynb create mode 100644 userknn.py diff --git a/HW-3.1.ipynb b/HW-3.1.ipynb new file mode 100644 index 00000000..c05a3b71 --- /dev/null +++ b/HW-3.1.ipynb @@ -0,0 +1,4027 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "398a86d9", + "metadata": {}, + "outputs": [], + "source": [ + "from pprint import pprint\n", + "import warnings\n", + "warnings.filterwarnings(\"ignore\")\n", + "\n", + "import sys\n", + "sys.path.append('../')" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "8dbe6bf0", + "metadata": {}, + "outputs": [], + "source": [ + "import plotly.express as px\n", + "import numpy as np\n", + "import pandas as pd\n", + "import scipy as sp\n", + "import requests\n", + "from tqdm.auto import tqdm\n", + "from scipy.stats import mode\n", + "from implicit.nearest_neighbours import CosineRecommender, TFIDFRecommender, BM25Recommender\n", + "from rectools import Columns\n", + "from rectools.model_selection import TimeRangeSplitter\n", + "from rectools.metrics import Precision, Recall, MAP, MeanInvUserFreq, Serendipity, calc_metrics\n", + "from rectools.dataset.interactions import Interactions\n", + "\n", + "from service.utils.user_knn import UserKnn" + ] + }, + { + "cell_type": "markdown", + "id": "b1baa79f", + "metadata": {}, + "source": [ + "# Data" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "f2a9e540", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "((5476251, 5), (840197, 5), (15963, 14))" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "interactions = pd.read_csv('../data/kion_train/interactions.csv')\n", + "users = pd.read_csv('../data/kion_train/users.csv')\n", + "items = pd.read_csv('../data/kion_train/items.csv')\n", + "\n", + "interactions.shape, users.shape, items.shape" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "456d25f4", + "metadata": {}, + "outputs": [], + "source": [ + "interactions.rename(\n", + " columns={\n", + " 'last_watch_dt': Columns.Datetime,\n", + " 'total_dur': Columns.Weight\n", + " }, \n", + " inplace=True) \n", + "\n", + "interactions[Columns.Datetime] = pd.to_datetime(interactions[Columns.Datetime])" + ] + }, + { + "cell_type": "markdown", + "id": "6f7b9b0c", + "metadata": {}, + "source": [ + "## Intersection" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "7c9c0c94", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
user_iditem_iddatetimeweightwatched_pct
017654995062021-05-11425072.0
169931716592021-05-298317100.0
265668371072021-05-09100.0
386461376382021-07-0514483100.0
496486895062021-04-306725100.0
5476246648596122252021-08-13760.0
547624754686296732021-04-13230849.0
5476248697262152972021-08-201830763.0
5476249384202161972021-04-196203100.0
547625031970944362021-08-15392145.0
\n", + "
" + ], + "text/plain": [ + " user_id item_id datetime weight watched_pct\n", + "0 176549 9506 2021-05-11 4250 72.0\n", + "1 699317 1659 2021-05-29 8317 100.0\n", + "2 656683 7107 2021-05-09 10 0.0\n", + "3 864613 7638 2021-07-05 14483 100.0\n", + "4 964868 9506 2021-04-30 6725 100.0\n", + "5476246 648596 12225 2021-08-13 76 0.0\n", + "5476247 546862 9673 2021-04-13 2308 49.0\n", + "5476248 697262 15297 2021-08-20 18307 63.0\n", + "5476249 384202 16197 2021-04-19 6203 100.0\n", + "5476250 319709 4436 2021-08-15 3921 45.0" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "pd.concat([interactions.head(), interactions.tail()])" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "c5c3ce6c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Interactions dataframe shape: (5476251, 5)\n", + "Unique users in interactions: 962179\n", + "Unique items in interactions: 15706\n" + ] + } + ], + "source": [ + "print(f\"Interactions dataframe shape: {interactions.shape}\")\n", + "print(f\"Unique users in interactions: {interactions[Columns.User].nunique()}\")\n", + "print(f\"Unique items in interactions: {interactions[Columns.Item].nunique()}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "0214a978", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "min date in interactions: 2021-03-13 00:00:00\n", + "max date in interactions: 2021-08-22 00:00:00\n" + ] + } + ], + "source": [ + "max_date = interactions[Columns.Datetime].max()\n", + "min_date = interactions[Columns.Datetime].min()\n", + "\n", + "print(f\"min date in interactions: {min_date}\")\n", + "print(f\"max date in interactions: {max_date}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "7829e796", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "RangeIndex: 5476251 entries, 0 to 5476250\n", + "Data columns (total 5 columns):\n", + " # Column Dtype \n", + "--- ------ ----- \n", + " 0 user_id int64 \n", + " 1 item_id int64 \n", + " 2 datetime datetime64[ns]\n", + " 3 weight int64 \n", + " 4 watched_pct float64 \n", + "dtypes: datetime64[ns](1), float64(1), int64(3)\n", + "memory usage: 208.9 MB\n" + ] + } + ], + "source": [ + "interactions.info()" + ] + }, + { + "cell_type": "markdown", + "id": "57cddf34", + "metadata": {}, + "source": [ + "## Users" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "de5dea16", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
user_idageincomesexkids_flg
0973171age_25_34income_60_90М1
1962099age_18_24income_20_40М0
21047345age_45_54income_40_60Ж0
3721985age_45_54income_20_40Ж0
4704055age_35_44income_60_90Ж0
840192339025age_65_infincome_0_20Ж0
840193983617age_18_24income_20_40Ж1
840194251008NaNNaNNaN0
840195590706NaNNaNЖ0
840196166555age_65_infincome_20_40Ж0
\n", + "
" + ], + "text/plain": [ + " user_id age income sex kids_flg\n", + "0 973171 age_25_34 income_60_90 М 1\n", + "1 962099 age_18_24 income_20_40 М 0\n", + "2 1047345 age_45_54 income_40_60 Ж 0\n", + "3 721985 age_45_54 income_20_40 Ж 0\n", + "4 704055 age_35_44 income_60_90 Ж 0\n", + "840192 339025 age_65_inf income_0_20 Ж 0\n", + "840193 983617 age_18_24 income_20_40 Ж 1\n", + "840194 251008 NaN NaN NaN 0\n", + "840195 590706 NaN NaN Ж 0\n", + "840196 166555 age_65_inf income_20_40 Ж 0" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "pd.concat([users.head(), users.tail()])" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "e4e6d2f5", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Users dataframe shape (840197, 5)\n", + "Unique users: 840197\n" + ] + } + ], + "source": [ + "print(f\"Users dataframe shape {users.shape}\")\n", + "print(f\"Unique users: {users['user_id'].nunique()}\")" + ] + }, + { + "cell_type": "markdown", + "id": "98b4ff6c", + "metadata": {}, + "source": [ + "## Items" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "19b43ff0", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
item_idcontent_typetitletitle_origrelease_yeargenrescountriesfor_kidsage_ratingstudiosdirectorsactorsdescriptionkeywords
010711filmПоговори с нейHable con ella2002.0драмы, зарубежные, детективы, мелодрамыИспанияNaN16.0NaNПедро АльмодоварАдольфо Фернандес, Ана Фернандес, Дарио Гранди...Мелодрама легендарного Педро Альмодовара «Пого...Поговори, ней, 2002, Испания, друзья, любовь, ...
12508filmГолые перцыSearch Party2014.0зарубежные, приключения, комедииСШАNaN16.0NaNСкот АрмстронгАдам Палли, Брайан Хаски, Дж.Б. Смув, Джейсон ...Уморительная современная комедия на популярную...Голые, перцы, 2014, США, друзья, свадьбы, прео...
159614538seriesСреди камнейDarklands2019.0драмы, спорт, криминалРоссия0.018.0NaNМарк О’Коннор, Конор МакМахонДэйн Уайт О’Хара, Томас Кэйн-Бирн, Джудит Родд...Семнадцатилетний Дэмиен мечтает вырваться за п...Среди, камней, 2019, Россия
159623206seriesГошаNaN2019.0комедииРоссия0.016.0NaNМихаил МироновМкртыч Арзуманян, Виктория РунцоваДобродушный Гоша не может выйти из дома, чтобы...Гоша, 2019, Россия
\n", + "
" + ], + "text/plain": [ + " item_id content_type title title_orig release_year \\\n", + "0 10711 film Поговори с ней Hable con ella 2002.0 \n", + "1 2508 film Голые перцы Search Party 2014.0 \n", + "15961 4538 series Среди камней Darklands 2019.0 \n", + "15962 3206 series Гоша NaN 2019.0 \n", + "\n", + " genres countries for_kids \\\n", + "0 драмы, зарубежные, детективы, мелодрамы Испания NaN \n", + "1 зарубежные, приключения, комедии США NaN \n", + "15961 драмы, спорт, криминал Россия 0.0 \n", + "15962 комедии Россия 0.0 \n", + "\n", + " age_rating studios directors \\\n", + "0 16.0 NaN Педро Альмодовар \n", + "1 16.0 NaN Скот Армстронг \n", + "15961 18.0 NaN Марк О’Коннор, Конор МакМахон \n", + "15962 16.0 NaN Михаил Миронов \n", + "\n", + " actors \\\n", + "0 Адольфо Фернандес, Ана Фернандес, Дарио Гранди... \n", + "1 Адам Палли, Брайан Хаски, Дж.Б. Смув, Джейсон ... \n", + "15961 Дэйн Уайт О’Хара, Томас Кэйн-Бирн, Джудит Родд... \n", + "15962 Мкртыч Арзуманян, Виктория Рунцова \n", + "\n", + " description \\\n", + "0 Мелодрама легендарного Педро Альмодовара «Пого... \n", + "1 Уморительная современная комедия на популярную... \n", + "15961 Семнадцатилетний Дэмиен мечтает вырваться за п... \n", + "15962 Добродушный Гоша не может выйти из дома, чтобы... \n", + "\n", + " keywords \n", + "0 Поговори, ней, 2002, Испания, друзья, любовь, ... \n", + "1 Голые, перцы, 2014, США, друзья, свадьбы, прео... \n", + "15961 Среди, камней, 2019, Россия \n", + "15962 Гоша, 2019, Россия " + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "pd.concat([items.head(2), items.tail(2)])" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "8c8fb319", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Items dataframe shape (15963, 14)\n", + "Unique item_id: 15963\n" + ] + } + ], + "source": [ + "print(f\"Items dataframe shape {items.shape}\")\n", + "print(f\"Unique item_id: {items['item_id'].nunique()}\")" + ] + }, + { + "cell_type": "markdown", + "id": "2b35b460", + "metadata": {}, + "source": [ + "# userkNN model CV" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "f60e6ecb", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + " \n", + " " + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.plotly.v1+json": { + "config": { + "plotlyServerURL": "https://plot.ly" + }, + "data": [ + { + "alignmentgroup": "True", + "hovertemplate": "variable=user_id
datetime=%{x}
value=%{y}", + "legendgroup": "user_id", + "marker": { + "color": "#636efa", + "pattern": { + "shape": "" + } + }, + "name": "user_id", + "offsetgroup": "user_id", + "orientation": "v", + "showlegend": true, + "textposition": "auto", + "type": "bar", + "x": [ + "2021-03-13T00:00:00", + "2021-03-14T00:00:00", + "2021-03-15T00:00:00", + "2021-03-16T00:00:00", + "2021-03-17T00:00:00", + "2021-03-18T00:00:00", + "2021-03-19T00:00:00", + "2021-03-20T00:00:00", + "2021-03-21T00:00:00", + "2021-03-22T00:00:00", + "2021-03-23T00:00:00", + "2021-03-24T00:00:00", + "2021-03-25T00:00:00", + "2021-03-26T00:00:00", + "2021-03-27T00:00:00", + "2021-03-28T00:00:00", + "2021-03-29T00:00:00", + "2021-03-30T00:00:00", + "2021-03-31T00:00:00", + "2021-04-01T00:00:00", + "2021-04-02T00:00:00", + "2021-04-03T00:00:00", + "2021-04-04T00:00:00", + "2021-04-05T00:00:00", + "2021-04-06T00:00:00", + "2021-04-07T00:00:00", + "2021-04-08T00:00:00", + "2021-04-09T00:00:00", + "2021-04-10T00:00:00", + "2021-04-11T00:00:00", + "2021-04-12T00:00:00", + "2021-04-13T00:00:00", + "2021-04-14T00:00:00", + "2021-04-15T00:00:00", + "2021-04-16T00:00:00", + "2021-04-17T00:00:00", + "2021-04-18T00:00:00", + "2021-04-19T00:00:00", + "2021-04-20T00:00:00", + "2021-04-21T00:00:00", + "2021-04-22T00:00:00", + "2021-04-23T00:00:00", + "2021-04-24T00:00:00", + "2021-04-25T00:00:00", + "2021-04-26T00:00:00", + "2021-04-27T00:00:00", + "2021-04-28T00:00:00", + "2021-04-29T00:00:00", + "2021-04-30T00:00:00", + "2021-05-01T00:00:00", + "2021-05-02T00:00:00", + "2021-05-03T00:00:00", + "2021-05-04T00:00:00", + "2021-05-05T00:00:00", + "2021-05-06T00:00:00", + "2021-05-07T00:00:00", + "2021-05-08T00:00:00", + "2021-05-09T00:00:00", + "2021-05-10T00:00:00", + "2021-05-11T00:00:00", + "2021-05-12T00:00:00", + "2021-05-13T00:00:00", + "2021-05-14T00:00:00", + "2021-05-15T00:00:00", + "2021-05-16T00:00:00", + "2021-05-17T00:00:00", + "2021-05-18T00:00:00", + "2021-05-19T00:00:00", + "2021-05-20T00:00:00", + "2021-05-21T00:00:00", + "2021-05-22T00:00:00", + "2021-05-23T00:00:00", + "2021-05-24T00:00:00", + "2021-05-25T00:00:00", + "2021-05-26T00:00:00", + "2021-05-27T00:00:00", + "2021-05-28T00:00:00", + "2021-05-29T00:00:00", + "2021-05-30T00:00:00", + "2021-05-31T00:00:00", + "2021-06-01T00:00:00", + "2021-06-02T00:00:00", + "2021-06-03T00:00:00", + "2021-06-04T00:00:00", + "2021-06-05T00:00:00", + "2021-06-06T00:00:00", + "2021-06-07T00:00:00", + "2021-06-08T00:00:00", + "2021-06-09T00:00:00", + "2021-06-10T00:00:00", + "2021-06-11T00:00:00", + "2021-06-12T00:00:00", + "2021-06-13T00:00:00", + "2021-06-14T00:00:00", + "2021-06-15T00:00:00", + "2021-06-16T00:00:00", + "2021-06-17T00:00:00", + "2021-06-18T00:00:00", + "2021-06-19T00:00:00", + "2021-06-20T00:00:00", + "2021-06-21T00:00:00", + "2021-06-22T00:00:00", + "2021-06-23T00:00:00", + "2021-06-24T00:00:00", + "2021-06-25T00:00:00", + "2021-06-26T00:00:00", + "2021-06-27T00:00:00", + "2021-06-28T00:00:00", + "2021-06-29T00:00:00", + "2021-06-30T00:00:00", + "2021-07-01T00:00:00", + "2021-07-02T00:00:00", + "2021-07-03T00:00:00", + "2021-07-04T00:00:00", + "2021-07-05T00:00:00", + "2021-07-06T00:00:00", + "2021-07-07T00:00:00", + "2021-07-08T00:00:00", + "2021-07-09T00:00:00", + "2021-07-10T00:00:00", + "2021-07-11T00:00:00", + "2021-07-12T00:00:00", + "2021-07-13T00:00:00", + "2021-07-14T00:00:00", + "2021-07-15T00:00:00", + "2021-07-16T00:00:00", + "2021-07-17T00:00:00", + "2021-07-18T00:00:00", + "2021-07-19T00:00:00", + "2021-07-20T00:00:00", + "2021-07-21T00:00:00", + "2021-07-22T00:00:00", + "2021-07-23T00:00:00", + "2021-07-24T00:00:00", + "2021-07-25T00:00:00", + "2021-07-26T00:00:00", + "2021-07-27T00:00:00", + "2021-07-28T00:00:00", + "2021-07-29T00:00:00", + "2021-07-30T00:00:00", + "2021-07-31T00:00:00", + "2021-08-01T00:00:00", + "2021-08-02T00:00:00", + "2021-08-03T00:00:00", + "2021-08-04T00:00:00", + "2021-08-05T00:00:00", + "2021-08-06T00:00:00", + "2021-08-07T00:00:00", + "2021-08-08T00:00:00", + "2021-08-09T00:00:00", + "2021-08-10T00:00:00", + "2021-08-11T00:00:00", + "2021-08-12T00:00:00", + "2021-08-13T00:00:00", + "2021-08-14T00:00:00", + "2021-08-15T00:00:00", + "2021-08-16T00:00:00", + "2021-08-17T00:00:00", + "2021-08-18T00:00:00", + "2021-08-19T00:00:00", + "2021-08-20T00:00:00", + "2021-08-21T00:00:00", + "2021-08-22T00:00:00" + ], + "xaxis": "x", + "y": [ + 16104, + 15606, + 12363, + 12643, + 12753, + 12788, + 13657, + 15346, + 15560, + 12752, + 13147, + 13435, + 12698, + 13909, + 15657, + 16112, + 12783, + 13101, + 13460, + 12966, + 14084, + 15431, + 15346, + 12642, + 12528, + 13129, + 13827, + 14416, + 15937, + 16046, + 12835, + 12322, + 12451, + 12275, + 13342, + 15464, + 16275, + 14286, + 20420, + 23200, + 21274, + 22127, + 26161, + 28964, + 21625, + 22590, + 21406, + 19987, + 21406, + 23479, + 24767, + 26267, + 25983, + 23941, + 23510, + 23201, + 27550, + 25986, + 27242, + 20957, + 20578, + 20729, + 21152, + 24530, + 24914, + 20960, + 20574, + 21561, + 22712, + 25697, + 27895, + 29978, + 24317, + 23667, + 22529, + 23881, + 24131, + 29035, + 31308, + 26821, + 26587, + 27577, + 28683, + 33150, + 34795, + 37096, + 31402, + 31107, + 32896, + 38964, + 37935, + 38619, + 42125, + 38973, + 35993, + 57686, + 41440, + 42174, + 43679, + 47989, + 39127, + 39693, + 41688, + 38394, + 41428, + 45898, + 48903, + 43301, + 43887, + 67749, + 53900, + 46642, + 48832, + 52812, + 43375, + 41380, + 41163, + 41592, + 40955, + 44798, + 46250, + 42487, + 43764, + 43128, + 43010, + 44878, + 49714, + 54139, + 45541, + 44431, + 44422, + 46313, + 46911, + 50317, + 54378, + 48531, + 49324, + 50267, + 50585, + 53121, + 59499, + 62128, + 53495, + 52181, + 51911, + 51047, + 53745, + 59316, + 61454, + 52794, + 53712, + 55617, + 56497, + 55843, + 61644, + 66546, + 54546, + 54311, + 56789, + 58640, + 60145, + 68834, + 71171 + ], + "yaxis": "y" + } + ], + "layout": { + "barmode": "relative", + "legend": { + "title": { + "text": "variable" + }, + "tracegroupgap": 0 + }, + "margin": { + "t": 60 + }, + "template": { + "data": { + "bar": [ + { + "error_x": { + "color": "#2a3f5f" + }, + "error_y": { + "color": "#2a3f5f" + }, + "marker": { + "line": { + "color": "#E5ECF6", + "width": 0.5 + }, + "pattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + } + }, + "type": "bar" + } + ], + "barpolar": [ + { + "marker": { + "line": { + "color": "#E5ECF6", + "width": 0.5 + }, + "pattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + } + }, + "type": "barpolar" + } + ], + "carpet": [ + { + "aaxis": { + "endlinecolor": "#2a3f5f", + "gridcolor": "white", + "linecolor": "white", + "minorgridcolor": "white", + "startlinecolor": "#2a3f5f" + }, + "baxis": { + "endlinecolor": "#2a3f5f", + "gridcolor": "white", + "linecolor": "white", + "minorgridcolor": "white", + "startlinecolor": "#2a3f5f" + }, + "type": "carpet" + } + ], + "choropleth": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "choropleth" + } + ], + "contour": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "contour" + } + ], + "contourcarpet": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "contourcarpet" + } + ], + "heatmap": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "heatmap" + } + ], + "heatmapgl": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "heatmapgl" + } + ], + "histogram": [ + { + "marker": { + "pattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + } + }, + "type": "histogram" + } + ], + "histogram2d": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "histogram2d" + } + ], + "histogram2dcontour": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "histogram2dcontour" + } + ], + "mesh3d": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "mesh3d" + } + ], + "parcoords": [ + { + "line": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "parcoords" + } + ], + "pie": [ + { + "automargin": true, + "type": "pie" + } + ], + "scatter": [ + { + "fillpattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + }, + "type": "scatter" + } + ], + "scatter3d": [ + { + "line": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatter3d" + } + ], + "scattercarpet": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattercarpet" + } + ], + "scattergeo": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattergeo" + } + ], + "scattergl": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattergl" + } + ], + "scattermapbox": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattermapbox" + } + ], + "scatterpolar": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterpolar" + } + ], + "scatterpolargl": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterpolargl" + } + ], + "scatterternary": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterternary" + } + ], + "surface": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "surface" + } + ], + "table": [ + { + "cells": { + "fill": { + "color": "#EBF0F8" + }, + "line": { + "color": "white" + } + }, + "header": { + "fill": { + "color": "#C8D4E3" + }, + "line": { + "color": "white" + } + }, + "type": "table" + } + ] + }, + "layout": { + "annotationdefaults": { + "arrowcolor": "#2a3f5f", + "arrowhead": 0, + "arrowwidth": 1 + }, + "autotypenumbers": "strict", + "coloraxis": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "colorscale": { + "diverging": [ + [ + 0, + "#8e0152" + ], + [ + 0.1, + "#c51b7d" + ], + [ + 0.2, + "#de77ae" + ], + [ + 0.3, + "#f1b6da" + ], + [ + 0.4, + "#fde0ef" + ], + [ + 0.5, + "#f7f7f7" + ], + [ + 0.6, + "#e6f5d0" + ], + [ + 0.7, + "#b8e186" + ], + [ + 0.8, + "#7fbc41" + ], + [ + 0.9, + "#4d9221" + ], + [ + 1, + "#276419" + ] + ], + "sequential": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "sequentialminus": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ] + }, + "colorway": [ + "#636efa", + "#EF553B", + "#00cc96", + "#ab63fa", + "#FFA15A", + "#19d3f3", + "#FF6692", + "#B6E880", + "#FF97FF", + "#FECB52" + ], + "font": { + "color": "#2a3f5f" + }, + "geo": { + "bgcolor": "white", + "lakecolor": "white", + "landcolor": "#E5ECF6", + "showlakes": true, + "showland": true, + "subunitcolor": "white" + }, + "hoverlabel": { + "align": "left" + }, + "hovermode": "closest", + "mapbox": { + "style": "light" + }, + "paper_bgcolor": "white", + "plot_bgcolor": "#E5ECF6", + "polar": { + "angularaxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + }, + "bgcolor": "#E5ECF6", + "radialaxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + } + }, + "scene": { + "xaxis": { + "backgroundcolor": "#E5ECF6", + "gridcolor": "white", + "gridwidth": 2, + "linecolor": "white", + "showbackground": true, + "ticks": "", + "zerolinecolor": "white" + }, + "yaxis": { + "backgroundcolor": "#E5ECF6", + "gridcolor": "white", + "gridwidth": 2, + "linecolor": "white", + "showbackground": true, + "ticks": "", + "zerolinecolor": "white" + }, + "zaxis": { + "backgroundcolor": "#E5ECF6", + "gridcolor": "white", + "gridwidth": 2, + "linecolor": "white", + "showbackground": true, + "ticks": "", + "zerolinecolor": "white" + } + }, + "shapedefaults": { + "line": { + "color": "#2a3f5f" + } + }, + "ternary": { + "aaxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + }, + "baxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + }, + "bgcolor": "#E5ECF6", + "caxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + } + }, + "title": { + "x": 0.05 + }, + "xaxis": { + "automargin": true, + "gridcolor": "white", + "linecolor": "white", + "ticks": "", + "title": { + "standoff": 15 + }, + "zerolinecolor": "white", + "zerolinewidth": 2 + }, + "yaxis": { + "automargin": true, + "gridcolor": "white", + "linecolor": "white", + "ticks": "", + "title": { + "standoff": 15 + }, + "zerolinecolor": "white", + "zerolinewidth": 2 + } + } + }, + "xaxis": { + "anchor": "y", + "domain": [ + 0, + 1 + ], + "title": { + "text": "datetime" + } + }, + "yaxis": { + "anchor": "x", + "domain": [ + 0, + 1 + ], + "title": { + "text": "value" + } + } + } + }, + "text/html": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "fig = px.bar(interactions.groupby(Columns.Datetime)[Columns.User].agg('count'))\n", + "fig.show()" + ] + }, + { + "cell_type": "markdown", + "id": "43f216d0", + "metadata": {}, + "source": [ + "Из графика видны **недельные тенденции** просмотров, поэтому следует fold-ы разделять по 7 дней, но т.к. на семинаре дали \"намек\", что private dataset имеет количество дней, меньшее чем 7. Поэтому фолды будут разбиваться на **5 и 7 дней**" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "07fbdb30", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "6" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "pd.to_datetime('23-05-2021', format='%d-%m-%Y').weekday()" + ] + }, + { + "cell_type": "markdown", + "id": "2ff625b2", + "metadata": {}, + "source": [ + "### train test split" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "759ba346", + "metadata": {}, + "outputs": [], + "source": [ + "def create_data_range(\n", + " last_date: pd.Timestamp, \n", + " n_folds: int = 7, \n", + " unit: str = \"W\", \n", + " n_units: int = 1, \n", + " show: bool = True,\n", + "):\n", + " periods = n_folds + 1\n", + " freq = f\"{n_units}{unit}\"\n", + " \n", + " start_date = last_date - pd.Timedelta(n_folds * n_units + n_units, unit=unit) \n", + " \n", + " date_range = pd.date_range(start=start_date, periods=periods, freq=freq, tz=last_date.tz)\n", + " \n", + " if show:\n", + " print(\n", + " f\"start_date: {start_date}\\n\"\n", + " f\"last_date: {last_date}\\n\"\n", + " f\"periods: {periods}\\n\"\n", + " f\"freq: {freq}\\n\"\n", + " f\"Test fold borders: {date_range.values.astype('datetime64[D]')}\\n\"\n", + " )\n", + " \n", + " return date_range" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "38bfd397", + "metadata": {}, + "outputs": [], + "source": [ + "CONFIG_CV = {\n", + " \"cv_v1\": {\n", + " \"n_folds\": 7,\n", + " \"unit\": \"W\",\n", + " \"n_units\": 1,\n", + " },\n", + " \"cv_v2\": {\n", + " \"n_folds\": 7,\n", + " \"unit\": \"D\",\n", + " \"n_units\": 5,\n", + " }, \n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "f518e089", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Timestamp('2021-08-22 00:00:00')" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "last_date = interactions[Columns.Datetime].max().normalize()\n", + "last_date" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "1fd68b9b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "***Folds v1***\n", + "start_date: 2021-07-13 00:00:00\n", + "last_date: 2021-08-22 00:00:00\n", + "periods: 8\n", + "freq: 5D\n", + "Test fold borders: ['2021-07-13' '2021-07-18' '2021-07-23' '2021-07-28' '2021-08-02'\n", + " '2021-08-07' '2021-08-12' '2021-08-17']\n", + "\n" + ] + } + ], + "source": [ + "print(\"***Folds v1***\")\n", + "date_range_v1 = create_data_range(\n", + " last_date, \n", + " n_folds=CONFIG_CV[\"cv_v2\"][\"n_folds\"], \n", + " unit=CONFIG_CV[\"cv_v2\"][\"unit\"], \n", + " n_units=CONFIG_CV[\"cv_v2\"][\"n_units\"]\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "efc59555", + "metadata": {}, + "source": [ + "**генерируем фолды** " + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "9fae43f6", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Real number of folds: 7\n" + ] + } + ], + "source": [ + "cv_v1 = TimeRangeSplitter(\n", + " date_range=date_range_v1,\n", + " filter_already_seen=True,\n", + " filter_cold_items=True,\n", + " filter_cold_users=True,\n", + ")\n", + "print(f\"Real number of folds: {cv_v1.get_n_splits(Interactions(interactions))}\")\n", + "\n", + "CV = [cv_v1]" + ] + }, + { + "cell_type": "markdown", + "id": "e15a83a7", + "metadata": {}, + "source": [ + "**Формируем метрики**" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "8f7742c6", + "metadata": {}, + "outputs": [], + "source": [ + "metrics = {\n", + " \"prec@10\": Precision(k=10),\n", + " \"recall@10\": Recall(k=10),\n", + " \"MAP@10\": MAP(k=10),\n", + " \"novelty\": MeanInvUserFreq(k=10),\n", + " \"serendipity\": Serendipity(k=10),\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "b21a1ecf", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'cosine_userknn_K30': ,\n", + " 'tfidf_userknn_K30': ,\n", + " 'bm25_userknn_K30': ,\n", + " 'cosine_userknn_K40': ,\n", + " 'tfidf_userknn_K40': ,\n", + " 'bm25_userknn_K40': }" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "K = [30, 40]\n", + "models = dict()\n", + "\n", + "for k in K:\n", + " models[f\"cosine_userknn_K{k}\"] = CosineRecommender(K=k)\n", + " models[f\"tfidf_userknn_K{k}\"] = TFIDFRecommender(K=k)\n", + " models[f\"bm25_userknn_K{k}\"] = BM25Recommender(K=k)\n", + "\n", + "models" + ] + }, + { + "cell_type": "markdown", + "id": "0103149a", + "metadata": {}, + "source": [ + "## Training" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "e78b8221", + "metadata": {}, + "outputs": [], + "source": [ + "N_USERS = 50" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "50dcff0b", + "metadata": {}, + "outputs": [], + "source": [ + "%%time\n", + "\n", + "results = []\n", + "\n", + "for idx, cv in enumerate(CV):\n", + " print(f\"\\n CV version {idx}\")\n", + " fold_iterator = cv.split(Interactions(interactions), collect_fold_stats=True)\n", + "\n", + " for i_fold, (train_ids, test_ids, fold_info) in enumerate(fold_iterator):\n", + " print(f\"\\n==================== Fold {i_fold}\")\n", + " pprint(fold_info)\n", + "\n", + " df_train = interactions.iloc[train_ids].copy()\n", + " df_test = interactions.iloc[test_ids][Columns.UserItem].copy()\n", + "\n", + " catalog = df_train[Columns.Item].unique()\n", + "\n", + " for model_name, model in models.items():\n", + " userknn_model = UserKnn(model=model, N_users=N_USERS, use_weight_idf=True)\n", + " userknn_model.fit(df_train)\n", + "\n", + " if 'bm25' in model_name:\n", + " recos = userknn_model.predict(df_test, bmp25=True)\n", + " else:\n", + " recos = userknn_model.predict(df_test)\n", + "\n", + " metric_values = calc_metrics(\n", + " metrics,\n", + " reco=recos,\n", + " interactions=df_test,\n", + " prev_interactions=df_train,\n", + " catalog=catalog,\n", + " )\n", + "\n", + " full_model_name = f\"{model_name}_cv-{idx}\"\n", + " fold = {\"fold\": i_fold, \"model\": full_model_name}\n", + " fold.update(metric_values)\n", + " results.append(fold)" + ] + }, + { + "cell_type": "markdown", + "id": "708ec5c2", + "metadata": {}, + "source": [ + "Работало больше 10 часов, случайно при перезапуске ноутбука была вызвана ячейка и остановлена, поэтому завершилась с ошибкой, поэтому ошибку убрали для лучшего вида" + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "id": "d7e2ffa7", + "metadata": { + "collapsed": true + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
foldmodelprec@10recall@10MAP@10noveltyserendipity
00cosine_userknn_K30_cv-00.0035570.0211280.0036958.3314910.000040
10tfidf_userknn_K30_cv-00.0064390.0391020.0073358.1550510.000048
20bm25_userknn_K30_cv-00.0025930.0134940.0025319.3984670.000081
30cosine_userknn_K40_cv-00.0032820.0193230.0034018.5615230.000043
40tfidf_userknn_K40_cv-00.0061780.0374580.0069578.3004040.000052
50bm25_userknn_K40_cv-00.0022410.0112550.0022109.6755330.000081
61cosine_userknn_K30_cv-00.0035050.0200020.0035808.3982480.000046
71tfidf_userknn_K30_cv-00.0063280.0368440.0070228.2401330.000058
81bm25_userknn_K30_cv-00.0027220.0138560.0026589.4846920.000088
91cosine_userknn_K40_cv-00.0032450.0183680.0033058.6269060.000047
101tfidf_userknn_K40_cv-00.0061500.0359640.0069168.3779880.000061
111bm25_userknn_K40_cv-00.0024060.0120670.0023939.7564580.000086
122cosine_userknn_K30_cv-00.0032610.0184980.0032958.4392630.000047
132tfidf_userknn_K30_cv-00.0059400.0342330.0064798.2623670.000059
142bm25_userknn_K30_cv-00.0027200.0134220.0025309.5356310.000091
152cosine_userknn_K40_cv-00.0030450.0170860.0031008.6615850.000050
162tfidf_userknn_K40_cv-00.0059140.0340710.0064398.3966180.000063
172bm25_userknn_K40_cv-00.0024040.0116380.0022319.7991190.000090
183cosine_userknn_K30_cv-00.0032770.0187860.0033958.4449860.000045
193tfidf_userknn_K30_cv-00.0060230.0341710.0063288.2765030.000059
203bm25_userknn_K30_cv-00.0026200.0127620.0024979.5609840.000091
213cosine_userknn_K40_cv-00.0030760.0175120.0031738.6581500.000045
223tfidf_userknn_K40_cv-00.0059190.0333680.0062538.3991690.000062
233bm25_userknn_K40_cv-00.0023370.0112730.0022539.8163250.000089
244cosine_userknn_K30_cv-00.0031180.0180640.0031578.4858990.000042
254tfidf_userknn_K30_cv-00.0059110.0336260.0063968.2824280.000059
264bm25_userknn_K30_cv-00.0025370.0123680.0024709.5996450.000086
274cosine_userknn_K40_cv-00.0028720.0165090.0028838.7119840.000043
284tfidf_userknn_K40_cv-00.0057930.0330280.0062618.4166800.000062
294bm25_userknn_K40_cv-00.0022130.0108600.0021799.8662010.000085
305cosine_userknn_K30_cv-00.0030030.0162520.0028998.4989680.000043
315tfidf_userknn_K30_cv-00.0055270.0309420.0058238.3252730.000057
325bm25_userknn_K30_cv-00.0025970.0122630.0023869.6469570.000100
335cosine_userknn_K40_cv-00.0027650.0147130.0026618.7175590.000047
345tfidf_userknn_K40_cv-00.0055450.0308920.0058178.4540910.000059
355bm25_userknn_K40_cv-00.0023020.0107770.0021359.9140420.000100
366cosine_userknn_K30_cv-00.0029630.0165320.0028878.5638090.000050
376tfidf_userknn_K30_cv-00.0053300.0307170.0057638.3662590.000064
386bm25_userknn_K30_cv-00.0025710.0126910.0024789.7150970.000100
396cosine_userknn_K40_cv-00.0027690.0154480.0026758.7750580.000051
406tfidf_userknn_K40_cv-00.0052840.0304180.0056978.4884730.000066
416bm25_userknn_K40_cv-00.0023400.0112780.0022089.9646640.000099
\n", + "
" + ], + "text/plain": [ + " fold model prec@10 recall@10 MAP@10 novelty \\\n", + "0 0 cosine_userknn_K30_cv-0 0.003557 0.021128 0.003695 8.331491 \n", + "1 0 tfidf_userknn_K30_cv-0 0.006439 0.039102 0.007335 8.155051 \n", + "2 0 bm25_userknn_K30_cv-0 0.002593 0.013494 0.002531 9.398467 \n", + "3 0 cosine_userknn_K40_cv-0 0.003282 0.019323 0.003401 8.561523 \n", + "4 0 tfidf_userknn_K40_cv-0 0.006178 0.037458 0.006957 8.300404 \n", + "5 0 bm25_userknn_K40_cv-0 0.002241 0.011255 0.002210 9.675533 \n", + "6 1 cosine_userknn_K30_cv-0 0.003505 0.020002 0.003580 8.398248 \n", + "7 1 tfidf_userknn_K30_cv-0 0.006328 0.036844 0.007022 8.240133 \n", + "8 1 bm25_userknn_K30_cv-0 0.002722 0.013856 0.002658 9.484692 \n", + "9 1 cosine_userknn_K40_cv-0 0.003245 0.018368 0.003305 8.626906 \n", + "10 1 tfidf_userknn_K40_cv-0 0.006150 0.035964 0.006916 8.377988 \n", + "11 1 bm25_userknn_K40_cv-0 0.002406 0.012067 0.002393 9.756458 \n", + "12 2 cosine_userknn_K30_cv-0 0.003261 0.018498 0.003295 8.439263 \n", + "13 2 tfidf_userknn_K30_cv-0 0.005940 0.034233 0.006479 8.262367 \n", + "14 2 bm25_userknn_K30_cv-0 0.002720 0.013422 0.002530 9.535631 \n", + "15 2 cosine_userknn_K40_cv-0 0.003045 0.017086 0.003100 8.661585 \n", + "16 2 tfidf_userknn_K40_cv-0 0.005914 0.034071 0.006439 8.396618 \n", + "17 2 bm25_userknn_K40_cv-0 0.002404 0.011638 0.002231 9.799119 \n", + "18 3 cosine_userknn_K30_cv-0 0.003277 0.018786 0.003395 8.444986 \n", + "19 3 tfidf_userknn_K30_cv-0 0.006023 0.034171 0.006328 8.276503 \n", + "20 3 bm25_userknn_K30_cv-0 0.002620 0.012762 0.002497 9.560984 \n", + "21 3 cosine_userknn_K40_cv-0 0.003076 0.017512 0.003173 8.658150 \n", + "22 3 tfidf_userknn_K40_cv-0 0.005919 0.033368 0.006253 8.399169 \n", + "23 3 bm25_userknn_K40_cv-0 0.002337 0.011273 0.002253 9.816325 \n", + "24 4 cosine_userknn_K30_cv-0 0.003118 0.018064 0.003157 8.485899 \n", + "25 4 tfidf_userknn_K30_cv-0 0.005911 0.033626 0.006396 8.282428 \n", + "26 4 bm25_userknn_K30_cv-0 0.002537 0.012368 0.002470 9.599645 \n", + "27 4 cosine_userknn_K40_cv-0 0.002872 0.016509 0.002883 8.711984 \n", + "28 4 tfidf_userknn_K40_cv-0 0.005793 0.033028 0.006261 8.416680 \n", + "29 4 bm25_userknn_K40_cv-0 0.002213 0.010860 0.002179 9.866201 \n", + "30 5 cosine_userknn_K30_cv-0 0.003003 0.016252 0.002899 8.498968 \n", + "31 5 tfidf_userknn_K30_cv-0 0.005527 0.030942 0.005823 8.325273 \n", + "32 5 bm25_userknn_K30_cv-0 0.002597 0.012263 0.002386 9.646957 \n", + "33 5 cosine_userknn_K40_cv-0 0.002765 0.014713 0.002661 8.717559 \n", + "34 5 tfidf_userknn_K40_cv-0 0.005545 0.030892 0.005817 8.454091 \n", + "35 5 bm25_userknn_K40_cv-0 0.002302 0.010777 0.002135 9.914042 \n", + "36 6 cosine_userknn_K30_cv-0 0.002963 0.016532 0.002887 8.563809 \n", + "37 6 tfidf_userknn_K30_cv-0 0.005330 0.030717 0.005763 8.366259 \n", + "38 6 bm25_userknn_K30_cv-0 0.002571 0.012691 0.002478 9.715097 \n", + "39 6 cosine_userknn_K40_cv-0 0.002769 0.015448 0.002675 8.775058 \n", + "40 6 tfidf_userknn_K40_cv-0 0.005284 0.030418 0.005697 8.488473 \n", + "41 6 bm25_userknn_K40_cv-0 0.002340 0.011278 0.002208 9.964664 \n", + "\n", + " serendipity \n", + "0 0.000040 \n", + "1 0.000048 \n", + "2 0.000081 \n", + "3 0.000043 \n", + "4 0.000052 \n", + "5 0.000081 \n", + "6 0.000046 \n", + "7 0.000058 \n", + "8 0.000088 \n", + "9 0.000047 \n", + "10 0.000061 \n", + "11 0.000086 \n", + "12 0.000047 \n", + "13 0.000059 \n", + "14 0.000091 \n", + "15 0.000050 \n", + "16 0.000063 \n", + "17 0.000090 \n", + "18 0.000045 \n", + "19 0.000059 \n", + "20 0.000091 \n", + "21 0.000045 \n", + "22 0.000062 \n", + "23 0.000089 \n", + "24 0.000042 \n", + "25 0.000059 \n", + "26 0.000086 \n", + "27 0.000043 \n", + "28 0.000062 \n", + "29 0.000085 \n", + "30 0.000043 \n", + "31 0.000057 \n", + "32 0.000100 \n", + "33 0.000047 \n", + "34 0.000059 \n", + "35 0.000100 \n", + "36 0.000050 \n", + "37 0.000064 \n", + "38 0.000100 \n", + "39 0.000051 \n", + "40 0.000066 \n", + "41 0.000099 " + ] + }, + "execution_count": 46, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df_metrics = pd.DataFrame(results)\n", + "df_metrics" + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "id": "a0334b9a", + "metadata": {}, + "outputs": [], + "source": [ + "df_metrics.to_pickle(\"../data/hw_3/df_metrics.pickle\")" + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "id": "446530ce", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
foldprec@10recall@10MAP@10noveltyserendipity
model
bm25_userknn_K30_cv-03.00.0026230.0129800.0025079.5630680.000091
bm25_userknn_K40_cv-03.00.0023200.0113070.0022309.8274770.000090
cosine_userknn_K30_cv-03.00.0032410.0184660.0032728.4518090.000045
cosine_userknn_K40_cv-03.00.0030080.0169940.0030288.6732520.000047
tfidf_userknn_K30_cv-03.00.0059280.0342340.0064498.2725730.000058
tfidf_userknn_K40_cv-03.00.0058260.0336000.0063348.4047750.000061
\n", + "
" + ], + "text/plain": [ + " fold prec@10 recall@10 MAP@10 novelty \\\n", + "model \n", + "bm25_userknn_K30_cv-0 3.0 0.002623 0.012980 0.002507 9.563068 \n", + "bm25_userknn_K40_cv-0 3.0 0.002320 0.011307 0.002230 9.827477 \n", + "cosine_userknn_K30_cv-0 3.0 0.003241 0.018466 0.003272 8.451809 \n", + "cosine_userknn_K40_cv-0 3.0 0.003008 0.016994 0.003028 8.673252 \n", + "tfidf_userknn_K30_cv-0 3.0 0.005928 0.034234 0.006449 8.272573 \n", + "tfidf_userknn_K40_cv-0 3.0 0.005826 0.033600 0.006334 8.404775 \n", + "\n", + " serendipity \n", + "model \n", + "bm25_userknn_K30_cv-0 0.000091 \n", + "bm25_userknn_K40_cv-0 0.000090 \n", + "cosine_userknn_K30_cv-0 0.000045 \n", + "cosine_userknn_K40_cv-0 0.000047 \n", + "tfidf_userknn_K30_cv-0 0.000058 \n", + "tfidf_userknn_K40_cv-0 0.000061 " + ] + }, + "execution_count": 48, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df_metrics.groupby('model').mean()" + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "id": "5fb9ba9f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
prec@10recall@10MAP@10noveltyserendipity
model
bm25_userknn_K30_cv-00.0000720.0006120.0000830.1044680.000007
bm25_userknn_K40_cv-00.0000740.0004420.0000810.0973590.000007
cosine_userknn_K30_cv-00.0002310.0017490.0003140.0746990.000003
cosine_userknn_K40_cv-00.0002130.0016030.0002950.0693100.000003
tfidf_userknn_K30_cv-00.0003980.0030030.0005770.0666270.000005
tfidf_userknn_K40_cv-00.0003210.0025340.0004870.0595650.000004
\n", + "
" + ], + "text/plain": [ + " prec@10 recall@10 MAP@10 novelty serendipity\n", + "model \n", + "bm25_userknn_K30_cv-0 0.000072 0.000612 0.000083 0.104468 0.000007\n", + "bm25_userknn_K40_cv-0 0.000074 0.000442 0.000081 0.097359 0.000007\n", + "cosine_userknn_K30_cv-0 0.000231 0.001749 0.000314 0.074699 0.000003\n", + "cosine_userknn_K40_cv-0 0.000213 0.001603 0.000295 0.069310 0.000003\n", + "tfidf_userknn_K30_cv-0 0.000398 0.003003 0.000577 0.066627 0.000005\n", + "tfidf_userknn_K40_cv-0 0.000321 0.002534 0.000487 0.059565 0.000004" + ] + }, + "execution_count": 49, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df_metrics.groupby('model').std()[metrics.keys()]" + ] + }, + { + "cell_type": "markdown", + "id": "41828ee5", + "metadata": {}, + "source": [ + "по **ofline** метрикам лучше всего себя показывает модель TFIDFRecommender\n", + "TFIDFRecommender подбор К" + ] + }, + { + "cell_type": "markdown", + "id": "7a8a0a41", + "metadata": {}, + "source": [ + "# Подбор оптимального K для TFIDFRecommender" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "1e91892d", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'tfidf_userknn_K50': ,\n", + " 'tfidf_userknn_K60': ,\n", + " 'tfidf_userknn_K70': }" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "N_USERS = 50\n", + "\n", + "# Т.к. метрики для К 30 и 40 уже есть\n", + "K = [k for k in range(50, 71, 10)]\n", + "models = dict()\n", + "\n", + "for k in K:\n", + " models[f\"tfidf_userknn_K{k}\"] = TFIDFRecommender(K=k)\n", + "models" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e7c2c43b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "==================== Fold 0\n", + "{'End date': Timestamp('2021-07-18 00:00:00', freq='5D'),\n", + " 'Start date': Timestamp('2021-07-13 00:00:00', freq='5D'),\n", + " 'Test': 156580,\n", + " 'Test items': 5793,\n", + " 'Test users': 68150,\n", + " 'Train': 3281612,\n", + " 'Train items': 14754,\n", + " 'Train users': 652905}\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "211234f034a54bae86b94dff33b9f5c4", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + " 0%| | 0/652905 [00:00\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
user_iditem_idlast_watch_dttotal_durwatched_pct
017654995062021-05-11425072.0
169931716592021-05-298317100.0
265668371072021-05-09100.0
386461376382021-07-0514483100.0
496486895062021-04-306725100.0
5476246648596122252021-08-13760.0
547624754686296732021-04-13230849.0
5476248697262152972021-08-201830763.0
5476249384202161972021-04-196203100.0
547625031970944362021-08-15392145.0
\n", + "" + ], + "text/plain": [ + " user_id item_id last_watch_dt total_dur watched_pct\n", + "0 176549 9506 2021-05-11 4250 72.0\n", + "1 699317 1659 2021-05-29 8317 100.0\n", + "2 656683 7107 2021-05-09 10 0.0\n", + "3 864613 7638 2021-07-05 14483 100.0\n", + "4 964868 9506 2021-04-30 6725 100.0\n", + "5476246 648596 12225 2021-08-13 76 0.0\n", + "5476247 546862 9673 2021-04-13 2308 49.0\n", + "5476248 697262 15297 2021-08-20 18307 63.0\n", + "5476249 384202 16197 2021-04-19 6203 100.0\n", + "5476250 319709 4436 2021-08-15 3921 45.0" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "pd.concat([interactions.head(), interactions.tail()])" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "dc4d9fd7", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(962179,)" + ] + }, + "execution_count": 33, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "interactions['user_id'].unique().shape" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "b7861d19", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[(961833, 1.0),\n", + " (961849, 1.0),\n", + " (961857, 1.0),\n", + " (961871, 1.0),\n", + " (961873, 1.0),\n", + " (961876, 1.0),\n", + " (961887, 1.0),\n", + " (961907, 1.0),\n", + " (961910, 1.0),\n", + " (961912, 1.0)]" + ] + }, + "execution_count": 35, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import dill\n", + "\n", + "with open('../service/weights/userKNN/userknn_tfidf_k30.dill', 'rb') as f:\n", + " userknn = dill.load(f)\n", + "\n", + "userknn.similar_items(962178, 10)" + ] + }, + { + "cell_type": "markdown", + "id": "1905033a", + "metadata": {}, + "source": [ + "# Popular Model" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "id": "2df74dba", + "metadata": {}, + "outputs": [], + "source": [ + "from rectools.models import PopularModel\n", + "from rectools.dataset import Dataset" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "6ba37a73", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Timestamp('2021-08-22 00:00:00')" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "max_date = interactions[Columns.Datetime].max().normalize()\n", + "max_date" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "901353f9", + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "train = interactions[[Columns.User, Columns.Item, Columns.Weight, Columns.Datetime]][\n", + " interactions[Columns.Datetime] < max_date - pd.Timedelta(5, \"D\")]\n", + "\n", + "test = interactions[[Columns.User, Columns.Item, Columns.Weight, Columns.Datetime]][\n", + " interactions[Columns.Datetime] >= max_date - pd.Timedelta(5, \"D\")]\n", + "\n", + "dataset_train = Dataset.construct(train)" + ] + }, + { + "cell_type": "code", + "execution_count": 144, + "id": "f08e3579", + "metadata": {}, + "outputs": [], + "source": [ + "popilarity_models = {\n", + " \"popular\": PopularModel(),\n", + " \"popular_mw\": PopularModel(popularity=\"mean_weight\")\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 145, + "id": "03c3bfb6", + "metadata": {}, + "outputs": [], + "source": [ + "popilarity_models[\"popular\"].fit(dataset_train)\n", + "popilarity_models[\"popular_mw\"].fit(dataset_train);" + ] + }, + { + "cell_type": "code", + "execution_count": 146, + "id": "0d7de49e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([ 24, 20, 31, 15, 167, 81, 89, 135, 355, 116])" + ] + }, + "execution_count": 146, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "popilarity_models[\"popular\"].popularity_list[0][:10]" + ] + }, + { + "cell_type": "code", + "execution_count": 147, + "id": "05ff208d", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([11363, 11681, 12841, 13017, 2069, 13691, 13552, 13397, 11774,\n", + " 12913])" + ] + }, + "execution_count": 147, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "popilarity_models[\"popular_mw\"].popularity_list[0][:10]" + ] + }, + { + "cell_type": "code", + "execution_count": 148, + "id": "00ef735c", + "metadata": {}, + "outputs": [], + "source": [ + "pecos_pop = popilarity_models[\"popular\"].recommend(\n", + " users=test[Columns.User].unique(),\n", + " dataset=dataset,\n", + " k=100,\n", + " filter_viewed=False,\n", + ")\n", + "\n", + "pecos_pop_mw = popilarity_models[\"popular_mw\"].recommend(\n", + " users=test[Columns.User].unique(),\n", + " dataset=dataset,\n", + " k=100,\n", + " filter_viewed=False,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 152, + "id": "b302db55", + "metadata": {}, + "outputs": [], + "source": [ + "metrics = {\n", + " \"prec@5\": Precision(k=5),\n", + " \"recall@5\": Recall(k=5),\n", + " \"MAP@5\": MAP(k=5),\n", + " \"prec@10\": Precision(k=10),\n", + " \"recall@10\": Recall(k=10),\n", + " \"MAP@20\": MAP(k=20),\n", + " \"prec@20\": Precision(k=20),\n", + " \"recall@20\": Recall(k=20),\n", + " \"MAP@100\": MAP(k=100),\n", + " \"prec@100\": Precision(k=100),\n", + " \"recall@100\": Recall(k=100),\n", + " \"MAP@100\": MAP(k=100),\n", + " \"novelty\": MeanInvUserFreq(k=10),\n", + " \"serendipity\": Serendipity(k=10),\n", + "}\n", + "catalog = train[Columns.Item].unique()\n", + "metric_values_pop = calc_metrics(metrics, pecos_pop, test, train, catalog)\n", + "metric_values_pop_mean_weight = calc_metrics(metrics, pecos_pop_mw, test, train, catalog)" + ] + }, + { + "cell_type": "code", + "execution_count": 153, + "id": "9631093b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'prec@5': 0.0017855613317256697,\n", + " 'recall@5': 0.004623809755660008,\n", + " 'prec@10': 0.0011648975773029461,\n", + " 'recall@10': 0.005682095875283048,\n", + " 'prec@20': 0.0010502526799891945,\n", + " 'recall@20': 0.00880186008464912,\n", + " 'prec@100': 0.003247020220987923,\n", + " 'recall@100': 0.16609031082955295,\n", + " 'MAP@5': 0.0013179725619140792,\n", + " 'MAP@20': 0.0016695313583723814,\n", + " 'MAP@100': 0.005578924867474493,\n", + " 'novelty': 9.976033936531364,\n", + " 'serendipity': 1.2752762676592953e-05}" + ] + }, + "execution_count": 153, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "metric_values_pop" + ] + }, + { + "cell_type": "code", + "execution_count": 154, + "id": "5d55b781", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'prec@5': 9.09252633867684e-05,\n", + " 'recall@5': 0.00014799438063171262,\n", + " 'prec@10': 4.612151041357817e-05,\n", + " 'recall@10': 0.00015458316783365238,\n", + " 'prec@20': 2.635514880775895e-05,\n", + " 'recall@20': 0.00016946607539568094,\n", + " 'prec@100': 0.00015147621777259455,\n", + " 'recall@100': 0.0065476971391510656,\n", + " 'MAP@5': 3.0257754846536496e-05,\n", + " 'MAP@20': 3.1771198360212185e-05,\n", + " 'MAP@100': 0.00011355765992119742,\n", + " 'novelty': 17.423655787689828,\n", + " 'serendipity': 1.8991632826477633e-06}" + ] + }, + "execution_count": 154, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "metric_values_pop_mean_weight" + ] + }, + { + "cell_type": "markdown", + "id": "e5a4a011", + "metadata": {}, + "source": [ + "**На офлайн метриках выигрывает обычная модель по популярному**" + ] + }, + { + "cell_type": "markdown", + "id": "5875fab7", + "metadata": {}, + "source": [ + "# Save item_idf data" + ] + }, + { + "cell_type": "markdown", + "id": "6589996f", + "metadata": {}, + "source": [ + "Создаем датасет со взвешенными item-ами по механизму idf для использования в будущем" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "id": "d62cabb9", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
indexidf
095067.150811
116598.524953
271075.821207
376388.407093
466867.778734
.........
15701783314.822785
15702912514.822785
157031006414.822785
157041301914.822785
157051054214.822785
\n", + "

15706 rows × 2 columns

\n", + "
" + ], + "text/plain": [ + " index idf\n", + "0 9506 7.150811\n", + "1 1659 8.524953\n", + "2 7107 5.821207\n", + "3 7638 8.407093\n", + "4 6686 7.778734\n", + "... ... ...\n", + "15701 7833 14.822785\n", + "15702 9125 14.822785\n", + "15703 10064 14.822785\n", + "15704 13019 14.822785\n", + "15705 10542 14.822785\n", + "\n", + "[15706 rows x 2 columns]" + ] + }, + "execution_count": 40, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "item_cnt = Counter(interactions['item_id'].values)\n", + "item_idf = pd.DataFrame.from_dict(item_cnt, orient='index', columns=['doc_freq']).reset_index()\n", + "n = interactions.shape[0]\n", + "item_idf['idf'] = item_idf['doc_freq'].apply(lambda x: np.log((1 + n) / (1 + x) + 1))\n", + "del item_idf['doc_freq']\n", + "item_idf" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "id": "7da47dfc", + "metadata": {}, + "outputs": [], + "source": [ + "item_idf = item_idf.sort_values(\"idf\", ascending=False)\n", + "item_idf.to_csv('../data/kion_train/items_idf.csv', index=False)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fdce2b60", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.8.10" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/notebooks/HW-3.1.ipynb b/notebooks/HW-3.1.ipynb new file mode 100644 index 00000000..c05a3b71 --- /dev/null +++ b/notebooks/HW-3.1.ipynb @@ -0,0 +1,4027 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "398a86d9", + "metadata": {}, + "outputs": [], + "source": [ + "from pprint import pprint\n", + "import warnings\n", + "warnings.filterwarnings(\"ignore\")\n", + "\n", + "import sys\n", + "sys.path.append('../')" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "8dbe6bf0", + "metadata": {}, + "outputs": [], + "source": [ + "import plotly.express as px\n", + "import numpy as np\n", + "import pandas as pd\n", + "import scipy as sp\n", + "import requests\n", + "from tqdm.auto import tqdm\n", + "from scipy.stats import mode\n", + "from implicit.nearest_neighbours import CosineRecommender, TFIDFRecommender, BM25Recommender\n", + "from rectools import Columns\n", + "from rectools.model_selection import TimeRangeSplitter\n", + "from rectools.metrics import Precision, Recall, MAP, MeanInvUserFreq, Serendipity, calc_metrics\n", + "from rectools.dataset.interactions import Interactions\n", + "\n", + "from service.utils.user_knn import UserKnn" + ] + }, + { + "cell_type": "markdown", + "id": "b1baa79f", + "metadata": {}, + "source": [ + "# Data" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "f2a9e540", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "((5476251, 5), (840197, 5), (15963, 14))" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "interactions = pd.read_csv('../data/kion_train/interactions.csv')\n", + "users = pd.read_csv('../data/kion_train/users.csv')\n", + "items = pd.read_csv('../data/kion_train/items.csv')\n", + "\n", + "interactions.shape, users.shape, items.shape" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "456d25f4", + "metadata": {}, + "outputs": [], + "source": [ + "interactions.rename(\n", + " columns={\n", + " 'last_watch_dt': Columns.Datetime,\n", + " 'total_dur': Columns.Weight\n", + " }, \n", + " inplace=True) \n", + "\n", + "interactions[Columns.Datetime] = pd.to_datetime(interactions[Columns.Datetime])" + ] + }, + { + "cell_type": "markdown", + "id": "6f7b9b0c", + "metadata": {}, + "source": [ + "## Intersection" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "7c9c0c94", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
user_iditem_iddatetimeweightwatched_pct
017654995062021-05-11425072.0
169931716592021-05-298317100.0
265668371072021-05-09100.0
386461376382021-07-0514483100.0
496486895062021-04-306725100.0
5476246648596122252021-08-13760.0
547624754686296732021-04-13230849.0
5476248697262152972021-08-201830763.0
5476249384202161972021-04-196203100.0
547625031970944362021-08-15392145.0
\n", + "
" + ], + "text/plain": [ + " user_id item_id datetime weight watched_pct\n", + "0 176549 9506 2021-05-11 4250 72.0\n", + "1 699317 1659 2021-05-29 8317 100.0\n", + "2 656683 7107 2021-05-09 10 0.0\n", + "3 864613 7638 2021-07-05 14483 100.0\n", + "4 964868 9506 2021-04-30 6725 100.0\n", + "5476246 648596 12225 2021-08-13 76 0.0\n", + "5476247 546862 9673 2021-04-13 2308 49.0\n", + "5476248 697262 15297 2021-08-20 18307 63.0\n", + "5476249 384202 16197 2021-04-19 6203 100.0\n", + "5476250 319709 4436 2021-08-15 3921 45.0" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "pd.concat([interactions.head(), interactions.tail()])" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "c5c3ce6c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Interactions dataframe shape: (5476251, 5)\n", + "Unique users in interactions: 962179\n", + "Unique items in interactions: 15706\n" + ] + } + ], + "source": [ + "print(f\"Interactions dataframe shape: {interactions.shape}\")\n", + "print(f\"Unique users in interactions: {interactions[Columns.User].nunique()}\")\n", + "print(f\"Unique items in interactions: {interactions[Columns.Item].nunique()}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "0214a978", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "min date in interactions: 2021-03-13 00:00:00\n", + "max date in interactions: 2021-08-22 00:00:00\n" + ] + } + ], + "source": [ + "max_date = interactions[Columns.Datetime].max()\n", + "min_date = interactions[Columns.Datetime].min()\n", + "\n", + "print(f\"min date in interactions: {min_date}\")\n", + "print(f\"max date in interactions: {max_date}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "7829e796", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "RangeIndex: 5476251 entries, 0 to 5476250\n", + "Data columns (total 5 columns):\n", + " # Column Dtype \n", + "--- ------ ----- \n", + " 0 user_id int64 \n", + " 1 item_id int64 \n", + " 2 datetime datetime64[ns]\n", + " 3 weight int64 \n", + " 4 watched_pct float64 \n", + "dtypes: datetime64[ns](1), float64(1), int64(3)\n", + "memory usage: 208.9 MB\n" + ] + } + ], + "source": [ + "interactions.info()" + ] + }, + { + "cell_type": "markdown", + "id": "57cddf34", + "metadata": {}, + "source": [ + "## Users" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "de5dea16", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
user_idageincomesexkids_flg
0973171age_25_34income_60_90М1
1962099age_18_24income_20_40М0
21047345age_45_54income_40_60Ж0
3721985age_45_54income_20_40Ж0
4704055age_35_44income_60_90Ж0
840192339025age_65_infincome_0_20Ж0
840193983617age_18_24income_20_40Ж1
840194251008NaNNaNNaN0
840195590706NaNNaNЖ0
840196166555age_65_infincome_20_40Ж0
\n", + "
" + ], + "text/plain": [ + " user_id age income sex kids_flg\n", + "0 973171 age_25_34 income_60_90 М 1\n", + "1 962099 age_18_24 income_20_40 М 0\n", + "2 1047345 age_45_54 income_40_60 Ж 0\n", + "3 721985 age_45_54 income_20_40 Ж 0\n", + "4 704055 age_35_44 income_60_90 Ж 0\n", + "840192 339025 age_65_inf income_0_20 Ж 0\n", + "840193 983617 age_18_24 income_20_40 Ж 1\n", + "840194 251008 NaN NaN NaN 0\n", + "840195 590706 NaN NaN Ж 0\n", + "840196 166555 age_65_inf income_20_40 Ж 0" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "pd.concat([users.head(), users.tail()])" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "e4e6d2f5", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Users dataframe shape (840197, 5)\n", + "Unique users: 840197\n" + ] + } + ], + "source": [ + "print(f\"Users dataframe shape {users.shape}\")\n", + "print(f\"Unique users: {users['user_id'].nunique()}\")" + ] + }, + { + "cell_type": "markdown", + "id": "98b4ff6c", + "metadata": {}, + "source": [ + "## Items" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "19b43ff0", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
item_idcontent_typetitletitle_origrelease_yeargenrescountriesfor_kidsage_ratingstudiosdirectorsactorsdescriptionkeywords
010711filmПоговори с нейHable con ella2002.0драмы, зарубежные, детективы, мелодрамыИспанияNaN16.0NaNПедро АльмодоварАдольфо Фернандес, Ана Фернандес, Дарио Гранди...Мелодрама легендарного Педро Альмодовара «Пого...Поговори, ней, 2002, Испания, друзья, любовь, ...
12508filmГолые перцыSearch Party2014.0зарубежные, приключения, комедииСШАNaN16.0NaNСкот АрмстронгАдам Палли, Брайан Хаски, Дж.Б. Смув, Джейсон ...Уморительная современная комедия на популярную...Голые, перцы, 2014, США, друзья, свадьбы, прео...
159614538seriesСреди камнейDarklands2019.0драмы, спорт, криминалРоссия0.018.0NaNМарк О’Коннор, Конор МакМахонДэйн Уайт О’Хара, Томас Кэйн-Бирн, Джудит Родд...Семнадцатилетний Дэмиен мечтает вырваться за п...Среди, камней, 2019, Россия
159623206seriesГошаNaN2019.0комедииРоссия0.016.0NaNМихаил МироновМкртыч Арзуманян, Виктория РунцоваДобродушный Гоша не может выйти из дома, чтобы...Гоша, 2019, Россия
\n", + "
" + ], + "text/plain": [ + " item_id content_type title title_orig release_year \\\n", + "0 10711 film Поговори с ней Hable con ella 2002.0 \n", + "1 2508 film Голые перцы Search Party 2014.0 \n", + "15961 4538 series Среди камней Darklands 2019.0 \n", + "15962 3206 series Гоша NaN 2019.0 \n", + "\n", + " genres countries for_kids \\\n", + "0 драмы, зарубежные, детективы, мелодрамы Испания NaN \n", + "1 зарубежные, приключения, комедии США NaN \n", + "15961 драмы, спорт, криминал Россия 0.0 \n", + "15962 комедии Россия 0.0 \n", + "\n", + " age_rating studios directors \\\n", + "0 16.0 NaN Педро Альмодовар \n", + "1 16.0 NaN Скот Армстронг \n", + "15961 18.0 NaN Марк О’Коннор, Конор МакМахон \n", + "15962 16.0 NaN Михаил Миронов \n", + "\n", + " actors \\\n", + "0 Адольфо Фернандес, Ана Фернандес, Дарио Гранди... \n", + "1 Адам Палли, Брайан Хаски, Дж.Б. Смув, Джейсон ... \n", + "15961 Дэйн Уайт О’Хара, Томас Кэйн-Бирн, Джудит Родд... \n", + "15962 Мкртыч Арзуманян, Виктория Рунцова \n", + "\n", + " description \\\n", + "0 Мелодрама легендарного Педро Альмодовара «Пого... \n", + "1 Уморительная современная комедия на популярную... \n", + "15961 Семнадцатилетний Дэмиен мечтает вырваться за п... \n", + "15962 Добродушный Гоша не может выйти из дома, чтобы... \n", + "\n", + " keywords \n", + "0 Поговори, ней, 2002, Испания, друзья, любовь, ... \n", + "1 Голые, перцы, 2014, США, друзья, свадьбы, прео... \n", + "15961 Среди, камней, 2019, Россия \n", + "15962 Гоша, 2019, Россия " + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "pd.concat([items.head(2), items.tail(2)])" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "8c8fb319", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Items dataframe shape (15963, 14)\n", + "Unique item_id: 15963\n" + ] + } + ], + "source": [ + "print(f\"Items dataframe shape {items.shape}\")\n", + "print(f\"Unique item_id: {items['item_id'].nunique()}\")" + ] + }, + { + "cell_type": "markdown", + "id": "2b35b460", + "metadata": {}, + "source": [ + "# userkNN model CV" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "f60e6ecb", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + " \n", + " " + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.plotly.v1+json": { + "config": { + "plotlyServerURL": "https://plot.ly" + }, + "data": [ + { + "alignmentgroup": "True", + "hovertemplate": "variable=user_id
datetime=%{x}
value=%{y}", + "legendgroup": "user_id", + "marker": { + "color": "#636efa", + "pattern": { + "shape": "" + } + }, + "name": "user_id", + "offsetgroup": "user_id", + "orientation": "v", + "showlegend": true, + "textposition": "auto", + "type": "bar", + "x": [ + "2021-03-13T00:00:00", + "2021-03-14T00:00:00", + "2021-03-15T00:00:00", + "2021-03-16T00:00:00", + "2021-03-17T00:00:00", + "2021-03-18T00:00:00", + "2021-03-19T00:00:00", + "2021-03-20T00:00:00", + "2021-03-21T00:00:00", + "2021-03-22T00:00:00", + "2021-03-23T00:00:00", + "2021-03-24T00:00:00", + "2021-03-25T00:00:00", + "2021-03-26T00:00:00", + "2021-03-27T00:00:00", + "2021-03-28T00:00:00", + "2021-03-29T00:00:00", + "2021-03-30T00:00:00", + "2021-03-31T00:00:00", + "2021-04-01T00:00:00", + "2021-04-02T00:00:00", + "2021-04-03T00:00:00", + "2021-04-04T00:00:00", + "2021-04-05T00:00:00", + "2021-04-06T00:00:00", + "2021-04-07T00:00:00", + "2021-04-08T00:00:00", + "2021-04-09T00:00:00", + "2021-04-10T00:00:00", + "2021-04-11T00:00:00", + "2021-04-12T00:00:00", + "2021-04-13T00:00:00", + "2021-04-14T00:00:00", + "2021-04-15T00:00:00", + "2021-04-16T00:00:00", + "2021-04-17T00:00:00", + "2021-04-18T00:00:00", + "2021-04-19T00:00:00", + "2021-04-20T00:00:00", + "2021-04-21T00:00:00", + "2021-04-22T00:00:00", + "2021-04-23T00:00:00", + "2021-04-24T00:00:00", + "2021-04-25T00:00:00", + "2021-04-26T00:00:00", + "2021-04-27T00:00:00", + "2021-04-28T00:00:00", + "2021-04-29T00:00:00", + "2021-04-30T00:00:00", + "2021-05-01T00:00:00", + "2021-05-02T00:00:00", + "2021-05-03T00:00:00", + "2021-05-04T00:00:00", + "2021-05-05T00:00:00", + "2021-05-06T00:00:00", + "2021-05-07T00:00:00", + "2021-05-08T00:00:00", + "2021-05-09T00:00:00", + "2021-05-10T00:00:00", + "2021-05-11T00:00:00", + "2021-05-12T00:00:00", + "2021-05-13T00:00:00", + "2021-05-14T00:00:00", + "2021-05-15T00:00:00", + "2021-05-16T00:00:00", + "2021-05-17T00:00:00", + "2021-05-18T00:00:00", + "2021-05-19T00:00:00", + "2021-05-20T00:00:00", + "2021-05-21T00:00:00", + "2021-05-22T00:00:00", + "2021-05-23T00:00:00", + "2021-05-24T00:00:00", + "2021-05-25T00:00:00", + "2021-05-26T00:00:00", + "2021-05-27T00:00:00", + "2021-05-28T00:00:00", + "2021-05-29T00:00:00", + "2021-05-30T00:00:00", + "2021-05-31T00:00:00", + "2021-06-01T00:00:00", + "2021-06-02T00:00:00", + "2021-06-03T00:00:00", + "2021-06-04T00:00:00", + "2021-06-05T00:00:00", + "2021-06-06T00:00:00", + "2021-06-07T00:00:00", + "2021-06-08T00:00:00", + "2021-06-09T00:00:00", + "2021-06-10T00:00:00", + "2021-06-11T00:00:00", + "2021-06-12T00:00:00", + "2021-06-13T00:00:00", + "2021-06-14T00:00:00", + "2021-06-15T00:00:00", + "2021-06-16T00:00:00", + "2021-06-17T00:00:00", + "2021-06-18T00:00:00", + "2021-06-19T00:00:00", + "2021-06-20T00:00:00", + "2021-06-21T00:00:00", + "2021-06-22T00:00:00", + "2021-06-23T00:00:00", + "2021-06-24T00:00:00", + "2021-06-25T00:00:00", + "2021-06-26T00:00:00", + "2021-06-27T00:00:00", + "2021-06-28T00:00:00", + "2021-06-29T00:00:00", + "2021-06-30T00:00:00", + "2021-07-01T00:00:00", + "2021-07-02T00:00:00", + "2021-07-03T00:00:00", + "2021-07-04T00:00:00", + "2021-07-05T00:00:00", + "2021-07-06T00:00:00", + "2021-07-07T00:00:00", + "2021-07-08T00:00:00", + "2021-07-09T00:00:00", + "2021-07-10T00:00:00", + "2021-07-11T00:00:00", + "2021-07-12T00:00:00", + "2021-07-13T00:00:00", + "2021-07-14T00:00:00", + "2021-07-15T00:00:00", + "2021-07-16T00:00:00", + "2021-07-17T00:00:00", + "2021-07-18T00:00:00", + "2021-07-19T00:00:00", + "2021-07-20T00:00:00", + "2021-07-21T00:00:00", + "2021-07-22T00:00:00", + "2021-07-23T00:00:00", + "2021-07-24T00:00:00", + "2021-07-25T00:00:00", + "2021-07-26T00:00:00", + "2021-07-27T00:00:00", + "2021-07-28T00:00:00", + "2021-07-29T00:00:00", + "2021-07-30T00:00:00", + "2021-07-31T00:00:00", + "2021-08-01T00:00:00", + "2021-08-02T00:00:00", + "2021-08-03T00:00:00", + "2021-08-04T00:00:00", + "2021-08-05T00:00:00", + "2021-08-06T00:00:00", + "2021-08-07T00:00:00", + "2021-08-08T00:00:00", + "2021-08-09T00:00:00", + "2021-08-10T00:00:00", + "2021-08-11T00:00:00", + "2021-08-12T00:00:00", + "2021-08-13T00:00:00", + "2021-08-14T00:00:00", + "2021-08-15T00:00:00", + "2021-08-16T00:00:00", + "2021-08-17T00:00:00", + "2021-08-18T00:00:00", + "2021-08-19T00:00:00", + "2021-08-20T00:00:00", + "2021-08-21T00:00:00", + "2021-08-22T00:00:00" + ], + "xaxis": "x", + "y": [ + 16104, + 15606, + 12363, + 12643, + 12753, + 12788, + 13657, + 15346, + 15560, + 12752, + 13147, + 13435, + 12698, + 13909, + 15657, + 16112, + 12783, + 13101, + 13460, + 12966, + 14084, + 15431, + 15346, + 12642, + 12528, + 13129, + 13827, + 14416, + 15937, + 16046, + 12835, + 12322, + 12451, + 12275, + 13342, + 15464, + 16275, + 14286, + 20420, + 23200, + 21274, + 22127, + 26161, + 28964, + 21625, + 22590, + 21406, + 19987, + 21406, + 23479, + 24767, + 26267, + 25983, + 23941, + 23510, + 23201, + 27550, + 25986, + 27242, + 20957, + 20578, + 20729, + 21152, + 24530, + 24914, + 20960, + 20574, + 21561, + 22712, + 25697, + 27895, + 29978, + 24317, + 23667, + 22529, + 23881, + 24131, + 29035, + 31308, + 26821, + 26587, + 27577, + 28683, + 33150, + 34795, + 37096, + 31402, + 31107, + 32896, + 38964, + 37935, + 38619, + 42125, + 38973, + 35993, + 57686, + 41440, + 42174, + 43679, + 47989, + 39127, + 39693, + 41688, + 38394, + 41428, + 45898, + 48903, + 43301, + 43887, + 67749, + 53900, + 46642, + 48832, + 52812, + 43375, + 41380, + 41163, + 41592, + 40955, + 44798, + 46250, + 42487, + 43764, + 43128, + 43010, + 44878, + 49714, + 54139, + 45541, + 44431, + 44422, + 46313, + 46911, + 50317, + 54378, + 48531, + 49324, + 50267, + 50585, + 53121, + 59499, + 62128, + 53495, + 52181, + 51911, + 51047, + 53745, + 59316, + 61454, + 52794, + 53712, + 55617, + 56497, + 55843, + 61644, + 66546, + 54546, + 54311, + 56789, + 58640, + 60145, + 68834, + 71171 + ], + "yaxis": "y" + } + ], + "layout": { + "barmode": "relative", + "legend": { + "title": { + "text": "variable" + }, + "tracegroupgap": 0 + }, + "margin": { + "t": 60 + }, + "template": { + "data": { + "bar": [ + { + "error_x": { + "color": "#2a3f5f" + }, + "error_y": { + "color": "#2a3f5f" + }, + "marker": { + "line": { + "color": "#E5ECF6", + "width": 0.5 + }, + "pattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + } + }, + "type": "bar" + } + ], + "barpolar": [ + { + "marker": { + "line": { + "color": "#E5ECF6", + "width": 0.5 + }, + "pattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + } + }, + "type": "barpolar" + } + ], + "carpet": [ + { + "aaxis": { + "endlinecolor": "#2a3f5f", + "gridcolor": "white", + "linecolor": "white", + "minorgridcolor": "white", + "startlinecolor": "#2a3f5f" + }, + "baxis": { + "endlinecolor": "#2a3f5f", + "gridcolor": "white", + "linecolor": "white", + "minorgridcolor": "white", + "startlinecolor": "#2a3f5f" + }, + "type": "carpet" + } + ], + "choropleth": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "choropleth" + } + ], + "contour": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "contour" + } + ], + "contourcarpet": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "contourcarpet" + } + ], + "heatmap": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "heatmap" + } + ], + "heatmapgl": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "heatmapgl" + } + ], + "histogram": [ + { + "marker": { + "pattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + } + }, + "type": "histogram" + } + ], + "histogram2d": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "histogram2d" + } + ], + "histogram2dcontour": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "histogram2dcontour" + } + ], + "mesh3d": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "mesh3d" + } + ], + "parcoords": [ + { + "line": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "parcoords" + } + ], + "pie": [ + { + "automargin": true, + "type": "pie" + } + ], + "scatter": [ + { + "fillpattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + }, + "type": "scatter" + } + ], + "scatter3d": [ + { + "line": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatter3d" + } + ], + "scattercarpet": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattercarpet" + } + ], + "scattergeo": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattergeo" + } + ], + "scattergl": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattergl" + } + ], + "scattermapbox": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattermapbox" + } + ], + "scatterpolar": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterpolar" + } + ], + "scatterpolargl": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterpolargl" + } + ], + "scatterternary": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterternary" + } + ], + "surface": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "surface" + } + ], + "table": [ + { + "cells": { + "fill": { + "color": "#EBF0F8" + }, + "line": { + "color": "white" + } + }, + "header": { + "fill": { + "color": "#C8D4E3" + }, + "line": { + "color": "white" + } + }, + "type": "table" + } + ] + }, + "layout": { + "annotationdefaults": { + "arrowcolor": "#2a3f5f", + "arrowhead": 0, + "arrowwidth": 1 + }, + "autotypenumbers": "strict", + "coloraxis": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "colorscale": { + "diverging": [ + [ + 0, + "#8e0152" + ], + [ + 0.1, + "#c51b7d" + ], + [ + 0.2, + "#de77ae" + ], + [ + 0.3, + "#f1b6da" + ], + [ + 0.4, + "#fde0ef" + ], + [ + 0.5, + "#f7f7f7" + ], + [ + 0.6, + "#e6f5d0" + ], + [ + 0.7, + "#b8e186" + ], + [ + 0.8, + "#7fbc41" + ], + [ + 0.9, + "#4d9221" + ], + [ + 1, + "#276419" + ] + ], + "sequential": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "sequentialminus": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ] + }, + "colorway": [ + "#636efa", + "#EF553B", + "#00cc96", + "#ab63fa", + "#FFA15A", + "#19d3f3", + "#FF6692", + "#B6E880", + "#FF97FF", + "#FECB52" + ], + "font": { + "color": "#2a3f5f" + }, + "geo": { + "bgcolor": "white", + "lakecolor": "white", + "landcolor": "#E5ECF6", + "showlakes": true, + "showland": true, + "subunitcolor": "white" + }, + "hoverlabel": { + "align": "left" + }, + "hovermode": "closest", + "mapbox": { + "style": "light" + }, + "paper_bgcolor": "white", + "plot_bgcolor": "#E5ECF6", + "polar": { + "angularaxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + }, + "bgcolor": "#E5ECF6", + "radialaxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + } + }, + "scene": { + "xaxis": { + "backgroundcolor": "#E5ECF6", + "gridcolor": "white", + "gridwidth": 2, + "linecolor": "white", + "showbackground": true, + "ticks": "", + "zerolinecolor": "white" + }, + "yaxis": { + "backgroundcolor": "#E5ECF6", + "gridcolor": "white", + "gridwidth": 2, + "linecolor": "white", + "showbackground": true, + "ticks": "", + "zerolinecolor": "white" + }, + "zaxis": { + "backgroundcolor": "#E5ECF6", + "gridcolor": "white", + "gridwidth": 2, + "linecolor": "white", + "showbackground": true, + "ticks": "", + "zerolinecolor": "white" + } + }, + "shapedefaults": { + "line": { + "color": "#2a3f5f" + } + }, + "ternary": { + "aaxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + }, + "baxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + }, + "bgcolor": "#E5ECF6", + "caxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + } + }, + "title": { + "x": 0.05 + }, + "xaxis": { + "automargin": true, + "gridcolor": "white", + "linecolor": "white", + "ticks": "", + "title": { + "standoff": 15 + }, + "zerolinecolor": "white", + "zerolinewidth": 2 + }, + "yaxis": { + "automargin": true, + "gridcolor": "white", + "linecolor": "white", + "ticks": "", + "title": { + "standoff": 15 + }, + "zerolinecolor": "white", + "zerolinewidth": 2 + } + } + }, + "xaxis": { + "anchor": "y", + "domain": [ + 0, + 1 + ], + "title": { + "text": "datetime" + } + }, + "yaxis": { + "anchor": "x", + "domain": [ + 0, + 1 + ], + "title": { + "text": "value" + } + } + } + }, + "text/html": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "fig = px.bar(interactions.groupby(Columns.Datetime)[Columns.User].agg('count'))\n", + "fig.show()" + ] + }, + { + "cell_type": "markdown", + "id": "43f216d0", + "metadata": {}, + "source": [ + "Из графика видны **недельные тенденции** просмотров, поэтому следует fold-ы разделять по 7 дней, но т.к. на семинаре дали \"намек\", что private dataset имеет количество дней, меньшее чем 7. Поэтому фолды будут разбиваться на **5 и 7 дней**" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "07fbdb30", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "6" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "pd.to_datetime('23-05-2021', format='%d-%m-%Y').weekday()" + ] + }, + { + "cell_type": "markdown", + "id": "2ff625b2", + "metadata": {}, + "source": [ + "### train test split" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "759ba346", + "metadata": {}, + "outputs": [], + "source": [ + "def create_data_range(\n", + " last_date: pd.Timestamp, \n", + " n_folds: int = 7, \n", + " unit: str = \"W\", \n", + " n_units: int = 1, \n", + " show: bool = True,\n", + "):\n", + " periods = n_folds + 1\n", + " freq = f\"{n_units}{unit}\"\n", + " \n", + " start_date = last_date - pd.Timedelta(n_folds * n_units + n_units, unit=unit) \n", + " \n", + " date_range = pd.date_range(start=start_date, periods=periods, freq=freq, tz=last_date.tz)\n", + " \n", + " if show:\n", + " print(\n", + " f\"start_date: {start_date}\\n\"\n", + " f\"last_date: {last_date}\\n\"\n", + " f\"periods: {periods}\\n\"\n", + " f\"freq: {freq}\\n\"\n", + " f\"Test fold borders: {date_range.values.astype('datetime64[D]')}\\n\"\n", + " )\n", + " \n", + " return date_range" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "38bfd397", + "metadata": {}, + "outputs": [], + "source": [ + "CONFIG_CV = {\n", + " \"cv_v1\": {\n", + " \"n_folds\": 7,\n", + " \"unit\": \"W\",\n", + " \"n_units\": 1,\n", + " },\n", + " \"cv_v2\": {\n", + " \"n_folds\": 7,\n", + " \"unit\": \"D\",\n", + " \"n_units\": 5,\n", + " }, \n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "f518e089", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Timestamp('2021-08-22 00:00:00')" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "last_date = interactions[Columns.Datetime].max().normalize()\n", + "last_date" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "1fd68b9b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "***Folds v1***\n", + "start_date: 2021-07-13 00:00:00\n", + "last_date: 2021-08-22 00:00:00\n", + "periods: 8\n", + "freq: 5D\n", + "Test fold borders: ['2021-07-13' '2021-07-18' '2021-07-23' '2021-07-28' '2021-08-02'\n", + " '2021-08-07' '2021-08-12' '2021-08-17']\n", + "\n" + ] + } + ], + "source": [ + "print(\"***Folds v1***\")\n", + "date_range_v1 = create_data_range(\n", + " last_date, \n", + " n_folds=CONFIG_CV[\"cv_v2\"][\"n_folds\"], \n", + " unit=CONFIG_CV[\"cv_v2\"][\"unit\"], \n", + " n_units=CONFIG_CV[\"cv_v2\"][\"n_units\"]\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "efc59555", + "metadata": {}, + "source": [ + "**генерируем фолды** " + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "9fae43f6", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Real number of folds: 7\n" + ] + } + ], + "source": [ + "cv_v1 = TimeRangeSplitter(\n", + " date_range=date_range_v1,\n", + " filter_already_seen=True,\n", + " filter_cold_items=True,\n", + " filter_cold_users=True,\n", + ")\n", + "print(f\"Real number of folds: {cv_v1.get_n_splits(Interactions(interactions))}\")\n", + "\n", + "CV = [cv_v1]" + ] + }, + { + "cell_type": "markdown", + "id": "e15a83a7", + "metadata": {}, + "source": [ + "**Формируем метрики**" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "8f7742c6", + "metadata": {}, + "outputs": [], + "source": [ + "metrics = {\n", + " \"prec@10\": Precision(k=10),\n", + " \"recall@10\": Recall(k=10),\n", + " \"MAP@10\": MAP(k=10),\n", + " \"novelty\": MeanInvUserFreq(k=10),\n", + " \"serendipity\": Serendipity(k=10),\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "b21a1ecf", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'cosine_userknn_K30': ,\n", + " 'tfidf_userknn_K30': ,\n", + " 'bm25_userknn_K30': ,\n", + " 'cosine_userknn_K40': ,\n", + " 'tfidf_userknn_K40': ,\n", + " 'bm25_userknn_K40': }" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "K = [30, 40]\n", + "models = dict()\n", + "\n", + "for k in K:\n", + " models[f\"cosine_userknn_K{k}\"] = CosineRecommender(K=k)\n", + " models[f\"tfidf_userknn_K{k}\"] = TFIDFRecommender(K=k)\n", + " models[f\"bm25_userknn_K{k}\"] = BM25Recommender(K=k)\n", + "\n", + "models" + ] + }, + { + "cell_type": "markdown", + "id": "0103149a", + "metadata": {}, + "source": [ + "## Training" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "e78b8221", + "metadata": {}, + "outputs": [], + "source": [ + "N_USERS = 50" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "50dcff0b", + "metadata": {}, + "outputs": [], + "source": [ + "%%time\n", + "\n", + "results = []\n", + "\n", + "for idx, cv in enumerate(CV):\n", + " print(f\"\\n CV version {idx}\")\n", + " fold_iterator = cv.split(Interactions(interactions), collect_fold_stats=True)\n", + "\n", + " for i_fold, (train_ids, test_ids, fold_info) in enumerate(fold_iterator):\n", + " print(f\"\\n==================== Fold {i_fold}\")\n", + " pprint(fold_info)\n", + "\n", + " df_train = interactions.iloc[train_ids].copy()\n", + " df_test = interactions.iloc[test_ids][Columns.UserItem].copy()\n", + "\n", + " catalog = df_train[Columns.Item].unique()\n", + "\n", + " for model_name, model in models.items():\n", + " userknn_model = UserKnn(model=model, N_users=N_USERS, use_weight_idf=True)\n", + " userknn_model.fit(df_train)\n", + "\n", + " if 'bm25' in model_name:\n", + " recos = userknn_model.predict(df_test, bmp25=True)\n", + " else:\n", + " recos = userknn_model.predict(df_test)\n", + "\n", + " metric_values = calc_metrics(\n", + " metrics,\n", + " reco=recos,\n", + " interactions=df_test,\n", + " prev_interactions=df_train,\n", + " catalog=catalog,\n", + " )\n", + "\n", + " full_model_name = f\"{model_name}_cv-{idx}\"\n", + " fold = {\"fold\": i_fold, \"model\": full_model_name}\n", + " fold.update(metric_values)\n", + " results.append(fold)" + ] + }, + { + "cell_type": "markdown", + "id": "708ec5c2", + "metadata": {}, + "source": [ + "Работало больше 10 часов, случайно при перезапуске ноутбука была вызвана ячейка и остановлена, поэтому завершилась с ошибкой, поэтому ошибку убрали для лучшего вида" + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "id": "d7e2ffa7", + "metadata": { + "collapsed": true + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
foldmodelprec@10recall@10MAP@10noveltyserendipity
00cosine_userknn_K30_cv-00.0035570.0211280.0036958.3314910.000040
10tfidf_userknn_K30_cv-00.0064390.0391020.0073358.1550510.000048
20bm25_userknn_K30_cv-00.0025930.0134940.0025319.3984670.000081
30cosine_userknn_K40_cv-00.0032820.0193230.0034018.5615230.000043
40tfidf_userknn_K40_cv-00.0061780.0374580.0069578.3004040.000052
50bm25_userknn_K40_cv-00.0022410.0112550.0022109.6755330.000081
61cosine_userknn_K30_cv-00.0035050.0200020.0035808.3982480.000046
71tfidf_userknn_K30_cv-00.0063280.0368440.0070228.2401330.000058
81bm25_userknn_K30_cv-00.0027220.0138560.0026589.4846920.000088
91cosine_userknn_K40_cv-00.0032450.0183680.0033058.6269060.000047
101tfidf_userknn_K40_cv-00.0061500.0359640.0069168.3779880.000061
111bm25_userknn_K40_cv-00.0024060.0120670.0023939.7564580.000086
122cosine_userknn_K30_cv-00.0032610.0184980.0032958.4392630.000047
132tfidf_userknn_K30_cv-00.0059400.0342330.0064798.2623670.000059
142bm25_userknn_K30_cv-00.0027200.0134220.0025309.5356310.000091
152cosine_userknn_K40_cv-00.0030450.0170860.0031008.6615850.000050
162tfidf_userknn_K40_cv-00.0059140.0340710.0064398.3966180.000063
172bm25_userknn_K40_cv-00.0024040.0116380.0022319.7991190.000090
183cosine_userknn_K30_cv-00.0032770.0187860.0033958.4449860.000045
193tfidf_userknn_K30_cv-00.0060230.0341710.0063288.2765030.000059
203bm25_userknn_K30_cv-00.0026200.0127620.0024979.5609840.000091
213cosine_userknn_K40_cv-00.0030760.0175120.0031738.6581500.000045
223tfidf_userknn_K40_cv-00.0059190.0333680.0062538.3991690.000062
233bm25_userknn_K40_cv-00.0023370.0112730.0022539.8163250.000089
244cosine_userknn_K30_cv-00.0031180.0180640.0031578.4858990.000042
254tfidf_userknn_K30_cv-00.0059110.0336260.0063968.2824280.000059
264bm25_userknn_K30_cv-00.0025370.0123680.0024709.5996450.000086
274cosine_userknn_K40_cv-00.0028720.0165090.0028838.7119840.000043
284tfidf_userknn_K40_cv-00.0057930.0330280.0062618.4166800.000062
294bm25_userknn_K40_cv-00.0022130.0108600.0021799.8662010.000085
305cosine_userknn_K30_cv-00.0030030.0162520.0028998.4989680.000043
315tfidf_userknn_K30_cv-00.0055270.0309420.0058238.3252730.000057
325bm25_userknn_K30_cv-00.0025970.0122630.0023869.6469570.000100
335cosine_userknn_K40_cv-00.0027650.0147130.0026618.7175590.000047
345tfidf_userknn_K40_cv-00.0055450.0308920.0058178.4540910.000059
355bm25_userknn_K40_cv-00.0023020.0107770.0021359.9140420.000100
366cosine_userknn_K30_cv-00.0029630.0165320.0028878.5638090.000050
376tfidf_userknn_K30_cv-00.0053300.0307170.0057638.3662590.000064
386bm25_userknn_K30_cv-00.0025710.0126910.0024789.7150970.000100
396cosine_userknn_K40_cv-00.0027690.0154480.0026758.7750580.000051
406tfidf_userknn_K40_cv-00.0052840.0304180.0056978.4884730.000066
416bm25_userknn_K40_cv-00.0023400.0112780.0022089.9646640.000099
\n", + "
" + ], + "text/plain": [ + " fold model prec@10 recall@10 MAP@10 novelty \\\n", + "0 0 cosine_userknn_K30_cv-0 0.003557 0.021128 0.003695 8.331491 \n", + "1 0 tfidf_userknn_K30_cv-0 0.006439 0.039102 0.007335 8.155051 \n", + "2 0 bm25_userknn_K30_cv-0 0.002593 0.013494 0.002531 9.398467 \n", + "3 0 cosine_userknn_K40_cv-0 0.003282 0.019323 0.003401 8.561523 \n", + "4 0 tfidf_userknn_K40_cv-0 0.006178 0.037458 0.006957 8.300404 \n", + "5 0 bm25_userknn_K40_cv-0 0.002241 0.011255 0.002210 9.675533 \n", + "6 1 cosine_userknn_K30_cv-0 0.003505 0.020002 0.003580 8.398248 \n", + "7 1 tfidf_userknn_K30_cv-0 0.006328 0.036844 0.007022 8.240133 \n", + "8 1 bm25_userknn_K30_cv-0 0.002722 0.013856 0.002658 9.484692 \n", + "9 1 cosine_userknn_K40_cv-0 0.003245 0.018368 0.003305 8.626906 \n", + "10 1 tfidf_userknn_K40_cv-0 0.006150 0.035964 0.006916 8.377988 \n", + "11 1 bm25_userknn_K40_cv-0 0.002406 0.012067 0.002393 9.756458 \n", + "12 2 cosine_userknn_K30_cv-0 0.003261 0.018498 0.003295 8.439263 \n", + "13 2 tfidf_userknn_K30_cv-0 0.005940 0.034233 0.006479 8.262367 \n", + "14 2 bm25_userknn_K30_cv-0 0.002720 0.013422 0.002530 9.535631 \n", + "15 2 cosine_userknn_K40_cv-0 0.003045 0.017086 0.003100 8.661585 \n", + "16 2 tfidf_userknn_K40_cv-0 0.005914 0.034071 0.006439 8.396618 \n", + "17 2 bm25_userknn_K40_cv-0 0.002404 0.011638 0.002231 9.799119 \n", + "18 3 cosine_userknn_K30_cv-0 0.003277 0.018786 0.003395 8.444986 \n", + "19 3 tfidf_userknn_K30_cv-0 0.006023 0.034171 0.006328 8.276503 \n", + "20 3 bm25_userknn_K30_cv-0 0.002620 0.012762 0.002497 9.560984 \n", + "21 3 cosine_userknn_K40_cv-0 0.003076 0.017512 0.003173 8.658150 \n", + "22 3 tfidf_userknn_K40_cv-0 0.005919 0.033368 0.006253 8.399169 \n", + "23 3 bm25_userknn_K40_cv-0 0.002337 0.011273 0.002253 9.816325 \n", + "24 4 cosine_userknn_K30_cv-0 0.003118 0.018064 0.003157 8.485899 \n", + "25 4 tfidf_userknn_K30_cv-0 0.005911 0.033626 0.006396 8.282428 \n", + "26 4 bm25_userknn_K30_cv-0 0.002537 0.012368 0.002470 9.599645 \n", + "27 4 cosine_userknn_K40_cv-0 0.002872 0.016509 0.002883 8.711984 \n", + "28 4 tfidf_userknn_K40_cv-0 0.005793 0.033028 0.006261 8.416680 \n", + "29 4 bm25_userknn_K40_cv-0 0.002213 0.010860 0.002179 9.866201 \n", + "30 5 cosine_userknn_K30_cv-0 0.003003 0.016252 0.002899 8.498968 \n", + "31 5 tfidf_userknn_K30_cv-0 0.005527 0.030942 0.005823 8.325273 \n", + "32 5 bm25_userknn_K30_cv-0 0.002597 0.012263 0.002386 9.646957 \n", + "33 5 cosine_userknn_K40_cv-0 0.002765 0.014713 0.002661 8.717559 \n", + "34 5 tfidf_userknn_K40_cv-0 0.005545 0.030892 0.005817 8.454091 \n", + "35 5 bm25_userknn_K40_cv-0 0.002302 0.010777 0.002135 9.914042 \n", + "36 6 cosine_userknn_K30_cv-0 0.002963 0.016532 0.002887 8.563809 \n", + "37 6 tfidf_userknn_K30_cv-0 0.005330 0.030717 0.005763 8.366259 \n", + "38 6 bm25_userknn_K30_cv-0 0.002571 0.012691 0.002478 9.715097 \n", + "39 6 cosine_userknn_K40_cv-0 0.002769 0.015448 0.002675 8.775058 \n", + "40 6 tfidf_userknn_K40_cv-0 0.005284 0.030418 0.005697 8.488473 \n", + "41 6 bm25_userknn_K40_cv-0 0.002340 0.011278 0.002208 9.964664 \n", + "\n", + " serendipity \n", + "0 0.000040 \n", + "1 0.000048 \n", + "2 0.000081 \n", + "3 0.000043 \n", + "4 0.000052 \n", + "5 0.000081 \n", + "6 0.000046 \n", + "7 0.000058 \n", + "8 0.000088 \n", + "9 0.000047 \n", + "10 0.000061 \n", + "11 0.000086 \n", + "12 0.000047 \n", + "13 0.000059 \n", + "14 0.000091 \n", + "15 0.000050 \n", + "16 0.000063 \n", + "17 0.000090 \n", + "18 0.000045 \n", + "19 0.000059 \n", + "20 0.000091 \n", + "21 0.000045 \n", + "22 0.000062 \n", + "23 0.000089 \n", + "24 0.000042 \n", + "25 0.000059 \n", + "26 0.000086 \n", + "27 0.000043 \n", + "28 0.000062 \n", + "29 0.000085 \n", + "30 0.000043 \n", + "31 0.000057 \n", + "32 0.000100 \n", + "33 0.000047 \n", + "34 0.000059 \n", + "35 0.000100 \n", + "36 0.000050 \n", + "37 0.000064 \n", + "38 0.000100 \n", + "39 0.000051 \n", + "40 0.000066 \n", + "41 0.000099 " + ] + }, + "execution_count": 46, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df_metrics = pd.DataFrame(results)\n", + "df_metrics" + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "id": "a0334b9a", + "metadata": {}, + "outputs": [], + "source": [ + "df_metrics.to_pickle(\"../data/hw_3/df_metrics.pickle\")" + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "id": "446530ce", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
foldprec@10recall@10MAP@10noveltyserendipity
model
bm25_userknn_K30_cv-03.00.0026230.0129800.0025079.5630680.000091
bm25_userknn_K40_cv-03.00.0023200.0113070.0022309.8274770.000090
cosine_userknn_K30_cv-03.00.0032410.0184660.0032728.4518090.000045
cosine_userknn_K40_cv-03.00.0030080.0169940.0030288.6732520.000047
tfidf_userknn_K30_cv-03.00.0059280.0342340.0064498.2725730.000058
tfidf_userknn_K40_cv-03.00.0058260.0336000.0063348.4047750.000061
\n", + "
" + ], + "text/plain": [ + " fold prec@10 recall@10 MAP@10 novelty \\\n", + "model \n", + "bm25_userknn_K30_cv-0 3.0 0.002623 0.012980 0.002507 9.563068 \n", + "bm25_userknn_K40_cv-0 3.0 0.002320 0.011307 0.002230 9.827477 \n", + "cosine_userknn_K30_cv-0 3.0 0.003241 0.018466 0.003272 8.451809 \n", + "cosine_userknn_K40_cv-0 3.0 0.003008 0.016994 0.003028 8.673252 \n", + "tfidf_userknn_K30_cv-0 3.0 0.005928 0.034234 0.006449 8.272573 \n", + "tfidf_userknn_K40_cv-0 3.0 0.005826 0.033600 0.006334 8.404775 \n", + "\n", + " serendipity \n", + "model \n", + "bm25_userknn_K30_cv-0 0.000091 \n", + "bm25_userknn_K40_cv-0 0.000090 \n", + "cosine_userknn_K30_cv-0 0.000045 \n", + "cosine_userknn_K40_cv-0 0.000047 \n", + "tfidf_userknn_K30_cv-0 0.000058 \n", + "tfidf_userknn_K40_cv-0 0.000061 " + ] + }, + "execution_count": 48, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df_metrics.groupby('model').mean()" + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "id": "5fb9ba9f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
prec@10recall@10MAP@10noveltyserendipity
model
bm25_userknn_K30_cv-00.0000720.0006120.0000830.1044680.000007
bm25_userknn_K40_cv-00.0000740.0004420.0000810.0973590.000007
cosine_userknn_K30_cv-00.0002310.0017490.0003140.0746990.000003
cosine_userknn_K40_cv-00.0002130.0016030.0002950.0693100.000003
tfidf_userknn_K30_cv-00.0003980.0030030.0005770.0666270.000005
tfidf_userknn_K40_cv-00.0003210.0025340.0004870.0595650.000004
\n", + "
" + ], + "text/plain": [ + " prec@10 recall@10 MAP@10 novelty serendipity\n", + "model \n", + "bm25_userknn_K30_cv-0 0.000072 0.000612 0.000083 0.104468 0.000007\n", + "bm25_userknn_K40_cv-0 0.000074 0.000442 0.000081 0.097359 0.000007\n", + "cosine_userknn_K30_cv-0 0.000231 0.001749 0.000314 0.074699 0.000003\n", + "cosine_userknn_K40_cv-0 0.000213 0.001603 0.000295 0.069310 0.000003\n", + "tfidf_userknn_K30_cv-0 0.000398 0.003003 0.000577 0.066627 0.000005\n", + "tfidf_userknn_K40_cv-0 0.000321 0.002534 0.000487 0.059565 0.000004" + ] + }, + "execution_count": 49, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df_metrics.groupby('model').std()[metrics.keys()]" + ] + }, + { + "cell_type": "markdown", + "id": "41828ee5", + "metadata": {}, + "source": [ + "по **ofline** метрикам лучше всего себя показывает модель TFIDFRecommender\n", + "TFIDFRecommender подбор К" + ] + }, + { + "cell_type": "markdown", + "id": "7a8a0a41", + "metadata": {}, + "source": [ + "# Подбор оптимального K для TFIDFRecommender" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "1e91892d", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'tfidf_userknn_K50': ,\n", + " 'tfidf_userknn_K60': ,\n", + " 'tfidf_userknn_K70': }" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "N_USERS = 50\n", + "\n", + "# Т.к. метрики для К 30 и 40 уже есть\n", + "K = [k for k in range(50, 71, 10)]\n", + "models = dict()\n", + "\n", + "for k in K:\n", + " models[f\"tfidf_userknn_K{k}\"] = TFIDFRecommender(K=k)\n", + "models" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e7c2c43b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "==================== Fold 0\n", + "{'End date': Timestamp('2021-07-18 00:00:00', freq='5D'),\n", + " 'Start date': Timestamp('2021-07-13 00:00:00', freq='5D'),\n", + " 'Test': 156580,\n", + " 'Test items': 5793,\n", + " 'Test users': 68150,\n", + " 'Train': 3281612,\n", + " 'Train items': 14754,\n", + " 'Train users': 652905}\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "211234f034a54bae86b94dff33b9f5c4", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + " 0%| | 0/652905 [00:00\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
user_iditem_idlast_watch_dttotal_durwatched_pct
017654995062021-05-11425072.0
169931716592021-05-298317100.0
265668371072021-05-09100.0
386461376382021-07-0514483100.0
496486895062021-04-306725100.0
5476246648596122252021-08-13760.0
547624754686296732021-04-13230849.0
5476248697262152972021-08-201830763.0
5476249384202161972021-04-196203100.0
547625031970944362021-08-15392145.0
\n", + "" + ], + "text/plain": [ + " user_id item_id last_watch_dt total_dur watched_pct\n", + "0 176549 9506 2021-05-11 4250 72.0\n", + "1 699317 1659 2021-05-29 8317 100.0\n", + "2 656683 7107 2021-05-09 10 0.0\n", + "3 864613 7638 2021-07-05 14483 100.0\n", + "4 964868 9506 2021-04-30 6725 100.0\n", + "5476246 648596 12225 2021-08-13 76 0.0\n", + "5476247 546862 9673 2021-04-13 2308 49.0\n", + "5476248 697262 15297 2021-08-20 18307 63.0\n", + "5476249 384202 16197 2021-04-19 6203 100.0\n", + "5476250 319709 4436 2021-08-15 3921 45.0" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "pd.concat([interactions.head(), interactions.tail()])" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "dc4d9fd7", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(962179,)" + ] + }, + "execution_count": 33, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "interactions['user_id'].unique().shape" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "b7861d19", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[(961833, 1.0),\n", + " (961849, 1.0),\n", + " (961857, 1.0),\n", + " (961871, 1.0),\n", + " (961873, 1.0),\n", + " (961876, 1.0),\n", + " (961887, 1.0),\n", + " (961907, 1.0),\n", + " (961910, 1.0),\n", + " (961912, 1.0)]" + ] + }, + "execution_count": 35, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import dill\n", + "\n", + "with open('../service/weights/userKNN/userknn_tfidf_k30.dill', 'rb') as f:\n", + " userknn = dill.load(f)\n", + "\n", + "userknn.similar_items(962178, 10)" + ] + }, + { + "cell_type": "markdown", + "id": "1905033a", + "metadata": {}, + "source": [ + "# Popular Model" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "id": "2df74dba", + "metadata": {}, + "outputs": [], + "source": [ + "from rectools.models import PopularModel\n", + "from rectools.dataset import Dataset" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "6ba37a73", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Timestamp('2021-08-22 00:00:00')" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "max_date = interactions[Columns.Datetime].max().normalize()\n", + "max_date" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "901353f9", + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "train = interactions[[Columns.User, Columns.Item, Columns.Weight, Columns.Datetime]][\n", + " interactions[Columns.Datetime] < max_date - pd.Timedelta(5, \"D\")]\n", + "\n", + "test = interactions[[Columns.User, Columns.Item, Columns.Weight, Columns.Datetime]][\n", + " interactions[Columns.Datetime] >= max_date - pd.Timedelta(5, \"D\")]\n", + "\n", + "dataset_train = Dataset.construct(train)" + ] + }, + { + "cell_type": "code", + "execution_count": 144, + "id": "f08e3579", + "metadata": {}, + "outputs": [], + "source": [ + "popilarity_models = {\n", + " \"popular\": PopularModel(),\n", + " \"popular_mw\": PopularModel(popularity=\"mean_weight\")\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 145, + "id": "03c3bfb6", + "metadata": {}, + "outputs": [], + "source": [ + "popilarity_models[\"popular\"].fit(dataset_train)\n", + "popilarity_models[\"popular_mw\"].fit(dataset_train);" + ] + }, + { + "cell_type": "code", + "execution_count": 146, + "id": "0d7de49e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([ 24, 20, 31, 15, 167, 81, 89, 135, 355, 116])" + ] + }, + "execution_count": 146, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "popilarity_models[\"popular\"].popularity_list[0][:10]" + ] + }, + { + "cell_type": "code", + "execution_count": 147, + "id": "05ff208d", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([11363, 11681, 12841, 13017, 2069, 13691, 13552, 13397, 11774,\n", + " 12913])" + ] + }, + "execution_count": 147, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "popilarity_models[\"popular_mw\"].popularity_list[0][:10]" + ] + }, + { + "cell_type": "code", + "execution_count": 148, + "id": "00ef735c", + "metadata": {}, + "outputs": [], + "source": [ + "pecos_pop = popilarity_models[\"popular\"].recommend(\n", + " users=test[Columns.User].unique(),\n", + " dataset=dataset,\n", + " k=100,\n", + " filter_viewed=False,\n", + ")\n", + "\n", + "pecos_pop_mw = popilarity_models[\"popular_mw\"].recommend(\n", + " users=test[Columns.User].unique(),\n", + " dataset=dataset,\n", + " k=100,\n", + " filter_viewed=False,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 152, + "id": "b302db55", + "metadata": {}, + "outputs": [], + "source": [ + "metrics = {\n", + " \"prec@5\": Precision(k=5),\n", + " \"recall@5\": Recall(k=5),\n", + " \"MAP@5\": MAP(k=5),\n", + " \"prec@10\": Precision(k=10),\n", + " \"recall@10\": Recall(k=10),\n", + " \"MAP@20\": MAP(k=20),\n", + " \"prec@20\": Precision(k=20),\n", + " \"recall@20\": Recall(k=20),\n", + " \"MAP@100\": MAP(k=100),\n", + " \"prec@100\": Precision(k=100),\n", + " \"recall@100\": Recall(k=100),\n", + " \"MAP@100\": MAP(k=100),\n", + " \"novelty\": MeanInvUserFreq(k=10),\n", + " \"serendipity\": Serendipity(k=10),\n", + "}\n", + "catalog = train[Columns.Item].unique()\n", + "metric_values_pop = calc_metrics(metrics, pecos_pop, test, train, catalog)\n", + "metric_values_pop_mean_weight = calc_metrics(metrics, pecos_pop_mw, test, train, catalog)" + ] + }, + { + "cell_type": "code", + "execution_count": 153, + "id": "9631093b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'prec@5': 0.0017855613317256697,\n", + " 'recall@5': 0.004623809755660008,\n", + " 'prec@10': 0.0011648975773029461,\n", + " 'recall@10': 0.005682095875283048,\n", + " 'prec@20': 0.0010502526799891945,\n", + " 'recall@20': 0.00880186008464912,\n", + " 'prec@100': 0.003247020220987923,\n", + " 'recall@100': 0.16609031082955295,\n", + " 'MAP@5': 0.0013179725619140792,\n", + " 'MAP@20': 0.0016695313583723814,\n", + " 'MAP@100': 0.005578924867474493,\n", + " 'novelty': 9.976033936531364,\n", + " 'serendipity': 1.2752762676592953e-05}" + ] + }, + "execution_count": 153, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "metric_values_pop" + ] + }, + { + "cell_type": "code", + "execution_count": 154, + "id": "5d55b781", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'prec@5': 9.09252633867684e-05,\n", + " 'recall@5': 0.00014799438063171262,\n", + " 'prec@10': 4.612151041357817e-05,\n", + " 'recall@10': 0.00015458316783365238,\n", + " 'prec@20': 2.635514880775895e-05,\n", + " 'recall@20': 0.00016946607539568094,\n", + " 'prec@100': 0.00015147621777259455,\n", + " 'recall@100': 0.0065476971391510656,\n", + " 'MAP@5': 3.0257754846536496e-05,\n", + " 'MAP@20': 3.1771198360212185e-05,\n", + " 'MAP@100': 0.00011355765992119742,\n", + " 'novelty': 17.423655787689828,\n", + " 'serendipity': 1.8991632826477633e-06}" + ] + }, + "execution_count": 154, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "metric_values_pop_mean_weight" + ] + }, + { + "cell_type": "markdown", + "id": "e5a4a011", + "metadata": {}, + "source": [ + "**На офлайн метриках выигрывает обычная модель по популярному**" + ] + }, + { + "cell_type": "markdown", + "id": "5875fab7", + "metadata": {}, + "source": [ + "# Save item_idf data" + ] + }, + { + "cell_type": "markdown", + "id": "6589996f", + "metadata": {}, + "source": [ + "Создаем датасет со взвешенными item-ами по механизму idf для использования в будущем" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "id": "d62cabb9", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
indexidf
095067.150811
116598.524953
271075.821207
376388.407093
466867.778734
.........
15701783314.822785
15702912514.822785
157031006414.822785
157041301914.822785
157051054214.822785
\n", + "

15706 rows × 2 columns

\n", + "
" + ], + "text/plain": [ + " index idf\n", + "0 9506 7.150811\n", + "1 1659 8.524953\n", + "2 7107 5.821207\n", + "3 7638 8.407093\n", + "4 6686 7.778734\n", + "... ... ...\n", + "15701 7833 14.822785\n", + "15702 9125 14.822785\n", + "15703 10064 14.822785\n", + "15704 13019 14.822785\n", + "15705 10542 14.822785\n", + "\n", + "[15706 rows x 2 columns]" + ] + }, + "execution_count": 40, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "item_cnt = Counter(interactions['item_id'].values)\n", + "item_idf = pd.DataFrame.from_dict(item_cnt, orient='index', columns=['doc_freq']).reset_index()\n", + "n = interactions.shape[0]\n", + "item_idf['idf'] = item_idf['doc_freq'].apply(lambda x: np.log((1 + n) / (1 + x) + 1))\n", + "del item_idf['doc_freq']\n", + "item_idf" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "id": "7da47dfc", + "metadata": {}, + "outputs": [], + "source": [ + "item_idf = item_idf.sort_values(\"idf\", ascending=False)\n", + "item_idf.to_csv('../data/kion_train/items_idf.csv', index=False)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fdce2b60", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.8.10" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/notebooks/HW-3.2-rectools-research.ipynb b/notebooks/HW-3.2-rectools-research.ipynb new file mode 100644 index 00000000..ed456f5f --- /dev/null +++ b/notebooks/HW-3.2-rectools-research.ipynb @@ -0,0 +1,725 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "855d49cd", + "metadata": {}, + "outputs": [], + "source": [ + "import pandas as pd\n", + "import numpy as np\n", + "import scipy as sp\n", + "import requests\n", + "from tqdm.auto import tqdm\n", + "from scipy.stats import mode \n", + "from pprint import pprint\n", + "from implicit.nearest_neighbours import CosineRecommender\n", + "import warnings\n", + "warnings.filterwarnings(\"ignore\")\n", + "\n", + "from rectools import Columns\n", + "\n", + "pd.set_option('display.max_columns', None)\n", + "pd.set_option('display.max_colwidth', 200)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "655cd033", + "metadata": {}, + "outputs": [], + "source": [ + "interactions = pd.read_csv('../data/kion_train/interactions.csv')\n", + "\n", + "interactions.rename(columns={'last_watch_dt': Columns.Datetime,\n", + " 'total_dur': Columns.Weight}, \n", + " inplace=True) \n", + "\n", + "interactions['datetime'] = pd.to_datetime(interactions['datetime'])" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "193c411d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Start date and last date of the test fold: (Timestamp('2021-08-08 00:00:00'), Timestamp('2021-08-22 00:00:00'))\n", + "Test fold borders: ['2021-08-08' '2021-08-15']\n", + "Real number of folds: 1\n" + ] + } + ], + "source": [ + "from rectools.model_selection import TimeRangeSplitter\n", + "from rectools.dataset import Interactions\n", + "\n", + "n_folds = 1\n", + "unit = \"W\"\n", + "n_units = 1\n", + "periods = n_folds + 1\n", + "freq = f\"{n_units}{unit}\"\n", + "\n", + "last_date = interactions[Columns.Datetime].max().normalize()\n", + "start_date = last_date - pd.Timedelta(n_folds * n_units + 1, unit=unit) \n", + "print(f\"Start date and last date of the test fold: {start_date, last_date}\")\n", + " \n", + "date_range = pd.date_range(start=start_date, periods=periods, freq=freq, tz=last_date.tz)\n", + "print(f\"Test fold borders: {date_range.values.astype('datetime64[D]')}\")\n", + "\n", + "# generator of folds\n", + "cv = TimeRangeSplitter(\n", + " date_range=date_range,\n", + " filter_already_seen=True,\n", + " filter_cold_items=True,\n", + " filter_cold_users=True,\n", + ")\n", + "print(f\"Real number of folds: {cv.get_n_splits(Interactions(interactions))}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "38b80f9f", + "metadata": {}, + "outputs": [], + "source": [ + "(train_ids, test_ids, fold_info) = cv.split(Interactions(interactions), collect_fold_stats=True).__next__()" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "e3051991", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([ 0, 1, 2, ..., 5476245, 5476247, 5476249])" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "train_ids" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "7bc27a2f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([ 6, 33, 56, ..., 5476229, 5476230, 5476240])" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "test_ids" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "ffdaad0c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "users_mapping amount: 842129\n", + "items_mapping amount: 15404\n" + ] + } + ], + "source": [ + "train = interactions.loc[train_ids]\n", + "test = interactions.loc[test_ids]\n", + "\n", + "users_inv_mapping = dict(enumerate(train['user_id'].unique()))\n", + "users_mapping = {v: k for k, v in users_inv_mapping.items()}\n", + "\n", + "items_inv_mapping = dict(enumerate(train['item_id'].unique()))\n", + "items_mapping = {v: k for k, v in items_inv_mapping.items()}\n", + "\n", + "print(f\"users_mapping amount: {len(users_mapping)}\")\n", + "print(f\"items_mapping amount: {len(items_mapping)}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "a6664026", + "metadata": {}, + "outputs": [], + "source": [ + "from rectools.dataset import Dataset\n", + "\n", + "dataset = Dataset.construct(\n", + " interactions_df=train,\n", + " user_features_df=None,\n", + " item_features_df=None\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "88f5a65c", + "metadata": {}, + "source": [ + "# ItemKNN CosineRecommender" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "9c4682c5", + "metadata": {}, + "outputs": [], + "source": [ + "from implicit.nearest_neighbours import CosineRecommender\n", + "from rectools.models.implicit_knn import ImplicitItemKNNWrapperModel\n", + "\n", + "item_knn = ImplicitItemKNNWrapperModel(model=CosineRecommender(K=30))\n", + "item_knn.fit(dataset);" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "198faaa4", + "metadata": {}, + "outputs": [], + "source": [ + "recs_itemknn = item_knn.recommend(\n", + " test['user_id'].unique(), \n", + " dataset=dataset, \n", + " k=10, \n", + " filter_viewed=False\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "76d1a3f5", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
user_iditem_idscorerank
010164581044020431.6311501
110164587348043.9999622
21016458121928033.5995303
3101645819867999.8057314
4101645844577763.2046075
\n", + "
" + ], + "text/plain": [ + " user_id item_id score rank\n", + "0 1016458 10440 20431.631150 1\n", + "1 1016458 734 8043.999962 2\n", + "2 1016458 12192 8033.599530 3\n", + "3 1016458 1986 7999.805731 4\n", + "4 1016458 4457 7763.204607 5" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "recs_itemknn.head()" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "c075a976", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'prec@10': 0.017311708814214132,\n", + " 'recall@10': 0.09520897568691472,\n", + " 'MAP@10': 0.023145528903990274,\n", + " 'novelty': 8.05318572965277,\n", + " 'serendipity': 6.63288816067437e-05}" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from rectools.metrics import Precision, Recall, MeanInvUserFreq, MAP, Serendipity, calc_metrics\n", + "\n", + "# calculate several classic (precision@k and recall@k) and \"beyond accuracy\" metrics\n", + "metrics = {\n", + " \"prec@10\": Precision(k=10),\n", + " \"recall@10\": Recall(k=10),\n", + " \"MAP@10\": MAP(k=10),\n", + " \"novelty\": MeanInvUserFreq(k=10),\n", + " \"serendipity\": Serendipity(k=10),\n", + "}\n", + "\n", + "catalog = train['item_id'].unique()\n", + "\n", + "metric_values_itemknn_cosine = calc_metrics(\n", + " metrics,\n", + " reco=recs_itemknn,\n", + " interactions=test,\n", + " prev_interactions=train,\n", + " catalog=catalog\n", + " )\n", + "\n", + "metric_values_itemknn_cosine" + ] + }, + { + "cell_type": "markdown", + "id": "b439f7fb", + "metadata": {}, + "source": [ + "# ItemKNN TFIDFRecommender" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "e31f5560", + "metadata": {}, + "outputs": [], + "source": [ + "from implicit.nearest_neighbours import TFIDFRecommender\n", + "from rectools.models.implicit_knn import ImplicitItemKNNWrapperModel\n", + "\n", + "item_knn_tfidf = ImplicitItemKNNWrapperModel(model=TFIDFRecommender(K=30))\n", + "item_knn_tfidf.fit(dataset);" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "360eafab", + "metadata": {}, + "outputs": [], + "source": [ + "recs_itemknn_tfidf = item_knn_tfidf.recommend(\n", + " test['user_id'].unique(), \n", + " dataset=dataset, \n", + " k=10, \n", + " filter_viewed=False \n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "63c31f04", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
user_iditem_idscorerank
010164581044021745.3769271
11016458445710234.8633082
2101645871028987.8781293
31016458121928957.1098134
4101645819868369.8324485
\n", + "
" + ], + "text/plain": [ + " user_id item_id score rank\n", + "0 1016458 10440 21745.376927 1\n", + "1 1016458 4457 10234.863308 2\n", + "2 1016458 7102 8987.878129 3\n", + "3 1016458 12192 8957.109813 4\n", + "4 1016458 1986 8369.832448 5" + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "recs_itemknn_tfidf.head()" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "7a4d01f7", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'prec@10': 0.023772589549238603,\n", + " 'recall@10': 0.12652382351172245,\n", + " 'MAP@10': 0.03005237337960426,\n", + " 'novelty': 6.699663403861505,\n", + " 'serendipity': 0.00010222896681730396}" + ] + }, + "execution_count": 33, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from rectools.metrics import Precision, Recall, MeanInvUserFreq, MAP, Serendipity, calc_metrics\n", + "\n", + "metrics = {\n", + " \"prec@10\": Precision(k=10),\n", + " \"recall@10\": Recall(k=10),\n", + " \"MAP@10\": MAP(k=10),\n", + " \"novelty\": MeanInvUserFreq(k=10),\n", + " \"serendipity\": Serendipity(k=10),\n", + "}\n", + "\n", + "catalog = train['item_id'].unique()\n", + "\n", + "metric_values_itemknn_tfidf = calc_metrics(\n", + " metrics,\n", + " reco=recs_itemknn_tfidf,\n", + " interactions=test,\n", + " prev_interactions=train,\n", + " catalog=catalog\n", + " )\n", + "\n", + "metric_values_itemknn_tfidf" + ] + }, + { + "cell_type": "markdown", + "id": "2270cb27", + "metadata": {}, + "source": [ + "# UserKNN BMP25" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "c7997faf", + "metadata": {}, + "outputs": [], + "source": [ + "from implicit.nearest_neighbours import BM25Recommender\n", + "from rectools.models.implicit_knn import ImplicitItemKNNWrapperModel\n", + "\n", + "item_knn_bmp = ImplicitItemKNNWrapperModel(model=BM25Recommender(K=30))\n", + "item_knn_bmp.fit(dataset);" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "c7ceb0e5", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
user_iditem_idscorerank
01016458104406.854547e+111
11016458152972.323138e+112
21016458138651.724740e+113
3101645897281.383208e+114
4101645841511.149358e+115
\n", + "
" + ], + "text/plain": [ + " user_id item_id score rank\n", + "0 1016458 10440 6.854547e+11 1\n", + "1 1016458 15297 2.323138e+11 2\n", + "2 1016458 13865 1.724740e+11 3\n", + "3 1016458 9728 1.383208e+11 4\n", + "4 1016458 4151 1.149358e+11 5" + ] + }, + "execution_count": 29, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "recs_itemknn_bmp = item_knn_bmp.recommend(\n", + " test['user_id'].unique(), \n", + " dataset=dataset, \n", + " k=10, \n", + " filter_viewed=False \n", + ")\n", + "\n", + "recs_itemknn_bmp.head()" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "e99f3649", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'prec@10': 0.03252208701450242,\n", + " 'recall@10': 0.1683399650610623,\n", + " 'MAP@10': 0.04827657497255996,\n", + " 'novelty': 3.9201705312554833,\n", + " 'serendipity': 2.616232292298612e-05}" + ] + }, + "execution_count": 31, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from rectools.metrics import Precision, Recall, MeanInvUserFreq, MAP, Serendipity, calc_metrics\n", + "\n", + "metrics = {\n", + " \"prec@10\": Precision(k=10),\n", + " \"recall@10\": Recall(k=10),\n", + " \"MAP@10\": MAP(k=10),\n", + " \"novelty\": MeanInvUserFreq(k=10),\n", + " \"serendipity\": Serendipity(k=10),\n", + "}\n", + "\n", + "catalog = train['item_id'].unique()\n", + "\n", + "metric_values_itemknn_bmp = calc_metrics(\n", + " metrics,\n", + " reco=recs_itemknn_bmp,\n", + " interactions=test,\n", + " prev_interactions=train,\n", + " catalog=catalog\n", + " )\n", + "\n", + "metric_values_itemknn_bmp" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "84fe056a", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.8.10" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/notebooks/HW-3.3-rectools-cv.ipynb b/notebooks/HW-3.3-rectools-cv.ipynb new file mode 100644 index 00000000..e5f56e68 --- /dev/null +++ b/notebooks/HW-3.3-rectools-cv.ipynb @@ -0,0 +1,4387 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 7, + "id": "f0145080", + "metadata": {}, + "outputs": [], + "source": [ + "import pandas as pd\n", + "import numpy as np\n", + "import scipy as sp\n", + "import requests\n", + "from tqdm.auto import tqdm\n", + "from scipy.stats import mode \n", + "from pprint import pprint\n", + "from implicit.nearest_neighbours import CosineRecommender, TFIDFRecommender, BM25Recommender\n", + "import warnings\n", + "warnings.filterwarnings(\"ignore\")\n", + "\n", + "from rectools import Columns\n", + "from rectools.model_selection import TimeRangeSplitter\n", + "from rectools.dataset import Dataset, Interactions\n", + "from rectools.models.popular import PopularModel\n", + "from rectools.models.implicit_knn import ImplicitItemKNNWrapperModel\n", + "from rectools.metrics import Precision, Recall, MeanInvUserFreq, MAP, Serendipity, calc_metrics\n", + "\n", + "pd.set_option('display.max_columns', None)\n", + "pd.set_option('display.max_colwidth', 200)" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "95ab759c", + "metadata": {}, + "outputs": [], + "source": [ + "interactions = pd.read_csv('../data/kion_train/interactions.csv')\n", + "\n", + "interactions.rename(columns={\n", + " 'last_watch_dt': Columns.Datetime,\n", + " 'total_dur': Columns.Weight\n", + " }, \n", + " inplace=True\n", + ") \n", + "\n", + "interactions['datetime'] = pd.to_datetime(interactions['datetime'])" + ] + }, + { + "cell_type": "markdown", + "id": "fbd3f42d", + "metadata": {}, + "source": [ + "# Split" + ] + }, + { + "cell_type": "markdown", + "id": "c89fcc74", + "metadata": {}, + "source": [ + "В соответствии с предположением из ноутбука \"HW-3.1\" сделаем **валидацию по 5 дней и по 7 дней**" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "368c7cf6", + "metadata": {}, + "outputs": [], + "source": [ + "def create_data_range(\n", + " last_date: pd.Timestamp, \n", + " n_folds: int = 7, \n", + " unit: str = \"W\", \n", + " n_units: int = 1, \n", + " show: bool = True,\n", + "):\n", + " periods = n_folds + 1\n", + " freq = f\"{n_units}{unit}\"\n", + " \n", + " start_date = last_date - pd.Timedelta(n_folds * n_units + n_units, unit=unit) \n", + " \n", + " date_range = pd.date_range(start=start_date, periods=periods, freq=freq, tz=last_date.tz)\n", + " \n", + " if show:\n", + " print(\n", + " f\"start_date: {start_date}\\n\"\n", + " f\"last_date: {last_date}\\n\"\n", + " f\"periods: {periods}\\n\"\n", + " f\"freq: {freq}\\n\"\n", + " f\"Test fold borders: {date_range.values.astype('datetime64[D]')}\\n\"\n", + " )\n", + " \n", + " return date_range" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "29af1fa3", + "metadata": {}, + "outputs": [], + "source": [ + "CONFIG_CV = {\n", + " \"cv_v1\": {\n", + " \"n_folds\": 5,\n", + " \"unit\": \"W\",\n", + " \"n_units\": 1,\n", + " },\n", + " \"cv_v2\": {\n", + " \"n_folds\": 5,\n", + " \"unit\": \"D\",\n", + " \"n_units\": 5,\n", + " }, \n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "3fdeb5a3", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Timestamp('2021-08-22 00:00:00')" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "last_date = interactions[Columns.Datetime].max().normalize()\n", + "last_date" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "9ee0372b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "***Folds v1***\n", + "start_date: 2021-07-11 00:00:00\n", + "last_date: 2021-08-22 00:00:00\n", + "periods: 6\n", + "freq: 1W\n", + "Test fold borders: ['2021-07-11' '2021-07-18' '2021-07-25' '2021-08-01' '2021-08-08'\n", + " '2021-08-15']\n", + "\n", + "***Folds v2***\n", + "start_date: 2021-07-23 00:00:00\n", + "last_date: 2021-08-22 00:00:00\n", + "periods: 6\n", + "freq: 5D\n", + "Test fold borders: ['2021-07-23' '2021-07-28' '2021-08-02' '2021-08-07' '2021-08-12'\n", + " '2021-08-17']\n", + "\n" + ] + } + ], + "source": [ + "print(\"***Folds v1***\")\n", + "date_range_v1 = create_data_range(\n", + " last_date, \n", + " n_folds=CONFIG_CV[\"cv_v1\"][\"n_folds\"], \n", + " unit=CONFIG_CV[\"cv_v1\"][\"unit\"], \n", + " n_units=CONFIG_CV[\"cv_v1\"][\"n_units\"]\n", + ")\n", + "\n", + "print(\"***Folds v2***\")\n", + "date_range_v2 = create_data_range(\n", + " last_date, \n", + " n_folds=CONFIG_CV[\"cv_v2\"][\"n_folds\"], \n", + " unit=CONFIG_CV[\"cv_v2\"][\"unit\"], \n", + " n_units=CONFIG_CV[\"cv_v2\"][\"n_units\"]\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "63d80785", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Real number of folds: 5\n", + "Real number of folds: 5\n" + ] + } + ], + "source": [ + "cv_v1 = TimeRangeSplitter(\n", + " date_range=date_range_v1,\n", + " filter_already_seen=True,\n", + " filter_cold_items=True,\n", + " filter_cold_users=True,\n", + ")\n", + "print(f\"Real number of folds: {cv_v1.get_n_splits(Interactions(interactions))}\")\n", + "\n", + "cv_v2 = TimeRangeSplitter(\n", + " date_range=date_range_v2,\n", + " filter_already_seen=True,\n", + " filter_cold_items=True,\n", + " filter_cold_users=True,\n", + ")\n", + "print(f\"Real number of folds: {cv_v2.get_n_splits(Interactions(interactions))}\")\n", + "\n", + "CV = [cv_v1, cv_v2]" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "1d4bc5e3", + "metadata": {}, + "outputs": [], + "source": [ + "metrics = {\n", + " \"prec@5\": Precision(k=5),\n", + " \"recall@5\": Recall(k=5),\n", + " \"MAP@5\": MAP(k=5),\n", + " \"prec@10\": Precision(k=10),\n", + " \"recall@10\": Recall(k=10),\n", + " \"MAP@10\": MAP(k=10),\n", + " \"novelty\": MeanInvUserFreq(k=10),\n", + " \"serendipity\": Serendipity(k=10),\n", + "}" + ] + }, + { + "cell_type": "markdown", + "id": "f480a12f", + "metadata": {}, + "source": [ + "# Find best models" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "48888d0d", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'popular': ,\n", + " 'popular_mw': ,\n", + " 'cosine_userknn_K30': ,\n", + " 'tfidf_userknn_K30': ,\n", + " 'bm25_userknn_K30': ,\n", + " 'cosine_userknn_K40': ,\n", + " 'tfidf_userknn_K40': ,\n", + " 'bm25_userknn_K40': ,\n", + " 'cosine_userknn_K50': ,\n", + " 'tfidf_userknn_K50': ,\n", + " 'bm25_userknn_K50': ,\n", + " 'cosine_userknn_K60': ,\n", + " 'tfidf_userknn_K60': ,\n", + " 'bm25_userknn_K60': }" + ] + }, + "execution_count": 31, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "K = [30, 40, 50, 60]\n", + "models = {\n", + " \"popular\": PopularModel(),\n", + " \"popular_mw\": PopularModel(popularity=\"mean_weight\")\n", + "}\n", + "\n", + "for k in K:\n", + " models[f\"popular\"]\n", + " models[f\"cosine_userknn_K{k}\"] = ImplicitItemKNNWrapperModel(model=CosineRecommender(K=k))\n", + " models[f\"tfidf_userknn_K{k}\"] = ImplicitItemKNNWrapperModel(model=TFIDFRecommender(K=k))\n", + " models[f\"bm25_userknn_K{k}\"] = ImplicitItemKNNWrapperModel(model=BM25Recommender(K=k))\n", + "\n", + "models" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "240478ad", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + " ***CV_0***\n", + "\n", + "==================== Fold 0\n", + "{'End date': Timestamp('2021-07-18 00:00:00', freq='W-SUN'),\n", + " 'Start date': Timestamp('2021-07-11 00:00:00', freq='W-SUN'),\n", + " 'Test': 214489,\n", + " 'Test items': 6313,\n", + " 'Test users': 84234,\n", + " 'Train': 3192875,\n", + " 'Train items': 14711,\n", + " 'Train users': 640144}\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "54fd89ff19334e3182f264d9c492bc0f", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + " 0%| | 0/14 [00:00\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
foldmodelprec@5recall@5prec@10recall@10MAP@5MAP@10noveltyserendipitycv
00popular_view-False0.0579270.1656440.0407500.2256730.0815120.0912683.5276320.000000fold_1w
10popular_view-True0.0670680.1877850.0431740.2368550.1064160.1146743.7705130.000003fold_1w
20popular_mw_view-False0.0000000.0000000.0000000.0000000.0000000.00000018.2251010.000000fold_1w
30popular_mw_view-True0.0000000.0000000.0000000.0000000.0000000.00000018.2251130.000000fold_1w
40cosine_userknn_K30_view-False0.0233090.0739180.0221430.1297750.0243640.0326517.9141100.000048fold_1w
....................................
2754cosine_userknn_K60_view-True0.0308600.0877970.0234320.1295780.0527720.0590919.1529680.000122fold_5d
2764tfidf_userknn_K60_view-False0.0197570.0608710.0214000.1222600.0196980.0285576.6513340.000095fold_5d
2774tfidf_userknn_K60_view-True0.0428030.1162650.0321730.1704580.0694600.0779126.7271280.000180fold_5d
2784bm25_userknn_K60_view-False0.0370060.1074420.0289580.1623460.0378950.0461993.9205840.000024fold_5d
2794bm25_userknn_K60_view-True0.0495680.1399710.0346460.1918270.0841810.0920224.0025740.000038fold_5d
\n", + "

280 rows × 11 columns

\n", + "" + ], + "text/plain": [ + " fold model prec@5 recall@5 prec@10 \\\n", + "0 0 popular_view-False 0.057927 0.165644 0.040750 \n", + "1 0 popular_view-True 0.067068 0.187785 0.043174 \n", + "2 0 popular_mw_view-False 0.000000 0.000000 0.000000 \n", + "3 0 popular_mw_view-True 0.000000 0.000000 0.000000 \n", + "4 0 cosine_userknn_K30_view-False 0.023309 0.073918 0.022143 \n", + ".. ... ... ... ... ... \n", + "275 4 cosine_userknn_K60_view-True 0.030860 0.087797 0.023432 \n", + "276 4 tfidf_userknn_K60_view-False 0.019757 0.060871 0.021400 \n", + "277 4 tfidf_userknn_K60_view-True 0.042803 0.116265 0.032173 \n", + "278 4 bm25_userknn_K60_view-False 0.037006 0.107442 0.028958 \n", + "279 4 bm25_userknn_K60_view-True 0.049568 0.139971 0.034646 \n", + "\n", + " recall@10 MAP@5 MAP@10 novelty serendipity cv \n", + "0 0.225673 0.081512 0.091268 3.527632 0.000000 fold_1w \n", + "1 0.236855 0.106416 0.114674 3.770513 0.000003 fold_1w \n", + "2 0.000000 0.000000 0.000000 18.225101 0.000000 fold_1w \n", + "3 0.000000 0.000000 0.000000 18.225113 0.000000 fold_1w \n", + "4 0.129775 0.024364 0.032651 7.914110 0.000048 fold_1w \n", + ".. ... ... ... ... ... ... \n", + "275 0.129578 0.052772 0.059091 9.152968 0.000122 fold_5d \n", + "276 0.122260 0.019698 0.028557 6.651334 0.000095 fold_5d \n", + "277 0.170458 0.069460 0.077912 6.727128 0.000180 fold_5d \n", + "278 0.162346 0.037895 0.046199 3.920584 0.000024 fold_5d \n", + "279 0.191827 0.084181 0.092022 4.002574 0.000038 fold_5d \n", + "\n", + "[280 rows x 11 columns]" + ] + }, + "execution_count": 42, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df_metrics = pd.DataFrame(results)\n", + "\n", + "df_metrics['cv'] = 'fold_1w'\n", + "df_metrics.loc[df_metrics[240:].index, 'cv'] = 'fold_5d'\n", + "\n", + "df_metrics" + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "id": "2c075a81", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
  prec@5recall@5prec@10recall@10MAP@5MAP@10noveltyserendipity
cvmodel        
fold_1wbm25_userknn_K30_view-False0.0421850.1198230.0341540.1853470.0425250.0526723.9462480.000025
bm25_userknn_K30_view-True0.0578090.1584620.0399610.2134740.0952320.1039424.0229380.000040
bm25_userknn_K40_view-False0.0421440.1197360.0341430.1853240.0424800.0526323.9468740.000024
bm25_userknn_K40_view-True0.0578090.1584590.0399780.2136720.0952250.1039584.0190520.000040
bm25_userknn_K50_view-False0.0427580.1212820.0346770.1878470.0430140.0533383.9522220.000024
bm25_userknn_K50_view-True0.0587270.1606350.0404860.2159230.0965760.1053574.0196010.000040
bm25_userknn_K60_view-False0.0427370.1212300.0346660.1877960.0429910.0533143.9540010.000024
bm25_userknn_K60_view-True0.0587330.1606450.0404890.2159890.0965820.1053694.0197450.000040
cosine_userknn_K30_view-False0.0182530.0566890.0183590.1056300.0186880.0258658.0175230.000059
cosine_userknn_K30_view-True0.0355550.0989330.0263640.1430560.0604240.0672809.2559170.000110
cosine_userknn_K40_view-False0.0182420.0566650.0184200.1058860.0186730.0258897.9988740.000059
cosine_userknn_K40_view-True0.0357950.0994330.0266170.1440550.0606590.0676009.1942320.000112
cosine_userknn_K50_view-False0.0185740.0575860.0187360.1074510.0189640.0262837.9764920.000059
cosine_userknn_K50_view-True0.0365740.1013450.0270880.1460780.0617260.0687059.1359440.000112
cosine_userknn_K60_view-False0.0185870.0576330.0187920.1077450.0189680.0263177.9643440.000059
cosine_userknn_K60_view-True0.0367750.1017880.0272630.1468410.0619650.0689959.0997830.000113
popular_mw_view-False0.0000010.0000040.0000010.0000050.0000010.00000118.4532010.000000
popular_mw_view-True0.0000010.0000040.0000010.0000050.0000010.00000118.4532120.000000
popular_view-False0.0478130.1347100.0336350.1829810.0675170.0751913.4627720.000000
popular_view-True0.0548010.1519420.0360520.1947680.0853480.0923823.7265770.000002
tfidf_userknn_K30_view-False0.0230980.0696630.0243060.1356420.0228240.0325196.7433550.000089
tfidf_userknn_K30_view-True0.0472470.1262870.0351810.1830160.0768820.0859166.9707800.000163
tfidf_userknn_K40_view-False0.0230880.0696410.0243660.1360420.0228090.0325576.7253990.000089
tfidf_userknn_K40_view-True0.0475380.1269040.0354420.1841430.0772930.0864016.9208620.000164
tfidf_userknn_K50_view-False0.0233680.0703830.0246770.1375270.0230520.0329166.7182830.000088
tfidf_userknn_K50_view-True0.0482450.1284920.0358830.1859640.0782480.0874186.8985940.000163
tfidf_userknn_K60_view-False0.0233350.0702720.0247020.1376080.0230200.0329066.7094850.000088
tfidf_userknn_K60_view-True0.0483400.1286640.0360000.1863860.0782780.0874876.8729720.000164
fold_5dbm25_userknn_K30_view-False0.0370910.1076370.0289830.1624440.0380040.0462953.9161130.000024
bm25_userknn_K30_view-True0.0495240.1398800.0346900.1919240.0841410.0920164.0087720.000040
bm25_userknn_K40_view-False0.0370410.1075290.0289650.1623580.0379570.0462533.9168820.000024
bm25_userknn_K40_view-True0.0495350.1398850.0346590.1918370.0841560.0920154.0040600.000039
bm25_userknn_K50_view-False0.0369970.1071580.0293990.1636720.0379280.0464873.9190890.000024
bm25_userknn_K50_view-True0.0500520.1405600.0352420.1936600.0843320.0924104.0038040.000039
bm25_userknn_K60_view-False0.0369840.1071180.0293940.1636480.0379040.0464643.9209690.000024
bm25_userknn_K60_view-True0.0500670.1405850.0352470.1937300.0843450.0924244.0038000.000039
cosine_userknn_K30_view-False0.0150530.0478120.0152010.0903210.0156960.0217948.0592570.000062
cosine_userknn_K30_view-True0.0301280.0862880.0227960.1271860.0520750.0582429.3133310.000118
cosine_userknn_K40_view-False0.0150880.0478410.0152530.0905090.0156930.0218108.0395780.000062
cosine_userknn_K40_view-True0.0304070.0868690.0230640.1281230.0523320.0585509.2453400.000120
cosine_userknn_K50_view-False0.0153600.0485870.0158260.0933330.0159890.0224308.0218540.000062
cosine_userknn_K50_view-True0.0312470.0885020.0239710.1324190.0535310.0601719.1785550.000119
cosine_userknn_K60_view-False0.0153780.0486400.0158630.0936170.0159980.0224648.0092990.000062
cosine_userknn_K60_view-True0.0314330.0888970.0241430.1332180.0537360.0604409.1400930.000120
popular_mw_view-False0.0000120.0000470.0000060.0000470.0000160.00001618.5009540.000001
popular_mw_view-True0.0000120.0000470.0000060.0000470.0000160.00001618.5009640.000001
popular_view-False0.0412890.1193840.0274250.1536150.0614460.0669593.4328550.000000
popular_view-True0.0471830.1346070.0301480.1685700.0769260.0821673.7145430.000002
tfidf_userknn_K30_view-False0.0198470.0612400.0213000.1217630.0198270.0285606.6921510.000096
tfidf_userknn_K30_view-True0.0423270.1152380.0315110.1680460.0689490.0771826.8414240.000178
tfidf_userknn_K40_view-False0.0197870.0609900.0213720.1222950.0197550.0285856.6717820.000096
tfidf_userknn_K40_view-True0.0425410.1157180.0318530.1694190.0691930.0775726.7890490.000178
tfidf_userknn_K50_view-False0.0201520.0619330.0218290.1244330.0201690.0292106.6699910.000095
tfidf_userknn_K50_view-True0.0431760.1170330.0325920.1725790.0705340.0792026.7754160.000176
tfidf_userknn_K60_view-False0.0201230.0618390.0218090.1242790.0201360.0291716.6608280.000094
tfidf_userknn_K60_view-True0.0432480.1171350.0326970.1727720.0705900.0792716.7478700.000176
\n" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 47, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df_metrics_mean = df_metrics.groupby(['cv', 'model'])[\n", + " 'prec@5', 'recall@5', 'prec@10', 'recall@10', 'MAP@5', 'MAP@10', 'novelty', 'serendipity'\n", + "].mean()\n", + "\n", + "df_metrics_mean.style.highlight_max(color='lightgreen', axis=0)" + ] + }, + { + "cell_type": "markdown", + "id": "c6f89d3a", + "metadata": {}, + "source": [ + "Из результатов видно, что среднее значение метрик моделей **bmp** имеют **наилучшие** значения, причем на недельном фолде метрики выше, чем на 5 дневном \n", + "\n", + "- Следует проверить статистически различимы значения или нет. Для этого следует посмотреть дисперсию и если дисперсия меньше чем различия между средними значениями метрик, то можно сделать вывод, что значения метрик статистически различны" + ] + }, + { + "cell_type": "code", + "execution_count": 51, + "id": "dbe6f6e9", + "metadata": { + "collapsed": true + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
prec@5recall@5prec@10recall@10MAP@5MAP@10noveltyserendipity
cvmodel
fold_1wbm25_userknn_K30_view-False0.0040420.0113800.0033190.0180500.0041440.0052350.0297301.677056e-06
bm25_userknn_K30_view-True0.0053480.0145870.0027870.0136200.0098230.0098710.0150063.357219e-06
bm25_userknn_K40_view-False0.0040360.0113930.0033100.0180330.0041490.0052340.0296131.666772e-06
bm25_userknn_K40_view-True0.0053340.0145920.0027740.0135110.0098240.0098590.0147093.359684e-06
bm25_userknn_K50_view-False0.0037770.0110130.0030870.0174760.0040390.0050710.0293291.807696e-06
bm25_userknn_K50_view-True0.0048940.0139550.0024670.0124850.0095620.0095320.0148613.329699e-06
bm25_userknn_K60_view-False0.0037740.0110020.0030880.0174920.0040380.0050740.0293681.805538e-06
bm25_userknn_K60_view-True0.0048940.0139630.0024660.0124780.0095620.0095290.0147383.340070e-06
cosine_userknn_K30_view-False0.0023930.0078030.0019180.0113260.0025180.0030910.0473375.304930e-06
cosine_userknn_K30_view-True0.0036240.0104000.0017860.0088100.0070000.0068420.0470279.087191e-06
cosine_userknn_K40_view-False0.0023890.0077940.0019080.0112950.0025170.0030810.0464455.302222e-06
cosine_userknn_K40_view-True0.0035680.0102570.0017520.0085470.0069510.0067840.0468249.276498e-06
cosine_userknn_K50_view-False0.0023160.0077730.0018440.0112840.0025120.0030720.0463455.581875e-06
cosine_userknn_K50_view-True0.0033820.0100320.0016460.0082760.0069180.0067300.0467999.814832e-06
cosine_userknn_K60_view-False0.0023140.0077920.0018590.0113540.0025140.0030830.0464545.564036e-06
cosine_userknn_K60_view-True0.0034040.0100880.0016380.0082460.0069610.0067690.0471339.793017e-06
popular_mw_view-False0.0000020.0000060.0000010.0000050.0000020.0000020.1251581.003157e-07
popular_mw_view-True0.0000020.0000060.0000010.0000050.0000020.0000020.1251551.003157e-07
popular_view-False0.0052120.0145720.0038290.0217390.0061000.0070750.0301750.000000e+00
popular_view-True0.0061040.0166560.0037930.0208750.0089550.0096530.0191312.429424e-07
tfidf_userknn_K30_view-False0.0022440.0069950.0019450.0106640.0023810.0029650.0398778.695037e-06
tfidf_userknn_K30_view-True0.0033320.0090010.0019680.0086110.0063740.0063900.0696381.279797e-05
tfidf_userknn_K40_view-False0.0022470.0070180.0019330.0105820.0023840.0029490.0408518.509896e-06
tfidf_userknn_K40_view-True0.0033190.0089040.0019240.0082880.0063000.0062880.0708191.317232e-05
tfidf_userknn_K50_view-False0.0022010.0070680.0018850.0105910.0024010.0029550.0404928.580259e-06
tfidf_userknn_K50_view-True0.0031950.0088190.0018010.0077960.0063570.0062790.0654901.351216e-05
tfidf_userknn_K60_view-False0.0022040.0070880.0018860.0106590.0024120.0029720.0408278.400699e-06
tfidf_userknn_K60_view-True0.0031990.0088290.0018090.0078410.0064200.0063480.0660501.359432e-05
fold_5dbm25_userknn_K30_view-FalseNaNNaNNaNNaNNaNNaNNaNNaN
bm25_userknn_K30_view-TrueNaNNaNNaNNaNNaNNaNNaNNaN
bm25_userknn_K40_view-FalseNaNNaNNaNNaNNaNNaNNaNNaN
bm25_userknn_K40_view-TrueNaNNaNNaNNaNNaNNaNNaNNaN
bm25_userknn_K50_view-False0.0000390.0004560.0006120.0018670.0000020.0003660.0007251.109718e-07
bm25_userknn_K50_view-True0.0006970.0008470.0008170.0025360.0002280.0005520.0012428.703289e-07
bm25_userknn_K60_view-False0.0000310.0004580.0006160.0018410.0000130.0003750.0005438.345726e-08
bm25_userknn_K60_view-True0.0007060.0008690.0008500.0026910.0002320.0005690.0017341.165137e-06
cosine_userknn_K30_view-FalseNaNNaNNaNNaNNaNNaNNaNNaN
cosine_userknn_K30_view-TrueNaNNaNNaNNaNNaNNaNNaNNaN
cosine_userknn_K40_view-FalseNaNNaNNaNNaNNaNNaNNaNNaN
cosine_userknn_K40_view-TrueNaNNaNNaNNaNNaNNaNNaNNaN
cosine_userknn_K50_view-False0.0003550.0009810.0007060.0033660.0004120.0007980.0010416.443968e-07
cosine_userknn_K50_view-True0.0007920.0014680.0009660.0047400.0013080.0018170.0183502.644474e-06
cosine_userknn_K60_view-False0.0003770.0010290.0007350.0035480.0004270.0008310.0032367.481876e-07
cosine_userknn_K60_view-True0.0008100.0015550.0010060.0051480.0013630.0019080.0182082.395850e-06
popular_mw_view-FalseNaNNaNNaNNaNNaNNaNNaNNaN
popular_mw_view-TrueNaNNaNNaNNaNNaNNaNNaNNaN
popular_view-FalseNaNNaNNaNNaNNaNNaNNaNNaN
popular_view-TrueNaNNaNNaNNaNNaNNaNNaNNaN
tfidf_userknn_K30_view-FalseNaNNaNNaNNaNNaNNaNNaNNaN
tfidf_userknn_K30_view-TrueNaNNaNNaNNaNNaNNaNNaNNaN
tfidf_userknn_K40_view-FalseNaNNaNNaNNaNNaNNaNNaNNaN
tfidf_userknn_K40_view-TrueNaNNaNNaNNaNNaNNaNNaNNaN
tfidf_userknn_K50_view-False0.0005680.0014770.0005630.0026220.0006370.0008480.0140731.071478e-06
tfidf_userknn_K50_view-True0.0006550.0012350.0007460.0033030.0015870.0019030.0288473.757778e-06
tfidf_userknn_K60_view-False0.0005170.0013700.0005780.0028550.0006190.0008680.0134261.001711e-06
tfidf_userknn_K60_view-True0.0006300.0012300.0007420.0032730.0015980.0019220.0293354.434436e-06
\n", + "
" + ], + "text/plain": [ + " prec@5 recall@5 prec@10 \\\n", + "cv model \n", + "fold_1w bm25_userknn_K30_view-False 0.004042 0.011380 0.003319 \n", + " bm25_userknn_K30_view-True 0.005348 0.014587 0.002787 \n", + " bm25_userknn_K40_view-False 0.004036 0.011393 0.003310 \n", + " bm25_userknn_K40_view-True 0.005334 0.014592 0.002774 \n", + " bm25_userknn_K50_view-False 0.003777 0.011013 0.003087 \n", + " bm25_userknn_K50_view-True 0.004894 0.013955 0.002467 \n", + " bm25_userknn_K60_view-False 0.003774 0.011002 0.003088 \n", + " bm25_userknn_K60_view-True 0.004894 0.013963 0.002466 \n", + " cosine_userknn_K30_view-False 0.002393 0.007803 0.001918 \n", + " cosine_userknn_K30_view-True 0.003624 0.010400 0.001786 \n", + " cosine_userknn_K40_view-False 0.002389 0.007794 0.001908 \n", + " cosine_userknn_K40_view-True 0.003568 0.010257 0.001752 \n", + " cosine_userknn_K50_view-False 0.002316 0.007773 0.001844 \n", + " cosine_userknn_K50_view-True 0.003382 0.010032 0.001646 \n", + " cosine_userknn_K60_view-False 0.002314 0.007792 0.001859 \n", + " cosine_userknn_K60_view-True 0.003404 0.010088 0.001638 \n", + " popular_mw_view-False 0.000002 0.000006 0.000001 \n", + " popular_mw_view-True 0.000002 0.000006 0.000001 \n", + " popular_view-False 0.005212 0.014572 0.003829 \n", + " popular_view-True 0.006104 0.016656 0.003793 \n", + " tfidf_userknn_K30_view-False 0.002244 0.006995 0.001945 \n", + " tfidf_userknn_K30_view-True 0.003332 0.009001 0.001968 \n", + " tfidf_userknn_K40_view-False 0.002247 0.007018 0.001933 \n", + " tfidf_userknn_K40_view-True 0.003319 0.008904 0.001924 \n", + " tfidf_userknn_K50_view-False 0.002201 0.007068 0.001885 \n", + " tfidf_userknn_K50_view-True 0.003195 0.008819 0.001801 \n", + " tfidf_userknn_K60_view-False 0.002204 0.007088 0.001886 \n", + " tfidf_userknn_K60_view-True 0.003199 0.008829 0.001809 \n", + "fold_5d bm25_userknn_K30_view-False NaN NaN NaN \n", + " bm25_userknn_K30_view-True NaN NaN NaN \n", + " bm25_userknn_K40_view-False NaN NaN NaN \n", + " bm25_userknn_K40_view-True NaN NaN NaN \n", + " bm25_userknn_K50_view-False 0.000039 0.000456 0.000612 \n", + " bm25_userknn_K50_view-True 0.000697 0.000847 0.000817 \n", + " bm25_userknn_K60_view-False 0.000031 0.000458 0.000616 \n", + " bm25_userknn_K60_view-True 0.000706 0.000869 0.000850 \n", + " cosine_userknn_K30_view-False NaN NaN NaN \n", + " cosine_userknn_K30_view-True NaN NaN NaN \n", + " cosine_userknn_K40_view-False NaN NaN NaN \n", + " cosine_userknn_K40_view-True NaN NaN NaN \n", + " cosine_userknn_K50_view-False 0.000355 0.000981 0.000706 \n", + " cosine_userknn_K50_view-True 0.000792 0.001468 0.000966 \n", + " cosine_userknn_K60_view-False 0.000377 0.001029 0.000735 \n", + " cosine_userknn_K60_view-True 0.000810 0.001555 0.001006 \n", + " popular_mw_view-False NaN NaN NaN \n", + " popular_mw_view-True NaN NaN NaN \n", + " popular_view-False NaN NaN NaN \n", + " popular_view-True NaN NaN NaN \n", + " tfidf_userknn_K30_view-False NaN NaN NaN \n", + " tfidf_userknn_K30_view-True NaN NaN NaN \n", + " tfidf_userknn_K40_view-False NaN NaN NaN \n", + " tfidf_userknn_K40_view-True NaN NaN NaN \n", + " tfidf_userknn_K50_view-False 0.000568 0.001477 0.000563 \n", + " tfidf_userknn_K50_view-True 0.000655 0.001235 0.000746 \n", + " tfidf_userknn_K60_view-False 0.000517 0.001370 0.000578 \n", + " tfidf_userknn_K60_view-True 0.000630 0.001230 0.000742 \n", + "\n", + " recall@10 MAP@5 MAP@10 \\\n", + "cv model \n", + "fold_1w bm25_userknn_K30_view-False 0.018050 0.004144 0.005235 \n", + " bm25_userknn_K30_view-True 0.013620 0.009823 0.009871 \n", + " bm25_userknn_K40_view-False 0.018033 0.004149 0.005234 \n", + " bm25_userknn_K40_view-True 0.013511 0.009824 0.009859 \n", + " bm25_userknn_K50_view-False 0.017476 0.004039 0.005071 \n", + " bm25_userknn_K50_view-True 0.012485 0.009562 0.009532 \n", + " bm25_userknn_K60_view-False 0.017492 0.004038 0.005074 \n", + " bm25_userknn_K60_view-True 0.012478 0.009562 0.009529 \n", + " cosine_userknn_K30_view-False 0.011326 0.002518 0.003091 \n", + " cosine_userknn_K30_view-True 0.008810 0.007000 0.006842 \n", + " cosine_userknn_K40_view-False 0.011295 0.002517 0.003081 \n", + " cosine_userknn_K40_view-True 0.008547 0.006951 0.006784 \n", + " cosine_userknn_K50_view-False 0.011284 0.002512 0.003072 \n", + " cosine_userknn_K50_view-True 0.008276 0.006918 0.006730 \n", + " cosine_userknn_K60_view-False 0.011354 0.002514 0.003083 \n", + " cosine_userknn_K60_view-True 0.008246 0.006961 0.006769 \n", + " popular_mw_view-False 0.000005 0.000002 0.000002 \n", + " popular_mw_view-True 0.000005 0.000002 0.000002 \n", + " popular_view-False 0.021739 0.006100 0.007075 \n", + " popular_view-True 0.020875 0.008955 0.009653 \n", + " tfidf_userknn_K30_view-False 0.010664 0.002381 0.002965 \n", + " tfidf_userknn_K30_view-True 0.008611 0.006374 0.006390 \n", + " tfidf_userknn_K40_view-False 0.010582 0.002384 0.002949 \n", + " tfidf_userknn_K40_view-True 0.008288 0.006300 0.006288 \n", + " tfidf_userknn_K50_view-False 0.010591 0.002401 0.002955 \n", + " tfidf_userknn_K50_view-True 0.007796 0.006357 0.006279 \n", + " tfidf_userknn_K60_view-False 0.010659 0.002412 0.002972 \n", + " tfidf_userknn_K60_view-True 0.007841 0.006420 0.006348 \n", + "fold_5d bm25_userknn_K30_view-False NaN NaN NaN \n", + " bm25_userknn_K30_view-True NaN NaN NaN \n", + " bm25_userknn_K40_view-False NaN NaN NaN \n", + " bm25_userknn_K40_view-True NaN NaN NaN \n", + " bm25_userknn_K50_view-False 0.001867 0.000002 0.000366 \n", + " bm25_userknn_K50_view-True 0.002536 0.000228 0.000552 \n", + " bm25_userknn_K60_view-False 0.001841 0.000013 0.000375 \n", + " bm25_userknn_K60_view-True 0.002691 0.000232 0.000569 \n", + " cosine_userknn_K30_view-False NaN NaN NaN \n", + " cosine_userknn_K30_view-True NaN NaN NaN \n", + " cosine_userknn_K40_view-False NaN NaN NaN \n", + " cosine_userknn_K40_view-True NaN NaN NaN \n", + " cosine_userknn_K50_view-False 0.003366 0.000412 0.000798 \n", + " cosine_userknn_K50_view-True 0.004740 0.001308 0.001817 \n", + " cosine_userknn_K60_view-False 0.003548 0.000427 0.000831 \n", + " cosine_userknn_K60_view-True 0.005148 0.001363 0.001908 \n", + " popular_mw_view-False NaN NaN NaN \n", + " popular_mw_view-True NaN NaN NaN \n", + " popular_view-False NaN NaN NaN \n", + " popular_view-True NaN NaN NaN \n", + " tfidf_userknn_K30_view-False NaN NaN NaN \n", + " tfidf_userknn_K30_view-True NaN NaN NaN \n", + " tfidf_userknn_K40_view-False NaN NaN NaN \n", + " tfidf_userknn_K40_view-True NaN NaN NaN \n", + " tfidf_userknn_K50_view-False 0.002622 0.000637 0.000848 \n", + " tfidf_userknn_K50_view-True 0.003303 0.001587 0.001903 \n", + " tfidf_userknn_K60_view-False 0.002855 0.000619 0.000868 \n", + " tfidf_userknn_K60_view-True 0.003273 0.001598 0.001922 \n", + "\n", + " novelty serendipity \n", + "cv model \n", + "fold_1w bm25_userknn_K30_view-False 0.029730 1.677056e-06 \n", + " bm25_userknn_K30_view-True 0.015006 3.357219e-06 \n", + " bm25_userknn_K40_view-False 0.029613 1.666772e-06 \n", + " bm25_userknn_K40_view-True 0.014709 3.359684e-06 \n", + " bm25_userknn_K50_view-False 0.029329 1.807696e-06 \n", + " bm25_userknn_K50_view-True 0.014861 3.329699e-06 \n", + " bm25_userknn_K60_view-False 0.029368 1.805538e-06 \n", + " bm25_userknn_K60_view-True 0.014738 3.340070e-06 \n", + " cosine_userknn_K30_view-False 0.047337 5.304930e-06 \n", + " cosine_userknn_K30_view-True 0.047027 9.087191e-06 \n", + " cosine_userknn_K40_view-False 0.046445 5.302222e-06 \n", + " cosine_userknn_K40_view-True 0.046824 9.276498e-06 \n", + " cosine_userknn_K50_view-False 0.046345 5.581875e-06 \n", + " cosine_userknn_K50_view-True 0.046799 9.814832e-06 \n", + " cosine_userknn_K60_view-False 0.046454 5.564036e-06 \n", + " cosine_userknn_K60_view-True 0.047133 9.793017e-06 \n", + " popular_mw_view-False 0.125158 1.003157e-07 \n", + " popular_mw_view-True 0.125155 1.003157e-07 \n", + " popular_view-False 0.030175 0.000000e+00 \n", + " popular_view-True 0.019131 2.429424e-07 \n", + " tfidf_userknn_K30_view-False 0.039877 8.695037e-06 \n", + " tfidf_userknn_K30_view-True 0.069638 1.279797e-05 \n", + " tfidf_userknn_K40_view-False 0.040851 8.509896e-06 \n", + " tfidf_userknn_K40_view-True 0.070819 1.317232e-05 \n", + " tfidf_userknn_K50_view-False 0.040492 8.580259e-06 \n", + " tfidf_userknn_K50_view-True 0.065490 1.351216e-05 \n", + " tfidf_userknn_K60_view-False 0.040827 8.400699e-06 \n", + " tfidf_userknn_K60_view-True 0.066050 1.359432e-05 \n", + "fold_5d bm25_userknn_K30_view-False NaN NaN \n", + " bm25_userknn_K30_view-True NaN NaN \n", + " bm25_userknn_K40_view-False NaN NaN \n", + " bm25_userknn_K40_view-True NaN NaN \n", + " bm25_userknn_K50_view-False 0.000725 1.109718e-07 \n", + " bm25_userknn_K50_view-True 0.001242 8.703289e-07 \n", + " bm25_userknn_K60_view-False 0.000543 8.345726e-08 \n", + " bm25_userknn_K60_view-True 0.001734 1.165137e-06 \n", + " cosine_userknn_K30_view-False NaN NaN \n", + " cosine_userknn_K30_view-True NaN NaN \n", + " cosine_userknn_K40_view-False NaN NaN \n", + " cosine_userknn_K40_view-True NaN NaN \n", + " cosine_userknn_K50_view-False 0.001041 6.443968e-07 \n", + " cosine_userknn_K50_view-True 0.018350 2.644474e-06 \n", + " cosine_userknn_K60_view-False 0.003236 7.481876e-07 \n", + " cosine_userknn_K60_view-True 0.018208 2.395850e-06 \n", + " popular_mw_view-False NaN NaN \n", + " popular_mw_view-True NaN NaN \n", + " popular_view-False NaN NaN \n", + " popular_view-True NaN NaN \n", + " tfidf_userknn_K30_view-False NaN NaN \n", + " tfidf_userknn_K30_view-True NaN NaN \n", + " tfidf_userknn_K40_view-False NaN NaN \n", + " tfidf_userknn_K40_view-True NaN NaN \n", + " tfidf_userknn_K50_view-False 0.014073 1.071478e-06 \n", + " tfidf_userknn_K50_view-True 0.028847 3.757778e-06 \n", + " tfidf_userknn_K60_view-False 0.013426 1.001711e-06 \n", + " tfidf_userknn_K60_view-True 0.029335 4.434436e-06 " + ] + }, + "execution_count": 51, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df_metrics_std = df_metrics.groupby(['cv', 'model'])[\n", + " 'prec@5', 'recall@5', 'prec@10', 'recall@10', 'MAP@5', 'MAP@10', 'novelty', 'serendipity'\n", + "].std()\n", + "\n", + "df_metrics_std" + ] + }, + { + "cell_type": "code", + "execution_count": 86, + "id": "58ad9d07", + "metadata": {}, + "outputs": [], + "source": [ + "df_metrics_1w_mean = df_metrics_mean.loc[\"fold_1w\"]\n", + "df_metrics_1w_std = df_metrics_std.loc[\"fold_1w\"]" + ] + }, + { + "cell_type": "code", + "execution_count": 82, + "id": "52bffafc", + "metadata": {}, + "outputs": [], + "source": [ + "best_model = \"bm25_userknn_K60_view-True\"\n", + "col_metrics = list(metrics.keys())\n", + "std_best_metrics = df_metrics_1w_std[df_metrics_1w_std[\"model\"] == best_model][col_metrics].values[0]" + ] + }, + { + "cell_type": "code", + "execution_count": 95, + "id": "0059da61", + "metadata": { + "collapsed": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "prec@5 0.004894\n", + "recall@5 0.013963\n", + "prec@10 0.002466\n", + "recall@10 0.012478\n", + "MAP@5 0.009562\n", + "MAP@10 0.009529\n", + "novelty 0.014738\n", + "serendipity 0.000003\n", + "Name: bm25_userknn_K60_view-True, dtype: float64\n", + "\n", + "===Сравнение с bm25_userknn_K30_view-False\n", + "prec@5 0.000852\n", + "recall@5 0.002584\n", + "prec@10 -0.000854\n", + "recall@10 -0.005571\n", + "MAP@5 0.005418\n", + "MAP@10 0.004293\n", + "novelty -0.014992\n", + "serendipity 0.000002\n", + "dtype: float64\n", + "=========================\n", + "\n", + "===Сравнение с bm25_userknn_K30_view-True\n", + "prec@5 -4.545181e-04\n", + "recall@5 -6.232778e-04\n", + "prec@10 -3.208519e-04\n", + "recall@10 -1.141463e-03\n", + "MAP@5 -2.606624e-04\n", + "MAP@10 -3.423636e-04\n", + "novelty -2.682734e-04\n", + "serendipity -1.714842e-08\n", + "dtype: float64\n", + "=========================\n", + "\n", + "===Сравнение с bm25_userknn_K40_view-False\n", + "prec@5 0.000858\n", + "recall@5 0.002570\n", + "prec@10 -0.000844\n", + "recall@10 -0.005554\n", + "MAP@5 0.005413\n", + "MAP@10 0.004294\n", + "novelty -0.014875\n", + "serendipity 0.000002\n", + "dtype: float64\n", + "=========================\n", + "\n", + "===Сравнение с bm25_userknn_K40_view-True\n", + "prec@5 -4.403880e-04\n", + "recall@5 -6.290758e-04\n", + "prec@10 -3.080923e-04\n", + "recall@10 -1.032860e-03\n", + "MAP@5 -2.622039e-04\n", + "MAP@10 -3.305713e-04\n", + "novelty 2.940168e-05\n", + "serendipity -1.961419e-08\n", + "dtype: float64\n", + "=========================\n", + "\n", + "===Сравнение с bm25_userknn_K50_view-False\n", + "prec@5 0.001117\n", + "recall@5 0.002950\n", + "prec@10 -0.000621\n", + "recall@10 -0.004998\n", + "MAP@5 0.005523\n", + "MAP@10 0.004457\n", + "novelty -0.014591\n", + "serendipity 0.000002\n", + "dtype: float64\n", + "=========================\n", + "\n", + "===Сравнение с bm25_userknn_K50_view-True\n", + "prec@5 -6.188074e-08\n", + "recall@5 8.340206e-06\n", + "prec@10 -1.581047e-06\n", + "recall@10 -6.557768e-06\n", + "MAP@5 8.532418e-08\n", + "MAP@10 -3.212381e-06\n", + "novelty -1.225529e-04\n", + "serendipity 1.037090e-08\n", + "dtype: float64\n", + "=========================\n", + "\n", + "===Сравнение с bm25_userknn_K60_view-False\n", + "prec@5 0.001119\n", + "recall@5 0.002961\n", + "prec@10 -0.000622\n", + "recall@10 -0.005014\n", + "MAP@5 0.005524\n", + "MAP@10 0.004455\n", + "novelty -0.014630\n", + "serendipity 0.000002\n", + "dtype: float64\n", + "=========================\n", + "\n", + "===Сравнение с cosine_userknn_K30_view-False\n", + "prec@5 0.002501\n", + "recall@5 0.006161\n", + "prec@10 0.000548\n", + "recall@10 0.001152\n", + "MAP@5 0.007044\n", + "MAP@10 0.006437\n", + "novelty -0.032599\n", + "serendipity -0.000002\n", + "dtype: float64\n", + "=========================\n", + "\n", + "===Сравнение с cosine_userknn_K30_view-True\n", + "prec@5 0.001270\n", + "recall@5 0.003563\n", + "prec@10 0.000680\n", + "recall@10 0.003668\n", + "MAP@5 0.002563\n", + "MAP@10 0.002687\n", + "novelty -0.032289\n", + "serendipity -0.000006\n", + "dtype: float64\n", + "=========================\n", + "\n", + "===Сравнение с cosine_userknn_K40_view-False\n", + "prec@5 0.002504\n", + "recall@5 0.006169\n", + "prec@10 0.000558\n", + "recall@10 0.001184\n", + "MAP@5 0.007046\n", + "MAP@10 0.006448\n", + "novelty -0.031707\n", + "serendipity -0.000002\n", + "dtype: float64\n", + "=========================\n", + "\n", + "===Сравнение с cosine_userknn_K40_view-True\n", + "prec@5 0.001325\n", + "recall@5 0.003706\n", + "prec@10 0.000714\n", + "recall@10 0.003931\n", + "MAP@5 0.002611\n", + "MAP@10 0.002744\n", + "novelty -0.032086\n", + "serendipity -0.000006\n", + "dtype: float64\n", + "=========================\n", + "\n", + "===Сравнение с cosine_userknn_K50_view-False\n", + "prec@5 0.002578\n", + "recall@5 0.006190\n", + "prec@10 0.000622\n", + "recall@10 0.001195\n", + "MAP@5 0.007051\n", + "MAP@10 0.006456\n", + "novelty -0.031607\n", + "serendipity -0.000002\n", + "dtype: float64\n", + "=========================\n", + "\n", + "===Сравнение с cosine_userknn_K50_view-True\n", + "prec@5 0.001511\n", + "recall@5 0.003931\n", + "prec@10 0.000820\n", + "recall@10 0.004203\n", + "MAP@5 0.002644\n", + "MAP@10 0.002798\n", + "novelty -0.032061\n", + "serendipity -0.000006\n", + "dtype: float64\n", + "=========================\n", + "\n", + "===Сравнение с cosine_userknn_K60_view-False\n", + "prec@5 0.002580\n", + "recall@5 0.006172\n", + "prec@10 0.000607\n", + "recall@10 0.001124\n", + "MAP@5 0.007049\n", + "MAP@10 0.006446\n", + "novelty -0.031716\n", + "serendipity -0.000002\n", + "dtype: float64\n", + "=========================\n", + "\n", + "===Сравнение с cosine_userknn_K60_view-True\n", + "prec@5 0.001489\n", + "recall@5 0.003875\n", + "prec@10 0.000827\n", + "recall@10 0.004232\n", + "MAP@5 0.002601\n", + "MAP@10 0.002760\n", + "novelty -0.032395\n", + "serendipity -0.000006\n", + "dtype: float64\n", + "=========================\n", + "\n", + "===Сравнение с popular_mw_view-False\n", + "prec@5 0.004892\n", + "recall@5 0.013958\n", + "prec@10 0.002465\n", + "recall@10 0.012473\n", + "MAP@5 0.009561\n", + "MAP@10 0.009527\n", + "novelty -0.110420\n", + "serendipity 0.000003\n", + "dtype: float64\n", + "=========================\n", + "\n", + "===Сравнение с popular_mw_view-True\n", + "prec@5 0.004892\n", + "recall@5 0.013958\n", + "prec@10 0.002465\n", + "recall@10 0.012473\n", + "MAP@5 0.009561\n", + "MAP@10 0.009527\n", + "novelty -0.110417\n", + "serendipity 0.000003\n", + "dtype: float64\n", + "=========================\n", + "\n", + "===Сравнение с popular_view-False\n", + "prec@5 -0.000319\n", + "recall@5 -0.000609\n", + "prec@10 -0.001363\n", + "recall@10 -0.009260\n", + "MAP@5 0.003462\n", + "MAP@10 0.002453\n", + "novelty -0.015437\n", + "serendipity 0.000003\n", + "dtype: float64\n", + "=========================\n", + "\n", + "===Сравнение с popular_view-True\n", + "prec@5 -0.001210\n", + "recall@5 -0.002692\n", + "prec@10 -0.001327\n", + "recall@10 -0.008397\n", + "MAP@5 0.000607\n", + "MAP@10 -0.000124\n", + "novelty -0.004393\n", + "serendipity 0.000003\n", + "dtype: float64\n", + "=========================\n", + "\n", + "===Сравнение с tfidf_userknn_K30_view-False\n", + "prec@5 0.002649\n", + "recall@5 0.006968\n", + "prec@10 0.000521\n", + "recall@10 0.001815\n", + "MAP@5 0.007181\n", + "MAP@10 0.006564\n", + "novelty -0.025139\n", + "serendipity -0.000005\n", + "dtype: float64\n", + "=========================\n", + "\n", + "===Сравнение с tfidf_userknn_K30_view-True\n", + "prec@5 0.001561\n", + "recall@5 0.004963\n", + "prec@10 0.000498\n", + "recall@10 0.003867\n", + "MAP@5 0.003188\n", + "MAP@10 0.003139\n", + "novelty -0.054900\n", + "serendipity -0.000009\n", + "dtype: float64\n", + "=========================\n", + "\n", + "===Сравнение с tfidf_userknn_K40_view-False\n", + "prec@5 0.002647\n", + "recall@5 0.006945\n", + "prec@10 0.000532\n", + "recall@10 0.001897\n", + "MAP@5 0.007178\n", + "MAP@10 0.006579\n", + "novelty -0.026113\n", + "serendipity -0.000005\n", + "dtype: float64\n", + "=========================\n", + "\n", + "===Сравнение с tfidf_userknn_K40_view-True\n", + "prec@5 0.001575\n", + "recall@5 0.005059\n", + "prec@10 0.000542\n", + "recall@10 0.004190\n", + "MAP@5 0.003262\n", + "MAP@10 0.003240\n", + "novelty -0.056080\n", + "serendipity -0.000010\n", + "dtype: float64\n", + "=========================\n", + "\n", + "===Сравнение с tfidf_userknn_K50_view-False\n", + "prec@5 0.002693\n", + "recall@5 0.006895\n", + "prec@10 0.000581\n", + "recall@10 0.001887\n", + "MAP@5 0.007161\n", + "MAP@10 0.006574\n", + "novelty -0.025754\n", + "serendipity -0.000005\n", + "dtype: float64\n", + "=========================\n", + "\n", + "===Сравнение с tfidf_userknn_K50_view-True\n", + "prec@5 0.001698\n", + "recall@5 0.005144\n", + "prec@10 0.000665\n", + "recall@10 0.004682\n", + "MAP@5 0.003205\n", + "MAP@10 0.003249\n", + "novelty -0.050752\n", + "serendipity -0.000010\n", + "dtype: float64\n", + "=========================\n", + "\n", + "===Сравнение с tfidf_userknn_K60_view-False\n", + "prec@5 0.002690\n", + "recall@5 0.006875\n", + "prec@10 0.000580\n", + "recall@10 0.001819\n", + "MAP@5 0.007150\n", + "MAP@10 0.006557\n", + "novelty -0.026089\n", + "serendipity -0.000005\n", + "dtype: float64\n", + "=========================\n", + "\n", + "===Сравнение с tfidf_userknn_K60_view-True\n", + "prec@5 0.001694\n", + "recall@5 0.005134\n", + "prec@10 0.000657\n", + "recall@10 0.004637\n", + "MAP@5 0.003142\n", + "MAP@10 0.003180\n", + "novelty -0.051312\n", + "serendipity -0.000010\n", + "dtype: float64\n", + "=========================\n" + ] + } + ], + "source": [ + "print(df_metrics_1w_std.loc[best_model])\n", + "for model in df_metrics_1w_mean.index:\n", + " if model != best_model:\n", + " print(f\"\\n===Сравнение с {model}\")\n", + " print(df_metrics_1w_mean.loc[best_model] - df_metrics_1w_mean.loc[model])\n", + " print(\"=========================\")" + ] + }, + { + "cell_type": "markdown", + "id": "0675ba9b", + "metadata": {}, + "source": [ + "Лучшей модели большинством из моделей видны статистические различия, кроме всех моделей bmp (логично, потому что лучшая модель bmp с k = 60) и моделью tfidf, где для рекомендаций стоял флаг filter_viewed = True, что означает рекомендовать не одинаковые элементы для всех пользователей" + ] + }, + { + "cell_type": "markdown", + "id": "e233b183", + "metadata": {}, + "source": [ + "# Обучение на всех имеющихся данных и формирование оффлайн рекомендаций" + ] + }, + { + "cell_type": "code", + "execution_count": 98, + "id": "30e985b6", + "metadata": {}, + "outputs": [], + "source": [ + "dataset = Dataset.construct(\n", + " interactions_df=interactions,\n", + " user_features_df=None,\n", + " item_features_df=None\n", + ")\n", + "\n", + "bmp25_k60_model = ImplicitItemKNNWrapperModel(BM25Recommender(K=60))\n", + "bmp25_k60_model.fit(dataset)\n", + "\n", + "K_RECOS = 30\n", + " \n", + "recos_offline_bmp25 = bmp25_k60_model.recommend(\n", + " users=interactions[Columns.User].unique(),\n", + " dataset=dataset,\n", + " k=K_RECOS,\n", + " filter_viewed=True,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 100, + "id": "4034d96f", + "metadata": {}, + "outputs": [], + "source": [ + "recos_offline_bmp25.to_csv(\"../data/hw_3/bmp_25_k60_rectools.csv\", index=False)" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "d52a48b5", + "metadata": {}, + "outputs": [], + "source": [ + "dataset = Dataset.construct(\n", + " interactions_df=interactions,\n", + " user_features_df=None,\n", + " item_features_df=None\n", + ")\n", + "\n", + "tfidf_k60_model = ImplicitItemKNNWrapperModel(TFIDFRecommender(K=60))\n", + "tfidf_k60_model.fit(dataset)\n", + "\n", + "K_RECOS = 30\n", + " \n", + "recos_offline_tfidf = tfidf_k60_model.recommend(\n", + " users=interactions[Columns.User].unique(),\n", + " dataset=dataset,\n", + " k=K_RECOS,\n", + " filter_viewed=True,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "d74a0ee6", + "metadata": {}, + "outputs": [], + "source": [ + "recos_offline_tfidf.to_csv(\"../data/hw_3/tfidf_k60_rectools.csv\", index=False)" + ] + }, + { + "cell_type": "markdown", + "id": "0164df93", + "metadata": {}, + "source": [ + "# Формирование рекомендаций для cold users" + ] + }, + { + "cell_type": "markdown", + "id": "5af7d214", + "metadata": {}, + "source": [ + "По моделям на основе популярного наилучшего качества достигали метрики по модели popular на основе количества уникальных пользователей взаимодействовавших с элементом, НО по среднему весу взаимодействия с элементами модель показывает по метрики новелти очень высокие результаты, поэтому стоит попробовать обе из моделей" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "51fdeae3", + "metadata": {}, + "outputs": [], + "source": [ + "dataset = Dataset.construct(\n", + " interactions_df=interactions,\n", + " user_features_df=None,\n", + " item_features_df=None\n", + ")\n", + "\n", + "popular_model = PopularModel()\n", + "popular_model.fit(dataset)\n", + "\n", + "item_inv = dict(enumerate(interactions[\"item_id\"].unique()))\n", + "recos_pop = []\n", + "for item_pop in popular_model.popularity_list[0]:\n", + " recos_pop.append(item_inv[item_pop])\n", + "\n", + "df_pop_recos = pd.DataFrame({\"item_id\": recos_pop})\n", + "\n", + "df_pop_recos.to_csv(\"../data/hw_3/popular_item.csv\", index=False)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "52981cce", + "metadata": {}, + "outputs": [], + "source": [ + "dataset = Dataset.construct(\n", + " interactions_df=interactions,\n", + " user_features_df=None,\n", + " item_features_df=None\n", + ")\n", + "\n", + "popular_model_mw = PopularModel(popularity=\"mean_weight\")\n", + "popular_model_mw.fit(dataset)\n", + "\n", + "item_inv = dict(enumerate(interactions[\"item_id\"].unique()))\n", + "recos_pop = []\n", + "for item_pop in popular_model_mw.popularity_list[0]:\n", + " recos_pop.append(item_inv[item_pop])\n", + "\n", + "df_pop_recos_mw = pd.DataFrame({\"item_id\": recos_pop})\n", + "\n", + "df_pop_recos_mw.to_csv(\"../data/hw_3/popular_mean_weight_item.csv\", index=False)" + ] + }, + { + "cell_type": "markdown", + "id": "170efd3c", + "metadata": {}, + "source": [ + "# Блендинг результатов моделей" + ] + }, + { + "cell_type": "markdown", + "id": "878f0b90", + "metadata": {}, + "source": [ + "Механизм блендинга будет выглядить следующим образом:\n", + "\n", + "1. Берутся рекомендации, сделанные моделями tfidf и bmp25, конкатятся результаты, удялются дубликаты item-ов\n", + "2. Берется заготовленный датаест items c полями item_id и idf\n", + "3. смотрится idf, чем он выше, тем выше будет стоять item в выдаче\n", + "\n", + "Такой подход обусловлен тем, что idf показывает обратную частоту item, соответственно в выдаче наверх будут попадать item, с которым меньшее количество раз взаимодейстовали пользователи, т.е. в перспективе такой подход может предлагать item, с которыми ни один пользователь не взаимодействовал или взаимодействовали очень мало, т.е. может решиться проблема длинного хвоста." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "3b35f8ff", + "metadata": {}, + "outputs": [], + "source": [ + "import pandas as pd" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "163f79b9", + "metadata": {}, + "outputs": [], + "source": [ + "df_bmp_recs = pd.read_csv(\"../data/hw_3/bmp_25_k60_rectools.csv\")\n", + "df_tfidf_recs = pd.read_csv(\"../data/hw_3/tfidf_k60_rectools.csv\") " + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "c842edef", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
user_iditem_idscorerank
0176549138658.899597e+101
1176549104408.153085e+102
2176549152977.204604e+103
317654937346.953473e+104
417654941514.674591e+105
2886225869726254341.615419e+1026
2886225969726211321.605160e+1027
2886226069726274761.566697e+1028
28862261697262112371.546907e+1029
28862262697262129951.542308e+1030
\n", + "
" + ], + "text/plain": [ + " user_id item_id score rank\n", + "0 176549 13865 8.899597e+10 1\n", + "1 176549 10440 8.153085e+10 2\n", + "2 176549 15297 7.204604e+10 3\n", + "3 176549 3734 6.953473e+10 4\n", + "4 176549 4151 4.674591e+10 5\n", + "28862258 697262 5434 1.615419e+10 26\n", + "28862259 697262 1132 1.605160e+10 27\n", + "28862260 697262 7476 1.566697e+10 28\n", + "28862261 697262 11237 1.546907e+10 29\n", + "28862262 697262 12995 1.542308e+10 30" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "pd.concat([df_bmp_recs.head(), df_bmp_recs.tail()])" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "576f23a7", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
user_iditem_idscorerank
01765491174913575.6601851
11765491627011946.8727082
21765491198511355.6931193
31765491315910375.5006474
41765491526610269.0196905
2886225869726261921294.34241426
28862259697262116401277.33233327
2886226069726274761262.91937728
28862261697262141213.49928129
2886226269726237841200.34778530
\n", + "
" + ], + "text/plain": [ + " user_id item_id score rank\n", + "0 176549 11749 13575.660185 1\n", + "1 176549 16270 11946.872708 2\n", + "2 176549 11985 11355.693119 3\n", + "3 176549 13159 10375.500647 4\n", + "4 176549 15266 10269.019690 5\n", + "28862258 697262 6192 1294.342414 26\n", + "28862259 697262 11640 1277.332333 27\n", + "28862260 697262 7476 1262.919377 28\n", + "28862261 697262 14 1213.499281 29\n", + "28862262 697262 3784 1200.347785 30" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "pd.concat([df_tfidf_recs.head(), df_tfidf_recs.tail()])" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "dc5bfdf3", + "metadata": {}, + "outputs": [], + "source": [ + "del df_tfidf_recs['rank'], df_bmp_recs['rank'], df_tfidf_recs['score'], df_bmp_recs['score']" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "edcc93bd", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
user_iditem_id
010975571132
110975575658
21097557142
310975573734
4109755716228
5109755712192
6109755713865
710975572657
810975579728
910975574880
10109755711778
1110975579996
1210975578636
1310975573935
1410975575803
1510975574457
1610975571844
1710975576382
1810975574716
1910975574495
4102337203734
4102337303935
4102337407417
4102337504495
4102337606382
4102337705803
4102337801844
41023379011778
4102338008636
4102338109996
4102338202657
41023383016228
4102338404880
41023385013865
410233860142
4102338706443
4102338804740
4102338906809
41023390010440
41023391014901
\n", + "
" + ], + "text/plain": [ + " user_id item_id\n", + "0 1097557 1132\n", + "1 1097557 5658\n", + "2 1097557 142\n", + "3 1097557 3734\n", + "4 1097557 16228\n", + "5 1097557 12192\n", + "6 1097557 13865\n", + "7 1097557 2657\n", + "8 1097557 9728\n", + "9 1097557 4880\n", + "10 1097557 11778\n", + "11 1097557 9996\n", + "12 1097557 8636\n", + "13 1097557 3935\n", + "14 1097557 5803\n", + "15 1097557 4457\n", + "16 1097557 1844\n", + "17 1097557 6382\n", + "18 1097557 4716\n", + "19 1097557 4495\n", + "41023372 0 3734\n", + "41023373 0 3935\n", + "41023374 0 7417\n", + "41023375 0 4495\n", + "41023376 0 6382\n", + "41023377 0 5803\n", + "41023378 0 1844\n", + "41023379 0 11778\n", + "41023380 0 8636\n", + "41023381 0 9996\n", + "41023382 0 2657\n", + "41023383 0 16228\n", + "41023384 0 4880\n", + "41023385 0 13865\n", + "41023386 0 142\n", + "41023387 0 6443\n", + "41023388 0 4740\n", + "41023389 0 6809\n", + "41023390 0 10440\n", + "41023391 0 14901" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df_all_recs = pd.concat(\n", + " [\n", + " df_bmp_recs, df_tfidf_recs\n", + " ],\n", + " ignore_index=True\n", + ").sort_values(\n", + " [\"user_id\"], ascending=False\n", + ").drop_duplicates(\n", + " [\"user_id\", \"item_id\"]\n", + ").reset_index(drop=True)\n", + "\n", + "pd.concat([df_all_recs.head(20), df_all_recs.tail(20)])" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "1267df73", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(15706, 2)\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
indexidf
095067.150811
116598.524953
271075.821207
376388.407093
466867.778734
\n", + "
" + ], + "text/plain": [ + " index idf\n", + "0 9506 7.150811\n", + "1 1659 8.524953\n", + "2 7107 5.821207\n", + "3 7638 8.407093\n", + "4 6686 7.778734" + ] + }, + "execution_count": 25, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "item_idf = pd.read_csv(\"../data/kion_train/items_idf.csv\")\n", + "print(item_idf.shape)\n", + "item_idf.head()" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "68c2c0c0", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
user_iditem_idindexidf
141097557580358036.840585
171097557638263826.806090
251097557747674766.545666
181097557471647166.480408
34109755714146.467549
24109755711640116406.318255
211097557543454346.226266
01097557113211326.183141
10109755711778117786.134312
131097557393539356.067242
231097557619261926.038563
11097557565856586.025091
261097557378437845.990008
4109755716228162285.756312
311097557741774175.715013
321097557782978295.615193
191097557449544955.563930
22109755714431144315.558556
151097557445744575.548639
28109755712995129955.495888
410233740741774175.715013
410233610782978295.615193
410233750449544955.563930
41023358014431144315.558556
410233640445744575.548639
41023363012995129955.495888
410233780184418445.419019
41023366011237112375.365593
410233600757175715.267906
410233880474047405.078522
410233800863686365.041418
410233810999699964.992277
410233890680968094.917360
4102338601421424.801620
410233840488048804.610045
410233820265726574.392592
410233720373437344.306872
410233650415141514.111983
41023385013865138653.825227
41023390010440104403.333947
\n", + "
" + ], + "text/plain": [ + " user_id item_id index idf\n", + "14 1097557 5803 5803 6.840585\n", + "17 1097557 6382 6382 6.806090\n", + "25 1097557 7476 7476 6.545666\n", + "18 1097557 4716 4716 6.480408\n", + "34 1097557 14 14 6.467549\n", + "24 1097557 11640 11640 6.318255\n", + "21 1097557 5434 5434 6.226266\n", + "0 1097557 1132 1132 6.183141\n", + "10 1097557 11778 11778 6.134312\n", + "13 1097557 3935 3935 6.067242\n", + "23 1097557 6192 6192 6.038563\n", + "1 1097557 5658 5658 6.025091\n", + "26 1097557 3784 3784 5.990008\n", + "4 1097557 16228 16228 5.756312\n", + "31 1097557 7417 7417 5.715013\n", + "32 1097557 7829 7829 5.615193\n", + "19 1097557 4495 4495 5.563930\n", + "22 1097557 14431 14431 5.558556\n", + "15 1097557 4457 4457 5.548639\n", + "28 1097557 12995 12995 5.495888\n", + "41023374 0 7417 7417 5.715013\n", + "41023361 0 7829 7829 5.615193\n", + "41023375 0 4495 4495 5.563930\n", + "41023358 0 14431 14431 5.558556\n", + "41023364 0 4457 4457 5.548639\n", + "41023363 0 12995 12995 5.495888\n", + "41023378 0 1844 1844 5.419019\n", + "41023366 0 11237 11237 5.365593\n", + "41023360 0 7571 7571 5.267906\n", + "41023388 0 4740 4740 5.078522\n", + "41023380 0 8636 8636 5.041418\n", + "41023381 0 9996 9996 4.992277\n", + "41023389 0 6809 6809 4.917360\n", + "41023386 0 142 142 4.801620\n", + "41023384 0 4880 4880 4.610045\n", + "41023382 0 2657 2657 4.392592\n", + "41023372 0 3734 3734 4.306872\n", + "41023365 0 4151 4151 4.111983\n", + "41023385 0 13865 13865 3.825227\n", + "41023390 0 10440 10440 3.333947" + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df_all_recs = df_all_recs.merge(\n", + " item_idf, left_on='item_id', right_on='index', how='left'\n", + ").sort_values(['user_id', 'idf'], ascending=False)\n", + "\n", + "pd.concat([df_all_recs.head(20), df_all_recs.tail(20)])" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "36ecc1dd", + "metadata": {}, + "outputs": [], + "source": [ + "del df_all_recs['index'], df_all_recs['idf']" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "2c868313", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Количество пользователей, у которорых рекомендаций меньше 10: 21\n" + ] + } + ], + "source": [ + "count_recs_by_users = df_all_recs.user_id.value_counts()\n", + "print(f\"Количество пользователей, у которорых рекомендаций меньше 10: {len(count_recs_by_users[count_recs_by_users < 10])}\")" + ] + }, + { + "cell_type": "markdown", + "id": "9820e5ab", + "metadata": {}, + "source": [ + "Для пользователей, у которых будет меньше рекомендаций, чем k_recs, рекомендации **будут пополняться популярным**" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "69d1fc9c", + "metadata": {}, + "outputs": [], + "source": [ + "df_popular = pd.read_csv('../data/hw_3/popular_item.csv')\n", + "users_need = count_recs_by_users[count_recs_by_users < 10].index" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "7242e8e3", + "metadata": {}, + "outputs": [], + "source": [ + "k_recs = 10\n", + "users, recs = [], []\n", + "for user, count in dict(count_recs_by_users[count_recs_by_users < 10]).items():\n", + " need_recs = k_recs - count\n", + " users.extend([user for _ in range(need_recs)])\n", + " recs.extend(df_popular[\"item_id\"][:need_recs].to_list())" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "29eaaadc", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
user_iditem_id
010975575803
110975576382
210975577476
310975574716
4109755714
.........
4102338702657
4102338803734
4102338904151
41023390013865
41023391010440
\n", + "

41023392 rows × 2 columns

\n", + "
" + ], + "text/plain": [ + " user_id item_id\n", + "0 1097557 5803\n", + "1 1097557 6382\n", + "2 1097557 7476\n", + "3 1097557 4716\n", + "4 1097557 14\n", + "... ... ...\n", + "41023387 0 2657\n", + "41023388 0 3734\n", + "41023389 0 4151\n", + "41023390 0 13865\n", + "41023391 0 10440\n", + "\n", + "[41023392 rows x 2 columns]" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df_all_recs" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "30df7064", + "metadata": {}, + "outputs": [], + "source": [ + "df_need = pd.DataFrame({\"user_id\": users, \"item_id\": recs})\n", + "df_all_recs = pd.concat([df_all_recs, df_need], ignore_index=True).sort_values(\"user_id\")" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "34f2a303", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Количество пользователей, у которорых рекомендаций меньше 10: 0\n" + ] + } + ], + "source": [ + "count_recs_by_users = df_all_recs.user_id.value_counts()\n", + "print(f\"Количество пользователей, у которорых рекомендаций меньше 10: {len(count_recs_by_users[count_recs_by_users < 10])}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "f9d7c2bc", + "metadata": {}, + "outputs": [], + "source": [ + "df_all_recs.to_csv(\"../data/hw_3/blending_tfidf_bmp25_idf_rectools.csv\", index=False)" + ] + }, + { + "cell_type": "markdown", + "id": "b8e2c037", + "metadata": {}, + "source": [ + "Offline рекомендации не работали с блендингом, решил уменьшить количество рекомендаций для одного юзера до 10 и заработало" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "6e76be8c", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(9621050, 2)" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df_all_recs['rank'] = df_all_recs.groupby('user_id').cumcount() + 1\n", + "df_all_recs_top10 = df_all_recs[df_all_recs['rank'] <= 10]\n", + "del df_all_recs_top10['rank']\n", + "df_all_recs_top10.shape" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "94d693eb", + "metadata": {}, + "outputs": [], + "source": [ + "df_all_recs_top10.to_csv(\"../data/hw_3/blending_tfidf_bmp25_idf_rectools_10.csv\", index=False)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "38f155ab", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.8.10" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/notebooks/HW-3.4-model-for-online-recs.ipynb b/notebooks/HW-3.4-model-for-online-recs.ipynb new file mode 100644 index 00000000..fafafb1f --- /dev/null +++ b/notebooks/HW-3.4-model-for-online-recs.ipynb @@ -0,0 +1,1161 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "faa0d200", + "metadata": {}, + "outputs": [], + "source": [ + "import warnings\n", + "warnings.filterwarnings(\"ignore\")" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "54683b63", + "metadata": {}, + "outputs": [], + "source": [ + "import typing as tp\n", + "\n", + "import dill\n", + "import pandas as pd\n", + "import numpy as np\n", + "from implicit.nearest_neighbours import BM25Recommender, TFIDFRecommender\n", + "from rectools import Columns\n", + "import scipy as sp" + ] + }, + { + "cell_type": "markdown", + "id": "e00f73f1", + "metadata": {}, + "source": [ + "В ноутбуку \"HW-3.3\" c помощью стратегии валидации по неделям были отобраны несколько моделей с наиболее высокими метриками:\n", + "\n", + "- BMP25Recommender с гиперпараметром k = 60\n", + "- TFIDFRecommender с гиперпараметром k = 60\n", + "\n", + "Для этих моделей сформированы оффлайн рекомендации, которые показали 0.10384918 и 0.09577425 соответственно.\n", + "\n", + "Для формирования онлайн рекомендаций следует обучить те же архитектуры моделей с такими же гиперпараметрами из библиотеки implicit" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "4e7ecfc4", + "metadata": {}, + "outputs": [], + "source": [ + "interactions = pd.read_csv('../data/kion_train/interactions.csv')\n", + "\n", + "interactions.rename(columns={\n", + " 'last_watch_dt': Columns.Datetime,\n", + " 'total_dur': Columns.Weight\n", + " }, \n", + " inplace=True\n", + ") \n", + "\n", + "interactions['datetime'] = pd.to_datetime(interactions['datetime'])" + ] + }, + { + "cell_type": "markdown", + "id": "55fbfe8e", + "metadata": {}, + "source": [ + "# Create train data" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "57f1394b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Unique users: 962179\n", + "Unique items: 15706\n" + ] + } + ], + "source": [ + "# формирование id для user и item\n", + "users_inv_mapping = dict(enumerate(interactions['user_id'].unique()))\n", + "users_mapping = {v: k for k, v in users_inv_mapping.items()}\n", + "items_inv_mapping = dict(enumerate(interactions['item_id'].unique()))\n", + "items_mapping = {v: k for k, v in items_inv_mapping.items()}\n", + "print(f\"Unique users: {len(users_inv_mapping)}\")\n", + "print(f\"Unique items: {len(items_inv_mapping)}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "bc704533", + "metadata": {}, + "outputs": [], + "source": [ + "def get_matrix(\n", + " df: pd.DataFrame,\n", + " user_col: str = Columns.User,\n", + " item_col: str = Columns.Item,\n", + " weight_col: str = None,\n", + " users_mapping: tp.Dict[int, int] = None,\n", + " items_mapping: tp.Dict[int, int] = None\n", + "):\n", + "\n", + " if weight_col:\n", + " weights = df[weight_col].astype(np.float32)\n", + " else:\n", + " weights = np.ones(len(df), dtype=np.float32)\n", + "\n", + " interaction_matrix = sp.sparse.coo_matrix((\n", + " weights,\n", + " (\n", + " df[user_col].map(users_mapping.get),\n", + " df[item_col].map(items_mapping.get)\n", + " )\n", + " ))\n", + "\n", + " watched = df.groupby(user_col).agg({item_col: list})\n", + " return interaction_matrix" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "00f46a78", + "metadata": {}, + "outputs": [], + "source": [ + "weight_matrix = get_matrix(\n", + " df=interactions,\n", + " users_mapping=users_mapping,\n", + " items_mapping=items_mapping\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "1cc272f9", + "metadata": {}, + "source": [ + "# Models train" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "2d8ec8a1", + "metadata": {}, + "outputs": [], + "source": [ + "model_implicit_tfidf = TFIDFRecommender(K=60)\n", + "model_implicit_bmp25 = BM25Recommender(K=60)" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "17990623", + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "0ce1d4c0e2184dfa8d159906a145f011", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + " 0%| | 0/962179 [00:00\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
item_id
user_id
0[7102, 14359, 15297, 6006, 9728, 12192]
1[3669, 10440]
2[7571, 3541, 15266, 13867, 12841, 10770, 4475,...
3[12192, 9728, 16406, 15719, 10440, 3475, 2025,...
4[4700, 6317]
1097553[24, 13058, 12463, 12659]
1097554[16361, 496, 1053, 11275, 4580, 1151, 849, 350...
1097555[14703, 140, 9728, 496, 6916, 4662, 4880]
1097556[12812]
1097557[4151, 3182, 15297]
\n", + "" + ], + "text/plain": [ + " item_id\n", + "user_id \n", + "0 [7102, 14359, 15297, 6006, 9728, 12192]\n", + "1 [3669, 10440]\n", + "2 [7571, 3541, 15266, 13867, 12841, 10770, 4475,...\n", + "3 [12192, 9728, 16406, 15719, 10440, 3475, 2025,...\n", + "4 [4700, 6317]\n", + "1097553 [24, 13058, 12463, 12659]\n", + "1097554 [16361, 496, 1053, 11275, 4580, 1151, 849, 350...\n", + "1097555 [14703, 140, 9728, 496, 6916, 4662, 4880]\n", + "1097556 [12812]\n", + "1097557 [4151, 3182, 15297]" + ] + }, + "execution_count": 66, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "watched = interactions.groupby('user_id').agg({'item_id': list})\n", + "pd.concat([watched.head(), watched.tail()])" + ] + }, + { + "cell_type": "code", + "execution_count": 95, + "id": "1460393c", + "metadata": {}, + "outputs": [], + "source": [ + "def recs_mapper(user, model, user_mapping, user_inv_mapping, k_reco: int = 10, bmp: bool = False):\n", + " user_id = user_mapping[user]\n", + " recs = model.similar_items(user_id, N=k_reco)\n", + " result = pd.DataFrame(\n", + " {\n", + " \"sim_user_id\": [user_inv_mapping[user] for user, _ in recs], \n", + " \"sim\": [sim for _, sim in recs] def\n", + " }\n", + " )\n", + " \n", + " if bmp:\n", + " return result[result['sim_user_id'] != user]\n", + " else: \n", + " return result[~(result['sim'] >= 1)] " + ] + }, + { + "cell_type": "code", + "execution_count": 96, + "id": "011fe4fb", + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "sample_users = interactions[Columns.User].sample(100).tolist()" + ] + }, + { + "cell_type": "code", + "execution_count": 99, + "id": "3ff09747", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "12861\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
sim_user_idsim
17372390.479295
210695580.427898
39333710.419511
44098500.391727
59892530.384045
68176360.380609
710784200.372851
81635950.370077
910037830.368852
\n", + "
" + ], + "text/plain": [ + " sim_user_id sim\n", + "1 737239 0.479295\n", + "2 1069558 0.427898\n", + "3 933371 0.419511\n", + "4 409850 0.391727\n", + "5 989253 0.384045\n", + "6 817636 0.380609\n", + "7 1078420 0.372851\n", + "8 163595 0.370077\n", + "9 1003783 0.368852" + ] + }, + "execution_count": 99, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "print(sample_users[0])\n", + "df_sim = recs_mapper(sample_users[0], model_implicit_tfidf, users_mapping, users_inv_mapping)\n", + "df_sim" + ] + }, + { + "cell_type": "code", + "execution_count": 100, + "id": "757a24ec", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
sim_user_idsimitem_id
07372390.47929510755
07372390.479295496
07372390.47929512324
07372390.47929510219
07372390.4792956898
07372390.47929514476
07372390.47929513411
07372390.4792959194
07372390.4792956404
07372390.47929514961
07372390.47929512995
110695580.4278985287
110695580.42789813973
110695580.42789813865
34098500.3917277793
49892530.3840456033
49892530.384045799
49892530.3840459617
49892530.3840455405
49892530.38404513849
49892530.38404512846
58176360.3806092981
610784200.3728513935
610784200.37285110283
71635950.3700779728
\n", + "
" + ], + "text/plain": [ + " sim_user_id sim item_id\n", + "0 737239 0.479295 10755\n", + "0 737239 0.479295 496\n", + "0 737239 0.479295 12324\n", + "0 737239 0.479295 10219\n", + "0 737239 0.479295 6898\n", + "0 737239 0.479295 14476\n", + "0 737239 0.479295 13411\n", + "0 737239 0.479295 9194\n", + "0 737239 0.479295 6404\n", + "0 737239 0.479295 14961\n", + "0 737239 0.479295 12995\n", + "1 1069558 0.427898 5287\n", + "1 1069558 0.427898 13973\n", + "1 1069558 0.427898 13865\n", + "3 409850 0.391727 7793\n", + "4 989253 0.384045 6033\n", + "4 989253 0.384045 799\n", + "4 989253 0.384045 9617\n", + "4 989253 0.384045 5405\n", + "4 989253 0.384045 13849\n", + "4 989253 0.384045 12846\n", + "5 817636 0.380609 2981\n", + "6 1078420 0.372851 3935\n", + "6 1078420 0.372851 10283\n", + "7 163595 0.370077 9728" + ] + }, + "execution_count": 100, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df_sim = df_sim.merge(\n", + " watched, left_on=['sim_user_id'], right_on=['user_id'], how='left'\n", + ").explode('item_id').sort_values(\n", + " [ 'sim'], ascending=False\n", + ").drop_duplicates(\n", + " ['item_id'], keep='first'\n", + ")\n", + "df_sim" + ] + }, + { + "cell_type": "code", + "execution_count": 101, + "id": "87a9e994", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "12861\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
sim_user_idsim
173723985.368217
2100621675.500227
393337171.779085
498925371.633147
5106955871.248461
612473569.342326
7107842067.839079
828985467.379224
940985066.214999
\n", + "
" + ], + "text/plain": [ + " sim_user_id sim\n", + "1 737239 85.368217\n", + "2 1006216 75.500227\n", + "3 933371 71.779085\n", + "4 989253 71.633147\n", + "5 1069558 71.248461\n", + "6 124735 69.342326\n", + "7 1078420 67.839079\n", + "8 289854 67.379224\n", + "9 409850 66.214999" + ] + }, + "execution_count": 101, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "print(sample_users[0])\n", + "df_sim = recs_mapper(sample_users[0], model_implicit_bmp25, users_mapping, users_inv_mapping, bmp=True)\n", + "df_sim" + ] + }, + { + "cell_type": "code", + "execution_count": 102, + "id": "2557f73f", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
sim_user_idsimitem_id
073723985.36821710755
073723985.36821714961
073723985.3682176404
073723985.3682179194
073723985.36821713411
073723985.368217496
073723985.3682176898
073723985.36821710219
073723985.36821714476
073723985.36821712324
073723985.36821712995
1100621675.50022713325
1100621675.5002278891
1100621675.5002275287
398925371.63314713865
398925371.633147799
398925371.6331476033
398925371.6331479617
398925371.63314713849
398925371.63314712846
398925371.6331475405
4106955871.24846113973
512473569.3423269288
512473569.3423262100
512473569.34232614242
512473569.3423264702
6107842067.8390793935
6107842067.83907910283
6107842067.8390792981
728985467.37922416021
728985467.3792244116
728985467.37922415464
840985066.2149997793
\n", + "
" + ], + "text/plain": [ + " sim_user_id sim item_id\n", + "0 737239 85.368217 10755\n", + "0 737239 85.368217 14961\n", + "0 737239 85.368217 6404\n", + "0 737239 85.368217 9194\n", + "0 737239 85.368217 13411\n", + "0 737239 85.368217 496\n", + "0 737239 85.368217 6898\n", + "0 737239 85.368217 10219\n", + "0 737239 85.368217 14476\n", + "0 737239 85.368217 12324\n", + "0 737239 85.368217 12995\n", + "1 1006216 75.500227 13325\n", + "1 1006216 75.500227 8891\n", + "1 1006216 75.500227 5287\n", + "3 989253 71.633147 13865\n", + "3 989253 71.633147 799\n", + "3 989253 71.633147 6033\n", + "3 989253 71.633147 9617\n", + "3 989253 71.633147 13849\n", + "3 989253 71.633147 12846\n", + "3 989253 71.633147 5405\n", + "4 1069558 71.248461 13973\n", + "5 124735 69.342326 9288\n", + "5 124735 69.342326 2100\n", + "5 124735 69.342326 14242\n", + "5 124735 69.342326 4702\n", + "6 1078420 67.839079 3935\n", + "6 1078420 67.839079 10283\n", + "6 1078420 67.839079 2981\n", + "7 289854 67.379224 16021\n", + "7 289854 67.379224 4116\n", + "7 289854 67.379224 15464\n", + "8 409850 66.214999 7793" + ] + }, + "execution_count": 102, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df_sim = df_sim.merge(\n", + " watched, left_on=['sim_user_id'], right_on=['user_id'], how='left'\n", + ").explode('item_id').sort_values(\n", + " [ 'sim'], ascending=False\n", + ").drop_duplicates(\n", + " ['item_id'], keep='first'\n", + ")\n", + "df_sim" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7e60a9c9", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.8.10" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/userknn.py b/userknn.py new file mode 100644 index 00000000..e7cf55ab --- /dev/null +++ b/userknn.py @@ -0,0 +1,112 @@ +from typing import Dict +from collections import Counter + +import pandas as pd +import numpy as np +import scipy as sp +from implicit.nearest_neighbours import ItemItemRecommender + + +class UserKnn(): + """Class for fit-perdict UserKNN model + based on ItemKNN model from implicit.nearest_neighbours + """ + + def __init__(self, model: ItemItemRecommender, N_users: int = 50): + self.N_users = N_users + self.model = model + self.is_fitted = False + + def get_mappings(self, train): + self.users_inv_mapping = dict(enumerate(train['user_id'].unique())) + self.users_mapping = {v: k for k, v in self.users_inv_mapping.items()} + + self.items_inv_mapping = dict(enumerate(train['item_id'].unique())) + self.items_mapping = {v: k for k, v in self.items_inv_mapping.items()} + + def get_matrix(self, df: pd.DataFrame, + user_col: str = 'user_id', + item_col: str = 'item_id', + weight_col: str = None, + users_mapping: Dict[int, int] = None, + items_mapping: Dict[int, int] = None): + + if weight_col: + weights = df[weight_col].astype(np.float32) + else: + weights = np.ones(len(df), dtype=np.float32) + + self.interaction_matrix = sp.sparse.coo_matrix(( + weights, + ( + df[item_col].map(self.items_mapping.get), + df[user_col].map(self.users_mapping.get) + ) + )) + + self.watched = df\ + .groupby(user_col, as_index=False)\ + .agg({item_col: list})\ + .rename(columns={user_col: 'sim_user_id'}) + + return self.interaction_matrix + + def idf(self, n: int, x: float): + return np.log((1 + n) / (1 + x) + 1) + + def _count_item_idf(self, df: pd.DataFrame): + item_cnt = Counter(df['item_id'].values) + item_idf = pd.DataFrame.from_dict(item_cnt, orient='index', + columns=['doc_freq']).reset_index() + item_idf['idf'] = item_idf['doc_freq'].apply(lambda x: self.idf(self.n, x)) + self.item_idf = item_idf + + def fit(self, train: pd.DataFrame): + self.user_knn = self.model + self.get_mappings(train) + self.weights_matrix = self.get_matrix(train, + users_mapping=self.users_mapping, + items_mapping=self.items_mapping) + + self.n = train.shape[0] + self._count_item_idf(train) + + self.user_knn.fit(self.weights_matrix) + self.is_fitted = True + + def _generate_recs_mapper(self, model: ItemItemRecommender, user_mapping: Dict[int, int], + user_inv_mapping: Dict[int, int], N: int): + def _recs_mapper(user): + user_id = self.users_mapping[user] + users, sim = model.similar_items(user_id, N=N) + return [self.users_inv_mapping[user] for user in users], sim + return _recs_mapper + + def predict(self, test: pd.DataFrame, N_recs: int = 10): + + if not self.is_fitted: + raise ValueError("Please call fit before predict") + + mapper = self._generate_recs_mapper( + model=self.user_knn, + user_mapping=self.users_mapping, + user_inv_mapping=self.users_inv_mapping, + N=self.N_users + ) + + recs = pd.DataFrame({'user_id': test['user_id'].unique()}) + recs['sim_user_id'], recs['sim'] = zip(*recs['user_id'].map(mapper)) + recs = recs.set_index('user_id').apply(pd.Series.explode).reset_index() + + recs = recs[~(recs['user_id'] == recs['sim_user_id'])]\ + .merge(self.watched, on=['sim_user_id'], how='left')\ + .explode('item_id')\ + .sort_values(['user_id', 'sim'], ascending=False)\ + .drop_duplicates(['user_id', 'item_id'], keep='first')\ + .merge(self.item_idf, left_on='item_id', right_on='index', how='left') + + recs['score'] = recs['sim'] * recs['idf'] + recs = recs.sort_values(['user_id', 'score'], ascending=False) + recs['rank'] = recs.groupby('user_id').cumcount() + 1 + return recs[recs['rank'] <= N_recs][['user_id', 'item_id', 'score', 'rank']] + \ No newline at end of file From 07f2cf022396bd77e5d62b43e3a700516775211a Mon Sep 17 00:00:00 2001 From: anettapik <120940816+anettapik@users.noreply.github.com> Date: Mon, 27 Nov 2023 17:00:17 +0300 Subject: [PATCH 5/7] Delete notebooks directory --- notebooks/HW-3.1.ipynb | 4027 ---------------- notebooks/HW-3.2-rectools-research.ipynb | 725 --- notebooks/HW-3.3-rectools-cv.ipynb | 4387 ------------------ notebooks/HW-3.4-model-for-online-recs.ipynb | 1161 ----- 4 files changed, 10300 deletions(-) delete mode 100644 notebooks/HW-3.1.ipynb delete mode 100644 notebooks/HW-3.2-rectools-research.ipynb delete mode 100644 notebooks/HW-3.3-rectools-cv.ipynb delete mode 100644 notebooks/HW-3.4-model-for-online-recs.ipynb diff --git a/notebooks/HW-3.1.ipynb b/notebooks/HW-3.1.ipynb deleted file mode 100644 index c05a3b71..00000000 --- a/notebooks/HW-3.1.ipynb +++ /dev/null @@ -1,4027 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "398a86d9", - "metadata": {}, - "outputs": [], - "source": [ - "from pprint import pprint\n", - "import warnings\n", - "warnings.filterwarnings(\"ignore\")\n", - "\n", - "import sys\n", - "sys.path.append('../')" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "8dbe6bf0", - "metadata": {}, - "outputs": [], - "source": [ - "import plotly.express as px\n", - "import numpy as np\n", - "import pandas as pd\n", - "import scipy as sp\n", - "import requests\n", - "from tqdm.auto import tqdm\n", - "from scipy.stats import mode\n", - "from implicit.nearest_neighbours import CosineRecommender, TFIDFRecommender, BM25Recommender\n", - "from rectools import Columns\n", - "from rectools.model_selection import TimeRangeSplitter\n", - "from rectools.metrics import Precision, Recall, MAP, MeanInvUserFreq, Serendipity, calc_metrics\n", - "from rectools.dataset.interactions import Interactions\n", - "\n", - "from service.utils.user_knn import UserKnn" - ] - }, - { - "cell_type": "markdown", - "id": "b1baa79f", - "metadata": {}, - "source": [ - "# Data" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "f2a9e540", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "((5476251, 5), (840197, 5), (15963, 14))" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "interactions = pd.read_csv('../data/kion_train/interactions.csv')\n", - "users = pd.read_csv('../data/kion_train/users.csv')\n", - "items = pd.read_csv('../data/kion_train/items.csv')\n", - "\n", - "interactions.shape, users.shape, items.shape" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "456d25f4", - "metadata": {}, - "outputs": [], - "source": [ - "interactions.rename(\n", - " columns={\n", - " 'last_watch_dt': Columns.Datetime,\n", - " 'total_dur': Columns.Weight\n", - " }, \n", - " inplace=True) \n", - "\n", - "interactions[Columns.Datetime] = pd.to_datetime(interactions[Columns.Datetime])" - ] - }, - { - "cell_type": "markdown", - "id": "6f7b9b0c", - "metadata": {}, - "source": [ - "## Intersection" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "7c9c0c94", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
user_iditem_iddatetimeweightwatched_pct
017654995062021-05-11425072.0
169931716592021-05-298317100.0
265668371072021-05-09100.0
386461376382021-07-0514483100.0
496486895062021-04-306725100.0
5476246648596122252021-08-13760.0
547624754686296732021-04-13230849.0
5476248697262152972021-08-201830763.0
5476249384202161972021-04-196203100.0
547625031970944362021-08-15392145.0
\n", - "
" - ], - "text/plain": [ - " user_id item_id datetime weight watched_pct\n", - "0 176549 9506 2021-05-11 4250 72.0\n", - "1 699317 1659 2021-05-29 8317 100.0\n", - "2 656683 7107 2021-05-09 10 0.0\n", - "3 864613 7638 2021-07-05 14483 100.0\n", - "4 964868 9506 2021-04-30 6725 100.0\n", - "5476246 648596 12225 2021-08-13 76 0.0\n", - "5476247 546862 9673 2021-04-13 2308 49.0\n", - "5476248 697262 15297 2021-08-20 18307 63.0\n", - "5476249 384202 16197 2021-04-19 6203 100.0\n", - "5476250 319709 4436 2021-08-15 3921 45.0" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "pd.concat([interactions.head(), interactions.tail()])" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "c5c3ce6c", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Interactions dataframe shape: (5476251, 5)\n", - "Unique users in interactions: 962179\n", - "Unique items in interactions: 15706\n" - ] - } - ], - "source": [ - "print(f\"Interactions dataframe shape: {interactions.shape}\")\n", - "print(f\"Unique users in interactions: {interactions[Columns.User].nunique()}\")\n", - "print(f\"Unique items in interactions: {interactions[Columns.Item].nunique()}\")" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "0214a978", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "min date in interactions: 2021-03-13 00:00:00\n", - "max date in interactions: 2021-08-22 00:00:00\n" - ] - } - ], - "source": [ - "max_date = interactions[Columns.Datetime].max()\n", - "min_date = interactions[Columns.Datetime].min()\n", - "\n", - "print(f\"min date in interactions: {min_date}\")\n", - "print(f\"max date in interactions: {max_date}\")" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "7829e796", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "RangeIndex: 5476251 entries, 0 to 5476250\n", - "Data columns (total 5 columns):\n", - " # Column Dtype \n", - "--- ------ ----- \n", - " 0 user_id int64 \n", - " 1 item_id int64 \n", - " 2 datetime datetime64[ns]\n", - " 3 weight int64 \n", - " 4 watched_pct float64 \n", - "dtypes: datetime64[ns](1), float64(1), int64(3)\n", - "memory usage: 208.9 MB\n" - ] - } - ], - "source": [ - "interactions.info()" - ] - }, - { - "cell_type": "markdown", - "id": "57cddf34", - "metadata": {}, - "source": [ - "## Users" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "de5dea16", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
user_idageincomesexkids_flg
0973171age_25_34income_60_90М1
1962099age_18_24income_20_40М0
21047345age_45_54income_40_60Ж0
3721985age_45_54income_20_40Ж0
4704055age_35_44income_60_90Ж0
840192339025age_65_infincome_0_20Ж0
840193983617age_18_24income_20_40Ж1
840194251008NaNNaNNaN0
840195590706NaNNaNЖ0
840196166555age_65_infincome_20_40Ж0
\n", - "
" - ], - "text/plain": [ - " user_id age income sex kids_flg\n", - "0 973171 age_25_34 income_60_90 М 1\n", - "1 962099 age_18_24 income_20_40 М 0\n", - "2 1047345 age_45_54 income_40_60 Ж 0\n", - "3 721985 age_45_54 income_20_40 Ж 0\n", - "4 704055 age_35_44 income_60_90 Ж 0\n", - "840192 339025 age_65_inf income_0_20 Ж 0\n", - "840193 983617 age_18_24 income_20_40 Ж 1\n", - "840194 251008 NaN NaN NaN 0\n", - "840195 590706 NaN NaN Ж 0\n", - "840196 166555 age_65_inf income_20_40 Ж 0" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "pd.concat([users.head(), users.tail()])" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "e4e6d2f5", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Users dataframe shape (840197, 5)\n", - "Unique users: 840197\n" - ] - } - ], - "source": [ - "print(f\"Users dataframe shape {users.shape}\")\n", - "print(f\"Unique users: {users['user_id'].nunique()}\")" - ] - }, - { - "cell_type": "markdown", - "id": "98b4ff6c", - "metadata": {}, - "source": [ - "## Items" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "19b43ff0", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
item_idcontent_typetitletitle_origrelease_yeargenrescountriesfor_kidsage_ratingstudiosdirectorsactorsdescriptionkeywords
010711filmПоговори с нейHable con ella2002.0драмы, зарубежные, детективы, мелодрамыИспанияNaN16.0NaNПедро АльмодоварАдольфо Фернандес, Ана Фернандес, Дарио Гранди...Мелодрама легендарного Педро Альмодовара «Пого...Поговори, ней, 2002, Испания, друзья, любовь, ...
12508filmГолые перцыSearch Party2014.0зарубежные, приключения, комедииСШАNaN16.0NaNСкот АрмстронгАдам Палли, Брайан Хаски, Дж.Б. Смув, Джейсон ...Уморительная современная комедия на популярную...Голые, перцы, 2014, США, друзья, свадьбы, прео...
159614538seriesСреди камнейDarklands2019.0драмы, спорт, криминалРоссия0.018.0NaNМарк О’Коннор, Конор МакМахонДэйн Уайт О’Хара, Томас Кэйн-Бирн, Джудит Родд...Семнадцатилетний Дэмиен мечтает вырваться за п...Среди, камней, 2019, Россия
159623206seriesГошаNaN2019.0комедииРоссия0.016.0NaNМихаил МироновМкртыч Арзуманян, Виктория РунцоваДобродушный Гоша не может выйти из дома, чтобы...Гоша, 2019, Россия
\n", - "
" - ], - "text/plain": [ - " item_id content_type title title_orig release_year \\\n", - "0 10711 film Поговори с ней Hable con ella 2002.0 \n", - "1 2508 film Голые перцы Search Party 2014.0 \n", - "15961 4538 series Среди камней Darklands 2019.0 \n", - "15962 3206 series Гоша NaN 2019.0 \n", - "\n", - " genres countries for_kids \\\n", - "0 драмы, зарубежные, детективы, мелодрамы Испания NaN \n", - "1 зарубежные, приключения, комедии США NaN \n", - "15961 драмы, спорт, криминал Россия 0.0 \n", - "15962 комедии Россия 0.0 \n", - "\n", - " age_rating studios directors \\\n", - "0 16.0 NaN Педро Альмодовар \n", - "1 16.0 NaN Скот Армстронг \n", - "15961 18.0 NaN Марк О’Коннор, Конор МакМахон \n", - "15962 16.0 NaN Михаил Миронов \n", - "\n", - " actors \\\n", - "0 Адольфо Фернандес, Ана Фернандес, Дарио Гранди... \n", - "1 Адам Палли, Брайан Хаски, Дж.Б. Смув, Джейсон ... \n", - "15961 Дэйн Уайт О’Хара, Томас Кэйн-Бирн, Джудит Родд... \n", - "15962 Мкртыч Арзуманян, Виктория Рунцова \n", - "\n", - " description \\\n", - "0 Мелодрама легендарного Педро Альмодовара «Пого... \n", - "1 Уморительная современная комедия на популярную... \n", - "15961 Семнадцатилетний Дэмиен мечтает вырваться за п... \n", - "15962 Добродушный Гоша не может выйти из дома, чтобы... \n", - "\n", - " keywords \n", - "0 Поговори, ней, 2002, Испания, друзья, любовь, ... \n", - "1 Голые, перцы, 2014, США, друзья, свадьбы, прео... \n", - "15961 Среди, камней, 2019, Россия \n", - "15962 Гоша, 2019, Россия " - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "pd.concat([items.head(2), items.tail(2)])" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "id": "8c8fb319", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Items dataframe shape (15963, 14)\n", - "Unique item_id: 15963\n" - ] - } - ], - "source": [ - "print(f\"Items dataframe shape {items.shape}\")\n", - "print(f\"Unique item_id: {items['item_id'].nunique()}\")" - ] - }, - { - "cell_type": "markdown", - "id": "2b35b460", - "metadata": {}, - "source": [ - "# userkNN model CV" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "id": "f60e6ecb", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - " \n", - " " - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.plotly.v1+json": { - "config": { - "plotlyServerURL": "https://plot.ly" - }, - "data": [ - { - "alignmentgroup": "True", - "hovertemplate": "variable=user_id
datetime=%{x}
value=%{y}", - "legendgroup": "user_id", - "marker": { - "color": "#636efa", - "pattern": { - "shape": "" - } - }, - "name": "user_id", - "offsetgroup": "user_id", - "orientation": "v", - "showlegend": true, - "textposition": "auto", - "type": "bar", - "x": [ - "2021-03-13T00:00:00", - "2021-03-14T00:00:00", - "2021-03-15T00:00:00", - "2021-03-16T00:00:00", - "2021-03-17T00:00:00", - "2021-03-18T00:00:00", - "2021-03-19T00:00:00", - "2021-03-20T00:00:00", - "2021-03-21T00:00:00", - "2021-03-22T00:00:00", - "2021-03-23T00:00:00", - "2021-03-24T00:00:00", - "2021-03-25T00:00:00", - "2021-03-26T00:00:00", - "2021-03-27T00:00:00", - "2021-03-28T00:00:00", - "2021-03-29T00:00:00", - "2021-03-30T00:00:00", - "2021-03-31T00:00:00", - "2021-04-01T00:00:00", - "2021-04-02T00:00:00", - "2021-04-03T00:00:00", - "2021-04-04T00:00:00", - "2021-04-05T00:00:00", - "2021-04-06T00:00:00", - "2021-04-07T00:00:00", - "2021-04-08T00:00:00", - "2021-04-09T00:00:00", - "2021-04-10T00:00:00", - "2021-04-11T00:00:00", - "2021-04-12T00:00:00", - "2021-04-13T00:00:00", - "2021-04-14T00:00:00", - "2021-04-15T00:00:00", - "2021-04-16T00:00:00", - "2021-04-17T00:00:00", - "2021-04-18T00:00:00", - "2021-04-19T00:00:00", - "2021-04-20T00:00:00", - "2021-04-21T00:00:00", - "2021-04-22T00:00:00", - "2021-04-23T00:00:00", - "2021-04-24T00:00:00", - "2021-04-25T00:00:00", - "2021-04-26T00:00:00", - "2021-04-27T00:00:00", - "2021-04-28T00:00:00", - "2021-04-29T00:00:00", - "2021-04-30T00:00:00", - "2021-05-01T00:00:00", - "2021-05-02T00:00:00", - "2021-05-03T00:00:00", - "2021-05-04T00:00:00", - "2021-05-05T00:00:00", - "2021-05-06T00:00:00", - "2021-05-07T00:00:00", - "2021-05-08T00:00:00", - "2021-05-09T00:00:00", - "2021-05-10T00:00:00", - "2021-05-11T00:00:00", - "2021-05-12T00:00:00", - "2021-05-13T00:00:00", - "2021-05-14T00:00:00", - "2021-05-15T00:00:00", - "2021-05-16T00:00:00", - "2021-05-17T00:00:00", - "2021-05-18T00:00:00", - "2021-05-19T00:00:00", - "2021-05-20T00:00:00", - "2021-05-21T00:00:00", - "2021-05-22T00:00:00", - "2021-05-23T00:00:00", - "2021-05-24T00:00:00", - "2021-05-25T00:00:00", - "2021-05-26T00:00:00", - "2021-05-27T00:00:00", - "2021-05-28T00:00:00", - "2021-05-29T00:00:00", - "2021-05-30T00:00:00", - "2021-05-31T00:00:00", - "2021-06-01T00:00:00", - "2021-06-02T00:00:00", - "2021-06-03T00:00:00", - "2021-06-04T00:00:00", - "2021-06-05T00:00:00", - "2021-06-06T00:00:00", - "2021-06-07T00:00:00", - "2021-06-08T00:00:00", - "2021-06-09T00:00:00", - "2021-06-10T00:00:00", - "2021-06-11T00:00:00", - "2021-06-12T00:00:00", - "2021-06-13T00:00:00", - "2021-06-14T00:00:00", - "2021-06-15T00:00:00", - "2021-06-16T00:00:00", - "2021-06-17T00:00:00", - "2021-06-18T00:00:00", - "2021-06-19T00:00:00", - "2021-06-20T00:00:00", - "2021-06-21T00:00:00", - "2021-06-22T00:00:00", - "2021-06-23T00:00:00", - "2021-06-24T00:00:00", - "2021-06-25T00:00:00", - "2021-06-26T00:00:00", - "2021-06-27T00:00:00", - "2021-06-28T00:00:00", - "2021-06-29T00:00:00", - "2021-06-30T00:00:00", - "2021-07-01T00:00:00", - "2021-07-02T00:00:00", - "2021-07-03T00:00:00", - "2021-07-04T00:00:00", - "2021-07-05T00:00:00", - "2021-07-06T00:00:00", - "2021-07-07T00:00:00", - "2021-07-08T00:00:00", - "2021-07-09T00:00:00", - "2021-07-10T00:00:00", - "2021-07-11T00:00:00", - "2021-07-12T00:00:00", - "2021-07-13T00:00:00", - "2021-07-14T00:00:00", - "2021-07-15T00:00:00", - "2021-07-16T00:00:00", - "2021-07-17T00:00:00", - "2021-07-18T00:00:00", - "2021-07-19T00:00:00", - "2021-07-20T00:00:00", - "2021-07-21T00:00:00", - "2021-07-22T00:00:00", - "2021-07-23T00:00:00", - "2021-07-24T00:00:00", - "2021-07-25T00:00:00", - "2021-07-26T00:00:00", - "2021-07-27T00:00:00", - "2021-07-28T00:00:00", - "2021-07-29T00:00:00", - "2021-07-30T00:00:00", - "2021-07-31T00:00:00", - "2021-08-01T00:00:00", - "2021-08-02T00:00:00", - "2021-08-03T00:00:00", - "2021-08-04T00:00:00", - "2021-08-05T00:00:00", - "2021-08-06T00:00:00", - "2021-08-07T00:00:00", - "2021-08-08T00:00:00", - "2021-08-09T00:00:00", - "2021-08-10T00:00:00", - "2021-08-11T00:00:00", - "2021-08-12T00:00:00", - "2021-08-13T00:00:00", - "2021-08-14T00:00:00", - "2021-08-15T00:00:00", - "2021-08-16T00:00:00", - "2021-08-17T00:00:00", - "2021-08-18T00:00:00", - "2021-08-19T00:00:00", - "2021-08-20T00:00:00", - "2021-08-21T00:00:00", - "2021-08-22T00:00:00" - ], - "xaxis": "x", - "y": [ - 16104, - 15606, - 12363, - 12643, - 12753, - 12788, - 13657, - 15346, - 15560, - 12752, - 13147, - 13435, - 12698, - 13909, - 15657, - 16112, - 12783, - 13101, - 13460, - 12966, - 14084, - 15431, - 15346, - 12642, - 12528, - 13129, - 13827, - 14416, - 15937, - 16046, - 12835, - 12322, - 12451, - 12275, - 13342, - 15464, - 16275, - 14286, - 20420, - 23200, - 21274, - 22127, - 26161, - 28964, - 21625, - 22590, - 21406, - 19987, - 21406, - 23479, - 24767, - 26267, - 25983, - 23941, - 23510, - 23201, - 27550, - 25986, - 27242, - 20957, - 20578, - 20729, - 21152, - 24530, - 24914, - 20960, - 20574, - 21561, - 22712, - 25697, - 27895, - 29978, - 24317, - 23667, - 22529, - 23881, - 24131, - 29035, - 31308, - 26821, - 26587, - 27577, - 28683, - 33150, - 34795, - 37096, - 31402, - 31107, - 32896, - 38964, - 37935, - 38619, - 42125, - 38973, - 35993, - 57686, - 41440, - 42174, - 43679, - 47989, - 39127, - 39693, - 41688, - 38394, - 41428, - 45898, - 48903, - 43301, - 43887, - 67749, - 53900, - 46642, - 48832, - 52812, - 43375, - 41380, - 41163, - 41592, - 40955, - 44798, - 46250, - 42487, - 43764, - 43128, - 43010, - 44878, - 49714, - 54139, - 45541, - 44431, - 44422, - 46313, - 46911, - 50317, - 54378, - 48531, - 49324, - 50267, - 50585, - 53121, - 59499, - 62128, - 53495, - 52181, - 51911, - 51047, - 53745, - 59316, - 61454, - 52794, - 53712, - 55617, - 56497, - 55843, - 61644, - 66546, - 54546, - 54311, - 56789, - 58640, - 60145, - 68834, - 71171 - ], - "yaxis": "y" - } - ], - "layout": { - "barmode": "relative", - "legend": { - "title": { - "text": "variable" - }, - "tracegroupgap": 0 - }, - "margin": { - "t": 60 - }, - "template": { - "data": { - "bar": [ - { - "error_x": { - "color": "#2a3f5f" - }, - "error_y": { - "color": "#2a3f5f" - }, - "marker": { - "line": { - "color": "#E5ECF6", - "width": 0.5 - }, - "pattern": { - "fillmode": "overlay", - "size": 10, - "solidity": 0.2 - } - }, - "type": "bar" - } - ], - "barpolar": [ - { - "marker": { - "line": { - "color": "#E5ECF6", - "width": 0.5 - }, - "pattern": { - "fillmode": "overlay", - "size": 10, - "solidity": 0.2 - } - }, - "type": "barpolar" - } - ], - "carpet": [ - { - "aaxis": { - "endlinecolor": "#2a3f5f", - "gridcolor": "white", - "linecolor": "white", - "minorgridcolor": "white", - "startlinecolor": "#2a3f5f" - }, - "baxis": { - "endlinecolor": "#2a3f5f", - "gridcolor": "white", - "linecolor": "white", - "minorgridcolor": "white", - "startlinecolor": "#2a3f5f" - }, - "type": "carpet" - } - ], - "choropleth": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "type": "choropleth" - } - ], - "contour": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "colorscale": [ - [ - 0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1, - "#f0f921" - ] - ], - "type": "contour" - } - ], - "contourcarpet": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "type": "contourcarpet" - } - ], - "heatmap": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "colorscale": [ - [ - 0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1, - "#f0f921" - ] - ], - "type": "heatmap" - } - ], - "heatmapgl": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "colorscale": [ - [ - 0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1, - "#f0f921" - ] - ], - "type": "heatmapgl" - } - ], - "histogram": [ - { - "marker": { - "pattern": { - "fillmode": "overlay", - "size": 10, - "solidity": 0.2 - } - }, - "type": "histogram" - } - ], - "histogram2d": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "colorscale": [ - [ - 0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1, - "#f0f921" - ] - ], - "type": "histogram2d" - } - ], - "histogram2dcontour": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "colorscale": [ - [ - 0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1, - "#f0f921" - ] - ], - "type": "histogram2dcontour" - } - ], - "mesh3d": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "type": "mesh3d" - } - ], - "parcoords": [ - { - "line": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "parcoords" - } - ], - "pie": [ - { - "automargin": true, - "type": "pie" - } - ], - "scatter": [ - { - "fillpattern": { - "fillmode": "overlay", - "size": 10, - "solidity": 0.2 - }, - "type": "scatter" - } - ], - "scatter3d": [ - { - "line": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scatter3d" - } - ], - "scattercarpet": [ - { - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scattercarpet" - } - ], - "scattergeo": [ - { - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scattergeo" - } - ], - "scattergl": [ - { - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scattergl" - } - ], - "scattermapbox": [ - { - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scattermapbox" - } - ], - "scatterpolar": [ - { - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scatterpolar" - } - ], - "scatterpolargl": [ - { - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scatterpolargl" - } - ], - "scatterternary": [ - { - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scatterternary" - } - ], - "surface": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "colorscale": [ - [ - 0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1, - "#f0f921" - ] - ], - "type": "surface" - } - ], - "table": [ - { - "cells": { - "fill": { - "color": "#EBF0F8" - }, - "line": { - "color": "white" - } - }, - "header": { - "fill": { - "color": "#C8D4E3" - }, - "line": { - "color": "white" - } - }, - "type": "table" - } - ] - }, - "layout": { - "annotationdefaults": { - "arrowcolor": "#2a3f5f", - "arrowhead": 0, - "arrowwidth": 1 - }, - "autotypenumbers": "strict", - "coloraxis": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "colorscale": { - "diverging": [ - [ - 0, - "#8e0152" - ], - [ - 0.1, - "#c51b7d" - ], - [ - 0.2, - "#de77ae" - ], - [ - 0.3, - "#f1b6da" - ], - [ - 0.4, - "#fde0ef" - ], - [ - 0.5, - "#f7f7f7" - ], - [ - 0.6, - "#e6f5d0" - ], - [ - 0.7, - "#b8e186" - ], - [ - 0.8, - "#7fbc41" - ], - [ - 0.9, - "#4d9221" - ], - [ - 1, - "#276419" - ] - ], - "sequential": [ - [ - 0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1, - "#f0f921" - ] - ], - "sequentialminus": [ - [ - 0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1, - "#f0f921" - ] - ] - }, - "colorway": [ - "#636efa", - "#EF553B", - "#00cc96", - "#ab63fa", - "#FFA15A", - "#19d3f3", - "#FF6692", - "#B6E880", - "#FF97FF", - "#FECB52" - ], - "font": { - "color": "#2a3f5f" - }, - "geo": { - "bgcolor": "white", - "lakecolor": "white", - "landcolor": "#E5ECF6", - "showlakes": true, - "showland": true, - "subunitcolor": "white" - }, - "hoverlabel": { - "align": "left" - }, - "hovermode": "closest", - "mapbox": { - "style": "light" - }, - "paper_bgcolor": "white", - "plot_bgcolor": "#E5ECF6", - "polar": { - "angularaxis": { - "gridcolor": "white", - "linecolor": "white", - "ticks": "" - }, - "bgcolor": "#E5ECF6", - "radialaxis": { - "gridcolor": "white", - "linecolor": "white", - "ticks": "" - } - }, - "scene": { - "xaxis": { - "backgroundcolor": "#E5ECF6", - "gridcolor": "white", - "gridwidth": 2, - "linecolor": "white", - "showbackground": true, - "ticks": "", - "zerolinecolor": "white" - }, - "yaxis": { - "backgroundcolor": "#E5ECF6", - "gridcolor": "white", - "gridwidth": 2, - "linecolor": "white", - "showbackground": true, - "ticks": "", - "zerolinecolor": "white" - }, - "zaxis": { - "backgroundcolor": "#E5ECF6", - "gridcolor": "white", - "gridwidth": 2, - "linecolor": "white", - "showbackground": true, - "ticks": "", - "zerolinecolor": "white" - } - }, - "shapedefaults": { - "line": { - "color": "#2a3f5f" - } - }, - "ternary": { - "aaxis": { - "gridcolor": "white", - "linecolor": "white", - "ticks": "" - }, - "baxis": { - "gridcolor": "white", - "linecolor": "white", - "ticks": "" - }, - "bgcolor": "#E5ECF6", - "caxis": { - "gridcolor": "white", - "linecolor": "white", - "ticks": "" - } - }, - "title": { - "x": 0.05 - }, - "xaxis": { - "automargin": true, - "gridcolor": "white", - "linecolor": "white", - "ticks": "", - "title": { - "standoff": 15 - }, - "zerolinecolor": "white", - "zerolinewidth": 2 - }, - "yaxis": { - "automargin": true, - "gridcolor": "white", - "linecolor": "white", - "ticks": "", - "title": { - "standoff": 15 - }, - "zerolinecolor": "white", - "zerolinewidth": 2 - } - } - }, - "xaxis": { - "anchor": "y", - "domain": [ - 0, - 1 - ], - "title": { - "text": "datetime" - } - }, - "yaxis": { - "anchor": "x", - "domain": [ - 0, - 1 - ], - "title": { - "text": "value" - } - } - } - }, - "text/html": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "fig = px.bar(interactions.groupby(Columns.Datetime)[Columns.User].agg('count'))\n", - "fig.show()" - ] - }, - { - "cell_type": "markdown", - "id": "43f216d0", - "metadata": {}, - "source": [ - "Из графика видны **недельные тенденции** просмотров, поэтому следует fold-ы разделять по 7 дней, но т.к. на семинаре дали \"намек\", что private dataset имеет количество дней, меньшее чем 7. Поэтому фолды будут разбиваться на **5 и 7 дней**" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "id": "07fbdb30", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "6" - ] - }, - "execution_count": 15, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "pd.to_datetime('23-05-2021', format='%d-%m-%Y').weekday()" - ] - }, - { - "cell_type": "markdown", - "id": "2ff625b2", - "metadata": {}, - "source": [ - "### train test split" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "759ba346", - "metadata": {}, - "outputs": [], - "source": [ - "def create_data_range(\n", - " last_date: pd.Timestamp, \n", - " n_folds: int = 7, \n", - " unit: str = \"W\", \n", - " n_units: int = 1, \n", - " show: bool = True,\n", - "):\n", - " periods = n_folds + 1\n", - " freq = f\"{n_units}{unit}\"\n", - " \n", - " start_date = last_date - pd.Timedelta(n_folds * n_units + n_units, unit=unit) \n", - " \n", - " date_range = pd.date_range(start=start_date, periods=periods, freq=freq, tz=last_date.tz)\n", - " \n", - " if show:\n", - " print(\n", - " f\"start_date: {start_date}\\n\"\n", - " f\"last_date: {last_date}\\n\"\n", - " f\"periods: {periods}\\n\"\n", - " f\"freq: {freq}\\n\"\n", - " f\"Test fold borders: {date_range.values.astype('datetime64[D]')}\\n\"\n", - " )\n", - " \n", - " return date_range" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "38bfd397", - "metadata": {}, - "outputs": [], - "source": [ - "CONFIG_CV = {\n", - " \"cv_v1\": {\n", - " \"n_folds\": 7,\n", - " \"unit\": \"W\",\n", - " \"n_units\": 1,\n", - " },\n", - " \"cv_v2\": {\n", - " \"n_folds\": 7,\n", - " \"unit\": \"D\",\n", - " \"n_units\": 5,\n", - " }, \n", - "}" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "f518e089", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "Timestamp('2021-08-22 00:00:00')" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "last_date = interactions[Columns.Datetime].max().normalize()\n", - "last_date" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "1fd68b9b", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "***Folds v1***\n", - "start_date: 2021-07-13 00:00:00\n", - "last_date: 2021-08-22 00:00:00\n", - "periods: 8\n", - "freq: 5D\n", - "Test fold borders: ['2021-07-13' '2021-07-18' '2021-07-23' '2021-07-28' '2021-08-02'\n", - " '2021-08-07' '2021-08-12' '2021-08-17']\n", - "\n" - ] - } - ], - "source": [ - "print(\"***Folds v1***\")\n", - "date_range_v1 = create_data_range(\n", - " last_date, \n", - " n_folds=CONFIG_CV[\"cv_v2\"][\"n_folds\"], \n", - " unit=CONFIG_CV[\"cv_v2\"][\"unit\"], \n", - " n_units=CONFIG_CV[\"cv_v2\"][\"n_units\"]\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "efc59555", - "metadata": {}, - "source": [ - "**генерируем фолды** " - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "9fae43f6", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Real number of folds: 7\n" - ] - } - ], - "source": [ - "cv_v1 = TimeRangeSplitter(\n", - " date_range=date_range_v1,\n", - " filter_already_seen=True,\n", - " filter_cold_items=True,\n", - " filter_cold_users=True,\n", - ")\n", - "print(f\"Real number of folds: {cv_v1.get_n_splits(Interactions(interactions))}\")\n", - "\n", - "CV = [cv_v1]" - ] - }, - { - "cell_type": "markdown", - "id": "e15a83a7", - "metadata": {}, - "source": [ - "**Формируем метрики**" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "8f7742c6", - "metadata": {}, - "outputs": [], - "source": [ - "metrics = {\n", - " \"prec@10\": Precision(k=10),\n", - " \"recall@10\": Recall(k=10),\n", - " \"MAP@10\": MAP(k=10),\n", - " \"novelty\": MeanInvUserFreq(k=10),\n", - " \"serendipity\": Serendipity(k=10),\n", - "}" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "id": "b21a1ecf", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'cosine_userknn_K30': ,\n", - " 'tfidf_userknn_K30': ,\n", - " 'bm25_userknn_K30': ,\n", - " 'cosine_userknn_K40': ,\n", - " 'tfidf_userknn_K40': ,\n", - " 'bm25_userknn_K40': }" - ] - }, - "execution_count": 14, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "K = [30, 40]\n", - "models = dict()\n", - "\n", - "for k in K:\n", - " models[f\"cosine_userknn_K{k}\"] = CosineRecommender(K=k)\n", - " models[f\"tfidf_userknn_K{k}\"] = TFIDFRecommender(K=k)\n", - " models[f\"bm25_userknn_K{k}\"] = BM25Recommender(K=k)\n", - "\n", - "models" - ] - }, - { - "cell_type": "markdown", - "id": "0103149a", - "metadata": {}, - "source": [ - "## Training" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "id": "e78b8221", - "metadata": {}, - "outputs": [], - "source": [ - "N_USERS = 50" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "50dcff0b", - "metadata": {}, - "outputs": [], - "source": [ - "%%time\n", - "\n", - "results = []\n", - "\n", - "for idx, cv in enumerate(CV):\n", - " print(f\"\\n CV version {idx}\")\n", - " fold_iterator = cv.split(Interactions(interactions), collect_fold_stats=True)\n", - "\n", - " for i_fold, (train_ids, test_ids, fold_info) in enumerate(fold_iterator):\n", - " print(f\"\\n==================== Fold {i_fold}\")\n", - " pprint(fold_info)\n", - "\n", - " df_train = interactions.iloc[train_ids].copy()\n", - " df_test = interactions.iloc[test_ids][Columns.UserItem].copy()\n", - "\n", - " catalog = df_train[Columns.Item].unique()\n", - "\n", - " for model_name, model in models.items():\n", - " userknn_model = UserKnn(model=model, N_users=N_USERS, use_weight_idf=True)\n", - " userknn_model.fit(df_train)\n", - "\n", - " if 'bm25' in model_name:\n", - " recos = userknn_model.predict(df_test, bmp25=True)\n", - " else:\n", - " recos = userknn_model.predict(df_test)\n", - "\n", - " metric_values = calc_metrics(\n", - " metrics,\n", - " reco=recos,\n", - " interactions=df_test,\n", - " prev_interactions=df_train,\n", - " catalog=catalog,\n", - " )\n", - "\n", - " full_model_name = f\"{model_name}_cv-{idx}\"\n", - " fold = {\"fold\": i_fold, \"model\": full_model_name}\n", - " fold.update(metric_values)\n", - " results.append(fold)" - ] - }, - { - "cell_type": "markdown", - "id": "708ec5c2", - "metadata": {}, - "source": [ - "Работало больше 10 часов, случайно при перезапуске ноутбука была вызвана ячейка и остановлена, поэтому завершилась с ошибкой, поэтому ошибку убрали для лучшего вида" - ] - }, - { - "cell_type": "code", - "execution_count": 46, - "id": "d7e2ffa7", - "metadata": { - "collapsed": true - }, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
foldmodelprec@10recall@10MAP@10noveltyserendipity
00cosine_userknn_K30_cv-00.0035570.0211280.0036958.3314910.000040
10tfidf_userknn_K30_cv-00.0064390.0391020.0073358.1550510.000048
20bm25_userknn_K30_cv-00.0025930.0134940.0025319.3984670.000081
30cosine_userknn_K40_cv-00.0032820.0193230.0034018.5615230.000043
40tfidf_userknn_K40_cv-00.0061780.0374580.0069578.3004040.000052
50bm25_userknn_K40_cv-00.0022410.0112550.0022109.6755330.000081
61cosine_userknn_K30_cv-00.0035050.0200020.0035808.3982480.000046
71tfidf_userknn_K30_cv-00.0063280.0368440.0070228.2401330.000058
81bm25_userknn_K30_cv-00.0027220.0138560.0026589.4846920.000088
91cosine_userknn_K40_cv-00.0032450.0183680.0033058.6269060.000047
101tfidf_userknn_K40_cv-00.0061500.0359640.0069168.3779880.000061
111bm25_userknn_K40_cv-00.0024060.0120670.0023939.7564580.000086
122cosine_userknn_K30_cv-00.0032610.0184980.0032958.4392630.000047
132tfidf_userknn_K30_cv-00.0059400.0342330.0064798.2623670.000059
142bm25_userknn_K30_cv-00.0027200.0134220.0025309.5356310.000091
152cosine_userknn_K40_cv-00.0030450.0170860.0031008.6615850.000050
162tfidf_userknn_K40_cv-00.0059140.0340710.0064398.3966180.000063
172bm25_userknn_K40_cv-00.0024040.0116380.0022319.7991190.000090
183cosine_userknn_K30_cv-00.0032770.0187860.0033958.4449860.000045
193tfidf_userknn_K30_cv-00.0060230.0341710.0063288.2765030.000059
203bm25_userknn_K30_cv-00.0026200.0127620.0024979.5609840.000091
213cosine_userknn_K40_cv-00.0030760.0175120.0031738.6581500.000045
223tfidf_userknn_K40_cv-00.0059190.0333680.0062538.3991690.000062
233bm25_userknn_K40_cv-00.0023370.0112730.0022539.8163250.000089
244cosine_userknn_K30_cv-00.0031180.0180640.0031578.4858990.000042
254tfidf_userknn_K30_cv-00.0059110.0336260.0063968.2824280.000059
264bm25_userknn_K30_cv-00.0025370.0123680.0024709.5996450.000086
274cosine_userknn_K40_cv-00.0028720.0165090.0028838.7119840.000043
284tfidf_userknn_K40_cv-00.0057930.0330280.0062618.4166800.000062
294bm25_userknn_K40_cv-00.0022130.0108600.0021799.8662010.000085
305cosine_userknn_K30_cv-00.0030030.0162520.0028998.4989680.000043
315tfidf_userknn_K30_cv-00.0055270.0309420.0058238.3252730.000057
325bm25_userknn_K30_cv-00.0025970.0122630.0023869.6469570.000100
335cosine_userknn_K40_cv-00.0027650.0147130.0026618.7175590.000047
345tfidf_userknn_K40_cv-00.0055450.0308920.0058178.4540910.000059
355bm25_userknn_K40_cv-00.0023020.0107770.0021359.9140420.000100
366cosine_userknn_K30_cv-00.0029630.0165320.0028878.5638090.000050
376tfidf_userknn_K30_cv-00.0053300.0307170.0057638.3662590.000064
386bm25_userknn_K30_cv-00.0025710.0126910.0024789.7150970.000100
396cosine_userknn_K40_cv-00.0027690.0154480.0026758.7750580.000051
406tfidf_userknn_K40_cv-00.0052840.0304180.0056978.4884730.000066
416bm25_userknn_K40_cv-00.0023400.0112780.0022089.9646640.000099
\n", - "
" - ], - "text/plain": [ - " fold model prec@10 recall@10 MAP@10 novelty \\\n", - "0 0 cosine_userknn_K30_cv-0 0.003557 0.021128 0.003695 8.331491 \n", - "1 0 tfidf_userknn_K30_cv-0 0.006439 0.039102 0.007335 8.155051 \n", - "2 0 bm25_userknn_K30_cv-0 0.002593 0.013494 0.002531 9.398467 \n", - "3 0 cosine_userknn_K40_cv-0 0.003282 0.019323 0.003401 8.561523 \n", - "4 0 tfidf_userknn_K40_cv-0 0.006178 0.037458 0.006957 8.300404 \n", - "5 0 bm25_userknn_K40_cv-0 0.002241 0.011255 0.002210 9.675533 \n", - "6 1 cosine_userknn_K30_cv-0 0.003505 0.020002 0.003580 8.398248 \n", - "7 1 tfidf_userknn_K30_cv-0 0.006328 0.036844 0.007022 8.240133 \n", - "8 1 bm25_userknn_K30_cv-0 0.002722 0.013856 0.002658 9.484692 \n", - "9 1 cosine_userknn_K40_cv-0 0.003245 0.018368 0.003305 8.626906 \n", - "10 1 tfidf_userknn_K40_cv-0 0.006150 0.035964 0.006916 8.377988 \n", - "11 1 bm25_userknn_K40_cv-0 0.002406 0.012067 0.002393 9.756458 \n", - "12 2 cosine_userknn_K30_cv-0 0.003261 0.018498 0.003295 8.439263 \n", - "13 2 tfidf_userknn_K30_cv-0 0.005940 0.034233 0.006479 8.262367 \n", - "14 2 bm25_userknn_K30_cv-0 0.002720 0.013422 0.002530 9.535631 \n", - "15 2 cosine_userknn_K40_cv-0 0.003045 0.017086 0.003100 8.661585 \n", - "16 2 tfidf_userknn_K40_cv-0 0.005914 0.034071 0.006439 8.396618 \n", - "17 2 bm25_userknn_K40_cv-0 0.002404 0.011638 0.002231 9.799119 \n", - "18 3 cosine_userknn_K30_cv-0 0.003277 0.018786 0.003395 8.444986 \n", - "19 3 tfidf_userknn_K30_cv-0 0.006023 0.034171 0.006328 8.276503 \n", - "20 3 bm25_userknn_K30_cv-0 0.002620 0.012762 0.002497 9.560984 \n", - "21 3 cosine_userknn_K40_cv-0 0.003076 0.017512 0.003173 8.658150 \n", - "22 3 tfidf_userknn_K40_cv-0 0.005919 0.033368 0.006253 8.399169 \n", - "23 3 bm25_userknn_K40_cv-0 0.002337 0.011273 0.002253 9.816325 \n", - "24 4 cosine_userknn_K30_cv-0 0.003118 0.018064 0.003157 8.485899 \n", - "25 4 tfidf_userknn_K30_cv-0 0.005911 0.033626 0.006396 8.282428 \n", - "26 4 bm25_userknn_K30_cv-0 0.002537 0.012368 0.002470 9.599645 \n", - "27 4 cosine_userknn_K40_cv-0 0.002872 0.016509 0.002883 8.711984 \n", - "28 4 tfidf_userknn_K40_cv-0 0.005793 0.033028 0.006261 8.416680 \n", - "29 4 bm25_userknn_K40_cv-0 0.002213 0.010860 0.002179 9.866201 \n", - "30 5 cosine_userknn_K30_cv-0 0.003003 0.016252 0.002899 8.498968 \n", - "31 5 tfidf_userknn_K30_cv-0 0.005527 0.030942 0.005823 8.325273 \n", - "32 5 bm25_userknn_K30_cv-0 0.002597 0.012263 0.002386 9.646957 \n", - "33 5 cosine_userknn_K40_cv-0 0.002765 0.014713 0.002661 8.717559 \n", - "34 5 tfidf_userknn_K40_cv-0 0.005545 0.030892 0.005817 8.454091 \n", - "35 5 bm25_userknn_K40_cv-0 0.002302 0.010777 0.002135 9.914042 \n", - "36 6 cosine_userknn_K30_cv-0 0.002963 0.016532 0.002887 8.563809 \n", - "37 6 tfidf_userknn_K30_cv-0 0.005330 0.030717 0.005763 8.366259 \n", - "38 6 bm25_userknn_K30_cv-0 0.002571 0.012691 0.002478 9.715097 \n", - "39 6 cosine_userknn_K40_cv-0 0.002769 0.015448 0.002675 8.775058 \n", - "40 6 tfidf_userknn_K40_cv-0 0.005284 0.030418 0.005697 8.488473 \n", - "41 6 bm25_userknn_K40_cv-0 0.002340 0.011278 0.002208 9.964664 \n", - "\n", - " serendipity \n", - "0 0.000040 \n", - "1 0.000048 \n", - "2 0.000081 \n", - "3 0.000043 \n", - "4 0.000052 \n", - "5 0.000081 \n", - "6 0.000046 \n", - "7 0.000058 \n", - "8 0.000088 \n", - "9 0.000047 \n", - "10 0.000061 \n", - "11 0.000086 \n", - "12 0.000047 \n", - "13 0.000059 \n", - "14 0.000091 \n", - "15 0.000050 \n", - "16 0.000063 \n", - "17 0.000090 \n", - "18 0.000045 \n", - "19 0.000059 \n", - "20 0.000091 \n", - "21 0.000045 \n", - "22 0.000062 \n", - "23 0.000089 \n", - "24 0.000042 \n", - "25 0.000059 \n", - "26 0.000086 \n", - "27 0.000043 \n", - "28 0.000062 \n", - "29 0.000085 \n", - "30 0.000043 \n", - "31 0.000057 \n", - "32 0.000100 \n", - "33 0.000047 \n", - "34 0.000059 \n", - "35 0.000100 \n", - "36 0.000050 \n", - "37 0.000064 \n", - "38 0.000100 \n", - "39 0.000051 \n", - "40 0.000066 \n", - "41 0.000099 " - ] - }, - "execution_count": 46, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df_metrics = pd.DataFrame(results)\n", - "df_metrics" - ] - }, - { - "cell_type": "code", - "execution_count": 47, - "id": "a0334b9a", - "metadata": {}, - "outputs": [], - "source": [ - "df_metrics.to_pickle(\"../data/hw_3/df_metrics.pickle\")" - ] - }, - { - "cell_type": "code", - "execution_count": 48, - "id": "446530ce", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
foldprec@10recall@10MAP@10noveltyserendipity
model
bm25_userknn_K30_cv-03.00.0026230.0129800.0025079.5630680.000091
bm25_userknn_K40_cv-03.00.0023200.0113070.0022309.8274770.000090
cosine_userknn_K30_cv-03.00.0032410.0184660.0032728.4518090.000045
cosine_userknn_K40_cv-03.00.0030080.0169940.0030288.6732520.000047
tfidf_userknn_K30_cv-03.00.0059280.0342340.0064498.2725730.000058
tfidf_userknn_K40_cv-03.00.0058260.0336000.0063348.4047750.000061
\n", - "
" - ], - "text/plain": [ - " fold prec@10 recall@10 MAP@10 novelty \\\n", - "model \n", - "bm25_userknn_K30_cv-0 3.0 0.002623 0.012980 0.002507 9.563068 \n", - "bm25_userknn_K40_cv-0 3.0 0.002320 0.011307 0.002230 9.827477 \n", - "cosine_userknn_K30_cv-0 3.0 0.003241 0.018466 0.003272 8.451809 \n", - "cosine_userknn_K40_cv-0 3.0 0.003008 0.016994 0.003028 8.673252 \n", - "tfidf_userknn_K30_cv-0 3.0 0.005928 0.034234 0.006449 8.272573 \n", - "tfidf_userknn_K40_cv-0 3.0 0.005826 0.033600 0.006334 8.404775 \n", - "\n", - " serendipity \n", - "model \n", - "bm25_userknn_K30_cv-0 0.000091 \n", - "bm25_userknn_K40_cv-0 0.000090 \n", - "cosine_userknn_K30_cv-0 0.000045 \n", - "cosine_userknn_K40_cv-0 0.000047 \n", - "tfidf_userknn_K30_cv-0 0.000058 \n", - "tfidf_userknn_K40_cv-0 0.000061 " - ] - }, - "execution_count": 48, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df_metrics.groupby('model').mean()" - ] - }, - { - "cell_type": "code", - "execution_count": 49, - "id": "5fb9ba9f", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
prec@10recall@10MAP@10noveltyserendipity
model
bm25_userknn_K30_cv-00.0000720.0006120.0000830.1044680.000007
bm25_userknn_K40_cv-00.0000740.0004420.0000810.0973590.000007
cosine_userknn_K30_cv-00.0002310.0017490.0003140.0746990.000003
cosine_userknn_K40_cv-00.0002130.0016030.0002950.0693100.000003
tfidf_userknn_K30_cv-00.0003980.0030030.0005770.0666270.000005
tfidf_userknn_K40_cv-00.0003210.0025340.0004870.0595650.000004
\n", - "
" - ], - "text/plain": [ - " prec@10 recall@10 MAP@10 novelty serendipity\n", - "model \n", - "bm25_userknn_K30_cv-0 0.000072 0.000612 0.000083 0.104468 0.000007\n", - "bm25_userknn_K40_cv-0 0.000074 0.000442 0.000081 0.097359 0.000007\n", - "cosine_userknn_K30_cv-0 0.000231 0.001749 0.000314 0.074699 0.000003\n", - "cosine_userknn_K40_cv-0 0.000213 0.001603 0.000295 0.069310 0.000003\n", - "tfidf_userknn_K30_cv-0 0.000398 0.003003 0.000577 0.066627 0.000005\n", - "tfidf_userknn_K40_cv-0 0.000321 0.002534 0.000487 0.059565 0.000004" - ] - }, - "execution_count": 49, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df_metrics.groupby('model').std()[metrics.keys()]" - ] - }, - { - "cell_type": "markdown", - "id": "41828ee5", - "metadata": {}, - "source": [ - "по **ofline** метрикам лучше всего себя показывает модель TFIDFRecommender\n", - "TFIDFRecommender подбор К" - ] - }, - { - "cell_type": "markdown", - "id": "7a8a0a41", - "metadata": {}, - "source": [ - "# Подбор оптимального K для TFIDFRecommender" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "id": "1e91892d", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'tfidf_userknn_K50': ,\n", - " 'tfidf_userknn_K60': ,\n", - " 'tfidf_userknn_K70': }" - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "N_USERS = 50\n", - "\n", - "# Т.к. метрики для К 30 и 40 уже есть\n", - "K = [k for k in range(50, 71, 10)]\n", - "models = dict()\n", - "\n", - "for k in K:\n", - " models[f\"tfidf_userknn_K{k}\"] = TFIDFRecommender(K=k)\n", - "models" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "e7c2c43b", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "==================== Fold 0\n", - "{'End date': Timestamp('2021-07-18 00:00:00', freq='5D'),\n", - " 'Start date': Timestamp('2021-07-13 00:00:00', freq='5D'),\n", - " 'Test': 156580,\n", - " 'Test items': 5793,\n", - " 'Test users': 68150,\n", - " 'Train': 3281612,\n", - " 'Train items': 14754,\n", - " 'Train users': 652905}\n" - ] - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "211234f034a54bae86b94dff33b9f5c4", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - " 0%| | 0/652905 [00:00\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
user_iditem_idlast_watch_dttotal_durwatched_pct
017654995062021-05-11425072.0
169931716592021-05-298317100.0
265668371072021-05-09100.0
386461376382021-07-0514483100.0
496486895062021-04-306725100.0
5476246648596122252021-08-13760.0
547624754686296732021-04-13230849.0
5476248697262152972021-08-201830763.0
5476249384202161972021-04-196203100.0
547625031970944362021-08-15392145.0
\n", - "" - ], - "text/plain": [ - " user_id item_id last_watch_dt total_dur watched_pct\n", - "0 176549 9506 2021-05-11 4250 72.0\n", - "1 699317 1659 2021-05-29 8317 100.0\n", - "2 656683 7107 2021-05-09 10 0.0\n", - "3 864613 7638 2021-07-05 14483 100.0\n", - "4 964868 9506 2021-04-30 6725 100.0\n", - "5476246 648596 12225 2021-08-13 76 0.0\n", - "5476247 546862 9673 2021-04-13 2308 49.0\n", - "5476248 697262 15297 2021-08-20 18307 63.0\n", - "5476249 384202 16197 2021-04-19 6203 100.0\n", - "5476250 319709 4436 2021-08-15 3921 45.0" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "pd.concat([interactions.head(), interactions.tail()])" - ] - }, - { - "cell_type": "code", - "execution_count": 33, - "id": "dc4d9fd7", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(962179,)" - ] - }, - "execution_count": 33, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "interactions['user_id'].unique().shape" - ] - }, - { - "cell_type": "code", - "execution_count": 35, - "id": "b7861d19", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[(961833, 1.0),\n", - " (961849, 1.0),\n", - " (961857, 1.0),\n", - " (961871, 1.0),\n", - " (961873, 1.0),\n", - " (961876, 1.0),\n", - " (961887, 1.0),\n", - " (961907, 1.0),\n", - " (961910, 1.0),\n", - " (961912, 1.0)]" - ] - }, - "execution_count": 35, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "import dill\n", - "\n", - "with open('../service/weights/userKNN/userknn_tfidf_k30.dill', 'rb') as f:\n", - " userknn = dill.load(f)\n", - "\n", - "userknn.similar_items(962178, 10)" - ] - }, - { - "cell_type": "markdown", - "id": "1905033a", - "metadata": {}, - "source": [ - "# Popular Model" - ] - }, - { - "cell_type": "code", - "execution_count": 42, - "id": "2df74dba", - "metadata": {}, - "outputs": [], - "source": [ - "from rectools.models import PopularModel\n", - "from rectools.dataset import Dataset" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "6ba37a73", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "Timestamp('2021-08-22 00:00:00')" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "max_date = interactions[Columns.Datetime].max().normalize()\n", - "max_date" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "901353f9", - "metadata": { - "scrolled": true - }, - "outputs": [], - "source": [ - "train = interactions[[Columns.User, Columns.Item, Columns.Weight, Columns.Datetime]][\n", - " interactions[Columns.Datetime] < max_date - pd.Timedelta(5, \"D\")]\n", - "\n", - "test = interactions[[Columns.User, Columns.Item, Columns.Weight, Columns.Datetime]][\n", - " interactions[Columns.Datetime] >= max_date - pd.Timedelta(5, \"D\")]\n", - "\n", - "dataset_train = Dataset.construct(train)" - ] - }, - { - "cell_type": "code", - "execution_count": 144, - "id": "f08e3579", - "metadata": {}, - "outputs": [], - "source": [ - "popilarity_models = {\n", - " \"popular\": PopularModel(),\n", - " \"popular_mw\": PopularModel(popularity=\"mean_weight\")\n", - "}" - ] - }, - { - "cell_type": "code", - "execution_count": 145, - "id": "03c3bfb6", - "metadata": {}, - "outputs": [], - "source": [ - "popilarity_models[\"popular\"].fit(dataset_train)\n", - "popilarity_models[\"popular_mw\"].fit(dataset_train);" - ] - }, - { - "cell_type": "code", - "execution_count": 146, - "id": "0d7de49e", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "array([ 24, 20, 31, 15, 167, 81, 89, 135, 355, 116])" - ] - }, - "execution_count": 146, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "popilarity_models[\"popular\"].popularity_list[0][:10]" - ] - }, - { - "cell_type": "code", - "execution_count": 147, - "id": "05ff208d", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "array([11363, 11681, 12841, 13017, 2069, 13691, 13552, 13397, 11774,\n", - " 12913])" - ] - }, - "execution_count": 147, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "popilarity_models[\"popular_mw\"].popularity_list[0][:10]" - ] - }, - { - "cell_type": "code", - "execution_count": 148, - "id": "00ef735c", - "metadata": {}, - "outputs": [], - "source": [ - "pecos_pop = popilarity_models[\"popular\"].recommend(\n", - " users=test[Columns.User].unique(),\n", - " dataset=dataset,\n", - " k=100,\n", - " filter_viewed=False,\n", - ")\n", - "\n", - "pecos_pop_mw = popilarity_models[\"popular_mw\"].recommend(\n", - " users=test[Columns.User].unique(),\n", - " dataset=dataset,\n", - " k=100,\n", - " filter_viewed=False,\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 152, - "id": "b302db55", - "metadata": {}, - "outputs": [], - "source": [ - "metrics = {\n", - " \"prec@5\": Precision(k=5),\n", - " \"recall@5\": Recall(k=5),\n", - " \"MAP@5\": MAP(k=5),\n", - " \"prec@10\": Precision(k=10),\n", - " \"recall@10\": Recall(k=10),\n", - " \"MAP@20\": MAP(k=20),\n", - " \"prec@20\": Precision(k=20),\n", - " \"recall@20\": Recall(k=20),\n", - " \"MAP@100\": MAP(k=100),\n", - " \"prec@100\": Precision(k=100),\n", - " \"recall@100\": Recall(k=100),\n", - " \"MAP@100\": MAP(k=100),\n", - " \"novelty\": MeanInvUserFreq(k=10),\n", - " \"serendipity\": Serendipity(k=10),\n", - "}\n", - "catalog = train[Columns.Item].unique()\n", - "metric_values_pop = calc_metrics(metrics, pecos_pop, test, train, catalog)\n", - "metric_values_pop_mean_weight = calc_metrics(metrics, pecos_pop_mw, test, train, catalog)" - ] - }, - { - "cell_type": "code", - "execution_count": 153, - "id": "9631093b", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'prec@5': 0.0017855613317256697,\n", - " 'recall@5': 0.004623809755660008,\n", - " 'prec@10': 0.0011648975773029461,\n", - " 'recall@10': 0.005682095875283048,\n", - " 'prec@20': 0.0010502526799891945,\n", - " 'recall@20': 0.00880186008464912,\n", - " 'prec@100': 0.003247020220987923,\n", - " 'recall@100': 0.16609031082955295,\n", - " 'MAP@5': 0.0013179725619140792,\n", - " 'MAP@20': 0.0016695313583723814,\n", - " 'MAP@100': 0.005578924867474493,\n", - " 'novelty': 9.976033936531364,\n", - " 'serendipity': 1.2752762676592953e-05}" - ] - }, - "execution_count": 153, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "metric_values_pop" - ] - }, - { - "cell_type": "code", - "execution_count": 154, - "id": "5d55b781", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'prec@5': 9.09252633867684e-05,\n", - " 'recall@5': 0.00014799438063171262,\n", - " 'prec@10': 4.612151041357817e-05,\n", - " 'recall@10': 0.00015458316783365238,\n", - " 'prec@20': 2.635514880775895e-05,\n", - " 'recall@20': 0.00016946607539568094,\n", - " 'prec@100': 0.00015147621777259455,\n", - " 'recall@100': 0.0065476971391510656,\n", - " 'MAP@5': 3.0257754846536496e-05,\n", - " 'MAP@20': 3.1771198360212185e-05,\n", - " 'MAP@100': 0.00011355765992119742,\n", - " 'novelty': 17.423655787689828,\n", - " 'serendipity': 1.8991632826477633e-06}" - ] - }, - "execution_count": 154, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "metric_values_pop_mean_weight" - ] - }, - { - "cell_type": "markdown", - "id": "e5a4a011", - "metadata": {}, - "source": [ - "**На офлайн метриках выигрывает обычная модель по популярному**" - ] - }, - { - "cell_type": "markdown", - "id": "5875fab7", - "metadata": {}, - "source": [ - "# Save item_idf data" - ] - }, - { - "cell_type": "markdown", - "id": "6589996f", - "metadata": {}, - "source": [ - "Создаем датасет со взвешенными item-ами по механизму idf для использования в будущем" - ] - }, - { - "cell_type": "code", - "execution_count": 40, - "id": "d62cabb9", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
indexidf
095067.150811
116598.524953
271075.821207
376388.407093
466867.778734
.........
15701783314.822785
15702912514.822785
157031006414.822785
157041301914.822785
157051054214.822785
\n", - "

15706 rows × 2 columns

\n", - "
" - ], - "text/plain": [ - " index idf\n", - "0 9506 7.150811\n", - "1 1659 8.524953\n", - "2 7107 5.821207\n", - "3 7638 8.407093\n", - "4 6686 7.778734\n", - "... ... ...\n", - "15701 7833 14.822785\n", - "15702 9125 14.822785\n", - "15703 10064 14.822785\n", - "15704 13019 14.822785\n", - "15705 10542 14.822785\n", - "\n", - "[15706 rows x 2 columns]" - ] - }, - "execution_count": 40, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "item_cnt = Counter(interactions['item_id'].values)\n", - "item_idf = pd.DataFrame.from_dict(item_cnt, orient='index', columns=['doc_freq']).reset_index()\n", - "n = interactions.shape[0]\n", - "item_idf['idf'] = item_idf['doc_freq'].apply(lambda x: np.log((1 + n) / (1 + x) + 1))\n", - "del item_idf['doc_freq']\n", - "item_idf" - ] - }, - { - "cell_type": "code", - "execution_count": 41, - "id": "7da47dfc", - "metadata": {}, - "outputs": [], - "source": [ - "item_idf = item_idf.sort_values(\"idf\", ascending=False)\n", - "item_idf.to_csv('../data/kion_train/items_idf.csv', index=False)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "fdce2b60", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "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.8.10" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/notebooks/HW-3.2-rectools-research.ipynb b/notebooks/HW-3.2-rectools-research.ipynb deleted file mode 100644 index ed456f5f..00000000 --- a/notebooks/HW-3.2-rectools-research.ipynb +++ /dev/null @@ -1,725 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "855d49cd", - "metadata": {}, - "outputs": [], - "source": [ - "import pandas as pd\n", - "import numpy as np\n", - "import scipy as sp\n", - "import requests\n", - "from tqdm.auto import tqdm\n", - "from scipy.stats import mode \n", - "from pprint import pprint\n", - "from implicit.nearest_neighbours import CosineRecommender\n", - "import warnings\n", - "warnings.filterwarnings(\"ignore\")\n", - "\n", - "from rectools import Columns\n", - "\n", - "pd.set_option('display.max_columns', None)\n", - "pd.set_option('display.max_colwidth', 200)" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "655cd033", - "metadata": {}, - "outputs": [], - "source": [ - "interactions = pd.read_csv('../data/kion_train/interactions.csv')\n", - "\n", - "interactions.rename(columns={'last_watch_dt': Columns.Datetime,\n", - " 'total_dur': Columns.Weight}, \n", - " inplace=True) \n", - "\n", - "interactions['datetime'] = pd.to_datetime(interactions['datetime'])" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "193c411d", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Start date and last date of the test fold: (Timestamp('2021-08-08 00:00:00'), Timestamp('2021-08-22 00:00:00'))\n", - "Test fold borders: ['2021-08-08' '2021-08-15']\n", - "Real number of folds: 1\n" - ] - } - ], - "source": [ - "from rectools.model_selection import TimeRangeSplitter\n", - "from rectools.dataset import Interactions\n", - "\n", - "n_folds = 1\n", - "unit = \"W\"\n", - "n_units = 1\n", - "periods = n_folds + 1\n", - "freq = f\"{n_units}{unit}\"\n", - "\n", - "last_date = interactions[Columns.Datetime].max().normalize()\n", - "start_date = last_date - pd.Timedelta(n_folds * n_units + 1, unit=unit) \n", - "print(f\"Start date and last date of the test fold: {start_date, last_date}\")\n", - " \n", - "date_range = pd.date_range(start=start_date, periods=periods, freq=freq, tz=last_date.tz)\n", - "print(f\"Test fold borders: {date_range.values.astype('datetime64[D]')}\")\n", - "\n", - "# generator of folds\n", - "cv = TimeRangeSplitter(\n", - " date_range=date_range,\n", - " filter_already_seen=True,\n", - " filter_cold_items=True,\n", - " filter_cold_users=True,\n", - ")\n", - "print(f\"Real number of folds: {cv.get_n_splits(Interactions(interactions))}\")" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "38b80f9f", - "metadata": {}, - "outputs": [], - "source": [ - "(train_ids, test_ids, fold_info) = cv.split(Interactions(interactions), collect_fold_stats=True).__next__()" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "e3051991", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "array([ 0, 1, 2, ..., 5476245, 5476247, 5476249])" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "train_ids" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "7bc27a2f", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "array([ 6, 33, 56, ..., 5476229, 5476230, 5476240])" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "test_ids" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "ffdaad0c", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "users_mapping amount: 842129\n", - "items_mapping amount: 15404\n" - ] - } - ], - "source": [ - "train = interactions.loc[train_ids]\n", - "test = interactions.loc[test_ids]\n", - "\n", - "users_inv_mapping = dict(enumerate(train['user_id'].unique()))\n", - "users_mapping = {v: k for k, v in users_inv_mapping.items()}\n", - "\n", - "items_inv_mapping = dict(enumerate(train['item_id'].unique()))\n", - "items_mapping = {v: k for k, v in items_inv_mapping.items()}\n", - "\n", - "print(f\"users_mapping amount: {len(users_mapping)}\")\n", - "print(f\"items_mapping amount: {len(items_mapping)}\")" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "a6664026", - "metadata": {}, - "outputs": [], - "source": [ - "from rectools.dataset import Dataset\n", - "\n", - "dataset = Dataset.construct(\n", - " interactions_df=train,\n", - " user_features_df=None,\n", - " item_features_df=None\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "88f5a65c", - "metadata": {}, - "source": [ - "# ItemKNN CosineRecommender" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "id": "9c4682c5", - "metadata": {}, - "outputs": [], - "source": [ - "from implicit.nearest_neighbours import CosineRecommender\n", - "from rectools.models.implicit_knn import ImplicitItemKNNWrapperModel\n", - "\n", - "item_knn = ImplicitItemKNNWrapperModel(model=CosineRecommender(K=30))\n", - "item_knn.fit(dataset);" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "id": "198faaa4", - "metadata": {}, - "outputs": [], - "source": [ - "recs_itemknn = item_knn.recommend(\n", - " test['user_id'].unique(), \n", - " dataset=dataset, \n", - " k=10, \n", - " filter_viewed=False\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "id": "76d1a3f5", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
user_iditem_idscorerank
010164581044020431.6311501
110164587348043.9999622
21016458121928033.5995303
3101645819867999.8057314
4101645844577763.2046075
\n", - "
" - ], - "text/plain": [ - " user_id item_id score rank\n", - "0 1016458 10440 20431.631150 1\n", - "1 1016458 734 8043.999962 2\n", - "2 1016458 12192 8033.599530 3\n", - "3 1016458 1986 7999.805731 4\n", - "4 1016458 4457 7763.204607 5" - ] - }, - "execution_count": 15, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "recs_itemknn.head()" - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "id": "c075a976", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'prec@10': 0.017311708814214132,\n", - " 'recall@10': 0.09520897568691472,\n", - " 'MAP@10': 0.023145528903990274,\n", - " 'novelty': 8.05318572965277,\n", - " 'serendipity': 6.63288816067437e-05}" - ] - }, - "execution_count": 19, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "from rectools.metrics import Precision, Recall, MeanInvUserFreq, MAP, Serendipity, calc_metrics\n", - "\n", - "# calculate several classic (precision@k and recall@k) and \"beyond accuracy\" metrics\n", - "metrics = {\n", - " \"prec@10\": Precision(k=10),\n", - " \"recall@10\": Recall(k=10),\n", - " \"MAP@10\": MAP(k=10),\n", - " \"novelty\": MeanInvUserFreq(k=10),\n", - " \"serendipity\": Serendipity(k=10),\n", - "}\n", - "\n", - "catalog = train['item_id'].unique()\n", - "\n", - "metric_values_itemknn_cosine = calc_metrics(\n", - " metrics,\n", - " reco=recs_itemknn,\n", - " interactions=test,\n", - " prev_interactions=train,\n", - " catalog=catalog\n", - " )\n", - "\n", - "metric_values_itemknn_cosine" - ] - }, - { - "cell_type": "markdown", - "id": "b439f7fb", - "metadata": {}, - "source": [ - "# ItemKNN TFIDFRecommender" - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "id": "e31f5560", - "metadata": {}, - "outputs": [], - "source": [ - "from implicit.nearest_neighbours import TFIDFRecommender\n", - "from rectools.models.implicit_knn import ImplicitItemKNNWrapperModel\n", - "\n", - "item_knn_tfidf = ImplicitItemKNNWrapperModel(model=TFIDFRecommender(K=30))\n", - "item_knn_tfidf.fit(dataset);" - ] - }, - { - "cell_type": "code", - "execution_count": 24, - "id": "360eafab", - "metadata": {}, - "outputs": [], - "source": [ - "recs_itemknn_tfidf = item_knn_tfidf.recommend(\n", - " test['user_id'].unique(), \n", - " dataset=dataset, \n", - " k=10, \n", - " filter_viewed=False \n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 26, - "id": "63c31f04", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
user_iditem_idscorerank
010164581044021745.3769271
11016458445710234.8633082
2101645871028987.8781293
31016458121928957.1098134
4101645819868369.8324485
\n", - "
" - ], - "text/plain": [ - " user_id item_id score rank\n", - "0 1016458 10440 21745.376927 1\n", - "1 1016458 4457 10234.863308 2\n", - "2 1016458 7102 8987.878129 3\n", - "3 1016458 12192 8957.109813 4\n", - "4 1016458 1986 8369.832448 5" - ] - }, - "execution_count": 26, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "recs_itemknn_tfidf.head()" - ] - }, - { - "cell_type": "code", - "execution_count": 33, - "id": "7a4d01f7", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'prec@10': 0.023772589549238603,\n", - " 'recall@10': 0.12652382351172245,\n", - " 'MAP@10': 0.03005237337960426,\n", - " 'novelty': 6.699663403861505,\n", - " 'serendipity': 0.00010222896681730396}" - ] - }, - "execution_count": 33, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "from rectools.metrics import Precision, Recall, MeanInvUserFreq, MAP, Serendipity, calc_metrics\n", - "\n", - "metrics = {\n", - " \"prec@10\": Precision(k=10),\n", - " \"recall@10\": Recall(k=10),\n", - " \"MAP@10\": MAP(k=10),\n", - " \"novelty\": MeanInvUserFreq(k=10),\n", - " \"serendipity\": Serendipity(k=10),\n", - "}\n", - "\n", - "catalog = train['item_id'].unique()\n", - "\n", - "metric_values_itemknn_tfidf = calc_metrics(\n", - " metrics,\n", - " reco=recs_itemknn_tfidf,\n", - " interactions=test,\n", - " prev_interactions=train,\n", - " catalog=catalog\n", - " )\n", - "\n", - "metric_values_itemknn_tfidf" - ] - }, - { - "cell_type": "markdown", - "id": "2270cb27", - "metadata": {}, - "source": [ - "# UserKNN BMP25" - ] - }, - { - "cell_type": "code", - "execution_count": 28, - "id": "c7997faf", - "metadata": {}, - "outputs": [], - "source": [ - "from implicit.nearest_neighbours import BM25Recommender\n", - "from rectools.models.implicit_knn import ImplicitItemKNNWrapperModel\n", - "\n", - "item_knn_bmp = ImplicitItemKNNWrapperModel(model=BM25Recommender(K=30))\n", - "item_knn_bmp.fit(dataset);" - ] - }, - { - "cell_type": "code", - "execution_count": 29, - "id": "c7ceb0e5", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
user_iditem_idscorerank
01016458104406.854547e+111
11016458152972.323138e+112
21016458138651.724740e+113
3101645897281.383208e+114
4101645841511.149358e+115
\n", - "
" - ], - "text/plain": [ - " user_id item_id score rank\n", - "0 1016458 10440 6.854547e+11 1\n", - "1 1016458 15297 2.323138e+11 2\n", - "2 1016458 13865 1.724740e+11 3\n", - "3 1016458 9728 1.383208e+11 4\n", - "4 1016458 4151 1.149358e+11 5" - ] - }, - "execution_count": 29, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "recs_itemknn_bmp = item_knn_bmp.recommend(\n", - " test['user_id'].unique(), \n", - " dataset=dataset, \n", - " k=10, \n", - " filter_viewed=False \n", - ")\n", - "\n", - "recs_itemknn_bmp.head()" - ] - }, - { - "cell_type": "code", - "execution_count": 31, - "id": "e99f3649", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'prec@10': 0.03252208701450242,\n", - " 'recall@10': 0.1683399650610623,\n", - " 'MAP@10': 0.04827657497255996,\n", - " 'novelty': 3.9201705312554833,\n", - " 'serendipity': 2.616232292298612e-05}" - ] - }, - "execution_count": 31, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "from rectools.metrics import Precision, Recall, MeanInvUserFreq, MAP, Serendipity, calc_metrics\n", - "\n", - "metrics = {\n", - " \"prec@10\": Precision(k=10),\n", - " \"recall@10\": Recall(k=10),\n", - " \"MAP@10\": MAP(k=10),\n", - " \"novelty\": MeanInvUserFreq(k=10),\n", - " \"serendipity\": Serendipity(k=10),\n", - "}\n", - "\n", - "catalog = train['item_id'].unique()\n", - "\n", - "metric_values_itemknn_bmp = calc_metrics(\n", - " metrics,\n", - " reco=recs_itemknn_bmp,\n", - " interactions=test,\n", - " prev_interactions=train,\n", - " catalog=catalog\n", - " )\n", - "\n", - "metric_values_itemknn_bmp" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "84fe056a", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "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.8.10" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/notebooks/HW-3.3-rectools-cv.ipynb b/notebooks/HW-3.3-rectools-cv.ipynb deleted file mode 100644 index e5f56e68..00000000 --- a/notebooks/HW-3.3-rectools-cv.ipynb +++ /dev/null @@ -1,4387 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 7, - "id": "f0145080", - "metadata": {}, - "outputs": [], - "source": [ - "import pandas as pd\n", - "import numpy as np\n", - "import scipy as sp\n", - "import requests\n", - "from tqdm.auto import tqdm\n", - "from scipy.stats import mode \n", - "from pprint import pprint\n", - "from implicit.nearest_neighbours import CosineRecommender, TFIDFRecommender, BM25Recommender\n", - "import warnings\n", - "warnings.filterwarnings(\"ignore\")\n", - "\n", - "from rectools import Columns\n", - "from rectools.model_selection import TimeRangeSplitter\n", - "from rectools.dataset import Dataset, Interactions\n", - "from rectools.models.popular import PopularModel\n", - "from rectools.models.implicit_knn import ImplicitItemKNNWrapperModel\n", - "from rectools.metrics import Precision, Recall, MeanInvUserFreq, MAP, Serendipity, calc_metrics\n", - "\n", - "pd.set_option('display.max_columns', None)\n", - "pd.set_option('display.max_colwidth', 200)" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "95ab759c", - "metadata": {}, - "outputs": [], - "source": [ - "interactions = pd.read_csv('../data/kion_train/interactions.csv')\n", - "\n", - "interactions.rename(columns={\n", - " 'last_watch_dt': Columns.Datetime,\n", - " 'total_dur': Columns.Weight\n", - " }, \n", - " inplace=True\n", - ") \n", - "\n", - "interactions['datetime'] = pd.to_datetime(interactions['datetime'])" - ] - }, - { - "cell_type": "markdown", - "id": "fbd3f42d", - "metadata": {}, - "source": [ - "# Split" - ] - }, - { - "cell_type": "markdown", - "id": "c89fcc74", - "metadata": {}, - "source": [ - "В соответствии с предположением из ноутбука \"HW-3.1\" сделаем **валидацию по 5 дней и по 7 дней**" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "368c7cf6", - "metadata": {}, - "outputs": [], - "source": [ - "def create_data_range(\n", - " last_date: pd.Timestamp, \n", - " n_folds: int = 7, \n", - " unit: str = \"W\", \n", - " n_units: int = 1, \n", - " show: bool = True,\n", - "):\n", - " periods = n_folds + 1\n", - " freq = f\"{n_units}{unit}\"\n", - " \n", - " start_date = last_date - pd.Timedelta(n_folds * n_units + n_units, unit=unit) \n", - " \n", - " date_range = pd.date_range(start=start_date, periods=periods, freq=freq, tz=last_date.tz)\n", - " \n", - " if show:\n", - " print(\n", - " f\"start_date: {start_date}\\n\"\n", - " f\"last_date: {last_date}\\n\"\n", - " f\"periods: {periods}\\n\"\n", - " f\"freq: {freq}\\n\"\n", - " f\"Test fold borders: {date_range.values.astype('datetime64[D]')}\\n\"\n", - " )\n", - " \n", - " return date_range" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "29af1fa3", - "metadata": {}, - "outputs": [], - "source": [ - "CONFIG_CV = {\n", - " \"cv_v1\": {\n", - " \"n_folds\": 5,\n", - " \"unit\": \"W\",\n", - " \"n_units\": 1,\n", - " },\n", - " \"cv_v2\": {\n", - " \"n_folds\": 5,\n", - " \"unit\": \"D\",\n", - " \"n_units\": 5,\n", - " }, \n", - "}" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "3fdeb5a3", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "Timestamp('2021-08-22 00:00:00')" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "last_date = interactions[Columns.Datetime].max().normalize()\n", - "last_date" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "9ee0372b", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "***Folds v1***\n", - "start_date: 2021-07-11 00:00:00\n", - "last_date: 2021-08-22 00:00:00\n", - "periods: 6\n", - "freq: 1W\n", - "Test fold borders: ['2021-07-11' '2021-07-18' '2021-07-25' '2021-08-01' '2021-08-08'\n", - " '2021-08-15']\n", - "\n", - "***Folds v2***\n", - "start_date: 2021-07-23 00:00:00\n", - "last_date: 2021-08-22 00:00:00\n", - "periods: 6\n", - "freq: 5D\n", - "Test fold borders: ['2021-07-23' '2021-07-28' '2021-08-02' '2021-08-07' '2021-08-12'\n", - " '2021-08-17']\n", - "\n" - ] - } - ], - "source": [ - "print(\"***Folds v1***\")\n", - "date_range_v1 = create_data_range(\n", - " last_date, \n", - " n_folds=CONFIG_CV[\"cv_v1\"][\"n_folds\"], \n", - " unit=CONFIG_CV[\"cv_v1\"][\"unit\"], \n", - " n_units=CONFIG_CV[\"cv_v1\"][\"n_units\"]\n", - ")\n", - "\n", - "print(\"***Folds v2***\")\n", - "date_range_v2 = create_data_range(\n", - " last_date, \n", - " n_folds=CONFIG_CV[\"cv_v2\"][\"n_folds\"], \n", - " unit=CONFIG_CV[\"cv_v2\"][\"unit\"], \n", - " n_units=CONFIG_CV[\"cv_v2\"][\"n_units\"]\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "63d80785", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Real number of folds: 5\n", - "Real number of folds: 5\n" - ] - } - ], - "source": [ - "cv_v1 = TimeRangeSplitter(\n", - " date_range=date_range_v1,\n", - " filter_already_seen=True,\n", - " filter_cold_items=True,\n", - " filter_cold_users=True,\n", - ")\n", - "print(f\"Real number of folds: {cv_v1.get_n_splits(Interactions(interactions))}\")\n", - "\n", - "cv_v2 = TimeRangeSplitter(\n", - " date_range=date_range_v2,\n", - " filter_already_seen=True,\n", - " filter_cold_items=True,\n", - " filter_cold_users=True,\n", - ")\n", - "print(f\"Real number of folds: {cv_v2.get_n_splits(Interactions(interactions))}\")\n", - "\n", - "CV = [cv_v1, cv_v2]" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "1d4bc5e3", - "metadata": {}, - "outputs": [], - "source": [ - "metrics = {\n", - " \"prec@5\": Precision(k=5),\n", - " \"recall@5\": Recall(k=5),\n", - " \"MAP@5\": MAP(k=5),\n", - " \"prec@10\": Precision(k=10),\n", - " \"recall@10\": Recall(k=10),\n", - " \"MAP@10\": MAP(k=10),\n", - " \"novelty\": MeanInvUserFreq(k=10),\n", - " \"serendipity\": Serendipity(k=10),\n", - "}" - ] - }, - { - "cell_type": "markdown", - "id": "f480a12f", - "metadata": {}, - "source": [ - "# Find best models" - ] - }, - { - "cell_type": "code", - "execution_count": 31, - "id": "48888d0d", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'popular': ,\n", - " 'popular_mw': ,\n", - " 'cosine_userknn_K30': ,\n", - " 'tfidf_userknn_K30': ,\n", - " 'bm25_userknn_K30': ,\n", - " 'cosine_userknn_K40': ,\n", - " 'tfidf_userknn_K40': ,\n", - " 'bm25_userknn_K40': ,\n", - " 'cosine_userknn_K50': ,\n", - " 'tfidf_userknn_K50': ,\n", - " 'bm25_userknn_K50': ,\n", - " 'cosine_userknn_K60': ,\n", - " 'tfidf_userknn_K60': ,\n", - " 'bm25_userknn_K60': }" - ] - }, - "execution_count": 31, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "K = [30, 40, 50, 60]\n", - "models = {\n", - " \"popular\": PopularModel(),\n", - " \"popular_mw\": PopularModel(popularity=\"mean_weight\")\n", - "}\n", - "\n", - "for k in K:\n", - " models[f\"popular\"]\n", - " models[f\"cosine_userknn_K{k}\"] = ImplicitItemKNNWrapperModel(model=CosineRecommender(K=k))\n", - " models[f\"tfidf_userknn_K{k}\"] = ImplicitItemKNNWrapperModel(model=TFIDFRecommender(K=k))\n", - " models[f\"bm25_userknn_K{k}\"] = ImplicitItemKNNWrapperModel(model=BM25Recommender(K=k))\n", - "\n", - "models" - ] - }, - { - "cell_type": "code", - "execution_count": 32, - "id": "240478ad", - "metadata": { - "scrolled": true - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - " ***CV_0***\n", - "\n", - "==================== Fold 0\n", - "{'End date': Timestamp('2021-07-18 00:00:00', freq='W-SUN'),\n", - " 'Start date': Timestamp('2021-07-11 00:00:00', freq='W-SUN'),\n", - " 'Test': 214489,\n", - " 'Test items': 6313,\n", - " 'Test users': 84234,\n", - " 'Train': 3192875,\n", - " 'Train items': 14711,\n", - " 'Train users': 640144}\n" - ] - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "54fd89ff19334e3182f264d9c492bc0f", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - " 0%| | 0/14 [00:00\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
foldmodelprec@5recall@5prec@10recall@10MAP@5MAP@10noveltyserendipitycv
00popular_view-False0.0579270.1656440.0407500.2256730.0815120.0912683.5276320.000000fold_1w
10popular_view-True0.0670680.1877850.0431740.2368550.1064160.1146743.7705130.000003fold_1w
20popular_mw_view-False0.0000000.0000000.0000000.0000000.0000000.00000018.2251010.000000fold_1w
30popular_mw_view-True0.0000000.0000000.0000000.0000000.0000000.00000018.2251130.000000fold_1w
40cosine_userknn_K30_view-False0.0233090.0739180.0221430.1297750.0243640.0326517.9141100.000048fold_1w
....................................
2754cosine_userknn_K60_view-True0.0308600.0877970.0234320.1295780.0527720.0590919.1529680.000122fold_5d
2764tfidf_userknn_K60_view-False0.0197570.0608710.0214000.1222600.0196980.0285576.6513340.000095fold_5d
2774tfidf_userknn_K60_view-True0.0428030.1162650.0321730.1704580.0694600.0779126.7271280.000180fold_5d
2784bm25_userknn_K60_view-False0.0370060.1074420.0289580.1623460.0378950.0461993.9205840.000024fold_5d
2794bm25_userknn_K60_view-True0.0495680.1399710.0346460.1918270.0841810.0920224.0025740.000038fold_5d
\n", - "

280 rows × 11 columns

\n", - "" - ], - "text/plain": [ - " fold model prec@5 recall@5 prec@10 \\\n", - "0 0 popular_view-False 0.057927 0.165644 0.040750 \n", - "1 0 popular_view-True 0.067068 0.187785 0.043174 \n", - "2 0 popular_mw_view-False 0.000000 0.000000 0.000000 \n", - "3 0 popular_mw_view-True 0.000000 0.000000 0.000000 \n", - "4 0 cosine_userknn_K30_view-False 0.023309 0.073918 0.022143 \n", - ".. ... ... ... ... ... \n", - "275 4 cosine_userknn_K60_view-True 0.030860 0.087797 0.023432 \n", - "276 4 tfidf_userknn_K60_view-False 0.019757 0.060871 0.021400 \n", - "277 4 tfidf_userknn_K60_view-True 0.042803 0.116265 0.032173 \n", - "278 4 bm25_userknn_K60_view-False 0.037006 0.107442 0.028958 \n", - "279 4 bm25_userknn_K60_view-True 0.049568 0.139971 0.034646 \n", - "\n", - " recall@10 MAP@5 MAP@10 novelty serendipity cv \n", - "0 0.225673 0.081512 0.091268 3.527632 0.000000 fold_1w \n", - "1 0.236855 0.106416 0.114674 3.770513 0.000003 fold_1w \n", - "2 0.000000 0.000000 0.000000 18.225101 0.000000 fold_1w \n", - "3 0.000000 0.000000 0.000000 18.225113 0.000000 fold_1w \n", - "4 0.129775 0.024364 0.032651 7.914110 0.000048 fold_1w \n", - ".. ... ... ... ... ... ... \n", - "275 0.129578 0.052772 0.059091 9.152968 0.000122 fold_5d \n", - "276 0.122260 0.019698 0.028557 6.651334 0.000095 fold_5d \n", - "277 0.170458 0.069460 0.077912 6.727128 0.000180 fold_5d \n", - "278 0.162346 0.037895 0.046199 3.920584 0.000024 fold_5d \n", - "279 0.191827 0.084181 0.092022 4.002574 0.000038 fold_5d \n", - "\n", - "[280 rows x 11 columns]" - ] - }, - "execution_count": 42, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df_metrics = pd.DataFrame(results)\n", - "\n", - "df_metrics['cv'] = 'fold_1w'\n", - "df_metrics.loc[df_metrics[240:].index, 'cv'] = 'fold_5d'\n", - "\n", - "df_metrics" - ] - }, - { - "cell_type": "code", - "execution_count": 47, - "id": "2c075a81", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
  prec@5recall@5prec@10recall@10MAP@5MAP@10noveltyserendipity
cvmodel        
fold_1wbm25_userknn_K30_view-False0.0421850.1198230.0341540.1853470.0425250.0526723.9462480.000025
bm25_userknn_K30_view-True0.0578090.1584620.0399610.2134740.0952320.1039424.0229380.000040
bm25_userknn_K40_view-False0.0421440.1197360.0341430.1853240.0424800.0526323.9468740.000024
bm25_userknn_K40_view-True0.0578090.1584590.0399780.2136720.0952250.1039584.0190520.000040
bm25_userknn_K50_view-False0.0427580.1212820.0346770.1878470.0430140.0533383.9522220.000024
bm25_userknn_K50_view-True0.0587270.1606350.0404860.2159230.0965760.1053574.0196010.000040
bm25_userknn_K60_view-False0.0427370.1212300.0346660.1877960.0429910.0533143.9540010.000024
bm25_userknn_K60_view-True0.0587330.1606450.0404890.2159890.0965820.1053694.0197450.000040
cosine_userknn_K30_view-False0.0182530.0566890.0183590.1056300.0186880.0258658.0175230.000059
cosine_userknn_K30_view-True0.0355550.0989330.0263640.1430560.0604240.0672809.2559170.000110
cosine_userknn_K40_view-False0.0182420.0566650.0184200.1058860.0186730.0258897.9988740.000059
cosine_userknn_K40_view-True0.0357950.0994330.0266170.1440550.0606590.0676009.1942320.000112
cosine_userknn_K50_view-False0.0185740.0575860.0187360.1074510.0189640.0262837.9764920.000059
cosine_userknn_K50_view-True0.0365740.1013450.0270880.1460780.0617260.0687059.1359440.000112
cosine_userknn_K60_view-False0.0185870.0576330.0187920.1077450.0189680.0263177.9643440.000059
cosine_userknn_K60_view-True0.0367750.1017880.0272630.1468410.0619650.0689959.0997830.000113
popular_mw_view-False0.0000010.0000040.0000010.0000050.0000010.00000118.4532010.000000
popular_mw_view-True0.0000010.0000040.0000010.0000050.0000010.00000118.4532120.000000
popular_view-False0.0478130.1347100.0336350.1829810.0675170.0751913.4627720.000000
popular_view-True0.0548010.1519420.0360520.1947680.0853480.0923823.7265770.000002
tfidf_userknn_K30_view-False0.0230980.0696630.0243060.1356420.0228240.0325196.7433550.000089
tfidf_userknn_K30_view-True0.0472470.1262870.0351810.1830160.0768820.0859166.9707800.000163
tfidf_userknn_K40_view-False0.0230880.0696410.0243660.1360420.0228090.0325576.7253990.000089
tfidf_userknn_K40_view-True0.0475380.1269040.0354420.1841430.0772930.0864016.9208620.000164
tfidf_userknn_K50_view-False0.0233680.0703830.0246770.1375270.0230520.0329166.7182830.000088
tfidf_userknn_K50_view-True0.0482450.1284920.0358830.1859640.0782480.0874186.8985940.000163
tfidf_userknn_K60_view-False0.0233350.0702720.0247020.1376080.0230200.0329066.7094850.000088
tfidf_userknn_K60_view-True0.0483400.1286640.0360000.1863860.0782780.0874876.8729720.000164
fold_5dbm25_userknn_K30_view-False0.0370910.1076370.0289830.1624440.0380040.0462953.9161130.000024
bm25_userknn_K30_view-True0.0495240.1398800.0346900.1919240.0841410.0920164.0087720.000040
bm25_userknn_K40_view-False0.0370410.1075290.0289650.1623580.0379570.0462533.9168820.000024
bm25_userknn_K40_view-True0.0495350.1398850.0346590.1918370.0841560.0920154.0040600.000039
bm25_userknn_K50_view-False0.0369970.1071580.0293990.1636720.0379280.0464873.9190890.000024
bm25_userknn_K50_view-True0.0500520.1405600.0352420.1936600.0843320.0924104.0038040.000039
bm25_userknn_K60_view-False0.0369840.1071180.0293940.1636480.0379040.0464643.9209690.000024
bm25_userknn_K60_view-True0.0500670.1405850.0352470.1937300.0843450.0924244.0038000.000039
cosine_userknn_K30_view-False0.0150530.0478120.0152010.0903210.0156960.0217948.0592570.000062
cosine_userknn_K30_view-True0.0301280.0862880.0227960.1271860.0520750.0582429.3133310.000118
cosine_userknn_K40_view-False0.0150880.0478410.0152530.0905090.0156930.0218108.0395780.000062
cosine_userknn_K40_view-True0.0304070.0868690.0230640.1281230.0523320.0585509.2453400.000120
cosine_userknn_K50_view-False0.0153600.0485870.0158260.0933330.0159890.0224308.0218540.000062
cosine_userknn_K50_view-True0.0312470.0885020.0239710.1324190.0535310.0601719.1785550.000119
cosine_userknn_K60_view-False0.0153780.0486400.0158630.0936170.0159980.0224648.0092990.000062
cosine_userknn_K60_view-True0.0314330.0888970.0241430.1332180.0537360.0604409.1400930.000120
popular_mw_view-False0.0000120.0000470.0000060.0000470.0000160.00001618.5009540.000001
popular_mw_view-True0.0000120.0000470.0000060.0000470.0000160.00001618.5009640.000001
popular_view-False0.0412890.1193840.0274250.1536150.0614460.0669593.4328550.000000
popular_view-True0.0471830.1346070.0301480.1685700.0769260.0821673.7145430.000002
tfidf_userknn_K30_view-False0.0198470.0612400.0213000.1217630.0198270.0285606.6921510.000096
tfidf_userknn_K30_view-True0.0423270.1152380.0315110.1680460.0689490.0771826.8414240.000178
tfidf_userknn_K40_view-False0.0197870.0609900.0213720.1222950.0197550.0285856.6717820.000096
tfidf_userknn_K40_view-True0.0425410.1157180.0318530.1694190.0691930.0775726.7890490.000178
tfidf_userknn_K50_view-False0.0201520.0619330.0218290.1244330.0201690.0292106.6699910.000095
tfidf_userknn_K50_view-True0.0431760.1170330.0325920.1725790.0705340.0792026.7754160.000176
tfidf_userknn_K60_view-False0.0201230.0618390.0218090.1242790.0201360.0291716.6608280.000094
tfidf_userknn_K60_view-True0.0432480.1171350.0326970.1727720.0705900.0792716.7478700.000176
\n" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 47, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df_metrics_mean = df_metrics.groupby(['cv', 'model'])[\n", - " 'prec@5', 'recall@5', 'prec@10', 'recall@10', 'MAP@5', 'MAP@10', 'novelty', 'serendipity'\n", - "].mean()\n", - "\n", - "df_metrics_mean.style.highlight_max(color='lightgreen', axis=0)" - ] - }, - { - "cell_type": "markdown", - "id": "c6f89d3a", - "metadata": {}, - "source": [ - "Из результатов видно, что среднее значение метрик моделей **bmp** имеют **наилучшие** значения, причем на недельном фолде метрики выше, чем на 5 дневном \n", - "\n", - "- Следует проверить статистически различимы значения или нет. Для этого следует посмотреть дисперсию и если дисперсия меньше чем различия между средними значениями метрик, то можно сделать вывод, что значения метрик статистически различны" - ] - }, - { - "cell_type": "code", - "execution_count": 51, - "id": "dbe6f6e9", - "metadata": { - "collapsed": true - }, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
prec@5recall@5prec@10recall@10MAP@5MAP@10noveltyserendipity
cvmodel
fold_1wbm25_userknn_K30_view-False0.0040420.0113800.0033190.0180500.0041440.0052350.0297301.677056e-06
bm25_userknn_K30_view-True0.0053480.0145870.0027870.0136200.0098230.0098710.0150063.357219e-06
bm25_userknn_K40_view-False0.0040360.0113930.0033100.0180330.0041490.0052340.0296131.666772e-06
bm25_userknn_K40_view-True0.0053340.0145920.0027740.0135110.0098240.0098590.0147093.359684e-06
bm25_userknn_K50_view-False0.0037770.0110130.0030870.0174760.0040390.0050710.0293291.807696e-06
bm25_userknn_K50_view-True0.0048940.0139550.0024670.0124850.0095620.0095320.0148613.329699e-06
bm25_userknn_K60_view-False0.0037740.0110020.0030880.0174920.0040380.0050740.0293681.805538e-06
bm25_userknn_K60_view-True0.0048940.0139630.0024660.0124780.0095620.0095290.0147383.340070e-06
cosine_userknn_K30_view-False0.0023930.0078030.0019180.0113260.0025180.0030910.0473375.304930e-06
cosine_userknn_K30_view-True0.0036240.0104000.0017860.0088100.0070000.0068420.0470279.087191e-06
cosine_userknn_K40_view-False0.0023890.0077940.0019080.0112950.0025170.0030810.0464455.302222e-06
cosine_userknn_K40_view-True0.0035680.0102570.0017520.0085470.0069510.0067840.0468249.276498e-06
cosine_userknn_K50_view-False0.0023160.0077730.0018440.0112840.0025120.0030720.0463455.581875e-06
cosine_userknn_K50_view-True0.0033820.0100320.0016460.0082760.0069180.0067300.0467999.814832e-06
cosine_userknn_K60_view-False0.0023140.0077920.0018590.0113540.0025140.0030830.0464545.564036e-06
cosine_userknn_K60_view-True0.0034040.0100880.0016380.0082460.0069610.0067690.0471339.793017e-06
popular_mw_view-False0.0000020.0000060.0000010.0000050.0000020.0000020.1251581.003157e-07
popular_mw_view-True0.0000020.0000060.0000010.0000050.0000020.0000020.1251551.003157e-07
popular_view-False0.0052120.0145720.0038290.0217390.0061000.0070750.0301750.000000e+00
popular_view-True0.0061040.0166560.0037930.0208750.0089550.0096530.0191312.429424e-07
tfidf_userknn_K30_view-False0.0022440.0069950.0019450.0106640.0023810.0029650.0398778.695037e-06
tfidf_userknn_K30_view-True0.0033320.0090010.0019680.0086110.0063740.0063900.0696381.279797e-05
tfidf_userknn_K40_view-False0.0022470.0070180.0019330.0105820.0023840.0029490.0408518.509896e-06
tfidf_userknn_K40_view-True0.0033190.0089040.0019240.0082880.0063000.0062880.0708191.317232e-05
tfidf_userknn_K50_view-False0.0022010.0070680.0018850.0105910.0024010.0029550.0404928.580259e-06
tfidf_userknn_K50_view-True0.0031950.0088190.0018010.0077960.0063570.0062790.0654901.351216e-05
tfidf_userknn_K60_view-False0.0022040.0070880.0018860.0106590.0024120.0029720.0408278.400699e-06
tfidf_userknn_K60_view-True0.0031990.0088290.0018090.0078410.0064200.0063480.0660501.359432e-05
fold_5dbm25_userknn_K30_view-FalseNaNNaNNaNNaNNaNNaNNaNNaN
bm25_userknn_K30_view-TrueNaNNaNNaNNaNNaNNaNNaNNaN
bm25_userknn_K40_view-FalseNaNNaNNaNNaNNaNNaNNaNNaN
bm25_userknn_K40_view-TrueNaNNaNNaNNaNNaNNaNNaNNaN
bm25_userknn_K50_view-False0.0000390.0004560.0006120.0018670.0000020.0003660.0007251.109718e-07
bm25_userknn_K50_view-True0.0006970.0008470.0008170.0025360.0002280.0005520.0012428.703289e-07
bm25_userknn_K60_view-False0.0000310.0004580.0006160.0018410.0000130.0003750.0005438.345726e-08
bm25_userknn_K60_view-True0.0007060.0008690.0008500.0026910.0002320.0005690.0017341.165137e-06
cosine_userknn_K30_view-FalseNaNNaNNaNNaNNaNNaNNaNNaN
cosine_userknn_K30_view-TrueNaNNaNNaNNaNNaNNaNNaNNaN
cosine_userknn_K40_view-FalseNaNNaNNaNNaNNaNNaNNaNNaN
cosine_userknn_K40_view-TrueNaNNaNNaNNaNNaNNaNNaNNaN
cosine_userknn_K50_view-False0.0003550.0009810.0007060.0033660.0004120.0007980.0010416.443968e-07
cosine_userknn_K50_view-True0.0007920.0014680.0009660.0047400.0013080.0018170.0183502.644474e-06
cosine_userknn_K60_view-False0.0003770.0010290.0007350.0035480.0004270.0008310.0032367.481876e-07
cosine_userknn_K60_view-True0.0008100.0015550.0010060.0051480.0013630.0019080.0182082.395850e-06
popular_mw_view-FalseNaNNaNNaNNaNNaNNaNNaNNaN
popular_mw_view-TrueNaNNaNNaNNaNNaNNaNNaNNaN
popular_view-FalseNaNNaNNaNNaNNaNNaNNaNNaN
popular_view-TrueNaNNaNNaNNaNNaNNaNNaNNaN
tfidf_userknn_K30_view-FalseNaNNaNNaNNaNNaNNaNNaNNaN
tfidf_userknn_K30_view-TrueNaNNaNNaNNaNNaNNaNNaNNaN
tfidf_userknn_K40_view-FalseNaNNaNNaNNaNNaNNaNNaNNaN
tfidf_userknn_K40_view-TrueNaNNaNNaNNaNNaNNaNNaNNaN
tfidf_userknn_K50_view-False0.0005680.0014770.0005630.0026220.0006370.0008480.0140731.071478e-06
tfidf_userknn_K50_view-True0.0006550.0012350.0007460.0033030.0015870.0019030.0288473.757778e-06
tfidf_userknn_K60_view-False0.0005170.0013700.0005780.0028550.0006190.0008680.0134261.001711e-06
tfidf_userknn_K60_view-True0.0006300.0012300.0007420.0032730.0015980.0019220.0293354.434436e-06
\n", - "
" - ], - "text/plain": [ - " prec@5 recall@5 prec@10 \\\n", - "cv model \n", - "fold_1w bm25_userknn_K30_view-False 0.004042 0.011380 0.003319 \n", - " bm25_userknn_K30_view-True 0.005348 0.014587 0.002787 \n", - " bm25_userknn_K40_view-False 0.004036 0.011393 0.003310 \n", - " bm25_userknn_K40_view-True 0.005334 0.014592 0.002774 \n", - " bm25_userknn_K50_view-False 0.003777 0.011013 0.003087 \n", - " bm25_userknn_K50_view-True 0.004894 0.013955 0.002467 \n", - " bm25_userknn_K60_view-False 0.003774 0.011002 0.003088 \n", - " bm25_userknn_K60_view-True 0.004894 0.013963 0.002466 \n", - " cosine_userknn_K30_view-False 0.002393 0.007803 0.001918 \n", - " cosine_userknn_K30_view-True 0.003624 0.010400 0.001786 \n", - " cosine_userknn_K40_view-False 0.002389 0.007794 0.001908 \n", - " cosine_userknn_K40_view-True 0.003568 0.010257 0.001752 \n", - " cosine_userknn_K50_view-False 0.002316 0.007773 0.001844 \n", - " cosine_userknn_K50_view-True 0.003382 0.010032 0.001646 \n", - " cosine_userknn_K60_view-False 0.002314 0.007792 0.001859 \n", - " cosine_userknn_K60_view-True 0.003404 0.010088 0.001638 \n", - " popular_mw_view-False 0.000002 0.000006 0.000001 \n", - " popular_mw_view-True 0.000002 0.000006 0.000001 \n", - " popular_view-False 0.005212 0.014572 0.003829 \n", - " popular_view-True 0.006104 0.016656 0.003793 \n", - " tfidf_userknn_K30_view-False 0.002244 0.006995 0.001945 \n", - " tfidf_userknn_K30_view-True 0.003332 0.009001 0.001968 \n", - " tfidf_userknn_K40_view-False 0.002247 0.007018 0.001933 \n", - " tfidf_userknn_K40_view-True 0.003319 0.008904 0.001924 \n", - " tfidf_userknn_K50_view-False 0.002201 0.007068 0.001885 \n", - " tfidf_userknn_K50_view-True 0.003195 0.008819 0.001801 \n", - " tfidf_userknn_K60_view-False 0.002204 0.007088 0.001886 \n", - " tfidf_userknn_K60_view-True 0.003199 0.008829 0.001809 \n", - "fold_5d bm25_userknn_K30_view-False NaN NaN NaN \n", - " bm25_userknn_K30_view-True NaN NaN NaN \n", - " bm25_userknn_K40_view-False NaN NaN NaN \n", - " bm25_userknn_K40_view-True NaN NaN NaN \n", - " bm25_userknn_K50_view-False 0.000039 0.000456 0.000612 \n", - " bm25_userknn_K50_view-True 0.000697 0.000847 0.000817 \n", - " bm25_userknn_K60_view-False 0.000031 0.000458 0.000616 \n", - " bm25_userknn_K60_view-True 0.000706 0.000869 0.000850 \n", - " cosine_userknn_K30_view-False NaN NaN NaN \n", - " cosine_userknn_K30_view-True NaN NaN NaN \n", - " cosine_userknn_K40_view-False NaN NaN NaN \n", - " cosine_userknn_K40_view-True NaN NaN NaN \n", - " cosine_userknn_K50_view-False 0.000355 0.000981 0.000706 \n", - " cosine_userknn_K50_view-True 0.000792 0.001468 0.000966 \n", - " cosine_userknn_K60_view-False 0.000377 0.001029 0.000735 \n", - " cosine_userknn_K60_view-True 0.000810 0.001555 0.001006 \n", - " popular_mw_view-False NaN NaN NaN \n", - " popular_mw_view-True NaN NaN NaN \n", - " popular_view-False NaN NaN NaN \n", - " popular_view-True NaN NaN NaN \n", - " tfidf_userknn_K30_view-False NaN NaN NaN \n", - " tfidf_userknn_K30_view-True NaN NaN NaN \n", - " tfidf_userknn_K40_view-False NaN NaN NaN \n", - " tfidf_userknn_K40_view-True NaN NaN NaN \n", - " tfidf_userknn_K50_view-False 0.000568 0.001477 0.000563 \n", - " tfidf_userknn_K50_view-True 0.000655 0.001235 0.000746 \n", - " tfidf_userknn_K60_view-False 0.000517 0.001370 0.000578 \n", - " tfidf_userknn_K60_view-True 0.000630 0.001230 0.000742 \n", - "\n", - " recall@10 MAP@5 MAP@10 \\\n", - "cv model \n", - "fold_1w bm25_userknn_K30_view-False 0.018050 0.004144 0.005235 \n", - " bm25_userknn_K30_view-True 0.013620 0.009823 0.009871 \n", - " bm25_userknn_K40_view-False 0.018033 0.004149 0.005234 \n", - " bm25_userknn_K40_view-True 0.013511 0.009824 0.009859 \n", - " bm25_userknn_K50_view-False 0.017476 0.004039 0.005071 \n", - " bm25_userknn_K50_view-True 0.012485 0.009562 0.009532 \n", - " bm25_userknn_K60_view-False 0.017492 0.004038 0.005074 \n", - " bm25_userknn_K60_view-True 0.012478 0.009562 0.009529 \n", - " cosine_userknn_K30_view-False 0.011326 0.002518 0.003091 \n", - " cosine_userknn_K30_view-True 0.008810 0.007000 0.006842 \n", - " cosine_userknn_K40_view-False 0.011295 0.002517 0.003081 \n", - " cosine_userknn_K40_view-True 0.008547 0.006951 0.006784 \n", - " cosine_userknn_K50_view-False 0.011284 0.002512 0.003072 \n", - " cosine_userknn_K50_view-True 0.008276 0.006918 0.006730 \n", - " cosine_userknn_K60_view-False 0.011354 0.002514 0.003083 \n", - " cosine_userknn_K60_view-True 0.008246 0.006961 0.006769 \n", - " popular_mw_view-False 0.000005 0.000002 0.000002 \n", - " popular_mw_view-True 0.000005 0.000002 0.000002 \n", - " popular_view-False 0.021739 0.006100 0.007075 \n", - " popular_view-True 0.020875 0.008955 0.009653 \n", - " tfidf_userknn_K30_view-False 0.010664 0.002381 0.002965 \n", - " tfidf_userknn_K30_view-True 0.008611 0.006374 0.006390 \n", - " tfidf_userknn_K40_view-False 0.010582 0.002384 0.002949 \n", - " tfidf_userknn_K40_view-True 0.008288 0.006300 0.006288 \n", - " tfidf_userknn_K50_view-False 0.010591 0.002401 0.002955 \n", - " tfidf_userknn_K50_view-True 0.007796 0.006357 0.006279 \n", - " tfidf_userknn_K60_view-False 0.010659 0.002412 0.002972 \n", - " tfidf_userknn_K60_view-True 0.007841 0.006420 0.006348 \n", - "fold_5d bm25_userknn_K30_view-False NaN NaN NaN \n", - " bm25_userknn_K30_view-True NaN NaN NaN \n", - " bm25_userknn_K40_view-False NaN NaN NaN \n", - " bm25_userknn_K40_view-True NaN NaN NaN \n", - " bm25_userknn_K50_view-False 0.001867 0.000002 0.000366 \n", - " bm25_userknn_K50_view-True 0.002536 0.000228 0.000552 \n", - " bm25_userknn_K60_view-False 0.001841 0.000013 0.000375 \n", - " bm25_userknn_K60_view-True 0.002691 0.000232 0.000569 \n", - " cosine_userknn_K30_view-False NaN NaN NaN \n", - " cosine_userknn_K30_view-True NaN NaN NaN \n", - " cosine_userknn_K40_view-False NaN NaN NaN \n", - " cosine_userknn_K40_view-True NaN NaN NaN \n", - " cosine_userknn_K50_view-False 0.003366 0.000412 0.000798 \n", - " cosine_userknn_K50_view-True 0.004740 0.001308 0.001817 \n", - " cosine_userknn_K60_view-False 0.003548 0.000427 0.000831 \n", - " cosine_userknn_K60_view-True 0.005148 0.001363 0.001908 \n", - " popular_mw_view-False NaN NaN NaN \n", - " popular_mw_view-True NaN NaN NaN \n", - " popular_view-False NaN NaN NaN \n", - " popular_view-True NaN NaN NaN \n", - " tfidf_userknn_K30_view-False NaN NaN NaN \n", - " tfidf_userknn_K30_view-True NaN NaN NaN \n", - " tfidf_userknn_K40_view-False NaN NaN NaN \n", - " tfidf_userknn_K40_view-True NaN NaN NaN \n", - " tfidf_userknn_K50_view-False 0.002622 0.000637 0.000848 \n", - " tfidf_userknn_K50_view-True 0.003303 0.001587 0.001903 \n", - " tfidf_userknn_K60_view-False 0.002855 0.000619 0.000868 \n", - " tfidf_userknn_K60_view-True 0.003273 0.001598 0.001922 \n", - "\n", - " novelty serendipity \n", - "cv model \n", - "fold_1w bm25_userknn_K30_view-False 0.029730 1.677056e-06 \n", - " bm25_userknn_K30_view-True 0.015006 3.357219e-06 \n", - " bm25_userknn_K40_view-False 0.029613 1.666772e-06 \n", - " bm25_userknn_K40_view-True 0.014709 3.359684e-06 \n", - " bm25_userknn_K50_view-False 0.029329 1.807696e-06 \n", - " bm25_userknn_K50_view-True 0.014861 3.329699e-06 \n", - " bm25_userknn_K60_view-False 0.029368 1.805538e-06 \n", - " bm25_userknn_K60_view-True 0.014738 3.340070e-06 \n", - " cosine_userknn_K30_view-False 0.047337 5.304930e-06 \n", - " cosine_userknn_K30_view-True 0.047027 9.087191e-06 \n", - " cosine_userknn_K40_view-False 0.046445 5.302222e-06 \n", - " cosine_userknn_K40_view-True 0.046824 9.276498e-06 \n", - " cosine_userknn_K50_view-False 0.046345 5.581875e-06 \n", - " cosine_userknn_K50_view-True 0.046799 9.814832e-06 \n", - " cosine_userknn_K60_view-False 0.046454 5.564036e-06 \n", - " cosine_userknn_K60_view-True 0.047133 9.793017e-06 \n", - " popular_mw_view-False 0.125158 1.003157e-07 \n", - " popular_mw_view-True 0.125155 1.003157e-07 \n", - " popular_view-False 0.030175 0.000000e+00 \n", - " popular_view-True 0.019131 2.429424e-07 \n", - " tfidf_userknn_K30_view-False 0.039877 8.695037e-06 \n", - " tfidf_userknn_K30_view-True 0.069638 1.279797e-05 \n", - " tfidf_userknn_K40_view-False 0.040851 8.509896e-06 \n", - " tfidf_userknn_K40_view-True 0.070819 1.317232e-05 \n", - " tfidf_userknn_K50_view-False 0.040492 8.580259e-06 \n", - " tfidf_userknn_K50_view-True 0.065490 1.351216e-05 \n", - " tfidf_userknn_K60_view-False 0.040827 8.400699e-06 \n", - " tfidf_userknn_K60_view-True 0.066050 1.359432e-05 \n", - "fold_5d bm25_userknn_K30_view-False NaN NaN \n", - " bm25_userknn_K30_view-True NaN NaN \n", - " bm25_userknn_K40_view-False NaN NaN \n", - " bm25_userknn_K40_view-True NaN NaN \n", - " bm25_userknn_K50_view-False 0.000725 1.109718e-07 \n", - " bm25_userknn_K50_view-True 0.001242 8.703289e-07 \n", - " bm25_userknn_K60_view-False 0.000543 8.345726e-08 \n", - " bm25_userknn_K60_view-True 0.001734 1.165137e-06 \n", - " cosine_userknn_K30_view-False NaN NaN \n", - " cosine_userknn_K30_view-True NaN NaN \n", - " cosine_userknn_K40_view-False NaN NaN \n", - " cosine_userknn_K40_view-True NaN NaN \n", - " cosine_userknn_K50_view-False 0.001041 6.443968e-07 \n", - " cosine_userknn_K50_view-True 0.018350 2.644474e-06 \n", - " cosine_userknn_K60_view-False 0.003236 7.481876e-07 \n", - " cosine_userknn_K60_view-True 0.018208 2.395850e-06 \n", - " popular_mw_view-False NaN NaN \n", - " popular_mw_view-True NaN NaN \n", - " popular_view-False NaN NaN \n", - " popular_view-True NaN NaN \n", - " tfidf_userknn_K30_view-False NaN NaN \n", - " tfidf_userknn_K30_view-True NaN NaN \n", - " tfidf_userknn_K40_view-False NaN NaN \n", - " tfidf_userknn_K40_view-True NaN NaN \n", - " tfidf_userknn_K50_view-False 0.014073 1.071478e-06 \n", - " tfidf_userknn_K50_view-True 0.028847 3.757778e-06 \n", - " tfidf_userknn_K60_view-False 0.013426 1.001711e-06 \n", - " tfidf_userknn_K60_view-True 0.029335 4.434436e-06 " - ] - }, - "execution_count": 51, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df_metrics_std = df_metrics.groupby(['cv', 'model'])[\n", - " 'prec@5', 'recall@5', 'prec@10', 'recall@10', 'MAP@5', 'MAP@10', 'novelty', 'serendipity'\n", - "].std()\n", - "\n", - "df_metrics_std" - ] - }, - { - "cell_type": "code", - "execution_count": 86, - "id": "58ad9d07", - "metadata": {}, - "outputs": [], - "source": [ - "df_metrics_1w_mean = df_metrics_mean.loc[\"fold_1w\"]\n", - "df_metrics_1w_std = df_metrics_std.loc[\"fold_1w\"]" - ] - }, - { - "cell_type": "code", - "execution_count": 82, - "id": "52bffafc", - "metadata": {}, - "outputs": [], - "source": [ - "best_model = \"bm25_userknn_K60_view-True\"\n", - "col_metrics = list(metrics.keys())\n", - "std_best_metrics = df_metrics_1w_std[df_metrics_1w_std[\"model\"] == best_model][col_metrics].values[0]" - ] - }, - { - "cell_type": "code", - "execution_count": 95, - "id": "0059da61", - "metadata": { - "collapsed": true - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "prec@5 0.004894\n", - "recall@5 0.013963\n", - "prec@10 0.002466\n", - "recall@10 0.012478\n", - "MAP@5 0.009562\n", - "MAP@10 0.009529\n", - "novelty 0.014738\n", - "serendipity 0.000003\n", - "Name: bm25_userknn_K60_view-True, dtype: float64\n", - "\n", - "===Сравнение с bm25_userknn_K30_view-False\n", - "prec@5 0.000852\n", - "recall@5 0.002584\n", - "prec@10 -0.000854\n", - "recall@10 -0.005571\n", - "MAP@5 0.005418\n", - "MAP@10 0.004293\n", - "novelty -0.014992\n", - "serendipity 0.000002\n", - "dtype: float64\n", - "=========================\n", - "\n", - "===Сравнение с bm25_userknn_K30_view-True\n", - "prec@5 -4.545181e-04\n", - "recall@5 -6.232778e-04\n", - "prec@10 -3.208519e-04\n", - "recall@10 -1.141463e-03\n", - "MAP@5 -2.606624e-04\n", - "MAP@10 -3.423636e-04\n", - "novelty -2.682734e-04\n", - "serendipity -1.714842e-08\n", - "dtype: float64\n", - "=========================\n", - "\n", - "===Сравнение с bm25_userknn_K40_view-False\n", - "prec@5 0.000858\n", - "recall@5 0.002570\n", - "prec@10 -0.000844\n", - "recall@10 -0.005554\n", - "MAP@5 0.005413\n", - "MAP@10 0.004294\n", - "novelty -0.014875\n", - "serendipity 0.000002\n", - "dtype: float64\n", - "=========================\n", - "\n", - "===Сравнение с bm25_userknn_K40_view-True\n", - "prec@5 -4.403880e-04\n", - "recall@5 -6.290758e-04\n", - "prec@10 -3.080923e-04\n", - "recall@10 -1.032860e-03\n", - "MAP@5 -2.622039e-04\n", - "MAP@10 -3.305713e-04\n", - "novelty 2.940168e-05\n", - "serendipity -1.961419e-08\n", - "dtype: float64\n", - "=========================\n", - "\n", - "===Сравнение с bm25_userknn_K50_view-False\n", - "prec@5 0.001117\n", - "recall@5 0.002950\n", - "prec@10 -0.000621\n", - "recall@10 -0.004998\n", - "MAP@5 0.005523\n", - "MAP@10 0.004457\n", - "novelty -0.014591\n", - "serendipity 0.000002\n", - "dtype: float64\n", - "=========================\n", - "\n", - "===Сравнение с bm25_userknn_K50_view-True\n", - "prec@5 -6.188074e-08\n", - "recall@5 8.340206e-06\n", - "prec@10 -1.581047e-06\n", - "recall@10 -6.557768e-06\n", - "MAP@5 8.532418e-08\n", - "MAP@10 -3.212381e-06\n", - "novelty -1.225529e-04\n", - "serendipity 1.037090e-08\n", - "dtype: float64\n", - "=========================\n", - "\n", - "===Сравнение с bm25_userknn_K60_view-False\n", - "prec@5 0.001119\n", - "recall@5 0.002961\n", - "prec@10 -0.000622\n", - "recall@10 -0.005014\n", - "MAP@5 0.005524\n", - "MAP@10 0.004455\n", - "novelty -0.014630\n", - "serendipity 0.000002\n", - "dtype: float64\n", - "=========================\n", - "\n", - "===Сравнение с cosine_userknn_K30_view-False\n", - "prec@5 0.002501\n", - "recall@5 0.006161\n", - "prec@10 0.000548\n", - "recall@10 0.001152\n", - "MAP@5 0.007044\n", - "MAP@10 0.006437\n", - "novelty -0.032599\n", - "serendipity -0.000002\n", - "dtype: float64\n", - "=========================\n", - "\n", - "===Сравнение с cosine_userknn_K30_view-True\n", - "prec@5 0.001270\n", - "recall@5 0.003563\n", - "prec@10 0.000680\n", - "recall@10 0.003668\n", - "MAP@5 0.002563\n", - "MAP@10 0.002687\n", - "novelty -0.032289\n", - "serendipity -0.000006\n", - "dtype: float64\n", - "=========================\n", - "\n", - "===Сравнение с cosine_userknn_K40_view-False\n", - "prec@5 0.002504\n", - "recall@5 0.006169\n", - "prec@10 0.000558\n", - "recall@10 0.001184\n", - "MAP@5 0.007046\n", - "MAP@10 0.006448\n", - "novelty -0.031707\n", - "serendipity -0.000002\n", - "dtype: float64\n", - "=========================\n", - "\n", - "===Сравнение с cosine_userknn_K40_view-True\n", - "prec@5 0.001325\n", - "recall@5 0.003706\n", - "prec@10 0.000714\n", - "recall@10 0.003931\n", - "MAP@5 0.002611\n", - "MAP@10 0.002744\n", - "novelty -0.032086\n", - "serendipity -0.000006\n", - "dtype: float64\n", - "=========================\n", - "\n", - "===Сравнение с cosine_userknn_K50_view-False\n", - "prec@5 0.002578\n", - "recall@5 0.006190\n", - "prec@10 0.000622\n", - "recall@10 0.001195\n", - "MAP@5 0.007051\n", - "MAP@10 0.006456\n", - "novelty -0.031607\n", - "serendipity -0.000002\n", - "dtype: float64\n", - "=========================\n", - "\n", - "===Сравнение с cosine_userknn_K50_view-True\n", - "prec@5 0.001511\n", - "recall@5 0.003931\n", - "prec@10 0.000820\n", - "recall@10 0.004203\n", - "MAP@5 0.002644\n", - "MAP@10 0.002798\n", - "novelty -0.032061\n", - "serendipity -0.000006\n", - "dtype: float64\n", - "=========================\n", - "\n", - "===Сравнение с cosine_userknn_K60_view-False\n", - "prec@5 0.002580\n", - "recall@5 0.006172\n", - "prec@10 0.000607\n", - "recall@10 0.001124\n", - "MAP@5 0.007049\n", - "MAP@10 0.006446\n", - "novelty -0.031716\n", - "serendipity -0.000002\n", - "dtype: float64\n", - "=========================\n", - "\n", - "===Сравнение с cosine_userknn_K60_view-True\n", - "prec@5 0.001489\n", - "recall@5 0.003875\n", - "prec@10 0.000827\n", - "recall@10 0.004232\n", - "MAP@5 0.002601\n", - "MAP@10 0.002760\n", - "novelty -0.032395\n", - "serendipity -0.000006\n", - "dtype: float64\n", - "=========================\n", - "\n", - "===Сравнение с popular_mw_view-False\n", - "prec@5 0.004892\n", - "recall@5 0.013958\n", - "prec@10 0.002465\n", - "recall@10 0.012473\n", - "MAP@5 0.009561\n", - "MAP@10 0.009527\n", - "novelty -0.110420\n", - "serendipity 0.000003\n", - "dtype: float64\n", - "=========================\n", - "\n", - "===Сравнение с popular_mw_view-True\n", - "prec@5 0.004892\n", - "recall@5 0.013958\n", - "prec@10 0.002465\n", - "recall@10 0.012473\n", - "MAP@5 0.009561\n", - "MAP@10 0.009527\n", - "novelty -0.110417\n", - "serendipity 0.000003\n", - "dtype: float64\n", - "=========================\n", - "\n", - "===Сравнение с popular_view-False\n", - "prec@5 -0.000319\n", - "recall@5 -0.000609\n", - "prec@10 -0.001363\n", - "recall@10 -0.009260\n", - "MAP@5 0.003462\n", - "MAP@10 0.002453\n", - "novelty -0.015437\n", - "serendipity 0.000003\n", - "dtype: float64\n", - "=========================\n", - "\n", - "===Сравнение с popular_view-True\n", - "prec@5 -0.001210\n", - "recall@5 -0.002692\n", - "prec@10 -0.001327\n", - "recall@10 -0.008397\n", - "MAP@5 0.000607\n", - "MAP@10 -0.000124\n", - "novelty -0.004393\n", - "serendipity 0.000003\n", - "dtype: float64\n", - "=========================\n", - "\n", - "===Сравнение с tfidf_userknn_K30_view-False\n", - "prec@5 0.002649\n", - "recall@5 0.006968\n", - "prec@10 0.000521\n", - "recall@10 0.001815\n", - "MAP@5 0.007181\n", - "MAP@10 0.006564\n", - "novelty -0.025139\n", - "serendipity -0.000005\n", - "dtype: float64\n", - "=========================\n", - "\n", - "===Сравнение с tfidf_userknn_K30_view-True\n", - "prec@5 0.001561\n", - "recall@5 0.004963\n", - "prec@10 0.000498\n", - "recall@10 0.003867\n", - "MAP@5 0.003188\n", - "MAP@10 0.003139\n", - "novelty -0.054900\n", - "serendipity -0.000009\n", - "dtype: float64\n", - "=========================\n", - "\n", - "===Сравнение с tfidf_userknn_K40_view-False\n", - "prec@5 0.002647\n", - "recall@5 0.006945\n", - "prec@10 0.000532\n", - "recall@10 0.001897\n", - "MAP@5 0.007178\n", - "MAP@10 0.006579\n", - "novelty -0.026113\n", - "serendipity -0.000005\n", - "dtype: float64\n", - "=========================\n", - "\n", - "===Сравнение с tfidf_userknn_K40_view-True\n", - "prec@5 0.001575\n", - "recall@5 0.005059\n", - "prec@10 0.000542\n", - "recall@10 0.004190\n", - "MAP@5 0.003262\n", - "MAP@10 0.003240\n", - "novelty -0.056080\n", - "serendipity -0.000010\n", - "dtype: float64\n", - "=========================\n", - "\n", - "===Сравнение с tfidf_userknn_K50_view-False\n", - "prec@5 0.002693\n", - "recall@5 0.006895\n", - "prec@10 0.000581\n", - "recall@10 0.001887\n", - "MAP@5 0.007161\n", - "MAP@10 0.006574\n", - "novelty -0.025754\n", - "serendipity -0.000005\n", - "dtype: float64\n", - "=========================\n", - "\n", - "===Сравнение с tfidf_userknn_K50_view-True\n", - "prec@5 0.001698\n", - "recall@5 0.005144\n", - "prec@10 0.000665\n", - "recall@10 0.004682\n", - "MAP@5 0.003205\n", - "MAP@10 0.003249\n", - "novelty -0.050752\n", - "serendipity -0.000010\n", - "dtype: float64\n", - "=========================\n", - "\n", - "===Сравнение с tfidf_userknn_K60_view-False\n", - "prec@5 0.002690\n", - "recall@5 0.006875\n", - "prec@10 0.000580\n", - "recall@10 0.001819\n", - "MAP@5 0.007150\n", - "MAP@10 0.006557\n", - "novelty -0.026089\n", - "serendipity -0.000005\n", - "dtype: float64\n", - "=========================\n", - "\n", - "===Сравнение с tfidf_userknn_K60_view-True\n", - "prec@5 0.001694\n", - "recall@5 0.005134\n", - "prec@10 0.000657\n", - "recall@10 0.004637\n", - "MAP@5 0.003142\n", - "MAP@10 0.003180\n", - "novelty -0.051312\n", - "serendipity -0.000010\n", - "dtype: float64\n", - "=========================\n" - ] - } - ], - "source": [ - "print(df_metrics_1w_std.loc[best_model])\n", - "for model in df_metrics_1w_mean.index:\n", - " if model != best_model:\n", - " print(f\"\\n===Сравнение с {model}\")\n", - " print(df_metrics_1w_mean.loc[best_model] - df_metrics_1w_mean.loc[model])\n", - " print(\"=========================\")" - ] - }, - { - "cell_type": "markdown", - "id": "0675ba9b", - "metadata": {}, - "source": [ - "Лучшей модели большинством из моделей видны статистические различия, кроме всех моделей bmp (логично, потому что лучшая модель bmp с k = 60) и моделью tfidf, где для рекомендаций стоял флаг filter_viewed = True, что означает рекомендовать не одинаковые элементы для всех пользователей" - ] - }, - { - "cell_type": "markdown", - "id": "e233b183", - "metadata": {}, - "source": [ - "# Обучение на всех имеющихся данных и формирование оффлайн рекомендаций" - ] - }, - { - "cell_type": "code", - "execution_count": 98, - "id": "30e985b6", - "metadata": {}, - "outputs": [], - "source": [ - "dataset = Dataset.construct(\n", - " interactions_df=interactions,\n", - " user_features_df=None,\n", - " item_features_df=None\n", - ")\n", - "\n", - "bmp25_k60_model = ImplicitItemKNNWrapperModel(BM25Recommender(K=60))\n", - "bmp25_k60_model.fit(dataset)\n", - "\n", - "K_RECOS = 30\n", - " \n", - "recos_offline_bmp25 = bmp25_k60_model.recommend(\n", - " users=interactions[Columns.User].unique(),\n", - " dataset=dataset,\n", - " k=K_RECOS,\n", - " filter_viewed=True,\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 100, - "id": "4034d96f", - "metadata": {}, - "outputs": [], - "source": [ - "recos_offline_bmp25.to_csv(\"../data/hw_3/bmp_25_k60_rectools.csv\", index=False)" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "d52a48b5", - "metadata": {}, - "outputs": [], - "source": [ - "dataset = Dataset.construct(\n", - " interactions_df=interactions,\n", - " user_features_df=None,\n", - " item_features_df=None\n", - ")\n", - "\n", - "tfidf_k60_model = ImplicitItemKNNWrapperModel(TFIDFRecommender(K=60))\n", - "tfidf_k60_model.fit(dataset)\n", - "\n", - "K_RECOS = 30\n", - " \n", - "recos_offline_tfidf = tfidf_k60_model.recommend(\n", - " users=interactions[Columns.User].unique(),\n", - " dataset=dataset,\n", - " k=K_RECOS,\n", - " filter_viewed=True,\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "d74a0ee6", - "metadata": {}, - "outputs": [], - "source": [ - "recos_offline_tfidf.to_csv(\"../data/hw_3/tfidf_k60_rectools.csv\", index=False)" - ] - }, - { - "cell_type": "markdown", - "id": "0164df93", - "metadata": {}, - "source": [ - "# Формирование рекомендаций для cold users" - ] - }, - { - "cell_type": "markdown", - "id": "5af7d214", - "metadata": {}, - "source": [ - "По моделям на основе популярного наилучшего качества достигали метрики по модели popular на основе количества уникальных пользователей взаимодействовавших с элементом, НО по среднему весу взаимодействия с элементами модель показывает по метрики новелти очень высокие результаты, поэтому стоит попробовать обе из моделей" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "51fdeae3", - "metadata": {}, - "outputs": [], - "source": [ - "dataset = Dataset.construct(\n", - " interactions_df=interactions,\n", - " user_features_df=None,\n", - " item_features_df=None\n", - ")\n", - "\n", - "popular_model = PopularModel()\n", - "popular_model.fit(dataset)\n", - "\n", - "item_inv = dict(enumerate(interactions[\"item_id\"].unique()))\n", - "recos_pop = []\n", - "for item_pop in popular_model.popularity_list[0]:\n", - " recos_pop.append(item_inv[item_pop])\n", - "\n", - "df_pop_recos = pd.DataFrame({\"item_id\": recos_pop})\n", - "\n", - "df_pop_recos.to_csv(\"../data/hw_3/popular_item.csv\", index=False)" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "52981cce", - "metadata": {}, - "outputs": [], - "source": [ - "dataset = Dataset.construct(\n", - " interactions_df=interactions,\n", - " user_features_df=None,\n", - " item_features_df=None\n", - ")\n", - "\n", - "popular_model_mw = PopularModel(popularity=\"mean_weight\")\n", - "popular_model_mw.fit(dataset)\n", - "\n", - "item_inv = dict(enumerate(interactions[\"item_id\"].unique()))\n", - "recos_pop = []\n", - "for item_pop in popular_model_mw.popularity_list[0]:\n", - " recos_pop.append(item_inv[item_pop])\n", - "\n", - "df_pop_recos_mw = pd.DataFrame({\"item_id\": recos_pop})\n", - "\n", - "df_pop_recos_mw.to_csv(\"../data/hw_3/popular_mean_weight_item.csv\", index=False)" - ] - }, - { - "cell_type": "markdown", - "id": "170efd3c", - "metadata": {}, - "source": [ - "# Блендинг результатов моделей" - ] - }, - { - "cell_type": "markdown", - "id": "878f0b90", - "metadata": {}, - "source": [ - "Механизм блендинга будет выглядить следующим образом:\n", - "\n", - "1. Берутся рекомендации, сделанные моделями tfidf и bmp25, конкатятся результаты, удялются дубликаты item-ов\n", - "2. Берется заготовленный датаест items c полями item_id и idf\n", - "3. смотрится idf, чем он выше, тем выше будет стоять item в выдаче\n", - "\n", - "Такой подход обусловлен тем, что idf показывает обратную частоту item, соответственно в выдаче наверх будут попадать item, с которым меньшее количество раз взаимодейстовали пользователи, т.е. в перспективе такой подход может предлагать item, с которыми ни один пользователь не взаимодействовал или взаимодействовали очень мало, т.е. может решиться проблема длинного хвоста." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "3b35f8ff", - "metadata": {}, - "outputs": [], - "source": [ - "import pandas as pd" - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "id": "163f79b9", - "metadata": {}, - "outputs": [], - "source": [ - "df_bmp_recs = pd.read_csv(\"../data/hw_3/bmp_25_k60_rectools.csv\")\n", - "df_tfidf_recs = pd.read_csv(\"../data/hw_3/tfidf_k60_rectools.csv\") " - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "id": "c842edef", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
user_iditem_idscorerank
0176549138658.899597e+101
1176549104408.153085e+102
2176549152977.204604e+103
317654937346.953473e+104
417654941514.674591e+105
2886225869726254341.615419e+1026
2886225969726211321.605160e+1027
2886226069726274761.566697e+1028
28862261697262112371.546907e+1029
28862262697262129951.542308e+1030
\n", - "
" - ], - "text/plain": [ - " user_id item_id score rank\n", - "0 176549 13865 8.899597e+10 1\n", - "1 176549 10440 8.153085e+10 2\n", - "2 176549 15297 7.204604e+10 3\n", - "3 176549 3734 6.953473e+10 4\n", - "4 176549 4151 4.674591e+10 5\n", - "28862258 697262 5434 1.615419e+10 26\n", - "28862259 697262 1132 1.605160e+10 27\n", - "28862260 697262 7476 1.566697e+10 28\n", - "28862261 697262 11237 1.546907e+10 29\n", - "28862262 697262 12995 1.542308e+10 30" - ] - }, - "execution_count": 20, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "pd.concat([df_bmp_recs.head(), df_bmp_recs.tail()])" - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "id": "576f23a7", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
user_iditem_idscorerank
01765491174913575.6601851
11765491627011946.8727082
21765491198511355.6931193
31765491315910375.5006474
41765491526610269.0196905
2886225869726261921294.34241426
28862259697262116401277.33233327
2886226069726274761262.91937728
28862261697262141213.49928129
2886226269726237841200.34778530
\n", - "
" - ], - "text/plain": [ - " user_id item_id score rank\n", - "0 176549 11749 13575.660185 1\n", - "1 176549 16270 11946.872708 2\n", - "2 176549 11985 11355.693119 3\n", - "3 176549 13159 10375.500647 4\n", - "4 176549 15266 10269.019690 5\n", - "28862258 697262 6192 1294.342414 26\n", - "28862259 697262 11640 1277.332333 27\n", - "28862260 697262 7476 1262.919377 28\n", - "28862261 697262 14 1213.499281 29\n", - "28862262 697262 3784 1200.347785 30" - ] - }, - "execution_count": 21, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "pd.concat([df_tfidf_recs.head(), df_tfidf_recs.tail()])" - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "id": "dc5bfdf3", - "metadata": {}, - "outputs": [], - "source": [ - "del df_tfidf_recs['rank'], df_bmp_recs['rank'], df_tfidf_recs['score'], df_bmp_recs['score']" - ] - }, - { - "cell_type": "code", - "execution_count": 23, - "id": "edcc93bd", - "metadata": { - "scrolled": true - }, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
user_iditem_id
010975571132
110975575658
21097557142
310975573734
4109755716228
5109755712192
6109755713865
710975572657
810975579728
910975574880
10109755711778
1110975579996
1210975578636
1310975573935
1410975575803
1510975574457
1610975571844
1710975576382
1810975574716
1910975574495
4102337203734
4102337303935
4102337407417
4102337504495
4102337606382
4102337705803
4102337801844
41023379011778
4102338008636
4102338109996
4102338202657
41023383016228
4102338404880
41023385013865
410233860142
4102338706443
4102338804740
4102338906809
41023390010440
41023391014901
\n", - "
" - ], - "text/plain": [ - " user_id item_id\n", - "0 1097557 1132\n", - "1 1097557 5658\n", - "2 1097557 142\n", - "3 1097557 3734\n", - "4 1097557 16228\n", - "5 1097557 12192\n", - "6 1097557 13865\n", - "7 1097557 2657\n", - "8 1097557 9728\n", - "9 1097557 4880\n", - "10 1097557 11778\n", - "11 1097557 9996\n", - "12 1097557 8636\n", - "13 1097557 3935\n", - "14 1097557 5803\n", - "15 1097557 4457\n", - "16 1097557 1844\n", - "17 1097557 6382\n", - "18 1097557 4716\n", - "19 1097557 4495\n", - "41023372 0 3734\n", - "41023373 0 3935\n", - "41023374 0 7417\n", - "41023375 0 4495\n", - "41023376 0 6382\n", - "41023377 0 5803\n", - "41023378 0 1844\n", - "41023379 0 11778\n", - "41023380 0 8636\n", - "41023381 0 9996\n", - "41023382 0 2657\n", - "41023383 0 16228\n", - "41023384 0 4880\n", - "41023385 0 13865\n", - "41023386 0 142\n", - "41023387 0 6443\n", - "41023388 0 4740\n", - "41023389 0 6809\n", - "41023390 0 10440\n", - "41023391 0 14901" - ] - }, - "execution_count": 23, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df_all_recs = pd.concat(\n", - " [\n", - " df_bmp_recs, df_tfidf_recs\n", - " ],\n", - " ignore_index=True\n", - ").sort_values(\n", - " [\"user_id\"], ascending=False\n", - ").drop_duplicates(\n", - " [\"user_id\", \"item_id\"]\n", - ").reset_index(drop=True)\n", - "\n", - "pd.concat([df_all_recs.head(20), df_all_recs.tail(20)])" - ] - }, - { - "cell_type": "code", - "execution_count": 25, - "id": "1267df73", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "(15706, 2)\n" - ] - }, - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
indexidf
095067.150811
116598.524953
271075.821207
376388.407093
466867.778734
\n", - "
" - ], - "text/plain": [ - " index idf\n", - "0 9506 7.150811\n", - "1 1659 8.524953\n", - "2 7107 5.821207\n", - "3 7638 8.407093\n", - "4 6686 7.778734" - ] - }, - "execution_count": 25, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "item_idf = pd.read_csv(\"../data/kion_train/items_idf.csv\")\n", - "print(item_idf.shape)\n", - "item_idf.head()" - ] - }, - { - "cell_type": "code", - "execution_count": 26, - "id": "68c2c0c0", - "metadata": { - "scrolled": true - }, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
user_iditem_idindexidf
141097557580358036.840585
171097557638263826.806090
251097557747674766.545666
181097557471647166.480408
34109755714146.467549
24109755711640116406.318255
211097557543454346.226266
01097557113211326.183141
10109755711778117786.134312
131097557393539356.067242
231097557619261926.038563
11097557565856586.025091
261097557378437845.990008
4109755716228162285.756312
311097557741774175.715013
321097557782978295.615193
191097557449544955.563930
22109755714431144315.558556
151097557445744575.548639
28109755712995129955.495888
410233740741774175.715013
410233610782978295.615193
410233750449544955.563930
41023358014431144315.558556
410233640445744575.548639
41023363012995129955.495888
410233780184418445.419019
41023366011237112375.365593
410233600757175715.267906
410233880474047405.078522
410233800863686365.041418
410233810999699964.992277
410233890680968094.917360
4102338601421424.801620
410233840488048804.610045
410233820265726574.392592
410233720373437344.306872
410233650415141514.111983
41023385013865138653.825227
41023390010440104403.333947
\n", - "
" - ], - "text/plain": [ - " user_id item_id index idf\n", - "14 1097557 5803 5803 6.840585\n", - "17 1097557 6382 6382 6.806090\n", - "25 1097557 7476 7476 6.545666\n", - "18 1097557 4716 4716 6.480408\n", - "34 1097557 14 14 6.467549\n", - "24 1097557 11640 11640 6.318255\n", - "21 1097557 5434 5434 6.226266\n", - "0 1097557 1132 1132 6.183141\n", - "10 1097557 11778 11778 6.134312\n", - "13 1097557 3935 3935 6.067242\n", - "23 1097557 6192 6192 6.038563\n", - "1 1097557 5658 5658 6.025091\n", - "26 1097557 3784 3784 5.990008\n", - "4 1097557 16228 16228 5.756312\n", - "31 1097557 7417 7417 5.715013\n", - "32 1097557 7829 7829 5.615193\n", - "19 1097557 4495 4495 5.563930\n", - "22 1097557 14431 14431 5.558556\n", - "15 1097557 4457 4457 5.548639\n", - "28 1097557 12995 12995 5.495888\n", - "41023374 0 7417 7417 5.715013\n", - "41023361 0 7829 7829 5.615193\n", - "41023375 0 4495 4495 5.563930\n", - "41023358 0 14431 14431 5.558556\n", - "41023364 0 4457 4457 5.548639\n", - "41023363 0 12995 12995 5.495888\n", - "41023378 0 1844 1844 5.419019\n", - "41023366 0 11237 11237 5.365593\n", - "41023360 0 7571 7571 5.267906\n", - "41023388 0 4740 4740 5.078522\n", - "41023380 0 8636 8636 5.041418\n", - "41023381 0 9996 9996 4.992277\n", - "41023389 0 6809 6809 4.917360\n", - "41023386 0 142 142 4.801620\n", - "41023384 0 4880 4880 4.610045\n", - "41023382 0 2657 2657 4.392592\n", - "41023372 0 3734 3734 4.306872\n", - "41023365 0 4151 4151 4.111983\n", - "41023385 0 13865 13865 3.825227\n", - "41023390 0 10440 10440 3.333947" - ] - }, - "execution_count": 26, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df_all_recs = df_all_recs.merge(\n", - " item_idf, left_on='item_id', right_on='index', how='left'\n", - ").sort_values(['user_id', 'idf'], ascending=False)\n", - "\n", - "pd.concat([df_all_recs.head(20), df_all_recs.tail(20)])" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "36ecc1dd", - "metadata": {}, - "outputs": [], - "source": [ - "del df_all_recs['index'], df_all_recs['idf']" - ] - }, - { - "cell_type": "code", - "execution_count": 34, - "id": "2c868313", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Количество пользователей, у которорых рекомендаций меньше 10: 21\n" - ] - } - ], - "source": [ - "count_recs_by_users = df_all_recs.user_id.value_counts()\n", - "print(f\"Количество пользователей, у которорых рекомендаций меньше 10: {len(count_recs_by_users[count_recs_by_users < 10])}\")" - ] - }, - { - "cell_type": "markdown", - "id": "9820e5ab", - "metadata": {}, - "source": [ - "Для пользователей, у которых будет меньше рекомендаций, чем k_recs, рекомендации **будут пополняться популярным**" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "69d1fc9c", - "metadata": {}, - "outputs": [], - "source": [ - "df_popular = pd.read_csv('../data/hw_3/popular_item.csv')\n", - "users_need = count_recs_by_users[count_recs_by_users < 10].index" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "7242e8e3", - "metadata": {}, - "outputs": [], - "source": [ - "k_recs = 10\n", - "users, recs = [], []\n", - "for user, count in dict(count_recs_by_users[count_recs_by_users < 10]).items():\n", - " need_recs = k_recs - count\n", - " users.extend([user for _ in range(need_recs)])\n", - " recs.extend(df_popular[\"item_id\"][:need_recs].to_list())" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "29eaaadc", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
user_iditem_id
010975575803
110975576382
210975577476
310975574716
4109755714
.........
4102338702657
4102338803734
4102338904151
41023390013865
41023391010440
\n", - "

41023392 rows × 2 columns

\n", - "
" - ], - "text/plain": [ - " user_id item_id\n", - "0 1097557 5803\n", - "1 1097557 6382\n", - "2 1097557 7476\n", - "3 1097557 4716\n", - "4 1097557 14\n", - "... ... ...\n", - "41023387 0 2657\n", - "41023388 0 3734\n", - "41023389 0 4151\n", - "41023390 0 13865\n", - "41023391 0 10440\n", - "\n", - "[41023392 rows x 2 columns]" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df_all_recs" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "30df7064", - "metadata": {}, - "outputs": [], - "source": [ - "df_need = pd.DataFrame({\"user_id\": users, \"item_id\": recs})\n", - "df_all_recs = pd.concat([df_all_recs, df_need], ignore_index=True).sort_values(\"user_id\")" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "34f2a303", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Количество пользователей, у которорых рекомендаций меньше 10: 0\n" - ] - } - ], - "source": [ - "count_recs_by_users = df_all_recs.user_id.value_counts()\n", - "print(f\"Количество пользователей, у которорых рекомендаций меньше 10: {len(count_recs_by_users[count_recs_by_users < 10])}\")" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "id": "f9d7c2bc", - "metadata": {}, - "outputs": [], - "source": [ - "df_all_recs.to_csv(\"../data/hw_3/blending_tfidf_bmp25_idf_rectools.csv\", index=False)" - ] - }, - { - "cell_type": "markdown", - "id": "b8e2c037", - "metadata": {}, - "source": [ - "Offline рекомендации не работали с блендингом, решил уменьшить количество рекомендаций для одного юзера до 10 и заработало" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "id": "6e76be8c", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(9621050, 2)" - ] - }, - "execution_count": 14, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df_all_recs['rank'] = df_all_recs.groupby('user_id').cumcount() + 1\n", - "df_all_recs_top10 = df_all_recs[df_all_recs['rank'] <= 10]\n", - "del df_all_recs_top10['rank']\n", - "df_all_recs_top10.shape" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "id": "94d693eb", - "metadata": {}, - "outputs": [], - "source": [ - "df_all_recs_top10.to_csv(\"../data/hw_3/blending_tfidf_bmp25_idf_rectools_10.csv\", index=False)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "38f155ab", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "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.8.10" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/notebooks/HW-3.4-model-for-online-recs.ipynb b/notebooks/HW-3.4-model-for-online-recs.ipynb deleted file mode 100644 index fafafb1f..00000000 --- a/notebooks/HW-3.4-model-for-online-recs.ipynb +++ /dev/null @@ -1,1161 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "faa0d200", - "metadata": {}, - "outputs": [], - "source": [ - "import warnings\n", - "warnings.filterwarnings(\"ignore\")" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "54683b63", - "metadata": {}, - "outputs": [], - "source": [ - "import typing as tp\n", - "\n", - "import dill\n", - "import pandas as pd\n", - "import numpy as np\n", - "from implicit.nearest_neighbours import BM25Recommender, TFIDFRecommender\n", - "from rectools import Columns\n", - "import scipy as sp" - ] - }, - { - "cell_type": "markdown", - "id": "e00f73f1", - "metadata": {}, - "source": [ - "В ноутбуку \"HW-3.3\" c помощью стратегии валидации по неделям были отобраны несколько моделей с наиболее высокими метриками:\n", - "\n", - "- BMP25Recommender с гиперпараметром k = 60\n", - "- TFIDFRecommender с гиперпараметром k = 60\n", - "\n", - "Для этих моделей сформированы оффлайн рекомендации, которые показали 0.10384918 и 0.09577425 соответственно.\n", - "\n", - "Для формирования онлайн рекомендаций следует обучить те же архитектуры моделей с такими же гиперпараметрами из библиотеки implicit" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "4e7ecfc4", - "metadata": {}, - "outputs": [], - "source": [ - "interactions = pd.read_csv('../data/kion_train/interactions.csv')\n", - "\n", - "interactions.rename(columns={\n", - " 'last_watch_dt': Columns.Datetime,\n", - " 'total_dur': Columns.Weight\n", - " }, \n", - " inplace=True\n", - ") \n", - "\n", - "interactions['datetime'] = pd.to_datetime(interactions['datetime'])" - ] - }, - { - "cell_type": "markdown", - "id": "55fbfe8e", - "metadata": {}, - "source": [ - "# Create train data" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "57f1394b", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Unique users: 962179\n", - "Unique items: 15706\n" - ] - } - ], - "source": [ - "# формирование id для user и item\n", - "users_inv_mapping = dict(enumerate(interactions['user_id'].unique()))\n", - "users_mapping = {v: k for k, v in users_inv_mapping.items()}\n", - "items_inv_mapping = dict(enumerate(interactions['item_id'].unique()))\n", - "items_mapping = {v: k for k, v in items_inv_mapping.items()}\n", - "print(f\"Unique users: {len(users_inv_mapping)}\")\n", - "print(f\"Unique items: {len(items_inv_mapping)}\")" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "bc704533", - "metadata": {}, - "outputs": [], - "source": [ - "def get_matrix(\n", - " df: pd.DataFrame,\n", - " user_col: str = Columns.User,\n", - " item_col: str = Columns.Item,\n", - " weight_col: str = None,\n", - " users_mapping: tp.Dict[int, int] = None,\n", - " items_mapping: tp.Dict[int, int] = None\n", - "):\n", - "\n", - " if weight_col:\n", - " weights = df[weight_col].astype(np.float32)\n", - " else:\n", - " weights = np.ones(len(df), dtype=np.float32)\n", - "\n", - " interaction_matrix = sp.sparse.coo_matrix((\n", - " weights,\n", - " (\n", - " df[user_col].map(users_mapping.get),\n", - " df[item_col].map(items_mapping.get)\n", - " )\n", - " ))\n", - "\n", - " watched = df.groupby(user_col).agg({item_col: list})\n", - " return interaction_matrix" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "00f46a78", - "metadata": {}, - "outputs": [], - "source": [ - "weight_matrix = get_matrix(\n", - " df=interactions,\n", - " users_mapping=users_mapping,\n", - " items_mapping=items_mapping\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "1cc272f9", - "metadata": {}, - "source": [ - "# Models train" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "2d8ec8a1", - "metadata": {}, - "outputs": [], - "source": [ - "model_implicit_tfidf = TFIDFRecommender(K=60)\n", - "model_implicit_bmp25 = BM25Recommender(K=60)" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "17990623", - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "0ce1d4c0e2184dfa8d159906a145f011", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - " 0%| | 0/962179 [00:00\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
item_id
user_id
0[7102, 14359, 15297, 6006, 9728, 12192]
1[3669, 10440]
2[7571, 3541, 15266, 13867, 12841, 10770, 4475,...
3[12192, 9728, 16406, 15719, 10440, 3475, 2025,...
4[4700, 6317]
1097553[24, 13058, 12463, 12659]
1097554[16361, 496, 1053, 11275, 4580, 1151, 849, 350...
1097555[14703, 140, 9728, 496, 6916, 4662, 4880]
1097556[12812]
1097557[4151, 3182, 15297]
\n", - "" - ], - "text/plain": [ - " item_id\n", - "user_id \n", - "0 [7102, 14359, 15297, 6006, 9728, 12192]\n", - "1 [3669, 10440]\n", - "2 [7571, 3541, 15266, 13867, 12841, 10770, 4475,...\n", - "3 [12192, 9728, 16406, 15719, 10440, 3475, 2025,...\n", - "4 [4700, 6317]\n", - "1097553 [24, 13058, 12463, 12659]\n", - "1097554 [16361, 496, 1053, 11275, 4580, 1151, 849, 350...\n", - "1097555 [14703, 140, 9728, 496, 6916, 4662, 4880]\n", - "1097556 [12812]\n", - "1097557 [4151, 3182, 15297]" - ] - }, - "execution_count": 66, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "watched = interactions.groupby('user_id').agg({'item_id': list})\n", - "pd.concat([watched.head(), watched.tail()])" - ] - }, - { - "cell_type": "code", - "execution_count": 95, - "id": "1460393c", - "metadata": {}, - "outputs": [], - "source": [ - "def recs_mapper(user, model, user_mapping, user_inv_mapping, k_reco: int = 10, bmp: bool = False):\n", - " user_id = user_mapping[user]\n", - " recs = model.similar_items(user_id, N=k_reco)\n", - " result = pd.DataFrame(\n", - " {\n", - " \"sim_user_id\": [user_inv_mapping[user] for user, _ in recs], \n", - " \"sim\": [sim for _, sim in recs] def\n", - " }\n", - " )\n", - " \n", - " if bmp:\n", - " return result[result['sim_user_id'] != user]\n", - " else: \n", - " return result[~(result['sim'] >= 1)] " - ] - }, - { - "cell_type": "code", - "execution_count": 96, - "id": "011fe4fb", - "metadata": { - "scrolled": true - }, - "outputs": [], - "source": [ - "sample_users = interactions[Columns.User].sample(100).tolist()" - ] - }, - { - "cell_type": "code", - "execution_count": 99, - "id": "3ff09747", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "12861\n" - ] - }, - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
sim_user_idsim
17372390.479295
210695580.427898
39333710.419511
44098500.391727
59892530.384045
68176360.380609
710784200.372851
81635950.370077
910037830.368852
\n", - "
" - ], - "text/plain": [ - " sim_user_id sim\n", - "1 737239 0.479295\n", - "2 1069558 0.427898\n", - "3 933371 0.419511\n", - "4 409850 0.391727\n", - "5 989253 0.384045\n", - "6 817636 0.380609\n", - "7 1078420 0.372851\n", - "8 163595 0.370077\n", - "9 1003783 0.368852" - ] - }, - "execution_count": 99, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "print(sample_users[0])\n", - "df_sim = recs_mapper(sample_users[0], model_implicit_tfidf, users_mapping, users_inv_mapping)\n", - "df_sim" - ] - }, - { - "cell_type": "code", - "execution_count": 100, - "id": "757a24ec", - "metadata": { - "scrolled": true - }, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
sim_user_idsimitem_id
07372390.47929510755
07372390.479295496
07372390.47929512324
07372390.47929510219
07372390.4792956898
07372390.47929514476
07372390.47929513411
07372390.4792959194
07372390.4792956404
07372390.47929514961
07372390.47929512995
110695580.4278985287
110695580.42789813973
110695580.42789813865
34098500.3917277793
49892530.3840456033
49892530.384045799
49892530.3840459617
49892530.3840455405
49892530.38404513849
49892530.38404512846
58176360.3806092981
610784200.3728513935
610784200.37285110283
71635950.3700779728
\n", - "
" - ], - "text/plain": [ - " sim_user_id sim item_id\n", - "0 737239 0.479295 10755\n", - "0 737239 0.479295 496\n", - "0 737239 0.479295 12324\n", - "0 737239 0.479295 10219\n", - "0 737239 0.479295 6898\n", - "0 737239 0.479295 14476\n", - "0 737239 0.479295 13411\n", - "0 737239 0.479295 9194\n", - "0 737239 0.479295 6404\n", - "0 737239 0.479295 14961\n", - "0 737239 0.479295 12995\n", - "1 1069558 0.427898 5287\n", - "1 1069558 0.427898 13973\n", - "1 1069558 0.427898 13865\n", - "3 409850 0.391727 7793\n", - "4 989253 0.384045 6033\n", - "4 989253 0.384045 799\n", - "4 989253 0.384045 9617\n", - "4 989253 0.384045 5405\n", - "4 989253 0.384045 13849\n", - "4 989253 0.384045 12846\n", - "5 817636 0.380609 2981\n", - "6 1078420 0.372851 3935\n", - "6 1078420 0.372851 10283\n", - "7 163595 0.370077 9728" - ] - }, - "execution_count": 100, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df_sim = df_sim.merge(\n", - " watched, left_on=['sim_user_id'], right_on=['user_id'], how='left'\n", - ").explode('item_id').sort_values(\n", - " [ 'sim'], ascending=False\n", - ").drop_duplicates(\n", - " ['item_id'], keep='first'\n", - ")\n", - "df_sim" - ] - }, - { - "cell_type": "code", - "execution_count": 101, - "id": "87a9e994", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "12861\n" - ] - }, - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
sim_user_idsim
173723985.368217
2100621675.500227
393337171.779085
498925371.633147
5106955871.248461
612473569.342326
7107842067.839079
828985467.379224
940985066.214999
\n", - "
" - ], - "text/plain": [ - " sim_user_id sim\n", - "1 737239 85.368217\n", - "2 1006216 75.500227\n", - "3 933371 71.779085\n", - "4 989253 71.633147\n", - "5 1069558 71.248461\n", - "6 124735 69.342326\n", - "7 1078420 67.839079\n", - "8 289854 67.379224\n", - "9 409850 66.214999" - ] - }, - "execution_count": 101, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "print(sample_users[0])\n", - "df_sim = recs_mapper(sample_users[0], model_implicit_bmp25, users_mapping, users_inv_mapping, bmp=True)\n", - "df_sim" - ] - }, - { - "cell_type": "code", - "execution_count": 102, - "id": "2557f73f", - "metadata": { - "scrolled": true - }, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
sim_user_idsimitem_id
073723985.36821710755
073723985.36821714961
073723985.3682176404
073723985.3682179194
073723985.36821713411
073723985.368217496
073723985.3682176898
073723985.36821710219
073723985.36821714476
073723985.36821712324
073723985.36821712995
1100621675.50022713325
1100621675.5002278891
1100621675.5002275287
398925371.63314713865
398925371.633147799
398925371.6331476033
398925371.6331479617
398925371.63314713849
398925371.63314712846
398925371.6331475405
4106955871.24846113973
512473569.3423269288
512473569.3423262100
512473569.34232614242
512473569.3423264702
6107842067.8390793935
6107842067.83907910283
6107842067.8390792981
728985467.37922416021
728985467.3792244116
728985467.37922415464
840985066.2149997793
\n", - "
" - ], - "text/plain": [ - " sim_user_id sim item_id\n", - "0 737239 85.368217 10755\n", - "0 737239 85.368217 14961\n", - "0 737239 85.368217 6404\n", - "0 737239 85.368217 9194\n", - "0 737239 85.368217 13411\n", - "0 737239 85.368217 496\n", - "0 737239 85.368217 6898\n", - "0 737239 85.368217 10219\n", - "0 737239 85.368217 14476\n", - "0 737239 85.368217 12324\n", - "0 737239 85.368217 12995\n", - "1 1006216 75.500227 13325\n", - "1 1006216 75.500227 8891\n", - "1 1006216 75.500227 5287\n", - "3 989253 71.633147 13865\n", - "3 989253 71.633147 799\n", - "3 989253 71.633147 6033\n", - "3 989253 71.633147 9617\n", - "3 989253 71.633147 13849\n", - "3 989253 71.633147 12846\n", - "3 989253 71.633147 5405\n", - "4 1069558 71.248461 13973\n", - "5 124735 69.342326 9288\n", - "5 124735 69.342326 2100\n", - "5 124735 69.342326 14242\n", - "5 124735 69.342326 4702\n", - "6 1078420 67.839079 3935\n", - "6 1078420 67.839079 10283\n", - "6 1078420 67.839079 2981\n", - "7 289854 67.379224 16021\n", - "7 289854 67.379224 4116\n", - "7 289854 67.379224 15464\n", - "8 409850 66.214999 7793" - ] - }, - "execution_count": 102, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df_sim = df_sim.merge(\n", - " watched, left_on=['sim_user_id'], right_on=['user_id'], how='left'\n", - ").explode('item_id').sort_values(\n", - " [ 'sim'], ascending=False\n", - ").drop_duplicates(\n", - " ['item_id'], keep='first'\n", - ")\n", - "df_sim" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "7e60a9c9", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "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.8.10" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} From 5b7733e5f86c1737ba7f4ed7afe9b016181939e3 Mon Sep 17 00:00:00 2001 From: anettapik <120940816+anettapik@users.noreply.github.com> Date: Wed, 13 Dec 2023 13:00:13 +0300 Subject: [PATCH 6/7] Add files via upload MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hw_5_dssm.ipynb - ноутбук с обучением DSSM модели, добавлены текстовые фичи, доработан генератор с учетом просмотренного контента hw_5_recbole.ipynb - обучено несколько моделей разных архитектур с использрванием библиотеки recbole hw_5_autoencoder.ipynb - обучен автоэнкодер, подобрана архитектура и параметры --- hw_5_autoencoder.ipynb | 1922 +++++++++++++++++++ hw_5_dssm.ipynb | 4034 ++++++++++++++++++++++++++++++++++++++++ hw_5_recbool.ipynb | 1 + 3 files changed, 5957 insertions(+) create mode 100644 hw_5_autoencoder.ipynb create mode 100644 hw_5_dssm.ipynb create mode 100644 hw_5_recbool.ipynb diff --git a/hw_5_autoencoder.ipynb b/hw_5_autoencoder.ipynb new file mode 100644 index 00000000..750a793d --- /dev/null +++ b/hw_5_autoencoder.ipynb @@ -0,0 +1,1922 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "7_8DlX_2jZzT", + "metadata": { + "execution": { + "iopub.execute_input": "2023-01-22T12:32:12.524590Z", + "iopub.status.busy": "2023-01-22T12:32:12.523513Z", + "iopub.status.idle": "2023-01-22T12:32:12.529931Z", + "shell.execute_reply": "2023-01-22T12:32:12.528298Z", + "shell.execute_reply.started": "2023-01-22T12:32:12.524533Z" + }, + "id": "7_8DlX_2jZzT" + }, + "outputs": [], + "source": [ + "import numpy as np\n", + "import os\n", + "import pandas as pd" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "IczRXBXHjZzV", + "metadata": { + "execution": { + "iopub.execute_input": "2023-01-22T12:32:13.867299Z", + "iopub.status.busy": "2023-01-22T12:32:13.866000Z", + "iopub.status.idle": "2023-01-22T12:32:16.353124Z", + "shell.execute_reply": "2023-01-22T12:32:16.352004Z", + "shell.execute_reply.started": "2023-01-22T12:32:13.867251Z" + }, + "id": "IczRXBXHjZzV" + }, + "outputs": [], + "source": [ + "from IPython.display import display, clear_output\n", + "\n", + "import numpy as np\n", + "import pandas as pd\n", + "from tqdm.notebook import tqdm\n", + "from sklearn.model_selection import train_test_split\n", + "from sklearn.preprocessing import StandardScaler\n", + "\n", + "import torch\n", + "from torch import nn\n", + "from torch.nn import functional as F\n", + "from torch.utils.data import Dataset, DataLoader" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "mA1MfXOnjZzW", + "metadata": { + "execution": { + "iopub.execute_input": "2023-01-22T12:41:13.399626Z", + "iopub.status.busy": "2023-01-22T12:41:13.398452Z", + "iopub.status.idle": "2023-01-22T12:41:19.723408Z", + "shell.execute_reply": "2023-01-22T12:41:19.722114Z", + "shell.execute_reply.started": "2023-01-22T12:41:13.399496Z" + }, + "id": "mA1MfXOnjZzW" + }, + "outputs": [], + "source": [ + "interactions_df = pd.read_csv('interactions_processed_kion.csv')\n", + "users_df = pd.read_csv('users_processed_kion.csv')\n", + "items_df = pd.read_csv('items_processed_kion.csv')" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "G5cP9QcUjZzW", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 204 + }, + "execution": { + "iopub.execute_input": "2023-01-22T12:41:19.726341Z", + "iopub.status.busy": "2023-01-22T12:41:19.725645Z", + "iopub.status.idle": "2023-01-22T12:41:19.751544Z", + "shell.execute_reply": "2023-01-22T12:41:19.750286Z", + "shell.execute_reply.started": "2023-01-22T12:41:19.726296Z" + }, + "id": "G5cP9QcUjZzW", + "outputId": "9fc311f0-6f5b-4327-9bbc-1f6bdee3f918" + }, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
user_iditem_idlast_watch_dttotal_durwatched_pct
017654995062021-05-11425072
169931716592021-05-298317100
265668371072021-05-09100
386461376382021-07-0514483100
496486895062021-04-306725100
\n", + "
\n", + " \n", + " \n", + " \n", + "\n", + " \n", + "
\n", + "
\n", + " " + ], + "text/plain": [ + " user_id item_id last_watch_dt total_dur watched_pct\n", + "0 176549 9506 2021-05-11 4250 72\n", + "1 699317 1659 2021-05-29 8317 100\n", + "2 656683 7107 2021-05-09 10 0\n", + "3 864613 7638 2021-07-05 14483 100\n", + "4 964868 9506 2021-04-30 6725 100" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "interactions_df.head()" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "b4omWvMOjZzX", + "metadata": { + "execution": { + "iopub.execute_input": "2023-01-22T12:41:21.721270Z", + "iopub.status.busy": "2023-01-22T12:41:21.720745Z", + "iopub.status.idle": "2023-01-22T12:41:22.116852Z", + "shell.execute_reply": "2023-01-22T12:41:22.115397Z", + "shell.execute_reply.started": "2023-01-22T12:41:21.721229Z" + }, + "id": "b4omWvMOjZzX" + }, + "outputs": [], + "source": [ + "interactions_df = interactions_df[interactions_df['last_watch_dt'] < '2021-04-01']" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "JAuH-fG0jZzX", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "execution": { + "iopub.execute_input": "2023-01-22T12:41:23.195240Z", + "iopub.status.busy": "2023-01-22T12:41:23.194661Z", + "iopub.status.idle": "2023-01-22T12:41:23.202760Z", + "shell.execute_reply": "2023-01-22T12:41:23.201745Z", + "shell.execute_reply.started": "2023-01-22T12:41:23.195188Z" + }, + "id": "JAuH-fG0jZzX", + "outputId": "3e259108-40e9-48fc-c212-1bbd7e9e21a1" + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(263874, 5)" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "interactions_df.shape" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "rWCoSNwWjZzX", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "execution": { + "iopub.execute_input": "2023-01-22T12:41:25.368988Z", + "iopub.status.busy": "2023-01-22T12:41:25.367925Z", + "iopub.status.idle": "2023-01-22T12:41:25.558751Z", + "shell.execute_reply": "2023-01-22T12:41:25.557372Z", + "shell.execute_reply.started": "2023-01-22T12:41:25.368937Z" + }, + "id": "rWCoSNwWjZzX", + "outputId": "c7738105-161d-4c43-9028-21b64809ad04" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "# users: 86614\n", + "# users with at least 5 interactions: 14563\n" + ] + } + ], + "source": [ + "users_interactions_count_df = interactions_df.groupby(['user_id', 'item_id']).size().groupby('user_id').size()\n", + "print('# users: %d' % len(users_interactions_count_df))\n", + "users_with_enough_interactions_df = users_interactions_count_df[users_interactions_count_df >= 5].reset_index()[['user_id']]\n", + "print('# users with at least 5 interactions: %d' % len(users_with_enough_interactions_df))" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "qDCcr1_UjZzY", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "execution": { + "iopub.execute_input": "2023-01-22T12:41:27.227318Z", + "iopub.status.busy": "2023-01-22T12:41:27.226717Z", + "iopub.status.idle": "2023-01-22T12:41:27.326827Z", + "shell.execute_reply": "2023-01-22T12:41:27.325761Z", + "shell.execute_reply.started": "2023-01-22T12:41:27.227269Z" + }, + "id": "qDCcr1_UjZzY", + "outputId": "cc44175d-eef0-42b9-839a-4c1a0efa5ce4" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "# of interactions: 263874\n", + "# of interactions from users with at least 5 interactions: 142670\n" + ] + } + ], + "source": [ + "print('# of interactions: %d' % len(interactions_df))\n", + "interactions_from_selected_users_df = interactions_df.merge(users_with_enough_interactions_df, \n", + " how = 'right',\n", + " left_on = 'user_id',\n", + " right_on = 'user_id')\n", + "print('# of interactions from users with at least 5 interactions: %d' % len(interactions_from_selected_users_df))" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "bs9IdB8fjZzY", + "metadata": { + "execution": { + "iopub.execute_input": "2023-01-22T12:41:30.431311Z", + "iopub.status.busy": "2023-01-22T12:41:30.430823Z", + "iopub.status.idle": "2023-01-22T12:41:30.436607Z", + "shell.execute_reply": "2023-01-22T12:41:30.435654Z", + "shell.execute_reply.started": "2023-01-22T12:41:30.431275Z" + }, + "id": "bs9IdB8fjZzY" + }, + "outputs": [], + "source": [ + "import math" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "MTW_Y4iOjZzY", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 376 + }, + "execution": { + "iopub.execute_input": "2023-01-22T12:41:32.237281Z", + "iopub.status.busy": "2023-01-22T12:41:32.236079Z", + "iopub.status.idle": "2023-01-22T12:41:32.403346Z", + "shell.execute_reply": "2023-01-22T12:41:32.401909Z", + "shell.execute_reply.started": "2023-01-22T12:41:32.237217Z" + }, + "id": "MTW_Y4iOjZzY", + "outputId": "8027a8a8-0bf9-4c97-9b69-5281653709c9" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "# of unique user/item interactions: 142670\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
user_iditem_idwatched_pct
0218496.375039
12143456.658211
221102836.658211
321122616.658211
421159976.658211
5329526.044394
63243824.954196
73248076.658211
832104366.658211
932121326.658211
\n", + "
\n", + " \n", + " \n", + " \n", + "\n", + " \n", + "
\n", + "
\n", + " " + ], + "text/plain": [ + " user_id item_id watched_pct\n", + "0 21 849 6.375039\n", + "1 21 4345 6.658211\n", + "2 21 10283 6.658211\n", + "3 21 12261 6.658211\n", + "4 21 15997 6.658211\n", + "5 32 952 6.044394\n", + "6 32 4382 4.954196\n", + "7 32 4807 6.658211\n", + "8 32 10436 6.658211\n", + "9 32 12132 6.658211" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def smooth_user_preference(x):\n", + " return math.log(1+x, 2)\n", + " \n", + "interactions_full_df = interactions_from_selected_users_df \\\n", + " .groupby(['user_id', 'item_id'])['watched_pct'].sum() \\\n", + " .apply(smooth_user_preference).reset_index()\n", + "print('# of unique user/item interactions: %d' % len(interactions_full_df))\n", + "interactions_full_df.head(10)" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "wNyqdsCxjZzZ", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "execution": { + "iopub.execute_input": "2023-01-22T12:41:34.443808Z", + "iopub.status.busy": "2023-01-22T12:41:34.443346Z", + "iopub.status.idle": "2023-01-22T12:41:34.651267Z", + "shell.execute_reply": "2023-01-22T12:41:34.650080Z", + "shell.execute_reply.started": "2023-01-22T12:41:34.443774Z" + }, + "id": "wNyqdsCxjZzZ", + "outputId": "e2a2e169-78ef-4f8e-c099-eb56de49338e" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "# interactions on Train set: 114136\n", + "# interactions on Test set: 28534\n" + ] + } + ], + "source": [ + "interactions_train_df, interactions_test_df = train_test_split(interactions_full_df,\n", + " stratify=interactions_full_df['user_id'], \n", + " test_size=0.20,\n", + " random_state=42)\n", + "\n", + "print('# interactions on Train set: %d' % len(interactions_train_df))\n", + "print('# interactions on Test set: %d' % len(interactions_test_df))" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "v1M9fBagjZzZ", + "metadata": { + "execution": { + "iopub.execute_input": "2023-01-22T12:41:38.570246Z", + "iopub.status.busy": "2023-01-22T12:41:38.568905Z", + "iopub.status.idle": "2023-01-22T12:41:38.583223Z", + "shell.execute_reply": "2023-01-22T12:41:38.581705Z", + "shell.execute_reply.started": "2023-01-22T12:41:38.570182Z" + }, + "id": "v1M9fBagjZzZ" + }, + "outputs": [], + "source": [ + "#Indexing by personId to speed up the searches during evaluation\n", + "interactions_full_indexed_df = interactions_full_df.set_index('user_id')\n", + "interactions_train_indexed_df = interactions_train_df.set_index('user_id')\n", + "interactions_test_indexed_df = interactions_test_df.set_index('user_id')" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "Ra2TntFUjZzZ", + "metadata": { + "execution": { + "iopub.execute_input": "2023-01-22T12:41:42.934656Z", + "iopub.status.busy": "2023-01-22T12:41:42.934139Z", + "iopub.status.idle": "2023-01-22T12:41:42.940917Z", + "shell.execute_reply": "2023-01-22T12:41:42.939611Z", + "shell.execute_reply.started": "2023-01-22T12:41:42.934617Z" + }, + "id": "Ra2TntFUjZzZ" + }, + "outputs": [], + "source": [ + "def get_items_interacted(person_id, interactions_df):\n", + " # Get the user's data and merge in the movie information.\n", + " interacted_items = interactions_df.loc[person_id]['item_id']\n", + " return set(interacted_items if type(interacted_items) == pd.Series else [interacted_items])" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "xpP7YjhRjZzZ", + "metadata": { + "execution": { + "iopub.execute_input": "2023-01-22T12:41:53.435832Z", + "iopub.status.busy": "2023-01-22T12:41:53.435366Z", + "iopub.status.idle": "2023-01-22T12:41:53.455616Z", + "shell.execute_reply": "2023-01-22T12:41:53.454525Z", + "shell.execute_reply.started": "2023-01-22T12:41:53.435796Z" + }, + "id": "xpP7YjhRjZzZ" + }, + "outputs": [], + "source": [ + "#Top-N accuracy metrics consts\n", + "EVAL_RANDOM_SAMPLE_NON_INTERACTED_ITEMS = 100\n", + "\n", + "class ModelEvaluator:\n", + "\n", + " def get_not_interacted_items_sample(self, person_id, sample_size, seed=42):\n", + " interacted_items = get_items_interacted(person_id, interactions_full_indexed_df)\n", + " all_items = set(articles_df['item_id'])\n", + " non_interacted_items = all_items - interacted_items\n", + "\n", + " random.seed(seed)\n", + " non_interacted_items_sample = random.sample(non_interacted_items, sample_size)\n", + " return set(non_interacted_items_sample)\n", + "\n", + " def _verify_hit_top_n(self, item_id, recommended_items, topn): \n", + " try:\n", + " index = next(i for i, c in enumerate(recommended_items) if c == item_id)\n", + " except:\n", + " index = -1\n", + " hit = int(index in range(0, topn))\n", + " return hit, index\n", + "\n", + " def evaluate_model_for_user(self, model, person_id):\n", + " #Getting the items in test set\n", + " interacted_values_testset = interactions_test_indexed_df.loc[person_id]\n", + " if type(interacted_values_testset['item_id']) == pd.Series:\n", + " person_interacted_items_testset = set(interacted_values_testset['item_id'])\n", + " else:\n", + " person_interacted_items_testset = set([int(interacted_values_testset['item_id'])]) \n", + " interacted_items_count_testset = len(person_interacted_items_testset) \n", + "\n", + " #Getting a ranked recommendation list from a model for a given user\n", + " person_recs_df = model.recommend_items(person_id, \n", + " items_to_ignore=get_items_interacted(person_id, \n", + " interactions_train_indexed_df), \n", + " topn=10000000000)\n", + "\n", + " hits_at_5_count = 0\n", + " hits_at_10_count = 0\n", + " #For each item the user has interacted in test set\n", + " for item_id in person_interacted_items_testset:\n", + " #Getting a random sample (100) items the user has not interacted \n", + " #(to represent items that are assumed to be no relevant to the user)\n", + " non_interacted_items_sample = self.get_not_interacted_items_sample(person_id, \n", + " sample_size=EVAL_RANDOM_SAMPLE_NON_INTERACTED_ITEMS, \n", + " seed=item_id%(2**32))\n", + "\n", + " #Combining the current interacted item with the 100 random items\n", + " items_to_filter_recs = non_interacted_items_sample.union(set([item_id]))\n", + "\n", + " #Filtering only recommendations that are either the interacted item or from a random sample of 100 non-interacted items\n", + " valid_recs_df = person_recs_df[person_recs_df['item_id'].isin(items_to_filter_recs)] \n", + " valid_recs = valid_recs_df['item_id'].values\n", + " #Verifying if the current interacted item is among the Top-N recommended items\n", + " hit_at_5, index_at_5 = self._verify_hit_top_n(item_id, valid_recs, 5)\n", + " hits_at_5_count += hit_at_5\n", + " hit_at_10, index_at_10 = self._verify_hit_top_n(item_id, valid_recs, 10)\n", + " hits_at_10_count += hit_at_10\n", + "\n", + " #Recall is the rate of the interacted items that are ranked among the Top-N recommended items, \n", + " #when mixed with a set of non-relevant items\n", + " recall_at_5 = hits_at_5_count / float(interacted_items_count_testset)\n", + " recall_at_10 = hits_at_10_count / float(interacted_items_count_testset)\n", + "\n", + " person_metrics = {'hits@5_count':hits_at_5_count, \n", + " 'hits@10_count':hits_at_10_count, \n", + " 'interacted_count': interacted_items_count_testset,\n", + " 'recall@5': recall_at_5,\n", + " 'recall@10': recall_at_10}\n", + " return person_metrics\n", + "\n", + " def evaluate_model(self, model):\n", + " #print('Running evaluation for users')\n", + " people_metrics = []\n", + " for idx, person_id in enumerate(tqdm(list(interactions_test_indexed_df.index.unique().values))):\n", + " #if idx % 100 == 0 and idx > 0:\n", + " # print('%d users processed' % idx)\n", + " person_metrics = self.evaluate_model_for_user(model, person_id) \n", + " person_metrics['user_id'] = person_id\n", + " people_metrics.append(person_metrics)\n", + " print('%d users processed' % idx)\n", + "\n", + " detailed_results_df = pd.DataFrame(people_metrics) \\\n", + " .sort_values('interacted_count', ascending=False)\n", + " \n", + " global_recall_at_5 = detailed_results_df['hits@5_count'].sum() / float(detailed_results_df['interacted_count'].sum())\n", + " global_recall_at_10 = detailed_results_df['hits@10_count'].sum() / float(detailed_results_df['interacted_count'].sum())\n", + " \n", + " global_metrics = {'modelName': model.get_model_name(),\n", + " 'recall@5': global_recall_at_5,\n", + " 'recall@10': global_recall_at_10} \n", + " return global_metrics, detailed_results_df\n", + " \n", + "model_evaluator = ModelEvaluator() " + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "bt-Ko_HMjZza", + "metadata": { + "execution": { + "iopub.execute_input": "2023-01-22T12:41:57.779034Z", + "iopub.status.busy": "2023-01-22T12:41:57.777417Z", + "iopub.status.idle": "2023-01-22T12:41:57.787389Z", + "shell.execute_reply": "2023-01-22T12:41:57.785909Z", + "shell.execute_reply.started": "2023-01-22T12:41:57.778960Z" + }, + "id": "bt-Ko_HMjZza" + }, + "outputs": [], + "source": [ + "from IPython.display import display, clear_output\n", + "\n", + "import numpy as np\n", + "import pandas as pd\n", + "from tqdm.notebook import tqdm\n", + "from sklearn.model_selection import train_test_split\n", + "from sklearn.preprocessing import StandardScaler\n", + "\n", + "import torch\n", + "from torch import nn\n", + "from torch.nn import functional as F\n", + "from torch.utils.data import Dataset, DataLoader" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "6ySqiCo5jZza", + "metadata": { + "execution": { + "iopub.execute_input": "2023-01-22T12:42:03.271305Z", + "iopub.status.busy": "2023-01-22T12:42:03.270810Z", + "iopub.status.idle": "2023-01-22T12:42:03.278535Z", + "shell.execute_reply": "2023-01-22T12:42:03.277141Z", + "shell.execute_reply.started": "2023-01-22T12:42:03.271268Z" + }, + "id": "6ySqiCo5jZza" + }, + "outputs": [], + "source": [ + "\n", + "# Constants\n", + "SEED = 42 # random seed for reproducibility\n", + "LR = 1e-3 # learning rate, controls the speed of the training\n", + "WEIGHT_DECAY = 0.01 # lambda for L2 reg. ()\n", + "NUM_EPOCHS = 200 # num training epochs (how many times each instance will be processed)\n", + "GAMMA = 0.9995 # learning rate scheduler parameter\n", + "BATCH_SIZE = 3000 # training batch size\n", + "EVAL_BATCH_SIZE = 3000 # evaluation batch size.\n", + "DEVICE = 'cuda' #'cuda' # device to make the calculations on" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "FtzzvibljZza", + "metadata": { + "execution": { + "iopub.execute_input": "2023-01-22T12:42:05.933060Z", + "iopub.status.busy": "2023-01-22T12:42:05.931911Z", + "iopub.status.idle": "2023-01-22T12:42:05.969002Z", + "shell.execute_reply": "2023-01-22T12:42:05.967458Z", + "shell.execute_reply.started": "2023-01-22T12:42:05.933000Z" + }, + "id": "FtzzvibljZza" + }, + "outputs": [], + "source": [ + "total_df = interactions_train_df.append(interactions_test_indexed_df.reset_index())\n", + "total_df['user_id'], users_keys = total_df.user_id.factorize()\n", + "total_df['item_id'], items_keys = total_df.item_id.factorize()\n", + "\n", + "train_encoded = total_df.iloc[:len(interactions_train_df)].values\n", + "test_encoded = total_df.iloc[len(interactions_train_df):].values" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "crbEdHiJjZza", + "metadata": { + "execution": { + "iopub.execute_input": "2023-01-22T12:42:09.354000Z", + "iopub.status.busy": "2023-01-22T12:42:09.352465Z", + "iopub.status.idle": "2023-01-22T12:42:09.967185Z", + "shell.execute_reply": "2023-01-22T12:42:09.965725Z", + "shell.execute_reply.started": "2023-01-22T12:42:09.353932Z" + }, + "id": "crbEdHiJjZza" + }, + "outputs": [], + "source": [ + "from scipy.sparse import csr_matrix\n", + "shape = [int(total_df['user_id'].max()+1), int(total_df['item_id'].max()+1)]\n", + "X_train = csr_matrix((train_encoded[:, 2], (train_encoded[:, 0], train_encoded[:, 1])), shape=shape).toarray()\n", + "X_test = csr_matrix((test_encoded[:, 2], (test_encoded[:, 0], test_encoded[:, 1])), shape=shape).toarray()" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "sFeJZsDJjZzb", + "metadata": { + "execution": { + "iopub.execute_input": "2023-01-22T12:42:12.745785Z", + "iopub.status.busy": "2023-01-22T12:42:12.745283Z", + "iopub.status.idle": "2023-01-22T12:42:12.754320Z", + "shell.execute_reply": "2023-01-22T12:42:12.752855Z", + "shell.execute_reply.started": "2023-01-22T12:42:12.745745Z" + }, + "id": "sFeJZsDJjZzb" + }, + "outputs": [], + "source": [ + "# Initialize the DataObject, which must return an element (features vector x and target value y)\n", + "# for a given idx. This class must also have a length atribute\n", + "class UserOrientedDataset(Dataset):\n", + " def __init__(self, X):\n", + " super().__init__() # to initialize the parent class\n", + " self.X = X.astype(np.float32)\n", + " self.len = len(X)\n", + "\n", + " def __len__(self): # We use __func__ for implementing in-built python functions\n", + " return self.len\n", + "\n", + " def __getitem__(self, index):\n", + " return self.X[index]" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "AoCCUSpUjZzb", + "metadata": { + "execution": { + "iopub.execute_input": "2023-01-22T12:42:16.254953Z", + "iopub.status.busy": "2023-01-22T12:42:16.254416Z", + "iopub.status.idle": "2023-01-22T12:42:17.434704Z", + "shell.execute_reply": "2023-01-22T12:42:17.433103Z", + "shell.execute_reply.started": "2023-01-22T12:42:16.254903Z" + }, + "id": "AoCCUSpUjZzb" + }, + "outputs": [], + "source": [ + "# Initialize DataLoaders - objects, which sample instances from DataObject-s\n", + "train_dl = DataLoader(\n", + " UserOrientedDataset(X_train),\n", + " batch_size = BATCH_SIZE,\n", + " shuffle = True\n", + ")\n", + "\n", + "test_dl = DataLoader(\n", + " UserOrientedDataset(X_test),\n", + " batch_size = EVAL_BATCH_SIZE,\n", + " shuffle = False\n", + ")\n", + "\n", + "dls = {'train': train_dl, 'test': test_dl}" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "b94CXGocjZzb", + "metadata": { + "execution": { + "iopub.execute_input": "2023-01-22T12:53:12.965059Z", + "iopub.status.busy": "2023-01-22T12:53:12.964527Z", + "iopub.status.idle": "2023-01-22T12:53:12.975037Z", + "shell.execute_reply": "2023-01-22T12:53:12.973690Z", + "shell.execute_reply.started": "2023-01-22T12:53:12.965016Z" + }, + "id": "b94CXGocjZzb" + }, + "outputs": [], + "source": [ + "class Model(nn.Module):\n", + " def __init__(self, in_and_out_features = 8287):\n", + " super().__init__()\n", + " self.in_and_out_features = in_and_out_features\n", + " self.hidden_size = 500\n", + "\n", + " self.sequential = nn.Sequential( \n", + " nn.Linear(in_and_out_features, self.hidden_size), \n", + " nn.ReLU(), \n", + " nn.Linear(self.hidden_size, in_and_out_features) # Another Linear transformation\n", + " )\n", + "\n", + " def forward(self, x): # In the forward function, you define how your model runs, from input to output \n", + " x = self.sequential(x)\n", + " return x" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "aY_vqVZLjZzb", + "metadata": { + "execution": { + "iopub.execute_input": "2023-01-22T12:54:25.315144Z", + "iopub.status.busy": "2023-01-22T12:54:25.314623Z", + "iopub.status.idle": "2023-01-22T12:54:26.136714Z", + "shell.execute_reply": "2023-01-22T12:54:26.135715Z", + "shell.execute_reply.started": "2023-01-22T12:54:25.315101Z" + }, + "id": "aY_vqVZLjZzb" + }, + "outputs": [], + "source": [ + "torch.manual_seed(SEED) # Fix random seed to have reproducible weights of model layers\n", + "\n", + "model = Model()\n", + "model.to(DEVICE)\n", + "\n", + "# Initialize GD method, which will update the weights of the model\n", + "optimizer = torch.optim.AdamW(model.parameters(), lr=LR, weight_decay=WEIGHT_DECAY)\n", + "# Initialize learning rate scheduler, which will decrease LR according to some rule\n", + "scheduler = torch.optim.lr_scheduler.ExponentialLR(optimizer, gamma=GAMMA)\n", + "\n", + "def rmse_for_sparse(x_pred, x_true):\n", + " mask = (x_true > 0)\n", + " sq_diff = (x_pred * mask - x_true) ** 2\n", + " mse = sq_diff.sum() / mask.sum()\n", + " return mse ** (1/2)" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "LdlKerxfjZzb", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 419 + }, + "execution": { + "iopub.execute_input": "2023-01-22T12:54:33.544338Z", + "iopub.status.busy": "2023-01-22T12:54:33.543734Z" + }, + "id": "LdlKerxfjZzb", + "outputId": "0bc103bb-151d-449f-b7b2-f670b8970d92" + }, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
EpochTrain RMSETest RMSE
002.3150152.295504
112.1916362.224912
221.9554972.108439
331.8361192.027701
441.7367832.026640
............
1951950.2886581.330020
1961960.2779171.331115
1971970.3070821.330125
1981980.3029801.331673
1991990.3073371.329725
\n", + "

200 rows × 3 columns

\n", + "
\n", + " \n", + " \n", + " \n", + "\n", + " \n", + "
\n", + "
\n", + " " + ], + "text/plain": [ + " Epoch Train RMSE Test RMSE\n", + "0 0 2.315015 2.295504\n", + "1 1 2.191636 2.224912\n", + "2 2 1.955497 2.108439\n", + "3 3 1.836119 2.027701\n", + "4 4 1.736783 2.026640\n", + ".. ... ... ...\n", + "195 195 0.288658 1.330020\n", + "196 196 0.277917 1.331115\n", + "197 197 0.307082 1.330125\n", + "198 198 0.302980 1.331673\n", + "199 199 0.307337 1.329725\n", + "\n", + "[200 rows x 3 columns]" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Training loop\n", + "metrics_dict = {\n", + " \"Epoch\": [],\n", + " \"Train RMSE\": [],\n", + " \"Test RMSE\": [],\n", + "}\n", + "\n", + "# Train loop\n", + "for epoch in range(NUM_EPOCHS):\n", + " metrics_dict[\"Epoch\"].append(epoch)\n", + " for stage in ['train', 'test']:\n", + " with torch.set_grad_enabled(stage == 'train'): # Whether to start building a graph for a backward pass\n", + " if stage == 'train':\n", + " model.train() # Enable some \"special\" layers (will speak about later)\n", + " else:\n", + " model.eval() # Disable some \"special\" layers (will speak about later)\n", + "\n", + " loss_at_stage = 0 \n", + " for batch in dls[stage]:\n", + " batch = batch.to(DEVICE)\n", + " x_pred = model(batch) # forward pass: model(x_batch) -> calls forward()\n", + " loss = rmse_for_sparse(x_pred, batch) # ¡Important! y_pred is always the first arg\n", + " if stage == \"train\":\n", + " loss.backward() # Calculate the gradients of all the parameters wrt loss\n", + " optimizer.step() # Update the parameters\n", + " scheduler.step()\n", + " optimizer.zero_grad() # Zero the saved gradient\n", + " loss_at_stage += loss.item() * len(batch)\n", + " rmse_at_stage = (loss_at_stage / len(dls[stage].dataset)) ** (1/2)\n", + " metrics_dict[f\"{stage.title()} RMSE\"].append(rmse_at_stage)\n", + " \n", + " if (epoch == NUM_EPOCHS - 1) or epoch % 10 == 9:\n", + " clear_output(wait=True)\n", + " display(pd.DataFrame(metrics_dict))" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "ZXCPjyMajZzb", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "ZXCPjyMajZzb", + "outputId": "a0448c4f-5e53-409b-c277-fc704e617202" + }, + "outputs": [ + { + "data": { + "text/plain": [ + "tensor([[ 0.3084, 2.5601, 1.0144, ..., -0.0948, -0.1467, 0.3106],\n", + " [ 0.1575, 0.8934, 0.1315, ..., -0.1049, 0.0096, 0.0350],\n", + " [ 0.6704, 1.5142, 0.6962, ..., -0.2259, -0.0353, 0.0676],\n", + " ...,\n", + " [ 0.3153, 1.1243, 0.1393, ..., -0.1222, -0.1398, 0.0617],\n", + " [ 0.3214, 1.9313, 0.3253, ..., -0.1548, -0.0918, -0.0392],\n", + " [ 0.3434, 0.9318, -0.0341, ..., -0.1714, -0.0446, 0.1267]],\n", + " device='cuda:0')" + ] + }, + "execution_count": 24, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "with torch.no_grad():\n", + " X_pred = model(torch.Tensor(X_test).to(DEVICE))\n", + "X_pred" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "bkSfO9fgjZzc", + "metadata": { + "id": "bkSfO9fgjZzc" + }, + "outputs": [], + "source": [ + "class AERecommender:\n", + " \n", + " MODEL_NAME = 'Autoencoder'\n", + " \n", + " def __init__(self, X_preds, X_train_and_val, X_test):\n", + "\n", + " self.X_preds = X_preds.cpu().detach().numpy()\n", + " self.X_train_and_val = X_train_and_val\n", + " self.X_test = X_test\n", + " \n", + " def get_model_name(self):\n", + " return self.MODEL_NAME\n", + " \n", + " def recommend_items(self, user_id, items_to_select_idx, topn=10, verbose=False):\n", + " user_preds = self.X_preds[user_id][items_to_select_idx]\n", + " items_idx = items_to_select_idx[np.argsort(-user_preds)[:topn]]\n", + "\n", + " # Recommend the highest predicted rating movies that the user hasn't seen yet.\n", + " return items_idx\n", + "\n", + " def evaluate(self, size=100):\n", + "\n", + " X_total = self.X_train_and_val + self.X_test\n", + "\n", + " true_5 = []\n", + " true_10 = []\n", + "\n", + " for user_id in range(len(X_test)):\n", + " non_zero = np.argwhere(self.X_test[user_id] > 0).ravel()\n", + " all_nonzero = np.argwhere(X_total[user_id] > 0).ravel()\n", + " select_from = np.setdiff1d(np.arange(X_total.shape[1]), all_nonzero)\n", + "\n", + " for non_zero_idx in non_zero:\n", + " random_non_interacted_100_items = np.random.choice(select_from, size=20, replace=False)\n", + " preds = self.recommend_items(user_id, np.append(random_non_interacted_100_items, non_zero_idx), topn=10)\n", + " true_5.append(non_zero_idx in preds[:5])\n", + " true_10.append(non_zero_idx in preds)\n", + "\n", + " return {\"recall@5\": np.mean(true_5), \"recall@10\": np.mean(true_10)}\n", + " \n", + "ae_recommender_model = AERecommender(X_pred, X_train, X_train)" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "yRBbD9xmjZzc", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "yRBbD9xmjZzc", + "outputId": "d407d2b7-ee44-4299-9b29-046f41deb396" + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{'recall@5': 0.08641891035330142, 'recall@10': 0.25274264483602643}" + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ae_global_metrics = ae_recommender_model.evaluate()\n", + "ae_global_metrics" + ] + }, + { + "cell_type": "markdown", + "id": "ydc-4MJn-KFM", + "metadata": { + "id": "ydc-4MJn-KFM" + }, + "source": [ + "Проведем эксперименты с моделями и гиперпараметрами" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "GZfxQH7Z-hMK", + "metadata": { + "id": "GZfxQH7Z-hMK" + }, + "outputs": [], + "source": [ + "def train_model():\n", + " torch.manual_seed(SEED) # Fix random seed to have reproducible weights of model layers\n", + "\n", + " model = Model()\n", + " model.to(DEVICE)\n", + "\n", + " # Initialize GD method, which will update the weights of the model\n", + " optimizer = torch.optim.AdamW(model.parameters(), lr=LR, weight_decay=WEIGHT_DECAY)\n", + " # Initialize learning rate scheduler, which will decrease LR according to some rule\n", + " scheduler = torch.optim.lr_scheduler.ExponentialLR(optimizer, gamma=GAMMA)\n", + "\n", + "\n", + " # Training loop\n", + " metrics_dict = {\n", + " \"Epoch\": [],\n", + " \"Train RMSE\": [],\n", + " \"Test RMSE\": [],\n", + " }\n", + "\n", + " # Train loop\n", + " for epoch in range(NUM_EPOCHS):\n", + " metrics_dict[\"Epoch\"].append(epoch)\n", + " for stage in ['train', 'test']:\n", + " with torch.set_grad_enabled(stage == 'train'): # Whether to start building a graph for a backward pass\n", + " if stage == 'train':\n", + " model.train() # Enable some \"special\" layers (will speak about later)\n", + " else:\n", + " model.eval() # Disable some \"special\" layers (will speak about later)\n", + "\n", + " loss_at_stage = 0 \n", + " for batch in dls[stage]:\n", + " batch = batch.to(DEVICE)\n", + " x_pred = model(batch) # forward pass: model(x_batch) -> calls forward()\n", + " loss = rmse_for_sparse(x_pred, batch) # ¡Important! y_pred is always the first arg\n", + " if stage == \"train\":\n", + " loss.backward() # Calculate the gradients of all the parameters wrt loss\n", + " optimizer.step() # Update the parameters\n", + " scheduler.step()\n", + " optimizer.zero_grad() # Zero the saved gradient\n", + " loss_at_stage += loss.item() * len(batch)\n", + " rmse_at_stage = (loss_at_stage / len(dls[stage].dataset)) ** (1/2)\n", + " metrics_dict[f\"{stage.title()} RMSE\"].append(rmse_at_stage)\n", + " \n", + " with torch.no_grad():\n", + " X_pred = model(torch.Tensor(X_test).to(DEVICE))\n", + "\n", + " ae_recommender_model = AERecommender(X_pred, X_train, X_train)\n", + "\n", + " ae_global_metrics = ae_recommender_model.evaluate()\n", + "\n", + " metrics_dict[\"recall@5\"] = ae_global_metrics[\"recall@5\"]\n", + " metrics_dict[\"recall@10\"] = ae_global_metrics[\"recall@10\"]\n", + "\n", + "\n", + " return metrics_dict" + ] + }, + { + "cell_type": "markdown", + "id": "iYS06bYkA5uD", + "metadata": { + "id": "iYS06bYkA5uD" + }, + "source": [ + "C изначальной архитектурой" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "s69HDH9P-PZl", + "metadata": { + "id": "s69HDH9P-PZl" + }, + "outputs": [], + "source": [ + "class Model(nn.Module):\n", + " def __init__(self, in_and_out_features = 8287):\n", + " super().__init__()\n", + " self.in_and_out_features = in_and_out_features\n", + " self.hidden_size = 500\n", + "\n", + " self.sequential = nn.Sequential( \n", + " nn.Linear(in_and_out_features, self.hidden_size), \n", + " nn.ReLU(), \n", + " nn.Linear(self.hidden_size, in_and_out_features) # Another Linear transformation\n", + " )\n", + "\n", + " def forward(self, x): # In the forward function, you define how your model runs, from input to output \n", + " x = self.sequential(x)\n", + " return x" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "TytUsH6vA9Wo", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "TytUsH6vA9Wo", + "outputId": "464573c4-6c3f-4b04-ac32-3ea09fb84f08" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "lr:0.001 ne:0.001 bs:3000 ....\n", + "lr:0.001 ne:0.001 bs:4500 ....\n", + "lr:0.001 ne:0.001 bs:3000 ....\n", + "lr:0.001 ne:0.001 bs:4500 ....\n", + "lr:0.0003 ne:0.0003 bs:3000 ....\n", + "lr:0.0003 ne:0.0003 bs:4500 ....\n", + "lr:0.0003 ne:0.0003 bs:3000 ....\n", + "lr:0.0003 ne:0.0003 bs:4500 ....\n" + ] + } + ], + "source": [ + "first_arch_metrics = {}\n", + "\n", + "for lr in [0.001, 0.0003]:\n", + " for ne in [100, 200]:\n", + " for bs in [3000, 4500]:\n", + " \n", + " print(f\"lr:{lr} ne:{lr} bs:{bs} ....\" )\n", + "\n", + " LR = lr\n", + " NUM_EPOCHS = ne\n", + " BATCH_SIZE = bs\n", + "\n", + " first_arch_metrics[f\"lr:{lr} ne:{ne} bs:{bs}\"] = train_model()\n" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "HnEm5GLZDAuC", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "HnEm5GLZDAuC", + "outputId": "d652f3d6-c81a-4e35-e070-7350ce956120" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "lr:0.001 ne:100 bs:3000 0.0856485318926931 0.24734999561176826\n", + "lr:0.001 ne:100 bs:4500 0.08339590626737009 0.2456629642992969\n", + "lr:0.001 ne:200 bs:3000 0.08698450466615308 0.2526061220708553\n", + "lr:0.001 ne:200 bs:4500 0.0867699688923128 0.2529181741055321\n", + "lr:0.0003 ne:100 bs:3000 0.08751109247467015 0.25363979443572215\n", + "lr:0.0003 ne:100 bs:4500 0.08879830711771187 0.25470272167883995\n", + "lr:0.0003 ne:200 bs:3000 0.08135781641588735 0.23005061093937415\n", + "lr:0.0003 ne:200 bs:4500 0.08193316235482266 0.23188391664310024\n" + ] + } + ], + "source": [ + "for i in first_arch_metrics.keys():\n", + " print(i, first_arch_metrics[i]['recall@5'], first_arch_metrics[i]['recall@10'])" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "id": "ZaDleIoiPyCJ", + "metadata": { + "id": "ZaDleIoiPyCJ" + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "DOeEZG5_A9p4", + "metadata": { + "id": "DOeEZG5_A9p4" + }, + "source": [ + "Усложним архитектуру" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "rTOhYgiX-fEr", + "metadata": { + "id": "rTOhYgiX-fEr" + }, + "outputs": [], + "source": [ + "class Model(nn.Module):\n", + " def __init__(self, in_and_out_features = 8287):\n", + " super().__init__()\n", + " self.in_and_out_features = in_and_out_features\n", + " self.hidden_size = 512\n", + "\n", + " self.sequential = nn.Sequential( \n", + " nn.Linear(in_and_out_features, 4096), \n", + " nn.ReLU(), \n", + "\n", + " nn.Linear(4096, self.hidden_size), \n", + " nn.ReLU(),\n", + "\n", + " nn.Linear(self.hidden_size, 4096), \n", + " nn.ReLU(), \n", + "\n", + " nn.Linear(4096, in_and_out_features) # Another Linear transformation\n", + " )\n", + "\n", + " def forward(self, x): # In the forward function, you define how your model runs, from input to output \n", + " x = self.sequential(x)\n", + " return x" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "id": "TPRykgiN-fNe", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "TPRykgiN-fNe", + "outputId": "a32247b1-3a86-479d-aa0f-df75119e18e2" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "lr:0.001 ne:100 bs:3000 ....\n", + "lr:0.001 ne:100 bs:4500 ....\n", + "lr:0.001 ne:200 bs:3000 ....\n", + "lr:0.001 ne:200 bs:4500 ....\n", + "lr:0.0003 ne:100 bs:3000 ....\n", + "lr:0.0003 ne:100 bs:4500 ....\n", + "lr:0.0003 ne:200 bs:3000 ....\n", + "lr:0.0003 ne:200 bs:4500 ....\n" + ] + } + ], + "source": [ + "second_arch_metrics = {}\n", + "\n", + "for lr in [0.001, 0.0003]:\n", + " for ne in [100, 200]:\n", + " for bs in [3000, 4500]:\n", + " \n", + " print(f\"lr:{lr} ne:{ne} bs:{bs} ....\" )\n", + "\n", + " LR = lr\n", + " NUM_EPOCHS = ne\n", + " BATCH_SIZE = bs\n", + "\n", + " second_arch_metrics[f\"lr:{lr} ne:{ne} bs:{bs}\"] = train_model()\n" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "wMGBNnslD4ax", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "wMGBNnslD4ax", + "outputId": "76b97e64-342e-4ef5-b71e-f6a88c7daf36" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "lr:0.001 ne:100 bs:3000 0.14852701688006476 0.363608881781037\n", + "lr:0.001 ne:100 bs:4500 0.14894633680166167 0.3632090651116074\n", + "lr:0.001 ne:200 bs:3000 0.15524588725169922 0.35925965654772934\n", + "lr:0.001 ne:200 bs:4500 0.1548265673301023 0.35853803621753927\n", + "lr:0.0003 ne:100 bs:3000 0.15456327342584375 0.37245360663890703\n", + "lr:0.0003 ne:100 bs:4500 0.15338332666972218 0.3731167172125952\n", + "lr:0.0003 ne:200 bs:3000 0.15394892098257384 0.3672852448145728\n", + "lr:0.0003 ne:200 bs:4500 0.1538026465913191 0.36697319277989604\n" + ] + } + ], + "source": [ + "for i in second_arch_metrics.keys():\n", + " print(i, second_arch_metrics[i]['recall@5'], second_arch_metrics[i]['recall@10'])" + ] + }, + { + "cell_type": "markdown", + "id": "k6zRyd-uD0Fy", + "metadata": { + "id": "k6zRyd-uD0Fy" + }, + "source": [ + "Добавим еще слоев: " + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "08GqJ7iu-fPo", + "metadata": { + "id": "08GqJ7iu-fPo" + }, + "outputs": [], + "source": [ + "class Model(nn.Module):\n", + " def __init__(self, in_and_out_features = 8287):\n", + " super().__init__()\n", + " self.in_and_out_features = in_and_out_features\n", + " self.hidden_size = 512\n", + "\n", + " self.sequential = nn.Sequential( \n", + " nn.Linear(in_and_out_features, 6000), \n", + " nn.ReLU(), \n", + "\n", + " nn.Linear(6000, 3000), \n", + " nn.ReLU(),\n", + "\n", + " nn.Linear(3000, 1024), \n", + " nn.ReLU(),\n", + "\n", + " nn.Linear(1024, self.hidden_size), \n", + " nn.ReLU(),\n", + "\n", + " nn.Linear(self.hidden_size, 1024), \n", + " nn.ReLU(),\n", + "\n", + " nn.Linear(1024, 3000), \n", + " nn.ReLU(),\n", + "\n", + " nn.Linear(3000, 6000), \n", + " nn.ReLU(), \n", + "\n", + " nn.Linear(6000, in_and_out_features) # Another Linear transformation\n", + " )\n", + "\n", + " def forward(self, x): # In the forward function, you define how your model runs, from input to output \n", + " x = self.sequential(x)\n", + " return x" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "id": "AV4bbBpd-fSg", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "AV4bbBpd-fSg", + "outputId": "2a985b96-d452-490b-d73c-419e0341b0d8" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "lr:0.0003 ne:100 bs:3000 ....\n", + "lr:0.0003 ne:100 bs:4500 ....\n", + "lr:0.0003 ne:200 bs:3000 ....\n", + "lr:0.0003 ne:200 bs:4500 ....\n" + ] + } + ], + "source": [ + "third_arch_metrics = {}\n", + "\n", + "for lr in [0.0003]:\n", + " for ne in [100, 200]:\n", + " for bs in [3000, 4500]:\n", + " \n", + " print(f\"lr:{lr} ne:{ne} bs:{bs} ....\" )\n", + "\n", + " LR = lr\n", + " NUM_EPOCHS = ne\n", + " BATCH_SIZE = bs\n", + "\n", + " third_arch_metrics[f\"lr:{lr} ne:{ne} bs:{bs}\"] = train_model()\n" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "id": "v1gCEb8aFQc-", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "v1gCEb8aFQc-", + "outputId": "f7084cf6-b161-4951-cc6d-1abe32766205" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "lr:0.0003 ne:100 bs:3000 0.24635532975123603 0.6131237383833754\n", + "lr:0.0003 ne:100 bs:4500 0.2430007703784606 0.6135430583049724\n", + "lr:0.0003 ne:200 bs:3000 0.2589251757730602 0.6017533423698401\n", + "lr:0.0003 ne:200 bs:4500 0.2589739339034784 0.6040157196212469\n" + ] + } + ], + "source": [ + "for i in third_arch_metrics.keys():\n", + " print(i, third_arch_metrics[i]['recall@5'], third_arch_metrics[i]['recall@10'])" + ] + }, + { + "cell_type": "markdown", + "id": "k0qGe8sVaZq4", + "metadata": { + "id": "k0qGe8sVaZq4" + }, + "source": [ + "Модель обучена. Лучшей моделью является модель последней архитектуры , со следующими подобранными гипперпараметрам:\n", + "\n", + "* LR: 0.0003\n", + "* NUM_EPOCHS: 200\n", + "* BATCH_SIZE: 4500\n", + "\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "sd7WVXXYo7H1", + "metadata": { + "id": "sd7WVXXYo7H1" + }, + "outputs": [], + "source": [] + } + ], + "metadata": { + "accelerator": "GPU", + "colab": { + "provenance": [] + }, + "gpuClass": "standard", + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.9.15" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/hw_5_dssm.ipynb b/hw_5_dssm.ipynb new file mode 100644 index 00000000..258bae7e --- /dev/null +++ b/hw_5_dssm.ipynb @@ -0,0 +1,4034 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "execution": { + "iopub.execute_input": "2023-01-22T16:23:22.841107Z", + "iopub.status.busy": "2023-01-22T16:23:22.840365Z", + "iopub.status.idle": "2023-01-22T16:23:22.850076Z", + "shell.execute_reply": "2023-01-22T16:23:22.848844Z", + "shell.execute_reply.started": "2023-01-22T16:23:22.841044Z" + } + }, + "outputs": [], + "source": [ + "import ast\n", + "import json\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "import os\n", + "import pandas as pd\n", + "import pickle\n", + "import tensorflow as tf\n", + "import tensorflow.keras.backend as K\n", + "import warnings\n", + "warnings.filterwarnings('ignore')\n", + "\n", + "from collections import Counter\n", + "from random import randint, random\n", + "from scipy.sparse import coo_matrix, hstack\n", + "from sklearn.metrics.pairwise import euclidean_distances, cosine_distances, cosine_similarity\n", + "from sklearn.metrics.pairwise import euclidean_distances as ED\n", + "from tensorflow import keras\n", + "from tqdm import tqdm" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "ExecuteTime": { + "end_time": "2021-10-28T18:39:51.661446Z", + "start_time": "2021-10-28T18:39:51.563879Z" + }, + "execution": { + "iopub.execute_input": "2023-01-22T16:23:22.852847Z", + "iopub.status.busy": "2023-01-22T16:23:22.851743Z", + "iopub.status.idle": "2023-01-22T16:23:29.088896Z", + "shell.execute_reply": "2023-01-22T16:23:29.087873Z", + "shell.execute_reply.started": "2023-01-22T16:23:22.852800Z" + }, + "id": "25508632" + }, + "outputs": [], + "source": [ + "interactions_df = pd.read_csv('interactions_processed_kion.csv')\n", + "users_df = pd.read_csv('users_processed_kion.csv')\n", + "items_df = pd.read_csv('items_processed_kion.csv')" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "ExecuteTime": { + "end_time": "2021-10-28T18:40:35.447336Z", + "start_time": "2021-10-28T18:40:35.434541Z" + }, + "execution": { + "iopub.execute_input": "2023-01-22T16:23:29.097384Z", + "iopub.status.busy": "2023-01-22T16:23:29.094877Z", + "iopub.status.idle": "2023-01-22T16:23:29.123826Z", + "shell.execute_reply": "2023-01-22T16:23:29.123005Z", + "shell.execute_reply.started": "2023-01-22T16:23:29.097341Z" + }, + "id": "f5eacb31", + "outputId": "37b5c35b-4f4b-48ea-9012-a6ce7eed31c7" + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
user_idageincomesexkids_flg
0973171age_25_34income_60_90MTrue
1962099age_18_24income_20_40MFalse
21047345age_45_54income_40_60FFalse
3721985age_45_54income_20_40FFalse
4704055age_35_44income_60_90FFalse
\n", + "
" + ], + "text/plain": [ + " user_id age income sex kids_flg\n", + "0 973171 age_25_34 income_60_90 M True\n", + "1 962099 age_18_24 income_20_40 M False\n", + "2 1047345 age_45_54 income_40_60 F False\n", + "3 721985 age_45_54 income_20_40 F False\n", + "4 704055 age_35_44 income_60_90 F False" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "users_df.head()" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "ExecuteTime": { + "end_time": "2021-10-28T18:40:36.103997Z", + "start_time": "2021-10-28T18:40:36.094699Z" + }, + "execution": { + "iopub.execute_input": "2023-01-22T16:23:29.130158Z", + "iopub.status.busy": "2023-01-22T16:23:29.127963Z", + "iopub.status.idle": "2023-01-22T16:23:29.145149Z", + "shell.execute_reply": "2023-01-22T16:23:29.144033Z", + "shell.execute_reply.started": "2023-01-22T16:23:29.130122Z" + }, + "id": "61669d0d" + }, + "outputs": [], + "source": [ + "items_df = items_df.rename(columns = {'id' : 'item_id'})" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "ExecuteTime": { + "end_time": "2021-10-28T18:40:36.378293Z", + "start_time": "2021-10-28T18:40:36.370946Z" + }, + "execution": { + "iopub.execute_input": "2023-01-22T16:23:29.146754Z", + "iopub.status.busy": "2023-01-22T16:23:29.146394Z", + "iopub.status.idle": "2023-01-22T16:23:29.166993Z", + "shell.execute_reply": "2023-01-22T16:23:29.165796Z", + "shell.execute_reply.started": "2023-01-22T16:23:29.146717Z" + }, + "id": "25f4462e", + "outputId": "5cc6c801-f866-4b52-aada-f5226a5ebc21" + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
item_idcontent_typetitletitle_origgenrescountriesfor_kidsage_ratingstudiosdirectorsactorsdescriptionkeywordsrelease_year_cat
010711filmпоговори с нейHable con ellaдрамы, зарубежные, детективы, мелодрамыиспанияFalse16.0unknownпедро альмодоварАдольфо Фернандес, Ана Фернандес, Дарио Гранди...Мелодрама легендарного Педро Альмодовара «Пого...Поговори, ней, 2002, Испания, друзья, любовь, ...2000-2010
12508filmголые перцыSearch Partyзарубежные, приключения, комедиисшаFalse16.0unknownскот армстронгАдам Палли, Брайан Хаски, Дж.Б. Смув, Джейсон ...Уморительная современная комедия на популярную...Голые, перцы, 2014, США, друзья, свадьбы, прео...2010-2020
210716filmтактическая силаTactical Forceкриминал, зарубежные, триллеры, боевики, комедииканадаFalse16.0unknownадам п. калтрароАдриан Холмс, Даррен Шалави, Джерри Вассерман,...Профессиональный рестлер Стив Остин («Все или ...Тактическая, сила, 2011, Канада, бандиты, ганг...2010-2020
37868film45 лет45 Yearsдрамы, зарубежные, мелодрамывеликобританияFalse16.0unknownэндрю хэйАлександра Риддлстон-Барретт, Джеральдин Джейм...Шарлотта Рэмплинг, Том Кортни, Джеральдин Джей...45, лет, 2015, Великобритания, брак, жизнь, лю...2010-2020
416268filmвсе решает мгновениеNaNдрамы, спорт, советские, мелодрамысссрFalse12.0ленфильмвиктор садовскийАлександр Абдулов, Александр Демьяненко, Алекс...Расчетливая чаровница из советского кинохита «...Все, решает, мгновение, 1978, СССР, сильные, ж...1970-1980
\n", + "
" + ], + "text/plain": [ + " item_id content_type title title_orig \\\n", + "0 10711 film поговори с ней Hable con ella \n", + "1 2508 film голые перцы Search Party \n", + "2 10716 film тактическая сила Tactical Force \n", + "3 7868 film 45 лет 45 Years \n", + "4 16268 film все решает мгновение NaN \n", + "\n", + " genres countries for_kids \\\n", + "0 драмы, зарубежные, детективы, мелодрамы испания False \n", + "1 зарубежные, приключения, комедии сша False \n", + "2 криминал, зарубежные, триллеры, боевики, комедии канада False \n", + "3 драмы, зарубежные, мелодрамы великобритания False \n", + "4 драмы, спорт, советские, мелодрамы ссср False \n", + "\n", + " age_rating studios directors \\\n", + "0 16.0 unknown педро альмодовар \n", + "1 16.0 unknown скот армстронг \n", + "2 16.0 unknown адам п. калтраро \n", + "3 16.0 unknown эндрю хэй \n", + "4 12.0 ленфильм виктор садовский \n", + "\n", + " actors \\\n", + "0 Адольфо Фернандес, Ана Фернандес, Дарио Гранди... \n", + "1 Адам Палли, Брайан Хаски, Дж.Б. Смув, Джейсон ... \n", + "2 Адриан Холмс, Даррен Шалави, Джерри Вассерман,... \n", + "3 Александра Риддлстон-Барретт, Джеральдин Джейм... \n", + "4 Александр Абдулов, Александр Демьяненко, Алекс... \n", + "\n", + " description \\\n", + "0 Мелодрама легендарного Педро Альмодовара «Пого... \n", + "1 Уморительная современная комедия на популярную... \n", + "2 Профессиональный рестлер Стив Остин («Все или ... \n", + "3 Шарлотта Рэмплинг, Том Кортни, Джеральдин Джей... \n", + "4 Расчетливая чаровница из советского кинохита «... \n", + "\n", + " keywords release_year_cat \n", + "0 Поговори, ней, 2002, Испания, друзья, любовь, ... 2000-2010 \n", + "1 Голые, перцы, 2014, США, друзья, свадьбы, прео... 2010-2020 \n", + "2 Тактическая, сила, 2011, Канада, бандиты, ганг... 2010-2020 \n", + "3 45, лет, 2015, Великобритания, брак, жизнь, лю... 2010-2020 \n", + "4 Все, решает, мгновение, 1978, СССР, сильные, ж... 1970-1980 " + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "items_df.head()" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "ExecuteTime": { + "end_time": "2021-10-28T18:40:36.607688Z", + "start_time": "2021-10-28T18:40:36.597640Z" + }, + "execution": { + "iopub.execute_input": "2023-01-22T16:23:29.169473Z", + "iopub.status.busy": "2023-01-22T16:23:29.168713Z", + "iopub.status.idle": "2023-01-22T16:23:29.183035Z", + "shell.execute_reply": "2023-01-22T16:23:29.181327Z", + "shell.execute_reply.started": "2023-01-22T16:23:29.169432Z" + }, + "id": "b41964d3", + "outputId": "b4c8f3d5-e7af-4e29-d2e8-0defb6993b35" + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
user_iditem_idlast_watch_dttotal_durwatched_pct
017654995062021-05-11425072
169931716592021-05-298317100
265668371072021-05-09100
386461376382021-07-0514483100
496486895062021-04-306725100
\n", + "
" + ], + "text/plain": [ + " user_id item_id last_watch_dt total_dur watched_pct\n", + "0 176549 9506 2021-05-11 4250 72\n", + "1 699317 1659 2021-05-29 8317 100\n", + "2 656683 7107 2021-05-09 10 0\n", + "3 864613 7638 2021-07-05 14483 100\n", + "4 964868 9506 2021-04-30 6725 100" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "interactions_df.head()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "cd252422" + }, + "source": [ + "## Готовим фичи пользователей" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "pBdccMPAr7KR" + }, + "source": [ + "Посмотрим, какие фичи в датасете фильмов являются категориальными и закодируем их с помощью one-hot encoding." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "ExecuteTime": { + "end_time": "2021-10-28T18:40:37.156260Z", + "start_time": "2021-10-28T18:40:37.138422Z" + }, + "execution": { + "iopub.execute_input": "2023-01-22T16:23:29.185708Z", + "iopub.status.busy": "2023-01-22T16:23:29.184841Z", + "iopub.status.idle": "2023-01-22T16:23:29.504659Z", + "shell.execute_reply": "2023-01-22T16:23:29.503366Z", + "shell.execute_reply.started": "2023-01-22T16:23:29.185668Z" + }, + "id": "692270ac", + "outputId": "7491ab1f-f9fb-4921-e383-7ecf5569e999" + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
user_idage_age_18_24age_age_25_34age_age_35_44age_age_45_54age_age_55_64age_age_65_infage_age_unknownincome_income_0_20income_income_150_infincome_income_20_40income_income_40_60income_income_60_90income_income_90_150income_income_unknownsex_Fsex_Msex_sex_unknownkids_flg_Falsekids_flg_True
0973171FalseTrueFalseFalseFalseFalseFalseFalseFalseFalseFalseTrueFalseFalseFalseTrueFalseFalseTrue
1962099TrueFalseFalseFalseFalseFalseFalseFalseFalseTrueFalseFalseFalseFalseFalseTrueFalseTrueFalse
21047345FalseFalseFalseTrueFalseFalseFalseFalseFalseFalseTrueFalseFalseFalseTrueFalseFalseTrueFalse
3721985FalseFalseFalseTrueFalseFalseFalseFalseFalseTrueFalseFalseFalseFalseTrueFalseFalseTrueFalse
4704055FalseFalseTrueFalseFalseFalseFalseFalseFalseFalseFalseTrueFalseFalseTrueFalseFalseTrueFalse
\n", + "
" + ], + "text/plain": [ + " user_id age_age_18_24 age_age_25_34 age_age_35_44 age_age_45_54 \\\n", + "0 973171 False True False False \n", + "1 962099 True False False False \n", + "2 1047345 False False False True \n", + "3 721985 False False False True \n", + "4 704055 False False True False \n", + "\n", + " age_age_55_64 age_age_65_inf age_age_unknown income_income_0_20 \\\n", + "0 False False False False \n", + "1 False False False False \n", + "2 False False False False \n", + "3 False False False False \n", + "4 False False False False \n", + "\n", + " income_income_150_inf income_income_20_40 income_income_40_60 \\\n", + "0 False False False \n", + "1 False True False \n", + "2 False False True \n", + "3 False True False \n", + "4 False False False \n", + "\n", + " income_income_60_90 income_income_90_150 income_income_unknown sex_F \\\n", + "0 True False False False \n", + "1 False False False False \n", + "2 False False False True \n", + "3 False False False True \n", + "4 True False False True \n", + "\n", + " sex_M sex_sex_unknown kids_flg_False kids_flg_True \n", + "0 True False False True \n", + "1 True False True False \n", + "2 False False True False \n", + "3 False False True False \n", + "4 False False True False " + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "user_cat_feats = [\"age\", \"income\", \"sex\", \"kids_flg\"]\n", + "# из исходного датафрейма оставим только item_id - этот признак нам понадобится позже\n", + "# для того, чтобы маппить айтемы из датафрейма с фильмами с айтемами \n", + "# из датафрейма с взаимодействиями\n", + "users_ohe_df = users_df.user_id\n", + "for feat in user_cat_feats:\n", + " # получаем датафрейм с one-hot encoding для каждой категориальной фичи\n", + " ohe_feat_df = pd.get_dummies(users_df[feat], prefix=feat)\n", + " # конкатенируем ohe-hot датафрейм с датафреймом, \n", + " # который мы получили на предыдущем шаге\n", + " users_ohe_df = pd.concat([users_ohe_df, ohe_feat_df], axis=1)\n", + "\n", + "users_ohe_df.head()\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "74cdbd93" + }, + "source": [ + "## Готовим фичи айтемов" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "5kHzJ91Mr35c" + }, + "source": [ + "Кодируем их точно так же - one-hot'ом." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "execution": { + "iopub.execute_input": "2023-01-22T16:23:29.507174Z", + "iopub.status.busy": "2023-01-22T16:23:29.506716Z", + "iopub.status.idle": "2023-01-22T16:23:29.528115Z", + "shell.execute_reply": "2023-01-22T16:23:29.526826Z", + "shell.execute_reply.started": "2023-01-22T16:23:29.507133Z" + }, + "id": "-2Wd9upSsCle", + "outputId": "671c2446-81f5-4e32-e24f-3aec9c8a2076" + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
item_idcontent_typetitletitle_origgenrescountriesfor_kidsage_ratingstudiosdirectorsactorsdescriptionkeywordsrelease_year_cat
010711filmпоговори с нейHable con ellaдрамы, зарубежные, детективы, мелодрамыиспанияFalse16.0unknownпедро альмодоварАдольфо Фернандес, Ана Фернандес, Дарио Гранди...Мелодрама легендарного Педро Альмодовара «Пого...Поговори, ней, 2002, Испания, друзья, любовь, ...2000-2010
12508filmголые перцыSearch Partyзарубежные, приключения, комедиисшаFalse16.0unknownскот армстронгАдам Палли, Брайан Хаски, Дж.Б. Смув, Джейсон ...Уморительная современная комедия на популярную...Голые, перцы, 2014, США, друзья, свадьбы, прео...2010-2020
210716filmтактическая силаTactical Forceкриминал, зарубежные, триллеры, боевики, комедииканадаFalse16.0unknownадам п. калтрароАдриан Холмс, Даррен Шалави, Джерри Вассерман,...Профессиональный рестлер Стив Остин («Все или ...Тактическая, сила, 2011, Канада, бандиты, ганг...2010-2020
37868film45 лет45 Yearsдрамы, зарубежные, мелодрамывеликобританияFalse16.0unknownэндрю хэйАлександра Риддлстон-Барретт, Джеральдин Джейм...Шарлотта Рэмплинг, Том Кортни, Джеральдин Джей...45, лет, 2015, Великобритания, брак, жизнь, лю...2010-2020
416268filmвсе решает мгновениеNaNдрамы, спорт, советские, мелодрамысссрFalse12.0ленфильмвиктор садовскийАлександр Абдулов, Александр Демьяненко, Алекс...Расчетливая чаровница из советского кинохита «...Все, решает, мгновение, 1978, СССР, сильные, ж...1970-1980
\n", + "
" + ], + "text/plain": [ + " item_id content_type title title_orig \\\n", + "0 10711 film поговори с ней Hable con ella \n", + "1 2508 film голые перцы Search Party \n", + "2 10716 film тактическая сила Tactical Force \n", + "3 7868 film 45 лет 45 Years \n", + "4 16268 film все решает мгновение NaN \n", + "\n", + " genres countries for_kids \\\n", + "0 драмы, зарубежные, детективы, мелодрамы испания False \n", + "1 зарубежные, приключения, комедии сша False \n", + "2 криминал, зарубежные, триллеры, боевики, комедии канада False \n", + "3 драмы, зарубежные, мелодрамы великобритания False \n", + "4 драмы, спорт, советские, мелодрамы ссср False \n", + "\n", + " age_rating studios directors \\\n", + "0 16.0 unknown педро альмодовар \n", + "1 16.0 unknown скот армстронг \n", + "2 16.0 unknown адам п. калтраро \n", + "3 16.0 unknown эндрю хэй \n", + "4 12.0 ленфильм виктор садовский \n", + "\n", + " actors \\\n", + "0 Адольфо Фернандес, Ана Фернандес, Дарио Гранди... \n", + "1 Адам Палли, Брайан Хаски, Дж.Б. Смув, Джейсон ... \n", + "2 Адриан Холмс, Даррен Шалави, Джерри Вассерман,... \n", + "3 Александра Риддлстон-Барретт, Джеральдин Джейм... \n", + "4 Александр Абдулов, Александр Демьяненко, Алекс... \n", + "\n", + " description \\\n", + "0 Мелодрама легендарного Педро Альмодовара «Пого... \n", + "1 Уморительная современная комедия на популярную... \n", + "2 Профессиональный рестлер Стив Остин («Все или ... \n", + "3 Шарлотта Рэмплинг, Том Кортни, Джеральдин Джей... \n", + "4 Расчетливая чаровница из советского кинохита «... \n", + "\n", + " keywords release_year_cat \n", + "0 Поговори, ней, 2002, Испания, друзья, любовь, ... 2000-2010 \n", + "1 Голые, перцы, 2014, США, друзья, свадьбы, прео... 2010-2020 \n", + "2 Тактическая, сила, 2011, Канада, бандиты, ганг... 2010-2020 \n", + "3 45, лет, 2015, Великобритания, брак, жизнь, лю... 2010-2020 \n", + "4 Все, решает, мгновение, 1978, СССР, сильные, ж... 1970-1980 " + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "items_df.head()" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "ExecuteTime": { + "end_time": "2021-10-28T18:40:37.792147Z", + "start_time": "2021-10-28T18:40:37.537501Z" + }, + "execution": { + "iopub.execute_input": "2023-01-22T16:23:29.534806Z", + "iopub.status.busy": "2023-01-22T16:23:29.533869Z", + "iopub.status.idle": "2023-01-22T16:23:30.291045Z", + "shell.execute_reply": "2023-01-22T16:23:30.289998Z", + "shell.execute_reply.started": "2023-01-22T16:23:29.534762Z" + }, + "id": "7a94ef7e", + "outputId": "1ea7a769-8c2d-43d5-f2cb-47500bc0a7ba" + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
item_idcontent_type_filmcontent_type_seriesrelease_year_cat_1920-1930release_year_cat_1930-1940release_year_cat_1940-1950release_year_cat_1950-1960release_year_cat_1960-1970release_year_cat_1970-1980release_year_cat_1980-1990...directors_ярив хоровицdirectors_ярон зильберманdirectors_ярополк лапшинdirectors_ярослав лупийdirectors_ярроу чейни, скотт моужерdirectors_ясина сезарdirectors_ясуоми умэцуdirectors_ёдзи фукуяма, ацуко фукусима, николас де креси, синъитиро ватанабэ, сёдзи кавамориdirectors_ёлкин туйчиевdirectors_ён сан-хо
010711TrueFalseFalseFalseFalseFalseFalseFalseFalse...FalseFalseFalseFalseFalseFalseFalseFalseFalseFalse
12508TrueFalseFalseFalseFalseFalseFalseFalseFalse...FalseFalseFalseFalseFalseFalseFalseFalseFalseFalse
210716TrueFalseFalseFalseFalseFalseFalseFalseFalse...FalseFalseFalseFalseFalseFalseFalseFalseFalseFalse
37868TrueFalseFalseFalseFalseFalseFalseFalseFalse...FalseFalseFalseFalseFalseFalseFalseFalseFalseFalse
416268TrueFalseFalseFalseFalseFalseFalseTrueFalse...FalseFalseFalseFalseFalseFalseFalseFalseFalseFalse
\n", + "

5 rows × 8589 columns

\n", + "
" + ], + "text/plain": [ + " item_id content_type_film content_type_series \\\n", + "0 10711 True False \n", + "1 2508 True False \n", + "2 10716 True False \n", + "3 7868 True False \n", + "4 16268 True False \n", + "\n", + " release_year_cat_1920-1930 release_year_cat_1930-1940 \\\n", + "0 False False \n", + "1 False False \n", + "2 False False \n", + "3 False False \n", + "4 False False \n", + "\n", + " release_year_cat_1940-1950 release_year_cat_1950-1960 \\\n", + "0 False False \n", + "1 False False \n", + "2 False False \n", + "3 False False \n", + "4 False False \n", + "\n", + " release_year_cat_1960-1970 release_year_cat_1970-1980 \\\n", + "0 False False \n", + "1 False False \n", + "2 False False \n", + "3 False False \n", + "4 False True \n", + "\n", + " release_year_cat_1980-1990 ... directors_ярив хоровиц \\\n", + "0 False ... False \n", + "1 False ... False \n", + "2 False ... False \n", + "3 False ... False \n", + "4 False ... False \n", + "\n", + " directors_ярон зильберман directors_ярополк лапшин \\\n", + "0 False False \n", + "1 False False \n", + "2 False False \n", + "3 False False \n", + "4 False False \n", + "\n", + " directors_ярослав лупий directors_ярроу чейни, скотт моужер \\\n", + "0 False False \n", + "1 False False \n", + "2 False False \n", + "3 False False \n", + "4 False False \n", + "\n", + " directors_ясина сезар directors_ясуоми умэцу \\\n", + "0 False False \n", + "1 False False \n", + "2 False False \n", + "3 False False \n", + "4 False False \n", + "\n", + " directors_ёдзи фукуяма, ацуко фукусима, николас де креси, синъитиро ватанабэ, сёдзи кавамори \\\n", + "0 False \n", + "1 False \n", + "2 False \n", + "3 False \n", + "4 False \n", + "\n", + " directors_ёлкин туйчиев directors_ён сан-хо \n", + "0 False False \n", + "1 False False \n", + "2 False False \n", + "3 False False \n", + "4 False False \n", + "\n", + "[5 rows x 8589 columns]" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "item_cat_feats = ['content_type', 'release_year_cat',\n", + " 'for_kids', 'age_rating', \n", + " 'studios', 'countries', 'directors']\n", + "\n", + "items_ohe_df = items_df.item_id\n", + "\n", + "for feat in item_cat_feats:\n", + " ohe_feat_df = pd.get_dummies(items_df[feat], prefix=feat)\n", + " items_ohe_df = pd.concat([items_ohe_df, ohe_feat_df], axis=1) \n", + "\n", + "items_ohe_df.head()" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "execution": { + "iopub.execute_input": "2023-01-22T16:23:30.294678Z", + "iopub.status.busy": "2023-01-22T16:23:30.294379Z", + "iopub.status.idle": "2023-01-22T16:23:30.316137Z", + "shell.execute_reply": "2023-01-22T16:23:30.314916Z", + "shell.execute_reply.started": "2023-01-22T16:23:30.294651Z" + } + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
item_idcontent_typetitletitle_origgenrescountriesfor_kidsage_ratingstudiosdirectorsactorsdescriptionkeywordsrelease_year_cat
010711filmпоговори с нейHable con ellaдрамы, зарубежные, детективы, мелодрамыиспанияFalse16.0unknownпедро альмодоварАдольфо Фернандес, Ана Фернандес, Дарио Гранди...Мелодрама легендарного Педро Альмодовара «Пого...Поговори, ней, 2002, Испания, друзья, любовь, ...2000-2010
12508filmголые перцыSearch Partyзарубежные, приключения, комедиисшаFalse16.0unknownскот армстронгАдам Палли, Брайан Хаски, Дж.Б. Смув, Джейсон ...Уморительная современная комедия на популярную...Голые, перцы, 2014, США, друзья, свадьбы, прео...2010-2020
210716filmтактическая силаTactical Forceкриминал, зарубежные, триллеры, боевики, комедииканадаFalse16.0unknownадам п. калтрароАдриан Холмс, Даррен Шалави, Джерри Вассерман,...Профессиональный рестлер Стив Остин («Все или ...Тактическая, сила, 2011, Канада, бандиты, ганг...2010-2020
37868film45 лет45 Yearsдрамы, зарубежные, мелодрамывеликобританияFalse16.0unknownэндрю хэйАлександра Риддлстон-Барретт, Джеральдин Джейм...Шарлотта Рэмплинг, Том Кортни, Джеральдин Джей...45, лет, 2015, Великобритания, брак, жизнь, лю...2010-2020
416268filmвсе решает мгновениеNaNдрамы, спорт, советские, мелодрамысссрFalse12.0ленфильмвиктор садовскийАлександр Абдулов, Александр Демьяненко, Алекс...Расчетливая чаровница из советского кинохита «...Все, решает, мгновение, 1978, СССР, сильные, ж...1970-1980
\n", + "
" + ], + "text/plain": [ + " item_id content_type title title_orig \\\n", + "0 10711 film поговори с ней Hable con ella \n", + "1 2508 film голые перцы Search Party \n", + "2 10716 film тактическая сила Tactical Force \n", + "3 7868 film 45 лет 45 Years \n", + "4 16268 film все решает мгновение NaN \n", + "\n", + " genres countries for_kids \\\n", + "0 драмы, зарубежные, детективы, мелодрамы испания False \n", + "1 зарубежные, приключения, комедии сша False \n", + "2 криминал, зарубежные, триллеры, боевики, комедии канада False \n", + "3 драмы, зарубежные, мелодрамы великобритания False \n", + "4 драмы, спорт, советские, мелодрамы ссср False \n", + "\n", + " age_rating studios directors \\\n", + "0 16.0 unknown педро альмодовар \n", + "1 16.0 unknown скот армстронг \n", + "2 16.0 unknown адам п. калтраро \n", + "3 16.0 unknown эндрю хэй \n", + "4 12.0 ленфильм виктор садовский \n", + "\n", + " actors \\\n", + "0 Адольфо Фернандес, Ана Фернандес, Дарио Гранди... \n", + "1 Адам Палли, Брайан Хаски, Дж.Б. Смув, Джейсон ... \n", + "2 Адриан Холмс, Даррен Шалави, Джерри Вассерман,... \n", + "3 Александра Риддлстон-Барретт, Джеральдин Джейм... \n", + "4 Александр Абдулов, Александр Демьяненко, Алекс... \n", + "\n", + " description \\\n", + "0 Мелодрама легендарного Педро Альмодовара «Пого... \n", + "1 Уморительная современная комедия на популярную... \n", + "2 Профессиональный рестлер Стив Остин («Все или ... \n", + "3 Шарлотта Рэмплинг, Том Кортни, Джеральдин Джей... \n", + "4 Расчетливая чаровница из советского кинохита «... \n", + "\n", + " keywords release_year_cat \n", + "0 Поговори, ней, 2002, Испания, друзья, любовь, ... 2000-2010 \n", + "1 Голые, перцы, 2014, США, друзья, свадьбы, прео... 2010-2020 \n", + "2 Тактическая, сила, 2011, Канада, бандиты, ганг... 2010-2020 \n", + "3 45, лет, 2015, Великобритания, брак, жизнь, лю... 2010-2020 \n", + "4 Все, решает, мгновение, 1978, СССР, сильные, ж... 1970-1980 " + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "items_df.head()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Добавим текстовые фичи\n", + "С помощью TFIDFVectorizer получим эмбеддинги следующих колонок: genres, description, keywords" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": { + "execution": { + "iopub.execute_input": "2023-01-22T16:23:30.318830Z", + "iopub.status.busy": "2023-01-22T16:23:30.318190Z", + "iopub.status.idle": "2023-01-22T16:23:30.335666Z", + "shell.execute_reply": "2023-01-22T16:23:30.334811Z", + "shell.execute_reply.started": "2023-01-22T16:23:30.318792Z" + } + }, + "outputs": [], + "source": [ + "from sklearn.feature_extraction.text import TfidfVectorizer" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": { + "execution": { + "iopub.execute_input": "2023-01-22T16:23:30.338779Z", + "iopub.status.busy": "2023-01-22T16:23:30.338019Z", + "iopub.status.idle": "2023-01-22T16:23:31.386164Z", + "shell.execute_reply": "2023-01-22T16:23:31.385106Z", + "shell.execute_reply.started": "2023-01-22T16:23:30.338741Z" + } + }, + "outputs": [], + "source": [ + "for column in ['genres', 'keywords']:\n", + " tv = TfidfVectorizer(max_features = 500)\n", + " t = pd.DataFrame.sparse.from_spmatrix(tv.fit_transform(items_df[column]))\n", + " t.columns = [column + '_' + str(x) for x in t.columns]\n", + " items_ohe_df = pd.concat([items_ohe_df, t], axis = 1)" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": { + "execution": { + "iopub.execute_input": "2023-01-22T16:23:31.388255Z", + "iopub.status.busy": "2023-01-22T16:23:31.387847Z", + "iopub.status.idle": "2023-01-22T16:23:31.483917Z", + "shell.execute_reply": "2023-01-22T16:23:31.482751Z", + "shell.execute_reply.started": "2023-01-22T16:23:31.388214Z" + } + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
item_idcontent_type_filmcontent_type_seriesrelease_year_cat_1920-1930release_year_cat_1930-1940release_year_cat_1940-1950release_year_cat_1950-1960release_year_cat_1960-1970release_year_cat_1970-1980release_year_cat_1980-1990...keywords_490keywords_491keywords_492keywords_493keywords_494keywords_495keywords_496keywords_497keywords_498keywords_499
010711TrueFalseFalseFalseFalseFalseFalseFalseFalse...0.00.00.00.00.00.00.00.00.00.0
12508TrueFalseFalseFalseFalseFalseFalseFalseFalse...0.00.00.00.00.00.00.00.00.00.0
210716TrueFalseFalseFalseFalseFalseFalseFalseFalse...0.00.00.00.00.00.00.00.00.00.0
37868TrueFalseFalseFalseFalseFalseFalseFalseFalse...0.00.00.00.00.00.00.00.00.00.0
416268TrueFalseFalseFalseFalseFalseFalseTrueFalse...0.00.00.00.00.00.00.00.00.00.0
\n", + "

5 rows × 9197 columns

\n", + "
" + ], + "text/plain": [ + " item_id content_type_film content_type_series \\\n", + "0 10711 True False \n", + "1 2508 True False \n", + "2 10716 True False \n", + "3 7868 True False \n", + "4 16268 True False \n", + "\n", + " release_year_cat_1920-1930 release_year_cat_1930-1940 \\\n", + "0 False False \n", + "1 False False \n", + "2 False False \n", + "3 False False \n", + "4 False False \n", + "\n", + " release_year_cat_1940-1950 release_year_cat_1950-1960 \\\n", + "0 False False \n", + "1 False False \n", + "2 False False \n", + "3 False False \n", + "4 False False \n", + "\n", + " release_year_cat_1960-1970 release_year_cat_1970-1980 \\\n", + "0 False False \n", + "1 False False \n", + "2 False False \n", + "3 False False \n", + "4 False True \n", + "\n", + " release_year_cat_1980-1990 ... keywords_490 keywords_491 keywords_492 \\\n", + "0 False ... 0.0 0.0 0.0 \n", + "1 False ... 0.0 0.0 0.0 \n", + "2 False ... 0.0 0.0 0.0 \n", + "3 False ... 0.0 0.0 0.0 \n", + "4 False ... 0.0 0.0 0.0 \n", + "\n", + " keywords_493 keywords_494 keywords_495 keywords_496 keywords_497 \\\n", + "0 0.0 0.0 0.0 0.0 0.0 \n", + "1 0.0 0.0 0.0 0.0 0.0 \n", + "2 0.0 0.0 0.0 0.0 0.0 \n", + "3 0.0 0.0 0.0 0.0 0.0 \n", + "4 0.0 0.0 0.0 0.0 0.0 \n", + "\n", + " keywords_498 keywords_499 \n", + "0 0.0 0.0 \n", + "1 0.0 0.0 \n", + "2 0.0 0.0 \n", + "3 0.0 0.0 \n", + "4 0.0 0.0 \n", + "\n", + "[5 rows x 9197 columns]" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "items_ohe_df.head()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "cc595c20" + }, + "source": [ + "## Сделаем матрицу взаимодействий" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": { + "ExecuteTime": { + "end_time": "2021-10-28T18:40:37.898427Z", + "start_time": "2021-10-28T18:40:37.864067Z" + }, + "execution": { + "iopub.execute_input": "2023-01-22T16:23:31.486206Z", + "iopub.status.busy": "2023-01-22T16:23:31.485812Z", + "iopub.status.idle": "2023-01-22T16:23:31.604748Z", + "shell.execute_reply": "2023-01-22T16:23:31.603679Z", + "shell.execute_reply.started": "2023-01-22T16:23:31.486170Z" + }, + "id": "79c9bca3", + "outputId": "6f6148e0-8de7-4ffc-82d9-cf396db1ed98" + }, + "outputs": [ + { + "data": { + "text/plain": [ + "item_id\n", + "10440 202457\n", + "15297 193123\n", + "9728 132865\n", + "13865 122119\n", + "4151 91167\n", + " ... \n", + "8076 1\n", + "8954 1\n", + "15664 1\n", + "818 1\n", + "10542 1\n", + "Name: count, Length: 15706, dtype: int64" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "interactions_df.item_id.value_counts()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "YAfqm8asrBfG" + }, + "source": [ + "В датасете взаимодействий есть непопулярные фильмы и малоактивные пользователи. Кроме того, в таблице взаимодействий есть события с низким качеством взаимодействия - когда юзер начал смотреть фильм, но вскоре после начала просмотра выключил.\n", + "\n", + "Отфильтруем такие события*, малоактивных юзеров и непопулярные фильмы.\n", + "\n", + "Можете не фильтровать такие события, тогда у вас будет больше негативных примеров." + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": { + "ExecuteTime": { + "end_time": "2021-10-28T18:40:38.103819Z", + "start_time": "2021-10-28T18:40:38.070117Z" + }, + "execution": { + "iopub.execute_input": "2023-01-22T16:23:31.606489Z", + "iopub.status.busy": "2023-01-22T16:23:31.606197Z", + "iopub.status.idle": "2023-01-22T16:23:31.985392Z", + "shell.execute_reply": "2023-01-22T16:23:31.984254Z", + "shell.execute_reply.started": "2023-01-22T16:23:31.606462Z" + }, + "id": "17334e80", + "outputId": "bfbe26dd-7778-42ad-c5dd-283635fcafa6" + }, + "outputs": [ + { + "data": { + "text/plain": [ + "user_id\n", + "416206 1341\n", + "1010539 764\n", + "555233 685\n", + "11526 676\n", + "409259 625\n", + " ... \n", + "45493 1\n", + "615194 1\n", + "96848 1\n", + "425823 1\n", + "697262 1\n", + "Name: count, Length: 962179, dtype: int64" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "interactions_df.user_id.value_counts()" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": { + "ExecuteTime": { + "end_time": "2021-10-28T18:40:39.717096Z", + "start_time": "2021-10-28T18:40:38.759740Z" + }, + "execution": { + "iopub.execute_input": "2023-01-22T16:23:31.987509Z", + "iopub.status.busy": "2023-01-22T16:23:31.986995Z", + "iopub.status.idle": "2023-01-22T16:23:34.897911Z", + "shell.execute_reply": "2023-01-22T16:23:34.896578Z", + "shell.execute_reply.started": "2023-01-22T16:23:31.987469Z" + }, + "id": "076e4ebc", + "outputId": "85c15fd2-12bb-478c-e00f-4f2b7bbcd6ab" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "N users before: 962179\n", + "N items before: 15706\n", + "\n", + "N users after: 79515\n", + "N items after: 6901\n" + ] + } + ], + "source": [ + "print(f\"N users before: {interactions_df.user_id.nunique()}\")\n", + "print(f\"N items before: {interactions_df.item_id.nunique()}\\n\")\n", + "\n", + "# отфильтруем все события взаимодействий, в которых пользователь посмотрел\n", + "# фильм менее чем на 10 процентов\n", + "interactions_df = interactions_df[interactions_df.watched_pct > 10]\n", + "\n", + "# соберем всех пользователей, которые посмотрели \n", + "# больше 10 фильмов (можете выбрать другой порог)\n", + "valid_users = []\n", + "\n", + "c = Counter(interactions_df.user_id)\n", + "for user_id, entries in c.most_common():\n", + " if entries > 10:\n", + " valid_users.append(user_id)\n", + "\n", + "# и соберем все фильмы, которые посмотрели больше 10 пользователей\n", + "valid_items = []\n", + "\n", + "c = Counter(interactions_df.item_id)\n", + "for item_id, entries in c.most_common():\n", + " if entries > 10:\n", + " valid_items.append(item_id)\n", + "\n", + "# отбросим непопулярные фильмы и неактивных юзеров\n", + "interactions_df = interactions_df[interactions_df.user_id.isin(valid_users)]\n", + "interactions_df = interactions_df[interactions_df.item_id.isin(valid_items)]\n", + "\n", + "print(f\"N users after: {interactions_df.user_id.nunique()}\")\n", + "print(f\"N items after: {interactions_df.item_id.nunique()}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "a9163fb2" + }, + "source": [ + "После фильтрации может получиться так, что некоторые айтемы/юзеры есть в датасете взаимодействий, но при этом они отсутствуют в датасетах айтемов/юзеров или наоборот. Поэтому найдем id айтемов и id юзеров, которые есть во всех датасетах и оставим только их." + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": { + "ExecuteTime": { + "end_time": "2021-10-28T18:40:40.231703Z", + "start_time": "2021-10-28T18:40:39.718626Z" + }, + "execution": { + "iopub.execute_input": "2023-01-22T16:23:34.900180Z", + "iopub.status.busy": "2023-01-22T16:23:34.899760Z", + "iopub.status.idle": "2023-01-22T16:23:36.064882Z", + "shell.execute_reply": "2023-01-22T16:23:36.063765Z", + "shell.execute_reply.started": "2023-01-22T16:23:34.900142Z" + }, + "id": "d55848e1", + "outputId": "48609a0b-06b9-4a5e-f8f6-061db1c6dcb2" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "65974\n", + "6901\n" + ] + } + ], + "source": [ + "common_users = set(interactions_df.user_id.unique()).intersection(set(users_ohe_df.user_id.unique()))\n", + "common_items = set(interactions_df.item_id.unique()).intersection(set(items_ohe_df.item_id.unique()))\n", + "\n", + "print(len(common_users))\n", + "print(len(common_items))\n", + "\n", + "interactions_df = interactions_df[interactions_df.item_id.isin(common_items)]\n", + "interactions_df = interactions_df[interactions_df.user_id.isin(common_users)]\n", + "\n", + "items_ohe_df = items_ohe_df[items_ohe_df.item_id.isin(common_items)]\n", + "users_ohe_df = users_ohe_df[users_ohe_df.user_id.isin(common_users)]" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "1e8b9480" + }, + "source": [ + "\n", + "Соберем взаимодействия в матрицу user*item так, чтобы в строках этой матрицы были user_id, в столбцах - item_id, а на пересечениях строк и столбцов - единица, если пользователь взаимодействовал с айтемом и ноль, если нет.\n", + "\n", + "Такую матрицу удобно собирать в numpy array, однако нужно помнить, что numpy array индексируется порядковыми индексами, а нам же удобнее использовать item_id и user_id.\n", + "\n", + "Создадим некие внутренние индексы для user_id и item_id - uid и iid. Для этого просто соберем все user_id и item_id и пронумеруем их по порядку." + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": { + "ExecuteTime": { + "end_time": "2021-10-28T18:40:40.346587Z", + "start_time": "2021-10-28T18:40:40.233046Z" + }, + "execution": { + "iopub.execute_input": "2023-01-22T16:23:36.066990Z", + "iopub.status.busy": "2023-01-22T16:23:36.066574Z", + "iopub.status.idle": "2023-01-22T16:23:36.211726Z", + "shell.execute_reply": "2023-01-22T16:23:36.210597Z", + "shell.execute_reply.started": "2023-01-22T16:23:36.066949Z" + }, + "id": "81679fb0", + "outputId": "0c6bf7ce-1ea0-46c2-9d70-42b32bf08c7e" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[0, 1, 2, 3, 4]\n", + "[0, 1, 2, 3, 4]\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
user_iditem_idlast_watch_dttotal_durwatched_pctuidiid
017654995062021-05-11425072106163944
169931716592021-05-29831710042131675
610164583542021-08-1416722561024139
78840096932021-08-047031453150279
14532484372021-04-186598923103485
\n", + "
" + ], + "text/plain": [ + " user_id item_id last_watch_dt total_dur watched_pct uid iid\n", + "0 176549 9506 2021-05-11 4250 72 10616 3944\n", + "1 699317 1659 2021-05-29 8317 100 42131 675\n", + "6 1016458 354 2021-08-14 1672 25 61024 139\n", + "7 884009 693 2021-08-04 703 14 53150 279\n", + "14 5324 8437 2021-04-18 6598 92 310 3485" + ] + }, + "execution_count": 24, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "interactions_df[\"uid\"] = interactions_df[\"user_id\"].astype(\"category\")\n", + "interactions_df[\"uid\"] = interactions_df[\"uid\"].cat.codes\n", + "\n", + "interactions_df[\"iid\"] = interactions_df[\"item_id\"].astype(\"category\")\n", + "interactions_df[\"iid\"] = interactions_df[\"iid\"].cat.codes\n", + "\n", + "print(sorted(interactions_df.iid.unique())[:5])\n", + "print(sorted(interactions_df.uid.unique())[:5])\n", + "interactions_df.head()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "61c855e5" + }, + "source": [ + "Отнормируем матрицу взаимодействий" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": { + "execution": { + "iopub.execute_input": "2023-01-22T16:23:36.214161Z", + "iopub.status.busy": "2023-01-22T16:23:36.213276Z", + "iopub.status.idle": "2023-01-22T16:23:36.223246Z", + "shell.execute_reply": "2023-01-22T16:23:36.222069Z", + "shell.execute_reply.started": "2023-01-22T16:23:36.214121Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "0 3944\n", + "1 675\n", + "6 139\n", + "7 279\n", + "14 3485\n", + " ... \n", + "5476218 169\n", + "5476224 923\n", + "5476226 5610\n", + "5476239 2929\n", + "5476249 6766\n", + "Name: iid, Length: 1463641, dtype: int16" + ] + }, + "execution_count": 25, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "interactions_df.iid" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": { + "ExecuteTime": { + "end_time": "2021-10-28T18:41:03.360248Z", + "start_time": "2021-10-28T18:40:40.348057Z" + }, + "execution": { + "iopub.execute_input": "2023-01-22T16:23:36.225520Z", + "iopub.status.busy": "2023-01-22T16:23:36.224590Z", + "iopub.status.idle": "2023-01-22T16:23:43.629733Z", + "shell.execute_reply": "2023-01-22T16:23:43.628568Z", + "shell.execute_reply.started": "2023-01-22T16:23:36.225480Z" + }, + "id": "3feced70" + }, + "outputs": [], + "source": [ + "interactions_vec = np.zeros((interactions_df.uid.nunique(), \n", + " interactions_df.iid.nunique())) \n", + "\n", + "for user_id, item_id in zip(interactions_df.uid, interactions_df.iid):\n", + " interactions_vec[user_id, item_id] += 1\n", + "\n", + "\n", + "res = interactions_vec.sum(axis=1)\n", + "for i in range(len(interactions_vec)):\n", + " interactions_vec[i] /= res[i]" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": { + "ExecuteTime": { + "end_time": "2021-10-28T18:41:03.416061Z", + "start_time": "2021-10-28T18:41:03.363462Z" + }, + "execution": { + "iopub.execute_input": "2023-01-22T16:23:43.634195Z", + "iopub.status.busy": "2023-01-22T16:23:43.631362Z", + "iopub.status.idle": "2023-01-22T16:23:43.711673Z", + "shell.execute_reply": "2023-01-22T16:23:43.710586Z", + "shell.execute_reply.started": "2023-01-22T16:23:43.634161Z" + }, + "id": "9f5ec90f", + "outputId": "9acdfe45-aa4e-4a64-ffdc-1a750390ae84" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "6897\n", + "6901\n", + "65974\n", + "65974\n", + "{11805, 9788, 11501, 1734}\n" + ] + } + ], + "source": [ + "print(interactions_df.item_id.nunique())\n", + "print(items_ohe_df.item_id.nunique())\n", + "print(interactions_df.user_id.nunique())\n", + "print(users_ohe_df.user_id.nunique())\n", + "\n", + "print(set(items_ohe_df.item_id.unique()) - set(interactions_df.item_id.unique()))" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": { + "execution": { + "iopub.execute_input": "2023-01-22T16:23:43.713551Z", + "iopub.status.busy": "2023-01-22T16:23:43.713196Z", + "iopub.status.idle": "2023-01-22T16:23:44.238808Z", + "shell.execute_reply": "2023-01-22T16:23:44.237691Z", + "shell.execute_reply.started": "2023-01-22T16:23:43.713517Z" + } + }, + "outputs": [], + "source": [ + "items_ohe_df = items_ohe_df[~items_ohe_df.item_id.isin([11805, 9788, 11501, 1734])]" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "19e69bae" + }, + "source": [ + "Для того, чтобы можно было удобно превратить iid/uid в item_id/user_id и наоборот соберем словари \n", + "\n", + "{iid: item_id}, {uid: user_id} и {item_id: iid}, {user_id: uid}." + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": { + "ExecuteTime": { + "end_time": "2021-10-28T18:41:03.637495Z", + "start_time": "2021-10-28T18:41:03.417544Z" + }, + "execution": { + "iopub.execute_input": "2023-01-22T16:23:44.243767Z", + "iopub.status.busy": "2023-01-22T16:23:44.243422Z", + "iopub.status.idle": "2023-01-22T16:23:44.817126Z", + "shell.execute_reply": "2023-01-22T16:23:44.816088Z", + "shell.execute_reply.started": "2023-01-22T16:23:44.243739Z" + }, + "id": "c8a84024" + }, + "outputs": [], + "source": [ + "iid_to_item_id = interactions_df[[\"iid\", \"item_id\"]].drop_duplicates().set_index(\"iid\").to_dict()[\"item_id\"]\n", + "item_id_to_iid = interactions_df[[\"iid\", \"item_id\"]].drop_duplicates().set_index(\"item_id\").to_dict()[\"iid\"]\n", + "\n", + "uid_to_user_id = interactions_df[[\"uid\", \"user_id\"]].drop_duplicates().set_index(\"uid\").to_dict()[\"user_id\"]\n", + "user_id_to_uid = interactions_df[[\"uid\", \"user_id\"]].drop_duplicates().set_index(\"user_id\").to_dict()[\"uid\"]" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "48ca5204" + }, + "source": [ + "И проиндексируем датасеты users_ohe_df и items_ohe_df по внутренним айди:" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": { + "ExecuteTime": { + "end_time": "2021-10-28T18:41:03.744883Z", + "start_time": "2021-10-28T18:41:03.638719Z" + }, + "execution": { + "iopub.execute_input": "2023-01-22T16:23:44.819593Z", + "iopub.status.busy": "2023-01-22T16:23:44.818859Z", + "iopub.status.idle": "2023-01-22T16:23:44.930257Z", + "shell.execute_reply": "2023-01-22T16:23:44.929032Z", + "shell.execute_reply.started": "2023-01-22T16:23:44.819553Z" + }, + "id": "4c4980ac" + }, + "outputs": [], + "source": [ + "items_ohe_df[\"iid\"] = items_ohe_df[\"item_id\"].apply(lambda x: item_id_to_iid[x])\n", + "items_ohe_df = items_ohe_df.set_index(\"iid\")\n", + "\n", + "users_ohe_df[\"uid\"] = users_ohe_df[\"user_id\"].apply(lambda x: user_id_to_uid[x])\n", + "users_ohe_df = users_ohe_df.set_index(\"uid\")" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "metadata": { + "ExecuteTime": { + "end_time": "2021-10-28T18:41:03.749717Z", + "start_time": "2021-10-28T18:41:03.746067Z" + }, + "execution": { + "iopub.execute_input": "2023-01-22T16:23:44.932306Z", + "iopub.status.busy": "2023-01-22T16:23:44.931684Z", + "iopub.status.idle": "2023-01-22T16:23:44.939719Z", + "shell.execute_reply": "2023-01-22T16:23:44.938755Z", + "shell.execute_reply.started": "2023-01-22T16:23:44.932267Z" + }, + "id": "22c26d39" + }, + "outputs": [], + "source": [ + "def triplet_loss(y_true, y_pred, n_dims=128, alpha=0.4):\n", + " # будем ожидать, что на вход функции прилетит три сконкатенированных \n", + " # вектора - вектор юзера и два вектора айтема\n", + " anchor = y_pred[:, 0:n_dims]\n", + " positive = y_pred[:, n_dims:n_dims*2]\n", + " negative = y_pred[:, n_dims*2:n_dims*3]\n", + "\n", + " # считаем расстояния от вектора юзера до вектора хорошего айтема\n", + " pos_dist = K.sum(K.square(anchor - positive), axis=1)\n", + " # и до плохого\n", + " neg_dist = K.sum(K.square(anchor - negative), axis=1)\n", + "\n", + " # считаем лосс\n", + " basic_loss = pos_dist - neg_dist + alpha\n", + " loss = K.maximum(basic_loss, 0.0) # возвращаем ноль, если лосс отрицательный\n", + " \n", + " return loss\n", + "\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "ExecuteTime": { + "end_time": "2021-10-28T19:19:05.615364Z", + "start_time": "2021-10-28T19:19:05.612463Z" + }, + "id": "4de262b4" + }, + "source": [ + "Попробуйте другие лоссы, например, BPR Triplet loss" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": { + "execution": { + "iopub.execute_input": "2023-01-22T16:18:11.520568Z", + "iopub.status.busy": "2023-01-22T16:18:11.519791Z", + "iopub.status.idle": "2023-01-22T16:18:11.535194Z", + "shell.execute_reply": "2023-01-22T16:18:11.533962Z", + "shell.execute_reply.started": "2023-01-22T16:18:11.520528Z" + } + }, + "outputs": [], + "source": [ + "def bpr_triplet_loss(y_true, y_pred, n_dims=128):\n", + " \n", + " from keras import backend as K\n", + " \n", + " anchor = y_pred[:, 0:n_dims]\n", + " positive = y_pred[:, n_dims:n_dims*2]\n", + " negative = y_pred[:, n_dims*2:n_dims*3]\n", + "\n", + " # BPR loss\n", + " loss = 1.0 - K.sigmoid(\n", + " K.sum(anchor * positive, axis=-1, keepdims=True) -\n", + " K.sum(anchor * negative, axis=-1, keepdims=True))\n", + "\n", + " return loss" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "ExecuteTime": { + "end_time": "2021-10-23T11:20:03.327838Z", + "start_time": "2021-10-23T11:20:03.324389Z" + }, + "id": "85d618b6" + }, + "source": [ + "## Генератор и семплирование\n", + "\n", + "- хорошим примером будет тот айтем, который был взят из датасета взаимодействий в соответствии с распределением просмотренных айтемов для этого юзера;\n", + "- Для негативного буду рандомно брать айтем из 100 наиболее непохожих по евклидовому расстоянию на положительный айтем по вектору жанр и ключевые слова, который человек при этом не смотрел \n", + "\n", + "Т. о., если например человек посмотрел целиком триллер, то в негативный для него должно попасть что-то вроде мелодрамы, при этом ключевые слова тоже будут сильно отличаться \n", + "\n", + "\n", + "Сформируем заранее следующий словарь - для каждого айтема: список из ста наиболее непохожих айтемов. Тогда в генераторе нужно будет взять рандомное значение их ста айтемов для положительного айтема. Если считать это в моменте работы генератора, то получается чрезвычайно долго, а здесь обращение к словарю - O(1), и взятие рандомного значения такое же по сложности, как в простом генераторе\n" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "metadata": { + "execution": { + "iopub.execute_input": "2023-01-22T15:37:25.302855Z", + "iopub.status.busy": "2023-01-22T15:37:25.302472Z", + "iopub.status.idle": "2023-01-22T15:37:32.215552Z", + "shell.execute_reply": "2023-01-22T15:37:32.214488Z", + "shell.execute_reply.started": "2023-01-22T15:37:25.302820Z" + } + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "100%|██████████| 6897/6897 [00:02<00:00, 2513.96it/s]\n" + ] + } + ], + "source": [ + "# формируем слоарь\n", + "\n", + "fts = items_ohe_df[[x for x in items_ohe_df if 'genre' in x or 'keywords' in x]]\n", + "\n", + "distances = pd.DataFrame(ED(fts))\n", + "distances.columns = list(fts.index)\n", + "distances.index = fts.index\n", + "\n", + "distance_dict = {}\n", + "for i in tqdm(distances.columns):\n", + " distance_dict[i] = list(distances[i].sort_values()[-100:].index)" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": { + "execution": { + "iopub.execute_input": "2023-01-22T12:39:46.221189Z", + "iopub.status.busy": "2023-01-22T12:39:46.220459Z", + "iopub.status.idle": "2023-01-22T12:39:49.254755Z", + "shell.execute_reply": "2023-01-22T12:39:49.253714Z", + "shell.execute_reply.started": "2023-01-22T12:39:46.221147Z" + } + }, + "outputs": [], + "source": [ + "iids_ = np.array(fts.index)\n", + "user_interactions = interactions_df.groupby(\"uid\")['iid'].apply(lambda x: np.array(x.unique())).to_dict()" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "metadata": { + "execution": { + "iopub.execute_input": "2023-01-22T15:37:35.885246Z", + "iopub.status.busy": "2023-01-22T15:37:35.884025Z", + "iopub.status.idle": "2023-01-22T15:37:35.891070Z", + "shell.execute_reply": "2023-01-22T15:37:35.889851Z", + "shell.execute_reply.started": "2023-01-22T15:37:35.885203Z" + } + }, + "outputs": [], + "source": [ + "def get_negative_sample(pos_i, uid_i, distance_dict):\n", + " \n", + " neg_i = np.random.choice(distance_dict[pos_i])\n", + " \n", + " return neg_i" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "metadata": { + "execution": { + "iopub.execute_input": "2023-01-22T12:39:49.263942Z", + "iopub.status.busy": "2023-01-22T12:39:49.263310Z", + "iopub.status.idle": "2023-01-22T12:39:49.273779Z", + "shell.execute_reply": "2023-01-22T12:39:49.272870Z", + "shell.execute_reply.started": "2023-01-22T12:39:49.263906Z" + } + }, + "outputs": [], + "source": [ + "# функция для нахождения отрицательных item\n", + "\n", + "# очень долго работает \n", + "def get_negative_sample_old(pos_i, uid_i, fts, iids_, user_interactions):\n", + " \n", + " # айтемы , с которыми взаимодействовал юзер, их исключим\n", + " user_watched_items = user_interactions[uid_i]\n", + " \n", + " # векторы айтмов, которые не смотрел юзер, и по которым посчитаем евклидовы дистанции,\n", + " # чтобы найти самые непохожие на тот айтем, который юзер смотрел\n", + "\n", + " # из всего списка item вычитаем те, с которыми пользователь взаимодействовал\n", + " # список item которых пользователь не видел\n", + " inters = np.setdiff1d(iids_, user_watched_items, assume_unique=True)\n", + " \n", + " fts_ = fts.loc[inters].sample(n = 100)\n", + " \n", + " # вектор позитивного айтема \n", + " pos_item_fts = pd.DataFrame(fts.loc[pos_i, :]).T\n", + " \n", + " # считаем дистанции\n", + " dists = ED(fts_, pos_item_fts)\n", + " \n", + " # берем десять самых непохожих и непросмотренных юзером айтемов и из них случайно выбираем один \n", + " fts_['dists'] = dists\n", + " fts_ = fts_[['dists']]\n", + " neg_candidates = fts_.sort_values(by = \"dists\")[-10:].index\n", + " \n", + " neg_i = np.random.choice(neg_candidates)\n", + " \n", + " return neg_i" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "metadata": { + "ExecuteTime": { + "end_time": "2021-10-28T18:41:03.755386Z", + "start_time": "2021-10-28T18:41:03.750664Z" + }, + "execution": { + "iopub.execute_input": "2023-01-22T15:37:53.122995Z", + "iopub.status.busy": "2023-01-22T15:37:53.122612Z", + "iopub.status.idle": "2023-01-22T15:37:53.132222Z", + "shell.execute_reply": "2023-01-22T15:37:53.130866Z", + "shell.execute_reply.started": "2023-01-22T15:37:53.122960Z" + }, + "id": "7829878b" + }, + "outputs": [], + "source": [ + "def generator(items, users, interactions, batch_size=1024):\n", + " while True:\n", + " uid_meta = []\n", + " uid_interaction = []\n", + " pos = []\n", + " neg = []\n", + " for _ in range(batch_size):\n", + " # берем рандомный uid\n", + " uid_i = randint(0, interactions.shape[0]-1)\n", + " # id хорошего айтема\n", + " pos_i = np.random.choice(range(interactions.shape[1]), p=interactions[uid_i])\n", + " # id плохого айтема\n", + " #neg_i = np.random.choice(range(interactions.shape[1]))\n", + " #neg_i = get_negative_sample_old(pos_i, uid_i, fts, iids_, user_interactions)\n", + " neg_i = get_negative_sample(pos_i, uid_i, distance_dict)\n", + " # фичи юзера\n", + " uid_meta.append(users.iloc[uid_i])\n", + " # вектор айтемов, с которыми юзер взаимодействовал\n", + " uid_interaction.append(interactions_vec[uid_i])\n", + " # фичи хорошего айтема\n", + " pos.append(items.iloc[pos_i])\n", + " # фичи плохого айтема\n", + " neg.append(items.iloc[neg_i])\n", + " \n", + " yield [np.array(uid_meta), np.array(uid_interaction), np.array(pos), np.array(neg)], [np.array(uid_meta), np.array(uid_interaction)]\n" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "metadata": { + "ExecuteTime": { + "end_time": "2021-10-28T18:41:16.386864Z", + "start_time": "2021-10-28T18:41:03.756363Z" + }, + "execution": { + "iopub.execute_input": "2023-01-22T15:37:57.807501Z", + "iopub.status.busy": "2023-01-22T15:37:57.807136Z", + "iopub.status.idle": "2023-01-22T15:38:48.900316Z", + "shell.execute_reply": "2023-01-22T15:38:48.899211Z", + "shell.execute_reply.started": "2023-01-22T15:37:57.807471Z" + }, + "id": "af9d3c3b", + "outputId": "1040f567-f64a-4ccb-91a8-48034694dfdc" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "вектор фичей юзера: (1024, 19)\n", + "вектор взаимодействий юзера с айтемами: (1024, 6897)\n", + "вектор 'хорошего' айтема: (1024, 9196)\n", + "вектор 'плохого' айтема: (1024, 9196)\n", + "\n", + "вектор фичей юзера: (1024, 19)\n", + "вектор взаимодействий юзера с айтемами: (1024, 6897)\n" + ] + } + ], + "source": [ + "# инициализируем генератор\n", + "gen = generator(items=items_ohe_df.drop([\"item_id\"], axis=1), \n", + " users=users_ohe_df.drop([\"user_id\"], axis=1), \n", + " interactions=interactions_vec, batch_size=1024)\n", + "\n", + "ret = next(gen)\n", + "\n", + "\n", + "print(f\"вектор фичей юзера: {ret[0][0].shape}\")\n", + "print(f\"вектор взаимодействий юзера с айтемами: {ret[0][1].shape}\")\n", + "print(f\"вектор 'хорошего' айтема: {ret[0][2].shape}\")\n", + "print(f\"вектор 'плохого' айтема: {ret[0][3].shape}\")\n", + "print()\n", + "print(f\"вектор фичей юзера: {ret[1][0].shape}\")\n", + "print(f\"вектор взаимодействий юзера с айтемами: {ret[1][1].shape}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "8bcc3e80" + }, + "source": [ + "##Генаратор, который будет использовать информацию о качестве взаимодействия юзеров с айтемами для более репрезентативного сэмплирования\n" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "metadata": { + "ExecuteTime": { + "end_time": "2021-10-28T18:41:16.493030Z", + "start_time": "2021-10-28T18:41:16.388592Z" + }, + "execution": { + "iopub.execute_input": "2023-01-22T15:38:48.903047Z", + "iopub.status.busy": "2023-01-22T15:38:48.902586Z", + "iopub.status.idle": "2023-01-22T15:38:49.025937Z", + "shell.execute_reply": "2023-01-22T15:38:49.024831Z", + "shell.execute_reply.started": "2023-01-22T15:38:48.902992Z" + }, + "id": "967b819f", + "outputId": "2f7a5885-dcb3-4ab8-80f8-57a21635595d" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "N_FACTORS: 128\n", + "ITEM_MODEL_SHAPE: (9196,)\n", + "USER_META_MODEL_SHAPE: (19,)\n", + "USER_INTERACTION_MODEL_SHAPE: (6897,)\n" + ] + } + ], + "source": [ + "N_FACTORS = 128\n", + "\n", + "# в датасетах есть столбец user_id/item_id, помним, что он не является фичей для обучения!\n", + "ITEM_MODEL_SHAPE = (items_ohe_df.drop([\"item_id\"], axis=1).shape[1], ) \n", + "USER_META_MODEL_SHAPE = (users_ohe_df.drop([\"user_id\"], axis=1).shape[1], )\n", + "\n", + "USER_INTERACTION_MODEL_SHAPE = (interactions_vec.shape[1], )\n", + "\n", + "print(f\"N_FACTORS: {N_FACTORS}\")\n", + "print(f\"ITEM_MODEL_SHAPE: {ITEM_MODEL_SHAPE}\")\n", + "print(f\"USER_META_MODEL_SHAPE: {USER_META_MODEL_SHAPE}\")\n", + "print(f\"USER_INTERACTION_MODEL_SHAPE: {USER_INTERACTION_MODEL_SHAPE}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "metadata": { + "ExecuteTime": { + "end_time": "2021-10-28T18:41:16.816499Z", + "start_time": "2021-10-28T18:41:16.494387Z" + }, + "execution": { + "iopub.execute_input": "2023-01-22T15:38:49.027755Z", + "iopub.status.busy": "2023-01-22T15:38:49.027467Z", + "iopub.status.idle": "2023-01-22T15:38:53.151538Z", + "shell.execute_reply": "2023-01-22T15:38:53.150595Z", + "shell.execute_reply.started": "2023-01-22T15:38:49.027729Z" + }, + "id": "de649a01" + }, + "outputs": [], + "source": [ + "def item_model(n_factors=N_FACTORS):\n", + " # входной слой\n", + " inp = keras.layers.Input(shape=ITEM_MODEL_SHAPE)\n", + " \n", + " # полносвязный слой\n", + " layer_1 = keras.layers.Dense(N_FACTORS, activation='elu', use_bias=False,\n", + " kernel_regularizer=keras.regularizers.l2(1e-6),\n", + " activity_regularizer=keras.regularizers.l2(l2=1e-6))(inp)\n", + "\n", + " # делаем residual connection - складываем два слоя, \n", + " # чтобы градиенты не затухали во время обучения\n", + " layer_2 = keras.layers.Dense(N_FACTORS, activation='elu', use_bias=False,\n", + " kernel_regularizer=keras.regularizers.l2(1e-6),\n", + " activity_regularizer=keras.regularizers.l2(l2=1e-6))(layer_1)\n", + " \n", + " add = keras.layers.Add()([layer_1, layer_2])\n", + " \n", + " # выходной слой\n", + " out = keras.layers.Dense(N_FACTORS, activation='linear', use_bias=False,\n", + " kernel_regularizer=keras.regularizers.l2(1e-6),\n", + " activity_regularizer=keras.regularizers.l2(l2=1e-6))(add)\n", + " \n", + " return keras.models.Model(inp, out)\n", + "\n", + "\n", + "def user_model(n_factors=N_FACTORS):\n", + " # входной слой для вектора фичей юзера (из users_ohe_df)\n", + " inp_meta = keras.layers.Input(shape=USER_META_MODEL_SHAPE)\n", + " # входной слой для вектора просмотров (из iteractions_vec)\n", + " inp_interaction = keras.layers.Input(shape=USER_INTERACTION_MODEL_SHAPE)\n", + "\n", + " # полносвязный слой\n", + " layer_1_meta = keras.layers.Dense(N_FACTORS, activation='elu', use_bias=False,\n", + " kernel_regularizer=keras.regularizers.l2(1e-6),\n", + " activity_regularizer=keras.regularizers.l2(l2=1e-6))(inp_meta)\n", + "\n", + " layer_1_interaction = keras.layers.Dense(N_FACTORS, activation='elu', use_bias=False,\n", + " kernel_regularizer=keras.regularizers.l2(1e-6),\n", + " activity_regularizer=keras.regularizers.l2(l2=1e-6))(inp_interaction)\n", + "\n", + " # делаем residual connection - складываем два слоя,\n", + " # чтобы градиенты не затухали во время обучения\n", + " layer_2_meta = keras.layers.Dense(N_FACTORS, activation='elu', use_bias=False,\n", + " kernel_regularizer=keras.regularizers.l2(1e-6),\n", + " activity_regularizer=keras.regularizers.l2(l2=1e-6))(layer_1_meta)\n", + " \n", + "\n", + " add = keras.layers.Add()([layer_1_meta, layer_2_meta])\n", + " \n", + " # конкатенируем вектор фичей с вектором просмотров\n", + " concat_meta_interaction = keras.layers.Concatenate()([add, layer_1_interaction])\n", + " \n", + " # выходной слой\n", + " out = keras.layers.Dense(N_FACTORS, activation='linear', use_bias=False,\n", + " kernel_regularizer=keras.regularizers.l2(1e-6),\n", + " activity_regularizer=keras.regularizers.l2(l2=1e-6))(concat_meta_interaction)\n", + " \n", + " return keras.models.Model([inp_meta, inp_interaction], out)\n", + "\n", + "# инициализируем модели юзера и айтема\n", + "i2v = item_model()\n", + "u2v = user_model()\n", + "\n", + "# вход для вектора фичей юзера (из users_ohe_df)\n", + "ancor_meta_in = keras.layers.Input(shape=USER_META_MODEL_SHAPE)\n", + "# вход для вектора просмотра юзера (из interactions_vec)\n", + "ancor_interaction_in = keras.layers.Input(shape=USER_INTERACTION_MODEL_SHAPE)\n", + "\n", + "# вход для вектора \"хорошего\" айтема\n", + "pos_in = keras.layers.Input(shape=ITEM_MODEL_SHAPE)\n", + "# вход для вектора \"плохого\" айтема\n", + "neg_in = keras.layers.Input(shape=ITEM_MODEL_SHAPE)\n", + "\n", + "# получаем вектор юзера\n", + "ancor = u2v([ancor_meta_in, ancor_interaction_in])\n", + "# получаем вектор \"хорошего\" айтема\n", + "pos = i2v(pos_in)\n", + "# получаем вектор \"плохого\" айтема\n", + "neg = i2v(neg_in)\n", + "\n", + "# конкатенируем полученные векторы\n", + "res = keras.layers.Concatenate(name=\"concat_ancor_pos_neg\")([ancor, pos, neg])\n", + "\n", + "# собираем модель\n", + "model = keras.models.Model([ancor_meta_in, ancor_interaction_in, pos_in, neg_in], res)" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "metadata": { + "ExecuteTime": { + "end_time": "2021-10-28T18:41:16.822662Z", + "start_time": "2021-10-28T18:41:16.817857Z" + }, + "execution": { + "iopub.execute_input": "2023-01-22T15:38:53.154784Z", + "iopub.status.busy": "2023-01-22T15:38:53.154419Z", + "iopub.status.idle": "2023-01-22T15:38:53.789679Z", + "shell.execute_reply": "2023-01-22T15:38:53.788675Z", + "shell.execute_reply.started": "2023-01-22T15:38:53.154748Z" + }, + "id": "e912d920" + }, + "outputs": [], + "source": [ + "model_name = 'recsys_resnet_linear'\n", + "\n", + "# логируем процесс обучения в тензорборд\n", + "t_board = keras.callbacks.TensorBoard(log_dir=f'runs/{model_name}')\n", + "\n", + "# уменьшаем learning_rate, если лосс долго не уменьшается (в течение двух эпох)\n", + "decay = keras.callbacks.ReduceLROnPlateau(monitor='loss', patience=2, factor=0.8, verbose=1)\n", + "\n", + "# сохраняем модель после каждой эпохи, если лосс уменьшился\n", + "check = keras.callbacks.ModelCheckpoint(filepath=model_name + '/epoch{epoch}-{loss:.2f}.h5', monitor=\"loss\")\n" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "metadata": { + "ExecuteTime": { + "end_time": "2021-10-28T18:41:16.832365Z", + "start_time": "2021-10-28T18:41:16.824484Z" + }, + "execution": { + "iopub.execute_input": "2023-01-22T15:38:53.792105Z", + "iopub.status.busy": "2023-01-22T15:38:53.791371Z", + "iopub.status.idle": "2023-01-22T15:38:53.808624Z", + "shell.execute_reply": "2023-01-22T15:38:53.807732Z", + "shell.execute_reply.started": "2023-01-22T15:38:53.792041Z" + }, + "id": "f95049f6" + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "WARNING:absl:At this time, the v2.11+ optimizer `tf.keras.optimizers.Adam` runs slowly on M1/M2 Macs, please use the legacy Keras optimizer instead, located at `tf.keras.optimizers.legacy.Adam`.\n", + "WARNING:absl:`lr` is deprecated in Keras optimizer, please use `learning_rate` or use the legacy optimizer, e.g.,tf.keras.optimizers.legacy.Adam.\n" + ] + } + ], + "source": [ + "# компилируем модель, используем оптимайзер Adam и triplet loss\n", + "opt = keras.optimizers.Adam(lr=0.001)\n", + "model.compile(loss=triplet_loss, optimizer=opt)" + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "metadata": { + "ExecuteTime": { + "end_time": "2021-10-28T18:41:16.867472Z", + "start_time": "2021-10-28T18:41:16.833753Z" + }, + "execution": { + "iopub.execute_input": "2023-01-22T15:38:53.811821Z", + "iopub.status.busy": "2023-01-22T15:38:53.811155Z", + "iopub.status.idle": "2023-01-22T15:38:53.852098Z", + "shell.execute_reply": "2023-01-22T15:38:53.851090Z", + "shell.execute_reply.started": "2023-01-22T15:38:53.811786Z" + }, + "id": "fb9382d0", + "outputId": "2eca9a17-1544-4e27-a483-b86d11391767" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Model: \"model_3\"\n", + "__________________________________________________________________________________________________\n", + " Layer (type) Output Shape Param # Connected to \n", + "==================================================================================================\n", + " input_8 (InputLayer) [(None, 9196)] 0 [] \n", + " \n", + " dense_7 (Dense) (None, 128) 1177088 ['input_8[0][0]'] \n", + " \n", + " dense_8 (Dense) (None, 128) 16384 ['dense_7[0][0]'] \n", + " \n", + " add_2 (Add) (None, 128) 0 ['dense_7[0][0]', \n", + " 'dense_8[0][0]'] \n", + " \n", + " dense_9 (Dense) (None, 128) 16384 ['add_2[0][0]'] \n", + " \n", + "==================================================================================================\n", + "Total params: 1209856 (4.62 MB)\n", + "Trainable params: 1209856 (4.62 MB)\n", + "Non-trainable params: 0 (0.00 Byte)\n", + "__________________________________________________________________________________________________\n" + ] + } + ], + "source": [ + "# модель айтема\n", + "item_model().summary()" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "metadata": { + "ExecuteTime": { + "end_time": "2021-10-28T18:41:16.923402Z", + "start_time": "2021-10-28T18:41:16.868877Z" + }, + "execution": { + "iopub.execute_input": "2023-01-22T15:38:53.854198Z", + "iopub.status.busy": "2023-01-22T15:38:53.853594Z", + "iopub.status.idle": "2023-01-22T15:38:53.908177Z", + "shell.execute_reply": "2023-01-22T15:38:53.907222Z", + "shell.execute_reply.started": "2023-01-22T15:38:53.854161Z" + }, + "id": "286149d1", + "outputId": "4284ba09-05ef-4963-c637-67e919701d19" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Model: \"model_4\"\n", + "__________________________________________________________________________________________________\n", + " Layer (type) Output Shape Param # Connected to \n", + "==================================================================================================\n", + " input_9 (InputLayer) [(None, 19)] 0 [] \n", + " \n", + " dense_10 (Dense) (None, 128) 2432 ['input_9[0][0]'] \n", + " \n", + " dense_12 (Dense) (None, 128) 16384 ['dense_10[0][0]'] \n", + " \n", + " input_10 (InputLayer) [(None, 6897)] 0 [] \n", + " \n", + " add_3 (Add) (None, 128) 0 ['dense_10[0][0]', \n", + " 'dense_12[0][0]'] \n", + " \n", + " dense_11 (Dense) (None, 128) 882816 ['input_10[0][0]'] \n", + " \n", + " concatenate_1 (Concatenate (None, 256) 0 ['add_3[0][0]', \n", + " ) 'dense_11[0][0]'] \n", + " \n", + " dense_13 (Dense) (None, 128) 32768 ['concatenate_1[0][0]'] \n", + " \n", + "==================================================================================================\n", + "Total params: 934400 (3.56 MB)\n", + "Trainable params: 934400 (3.56 MB)\n", + "Non-trainable params: 0 (0.00 Byte)\n", + "__________________________________________________________________________________________________\n" + ] + } + ], + "source": [ + "# модель юзера\n", + "user_model().summary()" + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "metadata": { + "ExecuteTime": { + "end_time": "2021-10-28T18:41:16.929341Z", + "start_time": "2021-10-28T18:41:16.924663Z" + }, + "execution": { + "iopub.execute_input": "2023-01-22T15:38:53.909970Z", + "iopub.status.busy": "2023-01-22T15:38:53.909370Z", + "iopub.status.idle": "2023-01-22T15:38:53.917202Z", + "shell.execute_reply": "2023-01-22T15:38:53.916103Z", + "shell.execute_reply.started": "2023-01-22T15:38:53.909934Z" + }, + "id": "d9f25a3f", + "outputId": "6f9a3700-4420-4345-8331-82f7207b566b" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Model: \"model_2\"\n", + "__________________________________________________________________________________________________\n", + " Layer (type) Output Shape Param # Connected to \n", + "==================================================================================================\n", + " input_4 (InputLayer) [(None, 19)] 0 [] \n", + " \n", + " input_5 (InputLayer) [(None, 6897)] 0 [] \n", + " \n", + " input_6 (InputLayer) [(None, 9196)] 0 [] \n", + " \n", + " input_7 (InputLayer) [(None, 9196)] 0 [] \n", + " \n", + " model_1 (Functional) (None, 128) 934400 ['input_4[0][0]', \n", + " 'input_5[0][0]'] \n", + " \n", + " model (Functional) (None, 128) 1209856 ['input_6[0][0]', \n", + " 'input_7[0][0]'] \n", + " \n", + " concat_ancor_pos_neg (Conc (None, 384) 0 ['model_1[0][0]', \n", + " atenate) 'model[0][0]', \n", + " 'model[1][0]'] \n", + " \n", + "==================================================================================================\n", + "Total params: 2144256 (8.18 MB)\n", + "Trainable params: 2144256 (8.18 MB)\n", + "Non-trainable params: 0 (0.00 Byte)\n", + "__________________________________________________________________________________________________\n" + ] + } + ], + "source": [ + "# общая модель\n", + "model.summary()" + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "metadata": { + "ExecuteTime": { + "end_time": "2021-10-28T19:15:21.657529Z", + "start_time": "2021-10-28T19:15:16.365923Z" + }, + "execution": { + "iopub.execute_input": "2023-01-22T15:38:53.919463Z", + "iopub.status.busy": "2023-01-22T15:38:53.918611Z", + "iopub.status.idle": "2023-01-22T16:17:01.448835Z", + "shell.execute_reply": "2023-01-22T16:17:01.447888Z", + "shell.execute_reply.started": "2023-01-22T15:38:53.919424Z" + }, + "id": "99d50830", + "outputId": "cee25813-2173-460f-e6f2-024d75d1db08" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 1/30\n", + "100/100 [==============================] - 42s 418ms/step - loss: 0.4123 - lr: 0.0010\n", + "Epoch 2/30\n", + "100/100 [==============================] - 42s 425ms/step - loss: 0.2973 - lr: 0.0010\n", + "Epoch 3/30\n", + "100/100 [==============================] - 42s 423ms/step - loss: 0.2713 - lr: 0.0010\n", + "Epoch 4/30\n", + "100/100 [==============================] - 642s 6s/step - loss: 0.2240 - lr: 0.0010\n", + "Epoch 5/30\n", + "100/100 [==============================] - 41s 413ms/step - loss: 0.2200 - lr: 0.0010\n", + "Epoch 6/30\n", + "100/100 [==============================] - 41s 415ms/step - loss: 0.1929 - lr: 0.0010\n", + "Epoch 7/30\n", + "100/100 [==============================] - 42s 427ms/step - loss: 0.1727 - lr: 0.0010\n", + "Epoch 8/30\n", + "100/100 [==============================] - 42s 423ms/step - loss: 0.1849 - lr: 0.0010\n", + "Epoch 9/30\n", + "100/100 [==============================] - 41s 418ms/step - loss: 0.1594 - lr: 0.0010\n", + "Epoch 10/30\n", + "100/100 [==============================] - 42s 420ms/step - loss: 0.1485 - lr: 0.0010\n", + "Epoch 11/30\n", + "100/100 [==============================] - 41s 417ms/step - loss: 0.1523 - lr: 0.0010\n", + "Epoch 12/30\n", + "100/100 [==============================] - 41s 415ms/step - loss: 0.1328 - lr: 0.0010\n", + "Epoch 13/30\n", + "100/100 [==============================] - 41s 419ms/step - loss: 0.1407 - lr: 0.0010\n", + "Epoch 14/30\n", + "100/100 [==============================] - ETA: 0s - loss: 0.1524\n", + "Epoch 14: ReduceLROnPlateau reducing learning rate to 0.000800000037997961.\n", + "100/100 [==============================] - 42s 421ms/step - loss: 0.1524 - lr: 0.0010\n", + "Epoch 15/30\n", + "100/100 [==============================] - 42s 420ms/step - loss: 0.1304 - lr: 8.0000e-04\n", + "Epoch 16/30\n", + "100/100 [==============================] - 42s 421ms/step - loss: 0.1305 - lr: 8.0000e-04\n", + "Epoch 17/30\n", + "100/100 [==============================] - 41s 415ms/step - loss: 0.1299 - lr: 8.0000e-04\n", + "Epoch 18/30\n", + "100/100 [==============================] - 41s 416ms/step - loss: 0.1332 - lr: 8.0000e-04\n", + "Epoch 19/30\n", + "100/100 [==============================] - 578s 6s/step - loss: 0.1128 - lr: 8.0000e-04\n", + "Epoch 20/30\n", + "100/100 [==============================] - 298s 3s/step - loss: 0.1168 - lr: 8.0000e-04\n", + "Epoch 21/30\n", + "100/100 [==============================] - ETA: 0s - loss: 0.1145\n", + "Epoch 21: ReduceLROnPlateau reducing learning rate to 0.0006400000303983689.\n", + "100/100 [==============================] - 42s 424ms/step - loss: 0.1145 - lr: 8.0000e-04\n", + "Epoch 22/30\n", + "100/100 [==============================] - 42s 423ms/step - loss: 0.1230 - lr: 6.4000e-04\n", + "Epoch 23/30\n", + "100/100 [==============================] - 42s 421ms/step - loss: 0.1108 - lr: 6.4000e-04\n", + "Epoch 24/30\n", + "100/100 [==============================] - 42s 422ms/step - loss: 0.0976 - lr: 6.4000e-04\n", + "Epoch 25/30\n", + "100/100 [==============================] - 42s 422ms/step - loss: 0.1116 - lr: 6.4000e-04\n", + "Epoch 26/30\n", + "100/100 [==============================] - ETA: 0s - loss: 0.0996\n", + "Epoch 26: ReduceLROnPlateau reducing learning rate to 0.0005120000336319208.\n", + "100/100 [==============================] - 43s 430ms/step - loss: 0.0996 - lr: 6.4000e-04\n", + "Epoch 27/30\n", + "100/100 [==============================] - 43s 433ms/step - loss: 0.1010 - lr: 5.1200e-04\n", + "Epoch 28/30\n", + "100/100 [==============================] - ETA: 0s - loss: 0.0984\n", + "Epoch 28: ReduceLROnPlateau reducing learning rate to 0.00040960004553198815.\n", + "100/100 [==============================] - 42s 429ms/step - loss: 0.0984 - lr: 5.1200e-04\n", + "Epoch 29/30\n", + "100/100 [==============================] - 43s 430ms/step - loss: 0.0865 - lr: 4.0960e-04\n", + "Epoch 30/30\n", + "100/100 [==============================] - 43s 433ms/step - loss: 0.1049 - lr: 4.0960e-04\n" + ] + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 46, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# начинаем обучение, не забывая дропнуть столбцы item_id и user_id \n", + "# из датафреймов при инициализации генератора.\n", + "\n", + "# batch_size можно (и лучше) поставить побольше, если вы не органичены в ресурсах\n", + "\n", + "model.fit(generator(items=items_ohe_df.drop([\"item_id\"], axis=1), \n", + " users=users_ohe_df.drop([\"user_id\"], axis=1), \n", + " interactions=interactions_vec,\n", + " batch_size=16), \n", + " steps_per_epoch=100, \n", + " epochs=30, \n", + " initial_epoch=0,\n", + " callbacks=[decay, t_board, check]\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "metadata": { + "execution": { + "iopub.execute_input": "2023-01-22T16:17:01.453483Z", + "iopub.status.busy": "2023-01-22T16:17:01.453198Z", + "iopub.status.idle": "2023-01-22T16:17:01.486783Z", + "shell.execute_reply": "2023-01-22T16:17:01.485812Z", + "shell.execute_reply.started": "2023-01-22T16:17:01.453458Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "WARNING:tensorflow:Compiled the loaded model, but the compiled metrics have yet to be built. `model.compile_metrics` will be empty until you train or evaluate the model.\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "WARNING:tensorflow:Compiled the loaded model, but the compiled metrics have yet to be built. `model.compile_metrics` will be empty until you train or evaluate the model.\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "WARNING:tensorflow:Compiled the loaded model, but the compiled metrics have yet to be built. `model.compile_metrics` will be empty until you train or evaluate the model.\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "WARNING:tensorflow:Compiled the loaded model, but the compiled metrics have yet to be built. `model.compile_metrics` will be empty until you train or evaluate the model.\n" + ] + } + ], + "source": [ + "i2v.save('i2v.hdf5')\n", + "u2v.save('u2v.hdf5')" + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "metadata": { + "ExecuteTime": { + "end_time": "2021-10-28T19:15:26.511958Z", + "start_time": "2021-10-28T19:15:26.151899Z" + }, + "execution": { + "iopub.execute_input": "2023-01-22T16:24:28.685695Z", + "iopub.status.busy": "2023-01-22T16:24:28.685290Z", + "iopub.status.idle": "2023-01-22T16:24:30.854186Z", + "shell.execute_reply": "2023-01-22T16:24:30.853120Z", + "shell.execute_reply.started": "2023-01-22T16:24:28.685657Z" + }, + "id": "94d23f62", + "outputId": "4a500ea7-fa38-4455-a51a-2bd0113fa2f2" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1/1 [==============================] - 0s 129ms/step\n", + "1/1 [==============================] - 0s 29ms/step\n" + ] + }, + { + "data": { + "text/plain": [ + "array([[0.76927984]], dtype=float32)" + ] + }, + "execution_count": 49, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# берем рандомного юзера\n", + "rand_uid = np.random.choice(list(users_ohe_df.index))\n", + "\n", + "# получаем фичи юзера и вектор его просмотров айтемов\n", + "user_meta_feats = users_ohe_df.drop([\"user_id\"], axis=1).iloc[rand_uid]\n", + "user_interaction_vec = interactions_vec[rand_uid]\n", + "\n", + "# берем рандомный айтем\n", + "rand_iid = np.random.choice(list(items_ohe_df.index))\n", + "# получаем фичи айтема\n", + "item_feats = items_ohe_df.drop([\"item_id\"], axis=1).iloc[rand_iid]\n", + "\n", + "# получаем вектор юзера\n", + "user_vec = u2v.predict([np.array(user_meta_feats).reshape(1, -1), \n", + " np.array(user_interaction_vec).reshape(1, -1)])\n", + "\n", + "# и вектор айтема\n", + "item_vec = i2v.predict(np.array(item_feats).reshape(1, -1))\n", + "\n", + "# считаем расстояние между вектором юзера и вектором айтема\n", + "from sklearn.metrics.pairwise import euclidean_distances as ED\n", + "\n", + "ED(user_vec, item_vec)" + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "metadata": { + "ExecuteTime": { + "end_time": "2021-10-28T19:15:28.951471Z", + "start_time": "2021-10-28T19:15:27.763367Z" + }, + "execution": { + "iopub.execute_input": "2023-01-22T16:24:35.398767Z", + "iopub.status.busy": "2023-01-22T16:24:35.398342Z", + "iopub.status.idle": "2023-01-22T16:24:37.179114Z", + "shell.execute_reply": "2023-01-22T16:24:37.177336Z", + "shell.execute_reply.started": "2023-01-22T16:24:35.398731Z" + }, + "id": "d537d3e8", + "outputId": "6bdce370-c348-4f0b-cd4f-3cbb0ec3d019" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "216/216 [==============================] - 0s 883us/step\n" + ] + } + ], + "source": [ + "# получаем фичи всех айтемов\n", + "items_feats = items_ohe_df.drop([\"item_id\"], axis=1).to_numpy()\n", + "# получаем векторы всех айтемов\n", + "items_vecs = i2v.predict(items_feats)\n", + "\n", + "# считаем расстояния\n", + "dists = ED(user_vec, items_vecs)" + ] + }, + { + "cell_type": "code", + "execution_count": 51, + "metadata": { + "execution": { + "iopub.execute_input": "2023-01-22T16:24:37.200481Z", + "iopub.status.busy": "2023-01-22T16:24:37.199790Z", + "iopub.status.idle": "2023-01-22T16:24:37.219685Z", + "shell.execute_reply": "2023-01-22T16:24:37.218365Z", + "shell.execute_reply.started": "2023-01-22T16:24:37.200421Z" + }, + "id": "udY36b_l0okL", + "outputId": "53287102-2434-490e-d668-b8085515d4b8" + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(6897, 128)" + ] + }, + "execution_count": 51, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "items_vecs.shape" + ] + }, + { + "cell_type": "code", + "execution_count": 52, + "metadata": { + "execution": { + "iopub.execute_input": "2023-01-22T16:24:38.051890Z", + "iopub.status.busy": "2023-01-22T16:24:38.051199Z", + "iopub.status.idle": "2023-01-22T16:24:38.063043Z", + "shell.execute_reply": "2023-01-22T16:24:38.061416Z", + "shell.execute_reply.started": "2023-01-22T16:24:38.051840Z" + }, + "id": "XasFl6RN0snT" + }, + "outputs": [], + "source": [ + "users_meta_feats = users_ohe_df.drop([\"user_id\"], axis=1)\n", + "users_interaction_vec = interactions_vec" + ] + }, + { + "cell_type": "code", + "execution_count": 53, + "metadata": { + "execution": { + "iopub.execute_input": "2023-01-22T16:24:38.561257Z", + "iopub.status.busy": "2023-01-22T16:24:38.560146Z", + "iopub.status.idle": "2023-01-22T16:24:38.568144Z", + "shell.execute_reply": "2023-01-22T16:24:38.566777Z", + "shell.execute_reply.started": "2023-01-22T16:24:38.561176Z" + }, + "id": "cntEZU450_MI", + "outputId": "c9aace32-281a-4b0e-8e0d-8b0f1088ce9b" + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(65974, 19)" + ] + }, + "execution_count": 53, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "users_meta_feats.shape" + ] + }, + { + "cell_type": "code", + "execution_count": 54, + "metadata": { + "execution": { + "iopub.execute_input": "2023-01-22T16:24:40.475433Z", + "iopub.status.busy": "2023-01-22T16:24:40.472691Z", + "iopub.status.idle": "2023-01-22T16:24:40.484559Z", + "shell.execute_reply": "2023-01-22T16:24:40.483559Z", + "shell.execute_reply.started": "2023-01-22T16:24:40.475392Z" + }, + "id": "kQ1EZolS1B1Y", + "outputId": "ca9dc5eb-5519-4c75-f1d8-4945941a46d1" + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(65974, 6897)" + ] + }, + "execution_count": 54, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "users_interaction_vec.shape" + ] + }, + { + "cell_type": "code", + "execution_count": 55, + "metadata": { + "execution": { + "iopub.execute_input": "2023-01-22T16:24:40.786186Z", + "iopub.status.busy": "2023-01-22T16:24:40.785775Z", + "iopub.status.idle": "2023-01-22T16:24:40.797826Z", + "shell.execute_reply": "2023-01-22T16:24:40.796580Z", + "shell.execute_reply.started": "2023-01-22T16:24:40.786151Z" + }, + "id": "hKU4MD7M1dp5", + "outputId": "bd79e8e2-8a82-4ff7-d1ac-849e3425c2c8" + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(65974, 19)" + ] + }, + "execution_count": 55, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "np.array(users_meta_feats).shape" + ] + }, + { + "cell_type": "code", + "execution_count": 56, + "metadata": { + "execution": { + "iopub.execute_input": "2023-01-22T16:24:41.775332Z", + "iopub.status.busy": "2023-01-22T16:24:41.774265Z", + "iopub.status.idle": "2023-01-22T16:24:41.780836Z", + "shell.execute_reply": "2023-01-22T16:24:41.779665Z", + "shell.execute_reply.started": "2023-01-22T16:24:41.775281Z" + } + }, + "outputs": [], + "source": [ + "del interactions_vec\n", + "del users_df, interactions_df" + ] + }, + { + "cell_type": "code", + "execution_count": 57, + "metadata": { + "execution": { + "iopub.execute_input": "2023-01-22T16:24:52.966474Z", + "iopub.status.busy": "2023-01-22T16:24:52.965002Z", + "iopub.status.idle": "2023-01-22T16:24:57.402446Z", + "shell.execute_reply": "2023-01-22T16:24:57.401009Z", + "shell.execute_reply.started": "2023-01-22T16:24:52.966417Z" + }, + "id": "x16g5FM21XGJ", + "outputId": "9b43e3e6-f98b-466a-8b0d-c2aeb0ca724e" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "625/625 [==============================] - 1s 809us/step\n", + "625/625 [==============================] - 0s 779us/step\n", + "812/812 [==============================] - 1s 763us/step\n" + ] + } + ], + "source": [ + "users_vec_1 = u2v.predict([np.array(users_meta_feats.iloc[:20000]), \n", + " np.array(users_interaction_vec[:20000])])\n", + "users_vec_2 = u2v.predict([np.array(users_meta_feats.iloc[20000:40000]), \n", + " np.array(users_interaction_vec[20000:40000])])\n", + "users_vec_3 = u2v.predict([np.array(users_meta_feats.iloc[40000:]), \n", + " np.array(users_interaction_vec[40000:])])\n", + "users_vec = np.concatenate((users_vec_1, users_vec_2, users_vec_3))" + ] + }, + { + "cell_type": "code", + "execution_count": 58, + "metadata": { + "execution": { + "iopub.execute_input": "2023-01-22T16:25:54.668894Z", + "iopub.status.busy": "2023-01-22T16:25:54.667629Z", + "iopub.status.idle": "2023-01-22T16:25:54.674606Z", + "shell.execute_reply": "2023-01-22T16:25:54.673447Z", + "shell.execute_reply.started": "2023-01-22T16:25:54.668856Z" + } + }, + "outputs": [], + "source": [ + "del users_vec_1, users_vec_2, users_vec_3, users_interaction_vec" + ] + }, + { + "cell_type": "code", + "execution_count": 59, + "metadata": { + "execution": { + "iopub.execute_input": "2023-01-22T16:25:57.451831Z", + "iopub.status.busy": "2023-01-22T16:25:57.451189Z", + "iopub.status.idle": "2023-01-22T16:25:57.458982Z", + "shell.execute_reply": "2023-01-22T16:25:57.457745Z", + "shell.execute_reply.started": "2023-01-22T16:25:57.451795Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(65974, 128)" + ] + }, + "execution_count": 59, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "users_vec.shape" + ] + }, + { + "cell_type": "code", + "execution_count": 60, + "metadata": { + "execution": { + "iopub.execute_input": "2023-01-22T16:20:21.497209Z", + "iopub.status.busy": "2023-01-22T16:20:21.496765Z", + "iopub.status.idle": "2023-01-22T16:20:21.504388Z", + "shell.execute_reply": "2023-01-22T16:20:21.503250Z", + "shell.execute_reply.started": "2023-01-22T16:20:21.497158Z" + }, + "id": "G4pntPu10ogl", + "outputId": "557b6f56-dff5-46e9-da97-569001c59c79" + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(6897, 128)" + ] + }, + "execution_count": 60, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "items_vecs.shape" + ] + }, + { + "cell_type": "code", + "execution_count": 61, + "metadata": { + "execution": { + "iopub.execute_input": "2023-01-22T16:26:12.212846Z", + "iopub.status.busy": "2023-01-22T16:26:12.212440Z", + "iopub.status.idle": "2023-01-22T16:26:19.980077Z", + "shell.execute_reply": "2023-01-22T16:26:19.978704Z", + "shell.execute_reply.started": "2023-01-22T16:26:12.212812Z" + }, + "id": "hnUX3Yte2Jcw" + }, + "outputs": [], + "source": [ + "dists = ED(users_vec, items_vecs)" + ] + }, + { + "cell_type": "code", + "execution_count": 62, + "metadata": { + "execution": { + "iopub.execute_input": "2023-01-22T16:26:33.221953Z", + "iopub.status.busy": "2023-01-22T16:26:33.220783Z", + "iopub.status.idle": "2023-01-22T16:26:33.231255Z", + "shell.execute_reply": "2023-01-22T16:26:33.229877Z", + "shell.execute_reply.started": "2023-01-22T16:26:33.221902Z" + }, + "id": "MDgiwnnu2KHk", + "outputId": "ae9eeb6f-8a29-4195-8bdf-eeaa51b6d049" + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(65974, 6897)" + ] + }, + "execution_count": 62, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "dists.shape" + ] + }, + { + "cell_type": "code", + "execution_count": 63, + "metadata": { + "execution": { + "iopub.execute_input": "2023-01-22T16:26:36.257959Z", + "iopub.status.busy": "2023-01-22T16:26:36.257347Z", + "iopub.status.idle": "2023-01-22T16:26:45.531254Z", + "shell.execute_reply": "2023-01-22T16:26:45.530120Z", + "shell.execute_reply.started": "2023-01-22T16:26:36.257910Z" + }, + "id": "Ru8IQwSV2UrB" + }, + "outputs": [], + "source": [ + "top10_iids_1 = np.argsort(dists[:20000], axis=1)[:,:10]\n", + "top10_iids_2 = np.argsort(dists[20000:40000], axis=1)[:,:10]\n", + "top10_iids_3 = np.argsort(dists[40000:], axis=1)[:,:10]\n", + "top10_iids = np.concatenate((top10_iids_1, top10_iids_2, top10_iids_3))" + ] + }, + { + "cell_type": "code", + "execution_count": 64, + "metadata": { + "execution": { + "iopub.execute_input": "2023-01-22T16:28:25.544273Z", + "iopub.status.busy": "2023-01-22T16:28:25.543272Z", + "iopub.status.idle": "2023-01-22T16:28:25.551809Z", + "shell.execute_reply": "2023-01-22T16:28:25.550511Z", + "shell.execute_reply.started": "2023-01-22T16:28:25.544233Z" + }, + "id": "pAzg23jU3TSo", + "outputId": "baeb6ea9-ca6f-4bb9-da7b-3fc16669db23" + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(65974, 10)" + ] + }, + "execution_count": 64, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "top10_iids.reshape(dists.shape[0], 10).shape" + ] + }, + { + "cell_type": "code", + "execution_count": 65, + "metadata": { + "execution": { + "iopub.execute_input": "2023-01-22T16:28:37.182827Z", + "iopub.status.busy": "2023-01-22T16:28:37.182088Z", + "iopub.status.idle": "2023-01-22T16:28:37.190183Z", + "shell.execute_reply": "2023-01-22T16:28:37.188831Z", + "shell.execute_reply.started": "2023-01-22T16:28:37.182788Z" + }, + "id": "ehH1-C-S6yE9", + "outputId": "5a08578e-7bc1-404a-db00-5d2114b7ad28" + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(65974, 10)" + ] + }, + "execution_count": 65, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "top10_iids.shape" + ] + }, + { + "cell_type": "code", + "execution_count": 66, + "metadata": { + "execution": { + "iopub.execute_input": "2023-01-22T16:28:47.517537Z", + "iopub.status.busy": "2023-01-22T16:28:47.516704Z", + "iopub.status.idle": "2023-01-22T16:28:47.800629Z", + "shell.execute_reply": "2023-01-22T16:28:47.799272Z", + "shell.execute_reply.started": "2023-01-22T16:28:47.517501Z" + }, + "id": "srptkYsFsk1V" + }, + "outputs": [], + "source": [ + "top10_iids_item = [iid_to_item_id[iid] for iid in top10_iids.reshape(-1)]" + ] + }, + { + "cell_type": "code", + "execution_count": 67, + "metadata": { + "execution": { + "iopub.execute_input": "2023-01-22T16:28:51.654826Z", + "iopub.status.busy": "2023-01-22T16:28:51.653959Z", + "iopub.status.idle": "2023-01-22T16:28:51.700602Z", + "shell.execute_reply": "2023-01-22T16:28:51.699239Z", + "shell.execute_reply.started": "2023-01-22T16:28:51.654791Z" + }, + "id": "GWCz9zErskwn" + }, + "outputs": [], + "source": [ + "top10_iids_item = np.array(top10_iids_item).reshape(top10_iids.shape)" + ] + }, + { + "cell_type": "code", + "execution_count": 68, + "metadata": { + "execution": { + "iopub.execute_input": "2023-01-22T16:28:57.535876Z", + "iopub.status.busy": "2023-01-22T16:28:57.535194Z", + "iopub.status.idle": "2023-01-22T16:28:57.543046Z", + "shell.execute_reply": "2023-01-22T16:28:57.541704Z", + "shell.execute_reply.started": "2023-01-22T16:28:57.535836Z" + }, + "id": "pNq_brUisknx", + "outputId": "f1980332-ef9e-4920-b470-675a567c815a" + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(65974, 10)" + ] + }, + "execution_count": 68, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "top10_iids_item.shape" + ] + }, + { + "cell_type": "code", + "execution_count": 69, + "metadata": { + "execution": { + "iopub.execute_input": "2023-01-22T16:29:00.647959Z", + "iopub.status.busy": "2023-01-22T16:29:00.646906Z", + "iopub.status.idle": "2023-01-22T16:29:00.657077Z", + "shell.execute_reply": "2023-01-22T16:29:00.655386Z", + "shell.execute_reply.started": "2023-01-22T16:29:00.647919Z" + }, + "id": "z6ussvRSth2h" + }, + "outputs": [], + "source": [ + "df_dssm = pd.DataFrame(columns = ['user_id', 'item_id'])" + ] + }, + { + "cell_type": "code", + "execution_count": 70, + "metadata": { + "execution": { + "iopub.execute_input": "2023-01-22T16:29:08.741226Z", + "iopub.status.busy": "2023-01-22T16:29:08.740780Z", + "iopub.status.idle": "2023-01-22T16:29:08.751118Z", + "shell.execute_reply": "2023-01-22T16:29:08.750073Z", + "shell.execute_reply.started": "2023-01-22T16:29:08.741183Z" + }, + "id": "Y9XvpPzRu82h", + "outputId": "8397c843-1427-444e-af8d-f5c5cd19a80d" + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
user_iditem_id
\n", + "
" + ], + "text/plain": [ + "Empty DataFrame\n", + "Columns: [user_id, item_id]\n", + "Index: []" + ] + }, + "execution_count": 70, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df_dssm.head()" + ] + }, + { + "cell_type": "code", + "execution_count": 71, + "metadata": { + "execution": { + "iopub.execute_input": "2023-01-22T16:29:16.894986Z", + "iopub.status.busy": "2023-01-22T16:29:16.894575Z", + "iopub.status.idle": "2023-01-22T16:29:16.955651Z", + "shell.execute_reply": "2023-01-22T16:29:16.954612Z", + "shell.execute_reply.started": "2023-01-22T16:29:16.894935Z" + }, + "id": "KieINSdwvIu7" + }, + "outputs": [], + "source": [ + "df_dssm = pd.DataFrame({'user_id': [uid_to_user_id[uid] for uid in np.arange(top10_iids_item.shape[0])]})" + ] + }, + { + "cell_type": "code", + "execution_count": 72, + "metadata": { + "execution": { + "iopub.execute_input": "2023-01-22T16:29:18.345819Z", + "iopub.status.busy": "2023-01-22T16:29:18.345404Z", + "iopub.status.idle": "2023-01-22T16:29:18.371714Z", + "shell.execute_reply": "2023-01-22T16:29:18.370527Z", + "shell.execute_reply.started": "2023-01-22T16:29:18.345785Z" + }, + "id": "RSYHUj7IuzT1" + }, + "outputs": [], + "source": [ + "df_dssm['item_id'] = list(top10_iids_item)" + ] + }, + { + "cell_type": "code", + "execution_count": 73, + "metadata": { + "execution": { + "iopub.execute_input": "2023-01-22T16:29:19.355843Z", + "iopub.status.busy": "2023-01-22T16:29:19.355038Z", + "iopub.status.idle": "2023-01-22T16:29:20.100815Z", + "shell.execute_reply": "2023-01-22T16:29:20.099612Z", + "shell.execute_reply.started": "2023-01-22T16:29:19.355801Z" + }, + "id": "xdPs4HY874OZ" + }, + "outputs": [], + "source": [ + "df_dssm = df_dssm.explode('item_id')\n", + "df_dssm['rank'] = df_dssm.groupby('user_id').cumcount() + 1\n", + "df_dssm = df_dssm.groupby('user_id').agg({'item_id': list}).reset_index()" + ] + }, + { + "cell_type": "code", + "execution_count": 74, + "metadata": { + "execution": { + "iopub.execute_input": "2023-01-22T16:29:20.104964Z", + "iopub.status.busy": "2023-01-22T16:29:20.104605Z", + "iopub.status.idle": "2023-01-22T16:29:20.117444Z", + "shell.execute_reply": "2023-01-22T16:29:20.115641Z", + "shell.execute_reply.started": "2023-01-22T16:29:20.104915Z" + }, + "id": "C8kdzzf6wAuz", + "outputId": "df930923-8ad5-45f8-f76c-ab847237802d" + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
user_iditem_id
02[4457, 4151, 142, 9988, 4475, 4740, 9169, 5982...
121[4457, 3734, 9988, 4740, 2954, 2657, 4151, 152...
253[4457, 2220, 4151, 142, 4740, 15297, 2657, 134...
360[4457, 4151, 142, 9988, 3734, 6443, 4740, 2954...
481[4151, 4740, 2657, 4457, 15297, 281, 142, 9169...
\n", + "
" + ], + "text/plain": [ + " user_id item_id\n", + "0 2 [4457, 4151, 142, 9988, 4475, 4740, 9169, 5982...\n", + "1 21 [4457, 3734, 9988, 4740, 2954, 2657, 4151, 152...\n", + "2 53 [4457, 2220, 4151, 142, 4740, 15297, 2657, 134...\n", + "3 60 [4457, 4151, 142, 9988, 3734, 6443, 4740, 2954...\n", + "4 81 [4151, 4740, 2657, 4457, 15297, 281, 142, 9169..." + ] + }, + "execution_count": 74, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df_dssm.head()" + ] + }, + { + "cell_type": "code", + "execution_count": 75, + "metadata": { + "execution": { + "iopub.execute_input": "2023-01-22T16:29:31.350415Z", + "iopub.status.busy": "2023-01-22T16:29:31.349997Z", + "iopub.status.idle": "2023-01-22T16:29:31.715454Z", + "shell.execute_reply": "2023-01-22T16:29:31.714324Z", + "shell.execute_reply.started": "2023-01-22T16:29:31.350382Z" + } + }, + "outputs": [], + "source": [ + "df_dssm.to_csv('dssm_predictions.csv', index = False)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.9.13" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/hw_5_recbool.ipynb b/hw_5_recbool.ipynb new file mode 100644 index 00000000..cf414323 --- /dev/null +++ b/hw_5_recbool.ipynb @@ -0,0 +1 @@ +{"cells":[{"cell_type":"code","execution_count":18,"metadata":{"execution":{"iopub.execute_input":"2023-01-22T17:56:37.034499Z","iopub.status.busy":"2023-01-22T17:56:37.034012Z","iopub.status.idle":"2023-01-22T17:56:37.042666Z","shell.execute_reply":"2023-01-22T17:56:37.041481Z","shell.execute_reply.started":"2023-01-22T17:56:37.034455Z"},"papermill":{"duration":1.244043,"end_time":"2022-11-27T16:33:29.277270","exception":false,"start_time":"2022-11-27T16:33:28.033227","status":"completed"},"tags":[],"trusted":true},"outputs":[],"source":["import ast\n","import json\n","import matplotlib.pyplot as plt\n","import numpy as np\n","import os\n","import pandas as pd\n","import pickle\n","\n","import warnings\n","warnings.filterwarnings('ignore')\n","\n","from collections import Counter\n","from random import randint, random\n","from scipy.sparse import coo_matrix, hstack\n","from sklearn.metrics.pairwise import euclidean_distances, cosine_distances, cosine_similarity"]},{"cell_type":"code","execution_count":20,"metadata":{"execution":{"iopub.execute_input":"2023-01-22T18:02:33.523160Z","iopub.status.busy":"2023-01-22T18:02:33.522766Z","iopub.status.idle":"2023-01-22T18:02:36.724444Z","shell.execute_reply":"2023-01-22T18:02:36.723409Z","shell.execute_reply.started":"2023-01-22T18:02:33.523126Z"},"papermill":{"duration":6.445298,"end_time":"2022-11-27T16:33:35.747539","exception":false,"start_time":"2022-11-27T16:33:29.302241","status":"completed"},"tags":[],"trusted":true},"outputs":[],"source":["interactions_df = pd.read_csv('interactions_processed_kion.csv')\n","users_df = pd.read_csv('users_processed_kion.csv')\n","items_df = pd.read_csv('items_processed_kion.csv')"]},{"cell_type":"code","execution_count":21,"metadata":{"execution":{"iopub.execute_input":"2023-01-22T18:02:41.118088Z","iopub.status.busy":"2023-01-22T18:02:41.117711Z","iopub.status.idle":"2023-01-22T18:02:42.100146Z","shell.execute_reply":"2023-01-22T18:02:42.098848Z","shell.execute_reply.started":"2023-01-22T18:02:41.118057Z"},"papermill":{"duration":0.925082,"end_time":"2022-11-27T16:33:36.677439","exception":false,"start_time":"2022-11-27T16:33:35.752357","status":"completed"},"tags":[],"trusted":true},"outputs":[],"source":["interactions_df['t_dat'] = pd.to_datetime(interactions_df['last_watch_dt'], format=\"%Y-%m-%d\")\n","interactions_df['timestamp'] = interactions_df.t_dat.values.astype(np.int64) // 10 ** 9"]},{"cell_type":"code","execution_count":22,"metadata":{"execution":{"iopub.execute_input":"2023-01-22T18:02:42.111635Z","iopub.status.busy":"2023-01-22T18:02:42.110287Z","iopub.status.idle":"2023-01-22T18:02:42.408437Z","shell.execute_reply":"2023-01-22T18:02:42.407310Z","shell.execute_reply.started":"2023-01-22T18:02:42.111593Z"},"papermill":{"duration":0.284147,"end_time":"2022-11-27T16:33:36.966533","exception":false,"start_time":"2022-11-27T16:33:36.682386","status":"completed"},"tags":[],"trusted":true},"outputs":[],"source":["df = interactions_df[['user_id', 'item_id', 'timestamp']].rename(\n"," columns={'user_id': 'user_id:token', 'item_id': 'item_id:token', 'timestamp': 'timestamp:float'})"]},{"cell_type":"code","execution_count":23,"metadata":{"execution":{"iopub.execute_input":"2023-01-22T18:02:43.902049Z","iopub.status.busy":"2023-01-22T18:02:43.901227Z","iopub.status.idle":"2023-01-22T18:02:43.927071Z","shell.execute_reply":"2023-01-22T18:02:43.925875Z","shell.execute_reply.started":"2023-01-22T18:02:43.902007Z"},"trusted":true},"outputs":[{"data":{"text/html":["
\n","\n","\n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n"," \n","
user_id:tokenitem_id:tokentimestamp:float
017654995061620691200
169931716591622246400
265668371071620518400
386461376381625443200
496486895061619740800
............
5476246648596122251628812800
547624754686296731618272000
5476248697262152971629417600
5476249384202161971618790400
547625031970944361628985600
\n","

5476251 rows × 3 columns

\n","
"],"text/plain":[" user_id:token item_id:token timestamp:float\n","0 176549 9506 1620691200\n","1 699317 1659 1622246400\n","2 656683 7107 1620518400\n","3 864613 7638 1625443200\n","4 964868 9506 1619740800\n","... ... ... ...\n","5476246 648596 12225 1628812800\n","5476247 546862 9673 1618272000\n","5476248 697262 15297 1629417600\n","5476249 384202 16197 1618790400\n","5476250 319709 4436 1628985600\n","\n","[5476251 rows x 3 columns]"]},"execution_count":23,"metadata":{},"output_type":"execute_result"}],"source":["df"]},{"cell_type":"code","execution_count":25,"metadata":{"execution":{"iopub.execute_input":"2023-01-22T18:02:47.909321Z","iopub.status.busy":"2023-01-22T18:02:47.908208Z","iopub.status.idle":"2023-01-22T18:02:54.589560Z","shell.execute_reply":"2023-01-22T18:02:54.588499Z","shell.execute_reply.started":"2023-01-22T18:02:47.909281Z"},"papermill":{"duration":7.834652,"end_time":"2022-11-27T16:33:45.906924","exception":false,"start_time":"2022-11-27T16:33:38.072272","status":"completed"},"tags":[],"trusted":true},"outputs":[],"source":["df.to_csv('recbox_data/recbox_data.inter', index=False, sep='\\t')"]},{"cell_type":"code","execution_count":28,"metadata":{"execution":{"iopub.execute_input":"2023-01-22T18:02:54.592386Z","iopub.status.busy":"2023-01-22T18:02:54.591996Z","iopub.status.idle":"2023-01-22T18:02:55.527789Z","shell.execute_reply":"2023-01-22T18:02:55.526787Z","shell.execute_reply.started":"2023-01-22T18:02:54.592332Z"},"papermill":{"duration":3.067001,"end_time":"2022-11-27T16:34:04.068318","exception":false,"start_time":"2022-11-27T16:34:01.001317","status":"completed"},"tags":[],"trusted":true},"outputs":[],"source":["import logging\n","from logging import getLogger\n","from recbole.config import Config\n","from recbole.data import create_dataset, data_preparation\n","from recbole.model.sequential_recommender import GRU4Rec, Caser\n","from recbole.trainer import Trainer\n","from recbole.utils import init_seed, init_logger\n","from recbole.quick_start import run_recbole"]},{"cell_type":"code","execution_count":29,"metadata":{"execution":{"iopub.execute_input":"2023-01-22T18:04:51.862473Z","iopub.status.busy":"2023-01-22T18:04:51.862041Z","iopub.status.idle":"2023-01-22T18:04:51.900690Z","shell.execute_reply":"2023-01-22T18:04:51.899741Z","shell.execute_reply.started":"2023-01-22T18:04:51.862435Z"},"papermill":{"duration":0.145622,"end_time":"2022-11-27T16:34:04.220395","exception":false,"start_time":"2022-11-27T16:34:04.074773","status":"completed"},"tags":[],"trusted":true},"outputs":[],"source":["parameter_dict = {\n"," 'data_path': '',\n"," 'USER_ID_FIELD': 'user_id',\n"," 'ITEM_ID_FIELD': 'item_id',\n"," 'TIME_FIELD': 'timestamp',\n"," 'device': 'GPU',\n"," 'user_inter_num_interval': \"[40,inf)\",\n"," 'item_inter_num_interval': \"[40,inf)\",\n"," 'load_col': {'inter': ['user_id', 'item_id', 'timestamp']},\n"," 'neg_sampling': None,\n"," 'epochs': 10,\n"," 'verbose': -1,\n"," 'show_progress' : False,\n"," 'eval_args': {\n"," 'split': {'RS': [9, 0, 1]},\n"," 'group_by': 'user',\n"," 'order': 'TO',\n"," 'mode': 'full'}\n","}\n","config = Config(model='MultiVAE', dataset='recbox_data', config_dict=parameter_dict)\n","\n","# init random seed\n","init_seed(config['seed'], config['reproducibility'])\n","\n","# logger initialization\n","init_logger(config)\n","logger = getLogger()\n","# Create handlers\n","c_handler = logging.StreamHandler()\n","c_handler.setLevel(logging.INFO)\n","logger.addHandler(c_handler)\n","\n","# write config info into log\n","# logger.info(config)"]},{"cell_type":"code","execution_count":30,"metadata":{"execution":{"iopub.execute_input":"2023-01-22T18:04:55.538201Z","iopub.status.busy":"2023-01-22T18:04:55.537818Z","iopub.status.idle":"2023-01-22T18:05:32.322220Z","shell.execute_reply":"2023-01-22T18:05:32.321423Z","shell.execute_reply.started":"2023-01-22T18:04:55.538170Z"},"papermill":{"duration":42.583583,"end_time":"2022-11-27T16:34:46.811041","exception":false,"start_time":"2022-11-27T16:34:04.227458","status":"completed"},"tags":[],"trusted":true},"outputs":[{"name":"stderr","output_type":"stream","text":["11 Dec 11:56 INFO recbox_data\n","The number of users: 13355\n","Average actions of users: 63.815710648494836\n","The number of items: 3294\n","Average actions of items: 258.78985727300335\n","The number of inters: 852195\n","The sparsity of the dataset: 98.06281322904924%\n","Remain Fields: ['user_id', 'item_id', 'timestamp']\n","recbox_data\n","The number of users: 13355\n","Average actions of users: 63.815710648494836\n","The number of items: 3294\n","Average actions of items: 258.78985727300335\n","The number of inters: 852195\n","The sparsity of the dataset: 98.06281322904924%\n","Remain Fields: ['user_id', 'item_id', 'timestamp']\n"]}],"source":["dataset = create_dataset(config)\n","logger.info(dataset)"]},{"cell_type":"code","execution_count":31,"metadata":{"execution":{"iopub.execute_input":"2023-01-22T18:05:32.324208Z","iopub.status.busy":"2023-01-22T18:05:32.323852Z","iopub.status.idle":"2023-01-22T18:05:34.256086Z","shell.execute_reply":"2023-01-22T18:05:34.255320Z","shell.execute_reply.started":"2023-01-22T18:05:32.324171Z"},"papermill":{"duration":2.241551,"end_time":"2022-11-27T16:34:49.059852","exception":false,"start_time":"2022-11-27T16:34:46.818301","status":"completed"},"tags":[],"trusted":true},"outputs":[{"name":"stderr","output_type":"stream","text":["11 Dec 11:56 INFO [Training]: train_batch_size = [2048] train_neg_sample_args: [{'distribution': 'uniform', 'sample_num': 1, 'alpha': 1.0, 'dynamic': False, 'candidate_num': 0}]\n","[Training]: train_batch_size = [2048] train_neg_sample_args: [{'distribution': 'uniform', 'sample_num': 1, 'alpha': 1.0, 'dynamic': False, 'candidate_num': 0}]\n","11 Dec 11:56 INFO [Evaluation]: eval_batch_size = [4096] eval_args: [{'split': {'RS': [9, 0, 1]}, 'order': 'TO', 'group_by': 'user', 'mode': {'valid': 'full', 'test': 'full'}}]\n","[Evaluation]: eval_batch_size = [4096] eval_args: [{'split': {'RS': [9, 0, 1]}, 'order': 'TO', 'group_by': 'user', 'mode': {'valid': 'full', 'test': 'full'}}]\n"]}],"source":["# dataset splitting\n","train_data, valid_data, test_data = data_preparation(config, dataset)"]},{"cell_type":"code","execution_count":32,"metadata":{"execution":{"iopub.execute_input":"2023-01-22T18:05:34.257762Z","iopub.status.busy":"2023-01-22T18:05:34.257174Z","iopub.status.idle":"2023-01-22T18:05:34.262360Z","shell.execute_reply":"2023-01-22T18:05:34.261553Z","shell.execute_reply.started":"2023-01-22T18:05:34.257723Z"},"papermill":{"duration":0.01694,"end_time":"2022-11-27T16:34:49.085164","exception":false,"start_time":"2022-11-27T16:34:49.068224","status":"completed"},"tags":[],"trusted":true},"outputs":[],"source":["import time"]},{"cell_type":"markdown","metadata":{},"source":["### Использование различных архитектур"]},{"cell_type":"code","execution_count":33,"metadata":{"execution":{"iopub.execute_input":"2023-01-22T18:05:41.096708Z","iopub.status.busy":"2023-01-22T18:05:41.096214Z","iopub.status.idle":"2023-01-22T18:11:38.568018Z","shell.execute_reply":"2023-01-22T18:11:38.567070Z","shell.execute_reply.started":"2023-01-22T18:05:41.096667Z"},"papermill":{"duration":27259.293886,"end_time":"2022-11-28T00:09:08.387403","exception":false,"start_time":"2022-11-27T16:34:49.093517","status":"completed"},"tags":[],"trusted":true},"outputs":[{"name":"stdout","output_type":"stream","text":["running LightGCN...\n"]},{"name":"stderr","output_type":"stream","text":["11 Dec 11:56 INFO ['/Users/annapikuleva/Library/Python/3.9/lib/python/site-packages/ipykernel_launcher.py', '--f=/Users/annapikuleva/Library/Jupyter/runtime/kernel-v2-3832937JAU6uqtVOE.json']\n","['/Users/annapikuleva/Library/Python/3.9/lib/python/site-packages/ipykernel_launcher.py', '--f=/Users/annapikuleva/Library/Jupyter/runtime/kernel-v2-3832937JAU6uqtVOE.json']\n","11 Dec 11:56 INFO \n","General Hyper Parameters:\n","gpu_id = 0\n","use_gpu = True\n","seed = 2020\n","state = INFO\n","reproducibility = True\n","data_path = recbox_data\n","checkpoint_dir = saved\n","show_progress = False\n","save_dataset = False\n","dataset_save_path = None\n","save_dataloaders = False\n","dataloaders_save_path = None\n","log_wandb = False\n","\n","Training Hyper Parameters:\n","epochs = 10\n","train_batch_size = 2048\n","learner = adam\n","learning_rate = 0.001\n","train_neg_sample_args = {'distribution': 'uniform', 'sample_num': 1, 'alpha': 1.0, 'dynamic': False, 'candidate_num': 0}\n","eval_step = 1\n","stopping_step = 10\n","clip_grad_norm = None\n","weight_decay = 0.0\n","loss_decimal_place = 4\n","\n","Evaluation Hyper Parameters:\n","eval_args = {'split': {'RS': [9, 0, 1]}, 'order': 'TO', 'group_by': 'user', 'mode': {'valid': 'full', 'test': 'full'}}\n","repeatable = False\n","metrics = ['Recall', 'MRR', 'NDCG', 'Hit', 'Precision']\n","topk = [10]\n","valid_metric = MRR@10\n","valid_metric_bigger = True\n","eval_batch_size = 4096\n","metric_decimal_place = 4\n","\n","Dataset Hyper Parameters:\n","field_separator = \t\n","seq_separator = \n","USER_ID_FIELD = user_id\n","ITEM_ID_FIELD = item_id\n","RATING_FIELD = rating\n","TIME_FIELD = timestamp\n","seq_len = None\n","LABEL_FIELD = label\n","threshold = None\n","NEG_PREFIX = neg_\n","load_col = {'inter': ['user_id', 'item_id', 'timestamp']}\n","unload_col = None\n","unused_col = None\n","additional_feat_suffix = None\n","rm_dup_inter = None\n","val_interval = None\n","filter_inter_by_user_or_item = True\n","user_inter_num_interval = [40,inf)\n","item_inter_num_interval = [40,inf)\n","alias_of_user_id = None\n","alias_of_item_id = None\n","alias_of_entity_id = None\n","alias_of_relation_id = None\n","preload_weight = None\n","normalize_field = None\n","normalize_all = None\n","ITEM_LIST_LENGTH_FIELD = item_length\n","LIST_SUFFIX = _list\n","MAX_ITEM_LIST_LENGTH = 50\n","POSITION_FIELD = position_id\n","HEAD_ENTITY_ID_FIELD = head_id\n","TAIL_ENTITY_ID_FIELD = tail_id\n","RELATION_ID_FIELD = relation_id\n","ENTITY_ID_FIELD = entity_id\n","benchmark_filename = None\n","\n","Other Hyper Parameters: \n","worker = 0\n","wandb_project = recbole\n","shuffle = True\n","require_pow = False\n","enable_amp = False\n","enable_scaler = False\n","transform = None\n","embedding_size = 64\n","n_layers = 2\n","reg_weight = 1e-05\n","numerical_features = []\n","discretization = None\n","kg_reverse_r = False\n","entity_kg_num_interval = [0,inf)\n","relation_kg_num_interval = [0,inf)\n","MODEL_TYPE = ModelType.GENERAL\n","device = cpu\n","neg_sampling = None\n","verbose = -1\n","MODEL_INPUT_TYPE = InputType.PAIRWISE\n","eval_type = EvaluatorType.RANKING\n","single_spec = True\n","local_rank = 0\n","valid_neg_sample_args = {'distribution': 'uniform', 'sample_num': 'none'}\n","test_neg_sample_args = {'distribution': 'uniform', 'sample_num': 'none'}\n","\n","\n","\n","General Hyper Parameters:\n","gpu_id = 0\n","use_gpu = True\n","seed = 2020\n","state = INFO\n","reproducibility = True\n","data_path = recbox_data\n","checkpoint_dir = saved\n","show_progress = False\n","save_dataset = False\n","dataset_save_path = None\n","save_dataloaders = False\n","dataloaders_save_path = None\n","log_wandb = False\n","\n","Training Hyper Parameters:\n","epochs = 10\n","train_batch_size = 2048\n","learner = adam\n","learning_rate = 0.001\n","train_neg_sample_args = {'distribution': 'uniform', 'sample_num': 1, 'alpha': 1.0, 'dynamic': False, 'candidate_num': 0}\n","eval_step = 1\n","stopping_step = 10\n","clip_grad_norm = None\n","weight_decay = 0.0\n","loss_decimal_place = 4\n","\n","Evaluation Hyper Parameters:\n","eval_args = {'split': {'RS': [9, 0, 1]}, 'order': 'TO', 'group_by': 'user', 'mode': {'valid': 'full', 'test': 'full'}}\n","repeatable = False\n","metrics = ['Recall', 'MRR', 'NDCG', 'Hit', 'Precision']\n","topk = [10]\n","valid_metric = MRR@10\n","valid_metric_bigger = True\n","eval_batch_size = 4096\n","metric_decimal_place = 4\n","\n","Dataset Hyper Parameters:\n","field_separator = \t\n","seq_separator = \n","USER_ID_FIELD = user_id\n","ITEM_ID_FIELD = item_id\n","RATING_FIELD = rating\n","TIME_FIELD = timestamp\n","seq_len = None\n","LABEL_FIELD = label\n","threshold = None\n","NEG_PREFIX = neg_\n","load_col = {'inter': ['user_id', 'item_id', 'timestamp']}\n","unload_col = None\n","unused_col = None\n","additional_feat_suffix = None\n","rm_dup_inter = None\n","val_interval = None\n","filter_inter_by_user_or_item = True\n","user_inter_num_interval = [40,inf)\n","item_inter_num_interval = [40,inf)\n","alias_of_user_id = None\n","alias_of_item_id = None\n","alias_of_entity_id = None\n","alias_of_relation_id = None\n","preload_weight = None\n","normalize_field = None\n","normalize_all = None\n","ITEM_LIST_LENGTH_FIELD = item_length\n","LIST_SUFFIX = _list\n","MAX_ITEM_LIST_LENGTH = 50\n","POSITION_FIELD = position_id\n","HEAD_ENTITY_ID_FIELD = head_id\n","TAIL_ENTITY_ID_FIELD = tail_id\n","RELATION_ID_FIELD = relation_id\n","ENTITY_ID_FIELD = entity_id\n","benchmark_filename = None\n","\n","Other Hyper Parameters: \n","worker = 0\n","wandb_project = recbole\n","shuffle = True\n","require_pow = False\n","enable_amp = False\n","enable_scaler = False\n","transform = None\n","embedding_size = 64\n","n_layers = 2\n","reg_weight = 1e-05\n","numerical_features = []\n","discretization = None\n","kg_reverse_r = False\n","entity_kg_num_interval = [0,inf)\n","relation_kg_num_interval = [0,inf)\n","MODEL_TYPE = ModelType.GENERAL\n","device = cpu\n","neg_sampling = None\n","verbose = -1\n","MODEL_INPUT_TYPE = InputType.PAIRWISE\n","eval_type = EvaluatorType.RANKING\n","single_spec = True\n","local_rank = 0\n","valid_neg_sample_args = {'distribution': 'uniform', 'sample_num': 'none'}\n","test_neg_sample_args = {'distribution': 'uniform', 'sample_num': 'none'}\n","\n","\n","11 Dec 11:58 INFO recbox_data\n","The number of users: 13355\n","Average actions of users: 63.815710648494836\n","The number of items: 3294\n","Average actions of items: 258.78985727300335\n","The number of inters: 852195\n","The sparsity of the dataset: 98.06281322904924%\n","Remain Fields: ['user_id', 'item_id', 'timestamp']\n","recbox_data\n","The number of users: 13355\n","Average actions of users: 63.815710648494836\n","The number of items: 3294\n","Average actions of items: 258.78985727300335\n","The number of inters: 852195\n","The sparsity of the dataset: 98.06281322904924%\n","Remain Fields: ['user_id', 'item_id', 'timestamp']\n","11 Dec 11:58 INFO [Training]: train_batch_size = [2048] train_neg_sample_args: [{'distribution': 'uniform', 'sample_num': 1, 'alpha': 1.0, 'dynamic': False, 'candidate_num': 0}]\n","[Training]: train_batch_size = [2048] train_neg_sample_args: [{'distribution': 'uniform', 'sample_num': 1, 'alpha': 1.0, 'dynamic': False, 'candidate_num': 0}]\n","11 Dec 11:58 INFO [Evaluation]: eval_batch_size = [4096] eval_args: [{'split': {'RS': [9, 0, 1]}, 'order': 'TO', 'group_by': 'user', 'mode': {'valid': 'full', 'test': 'full'}}]\n","[Evaluation]: eval_batch_size = [4096] eval_args: [{'split': {'RS': [9, 0, 1]}, 'order': 'TO', 'group_by': 'user', 'mode': {'valid': 'full', 'test': 'full'}}]\n","11 Dec 11:58 INFO LightGCN(\n"," (user_embedding): Embedding(13355, 64)\n"," (item_embedding): Embedding(3294, 64)\n"," (mf_loss): BPRLoss()\n"," (reg_loss): EmbLoss()\n",")\n","Trainable parameters: 1065536\n","LightGCN(\n"," (user_embedding): Embedding(13355, 64)\n"," (item_embedding): Embedding(3294, 64)\n"," (mf_loss): BPRLoss()\n"," (reg_loss): EmbLoss()\n",")\n","Trainable parameters: 1065536\n","11 Dec 11:58 INFO FLOPs: 0.0\n","FLOPs: 0.0\n","11 Dec 12:00 INFO epoch 0 training [time: 96.30s, train loss: 201.8552]\n","epoch 0 training [time: 96.30s, train loss: 201.8552]\n","11 Dec 12:00 INFO Saving current: saved/LightGCN-Dec-11-2023_11-58-43.pth\n","Saving current: saved/LightGCN-Dec-11-2023_11-58-43.pth\n","11 Dec 12:01 INFO epoch 1 training [time: 91.67s, train loss: 166.0586]\n","epoch 1 training [time: 91.67s, train loss: 166.0586]\n","11 Dec 12:01 INFO Saving current: saved/LightGCN-Dec-11-2023_11-58-43.pth\n","Saving current: saved/LightGCN-Dec-11-2023_11-58-43.pth\n","11 Dec 12:03 INFO epoch 2 training [time: 106.54s, train loss: 156.0221]\n","epoch 2 training [time: 106.54s, train loss: 156.0221]\n","11 Dec 12:03 INFO Saving current: saved/LightGCN-Dec-11-2023_11-58-43.pth\n","Saving current: saved/LightGCN-Dec-11-2023_11-58-43.pth\n","11 Dec 12:05 INFO epoch 3 training [time: 125.49s, train loss: 149.5909]\n","epoch 3 training [time: 125.49s, train loss: 149.5909]\n","11 Dec 12:05 INFO Saving current: saved/LightGCN-Dec-11-2023_11-58-43.pth\n","Saving current: saved/LightGCN-Dec-11-2023_11-58-43.pth\n","11 Dec 12:07 INFO epoch 4 training [time: 123.82s, train loss: 146.1851]\n","epoch 4 training [time: 123.82s, train loss: 146.1851]\n","11 Dec 12:07 INFO Saving current: saved/LightGCN-Dec-11-2023_11-58-43.pth\n","Saving current: saved/LightGCN-Dec-11-2023_11-58-43.pth\n","11 Dec 12:10 INFO epoch 5 training [time: 135.29s, train loss: 143.7300]\n","epoch 5 training [time: 135.29s, train loss: 143.7300]\n","11 Dec 12:10 INFO Saving current: saved/LightGCN-Dec-11-2023_11-58-43.pth\n","Saving current: saved/LightGCN-Dec-11-2023_11-58-43.pth\n","11 Dec 12:12 INFO epoch 6 training [time: 152.14s, train loss: 141.2300]\n","epoch 6 training [time: 152.14s, train loss: 141.2300]\n","11 Dec 12:12 INFO Saving current: saved/LightGCN-Dec-11-2023_11-58-43.pth\n","Saving current: saved/LightGCN-Dec-11-2023_11-58-43.pth\n","11 Dec 12:14 INFO epoch 7 training [time: 114.99s, train loss: 137.4871]\n","epoch 7 training [time: 114.99s, train loss: 137.4871]\n","11 Dec 12:14 INFO Saving current: saved/LightGCN-Dec-11-2023_11-58-43.pth\n","Saving current: saved/LightGCN-Dec-11-2023_11-58-43.pth\n","11 Dec 12:16 INFO epoch 8 training [time: 128.70s, train loss: 133.3195]\n","epoch 8 training [time: 128.70s, train loss: 133.3195]\n","11 Dec 12:16 INFO Saving current: saved/LightGCN-Dec-11-2023_11-58-43.pth\n","Saving current: saved/LightGCN-Dec-11-2023_11-58-43.pth\n","11 Dec 12:18 INFO epoch 9 training [time: 126.52s, train loss: 129.4056]\n","epoch 9 training [time: 126.52s, train loss: 129.4056]\n","11 Dec 12:18 INFO Saving current: saved/LightGCN-Dec-11-2023_11-58-43.pth\n","Saving current: saved/LightGCN-Dec-11-2023_11-58-43.pth\n","11 Dec 12:18 INFO Loading model structure and parameters from saved/LightGCN-Dec-11-2023_11-58-43.pth\n","Loading model structure and parameters from saved/LightGCN-Dec-11-2023_11-58-43.pth\n","11 Dec 12:18 INFO The running environment of this training is as follows:\n","+-------------+---------------+\n","| Environment | Usage |\n","+=============+===============+\n","| CPU | 48.50 % |\n","+-------------+---------------+\n","| GPU | 0.0 / 0.0 |\n","+-------------+---------------+\n","| Memory | 0.07 G/8.00 G |\n","+-------------+---------------+\n","The running environment of this training is as follows:\n","+-------------+---------------+\n","| Environment | Usage |\n","+=============+===============+\n","| CPU | 48.50 % |\n","+-------------+---------------+\n","| GPU | 0.0 / 0.0 |\n","+-------------+---------------+\n","| Memory | 0.07 G/8.00 G |\n","+-------------+---------------+\n","11 Dec 12:18 INFO best valid : None\n","best valid : None\n","11 Dec 12:18 INFO test result: OrderedDict([('recall@10', 0.0792), ('mrr@10', 0.1685), ('ndcg@10', 0.0795), ('hit@10', 0.3385), ('precision@10', 0.0441)])\n","test result: OrderedDict([('recall@10', 0.0792), ('mrr@10', 0.1685), ('ndcg@10', 0.0795), ('hit@10', 0.3385), ('precision@10', 0.0441)])\n"]},{"name":"stdout","output_type":"stream","text":["It took 21.95 mins\n","{'best_valid_score': -inf, 'valid_score_bigger': True, 'best_valid_result': None, 'test_result': OrderedDict([('recall@10', 0.0792), ('mrr@10', 0.1685), ('ndcg@10', 0.0795), ('hit@10', 0.3385), ('precision@10', 0.0441)])}\n","running MultiVAE...\n"]},{"name":"stderr","output_type":"stream","text":["11 Dec 12:18 INFO ['/Users/annapikuleva/Library/Python/3.9/lib/python/site-packages/ipykernel_launcher.py', '--f=/Users/annapikuleva/Library/Jupyter/runtime/kernel-v2-3832937JAU6uqtVOE.json']\n","['/Users/annapikuleva/Library/Python/3.9/lib/python/site-packages/ipykernel_launcher.py', '--f=/Users/annapikuleva/Library/Jupyter/runtime/kernel-v2-3832937JAU6uqtVOE.json']\n","11 Dec 12:18 INFO \n","General Hyper Parameters:\n","gpu_id = 0\n","use_gpu = True\n","seed = 2020\n","state = INFO\n","reproducibility = True\n","data_path = recbox_data\n","checkpoint_dir = saved\n","show_progress = False\n","save_dataset = False\n","dataset_save_path = None\n","save_dataloaders = False\n","dataloaders_save_path = None\n","log_wandb = False\n","\n","Training Hyper Parameters:\n","epochs = 10\n","train_batch_size = 2048\n","learner = adam\n","learning_rate = 0.001\n","train_neg_sample_args = {'distribution': 'uniform', 'sample_num': 1, 'alpha': 1.0, 'dynamic': False, 'candidate_num': 0}\n","eval_step = 1\n","stopping_step = 10\n","clip_grad_norm = None\n","weight_decay = 0.0\n","loss_decimal_place = 4\n","\n","Evaluation Hyper Parameters:\n","eval_args = {'split': {'RS': [9, 0, 1]}, 'order': 'TO', 'group_by': 'user', 'mode': {'valid': 'full', 'test': 'full'}}\n","repeatable = False\n","metrics = ['Recall', 'MRR', 'NDCG', 'Hit', 'Precision']\n","topk = [10]\n","valid_metric = MRR@10\n","valid_metric_bigger = True\n","eval_batch_size = 4096\n","metric_decimal_place = 4\n","\n","Dataset Hyper Parameters:\n","field_separator = \t\n","seq_separator = \n","USER_ID_FIELD = user_id\n","ITEM_ID_FIELD = item_id\n","RATING_FIELD = rating\n","TIME_FIELD = timestamp\n","seq_len = None\n","LABEL_FIELD = label\n","threshold = None\n","NEG_PREFIX = neg_\n","load_col = {'inter': ['user_id', 'item_id', 'timestamp']}\n","unload_col = None\n","unused_col = None\n","additional_feat_suffix = None\n","rm_dup_inter = None\n","val_interval = None\n","filter_inter_by_user_or_item = True\n","user_inter_num_interval = [40,inf)\n","item_inter_num_interval = [40,inf)\n","alias_of_user_id = None\n","alias_of_item_id = None\n","alias_of_entity_id = None\n","alias_of_relation_id = None\n","preload_weight = None\n","normalize_field = None\n","normalize_all = None\n","ITEM_LIST_LENGTH_FIELD = item_length\n","LIST_SUFFIX = _list\n","MAX_ITEM_LIST_LENGTH = 50\n","POSITION_FIELD = position_id\n","HEAD_ENTITY_ID_FIELD = head_id\n","TAIL_ENTITY_ID_FIELD = tail_id\n","RELATION_ID_FIELD = relation_id\n","ENTITY_ID_FIELD = entity_id\n","benchmark_filename = None\n","\n","Other Hyper Parameters: \n","worker = 0\n","wandb_project = recbole\n","shuffle = True\n","require_pow = False\n","enable_amp = False\n","enable_scaler = False\n","transform = None\n","mlp_hidden_size = [600]\n","latent_dimension = 128\n","dropout_prob = 0.5\n","anneal_cap = 0.2\n","total_anneal_steps = 200000\n","numerical_features = []\n","discretization = None\n","kg_reverse_r = False\n","entity_kg_num_interval = [0,inf)\n","relation_kg_num_interval = [0,inf)\n","MODEL_TYPE = ModelType.GENERAL\n","device = cpu\n","neg_sampling = None\n","verbose = -1\n","MODEL_INPUT_TYPE = InputType.PAIRWISE\n","eval_type = EvaluatorType.RANKING\n","single_spec = True\n","local_rank = 0\n","valid_neg_sample_args = {'distribution': 'uniform', 'sample_num': 'none'}\n","test_neg_sample_args = {'distribution': 'uniform', 'sample_num': 'none'}\n","\n","\n","\n","General Hyper Parameters:\n","gpu_id = 0\n","use_gpu = True\n","seed = 2020\n","state = INFO\n","reproducibility = True\n","data_path = recbox_data\n","checkpoint_dir = saved\n","show_progress = False\n","save_dataset = False\n","dataset_save_path = None\n","save_dataloaders = False\n","dataloaders_save_path = None\n","log_wandb = False\n","\n","Training Hyper Parameters:\n","epochs = 10\n","train_batch_size = 2048\n","learner = adam\n","learning_rate = 0.001\n","train_neg_sample_args = {'distribution': 'uniform', 'sample_num': 1, 'alpha': 1.0, 'dynamic': False, 'candidate_num': 0}\n","eval_step = 1\n","stopping_step = 10\n","clip_grad_norm = None\n","weight_decay = 0.0\n","loss_decimal_place = 4\n","\n","Evaluation Hyper Parameters:\n","eval_args = {'split': {'RS': [9, 0, 1]}, 'order': 'TO', 'group_by': 'user', 'mode': {'valid': 'full', 'test': 'full'}}\n","repeatable = False\n","metrics = ['Recall', 'MRR', 'NDCG', 'Hit', 'Precision']\n","topk = [10]\n","valid_metric = MRR@10\n","valid_metric_bigger = True\n","eval_batch_size = 4096\n","metric_decimal_place = 4\n","\n","Dataset Hyper Parameters:\n","field_separator = \t\n","seq_separator = \n","USER_ID_FIELD = user_id\n","ITEM_ID_FIELD = item_id\n","RATING_FIELD = rating\n","TIME_FIELD = timestamp\n","seq_len = None\n","LABEL_FIELD = label\n","threshold = None\n","NEG_PREFIX = neg_\n","load_col = {'inter': ['user_id', 'item_id', 'timestamp']}\n","unload_col = None\n","unused_col = None\n","additional_feat_suffix = None\n","rm_dup_inter = None\n","val_interval = None\n","filter_inter_by_user_or_item = True\n","user_inter_num_interval = [40,inf)\n","item_inter_num_interval = [40,inf)\n","alias_of_user_id = None\n","alias_of_item_id = None\n","alias_of_entity_id = None\n","alias_of_relation_id = None\n","preload_weight = None\n","normalize_field = None\n","normalize_all = None\n","ITEM_LIST_LENGTH_FIELD = item_length\n","LIST_SUFFIX = _list\n","MAX_ITEM_LIST_LENGTH = 50\n","POSITION_FIELD = position_id\n","HEAD_ENTITY_ID_FIELD = head_id\n","TAIL_ENTITY_ID_FIELD = tail_id\n","RELATION_ID_FIELD = relation_id\n","ENTITY_ID_FIELD = entity_id\n","benchmark_filename = None\n","\n","Other Hyper Parameters: \n","worker = 0\n","wandb_project = recbole\n","shuffle = True\n","require_pow = False\n","enable_amp = False\n","enable_scaler = False\n","transform = None\n","mlp_hidden_size = [600]\n","latent_dimension = 128\n","dropout_prob = 0.5\n","anneal_cap = 0.2\n","total_anneal_steps = 200000\n","numerical_features = []\n","discretization = None\n","kg_reverse_r = False\n","entity_kg_num_interval = [0,inf)\n","relation_kg_num_interval = [0,inf)\n","MODEL_TYPE = ModelType.GENERAL\n","device = cpu\n","neg_sampling = None\n","verbose = -1\n","MODEL_INPUT_TYPE = InputType.PAIRWISE\n","eval_type = EvaluatorType.RANKING\n","single_spec = True\n","local_rank = 0\n","valid_neg_sample_args = {'distribution': 'uniform', 'sample_num': 'none'}\n","test_neg_sample_args = {'distribution': 'uniform', 'sample_num': 'none'}\n","\n","\n","11 Dec 12:21 INFO recbox_data\n","The number of users: 13355\n","Average actions of users: 63.815710648494836\n","The number of items: 3294\n","Average actions of items: 258.78985727300335\n","The number of inters: 852195\n","The sparsity of the dataset: 98.06281322904924%\n","Remain Fields: ['user_id', 'item_id', 'timestamp']\n","recbox_data\n","The number of users: 13355\n","Average actions of users: 63.815710648494836\n","The number of items: 3294\n","Average actions of items: 258.78985727300335\n","The number of inters: 852195\n","The sparsity of the dataset: 98.06281322904924%\n","Remain Fields: ['user_id', 'item_id', 'timestamp']\n","11 Dec 12:21 INFO [Training]: train_batch_size = [2048] train_neg_sample_args: [{'distribution': 'uniform', 'sample_num': 1, 'alpha': 1.0, 'dynamic': False, 'candidate_num': 0}]\n","[Training]: train_batch_size = [2048] train_neg_sample_args: [{'distribution': 'uniform', 'sample_num': 1, 'alpha': 1.0, 'dynamic': False, 'candidate_num': 0}]\n","11 Dec 12:21 INFO [Evaluation]: eval_batch_size = [4096] eval_args: [{'split': {'RS': [9, 0, 1]}, 'order': 'TO', 'group_by': 'user', 'mode': {'valid': 'full', 'test': 'full'}}]\n","[Evaluation]: eval_batch_size = [4096] eval_args: [{'split': {'RS': [9, 0, 1]}, 'order': 'TO', 'group_by': 'user', 'mode': {'valid': 'full', 'test': 'full'}}]\n","11 Dec 12:21 WARNING Max value of user's history interaction records has reached 20.9471766848816% of the total.\n","Max value of user's history interaction records has reached 20.9471766848816% of the total.\n","11 Dec 12:21 INFO MultiVAE(\n"," (encoder): Sequential(\n"," (0): Linear(in_features=3294, out_features=600, bias=True)\n"," (1): Tanh()\n"," (2): Linear(in_features=600, out_features=128, bias=True)\n"," )\n"," (decoder): Sequential(\n"," (0): Linear(in_features=64, out_features=600, bias=True)\n"," (1): Tanh()\n"," (2): Linear(in_features=600, out_features=3294, bias=True)\n"," )\n",")\n","Trainable parameters: 4072622\n","MultiVAE(\n"," (encoder): Sequential(\n"," (0): Linear(in_features=3294, out_features=600, bias=True)\n"," (1): Tanh()\n"," (2): Linear(in_features=600, out_features=128, bias=True)\n"," )\n"," (decoder): Sequential(\n"," (0): Linear(in_features=64, out_features=600, bias=True)\n"," (1): Tanh()\n"," (2): Linear(in_features=600, out_features=3294, bias=True)\n"," )\n",")\n","Trainable parameters: 4072622\n","11 Dec 12:21 INFO FLOPs: 4068000.0\n","FLOPs: 4068000.0\n","11 Dec 12:21 INFO epoch 0 training [time: 2.16s, train loss: 3249.3142]\n","epoch 0 training [time: 2.16s, train loss: 3249.3142]\n","11 Dec 12:21 INFO Saving current: saved/MultiVAE-Dec-11-2023_12-21-45.pth\n","Saving current: saved/MultiVAE-Dec-11-2023_12-21-45.pth\n","11 Dec 12:21 INFO epoch 1 training [time: 1.96s, train loss: 3098.4010]\n","epoch 1 training [time: 1.96s, train loss: 3098.4010]\n","11 Dec 12:21 INFO Saving current: saved/MultiVAE-Dec-11-2023_12-21-45.pth\n","Saving current: saved/MultiVAE-Dec-11-2023_12-21-45.pth\n","11 Dec 12:21 INFO epoch 2 training [time: 1.97s, train loss: 3045.1938]\n","epoch 2 training [time: 1.97s, train loss: 3045.1938]\n","11 Dec 12:21 INFO Saving current: saved/MultiVAE-Dec-11-2023_12-21-45.pth\n","Saving current: saved/MultiVAE-Dec-11-2023_12-21-45.pth\n","11 Dec 12:21 INFO epoch 3 training [time: 2.02s, train loss: 3008.0520]\n","epoch 3 training [time: 2.02s, train loss: 3008.0520]\n","11 Dec 12:21 INFO Saving current: saved/MultiVAE-Dec-11-2023_12-21-45.pth\n","Saving current: saved/MultiVAE-Dec-11-2023_12-21-45.pth\n","11 Dec 12:21 INFO epoch 4 training [time: 2.58s, train loss: 2949.4743]\n","epoch 4 training [time: 2.58s, train loss: 2949.4743]\n","11 Dec 12:21 INFO Saving current: saved/MultiVAE-Dec-11-2023_12-21-45.pth\n","Saving current: saved/MultiVAE-Dec-11-2023_12-21-45.pth\n","11 Dec 12:21 INFO epoch 5 training [time: 2.14s, train loss: 2917.6707]\n","epoch 5 training [time: 2.14s, train loss: 2917.6707]\n","11 Dec 12:21 INFO Saving current: saved/MultiVAE-Dec-11-2023_12-21-45.pth\n","Saving current: saved/MultiVAE-Dec-11-2023_12-21-45.pth\n","11 Dec 12:22 INFO epoch 6 training [time: 2.28s, train loss: 2897.4954]\n","epoch 6 training [time: 2.28s, train loss: 2897.4954]\n","11 Dec 12:22 INFO Saving current: saved/MultiVAE-Dec-11-2023_12-21-45.pth\n","Saving current: saved/MultiVAE-Dec-11-2023_12-21-45.pth\n","11 Dec 12:22 INFO epoch 7 training [time: 2.03s, train loss: 2885.5641]\n","epoch 7 training [time: 2.03s, train loss: 2885.5641]\n","11 Dec 12:22 INFO Saving current: saved/MultiVAE-Dec-11-2023_12-21-45.pth\n","Saving current: saved/MultiVAE-Dec-11-2023_12-21-45.pth\n","11 Dec 12:22 INFO epoch 8 training [time: 2.38s, train loss: 2871.9012]\n","epoch 8 training [time: 2.38s, train loss: 2871.9012]\n","11 Dec 12:22 INFO Saving current: saved/MultiVAE-Dec-11-2023_12-21-45.pth\n","Saving current: saved/MultiVAE-Dec-11-2023_12-21-45.pth\n","11 Dec 12:22 INFO epoch 9 training [time: 2.46s, train loss: 2851.2055]\n","epoch 9 training [time: 2.46s, train loss: 2851.2055]\n","11 Dec 12:22 INFO Saving current: saved/MultiVAE-Dec-11-2023_12-21-45.pth\n","Saving current: saved/MultiVAE-Dec-11-2023_12-21-45.pth\n","11 Dec 12:22 INFO Loading model structure and parameters from saved/MultiVAE-Dec-11-2023_12-21-45.pth\n","Loading model structure and parameters from saved/MultiVAE-Dec-11-2023_12-21-45.pth\n","11 Dec 12:22 INFO The running environment of this training is as follows:\n","+-------------+---------------+\n","| Environment | Usage |\n","+=============+===============+\n","| CPU | 74.20 % |\n","+-------------+---------------+\n","| GPU | 0.0 / 0.0 |\n","+-------------+---------------+\n","| Memory | 0.08 G/8.00 G |\n","+-------------+---------------+\n","The running environment of this training is as follows:\n","+-------------+---------------+\n","| Environment | Usage |\n","+=============+===============+\n","| CPU | 74.20 % |\n","+-------------+---------------+\n","| GPU | 0.0 / 0.0 |\n","+-------------+---------------+\n","| Memory | 0.08 G/8.00 G |\n","+-------------+---------------+\n","11 Dec 12:22 INFO best valid : None\n","best valid : None\n","11 Dec 12:22 INFO test result: OrderedDict([('recall@10', 0.0839), ('mrr@10', 0.1687), ('ndcg@10', 0.0823), ('hit@10', 0.3494), ('precision@10', 0.0465)])\n","test result: OrderedDict([('recall@10', 0.0839), ('mrr@10', 0.1687), ('ndcg@10', 0.0823), ('hit@10', 0.3494), ('precision@10', 0.0465)])\n"]},{"name":"stdout","output_type":"stream","text":["It took 3.87 mins\n","{'best_valid_score': -inf, 'valid_score_bigger': True, 'best_valid_result': None, 'test_result': OrderedDict([('recall@10', 0.0839), ('mrr@10', 0.1687), ('ndcg@10', 0.0823), ('hit@10', 0.3494), ('precision@10', 0.0465)])}\n","running RecVAE...\n"]},{"name":"stderr","output_type":"stream","text":["11 Dec 12:22 INFO ['/Users/annapikuleva/Library/Python/3.9/lib/python/site-packages/ipykernel_launcher.py', '--f=/Users/annapikuleva/Library/Jupyter/runtime/kernel-v2-3832937JAU6uqtVOE.json']\n","['/Users/annapikuleva/Library/Python/3.9/lib/python/site-packages/ipykernel_launcher.py', '--f=/Users/annapikuleva/Library/Jupyter/runtime/kernel-v2-3832937JAU6uqtVOE.json']\n","11 Dec 12:22 INFO \n","General Hyper Parameters:\n","gpu_id = 0\n","use_gpu = True\n","seed = 2020\n","state = INFO\n","reproducibility = True\n","data_path = recbox_data\n","checkpoint_dir = saved\n","show_progress = False\n","save_dataset = False\n","dataset_save_path = None\n","save_dataloaders = False\n","dataloaders_save_path = None\n","log_wandb = False\n","\n","Training Hyper Parameters:\n","epochs = 10\n","train_batch_size = 2048\n","learner = adam\n","learning_rate = 0.001\n","train_neg_sample_args = {'distribution': 'uniform', 'sample_num': 1, 'alpha': 1.0, 'dynamic': False, 'candidate_num': 0}\n","eval_step = 1\n","stopping_step = 10\n","clip_grad_norm = None\n","weight_decay = 0.0\n","loss_decimal_place = 4\n","\n","Evaluation Hyper Parameters:\n","eval_args = {'split': {'RS': [9, 0, 1]}, 'order': 'TO', 'group_by': 'user', 'mode': {'valid': 'full', 'test': 'full'}}\n","repeatable = False\n","metrics = ['Recall', 'MRR', 'NDCG', 'Hit', 'Precision']\n","topk = [10]\n","valid_metric = MRR@10\n","valid_metric_bigger = True\n","eval_batch_size = 4096\n","metric_decimal_place = 4\n","\n","Dataset Hyper Parameters:\n","field_separator = \t\n","seq_separator = \n","USER_ID_FIELD = user_id\n","ITEM_ID_FIELD = item_id\n","RATING_FIELD = rating\n","TIME_FIELD = timestamp\n","seq_len = None\n","LABEL_FIELD = label\n","threshold = None\n","NEG_PREFIX = neg_\n","load_col = {'inter': ['user_id', 'item_id', 'timestamp']}\n","unload_col = None\n","unused_col = None\n","additional_feat_suffix = None\n","rm_dup_inter = None\n","val_interval = None\n","filter_inter_by_user_or_item = True\n","user_inter_num_interval = [40,inf)\n","item_inter_num_interval = [40,inf)\n","alias_of_user_id = None\n","alias_of_item_id = None\n","alias_of_entity_id = None\n","alias_of_relation_id = None\n","preload_weight = None\n","normalize_field = None\n","normalize_all = None\n","ITEM_LIST_LENGTH_FIELD = item_length\n","LIST_SUFFIX = _list\n","MAX_ITEM_LIST_LENGTH = 50\n","POSITION_FIELD = position_id\n","HEAD_ENTITY_ID_FIELD = head_id\n","TAIL_ENTITY_ID_FIELD = tail_id\n","RELATION_ID_FIELD = relation_id\n","ENTITY_ID_FIELD = entity_id\n","benchmark_filename = None\n","\n","Other Hyper Parameters: \n","worker = 0\n","wandb_project = recbole\n","shuffle = True\n","require_pow = False\n","enable_amp = False\n","enable_scaler = False\n","transform = None\n","hidden_dimension = 600\n","latent_dimension = 200\n","dropout_prob = 0.5\n","beta = 0.2\n","gamma = 0.005\n","mixture_weights = [0.15, 0.75, 0.1]\n","n_enc_epochs = 3\n","n_dec_epochs = 1\n","numerical_features = []\n","discretization = None\n","kg_reverse_r = False\n","entity_kg_num_interval = [0,inf)\n","relation_kg_num_interval = [0,inf)\n","MODEL_TYPE = ModelType.GENERAL\n","device = cpu\n","neg_sampling = None\n","verbose = -1\n","MODEL_INPUT_TYPE = InputType.PAIRWISE\n","eval_type = EvaluatorType.RANKING\n","single_spec = True\n","local_rank = 0\n","valid_neg_sample_args = {'distribution': 'uniform', 'sample_num': 'none'}\n","test_neg_sample_args = {'distribution': 'uniform', 'sample_num': 'none'}\n","\n","\n","\n","General Hyper Parameters:\n","gpu_id = 0\n","use_gpu = True\n","seed = 2020\n","state = INFO\n","reproducibility = True\n","data_path = recbox_data\n","checkpoint_dir = saved\n","show_progress = False\n","save_dataset = False\n","dataset_save_path = None\n","save_dataloaders = False\n","dataloaders_save_path = None\n","log_wandb = False\n","\n","Training Hyper Parameters:\n","epochs = 10\n","train_batch_size = 2048\n","learner = adam\n","learning_rate = 0.001\n","train_neg_sample_args = {'distribution': 'uniform', 'sample_num': 1, 'alpha': 1.0, 'dynamic': False, 'candidate_num': 0}\n","eval_step = 1\n","stopping_step = 10\n","clip_grad_norm = None\n","weight_decay = 0.0\n","loss_decimal_place = 4\n","\n","Evaluation Hyper Parameters:\n","eval_args = {'split': {'RS': [9, 0, 1]}, 'order': 'TO', 'group_by': 'user', 'mode': {'valid': 'full', 'test': 'full'}}\n","repeatable = False\n","metrics = ['Recall', 'MRR', 'NDCG', 'Hit', 'Precision']\n","topk = [10]\n","valid_metric = MRR@10\n","valid_metric_bigger = True\n","eval_batch_size = 4096\n","metric_decimal_place = 4\n","\n","Dataset Hyper Parameters:\n","field_separator = \t\n","seq_separator = \n","USER_ID_FIELD = user_id\n","ITEM_ID_FIELD = item_id\n","RATING_FIELD = rating\n","TIME_FIELD = timestamp\n","seq_len = None\n","LABEL_FIELD = label\n","threshold = None\n","NEG_PREFIX = neg_\n","load_col = {'inter': ['user_id', 'item_id', 'timestamp']}\n","unload_col = None\n","unused_col = None\n","additional_feat_suffix = None\n","rm_dup_inter = None\n","val_interval = None\n","filter_inter_by_user_or_item = True\n","user_inter_num_interval = [40,inf)\n","item_inter_num_interval = [40,inf)\n","alias_of_user_id = None\n","alias_of_item_id = None\n","alias_of_entity_id = None\n","alias_of_relation_id = None\n","preload_weight = None\n","normalize_field = None\n","normalize_all = None\n","ITEM_LIST_LENGTH_FIELD = item_length\n","LIST_SUFFIX = _list\n","MAX_ITEM_LIST_LENGTH = 50\n","POSITION_FIELD = position_id\n","HEAD_ENTITY_ID_FIELD = head_id\n","TAIL_ENTITY_ID_FIELD = tail_id\n","RELATION_ID_FIELD = relation_id\n","ENTITY_ID_FIELD = entity_id\n","benchmark_filename = None\n","\n","Other Hyper Parameters: \n","worker = 0\n","wandb_project = recbole\n","shuffle = True\n","require_pow = False\n","enable_amp = False\n","enable_scaler = False\n","transform = None\n","hidden_dimension = 600\n","latent_dimension = 200\n","dropout_prob = 0.5\n","beta = 0.2\n","gamma = 0.005\n","mixture_weights = [0.15, 0.75, 0.1]\n","n_enc_epochs = 3\n","n_dec_epochs = 1\n","numerical_features = []\n","discretization = None\n","kg_reverse_r = False\n","entity_kg_num_interval = [0,inf)\n","relation_kg_num_interval = [0,inf)\n","MODEL_TYPE = ModelType.GENERAL\n","device = cpu\n","neg_sampling = None\n","verbose = -1\n","MODEL_INPUT_TYPE = InputType.PAIRWISE\n","eval_type = EvaluatorType.RANKING\n","single_spec = True\n","local_rank = 0\n","valid_neg_sample_args = {'distribution': 'uniform', 'sample_num': 'none'}\n","test_neg_sample_args = {'distribution': 'uniform', 'sample_num': 'none'}\n","\n","\n","11 Dec 12:25 INFO recbox_data\n","The number of users: 13355\n","Average actions of users: 63.815710648494836\n","The number of items: 3294\n","Average actions of items: 258.78985727300335\n","The number of inters: 852195\n","The sparsity of the dataset: 98.06281322904924%\n","Remain Fields: ['user_id', 'item_id', 'timestamp']\n","recbox_data\n","The number of users: 13355\n","Average actions of users: 63.815710648494836\n","The number of items: 3294\n","Average actions of items: 258.78985727300335\n","The number of inters: 852195\n","The sparsity of the dataset: 98.06281322904924%\n","Remain Fields: ['user_id', 'item_id', 'timestamp']\n","11 Dec 12:25 INFO [Training]: train_batch_size = [2048] train_neg_sample_args: [{'distribution': 'uniform', 'sample_num': 1, 'alpha': 1.0, 'dynamic': False, 'candidate_num': 0}]\n","[Training]: train_batch_size = [2048] train_neg_sample_args: [{'distribution': 'uniform', 'sample_num': 1, 'alpha': 1.0, 'dynamic': False, 'candidate_num': 0}]\n","11 Dec 12:25 INFO [Evaluation]: eval_batch_size = [4096] eval_args: [{'split': {'RS': [9, 0, 1]}, 'order': 'TO', 'group_by': 'user', 'mode': {'valid': 'full', 'test': 'full'}}]\n","[Evaluation]: eval_batch_size = [4096] eval_args: [{'split': {'RS': [9, 0, 1]}, 'order': 'TO', 'group_by': 'user', 'mode': {'valid': 'full', 'test': 'full'}}]\n","11 Dec 12:25 WARNING Max value of user's history interaction records has reached 20.9471766848816% of the total.\n","Max value of user's history interaction records has reached 20.9471766848816% of the total.\n","11 Dec 12:25 INFO RecVAE(\n"," (encoder): Encoder(\n"," (fc1): Linear(in_features=3294, out_features=600, bias=True)\n"," (ln1): LayerNorm((600,), eps=0.1, elementwise_affine=True)\n"," (fc2): Linear(in_features=600, out_features=600, bias=True)\n"," (ln2): LayerNorm((600,), eps=0.1, elementwise_affine=True)\n"," (fc3): Linear(in_features=600, out_features=600, bias=True)\n"," (ln3): LayerNorm((600,), eps=0.1, elementwise_affine=True)\n"," (fc4): Linear(in_features=600, out_features=600, bias=True)\n"," (ln4): LayerNorm((600,), eps=0.1, elementwise_affine=True)\n"," (fc5): Linear(in_features=600, out_features=600, bias=True)\n"," (ln5): LayerNorm((600,), eps=0.1, elementwise_affine=True)\n"," (fc_mu): Linear(in_features=600, out_features=200, bias=True)\n"," (fc_logvar): Linear(in_features=600, out_features=200, bias=True)\n"," )\n"," (prior): CompositePrior(\n"," (encoder_old): Encoder(\n"," (fc1): Linear(in_features=3294, out_features=600, bias=True)\n"," (ln1): LayerNorm((600,), eps=0.1, elementwise_affine=True)\n"," (fc2): Linear(in_features=600, out_features=600, bias=True)\n"," (ln2): LayerNorm((600,), eps=0.1, elementwise_affine=True)\n"," (fc3): Linear(in_features=600, out_features=600, bias=True)\n"," (ln3): LayerNorm((600,), eps=0.1, elementwise_affine=True)\n"," (fc4): Linear(in_features=600, out_features=600, bias=True)\n"," (ln4): LayerNorm((600,), eps=0.1, elementwise_affine=True)\n"," (fc5): Linear(in_features=600, out_features=600, bias=True)\n"," (ln5): LayerNorm((600,), eps=0.1, elementwise_affine=True)\n"," (fc_mu): Linear(in_features=600, out_features=200, bias=True)\n"," (fc_logvar): Linear(in_features=600, out_features=200, bias=True)\n"," )\n"," )\n"," (decoder): Linear(in_features=200, out_features=3294, bias=True)\n",")\n","Trainable parameters: 4327894\n","RecVAE(\n"," (encoder): Encoder(\n"," (fc1): Linear(in_features=3294, out_features=600, bias=True)\n"," (ln1): LayerNorm((600,), eps=0.1, elementwise_affine=True)\n"," (fc2): Linear(in_features=600, out_features=600, bias=True)\n"," (ln2): LayerNorm((600,), eps=0.1, elementwise_affine=True)\n"," (fc3): Linear(in_features=600, out_features=600, bias=True)\n"," (ln3): LayerNorm((600,), eps=0.1, elementwise_affine=True)\n"," (fc4): Linear(in_features=600, out_features=600, bias=True)\n"," (ln4): LayerNorm((600,), eps=0.1, elementwise_affine=True)\n"," (fc5): Linear(in_features=600, out_features=600, bias=True)\n"," (ln5): LayerNorm((600,), eps=0.1, elementwise_affine=True)\n"," (fc_mu): Linear(in_features=600, out_features=200, bias=True)\n"," (fc_logvar): Linear(in_features=600, out_features=200, bias=True)\n"," )\n"," (prior): CompositePrior(\n"," (encoder_old): Encoder(\n"," (fc1): Linear(in_features=3294, out_features=600, bias=True)\n"," (ln1): LayerNorm((600,), eps=0.1, elementwise_affine=True)\n"," (fc2): Linear(in_features=600, out_features=600, bias=True)\n"," (ln2): LayerNorm((600,), eps=0.1, elementwise_affine=True)\n"," (fc3): Linear(in_features=600, out_features=600, bias=True)\n"," (ln3): LayerNorm((600,), eps=0.1, elementwise_affine=True)\n"," (fc4): Linear(in_features=600, out_features=600, bias=True)\n"," (ln4): LayerNorm((600,), eps=0.1, elementwise_affine=True)\n"," (fc5): Linear(in_features=600, out_features=600, bias=True)\n"," (ln5): LayerNorm((600,), eps=0.1, elementwise_affine=True)\n"," (fc_mu): Linear(in_features=600, out_features=200, bias=True)\n"," (fc_logvar): Linear(in_features=600, out_features=200, bias=True)\n"," )\n"," )\n"," (decoder): Linear(in_features=200, out_features=3294, bias=True)\n",")\n","Trainable parameters: 4327894\n","11 Dec 12:25 INFO FLOPs: 4321200.0\n","FLOPs: 4321200.0\n","11 Dec 12:25 INFO epoch 0 training [time: 23.87s, train loss: 2354.4009]\n","epoch 0 training [time: 23.87s, train loss: 2354.4009]\n","11 Dec 12:25 INFO Saving current: saved/RecVAE-Dec-11-2023_12-25-16.pth\n","Saving current: saved/RecVAE-Dec-11-2023_12-25-16.pth\n","11 Dec 12:26 INFO epoch 1 training [time: 26.41s, train loss: 2247.2854]\n","epoch 1 training [time: 26.41s, train loss: 2247.2854]\n","11 Dec 12:26 INFO Saving current: saved/RecVAE-Dec-11-2023_12-25-16.pth\n","Saving current: saved/RecVAE-Dec-11-2023_12-25-16.pth\n","11 Dec 12:26 INFO epoch 2 training [time: 26.19s, train loss: 2184.4206]\n","epoch 2 training [time: 26.19s, train loss: 2184.4206]\n","11 Dec 12:26 INFO Saving current: saved/RecVAE-Dec-11-2023_12-25-16.pth\n","Saving current: saved/RecVAE-Dec-11-2023_12-25-16.pth\n","11 Dec 12:27 INFO epoch 3 training [time: 28.26s, train loss: 2147.9836]\n","epoch 3 training [time: 28.26s, train loss: 2147.9836]\n","11 Dec 12:27 INFO Saving current: saved/RecVAE-Dec-11-2023_12-25-16.pth\n","Saving current: saved/RecVAE-Dec-11-2023_12-25-16.pth\n","11 Dec 12:27 INFO epoch 4 training [time: 25.59s, train loss: 2108.6837]\n","epoch 4 training [time: 25.59s, train loss: 2108.6837]\n","11 Dec 12:27 INFO Saving current: saved/RecVAE-Dec-11-2023_12-25-16.pth\n","Saving current: saved/RecVAE-Dec-11-2023_12-25-16.pth\n","11 Dec 12:27 INFO epoch 5 training [time: 19.99s, train loss: 2073.2995]\n","epoch 5 training [time: 19.99s, train loss: 2073.2995]\n","11 Dec 12:27 INFO Saving current: saved/RecVAE-Dec-11-2023_12-25-16.pth\n","Saving current: saved/RecVAE-Dec-11-2023_12-25-16.pth\n","11 Dec 12:28 INFO epoch 6 training [time: 23.58s, train loss: 2043.1616]\n","epoch 6 training [time: 23.58s, train loss: 2043.1616]\n","11 Dec 12:28 INFO Saving current: saved/RecVAE-Dec-11-2023_12-25-16.pth\n","Saving current: saved/RecVAE-Dec-11-2023_12-25-16.pth\n","11 Dec 12:28 INFO epoch 7 training [time: 24.14s, train loss: 2013.9314]\n","epoch 7 training [time: 24.14s, train loss: 2013.9314]\n","11 Dec 12:28 INFO Saving current: saved/RecVAE-Dec-11-2023_12-25-16.pth\n","Saving current: saved/RecVAE-Dec-11-2023_12-25-16.pth\n","11 Dec 12:28 INFO epoch 8 training [time: 12.42s, train loss: 1998.7426]\n","epoch 8 training [time: 12.42s, train loss: 1998.7426]\n","11 Dec 12:28 INFO Saving current: saved/RecVAE-Dec-11-2023_12-25-16.pth\n","Saving current: saved/RecVAE-Dec-11-2023_12-25-16.pth\n","11 Dec 12:28 INFO epoch 9 training [time: 10.69s, train loss: 1973.2974]\n","epoch 9 training [time: 10.69s, train loss: 1973.2974]\n","11 Dec 12:28 INFO Saving current: saved/RecVAE-Dec-11-2023_12-25-16.pth\n","Saving current: saved/RecVAE-Dec-11-2023_12-25-16.pth\n","11 Dec 12:28 INFO Loading model structure and parameters from saved/RecVAE-Dec-11-2023_12-25-16.pth\n","Loading model structure and parameters from saved/RecVAE-Dec-11-2023_12-25-16.pth\n","11 Dec 12:29 INFO The running environment of this training is as follows:\n","+-------------+---------------+\n","| Environment | Usage |\n","+=============+===============+\n","| CPU | 50.10 % |\n","+-------------+---------------+\n","| GPU | 0.0 / 0.0 |\n","+-------------+---------------+\n","| Memory | 0.09 G/8.00 G |\n","+-------------+---------------+\n","The running environment of this training is as follows:\n","+-------------+---------------+\n","| Environment | Usage |\n","+=============+===============+\n","| CPU | 50.10 % |\n","+-------------+---------------+\n","| GPU | 0.0 / 0.0 |\n","+-------------+---------------+\n","| Memory | 0.09 G/8.00 G |\n","+-------------+---------------+\n","11 Dec 12:29 INFO best valid : None\n","best valid : None\n","11 Dec 12:29 INFO test result: OrderedDict([('recall@10', 0.0844), ('mrr@10', 0.1662), ('ndcg@10', 0.0816), ('hit@10', 0.3519), ('precision@10', 0.0468)])\n","test result: OrderedDict([('recall@10', 0.0844), ('mrr@10', 0.1662), ('ndcg@10', 0.0816), ('hit@10', 0.3519), ('precision@10', 0.0468)])\n"]},{"name":"stdout","output_type":"stream","text":["It took 6.70 mins\n","{'best_valid_score': -inf, 'valid_score_bigger': True, 'best_valid_result': None, 'test_result': OrderedDict([('recall@10', 0.0844), ('mrr@10', 0.1662), ('ndcg@10', 0.0816), ('hit@10', 0.3519), ('precision@10', 0.0468)])}\n","CPU times: user 25min 39s, sys: 9min 10s, total: 34min 49s\n","Wall time: 32min 31s\n"]}],"source":["%%time\n","model_list = [ \"LightGCN\", \"MultiVAE\", \"RecVAE\"] \n","\n","for model_name in model_list:\n"," print(f\"running {model_name}...\")\n"," start = time.time()\n"," result = run_recbole(model=model_name, dataset = 'recbox_data',config_dict = parameter_dict)\n"," t = time.time() - start\n"," print(f\"It took {t/60:.2f} mins\")\n"," print(result)"]},{"cell_type":"code","execution_count":35,"metadata":{},"outputs":[{"name":"stdout","output_type":"stream","text":["Collecting kmeans-pytorch\n"," Downloading kmeans_pytorch-0.3-py3-none-any.whl (4.4 kB)\n","Installing collected packages: kmeans-pytorch\n","Successfully installed kmeans-pytorch-0.3\n","Note: you may need to restart the kernel to use updated packages.\n"]}],"source":["%pip install kmeans-pytorch"]},{"cell_type":"code","execution_count":36,"metadata":{},"outputs":[],"source":["from kmeans_pytorch import kmeans"]},{"cell_type":"code","execution_count":37,"metadata":{"execution":{"iopub.execute_input":"2023-01-22T18:14:48.482175Z","iopub.status.busy":"2023-01-22T18:14:48.481796Z","iopub.status.idle":"2023-01-22T19:32:27.636297Z","shell.execute_reply":"2023-01-22T19:32:27.635371Z","shell.execute_reply.started":"2023-01-22T18:14:48.482143Z"},"trusted":true},"outputs":[{"name":"stdout","output_type":"stream","text":["running CORE...\n"]},{"name":"stderr","output_type":"stream","text":["11 Dec 13:40 INFO ['/Users/annapikuleva/Library/Python/3.9/lib/python/site-packages/ipykernel_launcher.py', '--f=/Users/annapikuleva/Library/Jupyter/runtime/kernel-v2-3832937JAU6uqtVOE.json']\n","['/Users/annapikuleva/Library/Python/3.9/lib/python/site-packages/ipykernel_launcher.py', '--f=/Users/annapikuleva/Library/Jupyter/runtime/kernel-v2-3832937JAU6uqtVOE.json']\n","11 Dec 13:40 INFO \n","General Hyper Parameters:\n","gpu_id = 0\n","use_gpu = True\n","seed = 2020\n","state = INFO\n","reproducibility = True\n","data_path = recbox_data\n","checkpoint_dir = saved\n","show_progress = False\n","save_dataset = False\n","dataset_save_path = None\n","save_dataloaders = False\n","dataloaders_save_path = None\n","log_wandb = False\n","\n","Training Hyper Parameters:\n","epochs = 10\n","train_batch_size = 2048\n","learner = adam\n","learning_rate = 0.001\n","train_neg_sample_args = {'distribution': 'none', 'sample_num': 'none', 'alpha': 'none', 'dynamic': False, 'candidate_num': 0}\n","eval_step = 1\n","stopping_step = 10\n","clip_grad_norm = None\n","weight_decay = 0.0\n","loss_decimal_place = 4\n","\n","Evaluation Hyper Parameters:\n","eval_args = {'split': {'RS': [9, 0, 1]}, 'order': 'TO', 'group_by': 'user', 'mode': {'valid': 'full', 'test': 'full'}}\n","repeatable = True\n","metrics = ['Recall', 'MRR', 'NDCG', 'Hit', 'Precision']\n","topk = [10]\n","valid_metric = MRR@10\n","valid_metric_bigger = True\n","eval_batch_size = 4096\n","metric_decimal_place = 4\n","\n","Dataset Hyper Parameters:\n","field_separator = \t\n","seq_separator = \n","USER_ID_FIELD = user_id\n","ITEM_ID_FIELD = item_id\n","RATING_FIELD = rating\n","TIME_FIELD = timestamp\n","seq_len = None\n","LABEL_FIELD = label\n","threshold = None\n","NEG_PREFIX = neg_\n","load_col = {'inter': ['user_id', 'item_id', 'timestamp']}\n","unload_col = None\n","unused_col = None\n","additional_feat_suffix = None\n","rm_dup_inter = None\n","val_interval = None\n","filter_inter_by_user_or_item = True\n","user_inter_num_interval = [40,inf)\n","item_inter_num_interval = [40,inf)\n","alias_of_user_id = None\n","alias_of_item_id = None\n","alias_of_entity_id = None\n","alias_of_relation_id = None\n","preload_weight = None\n","normalize_field = None\n","normalize_all = None\n","ITEM_LIST_LENGTH_FIELD = item_length\n","LIST_SUFFIX = _list\n","MAX_ITEM_LIST_LENGTH = 50\n","POSITION_FIELD = position_id\n","HEAD_ENTITY_ID_FIELD = head_id\n","TAIL_ENTITY_ID_FIELD = tail_id\n","RELATION_ID_FIELD = relation_id\n","ENTITY_ID_FIELD = entity_id\n","benchmark_filename = None\n","\n","Other Hyper Parameters: \n","worker = 0\n","wandb_project = recbole\n","shuffle = True\n","require_pow = False\n","enable_amp = False\n","enable_scaler = False\n","transform = None\n","embedding_size = 64\n","inner_size = 256\n","n_layers = 2\n","n_heads = 2\n","hidden_dropout_prob = 0.5\n","attn_dropout_prob = 0.5\n","hidden_act = gelu\n","layer_norm_eps = 1e-12\n","initializer_range = 0.02\n","loss_type = CE\n","dnn_type = trm\n","sess_dropout = 0.2\n","item_dropout = 0.2\n","temperature = 0.07\n","numerical_features = []\n","discretization = None\n","kg_reverse_r = False\n","entity_kg_num_interval = [0,inf)\n","relation_kg_num_interval = [0,inf)\n","MODEL_TYPE = ModelType.SEQUENTIAL\n","device = cpu\n","neg_sampling = None\n","verbose = -1\n","MODEL_INPUT_TYPE = InputType.POINTWISE\n","eval_type = EvaluatorType.RANKING\n","single_spec = True\n","local_rank = 0\n","valid_neg_sample_args = {'distribution': 'uniform', 'sample_num': 'none'}\n","test_neg_sample_args = {'distribution': 'uniform', 'sample_num': 'none'}\n","\n","\n","\n","General Hyper Parameters:\n","gpu_id = 0\n","use_gpu = True\n","seed = 2020\n","state = INFO\n","reproducibility = True\n","data_path = recbox_data\n","checkpoint_dir = saved\n","show_progress = False\n","save_dataset = False\n","dataset_save_path = None\n","save_dataloaders = False\n","dataloaders_save_path = None\n","log_wandb = False\n","\n","Training Hyper Parameters:\n","epochs = 10\n","train_batch_size = 2048\n","learner = adam\n","learning_rate = 0.001\n","train_neg_sample_args = {'distribution': 'none', 'sample_num': 'none', 'alpha': 'none', 'dynamic': False, 'candidate_num': 0}\n","eval_step = 1\n","stopping_step = 10\n","clip_grad_norm = None\n","weight_decay = 0.0\n","loss_decimal_place = 4\n","\n","Evaluation Hyper Parameters:\n","eval_args = {'split': {'RS': [9, 0, 1]}, 'order': 'TO', 'group_by': 'user', 'mode': {'valid': 'full', 'test': 'full'}}\n","repeatable = True\n","metrics = ['Recall', 'MRR', 'NDCG', 'Hit', 'Precision']\n","topk = [10]\n","valid_metric = MRR@10\n","valid_metric_bigger = True\n","eval_batch_size = 4096\n","metric_decimal_place = 4\n","\n","Dataset Hyper Parameters:\n","field_separator = \t\n","seq_separator = \n","USER_ID_FIELD = user_id\n","ITEM_ID_FIELD = item_id\n","RATING_FIELD = rating\n","TIME_FIELD = timestamp\n","seq_len = None\n","LABEL_FIELD = label\n","threshold = None\n","NEG_PREFIX = neg_\n","load_col = {'inter': ['user_id', 'item_id', 'timestamp']}\n","unload_col = None\n","unused_col = None\n","additional_feat_suffix = None\n","rm_dup_inter = None\n","val_interval = None\n","filter_inter_by_user_or_item = True\n","user_inter_num_interval = [40,inf)\n","item_inter_num_interval = [40,inf)\n","alias_of_user_id = None\n","alias_of_item_id = None\n","alias_of_entity_id = None\n","alias_of_relation_id = None\n","preload_weight = None\n","normalize_field = None\n","normalize_all = None\n","ITEM_LIST_LENGTH_FIELD = item_length\n","LIST_SUFFIX = _list\n","MAX_ITEM_LIST_LENGTH = 50\n","POSITION_FIELD = position_id\n","HEAD_ENTITY_ID_FIELD = head_id\n","TAIL_ENTITY_ID_FIELD = tail_id\n","RELATION_ID_FIELD = relation_id\n","ENTITY_ID_FIELD = entity_id\n","benchmark_filename = None\n","\n","Other Hyper Parameters: \n","worker = 0\n","wandb_project = recbole\n","shuffle = True\n","require_pow = False\n","enable_amp = False\n","enable_scaler = False\n","transform = None\n","embedding_size = 64\n","inner_size = 256\n","n_layers = 2\n","n_heads = 2\n","hidden_dropout_prob = 0.5\n","attn_dropout_prob = 0.5\n","hidden_act = gelu\n","layer_norm_eps = 1e-12\n","initializer_range = 0.02\n","loss_type = CE\n","dnn_type = trm\n","sess_dropout = 0.2\n","item_dropout = 0.2\n","temperature = 0.07\n","numerical_features = []\n","discretization = None\n","kg_reverse_r = False\n","entity_kg_num_interval = [0,inf)\n","relation_kg_num_interval = [0,inf)\n","MODEL_TYPE = ModelType.SEQUENTIAL\n","device = cpu\n","neg_sampling = None\n","verbose = -1\n","MODEL_INPUT_TYPE = InputType.POINTWISE\n","eval_type = EvaluatorType.RANKING\n","single_spec = True\n","local_rank = 0\n","valid_neg_sample_args = {'distribution': 'uniform', 'sample_num': 'none'}\n","test_neg_sample_args = {'distribution': 'uniform', 'sample_num': 'none'}\n","\n","\n","11 Dec 13:41 INFO recbox_data\n","The number of users: 13355\n","Average actions of users: 63.815710648494836\n","The number of items: 3294\n","Average actions of items: 258.78985727300335\n","The number of inters: 852195\n","The sparsity of the dataset: 98.06281322904924%\n","Remain Fields: ['user_id', 'item_id', 'timestamp']\n","recbox_data\n","The number of users: 13355\n","Average actions of users: 63.815710648494836\n","The number of items: 3294\n","Average actions of items: 258.78985727300335\n","The number of inters: 852195\n","The sparsity of the dataset: 98.06281322904924%\n","Remain Fields: ['user_id', 'item_id', 'timestamp']\n","11 Dec 13:42 INFO [Training]: train_batch_size = [2048] train_neg_sample_args: [{'distribution': 'none', 'sample_num': 'none', 'alpha': 'none', 'dynamic': False, 'candidate_num': 0}]\n","[Training]: train_batch_size = [2048] train_neg_sample_args: [{'distribution': 'none', 'sample_num': 'none', 'alpha': 'none', 'dynamic': False, 'candidate_num': 0}]\n","11 Dec 13:42 INFO [Evaluation]: eval_batch_size = [4096] eval_args: [{'split': {'RS': [9, 0, 1]}, 'order': 'TO', 'group_by': 'user', 'mode': {'valid': 'full', 'test': 'full'}}]\n","[Evaluation]: eval_batch_size = [4096] eval_args: [{'split': {'RS': [9, 0, 1]}, 'order': 'TO', 'group_by': 'user', 'mode': {'valid': 'full', 'test': 'full'}}]\n","11 Dec 13:42 INFO CORE(\n"," (sess_dropout): Dropout(p=0.2, inplace=False)\n"," (item_dropout): Dropout(p=0.2, inplace=False)\n"," (item_embedding): Embedding(3294, 64, padding_idx=0)\n"," (net): TransNet(\n"," (position_embedding): Embedding(50, 64)\n"," (trm_encoder): TransformerEncoder(\n"," (layer): ModuleList(\n"," (0-1): 2 x TransformerLayer(\n"," (multi_head_attention): MultiHeadAttention(\n"," (query): Linear(in_features=64, out_features=64, bias=True)\n"," (key): Linear(in_features=64, out_features=64, bias=True)\n"," (value): Linear(in_features=64, out_features=64, bias=True)\n"," (softmax): Softmax(dim=-1)\n"," (attn_dropout): Dropout(p=0.5, inplace=False)\n"," (dense): Linear(in_features=64, out_features=64, bias=True)\n"," (LayerNorm): LayerNorm((64,), eps=1e-12, elementwise_affine=True)\n"," (out_dropout): Dropout(p=0.5, inplace=False)\n"," )\n"," (feed_forward): FeedForward(\n"," (dense_1): Linear(in_features=64, out_features=256, bias=True)\n"," (dense_2): Linear(in_features=256, out_features=64, bias=True)\n"," (LayerNorm): LayerNorm((64,), eps=1e-12, elementwise_affine=True)\n"," (dropout): Dropout(p=0.5, inplace=False)\n"," )\n"," )\n"," )\n"," )\n"," (LayerNorm): LayerNorm((64,), eps=1e-12, elementwise_affine=True)\n"," (dropout): Dropout(p=0.5, inplace=False)\n"," (fn): Linear(in_features=64, out_features=1, bias=True)\n"," )\n"," (loss_fct): CrossEntropyLoss()\n",")\n","Trainable parameters: 314177\n","CORE(\n"," (sess_dropout): Dropout(p=0.2, inplace=False)\n"," (item_dropout): Dropout(p=0.2, inplace=False)\n"," (item_embedding): Embedding(3294, 64, padding_idx=0)\n"," (net): TransNet(\n"," (position_embedding): Embedding(50, 64)\n"," (trm_encoder): TransformerEncoder(\n"," (layer): ModuleList(\n"," (0-1): 2 x TransformerLayer(\n"," (multi_head_attention): MultiHeadAttention(\n"," (query): Linear(in_features=64, out_features=64, bias=True)\n"," (key): Linear(in_features=64, out_features=64, bias=True)\n"," (value): Linear(in_features=64, out_features=64, bias=True)\n"," (softmax): Softmax(dim=-1)\n"," (attn_dropout): Dropout(p=0.5, inplace=False)\n"," (dense): Linear(in_features=64, out_features=64, bias=True)\n"," (LayerNorm): LayerNorm((64,), eps=1e-12, elementwise_affine=True)\n"," (out_dropout): Dropout(p=0.5, inplace=False)\n"," )\n"," (feed_forward): FeedForward(\n"," (dense_1): Linear(in_features=64, out_features=256, bias=True)\n"," (dense_2): Linear(in_features=256, out_features=64, bias=True)\n"," (LayerNorm): LayerNorm((64,), eps=1e-12, elementwise_affine=True)\n"," (dropout): Dropout(p=0.5, inplace=False)\n"," )\n"," )\n"," )\n"," )\n"," (LayerNorm): LayerNorm((64,), eps=1e-12, elementwise_affine=True)\n"," (dropout): Dropout(p=0.5, inplace=False)\n"," (fn): Linear(in_features=64, out_features=1, bias=True)\n"," )\n"," (loss_fct): CrossEntropyLoss()\n",")\n","Trainable parameters: 314177\n","11 Dec 13:42 INFO FLOPs: 4986664.0\n","FLOPs: 4986664.0\n","11 Dec 14:56 INFO epoch 0 training [time: 4465.02s, train loss: 3014.5362]\n","epoch 0 training [time: 4465.02s, train loss: 3014.5362]\n","11 Dec 14:56 INFO Saving current: saved/CORE-Dec-11-2023_13-42-03.pth\n","Saving current: saved/CORE-Dec-11-2023_13-42-03.pth\n","11 Dec 15:22 INFO epoch 1 training [time: 1551.87s, train loss: 2695.9846]\n","epoch 1 training [time: 1551.87s, train loss: 2695.9846]\n","11 Dec 15:22 INFO Saving current: saved/CORE-Dec-11-2023_13-42-03.pth\n","Saving current: saved/CORE-Dec-11-2023_13-42-03.pth\n","11 Dec 15:51 INFO epoch 2 training [time: 1725.13s, train loss: 2613.6121]\n","epoch 2 training [time: 1725.13s, train loss: 2613.6121]\n","11 Dec 15:51 INFO Saving current: saved/CORE-Dec-11-2023_13-42-03.pth\n","Saving current: saved/CORE-Dec-11-2023_13-42-03.pth\n","11 Dec 16:20 INFO epoch 3 training [time: 1788.98s, train loss: 2579.6137]\n","epoch 3 training [time: 1788.98s, train loss: 2579.6137]\n","11 Dec 16:20 INFO Saving current: saved/CORE-Dec-11-2023_13-42-03.pth\n","Saving current: saved/CORE-Dec-11-2023_13-42-03.pth\n","11 Dec 17:53 INFO epoch 4 training [time: 5548.65s, train loss: 2562.4357]\n","epoch 4 training [time: 5548.65s, train loss: 2562.4357]\n","11 Dec 17:53 INFO Saving current: saved/CORE-Dec-11-2023_13-42-03.pth\n","Saving current: saved/CORE-Dec-11-2023_13-42-03.pth\n","11 Dec 18:23 INFO epoch 5 training [time: 1835.07s, train loss: 2551.7563]\n","epoch 5 training [time: 1835.07s, train loss: 2551.7563]\n","11 Dec 18:23 INFO Saving current: saved/CORE-Dec-11-2023_13-42-03.pth\n","Saving current: saved/CORE-Dec-11-2023_13-42-03.pth\n","11 Dec 18:52 INFO epoch 6 training [time: 1691.97s, train loss: 2545.4003]\n","epoch 6 training [time: 1691.97s, train loss: 2545.4003]\n","11 Dec 18:52 INFO Saving current: saved/CORE-Dec-11-2023_13-42-03.pth\n","Saving current: saved/CORE-Dec-11-2023_13-42-03.pth\n","11 Dec 19:23 INFO epoch 7 training [time: 1858.18s, train loss: 2540.3678]\n","epoch 7 training [time: 1858.18s, train loss: 2540.3678]\n","11 Dec 19:23 INFO Saving current: saved/CORE-Dec-11-2023_13-42-03.pth\n","Saving current: saved/CORE-Dec-11-2023_13-42-03.pth\n","11 Dec 20:06 INFO epoch 8 training [time: 2614.77s, train loss: 2537.1684]\n","epoch 8 training [time: 2614.77s, train loss: 2537.1684]\n","11 Dec 20:06 INFO Saving current: saved/CORE-Dec-11-2023_13-42-03.pth\n","Saving current: saved/CORE-Dec-11-2023_13-42-03.pth\n","11 Dec 21:22 INFO epoch 9 training [time: 4561.69s, train loss: 2534.2584]\n","epoch 9 training [time: 4561.69s, train loss: 2534.2584]\n","11 Dec 21:22 INFO Saving current: saved/CORE-Dec-11-2023_13-42-03.pth\n","Saving current: saved/CORE-Dec-11-2023_13-42-03.pth\n","11 Dec 21:22 INFO Loading model structure and parameters from saved/CORE-Dec-11-2023_13-42-03.pth\n","Loading model structure and parameters from saved/CORE-Dec-11-2023_13-42-03.pth\n","11 Dec 21:23 INFO The running environment of this training is as follows:\n","+-------------+---------------+\n","| Environment | Usage |\n","+=============+===============+\n","| CPU | 46.10 % |\n","+-------------+---------------+\n","| GPU | 0.0 / 0.0 |\n","+-------------+---------------+\n","| Memory | 0.77 G/8.00 G |\n","+-------------+---------------+\n","The running environment of this training is as follows:\n","+-------------+---------------+\n","| Environment | Usage |\n","+=============+===============+\n","| CPU | 46.10 % |\n","+-------------+---------------+\n","| GPU | 0.0 / 0.0 |\n","+-------------+---------------+\n","| Memory | 0.77 G/8.00 G |\n","+-------------+---------------+\n","11 Dec 21:23 INFO best valid : None\n","best valid : None\n","11 Dec 21:23 INFO test result: OrderedDict([('recall@10', 0.0921), ('mrr@10', 0.0297), ('ndcg@10', 0.044), ('hit@10', 0.0921), ('precision@10', 0.0092)])\n","test result: OrderedDict([('recall@10', 0.0921), ('mrr@10', 0.0297), ('ndcg@10', 0.044), ('hit@10', 0.0921), ('precision@10', 0.0092)])\n"]},{"name":"stdout","output_type":"stream","text":["It took 463.62 mins\n","{'best_valid_score': -inf, 'valid_score_bigger': True, 'best_valid_result': None, 'test_result': OrderedDict([('recall@10', 0.0921), ('mrr@10', 0.0297), ('ndcg@10', 0.044), ('hit@10', 0.0921), ('precision@10', 0.0092)])}\n","running LightSANs...\n"]},{"name":"stderr","output_type":"stream","text":["11 Dec 21:23 INFO ['/Users/annapikuleva/Library/Python/3.9/lib/python/site-packages/ipykernel_launcher.py', '--f=/Users/annapikuleva/Library/Jupyter/runtime/kernel-v2-3832937JAU6uqtVOE.json']\n","['/Users/annapikuleva/Library/Python/3.9/lib/python/site-packages/ipykernel_launcher.py', '--f=/Users/annapikuleva/Library/Jupyter/runtime/kernel-v2-3832937JAU6uqtVOE.json']\n","11 Dec 21:23 INFO \n","General Hyper Parameters:\n","gpu_id = 0\n","use_gpu = True\n","seed = 2020\n","state = INFO\n","reproducibility = True\n","data_path = recbox_data\n","checkpoint_dir = saved\n","show_progress = False\n","save_dataset = False\n","dataset_save_path = None\n","save_dataloaders = False\n","dataloaders_save_path = None\n","log_wandb = False\n","\n","Training Hyper Parameters:\n","epochs = 10\n","train_batch_size = 2048\n","learner = adam\n","learning_rate = 0.001\n","train_neg_sample_args = {'distribution': 'none', 'sample_num': 'none', 'alpha': 'none', 'dynamic': False, 'candidate_num': 0}\n","eval_step = 1\n","stopping_step = 10\n","clip_grad_norm = None\n","weight_decay = 0.0\n","loss_decimal_place = 4\n","\n","Evaluation Hyper Parameters:\n","eval_args = {'split': {'RS': [9, 0, 1]}, 'order': 'TO', 'group_by': 'user', 'mode': {'valid': 'full', 'test': 'full'}}\n","repeatable = True\n","metrics = ['Recall', 'MRR', 'NDCG', 'Hit', 'Precision']\n","topk = [10]\n","valid_metric = MRR@10\n","valid_metric_bigger = True\n","eval_batch_size = 4096\n","metric_decimal_place = 4\n","\n","Dataset Hyper Parameters:\n","field_separator = \t\n","seq_separator = \n","USER_ID_FIELD = user_id\n","ITEM_ID_FIELD = item_id\n","RATING_FIELD = rating\n","TIME_FIELD = timestamp\n","seq_len = None\n","LABEL_FIELD = label\n","threshold = None\n","NEG_PREFIX = neg_\n","load_col = {'inter': ['user_id', 'item_id', 'timestamp']}\n","unload_col = None\n","unused_col = None\n","additional_feat_suffix = None\n","rm_dup_inter = None\n","val_interval = None\n","filter_inter_by_user_or_item = True\n","user_inter_num_interval = [40,inf)\n","item_inter_num_interval = [40,inf)\n","alias_of_user_id = None\n","alias_of_item_id = None\n","alias_of_entity_id = None\n","alias_of_relation_id = None\n","preload_weight = None\n","normalize_field = None\n","normalize_all = None\n","ITEM_LIST_LENGTH_FIELD = item_length\n","LIST_SUFFIX = _list\n","MAX_ITEM_LIST_LENGTH = 50\n","POSITION_FIELD = position_id\n","HEAD_ENTITY_ID_FIELD = head_id\n","TAIL_ENTITY_ID_FIELD = tail_id\n","RELATION_ID_FIELD = relation_id\n","ENTITY_ID_FIELD = entity_id\n","benchmark_filename = None\n","\n","Other Hyper Parameters: \n","worker = 0\n","wandb_project = recbole\n","shuffle = True\n","require_pow = False\n","enable_amp = False\n","enable_scaler = False\n","transform = None\n","k_interests = 5\n","n_layers = 2\n","n_heads = 2\n","hidden_size = 64\n","inner_size = 256\n","hidden_dropout_prob = 0.5\n","attn_dropout_prob = 0.5\n","hidden_act = gelu\n","layer_norm_eps = 1e-12\n","initializer_range = 0.02\n","loss_type = CE\n","numerical_features = []\n","discretization = None\n","kg_reverse_r = False\n","entity_kg_num_interval = [0,inf)\n","relation_kg_num_interval = [0,inf)\n","MODEL_TYPE = ModelType.SEQUENTIAL\n","device = cpu\n","neg_sampling = None\n","verbose = -1\n","MODEL_INPUT_TYPE = InputType.POINTWISE\n","eval_type = EvaluatorType.RANKING\n","single_spec = True\n","local_rank = 0\n","valid_neg_sample_args = {'distribution': 'uniform', 'sample_num': 'none'}\n","test_neg_sample_args = {'distribution': 'uniform', 'sample_num': 'none'}\n","\n","\n","\n","General Hyper Parameters:\n","gpu_id = 0\n","use_gpu = True\n","seed = 2020\n","state = INFO\n","reproducibility = True\n","data_path = recbox_data\n","checkpoint_dir = saved\n","show_progress = False\n","save_dataset = False\n","dataset_save_path = None\n","save_dataloaders = False\n","dataloaders_save_path = None\n","log_wandb = False\n","\n","Training Hyper Parameters:\n","epochs = 10\n","train_batch_size = 2048\n","learner = adam\n","learning_rate = 0.001\n","train_neg_sample_args = {'distribution': 'none', 'sample_num': 'none', 'alpha': 'none', 'dynamic': False, 'candidate_num': 0}\n","eval_step = 1\n","stopping_step = 10\n","clip_grad_norm = None\n","weight_decay = 0.0\n","loss_decimal_place = 4\n","\n","Evaluation Hyper Parameters:\n","eval_args = {'split': {'RS': [9, 0, 1]}, 'order': 'TO', 'group_by': 'user', 'mode': {'valid': 'full', 'test': 'full'}}\n","repeatable = True\n","metrics = ['Recall', 'MRR', 'NDCG', 'Hit', 'Precision']\n","topk = [10]\n","valid_metric = MRR@10\n","valid_metric_bigger = True\n","eval_batch_size = 4096\n","metric_decimal_place = 4\n","\n","Dataset Hyper Parameters:\n","field_separator = \t\n","seq_separator = \n","USER_ID_FIELD = user_id\n","ITEM_ID_FIELD = item_id\n","RATING_FIELD = rating\n","TIME_FIELD = timestamp\n","seq_len = None\n","LABEL_FIELD = label\n","threshold = None\n","NEG_PREFIX = neg_\n","load_col = {'inter': ['user_id', 'item_id', 'timestamp']}\n","unload_col = None\n","unused_col = None\n","additional_feat_suffix = None\n","rm_dup_inter = None\n","val_interval = None\n","filter_inter_by_user_or_item = True\n","user_inter_num_interval = [40,inf)\n","item_inter_num_interval = [40,inf)\n","alias_of_user_id = None\n","alias_of_item_id = None\n","alias_of_entity_id = None\n","alias_of_relation_id = None\n","preload_weight = None\n","normalize_field = None\n","normalize_all = None\n","ITEM_LIST_LENGTH_FIELD = item_length\n","LIST_SUFFIX = _list\n","MAX_ITEM_LIST_LENGTH = 50\n","POSITION_FIELD = position_id\n","HEAD_ENTITY_ID_FIELD = head_id\n","TAIL_ENTITY_ID_FIELD = tail_id\n","RELATION_ID_FIELD = relation_id\n","ENTITY_ID_FIELD = entity_id\n","benchmark_filename = None\n","\n","Other Hyper Parameters: \n","worker = 0\n","wandb_project = recbole\n","shuffle = True\n","require_pow = False\n","enable_amp = False\n","enable_scaler = False\n","transform = None\n","k_interests = 5\n","n_layers = 2\n","n_heads = 2\n","hidden_size = 64\n","inner_size = 256\n","hidden_dropout_prob = 0.5\n","attn_dropout_prob = 0.5\n","hidden_act = gelu\n","layer_norm_eps = 1e-12\n","initializer_range = 0.02\n","loss_type = CE\n","numerical_features = []\n","discretization = None\n","kg_reverse_r = False\n","entity_kg_num_interval = [0,inf)\n","relation_kg_num_interval = [0,inf)\n","MODEL_TYPE = ModelType.SEQUENTIAL\n","device = cpu\n","neg_sampling = None\n","verbose = -1\n","MODEL_INPUT_TYPE = InputType.POINTWISE\n","eval_type = EvaluatorType.RANKING\n","single_spec = True\n","local_rank = 0\n","valid_neg_sample_args = {'distribution': 'uniform', 'sample_num': 'none'}\n","test_neg_sample_args = {'distribution': 'uniform', 'sample_num': 'none'}\n","\n","\n","11 Dec 21:31 INFO recbox_data\n","The number of users: 13355\n","Average actions of users: 63.815710648494836\n","The number of items: 3294\n","Average actions of items: 258.78985727300335\n","The number of inters: 852195\n","The sparsity of the dataset: 98.06281322904924%\n","Remain Fields: ['user_id', 'item_id', 'timestamp']\n","recbox_data\n","The number of users: 13355\n","Average actions of users: 63.815710648494836\n","The number of items: 3294\n","Average actions of items: 258.78985727300335\n","The number of inters: 852195\n","The sparsity of the dataset: 98.06281322904924%\n","Remain Fields: ['user_id', 'item_id', 'timestamp']\n","11 Dec 21:31 INFO [Training]: train_batch_size = [2048] train_neg_sample_args: [{'distribution': 'none', 'sample_num': 'none', 'alpha': 'none', 'dynamic': False, 'candidate_num': 0}]\n","[Training]: train_batch_size = [2048] train_neg_sample_args: [{'distribution': 'none', 'sample_num': 'none', 'alpha': 'none', 'dynamic': False, 'candidate_num': 0}]\n","11 Dec 21:31 INFO [Evaluation]: eval_batch_size = [4096] eval_args: [{'split': {'RS': [9, 0, 1]}, 'order': 'TO', 'group_by': 'user', 'mode': {'valid': 'full', 'test': 'full'}}]\n","[Evaluation]: eval_batch_size = [4096] eval_args: [{'split': {'RS': [9, 0, 1]}, 'order': 'TO', 'group_by': 'user', 'mode': {'valid': 'full', 'test': 'full'}}]\n","11 Dec 21:31 INFO LightSANs(\n"," (item_embedding): Embedding(3294, 64, padding_idx=0)\n"," (position_embedding): Embedding(50, 64)\n"," (trm_encoder): LightTransformerEncoder(\n"," (layer): ModuleList(\n"," (0-1): 2 x LightTransformerLayer(\n"," (multi_head_attention): LightMultiHeadAttention(\n"," (query): Linear(in_features=64, out_features=64, bias=True)\n"," (key): Linear(in_features=64, out_features=64, bias=True)\n"," (value): Linear(in_features=64, out_features=64, bias=True)\n"," (attpooling_key): ItemToInterestAggregation()\n"," (attpooling_value): ItemToInterestAggregation()\n"," (pos_q_linear): Linear(in_features=64, out_features=64, bias=True)\n"," (pos_k_linear): Linear(in_features=64, out_features=64, bias=True)\n"," (pos_ln): LayerNorm((64,), eps=1e-12, elementwise_affine=True)\n"," (attn_dropout): Dropout(p=0.5, inplace=False)\n"," (dense): Linear(in_features=64, out_features=64, bias=True)\n"," (LayerNorm): LayerNorm((64,), eps=1e-12, elementwise_affine=True)\n"," (out_dropout): Dropout(p=0.5, inplace=False)\n"," )\n"," (feed_forward): FeedForward(\n"," (dense_1): Linear(in_features=64, out_features=256, bias=True)\n"," (dense_2): Linear(in_features=256, out_features=64, bias=True)\n"," (LayerNorm): LayerNorm((64,), eps=1e-12, elementwise_affine=True)\n"," (dropout): Dropout(p=0.5, inplace=False)\n"," )\n"," )\n"," )\n"," )\n"," (LayerNorm): LayerNorm((64,), eps=1e-12, elementwise_affine=True)\n"," (dropout): Dropout(p=0.5, inplace=False)\n"," (loss_fct): CrossEntropyLoss()\n",")\n","Trainable parameters: 332288\n","LightSANs(\n"," (item_embedding): Embedding(3294, 64, padding_idx=0)\n"," (position_embedding): Embedding(50, 64)\n"," (trm_encoder): LightTransformerEncoder(\n"," (layer): ModuleList(\n"," (0-1): 2 x LightTransformerLayer(\n"," (multi_head_attention): LightMultiHeadAttention(\n"," (query): Linear(in_features=64, out_features=64, bias=True)\n"," (key): Linear(in_features=64, out_features=64, bias=True)\n"," (value): Linear(in_features=64, out_features=64, bias=True)\n"," (attpooling_key): ItemToInterestAggregation()\n"," (attpooling_value): ItemToInterestAggregation()\n"," (pos_q_linear): Linear(in_features=64, out_features=64, bias=True)\n"," (pos_k_linear): Linear(in_features=64, out_features=64, bias=True)\n"," (pos_ln): LayerNorm((64,), eps=1e-12, elementwise_affine=True)\n"," (attn_dropout): Dropout(p=0.5, inplace=False)\n"," (dense): Linear(in_features=64, out_features=64, bias=True)\n"," (LayerNorm): LayerNorm((64,), eps=1e-12, elementwise_affine=True)\n"," (out_dropout): Dropout(p=0.5, inplace=False)\n"," )\n"," (feed_forward): FeedForward(\n"," (dense_1): Linear(in_features=64, out_features=256, bias=True)\n"," (dense_2): Linear(in_features=256, out_features=64, bias=True)\n"," (LayerNorm): LayerNorm((64,), eps=1e-12, elementwise_affine=True)\n"," (dropout): Dropout(p=0.5, inplace=False)\n"," )\n"," )\n"," )\n"," )\n"," (LayerNorm): LayerNorm((64,), eps=1e-12, elementwise_affine=True)\n"," (dropout): Dropout(p=0.5, inplace=False)\n"," (loss_fct): CrossEntropyLoss()\n",")\n","Trainable parameters: 332288\n","11 Dec 21:31 INFO FLOPs: 5785664.0\n","FLOPs: 5785664.0\n","11 Dec 22:10 INFO epoch 0 training [time: 2297.51s, train loss: 2745.5162]\n","epoch 0 training [time: 2297.51s, train loss: 2745.5162]\n","11 Dec 22:10 INFO Saving current: saved/LightSANs-Dec-11-2023_21-31-57.pth\n","Saving current: saved/LightSANs-Dec-11-2023_21-31-57.pth\n","11 Dec 22:35 INFO epoch 1 training [time: 1523.42s, train loss: 2594.7601]\n","epoch 1 training [time: 1523.42s, train loss: 2594.7601]\n","11 Dec 22:35 INFO Saving current: saved/LightSANs-Dec-11-2023_21-31-57.pth\n","Saving current: saved/LightSANs-Dec-11-2023_21-31-57.pth\n","11 Dec 23:00 INFO epoch 2 training [time: 1474.23s, train loss: 2552.7842]\n","epoch 2 training [time: 1474.23s, train loss: 2552.7842]\n","11 Dec 23:00 INFO Saving current: saved/LightSANs-Dec-11-2023_21-31-57.pth\n","Saving current: saved/LightSANs-Dec-11-2023_21-31-57.pth\n","11 Dec 23:24 INFO epoch 3 training [time: 1459.72s, train loss: 2529.9439]\n","epoch 3 training [time: 1459.72s, train loss: 2529.9439]\n","11 Dec 23:24 INFO Saving current: saved/LightSANs-Dec-11-2023_21-31-57.pth\n","Saving current: saved/LightSANs-Dec-11-2023_21-31-57.pth\n","11 Dec 23:47 INFO epoch 4 training [time: 1402.02s, train loss: 2516.8341]\n","epoch 4 training [time: 1402.02s, train loss: 2516.8341]\n","11 Dec 23:47 INFO Saving current: saved/LightSANs-Dec-11-2023_21-31-57.pth\n","Saving current: saved/LightSANs-Dec-11-2023_21-31-57.pth\n","12 Dec 00:44 INFO epoch 5 training [time: 3408.17s, train loss: 2508.0956]\n","epoch 5 training [time: 3408.17s, train loss: 2508.0956]\n","12 Dec 00:44 INFO Saving current: saved/LightSANs-Dec-11-2023_21-31-57.pth\n","Saving current: saved/LightSANs-Dec-11-2023_21-31-57.pth\n","12 Dec 01:06 INFO epoch 6 training [time: 1326.43s, train loss: 2501.2301]\n","epoch 6 training [time: 1326.43s, train loss: 2501.2301]\n","12 Dec 01:06 INFO Saving current: saved/LightSANs-Dec-11-2023_21-31-57.pth\n","Saving current: saved/LightSANs-Dec-11-2023_21-31-57.pth\n","12 Dec 02:02 INFO epoch 7 training [time: 3351.58s, train loss: 2496.2055]\n","epoch 7 training [time: 3351.58s, train loss: 2496.2055]\n","12 Dec 02:02 INFO Saving current: saved/LightSANs-Dec-11-2023_21-31-57.pth\n","Saving current: saved/LightSANs-Dec-11-2023_21-31-57.pth\n","12 Dec 06:08 INFO epoch 8 training [time: 14777.87s, train loss: 2491.3191]\n","epoch 8 training [time: 14777.87s, train loss: 2491.3191]\n","12 Dec 06:08 INFO Saving current: saved/LightSANs-Dec-11-2023_21-31-57.pth\n","Saving current: saved/LightSANs-Dec-11-2023_21-31-57.pth\n","12 Dec 08:40 INFO epoch 9 training [time: 9116.53s, train loss: 2487.0272]\n","epoch 9 training [time: 9116.53s, train loss: 2487.0272]\n","12 Dec 08:40 INFO Saving current: saved/LightSANs-Dec-11-2023_21-31-57.pth\n","Saving current: saved/LightSANs-Dec-11-2023_21-31-57.pth\n","12 Dec 08:40 INFO Loading model structure and parameters from saved/LightSANs-Dec-11-2023_21-31-57.pth\n","Loading model structure and parameters from saved/LightSANs-Dec-11-2023_21-31-57.pth\n","12 Dec 08:41 INFO The running environment of this training is as follows:\n","+-------------+---------------+\n","| Environment | Usage |\n","+=============+===============+\n","| CPU | 30.70 % |\n","+-------------+---------------+\n","| GPU | 0.0 / 0.0 |\n","+-------------+---------------+\n","| Memory | 0.82 G/8.00 G |\n","+-------------+---------------+\n","The running environment of this training is as follows:\n","+-------------+---------------+\n","| Environment | Usage |\n","+=============+===============+\n","| CPU | 30.70 % |\n","+-------------+---------------+\n","| GPU | 0.0 / 0.0 |\n","+-------------+---------------+\n","| Memory | 0.82 G/8.00 G |\n","+-------------+---------------+\n","12 Dec 08:41 INFO best valid : None\n","best valid : None\n","12 Dec 08:41 INFO test result: OrderedDict([('recall@10', 0.1029), ('mrr@10', 0.0358), ('ndcg@10', 0.0513), ('hit@10', 0.1029), ('precision@10', 0.0103)])\n","test result: OrderedDict([('recall@10', 0.1029), ('mrr@10', 0.0358), ('ndcg@10', 0.0513), ('hit@10', 0.1029), ('precision@10', 0.0103)])\n"]},{"name":"stdout","output_type":"stream","text":["It took 677.87 mins\n","{'best_valid_score': -inf, 'valid_score_bigger': True, 'best_valid_result': None, 'test_result': OrderedDict([('recall@10', 0.1029), ('mrr@10', 0.0358), ('ndcg@10', 0.0513), ('hit@10', 0.1029), ('precision@10', 0.0103)])}\n","running NextItNet...\n"]},{"name":"stderr","output_type":"stream","text":["12 Dec 08:41 INFO ['/Users/annapikuleva/Library/Python/3.9/lib/python/site-packages/ipykernel_launcher.py', '--f=/Users/annapikuleva/Library/Jupyter/runtime/kernel-v2-3832937JAU6uqtVOE.json']\n","['/Users/annapikuleva/Library/Python/3.9/lib/python/site-packages/ipykernel_launcher.py', '--f=/Users/annapikuleva/Library/Jupyter/runtime/kernel-v2-3832937JAU6uqtVOE.json']\n","12 Dec 08:41 INFO \n","General Hyper Parameters:\n","gpu_id = 0\n","use_gpu = True\n","seed = 2020\n","state = INFO\n","reproducibility = True\n","data_path = recbox_data\n","checkpoint_dir = saved\n","show_progress = False\n","save_dataset = False\n","dataset_save_path = None\n","save_dataloaders = False\n","dataloaders_save_path = None\n","log_wandb = False\n","\n","Training Hyper Parameters:\n","epochs = 10\n","train_batch_size = 2048\n","learner = adam\n","learning_rate = 0.001\n","train_neg_sample_args = {'distribution': 'none', 'sample_num': 'none', 'alpha': 'none', 'dynamic': False, 'candidate_num': 0}\n","eval_step = 1\n","stopping_step = 10\n","clip_grad_norm = None\n","weight_decay = 0.0\n","loss_decimal_place = 4\n","\n","Evaluation Hyper Parameters:\n","eval_args = {'split': {'RS': [9, 0, 1]}, 'order': 'TO', 'group_by': 'user', 'mode': {'valid': 'full', 'test': 'full'}}\n","repeatable = True\n","metrics = ['Recall', 'MRR', 'NDCG', 'Hit', 'Precision']\n","topk = [10]\n","valid_metric = MRR@10\n","valid_metric_bigger = True\n","eval_batch_size = 4096\n","metric_decimal_place = 4\n","\n","Dataset Hyper Parameters:\n","field_separator = \t\n","seq_separator = \n","USER_ID_FIELD = user_id\n","ITEM_ID_FIELD = item_id\n","RATING_FIELD = rating\n","TIME_FIELD = timestamp\n","seq_len = None\n","LABEL_FIELD = label\n","threshold = None\n","NEG_PREFIX = neg_\n","load_col = {'inter': ['user_id', 'item_id', 'timestamp']}\n","unload_col = None\n","unused_col = None\n","additional_feat_suffix = None\n","rm_dup_inter = None\n","val_interval = None\n","filter_inter_by_user_or_item = True\n","user_inter_num_interval = [40,inf)\n","item_inter_num_interval = [40,inf)\n","alias_of_user_id = None\n","alias_of_item_id = None\n","alias_of_entity_id = None\n","alias_of_relation_id = None\n","preload_weight = None\n","normalize_field = None\n","normalize_all = None\n","ITEM_LIST_LENGTH_FIELD = item_length\n","LIST_SUFFIX = _list\n","MAX_ITEM_LIST_LENGTH = 50\n","POSITION_FIELD = position_id\n","HEAD_ENTITY_ID_FIELD = head_id\n","TAIL_ENTITY_ID_FIELD = tail_id\n","RELATION_ID_FIELD = relation_id\n","ENTITY_ID_FIELD = entity_id\n","benchmark_filename = None\n","\n","Other Hyper Parameters: \n","worker = 0\n","wandb_project = recbole\n","shuffle = True\n","require_pow = False\n","enable_amp = False\n","enable_scaler = False\n","transform = None\n","embedding_size = 64\n","kernel_size = 3\n","block_num = 5\n","dilations = [1, 4]\n","reg_weight = 1e-05\n","loss_type = CE\n","numerical_features = []\n","discretization = None\n","kg_reverse_r = False\n","entity_kg_num_interval = [0,inf)\n","relation_kg_num_interval = [0,inf)\n","MODEL_TYPE = ModelType.SEQUENTIAL\n","device = cpu\n","neg_sampling = None\n","verbose = -1\n","MODEL_INPUT_TYPE = InputType.POINTWISE\n","eval_type = EvaluatorType.RANKING\n","single_spec = True\n","local_rank = 0\n","valid_neg_sample_args = {'distribution': 'uniform', 'sample_num': 'none'}\n","test_neg_sample_args = {'distribution': 'uniform', 'sample_num': 'none'}\n","\n","\n","\n","General Hyper Parameters:\n","gpu_id = 0\n","use_gpu = True\n","seed = 2020\n","state = INFO\n","reproducibility = True\n","data_path = recbox_data\n","checkpoint_dir = saved\n","show_progress = False\n","save_dataset = False\n","dataset_save_path = None\n","save_dataloaders = False\n","dataloaders_save_path = None\n","log_wandb = False\n","\n","Training Hyper Parameters:\n","epochs = 10\n","train_batch_size = 2048\n","learner = adam\n","learning_rate = 0.001\n","train_neg_sample_args = {'distribution': 'none', 'sample_num': 'none', 'alpha': 'none', 'dynamic': False, 'candidate_num': 0}\n","eval_step = 1\n","stopping_step = 10\n","clip_grad_norm = None\n","weight_decay = 0.0\n","loss_decimal_place = 4\n","\n","Evaluation Hyper Parameters:\n","eval_args = {'split': {'RS': [9, 0, 1]}, 'order': 'TO', 'group_by': 'user', 'mode': {'valid': 'full', 'test': 'full'}}\n","repeatable = True\n","metrics = ['Recall', 'MRR', 'NDCG', 'Hit', 'Precision']\n","topk = [10]\n","valid_metric = MRR@10\n","valid_metric_bigger = True\n","eval_batch_size = 4096\n","metric_decimal_place = 4\n","\n","Dataset Hyper Parameters:\n","field_separator = \t\n","seq_separator = \n","USER_ID_FIELD = user_id\n","ITEM_ID_FIELD = item_id\n","RATING_FIELD = rating\n","TIME_FIELD = timestamp\n","seq_len = None\n","LABEL_FIELD = label\n","threshold = None\n","NEG_PREFIX = neg_\n","load_col = {'inter': ['user_id', 'item_id', 'timestamp']}\n","unload_col = None\n","unused_col = None\n","additional_feat_suffix = None\n","rm_dup_inter = None\n","val_interval = None\n","filter_inter_by_user_or_item = True\n","user_inter_num_interval = [40,inf)\n","item_inter_num_interval = [40,inf)\n","alias_of_user_id = None\n","alias_of_item_id = None\n","alias_of_entity_id = None\n","alias_of_relation_id = None\n","preload_weight = None\n","normalize_field = None\n","normalize_all = None\n","ITEM_LIST_LENGTH_FIELD = item_length\n","LIST_SUFFIX = _list\n","MAX_ITEM_LIST_LENGTH = 50\n","POSITION_FIELD = position_id\n","HEAD_ENTITY_ID_FIELD = head_id\n","TAIL_ENTITY_ID_FIELD = tail_id\n","RELATION_ID_FIELD = relation_id\n","ENTITY_ID_FIELD = entity_id\n","benchmark_filename = None\n","\n","Other Hyper Parameters: \n","worker = 0\n","wandb_project = recbole\n","shuffle = True\n","require_pow = False\n","enable_amp = False\n","enable_scaler = False\n","transform = None\n","embedding_size = 64\n","kernel_size = 3\n","block_num = 5\n","dilations = [1, 4]\n","reg_weight = 1e-05\n","loss_type = CE\n","numerical_features = []\n","discretization = None\n","kg_reverse_r = False\n","entity_kg_num_interval = [0,inf)\n","relation_kg_num_interval = [0,inf)\n","MODEL_TYPE = ModelType.SEQUENTIAL\n","device = cpu\n","neg_sampling = None\n","verbose = -1\n","MODEL_INPUT_TYPE = InputType.POINTWISE\n","eval_type = EvaluatorType.RANKING\n","single_spec = True\n","local_rank = 0\n","valid_neg_sample_args = {'distribution': 'uniform', 'sample_num': 'none'}\n","test_neg_sample_args = {'distribution': 'uniform', 'sample_num': 'none'}\n","\n","\n","12 Dec 08:43 INFO recbox_data\n","The number of users: 13355\n","Average actions of users: 63.815710648494836\n","The number of items: 3294\n","Average actions of items: 258.78985727300335\n","The number of inters: 852195\n","The sparsity of the dataset: 98.06281322904924%\n","Remain Fields: ['user_id', 'item_id', 'timestamp']\n","recbox_data\n","The number of users: 13355\n","Average actions of users: 63.815710648494836\n","The number of items: 3294\n","Average actions of items: 258.78985727300335\n","The number of inters: 852195\n","The sparsity of the dataset: 98.06281322904924%\n","Remain Fields: ['user_id', 'item_id', 'timestamp']\n","12 Dec 08:43 INFO [Training]: train_batch_size = [2048] train_neg_sample_args: [{'distribution': 'none', 'sample_num': 'none', 'alpha': 'none', 'dynamic': False, 'candidate_num': 0}]\n","[Training]: train_batch_size = [2048] train_neg_sample_args: [{'distribution': 'none', 'sample_num': 'none', 'alpha': 'none', 'dynamic': False, 'candidate_num': 0}]\n","12 Dec 08:43 INFO [Evaluation]: eval_batch_size = [4096] eval_args: [{'split': {'RS': [9, 0, 1]}, 'order': 'TO', 'group_by': 'user', 'mode': {'valid': 'full', 'test': 'full'}}]\n","[Evaluation]: eval_batch_size = [4096] eval_args: [{'split': {'RS': [9, 0, 1]}, 'order': 'TO', 'group_by': 'user', 'mode': {'valid': 'full', 'test': 'full'}}]\n","12 Dec 08:43 INFO NextItNet(\n"," (item_embedding): Embedding(3294, 64, padding_idx=0)\n"," (residual_blocks): Sequential(\n"," (0): ResidualBlock_b(\n"," (conv1): Conv2d(64, 64, kernel_size=(1, 3), stride=(1, 1))\n"," (ln1): LayerNorm((64,), eps=1e-08, elementwise_affine=True)\n"," (conv2): Conv2d(64, 64, kernel_size=(1, 3), stride=(1, 1), dilation=(2, 2))\n"," (ln2): LayerNorm((64,), eps=1e-08, elementwise_affine=True)\n"," )\n"," (1): ResidualBlock_b(\n"," (conv1): Conv2d(64, 64, kernel_size=(1, 3), stride=(1, 1), dilation=(4, 4))\n"," (ln1): LayerNorm((64,), eps=1e-08, elementwise_affine=True)\n"," (conv2): Conv2d(64, 64, kernel_size=(1, 3), stride=(1, 1), dilation=(8, 8))\n"," (ln2): LayerNorm((64,), eps=1e-08, elementwise_affine=True)\n"," )\n"," (2): ResidualBlock_b(\n"," (conv1): Conv2d(64, 64, kernel_size=(1, 3), stride=(1, 1))\n"," (ln1): LayerNorm((64,), eps=1e-08, elementwise_affine=True)\n"," (conv2): Conv2d(64, 64, kernel_size=(1, 3), stride=(1, 1), dilation=(2, 2))\n"," (ln2): LayerNorm((64,), eps=1e-08, elementwise_affine=True)\n"," )\n"," (3): ResidualBlock_b(\n"," (conv1): Conv2d(64, 64, kernel_size=(1, 3), stride=(1, 1), dilation=(4, 4))\n"," (ln1): LayerNorm((64,), eps=1e-08, elementwise_affine=True)\n"," (conv2): Conv2d(64, 64, kernel_size=(1, 3), stride=(1, 1), dilation=(8, 8))\n"," (ln2): LayerNorm((64,), eps=1e-08, elementwise_affine=True)\n"," )\n"," (4): ResidualBlock_b(\n"," (conv1): Conv2d(64, 64, kernel_size=(1, 3), stride=(1, 1))\n"," (ln1): LayerNorm((64,), eps=1e-08, elementwise_affine=True)\n"," (conv2): Conv2d(64, 64, kernel_size=(1, 3), stride=(1, 1), dilation=(2, 2))\n"," (ln2): LayerNorm((64,), eps=1e-08, elementwise_affine=True)\n"," )\n"," (5): ResidualBlock_b(\n"," (conv1): Conv2d(64, 64, kernel_size=(1, 3), stride=(1, 1), dilation=(4, 4))\n"," (ln1): LayerNorm((64,), eps=1e-08, elementwise_affine=True)\n"," (conv2): Conv2d(64, 64, kernel_size=(1, 3), stride=(1, 1), dilation=(8, 8))\n"," (ln2): LayerNorm((64,), eps=1e-08, elementwise_affine=True)\n"," )\n"," (6): ResidualBlock_b(\n"," (conv1): Conv2d(64, 64, kernel_size=(1, 3), stride=(1, 1))\n"," (ln1): LayerNorm((64,), eps=1e-08, elementwise_affine=True)\n"," (conv2): Conv2d(64, 64, kernel_size=(1, 3), stride=(1, 1), dilation=(2, 2))\n"," (ln2): LayerNorm((64,), eps=1e-08, elementwise_affine=True)\n"," )\n"," (7): ResidualBlock_b(\n"," (conv1): Conv2d(64, 64, kernel_size=(1, 3), stride=(1, 1), dilation=(4, 4))\n"," (ln1): LayerNorm((64,), eps=1e-08, elementwise_affine=True)\n"," (conv2): Conv2d(64, 64, kernel_size=(1, 3), stride=(1, 1), dilation=(8, 8))\n"," (ln2): LayerNorm((64,), eps=1e-08, elementwise_affine=True)\n"," )\n"," (8): ResidualBlock_b(\n"," (conv1): Conv2d(64, 64, kernel_size=(1, 3), stride=(1, 1))\n"," (ln1): LayerNorm((64,), eps=1e-08, elementwise_affine=True)\n"," (conv2): Conv2d(64, 64, kernel_size=(1, 3), stride=(1, 1), dilation=(2, 2))\n"," (ln2): LayerNorm((64,), eps=1e-08, elementwise_affine=True)\n"," )\n"," (9): ResidualBlock_b(\n"," (conv1): Conv2d(64, 64, kernel_size=(1, 3), stride=(1, 1), dilation=(4, 4))\n"," (ln1): LayerNorm((64,), eps=1e-08, elementwise_affine=True)\n"," (conv2): Conv2d(64, 64, kernel_size=(1, 3), stride=(1, 1), dilation=(8, 8))\n"," (ln2): LayerNorm((64,), eps=1e-08, elementwise_affine=True)\n"," )\n"," )\n"," (final_layer): Linear(in_features=64, out_features=64, bias=True)\n"," (loss_fct): CrossEntropyLoss()\n"," (reg_loss): RegLoss()\n",")\n","Trainable parameters: 464576\n","NextItNet(\n"," (item_embedding): Embedding(3294, 64, padding_idx=0)\n"," (residual_blocks): Sequential(\n"," (0): ResidualBlock_b(\n"," (conv1): Conv2d(64, 64, kernel_size=(1, 3), stride=(1, 1))\n"," (ln1): LayerNorm((64,), eps=1e-08, elementwise_affine=True)\n"," (conv2): Conv2d(64, 64, kernel_size=(1, 3), stride=(1, 1), dilation=(2, 2))\n"," (ln2): LayerNorm((64,), eps=1e-08, elementwise_affine=True)\n"," )\n"," (1): ResidualBlock_b(\n"," (conv1): Conv2d(64, 64, kernel_size=(1, 3), stride=(1, 1), dilation=(4, 4))\n"," (ln1): LayerNorm((64,), eps=1e-08, elementwise_affine=True)\n"," (conv2): Conv2d(64, 64, kernel_size=(1, 3), stride=(1, 1), dilation=(8, 8))\n"," (ln2): LayerNorm((64,), eps=1e-08, elementwise_affine=True)\n"," )\n"," (2): ResidualBlock_b(\n"," (conv1): Conv2d(64, 64, kernel_size=(1, 3), stride=(1, 1))\n"," (ln1): LayerNorm((64,), eps=1e-08, elementwise_affine=True)\n"," (conv2): Conv2d(64, 64, kernel_size=(1, 3), stride=(1, 1), dilation=(2, 2))\n"," (ln2): LayerNorm((64,), eps=1e-08, elementwise_affine=True)\n"," )\n"," (3): ResidualBlock_b(\n"," (conv1): Conv2d(64, 64, kernel_size=(1, 3), stride=(1, 1), dilation=(4, 4))\n"," (ln1): LayerNorm((64,), eps=1e-08, elementwise_affine=True)\n"," (conv2): Conv2d(64, 64, kernel_size=(1, 3), stride=(1, 1), dilation=(8, 8))\n"," (ln2): LayerNorm((64,), eps=1e-08, elementwise_affine=True)\n"," )\n"," (4): ResidualBlock_b(\n"," (conv1): Conv2d(64, 64, kernel_size=(1, 3), stride=(1, 1))\n"," (ln1): LayerNorm((64,), eps=1e-08, elementwise_affine=True)\n"," (conv2): Conv2d(64, 64, kernel_size=(1, 3), stride=(1, 1), dilation=(2, 2))\n"," (ln2): LayerNorm((64,), eps=1e-08, elementwise_affine=True)\n"," )\n"," (5): ResidualBlock_b(\n"," (conv1): Conv2d(64, 64, kernel_size=(1, 3), stride=(1, 1), dilation=(4, 4))\n"," (ln1): LayerNorm((64,), eps=1e-08, elementwise_affine=True)\n"," (conv2): Conv2d(64, 64, kernel_size=(1, 3), stride=(1, 1), dilation=(8, 8))\n"," (ln2): LayerNorm((64,), eps=1e-08, elementwise_affine=True)\n"," )\n"," (6): ResidualBlock_b(\n"," (conv1): Conv2d(64, 64, kernel_size=(1, 3), stride=(1, 1))\n"," (ln1): LayerNorm((64,), eps=1e-08, elementwise_affine=True)\n"," (conv2): Conv2d(64, 64, kernel_size=(1, 3), stride=(1, 1), dilation=(2, 2))\n"," (ln2): LayerNorm((64,), eps=1e-08, elementwise_affine=True)\n"," )\n"," (7): ResidualBlock_b(\n"," (conv1): Conv2d(64, 64, kernel_size=(1, 3), stride=(1, 1), dilation=(4, 4))\n"," (ln1): LayerNorm((64,), eps=1e-08, elementwise_affine=True)\n"," (conv2): Conv2d(64, 64, kernel_size=(1, 3), stride=(1, 1), dilation=(8, 8))\n"," (ln2): LayerNorm((64,), eps=1e-08, elementwise_affine=True)\n"," )\n"," (8): ResidualBlock_b(\n"," (conv1): Conv2d(64, 64, kernel_size=(1, 3), stride=(1, 1))\n"," (ln1): LayerNorm((64,), eps=1e-08, elementwise_affine=True)\n"," (conv2): Conv2d(64, 64, kernel_size=(1, 3), stride=(1, 1), dilation=(2, 2))\n"," (ln2): LayerNorm((64,), eps=1e-08, elementwise_affine=True)\n"," )\n"," (9): ResidualBlock_b(\n"," (conv1): Conv2d(64, 64, kernel_size=(1, 3), stride=(1, 1), dilation=(4, 4))\n"," (ln1): LayerNorm((64,), eps=1e-08, elementwise_affine=True)\n"," (conv2): Conv2d(64, 64, kernel_size=(1, 3), stride=(1, 1), dilation=(8, 8))\n"," (ln2): LayerNorm((64,), eps=1e-08, elementwise_affine=True)\n"," )\n"," )\n"," (final_layer): Linear(in_features=64, out_features=64, bias=True)\n"," (loss_fct): CrossEntropyLoss()\n"," (reg_loss): RegLoss()\n",")\n","Trainable parameters: 464576\n","12 Dec 08:43 INFO FLOPs: 12423360.0\n","FLOPs: 12423360.0\n","12 Dec 10:08 INFO epoch 0 training [time: 5095.82s, train loss: 2732.6105]\n","epoch 0 training [time: 5095.82s, train loss: 2732.6105]\n","12 Dec 10:08 INFO Saving current: saved/NextItNet-Dec-12-2023_08-43-30.pth\n","Saving current: saved/NextItNet-Dec-12-2023_08-43-30.pth\n","12 Dec 11:33 INFO epoch 1 training [time: 5097.32s, train loss: 2601.4325]\n","epoch 1 training [time: 5097.32s, train loss: 2601.4325]\n","12 Dec 11:33 INFO Saving current: saved/NextItNet-Dec-12-2023_08-43-30.pth\n","Saving current: saved/NextItNet-Dec-12-2023_08-43-30.pth\n","12 Dec 13:31 INFO epoch 2 training [time: 7077.48s, train loss: 2554.3704]\n","epoch 2 training [time: 7077.48s, train loss: 2554.3704]\n","12 Dec 13:31 INFO Saving current: saved/NextItNet-Dec-12-2023_08-43-30.pth\n","Saving current: saved/NextItNet-Dec-12-2023_08-43-30.pth\n","12 Dec 14:39 INFO epoch 3 training [time: 4117.24s, train loss: 2529.2335]\n","epoch 3 training [time: 4117.24s, train loss: 2529.2335]\n","12 Dec 14:39 INFO Saving current: saved/NextItNet-Dec-12-2023_08-43-30.pth\n","Saving current: saved/NextItNet-Dec-12-2023_08-43-30.pth\n","12 Dec 17:57 INFO epoch 4 training [time: 11838.39s, train loss: 2512.5584]\n","epoch 4 training [time: 11838.39s, train loss: 2512.5584]\n","12 Dec 17:57 INFO Saving current: saved/NextItNet-Dec-12-2023_08-43-30.pth\n","Saving current: saved/NextItNet-Dec-12-2023_08-43-30.pth\n","12 Dec 18:42 INFO epoch 5 training [time: 2724.19s, train loss: 2497.9890]\n","epoch 5 training [time: 2724.19s, train loss: 2497.9890]\n","12 Dec 18:42 INFO Saving current: saved/NextItNet-Dec-12-2023_08-43-30.pth\n","Saving current: saved/NextItNet-Dec-12-2023_08-43-30.pth\n","12 Dec 19:30 INFO epoch 6 training [time: 2856.86s, train loss: 2485.4469]\n","epoch 6 training [time: 2856.86s, train loss: 2485.4469]\n","12 Dec 19:30 INFO Saving current: saved/NextItNet-Dec-12-2023_08-43-30.pth\n","Saving current: saved/NextItNet-Dec-12-2023_08-43-30.pth\n","12 Dec 20:13 INFO epoch 7 training [time: 2609.22s, train loss: 2474.9533]\n","epoch 7 training [time: 2609.22s, train loss: 2474.9533]\n","12 Dec 20:13 INFO Saving current: saved/NextItNet-Dec-12-2023_08-43-30.pth\n","Saving current: saved/NextItNet-Dec-12-2023_08-43-30.pth\n","12 Dec 21:18 INFO epoch 8 training [time: 3904.76s, train loss: 2465.3467]\n","epoch 8 training [time: 3904.76s, train loss: 2465.3467]\n","12 Dec 21:18 INFO Saving current: saved/NextItNet-Dec-12-2023_08-43-30.pth\n","Saving current: saved/NextItNet-Dec-12-2023_08-43-30.pth\n","12 Dec 22:13 INFO epoch 9 training [time: 3272.21s, train loss: 2456.8602]\n","epoch 9 training [time: 3272.21s, train loss: 2456.8602]\n","12 Dec 22:13 INFO Saving current: saved/NextItNet-Dec-12-2023_08-43-30.pth\n","Saving current: saved/NextItNet-Dec-12-2023_08-43-30.pth\n","12 Dec 22:13 INFO Loading model structure and parameters from saved/NextItNet-Dec-12-2023_08-43-30.pth\n","Loading model structure and parameters from saved/NextItNet-Dec-12-2023_08-43-30.pth\n","12 Dec 22:16 INFO The running environment of this training is as follows:\n","+-------------+---------------+\n","| Environment | Usage |\n","+=============+===============+\n","| CPU | 11.80 % |\n","+-------------+---------------+\n","| GPU | 0.0 / 0.0 |\n","+-------------+---------------+\n","| Memory | 0.74 G/8.00 G |\n","+-------------+---------------+\n","The running environment of this training is as follows:\n","+-------------+---------------+\n","| Environment | Usage |\n","+=============+===============+\n","| CPU | 11.80 % |\n","+-------------+---------------+\n","| GPU | 0.0 / 0.0 |\n","+-------------+---------------+\n","| Memory | 0.74 G/8.00 G |\n","+-------------+---------------+\n","12 Dec 22:16 INFO best valid : None\n","best valid : None\n","12 Dec 22:16 INFO test result: OrderedDict([('recall@10', 0.0922), ('mrr@10', 0.0329), ('ndcg@10', 0.0466), ('hit@10', 0.0922), ('precision@10', 0.0092)])\n","test result: OrderedDict([('recall@10', 0.0922), ('mrr@10', 0.0329), ('ndcg@10', 0.0466), ('hit@10', 0.0922), ('precision@10', 0.0092)])\n"]},{"name":"stdout","output_type":"stream","text":["It took 814.55 mins\n","{'best_valid_score': -inf, 'valid_score_bigger': True, 'best_valid_result': None, 'test_result': OrderedDict([('recall@10', 0.0922), ('mrr@10', 0.0329), ('ndcg@10', 0.0466), ('hit@10', 0.0922), ('precision@10', 0.0092)])}\n","CPU times: user 1d 24min 58s, sys: 13h 28min 5s, total: 1d 13h 53min 4s\n","Wall time: 1d 8h 36min 2s\n"]}],"source":["%%time\n","model_list = [\"CORE\", \"LightSANs\", \"NextItNet\",] \n","\n","parameter_dict[\"train_neg_sample_args\"] = None\n","\n","for model_name in model_list:\n"," print(f\"running {model_name}...\")\n"," start = time.time()\n"," result = run_recbole(model=model_name, dataset = 'recbox_data', config_dict = parameter_dict)\n"," t = time.time() - start\n"," print(f\"It took {t/60:.2f} mins\")\n"," print(result)"]}],"metadata":{"kernelspec":{"display_name":"Python 3","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.9.13"},"papermill":{"default_parameters":{},"duration":27491.154881,"end_time":"2022-11-28T00:11:27.624787","environment_variables":{},"exception":null,"input_path":"__notebook__.ipynb","output_path":"__notebook__.ipynb","parameters":{},"start_time":"2022-11-27T16:33:16.469906","version":"2.3.4"}},"nbformat":4,"nbformat_minor":5} From 9238b51fb75bf2ac1571b24f98fc55e23acb863b Mon Sep 17 00:00:00 2001 From: Anna Pikuleva Date: Fri, 15 Dec 2023 09:08:54 +0300 Subject: [PATCH 7/7] all changes --- service/api/views.py | 58 +++++++++++++++++++++++++++++++------------- 1 file changed, 41 insertions(+), 17 deletions(-) diff --git a/service/api/views.py b/service/api/views.py index 24cf4a7f..95178c1e 100644 --- a/service/api/views.py +++ b/service/api/views.py @@ -1,20 +1,38 @@ from typing import List - -from fastapi import APIRouter, FastAPI, Request +from fastapi import APIRouter, Depends, FastAPI, Request from pydantic import BaseModel - -from service.api.exceptions import UserNotFoundError +import dill +from service.api.exceptions import ModelNotFoundError, UnauthorizedUserError, UserNotFoundError from service.log import app_logger +import pandas as pd +from service.models import recommend_popular + + +# load predictions of dssm model +dssm_preds = pd.read_csv("dssm_predictions.csv") +dssm_preds.item_id = dssm_preds.item_id.apply(lambda x: [int(i) for i in x[1:-1].split(", ")]) + + +# get popular recommendations +interactions = pd.read_csv('data/interactions.csv') +interactions['last_watch_dt'] = pd.to_datetime(interactions['last_watch_dt']) +interactions.rename( + columns={ + 'last_watch_dt': 'datetime', + 'total_dur': 'weight', + }, + inplace=True, + ) +popular_recs = recommend_popular(interactions) +popular_recs_30 = recommend_popular(interactions, days = 30) class RecoResponse(BaseModel): user_id: int items: List[int] - router = APIRouter() - @router.get( path="/health", tags=["Health"], @@ -23,26 +41,32 @@ async def health() -> str: return "I am alive" + @router.get( path="/reco/{model_name}/{user_id}", tags=["Recommendations"], - response_model=RecoResponse, + response_model=RecoResponse ) async def get_reco( - request: Request, - model_name: str, - user_id: int, -) -> RecoResponse: - app_logger.info(f"Request for model: {model_name}, user_id: {user_id}") - - # Write your code here + request: Request, + model_name: str, + user_id: int, + # token=Depends(bearer) + ) -> RecoResponse: + # app_logger.info(f"Request for model: {model_name}, user_id: {user_id}") + app_logger.info(f"Request for model: {model_name}") + app_logger.info(f"Request for user: {user_id}") if user_id > 10**9: raise UserNotFoundError(error_message=f"User {user_id} not found") + + if model_name == "DSSM": + try: + recs_list = dssm_preds[dssm_preds.user_id == user_id].item_id.values[0] + except: + recs_list = popular_recs_30 - k_recs = request.app.state.k_recs - reco = list(range(k_recs)) - return RecoResponse(user_id=user_id, items=reco) + return RecoResponse(user_id=user_id, items=recs_list) def add_views(app: FastAPI) -> None: