Deep Autoencoders For Collaborative Filtering | Towards Data Science

Collaborative Filtering is a method used by recommender systems to make predictions about the interest of a specific user by collecting taste or preference information from many other users. The technique of Collaborative Filtering has the underlying assumption that if a user A has the same taste or opinion on an issue as the person B, A is more likely to have B’s opinion on a different issue.
In this article, you will learn how to predict the ratings a user would give a movie based on this user’s taste and the taste of other users who watched and rated the same and other movies.
Introduction
Deep Autoencoder
Model Implementation
An Autoencoder is a deep learning neural network architecture that achieves state of the art performance in the area of collaborative filtering. In the first part of the article I will give you a theoretical overview and basic mathematics behind simple Autoencoders and their extension the Deep Autoencoders. In the second part we will dive in the practical stuff and I will show you how to implement this technique in TensorFlow step by step. In this article I will include and comment only the most important parts of the model. The whole model, the input pipeline and the preprocessing can be viewed in the corresponding GitHub repository.
Before we can focus on the Deep Autoencoders we should discuss it’s simpler version. An Autoencoder is an artificial neural network used to learn a representation (encoding) for a set of input data, usually to a achieve dimensionality reduction.
Architecturally, the form of an Autoencoder is a feedforward neural network having an input layer, one hidden layer and an output layer (Fig.1). The output layer has the same number of neurons as the input layer for the purpose of reconstructing it’s own inputs. This makes an Autoencoder a form of unsupervised learning, which means no labelled data are necessary — only a set of input data instead of input-output pairs.
It is useful that an Autoencoder has a smaller hidden layer than the input layer. This effect forces the model to create a compressed representation of the data in the hidden layer by learning correlations in the data.
The transition from the input to the hidden layer is called the encoding step and the transition from the hidden to the output layer is called the decoding step. We can also define these transitions mathematically as a mapping:
The mapping is realized by multiplying the input data vector x with a weight matrix, adding a bias term and applying to the resulting vector a non linear operation σ such as sigmoid, tanh or rectified linear unit.
Coming Soon: Advanced Deep Learning Education for software developers, data analysts, academics and industry insiders
For more details check out: www.deeplearning-academy.com
During the training time the encoder takes a input data sample x and maps it to the so called hidden or latent representation z. Then the decoder maps z to the output vector x’ which is (in the best case scenario) the exact representation of the input data x. Please notice that usually an exact recreation of the input x is not possible.
Having the output x’ the training consists of applying stochastic gradient descent to minimize a predefined loss such as a mean squared error:
The extension of the simple Autoencoder is the Deep Autoencoder (Fig. 2). As can be seen in the Fig. 2 the only difference to it’s simpler counter part is number of hidden layers.
The additional hidden layers enable the Autoencoder to learn mathematically more complex underlying patterns in the data. The first layer of the Deep Autoencoder may learn first-order features in the raw input (such as edges in an image). The second layer may learn second-order features corresponding to patterns in the appearance of first-order features (e.g., in terms of what edges tend to occur together — for example, to form contour or corner detectors). Deeper layers of the Deep Autoencoder tend to learn even higher-order features.
To put everything together: We need additional layers to be able to handle more complex data — such as the data we use in collaborative filtering.
As previously mentioned you will learn to predict the rating a user would give a movie. For that matter we will use the famous MovieLens dataset. MovieLens is a web based recommender system and online community that recommends movies for its users to watch.
More specifically we will use the ml-1m.zip dataset that contains 1,000,209 anonymous ratings of approximately 3,900 movies made by 6,040 MovieLens users. The import file we need is ratings.dat. This file contains 1,000,209 lines all having the following format: user_id::movie_id::rating:time_stamp.
For example the first line in ratings.dat:
1::595::5::978824268  
means that user Nr. 1 gave movie Nr. 595 a five star rating. The time stamp can be ignored because it won’t be used.
The deep learning model we implement needs a specific data structure for the training and testing. This data structure is a UxM-Matrix where U is the number of users and M the number of movies. Each row i ∈ U is a unique user id and each column j ∈ M a unique movie id. A visualization for such a matrix can be seen in Fig.3.
Each entry in this matrix is a rating a user gave a specific movie. An entry of 0 means that the user did not gave this movie any rating. E.g. user Nr.1 gave movie Nr.3 a rating of 4 stars while movie Nr.1 was not rated at all.
Since this tutorial focuses on the implementation of the deep learning model the step of making the User-Movie-Matrix out of ratings.dat file won’t be covered here. For further questions on this topic, I would like to redirect you to my GitHub repository where you can examine the corresponding python script.
Before the model can be implemented and trained another reprocessing step of the data is necessary - dividing the data into training and testing data sets. This step is pretty straight forward. So far we have a User-Movie Matrix where each row is a list of ratings. To obtain the training and testing sets out of this list we must take a subset of the ratings from each row and use them only for training while using the remaining subset only for the testing.
As an example of the described procedure let’s consider a much smaller data set consisting only out of 15 movies. A specific user may have given these movies the following ratings:
Movie Nr. : 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15Rating:     5 0 2 4 0 0 2 1 5  1  0  4  5  1  3
Remember that 0 means the movie was not rated. Now we take a subset consisting out of the first 10 movies as the training set and pretend that the remaining ones were not rated yet:
Movie Nr. : 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15Rating:     5 0 2 4 0 0 2 1 5  0  0  0  0  0  0
Accordingly the last 5 movie ratings of the original data are used as test data, while movies 1–10 are masked as not rated:
Movie Nr. : 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15Rating:     0 0 0 0 0 0 0 0 0  1  0  4  5  1  3
This was just a simple demonstration how the different sets were obtained. In the original MovieLens data set I use for each user only 10 movie ratings for the testing while the rest (vast majority) were used for the training of the model.
The Deep Autoencoder is implemented as a class with all necessary operations like inference, optimization, loss, accuracy etc. inside the class.
In the constructor kernel initializers for the weights and biases are set. In the next step all weights and biases in the network get initialized. The weights are normal distributed with a mean of 0.0 and a variance of 0.02, while the biases are all set to 0.0 in the beginning.
In this particular example the network has three hidden layers each containing 128 neurons. The size of the input layer (and output layer) corresponds to the number of all present movies in the dataset.
Given an input data sample x (a row of the user-movie matrix) a forward pass that computes the network outputs is made. The hidden layers use sigmoid as an activation function. Please note that the last layer does not have either a non linearity nor a bias term.
Having the network predictions we can compute the loss between these predictions and the corresponding labels (network inputs x). To compute the mean of the loss we also need to know the number of labels that are non zero — in other words the number of total ratings of an user in the training set.
The optimization/training step of the network may appear little tricky, let’s discuss it step by step. Given an input x the corresponding outputs are calculated. As you already probably noticed the majority of the values in the input x are zero values because a user most certainly did not watch and rated all of the 5953 movies what are in the data set. As a consequence it is advisable not use the raw predictions of the network directly. Instead we must identify the indices of zero values in the data input x and set the values in the prediction vector that corresponds to these indices to zeros also. This manipulation of the prediction massively reduces the training time of the network giving the network the opportunity to concentrate it’s training effort only on the ratings the user have actually made.
After this step the loss can be calculated as well as the regularization loss (optional). The loss function is minimized by the AdamOptimizer. Please notice that the method returns a root mean squared error (RMSE) instead of a mean squared error (MSE) for better accuracy measurement.
After some epochs of the training phase the neural network has seen all ratings in the training date set of each user multiply times. At this time the model should have learned the underlying hidden patterns in the data and corresponding collaborative movie tastes of the users. Given a user rating training sample x the model predicts an output x’. This vector consists out of the recreation of the input x (as expected ) but now also contains values for the previously zero rating in the input x. Meaning that the model gave yet unrated movies a rating. This ratings corresponds to the taste of the user — the taste the model have recognized and learned from the data.
In order that we can measure the accuracy of the model, both the training and testing data sets are required. Based on the training set a prediction is made. Analogous to the training phase we only consider the output values which correspond to the indices of the non zero values in the test set.
Now we can calculate the root mean squared error loss (RMSE) between the predicted and the actual ratings. The RMSE represents the sample standard deviation of the differences between predicted values and observed values. E.g. a RMSE of 0.5 means that on an average the predicted rating deviates from the actual rating by 0.5 stars.
The final step consists of executing the training process and examine the models performance. At this point I won’t go into the detail of building the data input pipeline, graph, session etc., since these steps are commonly known. Readers who are interested in this topic can view these steps in my GitHub repository.
Here you can observe the training and testing performance for the first 50 epochs. After 50 epochs we get a 0.929 star deviation between the predicted and actual ratings on the test set.
epoch_nr: 0,  train_loss: 1.169, test_loss: 1.020epoch_nr: 10, train_loss: 0.936, test_loss: 0.959epoch_nr: 20, train_loss: 0.889, test_loss: 0.931epoch_nr: 30, train_loss: 0.873, test_loss: 0.923epoch_nr: 40, train_loss: 0.859, test_loss: 0.925epoch_nr: 50, train_loss: 0.844, test_loss: 0.929