Deep Learning meets Physics: Restricted Boltzmann Machines Part II | by Artem Oppermann | Towards Data Science

This article is the sequel of the first part where I introduced the theory behind Restricted Boltzmann Machines. This second part consists in a step by step guide through a practical implementation of a Restricted Boltzmann Machine which serves as a Recommender System and can predict whether a user would like a movie or not based on the users taste.
(1) In this article I wont cover the theory behind the steps I make, I will only explain the practical parts. Make sure to renew your theoretical knowledge by reviewing the first part of this series.
(2) The code I present in this article is from my project repository on GitHub. Since I focus only on the implementation of the model I skip some preprocessing steps like, splitting the data into training/test sets and building the input pipeline. These steps can be examined in the repository.
Python 3.6
TensorFlow 1.5 or higher
NumPy 1.11 or higher
We are using the MovieLens 1M Dataset. This set contains 1 million ratings of approximately 4000 movies made by approximately 6000 users. The model will be trained on this dataset and will learn to make predictions whether a user would like a random movie or not. The dataset requires some reprocessing steps. Because an usual Restricted Boltzmann Machine accepts only binary values it is necessary to give ratings 1–2 a value of 0 — hence the user does not like the movie. Accordingly the ratings 3–5 receive a value of 1. The movies that are not rated yet receive a value of -1.
In the next step the transformed original data is divided into two separate training and test datasets. It is necessary two have exactly the same users in both datasets but different movie ratings. Fig. 1 shows a simple example for the partitioning of the original dataset into the training and test data. In this example the first 5 ratings are put into the training set, while the rest is masked with -1 as not rated yet. Accordingly the test set receives the remaining 5 ratings.
During the training time the Restricted Boltzmann Machine learns on the first 5 movie ratings of each user, while during the inference time the model tries to predict the ratings for the last 5 movies. These predicted ratings are then compared with the actual ratings which were put into the test set.
Both datasets are saved in a binary TFRecords format that enables a very efficient data input pipeline.
The model is implemented in an object oriented manner. The Restricted Boltzmann Machine is a class with all necessary operations like training, loss, accuracy, inference etc. inside of it. Some helper functions are outsourced into a separate script.
The constructor sets the kernel initializers for the weights and biases. 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. It can be noticed that the network consists only out of one hidden layer. As a result only one weight matrix is needed.
Giving the binary input v the following function _sample_h(self) obtains the probabilities that a hidden neuron is activated (Eq.1). This is achieved by multiplying the input v by the weight matrix, adding a bias and applying a sigmoidal activation . The obtained probabilities are used to sample from Bernoulli distribution. The sampled values which are either 1.0 or 0.0 are the states of the hidden neurons.
Given the hidden states h we can use these to obtain the probabilities that a visible neuron is active (Eq.2) as well as the corresponding state values. This is implemented in _sample_v(self) .
The first part of the training consists in an operation that is called Gibbs Sampling. Briefly speaking we take an input vector v_0 and use it to predict the values of the hidden state h_0. The hidden state are used on the other hand to predict new input state v. This procedure is repeated k times. This procedure is illustrated in Fig. 2.
Gibbs Sampling is implemented in the code snipped below. The iteration is happening in the while loop body. An important step in the body is Vk=tf.where(tf.less(V,0),V,Vk). This operations makes sure that the ratings in v which are -1 (meaning movies that have not been seen yet) remain -1 for every v_k in every iteration. After k iteration we obtain v_k and corresponding probabilities p(h_k|v_k). Together with v_0 and h_0 these values can be used to compute the gradient matrix in the next training step.
The values obtained in the previous step can be used to compute the gradient matrix and the gradient vectors. The computation of gradients according to Eq. 3 are straight forward. Please notice that the symbols a and b in this equations stand for hidden respectively visible biases in contrasts to different symbols I used in my code.
The only tricky part is that TensorFlow 1.5 does not support outer products. But this issue can be solved by temporary reshaping and applying usual point wise multiplication.
Notice that the computation of the gradients is happening in while loop. This is only due to the fact that the training is happening in mini-batches. Meaning the loop computes for each data sample in the mini-batch the gradients and adds them to the previously defined gradient placeholders. In the end the sum of gradients is divided by the size of the mini-batch.
After the gradients are computed all weights and biases can be updated through gradient ascent according to eq. 4. For this procedure we must create an assign operation in _update_parameter(self).
The whole training operation is computed in optimize(self) method under the name scope “operation”. Below that the more complicated accuracy operation of the training is implemented. Basically this operation subtracts the original input values v_0 from v_k that are obtained during Gibbs Sampling. The subtraction is only happening for v_0 ≥ 0. After that the summed subtractions are divided by the number of all ratings ≥ 0. The accuracy gives the ratio of correctly predicted binary movie ratings during training.
During inference time the method inference(self) receives the input v. That input is one training sample of a specific user that is used to activate the hidden neurons (the underlying features of users movie taste). The hidden neurons are used again to predict a new input v. In the best scenario this new input consists of the recreation of already present ratings as well as ratings of movies that were not rated yet.
The made prediction are compared outside the TensorFlow Session with the according test data for validation purposes.
The Network Graph
To outline the previous steps here is the definition of the main network graph and the start of the session where the training and inference steps are executed.
During the training process we can examine the progress of the accuracy on training and test sets. The accuracy gives the ratio of correctly predicted binary movie ratings. It can be seen that after 6 epochs the model predicts 78% of the time correctly if a user would like a random movie or not.
epoch_nr: 0, batch: 50/188, acc_train: 0.721, acc_test: 0.709epoch_nr: 1, batch: 50/188, acc_train: 0.767, acc_test: 0.764epoch_nr: 2, batch: 50/188, acc_train: 0.772, acc_test: 0.773epoch_nr: 3, batch: 50/188, acc_train: 0.767, acc_test: 0.725epoch_nr: 4, batch: 50/188, acc_train: 0.768, acc_test: 0.717epoch_nr: 5, batch: 50/188, acc_train: 0.772, acc_test: 0.769epoch_nr: 6, batch: 50/188, acc_train: 0.774, acc_test: 0.771epoch_nr: 7, batch: 50/188, acc_train: 0.779, acc_test: 0.780