├── .ipynb_checkpoints ├── Untitled-checkpoint.ipynb └── recommender-checkpoint.ipynb ├── Error Chart.png ├── LICENSE ├── Presentation.pdf ├── Presentation_Images ├── 1.png ├── 2.png └── 3.png ├── README.md ├── __pycache__ └── mf.cpython-36.pyc ├── feasible_data_10000.txt ├── feasible_data_100000.txt ├── feasible_data_1024.txt ├── feasible_data_150000.txt ├── feasible_data_175000.txt ├── feasible_data_200000.txt ├── feasible_data_25000.txt ├── feasible_data_5000.txt ├── feasible_data_50000.txt ├── feasible_data_75000.txt ├── mf.py ├── movie_titles.csv ├── presentation.tex ├── recommender.ipynb ├── recommender_final.py └── recommender_final_toy_dataset.py /.ipynb_checkpoints/Untitled-checkpoint.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [], 3 | "metadata": {}, 4 | "nbformat": 4, 5 | "nbformat_minor": 2 6 | } 7 | -------------------------------------------------------------------------------- /.ipynb_checkpoints/recommender-checkpoint.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 26, 6 | "metadata": {}, 7 | "outputs": [ 8 | { 9 | "name": "stdout", 10 | "output_type": "stream", 11 | "text": [ 12 | "Setup Complete\n", 13 | "\n" 14 | ] 15 | } 16 | ], 17 | "source": [ 18 | "#Setting up prerequisites\n", 19 | "import pandas as pd\n", 20 | "import numpy as np\n", 21 | "import math\n", 22 | "import re\n", 23 | "import sklearn\n", 24 | "from scipy.sparse import csr_matrix\n", 25 | "import matplotlib.pyplot as plt\n", 26 | "import seaborn as sns\n", 27 | "from surprise import Reader, Dataset, SVD, evaluate\n", 28 | "sns.set_style(\"darkgrid\")\n", 29 | "\n", 30 | "from cvxpy import *\n", 31 | "from numpy import matrix\n", 32 | "\n", 33 | "print(\"Setup Complete\\n\")" 34 | ] 35 | }, 36 | { 37 | "cell_type": "code", 38 | "execution_count": 2, 39 | "metadata": {}, 40 | "outputs": [ 41 | { 42 | "name": "stdout", 43 | "output_type": "stream", 44 | "text": [ 45 | "Dataset 1 shape: (1024, 3)\n", 46 | "-Dataset examples-\n", 47 | " Cust_Id Rating Date\n", 48 | "0 1: NaN NaN\n", 49 | "100 2630337 5.0 20050310.0\n", 50 | "200 573434 4.0 20040526.0\n", 51 | "300 638824 5.0 20040519.0\n", 52 | "400 1653834 4.0 20040822.0\n", 53 | "500 1033930 3.0 20050811.0\n", 54 | "600 349407 5.0 20050102.0\n", 55 | "700 656399 4.0 20030920.0\n", 56 | "800 1456369 4.0 20030708.0\n", 57 | "900 253037 3.0 20050805.0\n", 58 | "1000 1369550 3.0 20041011.0\n", 59 | "float64\n" 60 | ] 61 | } 62 | ], 63 | "source": [ 64 | "df1 = pd.read_csv('netflix-prize-data/toy_combined_data.txt', header = None, names = ['Cust_Id', 'Rating', 'Date'], usecols = [0,1,2])\n", 65 | "df1['Rating'] = df1['Rating'].astype(float)\n", 66 | "df1['Date'] = df1['Date'].astype(str)\n", 67 | "df1['Date'] = df1['Date'].map( lambda s : (s[:4])+(s[5:7])+(s[8:]))\n", 68 | "df1['Date'] = df1['Date'].astype(float)\n", 69 | "print('Dataset 1 shape: {}'.format(df1.shape))\n", 70 | "print('-Dataset examples-')\n", 71 | "print(df1.iloc[::100, :])\n", 72 | "print(df1['Date'].dtype)\n", 73 | "df = df1" 74 | ] 75 | }, 76 | { 77 | "cell_type": "code", 78 | "execution_count": 3, 79 | "metadata": {}, 80 | "outputs": [ 81 | { 82 | "name": "stdout", 83 | "output_type": "stream", 84 | "text": [ 85 | "See Overview of the Data\n" 86 | ] 87 | }, 88 | { 89 | "data": { 90 | "image/png": "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\n", 91 | "text/plain": [ 92 | "
" 93 | ] 94 | }, 95 | "metadata": {}, 96 | "output_type": "display_data" 97 | } 98 | ], 99 | "source": [ 100 | "#Seeing the distribution of ratings given by the users\n", 101 | "print(\"See Overview of the Data\")\n", 102 | "p = df.groupby('Rating')['Rating'].agg(['count'])\n", 103 | "# get movie count\n", 104 | "movie_count = df.isnull().sum()[1]\n", 105 | "# get customer count\n", 106 | "cust_count = df['Cust_Id'].nunique() - movie_count\n", 107 | "# get rating count\n", 108 | "rating_count = df['Cust_Id'].count() - movie_count\n", 109 | "ax = p.plot(kind = 'barh', legend = False, figsize = (15,10))\n", 110 | "plt.title('Total pool: {:,} Movies, {:,} customers, {:,} ratings given'.format(movie_count, cust_count, rating_count), fontsize=20)\n", 111 | "plt.axis('off')\n", 112 | "for i in range(1,6):\n", 113 | " ax.text(p.iloc[i-1][0]/4, i-1, 'Rated {}: {:.0f}%'.format(i, p.iloc[i-1][0]*100 / p.sum()[0]), color = 'white', weight = 'bold')" 114 | ] 115 | }, 116 | { 117 | "cell_type": "code", 118 | "execution_count": 4, 119 | "metadata": {}, 120 | "outputs": [ 121 | { 122 | "name": "stdout", 123 | "output_type": "stream", 124 | "text": [ 125 | "Movie IDs extracted from the extra rows given\n" 126 | ] 127 | } 128 | ], 129 | "source": [ 130 | "#Adding movie IDs to the dataset\n", 131 | "movie_np = []\n", 132 | "movie_id = 0\n", 133 | "for x in range(df.shape[0]):\n", 134 | " if(np.isnan(df.iloc[x]['Rating'])):\n", 135 | " movie_id = movie_id+1\n", 136 | " movie_np = np.append(movie_np,movie_id)\n", 137 | "\n", 138 | "#print(movie_np)\n", 139 | "#print(len(movie_np))\n", 140 | "df['Movie_Id'] = movie_np.astype(int)\n", 141 | "print(\"Movie IDs extracted from the extra rows given\")" 142 | ] 143 | }, 144 | { 145 | "cell_type": "code", 146 | "execution_count": 5, 147 | "metadata": {}, 148 | "outputs": [ 149 | { 150 | "name": "stdout", 151 | "output_type": "stream", 152 | "text": [ 153 | "-Dataset examples-\n", 154 | " Cust_Id Rating Date Movie_Id\n", 155 | "1 1488844 3.0 20050906.0 1\n", 156 | "101 1155747 3.0 20050703.0 1\n", 157 | "201 1141189 4.0 20041215.0 1\n", 158 | "301 2256485 1.0 20040819.0 1\n", 159 | "401 2322840 3.0 20050712.0 1\n", 160 | "501 45117 5.0 20050815.0 1\n", 161 | "602 2596999 4.0 20051007.0 2\n", 162 | "703 1644750 3.0 20030319.0 3\n", 163 | "803 372528 3.0 20040630.0 3\n", 164 | "903 1115632 3.0 20031124.0 3\n", 165 | "1003 2085230 4.0 20040315.0 3\n", 166 | "\n", 167 | "\n", 168 | "These are the final datatypes of the dataset\n", 169 | "Cust_Id int64\n", 170 | "Rating float64\n", 171 | "Date float64\n", 172 | "Movie_Id int64\n", 173 | "dtype: object\n" 174 | ] 175 | }, 176 | { 177 | "name": "stderr", 178 | "output_type": "stream", 179 | "text": [ 180 | "/usr/lib/python3/dist-packages/ipykernel_launcher.py:3: SettingWithCopyWarning: \n", 181 | "A value is trying to be set on a copy of a slice from a DataFrame.\n", 182 | "Try using .loc[row_indexer,col_indexer] = value instead\n", 183 | "\n", 184 | "See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy\n", 185 | " This is separate from the ipykernel package so we can avoid doing imports until\n" 186 | ] 187 | } 188 | ], 189 | "source": [ 190 | "# remove the extra Movie ID rows\n", 191 | "df = df[pd.notnull(df['Rating'])]\n", 192 | "df['Cust_Id'] = df['Cust_Id'].astype(int)\n", 193 | "print('-Dataset examples-')\n", 194 | "print(df.iloc[::100, :])\n", 195 | "\n", 196 | "\n", 197 | "print(\"\\n\\nThese are the final datatypes of the dataset\")\n", 198 | "print(df.dtypes)" 199 | ] 200 | }, 201 | { 202 | "cell_type": "code", 203 | "execution_count": 6, 204 | "metadata": {}, 205 | "outputs": [ 206 | { 207 | "name": "stdout", 208 | "output_type": "stream", 209 | "text": [ 210 | "(1009, 3)\n" 211 | ] 212 | } 213 | ], 214 | "source": [ 215 | "#Creating Data Matrix\n", 216 | "df_matrix=pd.pivot_table(df,values='Rating',index='Cust_Id',columns='Movie_Id')\n", 217 | "print(df_matrix.shape)" 218 | ] 219 | }, 220 | { 221 | "cell_type": "code", 222 | "execution_count": 7, 223 | "metadata": {}, 224 | "outputs": [ 225 | { 226 | "name": "stdout", 227 | "output_type": "stream", 228 | "text": [ 229 | "See some Movie ID- Movie Title Mapping : \n", 230 | "\n", 231 | " Year Name\n", 232 | "Movie_Id \n", 233 | "1 2003.0 Dinosaur Planet\n", 234 | "2 2004.0 Isle of Man TT 2004 Review\n", 235 | "3 1997.0 Character\n", 236 | "4 1994.0 Paula Abdul's Get Up & Dance\n", 237 | "5 2004.0 The Rise and Fall of ECW\n", 238 | "6 1997.0 Sick\n", 239 | "7 1992.0 8 Man\n", 240 | "8 2004.0 What the #$*! Do We Know!?\n" 241 | ] 242 | } 243 | ], 244 | "source": [ 245 | "#Loading the Movie ID- Movie Title Mapping File\n", 246 | "\n", 247 | "df_title = pd.read_csv('netflix-prize-data/movie_titles.csv', encoding = \"ISO-8859-1\", header = None, names = ['Movie_Id', 'Year', 'Name'])\n", 248 | "df_title.set_index('Movie_Id', inplace = True)\n", 249 | "print(\"See some Movie ID- Movie Title Mapping : \\n\")\n", 250 | "print (df_title.head(8))" 251 | ] 252 | }, 253 | { 254 | "cell_type": "code", 255 | "execution_count": 8, 256 | "metadata": {}, 257 | "outputs": [ 258 | { 259 | "name": "stdout", 260 | "output_type": "stream", 261 | "text": [ 262 | "\n", 263 | "\n", 264 | "Data Cleaning Complete.\n", 265 | " See head of the Data Matrix:\n", 266 | "\n", 267 | "Movie_Id 1 2 3\n", 268 | "Cust_Id \n", 269 | "915 5.0 NaN NaN\n", 270 | "1333 NaN NaN 4.0\n", 271 | "2442 3.0 NaN NaN\n", 272 | "3321 3.0 NaN NaN\n", 273 | "4326 4.0 NaN NaN\n", 274 | "\n", 275 | "Num of movies = 3\n", 276 | "Num of users = 1009\n" 277 | ] 278 | } 279 | ], 280 | "source": [ 281 | "print(\"\\n\\nData Cleaning Complete.\\n See head of the Data Matrix:\\n\")\n", 282 | "print(df_matrix.head())\n", 283 | "\n", 284 | "n_movies = movie_count\n", 285 | "n_customers = cust_count\n", 286 | "\n", 287 | "print(\"\\nNum of movies =\", movie_count)\n", 288 | "print(\"Num of users =\", cust_count)" 289 | ] 290 | }, 291 | { 292 | "cell_type": "code", 293 | "execution_count": 9, 294 | "metadata": {}, 295 | "outputs": [ 296 | { 297 | "name": "stdout", 298 | "output_type": "stream", 299 | "text": [ 300 | "Movie_Id 1 2 3\n", 301 | "Cust_Id \n", 302 | "915 5.0 0.0 0.0\n", 303 | "1333 0.0 0.0 4.0\n", 304 | "2442 3.0 0.0 0.0\n", 305 | "3321 3.0 0.0 0.0\n", 306 | "4326 4.0 0.0 0.0\n" 307 | ] 308 | } 309 | ], 310 | "source": [ 311 | "#Choosing the number of latent attributes\n", 312 | "n_attr= 100*1000000\n", 313 | "#print(type(n_attr),type(n_movies), type(n_customers))\n", 314 | "Q = Variable((n_attr,n_movies))\n", 315 | "P = Variable((n_attr, n_customers))\n", 316 | "\n", 317 | "\n", 318 | "\n", 319 | "acq_data = df_matrix.fillna(0.0)\n", 320 | "print(acq_data.head())\n", 321 | "\n" 322 | ] 323 | }, 324 | { 325 | "cell_type": "code", 326 | "execution_count": 49, 327 | "metadata": {}, 328 | "outputs": [], 329 | "source": [ 330 | "class MF():\n", 331 | "\n", 332 | " def __init__(self, R, K, alpha, beta, iterations):\n", 333 | " \"\"\"\n", 334 | " Perform matrix factorization to predict empty\n", 335 | " entries in a matrix.\n", 336 | "\n", 337 | " Arguments\n", 338 | " - R (ndarray) : user-item rating matrix\n", 339 | " - K (int) : number of latent dimensions\n", 340 | " - alpha (float) : learning rate\n", 341 | " - beta (float) : regularization parameter\n", 342 | " \"\"\"\n", 343 | "\n", 344 | " self.R = R\n", 345 | " self.num_users, self.num_items = R.shape\n", 346 | " self.K = K\n", 347 | " self.alpha = alpha\n", 348 | " self.beta = beta\n", 349 | " self.iterations = iterations\n", 350 | "\n", 351 | " def train(self):\n", 352 | " # Initialize user and item latent feature matrice\n", 353 | " self.P = np.random.normal(scale=1./self.K, size=(self.num_users, self.K))\n", 354 | " self.Q = np.random.normal(scale=1./self.K, size=(self.num_items, self.K))\n", 355 | "\n", 356 | " # Initialize the biases\n", 357 | " self.b_u = np.zeros(self.num_users)\n", 358 | " self.b_i = np.zeros(self.num_items)\n", 359 | " self.b = np.mean(self.R[np.where(self.R != 0)])\n", 360 | "\n", 361 | " # Create a list of training samples\n", 362 | " self.samples = [\n", 363 | " (i, j, self.R[i, j])\n", 364 | " for i in range(self.num_users)\n", 365 | " for j in range(self.num_items)\n", 366 | " if self.R[i, j] > 0\n", 367 | " ]\n", 368 | "\n", 369 | " # Perform stochastic gradient descent for number of iterations\n", 370 | " training_process = []\n", 371 | " for i in range(self.iterations):\n", 372 | " np.random.shuffle(self.samples)\n", 373 | " self.sgd()\n", 374 | " mse = self.mse()\n", 375 | " training_process.append((i, mse))\n", 376 | " if (i+1) % 100 == 0:\n", 377 | " print(\"Iteration: %d ; error = %.4f\" % (i+1, mse))\n", 378 | "\n", 379 | " return training_process\n", 380 | "\n", 381 | " def mse(self):\n", 382 | " \"\"\"\n", 383 | " A function to compute the total mean square error\n", 384 | " \"\"\"\n", 385 | " xs, ys = self.R.nonzero()\n", 386 | " predicted = self.full_matrix()\n", 387 | " error = 0\n", 388 | " for x, y in zip(xs, ys):\n", 389 | " error += pow(self.R[x, y] - predicted[x, y], 2)\n", 390 | " return np.sqrt(error)\n", 391 | "\n", 392 | " def sgd(self):\n", 393 | " \"\"\"\n", 394 | " Perform stochastic graident descent\n", 395 | " \"\"\"\n", 396 | " for i, j, r in self.samples:\n", 397 | " # Computer prediction and error\n", 398 | " prediction = self.get_rating(i, j)\n", 399 | " e = (r - prediction)\n", 400 | "\n", 401 | " # Update biases\n", 402 | " self.b_u[i] += self.alpha * (e - self.beta * self.b_u[i])\n", 403 | " self.b_i[j] += self.alpha * (e - self.beta * self.b_i[j])\n", 404 | "\n", 405 | " # Update user and item latent feature matrices\n", 406 | " self.P[i, :] += self.alpha * (e * self.Q[j, :] - self.beta * self.P[i,:])\n", 407 | " self.Q[j, :] += self.alpha * (e * self.P[i, :] - self.beta * self.Q[j,:])\n", 408 | "\n", 409 | " def get_rating(self, i, j):\n", 410 | " \"\"\"\n", 411 | " Get the predicted rating of user i and item j\n", 412 | " \"\"\"\n", 413 | " prediction = self.b + self.b_u[i] + self.b_i[j] + self.P[i, :].dot(self.Q[j, :].T)\n", 414 | " return prediction\n", 415 | "\n", 416 | " def full_matrix(self):\n", 417 | " \"\"\"\n", 418 | " Computer the full matrix using the resultant biases, P and Q\n", 419 | " \"\"\"\n", 420 | " return self.b + self.b_u[:,np.newaxis] + self.b_i[np.newaxis:,] + self.P.dot(self.Q.T)" 421 | ] 422 | }, 423 | { 424 | "cell_type": "code", 425 | "execution_count": 50, 426 | "metadata": {}, 427 | "outputs": [ 428 | { 429 | "name": "stdout", 430 | "output_type": "stream", 431 | "text": [ 432 | "\n", 433 | "P x Q:\n", 434 | "[[4.46271506 3.82325605 3.80535557]\n", 435 | " [3.85422058 3.64783311 3.68900504]\n", 436 | " [3.36439893 3.43993058 3.44318097]\n", 437 | " ...\n", 438 | " [3.58337245 3.46726285 3.37777364]\n", 439 | " [3.91898975 3.63739457 3.58991505]\n", 440 | " [3.63308644 3.42857823 3.47186301]]\n", 441 | "\n", 442 | "Global bias:\n", 443 | "3.6787463271302645\n", 444 | "\n", 445 | "User bias:\n", 446 | "[ 0.20857453 0.06703809 -0.12422918 ... -0.10967525 0.04237086\n", 447 | " -0.10272106]\n", 448 | "\n", 449 | "Item bias:\n", 450 | "[ 0.0937364 -0.10371626 -0.10917139]\n" 451 | ] 452 | } 453 | ], 454 | "source": [ 455 | "#R = np.array([\n", 456 | "# [5, 3, 0, 1],\n", 457 | "# [4, 0, 0, 1],\n", 458 | "# [1, 1, 0, 5],\n", 459 | "# [1, 0, 0, 4],\n", 460 | "# [0, 1, 5, 4],\n", 461 | "#])\n", 462 | "\n", 463 | "R = np.array(acq_data)\n", 464 | "\n", 465 | "mf = MF(R, K=100, alpha=0.01, beta=0.01, iterations=20)\n", 466 | "training_process = mf.train()\n", 467 | "print()\n", 468 | "print(\"P x Q:\")\n", 469 | "print(mf.full_matrix())\n", 470 | "print()\n", 471 | "print(\"Global bias:\")\n", 472 | "print(mf.b)\n", 473 | "print()\n", 474 | "print(\"User bias:\")\n", 475 | "print(mf.b_u)\n", 476 | "print()\n", 477 | "print(\"Item bias:\")\n", 478 | "print(mf.b_i)" 479 | ] 480 | }, 481 | { 482 | "cell_type": "code", 483 | "execution_count": 51, 484 | "metadata": {}, 485 | "outputs": [ 486 | { 487 | "name": "stdout", 488 | "output_type": "stream", 489 | "text": [ 490 | "This is the Result for Training Set:\n", 491 | "\n" 492 | ] 493 | }, 494 | { 495 | "data": { 496 | "image/png": "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kxMGBwexbNkyhIaGYvPmzcjJyUFBQcGI5youLkZLSwsKCgrQ29uLZcuWYd68eebHLioqgk6nQ3x8PBobG+Ht7Y2SkhKcOHECGo0Gv/7663+0VIiIiMYeB7NERET/olOnTqG1tRUnT54EAPT19eHChQsYN24c5syZYx7IAsBnn32GkpISAEB3dzcuXLgAZ2dni4/d2NiI8PBw2Nraws3NDf7+/vjuu+/g4OAAHx8f6PV6AMDMmTPR1dUFPz8/jB8/HikpKQgKCsKiRYv+vRdORET0L+NgloiI6F8khMArr7yCRx999La/19XV4Z577rntek1NDQ4dOgR7e3usWLECN27cuOPntbOzM1+2tbWFyWSCVqvF4cOHUVtbixMnTmD//v349NNP7/g5iIiIZOIJoIiIiMbQhAkT0N/fb74eEBCA3NxcDA8PAwDa29thMBhG3K+vrw+TJk2Cvb092tra8M0335hv02q15vv/3rx583D8+HGYTCZcu3YNDQ0N8PHxsdjW39+Pvr4+BAYGIiUlBa2trf/kpRIREUnFI7NERERjaMaMGbCxsUFkZCRiYmKwcuVKdHV1ISYmBkIIODs74/333x9xv4ULF+LgwYNYsmQJpk6dCj8/P/NtTz31FCIjI/Hggw9i9+7d5r+HhITg7NmziIqKgkajwZYtW+Du7o7z588rtvX392PDhg3mI77btm0b41dPRET039EIIYTsCCIiIiIiIqK/g9OMiYiIiIiIyOpwMEtERERERERWh4NZIiIiIiIisjoczBIREREREZHV4WCWiIiIiIiIrA4Hs0RERERERGR1OJglIiIiIiIiq8PBLBEREREREVmd/wGqurv4eUSv3wAAAABJRU5ErkJggg==\n", 497 | "text/plain": [ 498 | "
" 499 | ] 500 | }, 501 | "metadata": {}, 502 | "output_type": "display_data" 503 | } 504 | ], 505 | "source": [ 506 | "x = [x for x, y in training_process]\n", 507 | "y = [y for x, y in training_process]\n", 508 | "plt.figure(figsize=((16,4)))\n", 509 | "plt.plot(x, np.sqrt(y))\n", 510 | "plt.xticks(x, x)\n", 511 | "\n", 512 | "print(\"This is the Result for Training Set:\\n\")\n", 513 | "plt.xlabel(\"Iterations\")\n", 514 | "plt.ylabel(\"Root Mean Square Error\")\n", 515 | "plt.grid(axis=\"y\")" 516 | ] 517 | }, 518 | { 519 | "cell_type": "code", 520 | "execution_count": 58, 521 | "metadata": {}, 522 | "outputs": [ 523 | { 524 | "name": "stdout", 525 | "output_type": "stream", 526 | "text": [ 527 | "Original:\n", 528 | " [[5 3 0 1]\n", 529 | " [4 0 0 1]\n", 530 | " [1 1 0 5]\n", 531 | " [1 0 0 4]\n", 532 | " [0 1 5 4]]\n", 533 | "Test Set:\n", 534 | " [[5 3 0 1]\n", 535 | " [4 0 0 1]\n", 536 | " [0 1 0 5]\n", 537 | " [1 0 0 4]\n", 538 | " [0 1 5 4]]\n", 539 | "MSE= 0.22360679774997896\n", 540 | "Iteration: 100 ; error = 4.3799\n", 541 | "Iteration: 200 ; error = 0.3920\n", 542 | "Iteration: 300 ; error = 0.1484\n", 543 | "Iteration: 400 ; error = 0.0773\n", 544 | "Iteration: 500 ; error = 0.0555\n", 545 | "Iteration: 600 ; error = 0.0488\n", 546 | "Iteration: 700 ; error = 0.0465\n", 547 | "Iteration: 800 ; error = 0.0457\n", 548 | "Iteration: 900 ; error = 0.0452\n", 549 | "Iteration: 1000 ; error = 0.0449\n", 550 | "Iteration: 1100 ; error = 0.0447\n", 551 | "Iteration: 1200 ; error = 0.0445\n", 552 | "Iteration: 1300 ; error = 0.0444\n", 553 | "Iteration: 1400 ; error = 0.0442\n", 554 | "Iteration: 1500 ; error = 0.0440\n", 555 | "Iteration: 1600 ; error = 0.0439\n", 556 | "Iteration: 1700 ; error = 0.0438\n", 557 | "Iteration: 1800 ; error = 0.0437\n", 558 | "Iteration: 1900 ; error = 0.0436\n", 559 | "Iteration: 2000 ; error = 0.0434\n", 560 | "Iteration: 2100 ; error = 0.0433\n", 561 | "Iteration: 2200 ; error = 0.0433\n", 562 | "Iteration: 2300 ; error = 0.0431\n", 563 | "Iteration: 2400 ; error = 0.0430\n", 564 | "Iteration: 2500 ; error = 0.0430\n", 565 | "Iteration: 2600 ; error = 0.0429\n", 566 | "Iteration: 2700 ; error = 0.0429\n", 567 | "Iteration: 2800 ; error = 0.0427\n", 568 | "Iteration: 2900 ; error = 0.0426\n", 569 | "Iteration: 3000 ; error = 0.0426\n", 570 | "Iteration: 3100 ; error = 0.0425\n", 571 | "Iteration: 3200 ; error = 0.0425\n", 572 | "Iteration: 3300 ; error = 0.0423\n", 573 | "Iteration: 3400 ; error = 0.0423\n", 574 | "Iteration: 3500 ; error = 0.0423\n", 575 | "Iteration: 3600 ; error = 0.0422\n", 576 | "Iteration: 3700 ; error = 0.0421\n", 577 | "Iteration: 3800 ; error = 0.0420\n", 578 | "Iteration: 3900 ; error = 0.0420\n", 579 | "Iteration: 4000 ; error = 0.0419\n", 580 | "Iteration: 4100 ; error = 0.0420\n", 581 | "Iteration: 4200 ; error = 0.0418\n", 582 | "Iteration: 4300 ; error = 0.0418\n", 583 | "Iteration: 4400 ; error = 0.0418\n", 584 | "Iteration: 4500 ; error = 0.0418\n", 585 | "Iteration: 4600 ; error = 0.0417\n", 586 | "Iteration: 4700 ; error = 0.0417\n", 587 | "Iteration: 4800 ; error = 0.0416\n", 588 | "Iteration: 4900 ; error = 0.0416\n", 589 | "Iteration: 5000 ; error = 0.0416\n", 590 | "Iteration: 5100 ; error = 0.0416\n", 591 | "Iteration: 5200 ; error = 0.0415\n", 592 | "Iteration: 5300 ; error = 0.0415\n", 593 | "Iteration: 5400 ; error = 0.0414\n", 594 | "Iteration: 5500 ; error = 0.0414\n", 595 | "Iteration: 5600 ; error = 0.0413\n", 596 | "Iteration: 5700 ; error = 0.0413\n", 597 | "Iteration: 5800 ; error = 0.0413\n", 598 | "Iteration: 5900 ; error = 0.0413\n", 599 | "Iteration: 6000 ; error = 0.0414\n", 600 | "Iteration: 6100 ; error = 0.0413\n", 601 | "Iteration: 6200 ; error = 0.0412\n", 602 | "Iteration: 6300 ; error = 0.0412\n", 603 | "Iteration: 6400 ; error = 0.0412\n", 604 | "Iteration: 6500 ; error = 0.0411\n", 605 | "Iteration: 6600 ; error = 0.0412\n", 606 | "Iteration: 6700 ; error = 0.0412\n", 607 | "Iteration: 6800 ; error = 0.0411\n", 608 | "Iteration: 6900 ; error = 0.0411\n", 609 | "Iteration: 7000 ; error = 0.0411\n", 610 | "Iteration: 7100 ; error = 0.0410\n", 611 | "Iteration: 7200 ; error = 0.0410\n", 612 | "Iteration: 7300 ; error = 0.0411\n", 613 | "Iteration: 7400 ; error = 0.0411\n", 614 | "Iteration: 7500 ; error = 0.0410\n", 615 | "Iteration: 7600 ; error = 0.0410\n", 616 | "Iteration: 7700 ; error = 0.0410\n", 617 | "Iteration: 7800 ; error = 0.0410\n", 618 | "Iteration: 7900 ; error = 0.0409\n", 619 | "Iteration: 8000 ; error = 0.0409\n", 620 | "Iteration: 8100 ; error = 0.0409\n", 621 | "Iteration: 8200 ; error = 0.0408\n", 622 | "Iteration: 8300 ; error = 0.0409\n", 623 | "Iteration: 8400 ; error = 0.0409\n", 624 | "Iteration: 8500 ; error = 0.0409\n", 625 | "Iteration: 8600 ; error = 0.0409\n", 626 | "Iteration: 8700 ; error = 0.0408\n", 627 | "Iteration: 8800 ; error = 0.0408\n", 628 | "Iteration: 8900 ; error = 0.0408\n", 629 | "Iteration: 9000 ; error = 0.0408\n", 630 | "Iteration: 9100 ; error = 0.0408\n", 631 | "Iteration: 9200 ; error = 0.0408\n", 632 | "Iteration: 9300 ; error = 0.0408\n", 633 | "Iteration: 9400 ; error = 0.0408\n", 634 | "Iteration: 9500 ; error = 0.0408\n", 635 | "Iteration: 9600 ; error = 0.0407\n", 636 | "Iteration: 9700 ; error = 0.0407\n", 637 | "Iteration: 9800 ; error = 0.0408\n", 638 | "Iteration: 9900 ; error = 0.0407\n", 639 | "Iteration: 10000 ; error = 0.0407\n", 640 | "Learnt=\n", 641 | " [[4.98310779 3.00307074 3.28549611 1.01519266]\n", 642 | " [3.99763226 2.207354 3.07663016 1.01276062]\n", 643 | " [1.57652706 1.00963135 5.71488038 4.9846755 ]\n", 644 | " [1.01285017 0.37091408 4.80884404 3.99512921]\n", 645 | " [1.82542641 1.0173647 4.9885115 3.99623184]]\n" 646 | ] 647 | } 648 | ], 649 | "source": [ 650 | "R = np.array([\n", 651 | " [5, 3, 0, 1],\n", 652 | " [4, 0, 0, 1],\n", 653 | " [1, 1, 0, 5],\n", 654 | " [1, 0, 0, 4],\n", 655 | " [0, 1, 5, 4],\n", 656 | "])\n", 657 | "\n", 658 | "R1= np.array([\n", 659 | " [5, 3, 0, 1],\n", 660 | " [4, 0, 0, 1],\n", 661 | " [1, 1, 0, 5],\n", 662 | " [1, 0, 0, 4],\n", 663 | " [0, 1, 5, 4],\n", 664 | "])\n", 665 | "\n", 666 | "#Set the number of values to replace. For example 20%:\n", 667 | "\n", 668 | "# Edit: changed len(mat) for mat.size\n", 669 | "prop = int(R.size * 0.2)\n", 670 | "\n", 671 | "#Randomly choose indices of the numpy array:\n", 672 | "i = [np.random.choice(range(R.shape[0])) for _ in range(prop)]\n", 673 | "j = [np.random.choice(range(R.shape[1])) for _ in range(prop)]\n", 674 | "\n", 675 | "#Change values with NaN\n", 676 | "R[i,j] = 0\n", 677 | "print(\"Original:\\n\",R1)\n", 678 | "print(\"Test Set:\\n\",R)\n", 679 | "R=np.rint(R)\n", 680 | "\n", 681 | "from sklearn.metrics import mean_squared_error\n", 682 | "mse = mean_squared_error(R, R1)\n", 683 | "\n", 684 | "print(\"MSE=\",mse**0.5)\n", 685 | "\n", 686 | "mf = MF(R, K=10000, alpha=0.01, beta=0.01, iterations=10000)\n", 687 | "training_process = mf.train()\n", 688 | "\n", 689 | "print(\"Learnt=\\n\",mf.full_matrix())\n" 690 | ] 691 | }, 692 | { 693 | "cell_type": "code", 694 | "execution_count": null, 695 | "metadata": {}, 696 | "outputs": [ 697 | { 698 | "name": "stdout", 699 | "output_type": "stream", 700 | "text": [ 701 | "Original:\n", 702 | " [[5. 0. 0.]\n", 703 | " [0. 0. 4.]\n", 704 | " [3. 0. 0.]\n", 705 | " ...\n", 706 | " [0. 0. 3.]\n", 707 | " [4. 0. 0.]\n", 708 | " [0. 3. 0.]]\n", 709 | "Test Set:\n", 710 | " [[5. 0. 0.]\n", 711 | " [0. 0. 0.]\n", 712 | " [0. 0. 0.]\n", 713 | " ...\n", 714 | " [0. 0. 3.]\n", 715 | " [4. 0. 0.]\n", 716 | " [0. 3. 0.]]\n", 717 | "RMSE= 0.9737214516879918\n", 718 | "Learnt=\n", 719 | " [[3.81775822 3.68859571 3.70036228]\n", 720 | " [3.69523794 3.56623209 3.57800652]\n", 721 | " [3.69523284 3.56623826 3.57801557]\n", 722 | " ...\n", 723 | " [3.63535051 3.50635783 3.5180917 ]\n", 724 | " [3.72342232 3.59438358 3.60615318]\n", 725 | " [3.64198163 3.51296124 3.52476696]]\n" 726 | ] 727 | } 728 | ], 729 | "source": [ 730 | "R = np.array(acq_data)\n", 731 | "\n", 732 | "R1= np.array(acq_data)\n", 733 | "\n", 734 | "#Set the number of values to replace. For example 20%:\n", 735 | "\n", 736 | "# Edit: changed len(mat) for mat.size\n", 737 | "prop = int(R.size * 0.2)\n", 738 | "\n", 739 | "#Randomly choose indices of the numpy array:\n", 740 | "i = [np.random.choice(range(R.shape[0])) for _ in range(prop)]\n", 741 | "j = [np.random.choice(range(R.shape[1])) for _ in range(prop)]\n", 742 | "\n", 743 | "#Change values with NaN\n", 744 | "R[i,j] = 0\n", 745 | "print(\"Original:\\n\",R1)\n", 746 | "print(\"Test Set:\\n\",R)\n", 747 | "R=np.rint(R)\n", 748 | "\n", 749 | "from sklearn.metrics import mean_squared_error\n", 750 | "mse = mean_squared_error(R, R1)\n", 751 | "print(\"RMSE=\",mse**0.5)\n", 752 | "\n", 753 | "mf = MF(R, K=10000, alpha=0.01, beta=0.01, iterations=10)\n", 754 | "training_process = mf.train()\n", 755 | "\n", 756 | "print(\"Learnt=\\n\",mf.full_matrix())\n", 757 | "msef=0.0\n", 758 | "for i1 in range(len(i)):\n", 759 | " for i2 in range(len(j)):\n", 760 | " msef = msef + (R1[i,j]-(mf.full_matrix())[i,j])**2\n", 761 | "msef = (msef/(len(j)*len(i)))\n", 762 | "print(\"RMSE f=\",msef**0.5)" 763 | ] 764 | }, 765 | { 766 | "cell_type": "code", 767 | "execution_count": null, 768 | "metadata": {}, 769 | "outputs": [], 770 | "source": [] 771 | } 772 | ], 773 | "metadata": { 774 | "kernelspec": { 775 | "display_name": "Python 3", 776 | "language": "python", 777 | "name": "python3" 778 | }, 779 | "language_info": { 780 | "codemirror_mode": { 781 | "name": "ipython", 782 | "version": 3 783 | }, 784 | "file_extension": ".py", 785 | "mimetype": "text/x-python", 786 | "name": "python", 787 | "nbconvert_exporter": "python", 788 | "pygments_lexer": "ipython3", 789 | "version": "3.6.7" 790 | } 791 | }, 792 | "nbformat": 4, 793 | "nbformat_minor": 2 794 | } 795 | -------------------------------------------------------------------------------- /Error Chart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harshraj11584/Paper-Implementation-Matrix-Factorization-Recommender-Systems-Netflix/752452296cbee241df0100a82b90e885c9ef6ec7/Error Chart.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Harsh Raj 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Presentation.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harshraj11584/Paper-Implementation-Matrix-Factorization-Recommender-Systems-Netflix/752452296cbee241df0100a82b90e885c9ef6ec7/Presentation.pdf -------------------------------------------------------------------------------- /Presentation_Images/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harshraj11584/Paper-Implementation-Matrix-Factorization-Recommender-Systems-Netflix/752452296cbee241df0100a82b90e885c9ef6ec7/Presentation_Images/1.png -------------------------------------------------------------------------------- /Presentation_Images/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harshraj11584/Paper-Implementation-Matrix-Factorization-Recommender-Systems-Netflix/752452296cbee241df0100a82b90e885c9ef6ec7/Presentation_Images/2.png -------------------------------------------------------------------------------- /Presentation_Images/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harshraj11584/Paper-Implementation-Matrix-Factorization-Recommender-Systems-Netflix/752452296cbee241df0100a82b90e885c9ef6ec7/Presentation_Images/3.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Paper-Implementation-Matrix-Factorization-Recommender-Systems-Netflix 2 | [![license](https://img.shields.io/github/license/DAVFoundation/captain-n3m0.svg?style=flat-square)](https://github.com/harshraj11584/Paper-Implementation-Matrix-Factorization-Recommender-Systems-Netflix/blob/master/LICENSE) [![Python 3.6](https://img.shields.io/badge/python-3.6-blue.svg)](https://www.python.org/downloads/release/python-360/) 3 | ## IEEE paper **"Matrix Factorization Techniques for Recommender Systems"** 4 | ### - Yehuda Koren, Robert Bell, Chris Volinsky 5 | ### Python 3.6 6 | 7 | Links to original paper published by IEEE Computer Society : [[1]](https://ieeexplore.ieee.org/document/5197422), [[2]](https://datajobs.com/data-science-repo/Recommender-Systems-[Netflix].pdf) 8 | 9 | Link to Netflix Dataset Used : [[1]](https://www.kaggle.com/netflix-inc/netflix-prize-data) 10 | 11 | ### Files 12 | 13 | 1) **Presentation.pdf** : Explains the paper. Was written in Latex Beamer, tex code is in _presentation.tex_ 14 | 15 | 2) **recommender_final.py** : The final recommender. Includes biases and regularization. Requires **mf.py** to be imported to run. Use directly on any dataset by changing line 19 in **recommender_final.py**. 16 | 17 | 3) **recommender_final_toy_dataset.py** shows how exactly Matrix Factorization Techniques work by considering a 5x5 toy dataset. 18 | 19 | 4) The **.ipynb_** files include visualizations of RMSE decreasing with iterations when fitting on the training dataset. All **.ipynb** files are standalone and do not require importing **mf.py** 20 | 21 | 5) **feasible_data_n.txt** : Files with only the first n datapoints from whole dataset. Used for Testing. 22 | 23 | 5) **Training** and **Testing Data** : 24 | Not given separately. Program randomly separates k% of data as Test data, trains on remaining, then tests on the k% values. Default k=20, can be changed on line 154. 25 | 26 | 27 | ### Error Analysis 28 | 29 | ![img](https://github.com/harshraj11584/Paper-Implementation-Matrix-Factorization-Recommender-Systems-Netflix/blob/master/Error%20Chart.png) 30 | -------------------------------------------------------------------------------- /__pycache__/mf.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harshraj11584/Paper-Implementation-Matrix-Factorization-Recommender-Systems-Netflix/752452296cbee241df0100a82b90e885c9ef6ec7/__pycache__/mf.cpython-36.pyc -------------------------------------------------------------------------------- /feasible_data_1024.txt: -------------------------------------------------------------------------------- 1 | 1: 2 | 1488844,3,2005-09-06 3 | 822109,5,2005-05-13 4 | 885013,4,2005-10-19 5 | 30878,4,2005-12-26 6 | 823519,3,2004-05-03 7 | 893988,3,2005-11-17 8 | 124105,4,2004-08-05 9 | 1248029,3,2004-04-22 10 | 1842128,4,2004-05-09 11 | 2238063,3,2005-05-11 12 | 1503895,4,2005-05-19 13 | 2207774,5,2005-06-06 14 | 2590061,3,2004-08-12 15 | 2442,3,2004-04-14 16 | 543865,4,2004-05-28 17 | 1209119,4,2004-03-23 18 | 804919,4,2004-06-10 19 | 1086807,3,2004-12-28 20 | 1711859,4,2005-05-08 21 | 372233,5,2005-11-23 22 | 1080361,3,2005-03-28 23 | 1245640,3,2005-12-19 24 | 558634,4,2004-12-14 25 | 2165002,4,2004-04-06 26 | 1181550,3,2004-02-01 27 | 1227322,4,2004-02-06 28 | 427928,4,2004-02-26 29 | 814701,5,2005-09-29 30 | 808731,4,2005-10-31 31 | 662870,5,2005-08-24 32 | 337541,5,2005-03-23 33 | 786312,3,2004-11-16 34 | 1133214,4,2004-03-07 35 | 1537427,4,2004-03-29 36 | 1209954,5,2005-05-09 37 | 2381599,3,2005-09-12 38 | 525356,2,2004-07-11 39 | 1910569,4,2004-04-12 40 | 2263586,4,2004-08-20 41 | 2421815,2,2004-02-26 42 | 1009622,1,2005-01-19 43 | 1481961,2,2005-05-24 44 | 401047,4,2005-06-03 45 | 2179073,3,2004-08-29 46 | 1434636,3,2004-05-01 47 | 93986,5,2005-10-06 48 | 1308744,5,2005-10-29 49 | 2647871,4,2005-12-30 50 | 1905581,5,2005-08-16 51 | 2508819,3,2004-05-18 52 | 1578279,1,2005-05-19 53 | 1159695,4,2005-02-15 54 | 2588432,3,2005-03-31 55 | 2423091,3,2005-09-12 56 | 470232,4,2004-04-08 57 | 2148699,2,2004-06-05 58 | 1342007,3,2004-07-16 59 | 466135,4,2004-07-13 60 | 2472440,3,2005-08-13 61 | 1283744,3,2004-04-17 62 | 1927580,4,2004-11-08 63 | 716874,5,2005-05-06 64 | 4326,4,2005-10-29 65 | 1546549,5,2004-07-20 66 | 1493697,1,2005-11-01 67 | 880166,5,2005-07-12 68 | 535396,2,2005-02-03 69 | 494609,4,2004-12-08 70 | 1961619,5,2005-06-01 71 | 883478,4,2005-12-16 72 | 793564,4,2004-04-19 73 | 1567202,2,2004-03-01 74 | 573537,4,2005-02-03 75 | 1972040,4,2005-06-02 76 | 1838912,3,2005-11-08 77 | 411705,4,2004-05-11 78 | 2244518,5,2004-09-15 79 | 584542,5,2005-02-01 80 | 667730,5,2005-05-26 81 | 2488120,5,2005-09-20 82 | 1926776,1,2005-10-05 83 | 38052,3,2004-06-03 84 | 1196100,4,2004-10-30 85 | 314933,3,2005-09-12 86 | 1792741,2,2004-02-09 87 | 769643,1,2004-11-18 88 | 2477242,5,2005-01-30 89 | 1421006,3,2005-08-03 90 | 729846,4,2005-08-09 91 | 1719610,2,2005-08-19 92 | 1696031,4,2005-01-25 93 | 1817215,4,2005-09-01 94 | 406057,4,2004-09-27 95 | 636262,1,2005-09-26 96 | 1245406,4,2004-12-30 97 | 1834590,3,2005-02-05 98 | 593225,3,2004-07-11 99 | 1011918,4,2005-05-28 100 | 1665054,4,2005-07-06 101 | 2630337,5,2005-03-10 102 | 1155747,3,2005-07-03 103 | 2439493,1,2004-02-06 104 | 479924,5,2005-08-02 105 | 530789,5,2005-03-29 106 | 765860,4,2005-06-13 107 | 231001,3,2005-01-03 108 | 1493615,5,2005-05-23 109 | 1850615,2,2005-07-12 110 | 68959,3,2005-08-11 111 | 147386,5,2004-09-19 112 | 624035,5,2004-02-19 113 | 782308,5,2004-07-02 114 | 1116080,5,2005-08-08 115 | 421374,5,2004-11-17 116 | 1158759,4,2004-06-13 117 | 1025798,3,2005-09-02 118 | 1215397,5,2005-07-19 119 | 2475251,4,2005-08-25 120 | 321111,2,2005-05-02 121 | 2162676,3,2005-08-01 122 | 2635437,4,2004-12-19 123 | 2389367,1,2004-07-01 124 | 485622,5,2005-08-01 125 | 235553,4,2004-07-15 126 | 831869,4,2005-06-02 127 | 99400,5,2005-09-16 128 | 684876,4,2005-11-01 129 | 1871179,3,2004-12-08 130 | 1107678,5,2005-03-15 131 | 642036,3,2005-09-21 132 | 700890,5,2004-05-06 133 | 2289956,5,2004-04-29 134 | 2040859,1,2004-11-23 135 | 1524964,3,2005-04-19 136 | 121318,4,2005-06-06 137 | 317050,5,2005-11-15 138 | 2287003,5,2004-12-20 139 | 59052,2,2004-07-12 140 | 893742,4,2005-04-27 141 | 1346257,3,2005-08-23 142 | 55016,3,2004-05-12 143 | 30245,5,2004-10-19 144 | 743633,4,2005-08-09 145 | 1596531,5,2004-01-23 146 | 1125499,5,2005-01-31 147 | 706832,4,2005-05-06 148 | 2465337,3,2005-04-04 149 | 2291422,1,2005-08-28 150 | 1777406,3,2005-01-02 151 | 1904905,4,2005-05-13 152 | 2450433,3,2005-01-08 153 | 1348967,2,2004-06-21 154 | 638020,3,2005-08-10 155 | 2217779,4,2005-02-14 156 | 194280,1,2004-12-14 157 | 493009,4,2005-03-27 158 | 1567167,4,2005-09-11 159 | 850327,5,2005-09-18 160 | 520386,3,2005-10-25 161 | 320540,2,2005-07-26 162 | 1188228,2,2004-12-05 163 | 57961,4,2004-11-09 164 | 1113230,3,2005-05-04 165 | 1374216,2,2005-09-23 166 | 595778,3,2004-03-03 167 | 209573,4,2005-10-31 168 | 2354601,5,2005-12-10 169 | 2563596,4,2004-04-04 170 | 835265,4,2005-09-01 171 | 1819474,3,2004-04-03 172 | 1447104,3,2004-08-28 173 | 1100940,1,2005-10-24 174 | 143274,3,2005-08-31 175 | 2329565,4,2005-01-19 176 | 181592,4,2005-03-26 177 | 936396,2,2004-02-09 178 | 1125797,3,2004-07-05 179 | 2283366,3,2004-12-27 180 | 514495,4,2005-04-13 181 | 1772176,3,2005-09-27 182 | 1877347,4,2005-07-13 183 | 1287892,4,2005-05-17 184 | 255443,2,2005-05-23 185 | 890669,4,2005-11-02 186 | 1989766,4,2005-07-08 187 | 2315073,4,2004-03-24 188 | 14756,4,2005-12-27 189 | 907623,3,2004-05-11 190 | 991423,4,2004-10-24 191 | 1604238,4,2005-06-02 192 | 1027056,3,2005-12-03 193 | 2025883,5,2005-03-24 194 | 732936,5,2005-04-24 195 | 563962,5,2005-11-09 196 | 799442,4,2005-11-21 197 | 352635,5,2004-05-11 198 | 2537543,5,2005-10-26 199 | 1564395,4,2005-07-09 200 | 1655178,4,2004-08-26 201 | 573434,4,2004-05-26 202 | 1141189,4,2004-12-15 203 | 383247,5,2005-02-03 204 | 1763921,5,2004-05-10 205 | 1943970,5,2004-09-20 206 | 322009,3,2004-05-20 207 | 2333817,3,2004-10-22 208 | 2095681,2,2005-10-29 209 | 1149588,4,2005-12-13 210 | 2354740,5,2005-05-05 211 | 2421360,5,2005-10-19 212 | 496087,2,2004-04-25 213 | 2191781,1,2005-04-03 214 | 1694083,4,2005-10-03 215 | 818416,3,2005-07-27 216 | 701960,5,2005-04-29 217 | 2090477,4,2005-06-28 218 | 1664010,5,2005-10-12 219 | 2583822,5,2005-08-17 220 | 369646,5,2005-04-30 221 | 2234063,4,2005-05-11 222 | 259799,4,2005-06-23 223 | 1077982,4,2004-03-16 224 | 2631796,4,2005-09-27 225 | 1122383,3,2004-10-22 226 | 1508526,3,2004-07-25 227 | 1600207,5,2004-04-17 228 | 1283117,5,2005-03-18 229 | 1727869,5,2005-07-11 230 | 1522799,4,2004-10-19 231 | 1394012,5,2005-12-19 232 | 1558286,3,2005-03-15 233 | 1155602,3,2005-07-05 234 | 361066,3,2004-09-24 235 | 1743210,5,2005-09-22 236 | 1148389,4,2004-04-03 237 | 2268101,4,2005-10-28 238 | 519684,5,2005-07-25 239 | 767518,5,2005-08-02 240 | 122197,1,2005-03-09 241 | 2112162,4,2005-10-14 242 | 1073367,3,2005-07-25 243 | 400162,5,2004-11-08 244 | 1524343,5,2005-03-24 245 | 741245,4,2005-06-28 246 | 2563768,3,2005-07-04 247 | 1406595,4,2005-08-27 248 | 1137010,4,2004-10-05 249 | 60343,5,2005-05-08 250 | 225765,4,2004-05-10 251 | 2530404,3,2004-05-01 252 | 437881,3,2004-11-17 253 | 1935793,1,2005-04-18 254 | 134001,4,2005-05-06 255 | 2607300,3,2005-07-25 256 | 1008986,4,2004-10-25 257 | 94565,4,2004-12-15 258 | 828410,4,2005-01-03 259 | 1805202,4,2005-10-03 260 | 1922925,4,2004-04-03 261 | 1435717,5,2005-08-11 262 | 2277395,4,2004-03-09 263 | 2305014,5,2004-08-31 264 | 166041,4,2005-09-02 265 | 2413320,4,2004-02-06 266 | 87113,2,2005-09-21 267 | 722591,5,2004-03-08 268 | 2291306,1,2004-03-01 269 | 2010770,4,2004-12-30 270 | 255383,5,2005-04-10 271 | 1873429,4,2005-10-27 272 | 1647618,4,2005-08-27 273 | 608234,5,2005-02-25 274 | 42930,3,2005-05-14 275 | 1462072,5,2005-06-22 276 | 685565,5,2005-09-26 277 | 3321,3,2005-09-27 278 | 2554942,4,2005-10-03 279 | 1874547,4,2004-08-11 280 | 2269844,5,2005-11-02 281 | 34907,3,2005-08-17 282 | 1779903,4,2005-06-04 283 | 2576424,4,2005-08-10 284 | 230112,3,2004-08-26 285 | 508727,3,2004-07-11 286 | 1603525,3,2004-10-13 287 | 172264,4,2005-09-17 288 | 1182185,4,2005-10-06 289 | 2275470,2,2005-10-05 290 | 491531,5,2005-02-17 291 | 1346432,4,2005-11-15 292 | 1554712,5,2004-10-27 293 | 1450941,5,2005-09-13 294 | 1714116,3,2005-08-09 295 | 2016488,4,2004-11-22 296 | 1782762,4,2005-01-19 297 | 1343170,5,2005-02-26 298 | 2565752,4,2004-12-10 299 | 435841,3,2005-09-19 300 | 2242821,5,2005-08-29 301 | 638824,5,2004-05-19 302 | 2256485,1,2004-08-19 303 | 101597,5,2004-10-01 304 | 623036,5,2005-05-16 305 | 1559445,5,2005-06-08 306 | 1723381,5,2005-08-30 307 | 1824586,4,2005-03-03 308 | 2233105,4,2005-09-08 309 | 682963,3,2005-06-21 310 | 2529547,5,2005-11-18 311 | 504620,2,2005-08-12 312 | 1682104,4,2005-08-30 313 | 16272,4,2005-01-20 314 | 2491785,5,2005-05-09 315 | 978412,5,2005-07-02 316 | 2054145,3,2005-07-28 317 | 2444240,3,2005-08-14 318 | 547732,3,2005-06-11 319 | 811790,5,2005-09-02 320 | 31913,4,2004-10-15 321 | 437111,4,2005-06-27 322 | 640588,4,2004-09-06 323 | 2625019,3,2005-09-12 324 | 2605190,5,2005-11-05 325 | 915,5,2005-08-17 326 | 1430587,4,2005-05-18 327 | 2544219,5,2005-11-20 328 | 2603381,5,2005-11-29 329 | 305344,1,2004-02-08 330 | 2569099,1,2005-08-16 331 | 2430356,4,2004-07-15 332 | 885165,4,2005-06-02 333 | 2380806,5,2005-09-06 334 | 1512406,1,2005-10-03 335 | 1774623,4,2005-11-23 336 | 2226525,4,2005-02-08 337 | 2537076,4,2005-10-17 338 | 2060858,4,2005-05-09 339 | 498469,5,2005-03-22 340 | 68033,4,2005-10-04 341 | 1819146,5,2005-08-15 342 | 2088415,4,2005-02-01 343 | 473070,5,2005-04-06 344 | 1823641,5,2004-03-29 345 | 1839976,2,2004-03-31 346 | 14924,5,2005-10-04 347 | 1852606,4,2004-07-23 348 | 453694,5,2004-07-21 349 | 921487,2,2004-07-19 350 | 1022254,5,2004-09-15 351 | 2464081,4,2005-01-22 352 | 1228324,4,2005-11-12 353 | 1563530,4,2004-08-18 354 | 1181170,3,2004-09-08 355 | 1357013,3,2004-10-02 356 | 21722,4,2005-02-07 357 | 288420,5,2005-06-02 358 | 1739170,5,2005-09-19 359 | 2584676,3,2005-08-06 360 | 2013504,4,2005-08-10 361 | 1245176,4,2004-07-27 362 | 269524,3,2005-03-05 363 | 661344,3,2005-03-16 364 | 652324,3,2004-04-15 365 | 2239213,3,2005-08-24 366 | 863302,4,2004-08-19 367 | 758850,4,2004-09-21 368 | 1884755,2,2004-11-15 369 | 544833,3,2005-09-27 370 | 1562707,1,2005-07-25 371 | 810700,5,2004-08-31 372 | 837756,5,2004-10-26 373 | 155164,4,2004-10-29 374 | 493945,5,2005-04-12 375 | 1565175,5,2004-08-10 376 | 2005193,4,2005-11-17 377 | 1605780,4,2004-09-17 378 | 1294335,2,2004-09-22 379 | 608576,4,2005-03-19 380 | 659505,4,2005-05-16 381 | 1604707,4,2005-10-17 382 | 2630797,5,2005-12-09 383 | 402266,5,2004-10-16 384 | 752642,3,2004-02-24 385 | 1906145,4,2005-07-19 386 | 389872,2,2005-08-09 387 | 1462866,2,2004-10-09 388 | 1952116,4,2005-04-28 389 | 54774,4,2005-05-25 390 | 1776980,5,2005-10-13 391 | 1494196,5,2004-02-29 392 | 253794,5,2004-08-10 393 | 1569513,3,2004-02-26 394 | 596728,2,2004-04-26 395 | 1107588,1,2004-02-22 396 | 1133763,3,2005-05-15 397 | 1398076,4,2004-07-02 398 | 1178171,4,2004-07-01 399 | 984369,3,2005-08-25 400 | 2618594,4,2004-07-27 401 | 1653834,4,2004-08-22 402 | 2322840,3,2005-07-12 403 | 2207647,4,2004-08-12 404 | 1994111,4,2005-01-11 405 | 1824044,4,2004-04-29 406 | 2255037,3,2004-06-01 407 | 2056022,3,2004-11-22 408 | 1458179,4,2005-01-26 409 | 1508350,4,2005-06-27 410 | 1168571,5,2005-09-14 411 | 766489,3,2005-10-01 412 | 1424199,5,2005-08-08 413 | 2054180,3,2004-07-09 414 | 448902,5,2005-07-20 415 | 1547173,3,2005-11-18 416 | 1751103,4,2004-08-05 417 | 121073,5,2004-12-16 418 | 2609436,4,2004-11-09 419 | 1398626,2,2004-12-03 420 | 1311231,3,2004-03-30 421 | 2279000,3,2005-02-18 422 | 236921,5,2005-03-19 423 | 2566259,5,2005-04-06 424 | 758937,4,2005-10-24 425 | 2260684,4,2004-11-08 426 | 1190829,4,2004-02-10 427 | 136106,3,2005-08-08 428 | 344753,3,2004-07-21 429 | 568930,5,2005-05-02 430 | 206115,4,2005-08-23 431 | 2390644,3,2004-09-06 432 | 2078679,5,2005-01-07 433 | 1682651,4,2005-05-02 434 | 386915,4,2005-05-27 435 | 972136,3,2005-06-20 436 | 1806515,3,2005-09-29 437 | 11589,3,2005-10-19 438 | 2118461,5,2005-10-24 439 | 444411,3,2004-09-05 440 | 691108,4,2005-02-27 441 | 332401,3,2005-04-28 442 | 1278488,4,2005-04-27 443 | 358776,4,2005-11-21 444 | 387418,1,2004-02-08 445 | 872408,4,2005-08-26 446 | 646098,4,2004-07-19 447 | 396595,5,2005-02-13 448 | 1366860,4,2004-01-26 449 | 1046882,3,2004-08-12 450 | 470861,5,2004-06-28 451 | 1455257,4,2004-09-17 452 | 1274780,3,2004-10-11 453 | 379184,4,2005-02-11 454 | 1273630,4,2005-09-08 455 | 492291,3,2005-06-06 456 | 145873,3,2004-02-25 457 | 1388284,5,2004-12-21 458 | 712610,4,2005-04-27 459 | 1116065,1,2005-05-03 460 | 660499,1,2005-07-08 461 | 1918987,4,2005-07-10 462 | 1357894,3,2004-09-09 463 | 190418,3,2004-09-30 464 | 1060658,3,2005-03-23 465 | 1443203,4,2005-05-22 466 | 1772839,5,2005-09-19 467 | 2385774,3,2004-04-07 468 | 1059319,3,2005-10-10 469 | 831775,4,2005-10-15 470 | 881346,5,2005-11-07 471 | 1066317,4,2004-05-07 472 | 13651,3,2004-06-16 473 | 208920,4,2005-01-23 474 | 308753,5,2005-10-31 475 | 2564257,3,2005-11-27 476 | 565041,4,2004-08-23 477 | 1602153,4,2005-06-13 478 | 173930,4,2005-06-30 479 | 202811,3,2005-07-18 480 | 353369,3,2005-08-01 481 | 1201176,4,2005-05-02 482 | 2047577,3,2005-05-27 483 | 685113,4,2005-10-24 484 | 1686060,5,2004-03-21 485 | 151004,5,2004-07-09 486 | 2126192,3,2004-04-16 487 | 1981464,4,2005-08-16 488 | 1862581,4,2004-06-08 489 | 1255780,5,2005-03-28 490 | 1962300,3,2005-06-22 491 | 1515355,3,2004-05-12 492 | 1001779,4,2005-09-09 493 | 2093105,3,2004-06-17 494 | 1123959,3,2005-01-05 495 | 1876297,5,2005-05-19 496 | 1364481,4,2004-05-29 497 | 998236,5,2004-06-23 498 | 328415,3,2004-11-01 499 | 1347129,4,2005-01-24 500 | 1117062,4,2005-07-07 501 | 1033930,3,2005-08-11 502 | 45117,5,2005-08-15 503 | 1005769,5,2004-09-28 504 | 712609,4,2005-03-20 505 | 740495,4,2005-02-05 506 | 2497991,4,2005-07-07 507 | 1017324,4,2005-04-05 508 | 120491,5,2004-09-13 509 | 1645794,4,2005-04-05 510 | 1658790,3,2005-06-23 511 | 2451020,4,2004-09-14 512 | 1878798,4,2005-01-11 513 | 1790903,4,2005-04-23 514 | 1254683,1,2004-02-13 515 | 874943,5,2005-03-22 516 | 121456,4,2005-04-19 517 | 1140108,4,2005-09-20 518 | 515436,1,2005-02-13 519 | 272689,5,2005-03-03 520 | 1247177,3,2005-12-04 521 | 263240,3,2004-07-07 522 | 2539549,3,2005-02-23 523 | 2565654,5,2004-11-15 524 | 334701,3,2005-02-07 525 | 42921,3,2005-10-04 526 | 2011399,5,2005-08-08 527 | 433945,5,2004-11-06 528 | 2151149,4,2005-01-13 529 | 1415954,2,2005-02-14 530 | 1086360,3,2005-03-10 531 | 2419258,4,2005-08-12 532 | 2380848,5,2005-01-11 533 | 1550216,1,2005-02-07 534 | 596533,5,2005-03-20 535 | 287901,5,2005-05-30 536 | 188613,4,2005-09-15 537 | 1654508,3,2005-04-25 538 | 1313126,5,2005-04-27 539 | 51334,4,2005-05-18 540 | 2374451,4,2005-06-05 541 | 2031093,4,2005-06-30 542 | 548064,5,2005-12-02 543 | 946102,5,2005-02-02 544 | 1790158,4,2005-05-17 545 | 1403184,3,2005-11-12 546 | 1535440,4,2005-08-18 547 | 1426604,4,2005-09-01 548 | 1815755,5,2004-07-20 549 | 2: 550 | 2059652,4,2005-09-05 551 | 1666394,3,2005-04-19 552 | 1759415,4,2005-04-22 553 | 1959936,5,2005-11-21 554 | 998862,4,2004-11-13 555 | 2625420,2,2004-12-06 556 | 573975,3,2005-07-21 557 | 392722,4,2004-12-10 558 | 1401650,4,2005-02-24 559 | 988104,3,2005-05-23 560 | 977632,4,2004-11-12 561 | 2557870,4,2005-03-27 562 | 1793899,5,2005-06-04 563 | 1340535,5,2004-12-12 564 | 1888322,5,2005-01-20 565 | 1283598,3,2004-11-30 566 | 1784150,4,2005-06-17 567 | 2271251,5,2005-08-19 568 | 65932,3,2005-07-19 569 | 1828884,5,2004-12-21 570 | 1878728,4,2005-12-01 571 | 1922778,3,2005-02-07 572 | 1176404,4,2005-02-23 573 | 2265116,3,2005-09-06 574 | 1078701,4,2005-10-12 575 | 1832577,4,2005-09-07 576 | 748922,5,2005-07-05 577 | 1013802,1,2005-05-30 578 | 1131325,2,2005-11-14 579 | 2244378,4,2005-02-09 580 | 494639,2,2005-09-26 581 | 636262,1,2005-08-23 582 | 1903158,4,2005-03-04 583 | 220427,4,2005-05-05 584 | 2439493,1,2005-02-10 585 | 2225116,4,2005-08-29 586 | 1445632,5,2005-01-30 587 | 2592823,4,2005-02-06 588 | 1288603,5,2005-06-20 589 | 2556926,3,2005-07-09 590 | 1190070,4,2005-11-05 591 | 1312846,3,2005-08-26 592 | 2226229,3,2005-04-24 593 | 1563935,1,2005-08-12 594 | 69809,5,2005-04-08 595 | 1349753,3,2005-03-23 596 | 785768,3,2005-04-19 597 | 426476,5,2005-05-10 598 | 810636,4,2005-04-25 599 | 468713,5,2005-07-18 600 | 222290,4,2005-03-14 601 | 349407,5,2005-01-02 602 | 311232,2,2005-06-28 603 | 2596999,4,2005-10-07 604 | 1025601,5,2005-11-09 605 | 1743759,4,2005-01-30 606 | 2385553,5,2005-05-24 607 | 1374216,1,2005-09-25 608 | 526466,4,2005-03-05 609 | 2648861,3,2005-05-25 610 | 1210631,3,2005-03-24 611 | 2314531,4,2005-07-15 612 | 618272,1,2005-08-14 613 | 2532807,3,2005-01-28 614 | 412535,4,2005-05-23 615 | 1315005,4,2005-06-28 616 | 1358911,5,2005-12-13 617 | 507603,1,2005-07-23 618 | 1507649,5,2005-02-17 619 | 845529,5,2005-04-26 620 | 1479907,5,2005-02-28 621 | 236271,2,2005-06-21 622 | 2422676,3,2005-04-07 623 | 1636093,5,2005-10-20 624 | 995594,5,2005-01-19 625 | 1664010,4,2005-10-13 626 | 2431481,3,2004-11-18 627 | 1980668,5,2004-12-16 628 | 402321,4,2005-04-18 629 | 1344564,3,2005-01-25 630 | 1632603,3,2005-09-09 631 | 2567280,3,2005-03-17 632 | 1623166,3,2005-02-10 633 | 521932,4,2005-01-11 634 | 105086,5,2005-09-20 635 | 2072554,5,2005-05-05 636 | 2231529,3,2005-08-25 637 | 2103439,2,2005-06-05 638 | 261764,1,2005-11-10 639 | 193476,5,2005-03-23 640 | 1576540,4,2005-04-14 641 | 1783594,5,2005-03-07 642 | 503334,4,2005-11-25 643 | 183903,5,2005-01-30 644 | 2606799,1,2005-05-16 645 | 1236127,4,2005-06-07 646 | 2375962,3,2005-07-04 647 | 2212071,3,2005-09-17 648 | 1252841,3,2005-02-06 649 | 247898,5,2005-07-15 650 | 970975,3,2005-09-02 651 | 305344,1,2004-10-16 652 | 1581186,4,2005-02-27 653 | 1129620,3,2005-09-03 654 | 584750,3,2005-11-27 655 | 11409,5,2005-01-13 656 | 1875495,2,2005-01-12 657 | 1403217,2,2005-08-17 658 | 2147527,1,2005-11-18 659 | 2418486,4,2005-11-08 660 | 1476323,5,2004-11-22 661 | 2345723,4,2004-12-26 662 | 2640085,5,2005-04-26 663 | 1803154,2,2004-12-14 664 | 1251170,2,2005-02-11 665 | 527491,4,2005-07-30 666 | 391517,4,2005-05-16 667 | 1398626,3,2005-11-26 668 | 828919,5,2004-12-18 669 | 196494,5,2005-10-03 670 | 715897,5,2005-10-18 671 | 268917,2,2005-06-03 672 | 41422,4,2005-09-11 673 | 1806515,3,2005-09-23 674 | 2118461,4,2005-12-20 675 | 387418,1,2004-11-19 676 | 2019055,5,2005-05-12 677 | 348960,1,2005-09-07 678 | 1167731,4,2005-06-07 679 | 2468831,5,2005-07-05 680 | 219925,4,2005-04-18 681 | 1025193,5,2005-07-07 682 | 630887,5,2005-01-20 683 | 1461435,1,2005-03-21 684 | 1838586,1,2005-08-17 685 | 1515430,3,2005-01-27 686 | 1807053,5,2005-04-25 687 | 1172326,5,2005-06-18 688 | 1785842,3,2005-06-01 689 | 803752,3,2004-11-17 690 | 1581265,3,2005-04-15 691 | 515436,1,2005-02-13 692 | 1824543,4,2005-04-20 693 | 1283204,3,2004-12-23 694 | 1272122,5,2005-07-25 695 | 3: 696 | 1025579,4,2003-03-29 697 | 712664,5,2004-02-01 698 | 1331154,4,2004-07-03 699 | 2632461,3,2005-07-22 700 | 44937,5,2004-06-22 701 | 656399,4,2003-09-20 702 | 439011,1,2004-01-22 703 | 1436762,3,2003-03-17 704 | 1644750,3,2003-03-19 705 | 2031561,4,2004-03-31 706 | 616720,4,2003-08-10 707 | 2467008,4,2004-03-15 708 | 975874,5,2004-02-09 709 | 701730,2,2005-10-05 710 | 1614320,4,2003-08-11 711 | 115498,3,2003-07-16 712 | 931626,2,2004-07-08 713 | 699878,4,2003-05-02 714 | 1694958,3,2005-08-10 715 | 66414,5,2004-02-21 716 | 2519847,5,2003-03-10 717 | 948069,3,2003-05-09 718 | 67315,4,2003-08-25 719 | 704249,4,2004-08-16 720 | 454417,4,2004-04-18 721 | 1995318,3,2004-07-14 722 | 2158448,5,2005-05-31 723 | 574843,5,2005-06-13 724 | 714960,4,2005-06-21 725 | 620771,2,2005-09-26 726 | 253876,4,2005-11-01 727 | 1632700,4,2005-11-14 728 | 603277,3,2003-06-21 729 | 79160,4,2005-07-11 730 | 1859725,4,2003-05-30 731 | 283774,5,2003-08-23 732 | 1983667,2,2003-08-08 733 | 2267507,4,2004-05-25 734 | 1813349,4,2003-03-18 735 | 2424721,3,2005-09-04 736 | 1275804,4,2003-08-27 737 | 1204327,4,2004-03-16 738 | 2143489,1,2004-08-23 739 | 672980,5,2004-11-18 740 | 166100,4,2004-12-17 741 | 2537764,5,2005-01-06 742 | 1650301,2,2005-06-17 743 | 553931,4,2005-10-04 744 | 214166,3,2005-10-09 745 | 6689,4,2003-02-20 746 | 109089,5,2003-05-27 747 | 1854303,1,2004-05-29 748 | 525003,5,2003-04-30 749 | 2312349,4,2003-05-13 750 | 188416,3,2004-01-27 751 | 2213550,4,2004-07-01 752 | 24344,4,2004-08-20 753 | 2344483,4,2004-09-28 754 | 531155,4,2005-02-28 755 | 1959707,3,2005-03-19 756 | 2120279,5,2005-05-15 757 | 1977959,4,2003-02-12 758 | 21983,4,2003-04-19 759 | 2173816,1,2003-07-17 760 | 78931,5,2003-07-09 761 | 2145227,4,2003-04-07 762 | 2463079,3,2003-08-26 763 | 1286051,3,2004-01-28 764 | 958104,4,2003-09-03 765 | 489962,3,2003-09-03 766 | 2297863,4,2004-01-13 767 | 958382,4,2004-08-22 768 | 248932,4,2005-04-03 769 | 1756658,3,2005-06-16 770 | 2579794,4,2005-10-26 771 | 1628475,4,2005-11-14 772 | 206809,5,2003-03-05 773 | 1333,4,2004-05-18 774 | 445828,2,2003-10-28 775 | 2079559,5,2004-08-28 776 | 1007809,4,2003-12-16 777 | 1562675,1,2003-03-06 778 | 1477923,4,2003-03-25 779 | 44783,3,2003-11-22 780 | 52540,1,2003-03-22 781 | 2436327,2,2004-07-13 782 | 1830211,4,2004-10-06 783 | 1857979,2,2005-03-08 784 | 1198785,2,2005-06-30 785 | 870391,2,2003-05-19 786 | 2164676,4,2003-03-03 787 | 1281996,4,2003-07-23 788 | 1853885,4,2004-06-18 789 | 2646060,3,2003-09-24 790 | 709342,4,2003-09-10 791 | 1195585,5,2003-08-04 792 | 1319527,5,2005-01-05 793 | 1478381,4,2005-05-26 794 | 1658752,3,2003-03-09 795 | 41371,5,2004-01-21 796 | 1479793,3,2004-01-18 797 | 1406148,3,2004-09-17 798 | 2446687,5,2004-12-14 799 | 968796,3,2005-02-06 800 | 2266857,3,2003-06-13 801 | 1456369,4,2003-07-08 802 | 1078792,4,2005-09-09 803 | 104768,5,2003-11-25 804 | 372528,3,2004-06-30 805 | 2240742,5,2004-11-05 806 | 1401399,4,2005-04-03 807 | 402377,4,2005-04-10 808 | 51230,4,2005-05-22 809 | 2229289,4,2005-12-30 810 | 2554745,4,2004-04-08 811 | 1710932,3,2004-08-16 812 | 1355097,4,2003-03-26 813 | 1231910,3,2003-12-23 814 | 2599552,4,2003-04-14 815 | 1394444,5,2005-02-17 816 | 1094443,2,2003-09-04 817 | 77266,2,2003-03-06 818 | 153249,4,2003-06-24 819 | 2590630,3,2003-10-10 820 | 2596383,3,2005-11-03 821 | 2601294,4,2004-02-10 822 | 2623268,4,2004-12-14 823 | 1756597,5,2005-04-11 824 | 1673185,3,2005-04-14 825 | 2611525,3,2004-02-05 826 | 2013198,4,2003-03-10 827 | 1704175,4,2004-04-29 828 | 2186436,2,2004-04-21 829 | 2252223,5,2004-06-24 830 | 780282,4,2004-06-06 831 | 203667,5,2003-03-25 832 | 2338873,5,2003-03-18 833 | 479779,4,2004-01-13 834 | 1927897,5,2004-07-27 835 | 719833,4,2003-09-08 836 | 871489,5,2004-09-14 837 | 968765,5,2004-10-11 838 | 1057518,4,2005-01-03 839 | 257517,4,2004-05-17 840 | 2003554,4,2003-04-25 841 | 2203875,4,2003-03-07 842 | 2213289,5,2003-06-06 843 | 2630072,3,2003-12-02 844 | 1142291,4,2005-08-22 845 | 1733188,4,2004-03-24 846 | 1614895,4,2003-09-13 847 | 1947922,2,2004-02-19 848 | 1036823,3,2004-09-20 849 | 786312,3,2004-11-06 850 | 1197233,2,2005-02-17 851 | 1100037,3,2005-04-02 852 | 1130826,3,2005-05-02 853 | 620147,5,2005-05-26 854 | 1479047,4,2005-09-27 855 | 1221390,4,2003-02-17 856 | 2193643,5,2003-06-04 857 | 544496,2,2004-02-02 858 | 357507,3,2003-03-04 859 | 976059,3,2004-06-22 860 | 820624,4,2004-02-09 861 | 924839,5,2004-05-18 862 | 966255,4,2004-07-09 863 | 108052,4,2005-03-22 864 | 375319,4,2005-09-15 865 | 309333,4,2005-10-16 866 | 1599030,5,2003-03-05 867 | 2443370,4,2003-07-07 868 | 871580,3,2003-05-13 869 | 311641,1,2005-08-27 870 | 532382,4,2003-08-21 871 | 2378011,5,2004-01-27 872 | 946970,4,2004-01-23 873 | 175763,4,2004-05-04 874 | 619721,4,2004-10-27 875 | 1248452,3,2005-01-20 876 | 1863843,2,2005-06-29 877 | 1915354,4,2005-10-20 878 | 920625,2,2005-11-14 879 | 1100170,4,2005-12-18 880 | 1733406,4,2003-03-26 881 | 755319,3,2005-08-14 882 | 1743030,3,2004-04-05 883 | 309567,2,2003-05-07 884 | 2096587,5,2003-06-09 885 | 345869,4,2004-08-08 886 | 1544094,5,2005-02-22 887 | 1022903,4,2005-09-14 888 | 290951,4,2003-02-24 889 | 1737484,4,2004-08-16 890 | 183215,4,2005-08-15 891 | 2065639,4,2004-04-02 892 | 250836,3,2005-09-08 893 | 2126122,4,2005-02-22 894 | 1206452,4,2005-03-21 895 | 2068821,3,2005-05-30 896 | 1924939,4,2005-10-25 897 | 1940163,4,2005-07-14 898 | 1033433,2,2004-06-05 899 | 1213801,3,2003-04-09 900 | 1045221,1,2003-05-13 901 | 253037,3,2005-08-05 902 | 341954,2,2003-12-15 903 | 697945,4,2003-05-28 904 | 1115632,3,2003-11-24 905 | 2103655,3,2003-08-01 906 | 2495200,5,2004-10-05 907 | 714550,4,2005-04-02 908 | 979820,1,2005-06-26 909 | 514312,3,2004-05-19 910 | 2025577,3,2003-08-22 911 | 1589677,4,2003-06-12 912 | 425033,3,2003-06-13 913 | 2352327,4,2004-09-08 914 | 156078,5,2004-08-03 915 | 851855,4,2003-05-20 916 | 2441707,3,2003-05-23 917 | 1278394,4,2003-06-07 918 | 962955,2,2003-10-03 919 | 811218,5,2004-05-11 920 | 636475,3,2005-01-29 921 | 1087412,4,2005-02-20 922 | 410537,4,2005-06-06 923 | 1586499,3,2003-07-28 924 | 917063,4,2003-02-15 925 | 1023101,3,2003-08-04 926 | 2393306,4,2004-08-31 927 | 788774,4,2003-04-25 928 | 2586963,4,2003-06-08 929 | 2368791,3,2003-04-30 930 | 244266,3,2003-12-04 931 | 2622138,1,2004-02-23 932 | 793228,4,2004-05-19 933 | 1283965,4,2005-11-08 934 | 2135038,4,2003-10-20 935 | 722006,4,2005-02-20 936 | 1511683,1,2003-05-30 937 | 1939663,4,2003-07-20 938 | 1763372,3,2003-06-18 939 | 1834472,3,2003-04-08 940 | 209549,5,2004-06-15 941 | 515850,5,2003-09-23 942 | 1455472,2,2004-06-22 943 | 2301782,4,2004-11-08 944 | 770921,5,2004-12-13 945 | 297498,5,2005-04-04 946 | 386510,5,2005-09-16 947 | 2494367,2,2005-10-08 948 | 1607574,4,2005-10-20 949 | 1594095,4,2004-09-08 950 | 1124822,4,2005-08-15 951 | 544022,3,2004-08-19 952 | 1817216,2,2003-06-10 953 | 1931698,2,2004-07-08 954 | 569099,3,2003-05-19 955 | 1771085,2,2003-07-21 956 | 604949,4,2004-09-19 957 | 213541,3,2004-10-31 958 | 790920,3,2004-12-02 959 | 2554707,4,2005-03-08 960 | 376148,5,2005-04-25 961 | 1689439,5,2004-06-25 962 | 2485566,5,2004-08-09 963 | 2232958,4,2004-03-01 964 | 2267858,3,2003-04-24 965 | 1956967,4,2005-08-13 966 | 2494005,2,2005-08-27 967 | 323148,3,2003-11-29 968 | 2158065,4,2003-10-28 969 | 2370268,4,2003-09-29 970 | 2152838,5,2004-08-01 971 | 1407746,3,2004-08-19 972 | 1666581,4,2005-05-25 973 | 871548,5,2005-03-10 974 | 2385706,4,2003-04-23 975 | 1969676,5,2004-01-26 976 | 1927329,5,2004-01-06 977 | 1938559,5,2004-06-21 978 | 199769,4,2004-01-22 979 | 2576108,1,2003-06-16 980 | 162854,4,2004-10-25 981 | 215406,5,2005-02-18 982 | 2095263,4,2005-05-22 983 | 556045,5,2005-10-18 984 | 817851,4,2004-01-17 985 | 2491399,2,2005-07-24 986 | 1134816,4,2003-07-23 987 | 660454,4,2003-03-24 988 | 581199,4,2003-10-27 989 | 1545189,4,2003-07-07 990 | 1929487,3,2005-09-04 991 | 528384,5,2003-09-26 992 | 2646115,3,2003-10-08 993 | 727242,1,2005-01-04 994 | 883478,1,2005-10-27 995 | 247940,4,2003-08-29 996 | 369761,3,2003-08-19 997 | 1065126,4,2004-03-09 998 | 1101467,4,2003-07-29 999 | 393413,3,2004-04-06 1000 | 478176,4,2004-05-26 1001 | 1369550,3,2004-10-11 1002 | 2428502,4,2005-12-05 1003 | 282525,4,2003-02-21 1004 | 2085230,4,2004-03-15 1005 | 282522,3,2003-06-18 1006 | 2246070,3,2004-01-06 1007 | 532649,3,2004-01-19 1008 | 1053903,2,2004-05-02 1009 | 1521266,3,2004-09-28 1010 | 2303969,4,2004-12-27 1011 | 2580481,2,2005-03-08 1012 | 2551806,5,2003-02-17 1013 | 1749903,4,2003-07-30 1014 | 2549926,5,2003-06-20 1015 | 781779,3,2003-06-17 1016 | 22853,4,2004-02-10 1017 | 1788346,1,2003-05-19 1018 | 1858421,4,2004-02-01 1019 | 354704,3,2003-10-15 1020 | 841137,5,2004-07-19 1021 | 475797,4,2004-05-11 1022 | 1876156,3,2005-01-12 1023 | 769670,4,2005-01-19 1024 | 1272379,1,2005-01-19 -------------------------------------------------------------------------------- /mf.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import numpy as np 3 | import math 4 | import re 5 | import sklearn 6 | from scipy.sparse import csr_matrix 7 | import matplotlib.pyplot as plt 8 | import seaborn as sns 9 | from surprise import Reader, Dataset, SVD, evaluate 10 | sns.set_style("darkgrid") 11 | from cvxpy import * 12 | from numpy import matrix 13 | 14 | 15 | class MF(): 16 | 17 | def __init__(self, R, K, alpha, beta, iterations): 18 | """ 19 | Perform matrix factorization to predict empty 20 | entries in a matrix. 21 | 22 | Arguments 23 | - R (ndarray) : user-item rating matrix 24 | - K (int) : number of latent dimensions 25 | - alpha (float) : learning rate 26 | - beta (float) : regularization parameter 27 | """ 28 | 29 | self.R = R 30 | self.num_users, self.num_items = R.shape 31 | self.K = K 32 | self.alpha = alpha 33 | self.beta = beta 34 | self.iterations = iterations 35 | 36 | def train(self): 37 | # Initialize user and item latent feature matrice 38 | self.P = np.random.normal(scale=1./self.K, size=(self.num_users, self.K)) 39 | self.Q = np.random.normal(scale=1./self.K, size=(self.num_items, self.K)) 40 | 41 | # Initialize the biases 42 | self.b_u = np.zeros(self.num_users) 43 | self.b_i = np.zeros(self.num_items) 44 | self.b = np.mean(self.R[np.where(self.R != 0)]) 45 | 46 | # Create a list of training samples 47 | self.samples = [ 48 | (i, j, self.R[i, j]) 49 | for i in range(self.num_users) 50 | for j in range(self.num_items) 51 | if self.R[i, j] > 0 52 | ] 53 | 54 | # Perform stochastic gradient descent for number of iterations 55 | training_process = [] 56 | for i in range(self.iterations): 57 | np.random.shuffle(self.samples) 58 | self.sgd() 59 | mse = self.mse() 60 | training_process.append((i, mse)) 61 | #if (i+1) % 100 == 0: 62 | # print("Iteration: %d ; error = %.4f" % (i+1, mse)) 63 | 64 | return training_process 65 | 66 | def mse(self): 67 | """ 68 | A function to compute the total mean square error 69 | """ 70 | xs, ys = self.R.nonzero() 71 | predicted = self.full_matrix() 72 | error = 0 73 | for x, y in zip(xs, ys): 74 | error += pow(self.R[x, y] - predicted[x, y], 2) 75 | return np.sqrt(error) 76 | 77 | def sgd(self): 78 | """ 79 | Perform stochastic graident descent 80 | """ 81 | for i, j, r in self.samples: 82 | # Computer prediction and error 83 | prediction = self.get_rating(i, j) 84 | e = (r - prediction) 85 | 86 | # Update biases 87 | self.b_u[i] += self.alpha * (e - self.beta * self.b_u[i]) 88 | self.b_i[j] += self.alpha * (e - self.beta * self.b_i[j]) 89 | 90 | # Update user and item latent feature matrices 91 | self.P[i, :] += self.alpha * (e * self.Q[j, :] - self.beta * self.P[i,:]) 92 | self.Q[j, :] += self.alpha * (e * self.P[i, :] - self.beta * self.Q[j,:]) 93 | 94 | def get_rating(self, i, j): 95 | """ 96 | Get the predicted rating of user i and item j 97 | """ 98 | prediction = self.b + self.b_u[i] + self.b_i[j] + self.P[i, :].dot(self.Q[j, :].T) 99 | return prediction 100 | 101 | def full_matrix(self): 102 | """ 103 | Computer the full matrix using the resultant biases, P and Q 104 | """ 105 | return self.b + self.b_u[:,np.newaxis] + self.b_i[np.newaxis:,] + self.P.dot(self.Q.T) -------------------------------------------------------------------------------- /movie_titles.csv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harshraj11584/Paper-Implementation-Matrix-Factorization-Recommender-Systems-Netflix/752452296cbee241df0100a82b90e885c9ef6ec7/movie_titles.csv -------------------------------------------------------------------------------- /presentation.tex: -------------------------------------------------------------------------------- 1 | \documentclass[10pt]{beamer} 2 | 3 | \usetheme[progressbar=frametitle]{metropolis} 4 | \usepackage{appendixnumberbeamer} 5 | \usepackage{amssymb} 6 | \usepackage{booktabs} 7 | \usepackage[scale=2]{ccicons} 8 | \usepackage{tikz} 9 | \usepackage{pgfplots} 10 | \usepgfplotslibrary{dateplot} 11 | \usepackage{wrapfig} 12 | \usepackage{xspace} 13 | \newcommand{\themename}{\textbf{\textsc{metropolis}}\xspace} 14 | 15 | \usepackage{graphicx} 16 | 17 | \usepackage{subcaption} 18 | 19 | \usepackage[export]{adjustbox} 20 | \title{EE5327 : Optimization} 21 | % \date{\today} 22 | \date{} 23 | \author{Harsh Raj - MA17BTECH11003 \newline Aravind Reddy K V - MA17BTECH11010} 24 | \institute{Mathematics and Computing, IIT-Hyderabad} 25 | % \titlegraphic{\hfill\includegraphics[height=1.5cm]{logo.pdf}} 26 | \begin{document} 27 | \maketitle 28 | %begin{frame}{Table of contents} 29 | % \setbeamertemplate{section in toc}[sections numbered] 30 | % \tableofcontents[hideallsubsections] 31 | %\end{frame} 32 | 33 | %\section{Recommender Systems Strategies} 34 | 35 | %\begin{frame}[fragile]{Recommender Systems Strategies} 36 | % \begin{itemize} 37 | % \item Electronic retailers and content providers offer a huge selection of products to meet a variety of special needs and tastes. 38 | % \item Matching consumers with the most appropriate products is key to enhancing user satisfaction and loyalty. 39 | % \item Therefore, more retailers and e-commerce leaders like Amazon and Netflix have become interested in recommender systems, which analyze patterns of %user interest in products to provide personalized recommendations that suit a user's taste. 40 | % \end{itemize} 41 | %\end{frame} 42 | \section{Recommender Systems Strategies} 43 | \begin{frame}[fragile]{Recommender Systems Strategies} 44 | \begin{center} 45 | \begin{tikzpicture}[sibling distance=10em, 46 | every node/.style = {shape=rectangle, rounded corners, 47 | draw, align=center, 48 | top color=white, bottom color=blue!20}]] 49 | \node {Recommender System Strategies} 50 | child { node {Neighbour Method} } 51 | child { node {Latent Factor Methods} }; 52 | \end{tikzpicture} 53 | \end{center} 54 | \\ 55 | %\begin{itemize} 56 | 57 | %\item The \textbf{content filtering} approach creates a profile for each user or product to characterize its nature. 58 | %\item The profiles allow programs to associate users with matching products. 59 | %\item Content-based strategies require gathering external information that might not be available or easy to collect. 60 | %\end{itemize} 61 | %\end{frame} 62 | 63 | %\begin{frame}[fragile]{Collaborative Filtering} 64 | % \textbf{Collaborative filtering} relies only on past user behavior—for example, previous transactions or product ratings—without requiring the creation of explicit profiles. 65 | % \\ 66 | % \vspace{3mm} 67 | % The two primary areas of collaborative filtering are the \textbf{neighborhood methods} and \textbf{latent factor models}. 68 | \begin{center} 69 | \graphicspath{ {./images/} } 70 | \includegraphics [scale=0.2] {3} 71 | \end{center} 72 | \end{frame} 73 | 74 | \begin{frame}[fragile]{Neighbourhood Method} 75 | 76 | This method involves finding $K$-nearest neighbours (\textbf{K-NN} algorithm and it's variants).\\ 77 | \vspace{3mm} 78 | \begin{center} 79 | \graphicspath{ {./images/} } 80 | \includegraphics [scale=0.2] {1} 81 | \end{center} 82 | Why is this method very slow and also not accurate always? 83 | \newline 84 | - If predicting among all possibilities, requires \textbf{O(n)} iterations for each prediction. 85 | \newline 86 | - Only predicts within predetermined cluster if time reduced to \textbf{O(1)}. 87 | \newline 88 | - Cannot Incorporate Item/User based Bias 89 | \end{frame} 90 | 91 | \begin{frame}[fragile]{Latent Factor Models} 92 | \textbf{Latent factor models} try to explain the ratings by characterizing both items and users on factors inferred from the ratings patterns. 93 | \begin{center} 94 | \graphicspath{ {./images/} } 95 | \includegraphics [scale=0.2] {2} 96 | \end{center} 97 | User's predicted relative rating for a movie = 98 | \newline \textbf{Dot product} of the movie's and user's location vectors on \text\textbf{Latent Space}. 99 | \end{frame} 100 | 101 | %\begin{frame}[fragile]{Matrix Factorization Meth%ods} 102 | % \begin{item%ize} 103 | % \item Some of the most successful realizations of latent factor models are based on \textbf{matrix factorizati%on}. 104 | % \item In its basic form, matrix factorization characterizes both items and users by vectors of factors inferred from item rating patte%rns. 105 | % \end{item%ize} 106 | % Recommender systems rely on different types of \textbf{input data}, which are often placed in a matrix with one dimension representing users and the other %dimension representing items of interes%t.\\ 107 | % \vspace{%3mm} 108 | % The input form could be either \textbf{explicit feedback} or \textbf{implicit feedb%ack} 109 | %\end{frame} 110 | 111 | 112 | \begin{frame}[fragile]{Input Data} 113 | \begin{enumerate} 114 | \item \textbf{\textit{Explicit Feedback}} 115 | \vspace{3mm} 116 | \begin{itemize} 117 | \item Explicit input by users regarding their interest in products. 118 | \vspace{3mm} 119 | \item Comprises a \textbf{Sparse Matrix}, since any single user is likely to have rated only a small percentage of possible i 120 | \vspace{3mm} 121 | \item \textbf{High confidence} on this data. 122 | \end{itemize} 123 | \vspace{3mm} 124 | \item \textbf{\textit{Implicit Feedback}} 125 | \vspace{3mm} 126 | \begin{itemize} 127 | \item Observing user behavior, including purchase history, browsing history, search patterns etc. 128 | \vspace{3mm} 129 | \item Denotes the presence or absence of an event, so it is typically represented by a \textbf{Dense Matrix} 130 | \vspace{3mm} 131 | \item \textbf{Low confidence} on this data. 132 | \end{itemize} 133 | \end{enumerate} 134 | \end{frame} 135 | 136 | \begin{frame}[fragile]{Matrix Factorization Model} 137 | Matrix factorization models map both users and items to a joint \textbf{Latent Factor Space} of dimensionality \boldsymbol{f}.\\ 138 | \vspace{3mm} 139 | Each item \boldsymbol{i} is associated with a vector \boldsymbol{q_i} $\in \mathbb{R}^{f} $, quantizing the amount of each attribute present in item {i}. 140 | \newline Each user \boldsymbol{u} is associated with a vector \boldsymbol{p_u} $\in \mathbb{R}^{f}$, quantizing the weightage of each attribute in the user's final decision.\\ 141 | \vspace{3mm} 142 | The resulting dot product, \boldsymbol{q_i^{T} p_u}, captures the user \boldsymbol{u}’s overall interest in the item \boldsymbol{i}.\\ 143 | \vspace{3mm} 144 | This approximates user \boldsymbol{u}'s estimated rating of item \boldsymbol{i}, denoted by \boldsymbol{\hat{r}_{ui}}: 145 | \begin{equation} 146 | \boldsymbol{\hat{r}_{ui}=q_{i}^{T}p_{u}}. 147 | \end{equation} 148 | \end{frame} 149 | 150 | \begin{frame}{Example} 151 | 152 | For 5 movies, 7 latent attributes, we get : 153 | \newline \newline 154 | \begin{bmatrix} 155 | q_{11} & q_{12}& q_{13}& q_{14}& q_{15}& q_{16}& q_{17}& \\ 156 | q_{21} & q_{22}& q_{23}& q_{24}& q_{25}& q_{26}& q_{27}& \\ 157 | q_{31} & q_{32}& q_{33}& q_{34}& q_{35}& q_{36}& q_{37}& \\ 158 | q_{41} & q_{42}& q_{43}& q_{44}& q_{45}& q_{46}& q_{47}& \\ 159 | q_{51} & q_{52}& q_{53}& q_{54}& q_{55}& q_{56}& q_{57}& \\ 160 | \end{bmatrix} 161 | \begin{bmatrix} 162 | p_{1} &\\ 163 | p_{2} &\\ 164 | p_{3} &\\ 165 | p_{4} &\\ 166 | p_{5} &\\ 167 | p_{6} &\\ 168 | p_{7} &\\ 169 | \end{bmatrix} 170 | = 171 | \begin{bmatrix} 172 | \hat{r}_{1} &\\ 173 | \hat{r}_{2} &\\ 174 | \hat{r}_{3} &\\ 175 | \hat{r}_{4} &\\ 176 | \hat{r}_{5} &\\ 177 | \end{bmatrix} 178 | \end{frame} 179 | 180 | 181 | \begin{frame}{Optimization Problem} 182 | %Minimizes the regularized squared error on the set of known ratings 183 | Introduce Regularization Parameter to avoid overfitting. 184 | To learn the factor vectors \boldsymbol{p_u} and \boldsymbol{q_i}, the system minimizes the regularized squared error on the set of known ratings: 185 | \begin{center} 186 | {\min\limits_{q^{\star}, p^{\star}}$\sum\limits_{(u,i)\in \kappa}(r_{ui}-q_{i}^{T} p_{u})^{2}+\lambda(\Vert q_{i}\Vert^{2}+\Vert p_{u}\Vert^{2})$} 187 | \end{center} 188 | 189 | The constant $\lambda$ controls the extent of regularization, by keeping each attribute close to zero. $\lambda$ is determined by cross-validation. 190 | \end{frame} 191 | 192 | \begin{frame}{Learning Algorithm : SGD} 193 | One option is to use Stochastic Gradient Descent Algorithm, i.e., 194 | $e_{ui} = r_{ui}- {q_i}^T p_u$ 195 | \newline 196 | $q_i \longleftarrow q_i + \gamma(e_{ui}p_u - \lambda q_i)$ 197 | \newline 198 | $p_u \longleftarrow p_u + \gamma(e_{ui}q_i - \lambda p_u)$ 199 | \newline \newline 200 | \textbf{Problems :} 201 | \newline \newline 202 | - Requires $\textbf{O(n)}$ operations for each iteration. 203 | \newline. \hspace{2mm} Feasible only for $\textbf{Sparse Matrix}$. 204 | \newline. \hspace{3mm}$\implies$ Cannot Use $\textbf{Implicit Feedback}$ Data. 205 | \newline 206 | - All operations must be performed in serial order. 207 | \end{frame} 208 | 209 | \begin{frame}{Learning Algorithm : ALS} 210 | \textbf{Alternating Least Squares:} 211 | \newline \newline 212 | As both \boldsymbol{p_u} and \boldsymbol{q_i} are unknown, the objective is not Convex. 213 | \begin{equation} 214 | \sum\limits_{(u,i)\in \kappa}(r_{ui}-q_{i}^{T} p_{u})^{2}+\lambda(\Vert q_{i}\Vert^{2}+\Vert p_{u}\Vert^{2}) 215 | \end{equation} 216 | \newline 217 | If we fix one of the unknowns, the optimization problem becomes Quadratic Convex (QCP) and can be solved optimally. 218 | \newline \newline 219 | ALS technique rotates between fixing \boldsymbol{q_i}’s and \boldsymbol{p_u}’s. 220 | \newline When all \boldsymbol{p_u}’s are fixed, the system recomputes the \boldsymbol{q_i}’s by Directly solving a least-squares problem, and vice-versa. 221 | \newline Each step decreases objective function until convergence. 222 | \end{frame} 223 | 224 | \begin{frame}{Learning Algorithm : ALS} 225 | \textbf{Repeat Until Convergence: } 226 | \begin{center} 227 | {(i) \min\limits_{q^{\star}}$\sum\limits_{(u,i)\in \kappa}(r_{ui}-q_{i}^{T} p_{u})^{2}+\lambda(\Vert q_{i}\Vert^{2}+\Vert p_{u}\Vert^{2})$} 228 | \end{center} 229 | \begin{center} 230 | {(ii) \min\limits_{p^{\star}}$\sum\limits_{(u,i)\in \kappa}(r_{ui}-q_{i}^{T} p_{u})^{2}+\lambda(\Vert q_{i}\Vert^{2}+\Vert p_{u}\Vert^{2})$} 231 | \end{center} 232 | \textbf{Advantages :} 233 | \newline \newline 234 | - Feasible for $\textbf{Dense Matrix}$. 235 | \newline. \hspace{2mm}$\implies$ Can Use $\textbf{Implicit Feedback}$ Data. 236 | \newline 237 | - All \boldsymbol{p_i} are computed independent of other factors (same for all \boldsymbol{q_i}). 238 | \newline. \hspace{2mm}$\implies$ Parallelization can be done here. 239 | \end{frame} 240 | 241 | \begin{frame}{Adding Biases and Confidence} 242 | 243 | Incorporate \textbf{Bias} in this model - 244 | \newline \newline 245 | (i) \boldsymbol{\mu} : Shifts the Prediction Mean from 0 to \boldsymbol{\mu} 246 | \newline . \hspace{7mm} where \boldsymbol{\mu}= Overall Average Rating 247 | \newline 248 | (ii) \boldsymbol{b_i} : Item Based Bias 249 | \newline . \hspace{7mm} where \boldsymbol{b_i}= Average Rating of Item i - Overall Average Rating 250 | \newline 251 | (iii) \boldsymbol{b_u} : User Based Bias 252 | \newline . \hspace{7mm} where \boldsymbol{b_u}= Average Rating by User u - Overall Average Rating 253 | \newline \newline 254 | 255 | Incorporate \textbf{Confidence} in this model - 256 | \newline \newline 257 | (iv) \boldsymbol{c_{ui}} : Confidence in observing \boldsymbol{r_{ui}} 258 | 259 | %(iv) $\boldsymbol{{\mid N(u) \mid}}^{-0.5}$ $(\sum\limits_{i \in N(u)}\boldsymbol{x_i} )$: Normalized Implicit Feedback 260 | %\vspace{2mm} 261 | %\newline . \hspace{7mm} where \boldsymbol{\mid N(u)\mid}= Items with Implicit Feedback from User u 262 | %\newline . \hspace{7mm} and \boldsymbol{x_i} = Implicit Feedback Vector for i $\in \boldsymbol{N(u)}$ 263 | %\newline 264 | \end{frame} 265 | 266 | 267 | \begin{frame}{Final Recommender} 268 | \newline Final Prediction is : \newline 269 | $ \boldsymbol{\hat{r}_{ui}}=c_{ui}(\mu + b_u + b_i + {p_u}^T q_i) $ 270 | \newline \newline 271 | Final form of Recommender: 272 | \begin{center} 273 | {\min\limits_{q^{\star}, p^{\star},b^{\star}}$\sum\limits_{(u,i)}c_{ui}(r_{ui}- \mu -b_u -b_i - q_{i}^{T} p_{u})^{2}+\lambda(\Vert q_{i}\Vert^{2}+\Vert p_{u}\Vert^{2} +\Vert b_{u}\Vert^{2} +\Vert b_{i}\Vert^{2} )$ 274 | } 275 | \end{center} 276 | subject to : $c_{ui} \geqslant 0 $ $\forall (u,i) $ 277 | \newline. \hspace{1.53cm} $ \lambda \geqslant 0 $ 278 | 279 | 280 | 281 | \end{frame} 282 | 283 | \begin{frame}{Proof of Convexity} 284 | \textbf{Claim:} For a fixed $p_u$, \newline $\sum\limits_{(u,i)}c_{ui}(r_{ui}- \mu -b_u -b_i - q_{i}^{T} p_{u})^{2}+\lambda(\Vert q_{i}\Vert^{2}+\Vert p_{u}\Vert^{2} +\Vert b_{u}\Vert^{2} +\Vert b_{i}\Vert^{2} )$ \newline 285 | is convex in q_{i}. 286 | \newline \newline \textbf{Proof:}\newline 287 | i) $(r_{ui}- \mu -b_u -b_i - q_{i}^{T} p_{u})$ is affine in q_{i} 288 | \newline 289 | ii) $(r_{ui}- \mu -b_u -b_i - q_{i}^{T} p_{u})^{2}$ is convex in $q_{i}$ as it is square of affine function 290 | \newline 291 | iii) $\lambda(\Vert q_{i}\Vert^{2} )$ is convex in $q_{i}$ because it is a norm. 292 | \newline 293 | iv) As sum of convex functions is convex, adding ii) and ii), we get, \newline $\sum\limits_{(u,i)}c_{ui}(r_{ui}- \mu -b_u -b_i - q_{i}^{T} p_{u})^{2}+\lambda(\Vert q_{i}\Vert^{2}+\Vert p_{u}\Vert^{2} +\Vert b_{u}\Vert^{2} +\Vert b_{i}\Vert^{2} )$ is convex. \newline(Proved) 294 | \end{frame} 295 | 296 | \begin{frame}{Accuracy Improvement} 297 | 298 | Original Netflix system : \newline RMSE = 0.9514 299 | \newline \newline 300 | Plain Matrix Factorization Model : \newline RMSE = 0.9025 301 | \newline \newline 302 | Included User and Item Biases : \newline RMSE = 0.9000 303 | \newline \newline 304 | Included Implicit Feedback and Confidence Parameter : \newline RMSE = 0.8925 305 | 306 | \end{frame} 307 | 308 | \end{document} 309 | -------------------------------------------------------------------------------- /recommender.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 116, 6 | "metadata": {}, 7 | "outputs": [ 8 | { 9 | "name": "stdout", 10 | "output_type": "stream", 11 | "text": [ 12 | "Setup Complete\n", 13 | "\n" 14 | ] 15 | } 16 | ], 17 | "source": [ 18 | "#Setting up prerequisites\n", 19 | "import pandas as pd\n", 20 | "import numpy as np\n", 21 | "import math\n", 22 | "import re\n", 23 | "import sklearn\n", 24 | "from scipy.sparse import csr_matrix\n", 25 | "import matplotlib.pyplot as plt\n", 26 | "import seaborn as sns\n", 27 | "from surprise import Reader, Dataset, SVD, evaluate\n", 28 | "sns.set_style(\"darkgrid\")\n", 29 | "\n", 30 | "from cvxpy import *\n", 31 | "from numpy import matrix\n", 32 | "\n", 33 | "print(\"Setup Complete\\n\")" 34 | ] 35 | }, 36 | { 37 | "cell_type": "code", 38 | "execution_count": 117, 39 | "metadata": {}, 40 | "outputs": [ 41 | { 42 | "name": "stdout", 43 | "output_type": "stream", 44 | "text": [ 45 | "Dataset 1 shape: (1024, 3)\n", 46 | "-Dataset examples-\n", 47 | " Cust_Id Rating Date\n", 48 | "0 1: NaN NaN\n", 49 | "100 2630337 5.0 20050310.0\n", 50 | "200 573434 4.0 20040526.0\n", 51 | "300 638824 5.0 20040519.0\n", 52 | "400 1653834 4.0 20040822.0\n", 53 | "500 1033930 3.0 20050811.0\n", 54 | "600 349407 5.0 20050102.0\n", 55 | "700 656399 4.0 20030920.0\n", 56 | "800 1456369 4.0 20030708.0\n", 57 | "900 253037 3.0 20050805.0\n", 58 | "1000 1369550 3.0 20041011.0\n", 59 | "float64\n" 60 | ] 61 | } 62 | ], 63 | "source": [ 64 | "df1 = pd.read_csv('netflix-prize-data/toy_combined_data.txt', header = None, names = ['Cust_Id', 'Rating', 'Date'], usecols = [0,1,2])\n", 65 | "df1['Rating'] = df1['Rating'].astype(float)\n", 66 | "df1['Date'] = df1['Date'].astype(str)\n", 67 | "df1['Date'] = df1['Date'].map( lambda s : (s[:4])+(s[5:7])+(s[8:]))\n", 68 | "df1['Date'] = df1['Date'].astype(float)\n", 69 | "print('Dataset 1 shape: {}'.format(df1.shape))\n", 70 | "print('-Dataset examples-')\n", 71 | "print(df1.iloc[::100, :])\n", 72 | "print(df1['Date'].dtype)\n", 73 | "df = df1" 74 | ] 75 | }, 76 | { 77 | "cell_type": "code", 78 | "execution_count": 118, 79 | "metadata": {}, 80 | "outputs": [ 81 | { 82 | "name": "stdout", 83 | "output_type": "stream", 84 | "text": [ 85 | "See Overview of the Data\n" 86 | ] 87 | }, 88 | { 89 | "data": { 90 | "image/png": "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Zp7p69evNywWizFlyhS76b/88othsViMmTNnFmif5syZY1gsFsPPz8/Ytm2b3bxp06YZFovFeO655+ymP/bYY4bFYjG++eYbu+lbtmwx/Pz8jJYtW9qV+cMPPzQsFosxYsQI49q1a7bp586dM1q2bGn4+/sb0dHRtumHDx82LBaLMXHiRLvtW6d37969QPt2o6NHjxqffvqp8dFHHxmvv/660aJFC8PPz8+YNm1agbdhPbbjx483rl27ZrRu3dru/UxJSTEaNGhgvPDCC4ZhGEavXr0Mi8ViJCUl2ZaJjY016tSpYzRt2tQ4efKkbXpubq7x6quvGhaLxXj33Xdt048fP25YLBZj0KBBecqzbds2w2KxGK+++qrd9KZNm+Y5Rtb3ecyYMXbvTW5uru0cXLBggW36U089ZVgsFiMuLi7P6964P3dDamqqYbFYjM6dOxd63V9//dWwWCzGo48+6nD+9u3bDYvFYjzxxBO2aatXrzYsFosxatQoh+ssXrzYsFgsxrhx42zTJk2aZFgsFuOzzz7Ls/zZs2cNi8ViWCwWY9euXbct89mzZ4369esbgYGBxm+//eZwvlV+nwWrPn36GA0bNrT9nZOTYwQGBhodOnRw+L1x83vn6FyxKuy5ai2PxWIxBgwYYKSmptqmHzlyxPD39zcefPBBo3PnzsaFCxds8y5cuGA0bNjQaNOmjd22oqOjjTp16hjdu3e3W94wDOPnn382LBaL8fLLLzt8/UceecS4fPmy3byEhATDYrEYAwcONHJzc+3m5ebmGsnJyQ6Pw52yfua+/PLLQq87YcIEw2KxGD/88IPD+WPHjjUsFouxdOnS224rKSnJaNKkiVG3bl0jPj7+lsuOGDHCsFgsxt69ewtdZuv+1qlTx9ixY4fDZX7//fc8065du2Z73SNHjjgsT37fOTef/4bx/7+nLRaLsXbtWrt5s2bNMiwWizF16lS76S+99JJhsViMzz//3G763r17jTp16uS5poaGhub5zrRKSUlx+Nm72eHDhw1/f3+jVatWRmJiom36tWvXjOeee86wWCz57tuNZSnsNSIjI8No3bq10bBhwzzfP6dOnTKCg4ONdu3aGdnZ2bbp1mtE06ZNjePHj9um5+bm2sq6cePG2+4z/jlooglT2bZtm06fPq2WLVuqQ4cOdvP69++vOnXq6ODBgzp48GCB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+vbM1366Onc98WKXZwIAAI4uK3hd9Oy6xnzk7iVZv70582aOzZTh/ZIkt8ydmt41Vfnwt5fkUw+sSJL0rK5Kkhxu4esrj63K5Xc80fm1dH1j57aKit9+r3jds1ZXVqTqMMev396cJLlj4ZrcsXBNkmTMby433dd+IEvX78ie1v25btbEfP7h+lw3a2Ke37InN3x3WebPnZrjelS97rkAAICjQ+B10famfVmwfHM+/eCK1FRX5vrZkzq31VRVprZPTd4ybeghx+zc25YkuWzGqMwcPzCLVm5L6/72XHTSsIyu7Z1pIwbkExdNTo+qys4VwWsvmJh5M8fkwiknHHaW3jVVefdpozNtxIAkydlvGJx3nzY6ycHX4D3/2Yvz1XmnvuKx9y3ZkCS54aITc8NFJyZJnlpz6Ju1fPD8CVmwfHM2NO5NZUVFpo3on4tPHp6KiopUvv7uBAAAjhKB9zo9Ur81yzfsyKzJQzN5WL98ZkF9drfuz3Wz3pCfr208ZN+7n1yXDY3N+djsSfnIH0/Mmm1NueauJWnetz/z507LVeeMy7rtzdnZ3JZH6rfmjoWrM2V4/1w6Y+TvRNf/a2Cfmtx+2Rsze+rBoLxi5pjcftkbf6/5v/H42ty1+MW85/TR+dNTR+Wfn1qXv330153bRx5/XC45ZUT+/icrkyRfemxletdU5dIZI/PXP34+Tfvau3rKAACAo6yio+PYe+uMsTc91N0jcBStvW1Od48AAADHJCt4AAAAhRB4AAAAhRB4AAAAhRB4AAAAhRB4AAAAhRB4AAAAhRB4AAAAhRB4AAAAhRB4AAAAhRB4AAAAhRB4AAAAhRB4AAAAhRB4AAAAhRB4AAAAhRB4AAAAhRB4AAAAhRB4AAAAhRB4AAAAhRB4AAAAhRB4AAAAhRB4AAAAhRB4AAAAhRB4AAAAhRB4AAAAhRB4AAAAhRB4AAAAhajo6Ojo6O4huqKlrT29elR19xgcRX7HAADw+hxzgQcAAMArc4kmAABAIQQeAABAIQQeAABAIQQeAABAIQQeAABAIQQeAABAIQQeAABAIQQeAABAIQQeAABAIQQeAABAIQQeAABAIQQeAABAIQQeAABAIQQeAABAIQQeAABAIQQeAABAIQQeAABAIQQeAABAIQQeAABAIQQeAABAIQQeAABAIQQeAABAIQQeAABAIQQeAABAIQQeAABAIQQeAABAIQQeAABAIQQeAABAIQQeAABAIQQeAABAIQQeAABAIQQeAABAIQQeAABAIQQeAABAIQQeAABAIQQeAABAIQQeAABAIQQeAABAIQQeAABAIQQeAABAIQQeAABAIQQeAABAIQQeAABAIQQeAABAIQQeAABAIQQeAABAIQQeAABAIQQeAABAIQQeAABAIf4vKtJl6cj5K9kAAAAASUVORK5CYII=\n", 91 | "text/plain": [ 92 | "
" 93 | ] 94 | }, 95 | "metadata": {}, 96 | "output_type": "display_data" 97 | } 98 | ], 99 | "source": [ 100 | "#Seeing the distribution of ratings given by the users\n", 101 | "print(\"See Overview of the Data\")\n", 102 | "p = df.groupby('Rating')['Rating'].agg(['count'])\n", 103 | "# get movie count\n", 104 | "movie_count = df.isnull().sum()[1]\n", 105 | "# get customer count\n", 106 | "cust_count = df['Cust_Id'].nunique() - movie_count\n", 107 | "# get rating count\n", 108 | "rating_count = df['Cust_Id'].count() - movie_count\n", 109 | "ax = p.plot(kind = 'barh', legend = False, figsize = (15,10))\n", 110 | "plt.title('Total pool: {:,} Movies, {:,} customers, {:,} ratings given'.format(movie_count, cust_count, rating_count), fontsize=20)\n", 111 | "plt.axis('off')\n", 112 | "for i in range(1,6):\n", 113 | " ax.text(p.iloc[i-1][0]/4, i-1, 'Rated {}: {:.0f}%'.format(i, p.iloc[i-1][0]*100 / p.sum()[0]), color = 'white', weight = 'bold')" 114 | ] 115 | }, 116 | { 117 | "cell_type": "code", 118 | "execution_count": 119, 119 | "metadata": {}, 120 | "outputs": [ 121 | { 122 | "name": "stdout", 123 | "output_type": "stream", 124 | "text": [ 125 | "Movie IDs extracted from the extra rows given\n" 126 | ] 127 | } 128 | ], 129 | "source": [ 130 | "#Adding movie IDs to the dataset\n", 131 | "movie_np = []\n", 132 | "movie_id = 0\n", 133 | "for x in range(df.shape[0]):\n", 134 | " if(np.isnan(df.iloc[x]['Rating'])):\n", 135 | " movie_id = movie_id+1\n", 136 | " movie_np = np.append(movie_np,movie_id)\n", 137 | "\n", 138 | "#print(movie_np)\n", 139 | "#print(len(movie_np))\n", 140 | "df['Movie_Id'] = movie_np.astype(int)\n", 141 | "print(\"Movie IDs extracted from the extra rows given\")" 142 | ] 143 | }, 144 | { 145 | "cell_type": "code", 146 | "execution_count": 120, 147 | "metadata": {}, 148 | "outputs": [ 149 | { 150 | "name": "stdout", 151 | "output_type": "stream", 152 | "text": [ 153 | "-Dataset examples-\n", 154 | " Cust_Id Rating Date Movie_Id\n", 155 | "1 1488844 3.0 20050906.0 1\n", 156 | "101 1155747 3.0 20050703.0 1\n", 157 | "201 1141189 4.0 20041215.0 1\n", 158 | "301 2256485 1.0 20040819.0 1\n", 159 | "401 2322840 3.0 20050712.0 1\n", 160 | "501 45117 5.0 20050815.0 1\n", 161 | "602 2596999 4.0 20051007.0 2\n", 162 | "703 1644750 3.0 20030319.0 3\n", 163 | "803 372528 3.0 20040630.0 3\n", 164 | "903 1115632 3.0 20031124.0 3\n", 165 | "1003 2085230 4.0 20040315.0 3\n", 166 | "\n", 167 | "\n", 168 | "These are the final datatypes of the dataset\n", 169 | "Cust_Id int64\n", 170 | "Rating float64\n", 171 | "Date float64\n", 172 | "Movie_Id int64\n", 173 | "dtype: object\n" 174 | ] 175 | }, 176 | { 177 | "name": "stderr", 178 | "output_type": "stream", 179 | "text": [ 180 | "/usr/lib/python3/dist-packages/ipykernel_launcher.py:3: SettingWithCopyWarning: \n", 181 | "A value is trying to be set on a copy of a slice from a DataFrame.\n", 182 | "Try using .loc[row_indexer,col_indexer] = value instead\n", 183 | "\n", 184 | "See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy\n", 185 | " This is separate from the ipykernel package so we can avoid doing imports until\n" 186 | ] 187 | } 188 | ], 189 | "source": [ 190 | "# remove the extra Movie ID rows\n", 191 | "df = df[pd.notnull(df['Rating'])]\n", 192 | "df['Cust_Id'] = df['Cust_Id'].astype(int)\n", 193 | "print('-Dataset examples-')\n", 194 | "print(df.iloc[::100, :])\n", 195 | "\n", 196 | "\n", 197 | "print(\"\\n\\nThese are the final datatypes of the dataset\")\n", 198 | "print(df.dtypes)" 199 | ] 200 | }, 201 | { 202 | "cell_type": "code", 203 | "execution_count": 121, 204 | "metadata": {}, 205 | "outputs": [ 206 | { 207 | "name": "stdout", 208 | "output_type": "stream", 209 | "text": [ 210 | "(1009, 3)\n" 211 | ] 212 | } 213 | ], 214 | "source": [ 215 | "#Creating Data Matrix\n", 216 | "df_matrix=pd.pivot_table(df,values='Rating',index='Cust_Id',columns='Movie_Id')\n", 217 | "print(df_matrix.shape)" 218 | ] 219 | }, 220 | { 221 | "cell_type": "code", 222 | "execution_count": 122, 223 | "metadata": {}, 224 | "outputs": [ 225 | { 226 | "name": "stdout", 227 | "output_type": "stream", 228 | "text": [ 229 | "See some Movie ID- Movie Title Mapping : \n", 230 | "\n", 231 | " Year Name\n", 232 | "Movie_Id \n", 233 | "1 2003.0 Dinosaur Planet\n", 234 | "2 2004.0 Isle of Man TT 2004 Review\n", 235 | "3 1997.0 Character\n", 236 | "4 1994.0 Paula Abdul's Get Up & Dance\n", 237 | "5 2004.0 The Rise and Fall of ECW\n", 238 | "6 1997.0 Sick\n", 239 | "7 1992.0 8 Man\n", 240 | "8 2004.0 What the #$*! Do We Know!?\n" 241 | ] 242 | } 243 | ], 244 | "source": [ 245 | "#Loading the Movie ID- Movie Title Mapping File\n", 246 | "\n", 247 | "df_title = pd.read_csv('netflix-prize-data/movie_titles.csv', encoding = \"ISO-8859-1\", header = None, names = ['Movie_Id', 'Year', 'Name'])\n", 248 | "df_title.set_index('Movie_Id', inplace = True)\n", 249 | "print(\"See some Movie ID- Movie Title Mapping : \\n\")\n", 250 | "print (df_title.head(8))" 251 | ] 252 | }, 253 | { 254 | "cell_type": "code", 255 | "execution_count": 123, 256 | "metadata": {}, 257 | "outputs": [ 258 | { 259 | "name": "stdout", 260 | "output_type": "stream", 261 | "text": [ 262 | "\n", 263 | "\n", 264 | "Data Cleaning Complete.\n", 265 | " See head of the Data Matrix:\n", 266 | "\n", 267 | "Movie_Id 1 2 3\n", 268 | "Cust_Id \n", 269 | "915 5.0 NaN NaN\n", 270 | "1333 NaN NaN 4.0\n", 271 | "2442 3.0 NaN NaN\n", 272 | "3321 3.0 NaN NaN\n", 273 | "4326 4.0 NaN NaN\n", 274 | "\n", 275 | "Num of movies = 3\n", 276 | "Num of users = 1009\n" 277 | ] 278 | } 279 | ], 280 | "source": [ 281 | "print(\"\\n\\nData Cleaning Complete.\\n See head of the Data Matrix:\\n\")\n", 282 | "print(df_matrix.head())\n", 283 | "\n", 284 | "n_movies = movie_count\n", 285 | "n_customers = cust_count\n", 286 | "\n", 287 | "print(\"\\nNum of movies =\", movie_count)\n", 288 | "print(\"Num of users =\", cust_count)" 289 | ] 290 | }, 291 | { 292 | "cell_type": "code", 293 | "execution_count": 124, 294 | "metadata": {}, 295 | "outputs": [ 296 | { 297 | "name": "stdout", 298 | "output_type": "stream", 299 | "text": [ 300 | "Movie_Id 1 2 3\n", 301 | "Cust_Id \n", 302 | "915 5.0 0.0 0.0\n", 303 | "1333 0.0 0.0 4.0\n", 304 | "2442 3.0 0.0 0.0\n", 305 | "3321 3.0 0.0 0.0\n", 306 | "4326 4.0 0.0 0.0\n" 307 | ] 308 | } 309 | ], 310 | "source": [ 311 | "#Choosing the number of latent attributes\n", 312 | "n_attr= 100*1000000\n", 313 | "#print(type(n_attr),type(n_movies), type(n_customers))\n", 314 | "Q = Variable((n_attr,n_movies))\n", 315 | "P = Variable((n_attr, n_customers))\n", 316 | "\n", 317 | "\n", 318 | "\n", 319 | "acq_data = df_matrix.fillna(0.0)\n", 320 | "print(acq_data.head())\n", 321 | "\n" 322 | ] 323 | }, 324 | { 325 | "cell_type": "code", 326 | "execution_count": 125, 327 | "metadata": {}, 328 | "outputs": [], 329 | "source": [ 330 | "class MF():\n", 331 | "\n", 332 | " def __init__(self, R, K, alpha, beta, iterations):\n", 333 | " \"\"\"\n", 334 | " Perform matrix factorization to predict empty\n", 335 | " entries in a matrix.\n", 336 | "\n", 337 | " Arguments\n", 338 | " - R (ndarray) : user-item rating matrix\n", 339 | " - K (int) : number of latent dimensions\n", 340 | " - alpha (float) : learning rate\n", 341 | " - beta (float) : regularization parameter\n", 342 | " \"\"\"\n", 343 | "\n", 344 | " self.R = R\n", 345 | " self.num_users, self.num_items = R.shape\n", 346 | " self.K = K\n", 347 | " self.alpha = alpha\n", 348 | " self.beta = beta\n", 349 | " self.iterations = iterations\n", 350 | "\n", 351 | " def train(self):\n", 352 | " # Initialize user and item latent feature matrice\n", 353 | " self.P = np.random.normal(scale=1./self.K, size=(self.num_users, self.K))\n", 354 | " self.Q = np.random.normal(scale=1./self.K, size=(self.num_items, self.K))\n", 355 | "\n", 356 | " # Initialize the biases\n", 357 | " self.b_u = np.zeros(self.num_users)\n", 358 | " self.b_i = np.zeros(self.num_items)\n", 359 | " self.b = np.mean(self.R[np.where(self.R != 0)])\n", 360 | "\n", 361 | " # Create a list of training samples\n", 362 | " self.samples = [\n", 363 | " (i, j, self.R[i, j])\n", 364 | " for i in range(self.num_users)\n", 365 | " for j in range(self.num_items)\n", 366 | " if self.R[i, j] > 0\n", 367 | " ]\n", 368 | "\n", 369 | " # Perform stochastic gradient descent for number of iterations\n", 370 | " training_process = []\n", 371 | " for i in range(self.iterations):\n", 372 | " np.random.shuffle(self.samples)\n", 373 | " self.sgd()\n", 374 | " mse = self.mse()\n", 375 | " training_process.append((i, mse))\n", 376 | " #if (i+1) % 100 == 0:\n", 377 | " # print(\"Iteration: %d ; error = %.4f\" % (i+1, mse))\n", 378 | "\n", 379 | " return training_process\n", 380 | "\n", 381 | " def mse(self):\n", 382 | " \"\"\"\n", 383 | " A function to compute the total mean square error\n", 384 | " \"\"\"\n", 385 | " xs, ys = self.R.nonzero()\n", 386 | " predicted = self.full_matrix()\n", 387 | " error = 0\n", 388 | " for x, y in zip(xs, ys):\n", 389 | " error += pow(self.R[x, y] - predicted[x, y], 2)\n", 390 | " return np.sqrt(error)\n", 391 | "\n", 392 | " def sgd(self):\n", 393 | " \"\"\"\n", 394 | " Perform stochastic graident descent\n", 395 | " \"\"\"\n", 396 | " for i, j, r in self.samples:\n", 397 | " # Computer prediction and error\n", 398 | " prediction = self.get_rating(i, j)\n", 399 | " e = (r - prediction)\n", 400 | "\n", 401 | " # Update biases\n", 402 | " self.b_u[i] += self.alpha * (e - self.beta * self.b_u[i])\n", 403 | " self.b_i[j] += self.alpha * (e - self.beta * self.b_i[j])\n", 404 | "\n", 405 | " # Update user and item latent feature matrices\n", 406 | " self.P[i, :] += self.alpha * (e * self.Q[j, :] - self.beta * self.P[i,:])\n", 407 | " self.Q[j, :] += self.alpha * (e * self.P[i, :] - self.beta * self.Q[j,:])\n", 408 | "\n", 409 | " def get_rating(self, i, j):\n", 410 | " \"\"\"\n", 411 | " Get the predicted rating of user i and item j\n", 412 | " \"\"\"\n", 413 | " prediction = self.b + self.b_u[i] + self.b_i[j] + self.P[i, :].dot(self.Q[j, :].T)\n", 414 | " return prediction\n", 415 | "\n", 416 | " def full_matrix(self):\n", 417 | " \"\"\"\n", 418 | " Computer the full matrix using the resultant biases, P and Q\n", 419 | " \"\"\"\n", 420 | " return self.b + self.b_u[:,np.newaxis] + self.b_i[np.newaxis:,] + self.P.dot(self.Q.T)" 421 | ] 422 | }, 423 | { 424 | "cell_type": "code", 425 | "execution_count": 101, 426 | "metadata": {}, 427 | "outputs": [ 428 | { 429 | "name": "stdout", 430 | "output_type": "stream", 431 | "text": [ 432 | "Original:\n", 433 | " [[5 3 0 1]\n", 434 | " [4 0 0 1]\n", 435 | " [1 1 0 5]\n", 436 | " [1 0 0 4]\n", 437 | " [0 1 5 4]]\n", 438 | "Test Set:\n", 439 | " [[5 3 0 1]\n", 440 | " [4 0 0 1]\n", 441 | " [1 1 0 5]\n", 442 | " [0 0 0 4]\n", 443 | " [0 1 5 0]]\n", 444 | "MSE= 0.9219544457292888\n", 445 | "\n", 446 | "Training ...\n", 447 | "\n", 448 | "Learnt=\n", 449 | " [[5. 3. 3. 1.]\n", 450 | " [4. 2. 3. 1.]\n", 451 | " [1. 1. 6. 5.]\n", 452 | " [3. 2. 5. 4.]\n", 453 | " [1. 1. 5. 4.]]\n", 454 | "RMSE f= 0.8660254037844386\n" 455 | ] 456 | } 457 | ], 458 | "source": [ 459 | "#This cell works on Toy Dataset\n", 460 | "#The next cell is for real data\n", 461 | "R = np.array([\n", 462 | " [5, 3, 0, 1],\n", 463 | " [4, 0, 0, 1],\n", 464 | " [1, 1, 0, 5],\n", 465 | " [1, 0, 0, 4],\n", 466 | " [0, 1, 5, 4],\n", 467 | "])\n", 468 | "\n", 469 | "R1= np.array([\n", 470 | " [5, 3, 0, 1],\n", 471 | " [4, 0, 0, 1],\n", 472 | " [1, 1, 0, 5],\n", 473 | " [1, 0, 0, 4],\n", 474 | " [0, 1, 5, 4],\n", 475 | "])\n", 476 | "\n", 477 | "#Set the number of values to replace. For example 20%:\n", 478 | "prop = int(R.size * 0.2)\n", 479 | "\n", 480 | "#Randomly choose indices of the numpy array:\n", 481 | "i = [np.random.choice(range(R.shape[0])) for _ in range(prop)]\n", 482 | "j = [np.random.choice(range(R.shape[1])) for _ in range(prop)]\n", 483 | "\n", 484 | "#Change values with 0\n", 485 | "R[i,j] = 0\n", 486 | "print(\"Original:\\n\",R1)\n", 487 | "print(\"Test Set:\\n\",R)\n", 488 | "R=np.rint(R)\n", 489 | "\n", 490 | "from sklearn.metrics import mean_squared_error\n", 491 | "mse = mean_squared_error(R, R1)\n", 492 | "\n", 493 | "print(\"MSE=\",mse**0.5)\n", 494 | "\n", 495 | "print(\"\\nTraining ...\\n\")\n", 496 | "\n", 497 | "\n", 498 | "mf = MF(R, K=10000, alpha=0.01, beta=0.01, iterations=10000)\n", 499 | "training_process = mf.train()\n", 500 | "L=np.rint(mf.full_matrix())\n", 501 | "\n", 502 | "\n", 503 | "\n", 504 | "print(\"Learnt=\\n\",L)\n", 505 | "msef=0.0\n", 506 | "for i1 in range(len(i)):\n", 507 | " for i2 in range(len(j)):\n", 508 | " if R1.item(i[i1],j[i2])!=0:\n", 509 | " msef = msef + (R1.item((i[i1],j[i2]))-(L).item((i[i1],j[i2])))**2\n", 510 | "msef = (msef/(len(j)*len(i)))\n", 511 | "print(\"RMSE f=\",msef**0.5)" 512 | ] 513 | }, 514 | { 515 | "cell_type": "code", 516 | "execution_count": 139, 517 | "metadata": {}, 518 | "outputs": [ 519 | { 520 | "name": "stdout", 521 | "output_type": "stream", 522 | "text": [ 523 | "Original:\n", 524 | " [[5. 0. 0.]\n", 525 | " [0. 0. 4.]\n", 526 | " [3. 0. 0.]\n", 527 | " ...\n", 528 | " [0. 0. 3.]\n", 529 | " [4. 0. 0.]\n", 530 | " [0. 3. 0.]]\n", 531 | "Test Set:\n", 532 | " [[5. 0. 0.]\n", 533 | " [0. 0. 4.]\n", 534 | " [3. 0. 0.]\n", 535 | " ...\n", 536 | " [0. 0. 3.]\n", 537 | " [4. 0. 0.]\n", 538 | " [0. 3. 0.]]\n", 539 | "MSE= 0.9522806592149013\n", 540 | "\n", 541 | "Training ...\n", 542 | "\n", 543 | "\n", 544 | "Done\n", 545 | "\n" 546 | ] 547 | } 548 | ], 549 | "source": [ 550 | "#This cell works on Real DataSet\n", 551 | "\n", 552 | "R = np.array(acq_data)\n", 553 | "\n", 554 | "R1= np.array(acq_data)\n", 555 | "\n", 556 | "#Set the number of values to replace. For example 20%:\n", 557 | "prop = int(R.size * 0.2)\n", 558 | "\n", 559 | "#Randomly choose indices of the numpy array:\n", 560 | "i = [np.random.choice(range(R.shape[0])) for _ in range(prop)]\n", 561 | "j = [np.random.choice(range(R.shape[1])) for _ in range(prop)]\n", 562 | "\n", 563 | "#Change values with 0\n", 564 | "R[i,j] = 0\n", 565 | "print(\"Original:\\n\",R1)\n", 566 | "print(\"Test Set:\\n\",R)\n", 567 | "R=np.rint(R)\n", 568 | "\n", 569 | "from sklearn.metrics import mean_squared_error\n", 570 | "mse = mean_squared_error(R, R1)\n", 571 | "\n", 572 | "print(\"MSE=\",mse**0.5)\n", 573 | "\n", 574 | "print(\"\\nTraining ...\\n\")\n", 575 | "\n", 576 | "\n", 577 | "mf = MF(R, K=10000, alpha=0.01, beta=0.01, iterations=100)\n", 578 | "training_process = mf.train()\n", 579 | "L=np.rint(mf.full_matrix())\n", 580 | "\n", 581 | "print(\"\\nDone\\n\")" 582 | ] 583 | }, 584 | { 585 | "cell_type": "code", 586 | "execution_count": 138, 587 | "metadata": {}, 588 | "outputs": [ 589 | { 590 | "name": "stdout", 591 | "output_type": "stream", 592 | "text": [ 593 | "Minimizing Error on Training Set:\n", 594 | "\n" 595 | ] 596 | }, 597 | { 598 | "data": { 599 | "image/png": "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\n", 600 | "text/plain": [ 601 | "
" 602 | ] 603 | }, 604 | "metadata": {}, 605 | "output_type": "display_data" 606 | } 607 | ], 608 | "source": [ 609 | "x = [x for x, y in training_process]\n", 610 | "y = [y for x, y in training_process]\n", 611 | "x = x[::10]\n", 612 | "y = y[::10]\n", 613 | "plt.figure(figsize=((16,4)))\n", 614 | "plt.plot(x, np.sqrt(y))\n", 615 | "plt.xticks(x, x)\n", 616 | "\n", 617 | "print(\"Minimizing Error on Training Set:\\n\")\n", 618 | "plt.xlabel(\"Iterations\")\n", 619 | "plt.ylabel(\"Root Mean Square Error\")\n", 620 | "plt.grid(axis=\"y\")" 621 | ] 622 | }, 623 | { 624 | "cell_type": "code", 625 | "execution_count": 135, 626 | "metadata": {}, 627 | "outputs": [ 628 | { 629 | "name": "stdout", 630 | "output_type": "stream", 631 | "text": [ 632 | "Learnt=\n", 633 | " [[4.92519455 4.45237587 4.03105201]\n", 634 | " [3.92656424 3.75922101 3.97495875]\n", 635 | " [3.03875459 3.06278695 3.34900645]\n", 636 | " ...\n", 637 | " [3.76684225 3.59991078 3.61421131]\n", 638 | " [3.76735095 3.6001197 3.61418442]\n", 639 | " [3.33974773 3.02411301 3.35034179]]\n", 640 | "\n", 641 | "Rating predictions=\n", 642 | " [[5. 4. 4.]\n", 643 | " [4. 4. 4.]\n", 644 | " [3. 3. 3.]\n", 645 | " ...\n", 646 | " [4. 4. 4.]\n", 647 | " [4. 4. 4.]\n", 648 | " [3. 3. 3.]]\n", 649 | "\n", 650 | "P x Q:\n", 651 | "[[4.92519455 4.45237587 4.03105201]\n", 652 | " [3.92656424 3.75922101 3.97495875]\n", 653 | " [3.03875459 3.06278695 3.34900645]\n", 654 | " ...\n", 655 | " [3.76684225 3.59991078 3.61421131]\n", 656 | " [3.76735095 3.6001197 3.61418442]\n", 657 | " [3.33974773 3.02411301 3.35034179]]\n", 658 | "\n", 659 | "Global bias:\n", 660 | "3.688861985472155\n", 661 | "\n", 662 | "User bias:\n", 663 | "[ 0.42868902 0.16245438 -0.27258023 ... 0. 0.\n", 664 | " -0.2687577 ]\n", 665 | "\n", 666 | "Item bias:\n", 667 | "[ 0.07818754 -0.08891514 -0.07470079]\n", 668 | "\n", 669 | "Finding Error on test set...\n", 670 | "\n", 671 | "RMSE f= 0.46105629990963165\n" 672 | ] 673 | } 674 | ], 675 | "source": [ 676 | "print(\"Learnt=\\n\",mf.full_matrix())\n", 677 | "print(\"\\nRating predictions=\\n\",L)\n", 678 | "\n", 679 | "print()\n", 680 | "print(\"P x Q:\")\n", 681 | "print(mf.full_matrix())\n", 682 | "print()\n", 683 | "print(\"Global bias:\")\n", 684 | "print(mf.b)\n", 685 | "print()\n", 686 | "print(\"User bias:\")\n", 687 | "print(mf.b_u)\n", 688 | "print()\n", 689 | "print(\"Item bias:\")\n", 690 | "print(mf.b_i)\n", 691 | "\n", 692 | "print(\"\\nFinding Error on test set...\\n\")\n", 693 | "msef=0.0\n", 694 | "for i1 in range(len(i)):\n", 695 | " for i2 in range(len(j)):\n", 696 | " if R1.item(i[i1],j[i2])!=0:\n", 697 | " msef = msef + (R1.item((i[i1],j[i2]))-(L).item((i[i1],j[i2])))**2\n", 698 | "msef = (msef/(len(j)*len(i)))\n", 699 | "print(\"RMSE f=\",msef**0.5)" 700 | ] 701 | }, 702 | { 703 | "cell_type": "code", 704 | "execution_count": null, 705 | "metadata": {}, 706 | "outputs": [], 707 | "source": [] 708 | } 709 | ], 710 | "metadata": { 711 | "kernelspec": { 712 | "display_name": "Python 3", 713 | "language": "python", 714 | "name": "python3" 715 | }, 716 | "language_info": { 717 | "codemirror_mode": { 718 | "name": "ipython", 719 | "version": 3 720 | }, 721 | "file_extension": ".py", 722 | "mimetype": "text/x-python", 723 | "name": "python", 724 | "nbconvert_exporter": "python", 725 | "pygments_lexer": "ipython3", 726 | "version": "3.6.7" 727 | } 728 | }, 729 | "nbformat": 4, 730 | "nbformat_minor": 2 731 | } 732 | -------------------------------------------------------------------------------- /recommender_final.py: -------------------------------------------------------------------------------- 1 | #Setting up prerequisites 2 | #from numba import prange 3 | from mf import MF 4 | import pandas as pd 5 | import numpy as np 6 | import math 7 | import re 8 | import sklearn 9 | from scipy.sparse import csr_matrix 10 | import matplotlib.pyplot as plt 11 | import seaborn as sns 12 | from surprise import Reader, Dataset, SVD, evaluate 13 | sns.set_style("darkgrid") 14 | from cvxpy import * 15 | from numpy import matrix 16 | print("Setup Complete\n") 17 | 18 | 19 | print("Select Number of DataPoints to Train on: \n1: 1024 \t2: 10000 \n3: 25000 \t4: 75000 \n5: 100000 \t6: 200000\n\n") 20 | choice = int(input()) 21 | print("\nLoading Data\n") 22 | if (choice==1 or choice==1024): 23 | df1 = pd.read_csv('feasible_data_1024.txt', header = None, names = ['Cust_Id', 'Rating', 'Date'], usecols = [0,1,2]) 24 | elif (choice==2 or choice==10000): 25 | df1 = pd.read_csv('feasible_data_10000.txt', header = None, names = ['Cust_Id', 'Rating', 'Date'], usecols = [0,1,2]) 26 | elif (choice==3 or choice==25000): 27 | df1 = pd.read_csv('feasible_data_25000.txt', header = None, names = ['Cust_Id', 'Rating', 'Date'], usecols = [0,1,2]) 28 | elif (choice==4 or choice==75000): 29 | df1 = pd.read_csv('feasible_data_75000.txt', header = None, names = ['Cust_Id', 'Rating', 'Date'], usecols = [0,1,2]) 30 | elif (choice==5 or choice==100000): 31 | df1 = pd.read_csv('feasible_data_100000.txt', header = None, names = ['Cust_Id', 'Rating', 'Date'], usecols = [0,1,2]) 32 | elif (choice==6 or choice==200000): 33 | df1 = pd.read_csv('feasible_data_200000.txt', header = None, names = ['Cust_Id', 'Rating', 'Date'], usecols = [0,1,2]) 34 | 35 | 36 | df1['Rating'] = df1['Rating'].astype(float) 37 | df1['Date'] = df1['Date'].astype(str) 38 | df1['Date'] = df1['Date'].map( lambda s : (s[:4])+(s[5:7])+(s[8:])) 39 | df1['Date'] = df1['Date'].astype(float) 40 | print('Dataset 1 shape: {}'.format(df1.shape)) 41 | print('-Dataset examples-') 42 | print(df1.iloc[::10000, :]) 43 | #print(df1['Date'].dtype) 44 | df = df1 45 | 46 | 47 | 48 | 49 | #Seeing the distribution of ratings given by the users 50 | #print("See Overview of the Data") 51 | p = df.groupby('Rating')['Rating'].agg(['count']) 52 | # get movie count 53 | movie_count = df.isnull().sum()[1] 54 | # get customer count 55 | cust_count = df['Cust_Id'].nunique() - movie_count 56 | # get rating count 57 | rating_count = df['Cust_Id'].count() - movie_count 58 | ax = p.plot(kind = 'barh', legend = False, figsize = (15,10)) 59 | plt.title('Total pool: {:,} Movies, {:,} customers, {:,} ratings given'.format(movie_count, cust_count, rating_count), fontsize=20) 60 | plt.axis('off') 61 | for i in range(1,6): 62 | ax.text(p.iloc[i-1][0]/4, i-1, 'Rated {}: {:.0f}%'.format(i, p.iloc[i-1][0]*100 / p.sum()[0]), color = 'white', weight = 'bold') 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | #Adding movie IDs to the dataset 74 | print("\nExtracting Movie IDs\n") 75 | movie_np = [] 76 | movie_id = 0 77 | for x in range(df.shape[0]): 78 | if(np.isnan(df.iloc[x]['Rating'])): 79 | movie_id = movie_id+1 80 | movie_np = np.append(movie_np,movie_id) 81 | #print(movie_np) 82 | #print(len(movie_np)) 83 | df['Movie_Id'] = movie_np.astype(int) 84 | print("Movie IDs extracted from the extra rows given") 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | # remove the extra Movie ID rows 94 | print("\nRemoving extra Movie ID rows\n") 95 | df = df[pd.notnull(df['Rating'])] 96 | df['Cust_Id'] = df['Cust_Id'].astype(int) 97 | print('-Dataset examples-') 98 | print(df.iloc[::100, :]) 99 | print("\n\nThese are the final datatypes of the dataset") 100 | print(df.dtypes) 101 | 102 | 103 | 104 | 105 | #Creating Data Matrix 106 | df_matrix=pd.pivot_table(df,values='Rating',index='Cust_Id',columns='Movie_Id') 107 | print(df_matrix.shape) 108 | 109 | 110 | 111 | #Loading the Movie ID- Movie Title Mapping File 112 | print("\nLoading the Movie ID- Movie Title Mapping File\n") 113 | df_title = pd.read_csv('netflix-prize-data/movie_titles.csv', encoding = "ISO-8859-1", header = None, names = ['Movie_Id', 'Year', 'Name']) 114 | df_title.set_index('Movie_Id', inplace = True) 115 | print("See some Movie ID- Movie Title Mapping : \n") 116 | print (df_title.head(8)) 117 | 118 | 119 | 120 | 121 | print("\n\nData Cleaning Complete.\n See head of the Data Matrix:\n") 122 | print(df_matrix.head()) 123 | n_movies = movie_count 124 | n_customers = cust_count 125 | print("\nNum of movies =", movie_count) 126 | print("Num of users =", cust_count) 127 | print() 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | #Choosing the number of latent attributes 137 | n_attr= 100*1000000 138 | #print(type(n_attr),type(n_movies), type(n_customers)) 139 | Q = Variable((n_attr,n_movies)) 140 | P = Variable((n_attr, n_customers)) 141 | acq_data = df_matrix.fillna(0.0) 142 | print(acq_data.head()) 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | R = np.array(acq_data) 152 | R1= np.array(acq_data) 153 | 154 | 155 | print("\nRandomly Distributing Test and Train Set by removing 20% values...\n") 156 | #This cell works on Real DataSet 157 | R = np.array(acq_data) 158 | R1= np.array(acq_data) 159 | #Set the number of values to replace. For example 20%: 160 | prop = int(R.size * 0.2) 161 | #Randomly choose indices of the numpy array: 162 | #print("Creating Random Indices\n") 163 | # i = [np.random.choice(range(R.shape[0])) for _ in range(prop)] 164 | # j = [np.random.choice(range(R.shape[1])) for _ in range(prop)] 165 | i = np.random.randint(0,R.shape[0],size=prop) 166 | j = np.random.randint(0,R.shape[1],size=prop) 167 | #print("Created Random Indices\n") 168 | print("Done\n") 169 | #print("i=",i) 170 | #print("j=",j) 171 | #Change values with 0 172 | R[i,j] = 0 173 | print("Original:\n",R1) 174 | print("Test Set:\n",R) 175 | R=np.rint(R) 176 | from sklearn.metrics import mean_squared_error 177 | mse = mean_squared_error(R, R1) 178 | print("RMSE=",mse**0.5) 179 | print("\nTraining ...\n") 180 | mf = MF(R, K=2, alpha=0.01, beta=0.01, iterations=100) 181 | training_process = mf.train() 182 | L=np.rint(mf.full_matrix()) 183 | print("\nDone\n") 184 | x = [x for x, y in training_process] 185 | y = [y for x, y in training_process] 186 | x = x[::10] 187 | y = y[::10] 188 | plt.figure(figsize=((16,4))) 189 | plt.plot(x, np.sqrt(y)) 190 | plt.xticks(x, x) 191 | print("Minimizing Error on Training Set:\n") 192 | plt.xlabel("Iterations") 193 | plt.ylabel("Root Mean Square Error") 194 | plt.grid(axis="y") 195 | print("Learnt=\n",mf.full_matrix()) 196 | print("\nRating predictions=\n",L) 197 | print() 198 | print() 199 | # print("Global bias:") 200 | # print(mf.b) 201 | # print() 202 | # print("User bias:") 203 | # print(mf.b_u) 204 | # print() 205 | # print("Item bias:") 206 | # print(mf.b_i) 207 | print("\nFinding Error on test set...\n") 208 | msef=0.0 209 | # for i1 in range(len(i)): 210 | # for i2 in range(len(j)): 211 | # if R1.item(i[i1],j[i2])!=0: 212 | # msef = msef + (R1.item((i[i1],j[i2]))-(L).item((i[i1],j[i2])))**2 213 | # msef = (msef/(len(j)*len(i))) 214 | valid_cmp = ~np.isnan(df_matrix) 215 | msef = np.sum(np.sum(np.multiply(valid_cmp,np.square(R1-L)),axis=None))/(len(j)*len(i)*1.00) 216 | 217 | print("RMSE final=",msef**0.5) -------------------------------------------------------------------------------- /recommender_final_toy_dataset.py: -------------------------------------------------------------------------------- 1 | #Setting up prerequisites 2 | from mf import MF 3 | import pandas as pd 4 | import numpy as np 5 | import math 6 | import re 7 | import sklearn 8 | from scipy.sparse import csr_matrix 9 | import matplotlib.pyplot as plt 10 | import seaborn as sns 11 | from surprise import Reader, Dataset, SVD, evaluate 12 | sns.set_style("darkgrid") 13 | from cvxpy import * 14 | from numpy import matrix 15 | print("Setup Complete\n") 16 | 17 | 18 | # df1 = pd.read_csv('netflix-prize-data/toy_combined_data.txt', header = None, names = ['Cust_Id', 'Rating', 'Date'], usecols = [0,1,2]) 19 | # df1['Rating'] = df1['Rating'].astype(float) 20 | # df1['Date'] = df1['Date'].astype(str) 21 | # df1['Date'] = df1['Date'].map( lambda s : (s[:4])+(s[5:7])+(s[8:])) 22 | # df1['Date'] = df1['Date'].astype(float) 23 | # print('Dataset 1 shape: {}'.format(df1.shape)) 24 | # print('-Dataset examples-') 25 | # print(df1.iloc[::100, :]) 26 | # print(df1['Date'].dtype) 27 | # df = df1 28 | 29 | 30 | 31 | 32 | 33 | 34 | # #Seeing the distribution of ratings given by the users 35 | # print("See Overview of the Data") 36 | # p = df.groupby('Rating')['Rating'].agg(['count']) 37 | # # get movie count 38 | # movie_count = df.isnull().sum()[1] 39 | # # get customer count 40 | # cust_count = df['Cust_Id'].nunique() - movie_count 41 | # # get rating count 42 | # rating_count = df['Cust_Id'].count() - movie_count 43 | # ax = p.plot(kind = 'barh', legend = False, figsize = (15,10)) 44 | # plt.title('Total pool: {:,} Movies, {:,} customers, {:,} ratings given'.format(movie_count, cust_count, rating_count), fontsize=20) 45 | # plt.axis('off') 46 | # for i in range(1,6): 47 | # ax.text(p.iloc[i-1][0]/4, i-1, 'Rated {}: {:.0f}%'.format(i, p.iloc[i-1][0]*100 / p.sum()[0]), color = 'white', weight = 'bold') 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | # #Adding movie IDs to the dataset 59 | # movie_np = [] 60 | # movie_id = 0 61 | # for x in range(df.shape[0]): 62 | # if(np.isnan(df.iloc[x]['Rating'])): 63 | # movie_id = movie_id+1 64 | # movie_np = np.append(movie_np,movie_id) 65 | # #print(movie_np) 66 | # #print(len(movie_np)) 67 | # df['Movie_Id'] = movie_np.astype(int) 68 | # print("Movie IDs extracted from the extra rows given") 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | # # remove the extra Movie ID rows 78 | # df = df[pd.notnull(df['Rating'])] 79 | # df['Cust_Id'] = df['Cust_Id'].astype(int) 80 | # print('-Dataset examples-') 81 | # print(df.iloc[::100, :]) 82 | # print("\n\nThese are the final datatypes of the dataset") 83 | # print(df.dtypes) 84 | 85 | 86 | 87 | 88 | # #Creating Data Matrix 89 | # df_matrix=pd.pivot_table(df,values='Rating',index='Cust_Id',columns='Movie_Id') 90 | # print(df_matrix.shape) 91 | 92 | 93 | 94 | # #Loading the Movie ID- Movie Title Mapping File 95 | # df_title = pd.read_csv('netflix-prize-data/movie_titles.csv', encoding = "ISO-8859-1", header = None, names = ['Movie_Id', 'Year', 'Name']) 96 | # df_title.set_index('Movie_Id', inplace = True) 97 | # print("See some Movie ID- Movie Title Mapping : \n") 98 | # print (df_title.head(8)) 99 | 100 | 101 | 102 | 103 | # print("\n\nData Cleaning Complete.\n See head of the Data Matrix:\n") 104 | # print(df_matrix.head()) 105 | # n_movies = movie_count 106 | # n_customers = cust_count 107 | # print("\nNum of movies =", movie_count) 108 | # print("Num of users =", cust_count) 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | # #Choosing the number of latent attributes 119 | # n_attr= 100*1000000 120 | # #print(type(n_attr),type(n_movies), type(n_customers)) 121 | # Q = Variable((n_attr,n_movies)) 122 | # P = Variable((n_attr, n_customers)) 123 | # acq_data = df_matrix.fillna(0.0) 124 | # print(acq_data.head()) 125 | 126 | 127 | 128 | 129 | 130 | #This cell works on Toy Dataset 131 | #The next cell is for real data 132 | R = np.array([ 133 | [5, 3, 0, 1], 134 | [4, 0, 0, 1], 135 | [1, 1, 0, 5], 136 | [1, 0, 0, 4], 137 | [0, 1, 5, 4], 138 | ]) 139 | R1= np.array([ 140 | [5, 3, 0, 1], 141 | [4, 0, 0, 1], 142 | [1, 1, 0, 5], 143 | [1, 0, 0, 4], 144 | [0, 1, 5, 4], 145 | ]) 146 | #Set the number of values to replace. For example 20%: 147 | prop = int(R.size * 0.2) 148 | #Randomly choose indices of the numpy array: 149 | i = [np.random.choice(range(R.shape[0])) for _ in range(prop)] 150 | j = [np.random.choice(range(R.shape[1])) for _ in range(prop)] 151 | #Change values with 0 152 | R[i,j] = 0 153 | print("Original:\n",R1) 154 | print("Test Set:\n",R) 155 | R=np.rint(R) 156 | from sklearn.metrics import mean_squared_error 157 | mse = mean_squared_error(R, R1) 158 | print("MSE=",mse**0.5) 159 | print("\nTraining ...\n") 160 | mf = MF(R, K=10000, alpha=0.01, beta=0.01, iterations=10000) 161 | training_process = mf.train() 162 | L=np.rint(mf.full_matrix()) 163 | print("Learnt=\n",L) 164 | print("\nFinding Error on test set...\n") 165 | msef=0.0 166 | for i1 in range(len(i)): 167 | for i2 in range(len(j)): 168 | if R1.item(i[i1],j[i2])!=0: 169 | msef = msef + (R1.item((i[i1],j[i2]))-(L).item((i[i1],j[i2])))**2 170 | msef = (msef/(len(j)*len(i))) 171 | print("RMSE f=",msef**0.5) --------------------------------------------------------------------------------