<jupyter_start><jupyter_text>Recommendation System with RBM Recommendation system is an algorithm that recommends items by trying to find users that are similar to each other based on their item ratings.  Table of contents- Acquiring the Data- Loading in the Data- The Restricted Boltzmann Machine model- Setting the Model's Parameters- Recommendation  Acquiring the Data The datasets we're going to use were acquired by [GroupLens](http://grouplens.org/datasets/movielens/) and contain movies, users and movie ratings by these users. With the datasets in place, let's now import the necessary libraries. We will be using [Tensorflow](https://www.tensorflow.org/) and [Numpy](http://www.numpy.org/) to model and initialize our Restricted Boltzmann Machine and [Pandas](http://pandas.pydata.org/pandas-docs/stable/) to manipulate our datasets.<jupyter_code>import tensorflow as tf
# Numpy contains helpful functions for efficient mathematical calculations.
import numpy as np
# Dataframe manipulation library.
import pandas as pd
# Graph plotting library.
import matplotlib.pyplot as plt
%matplotlib inline<jupyter_output><empty_output><jupyter_text>Loading in the DataLet's begin by loading in our data with Pandas. The `.dat` files containing our data are similar to CSV files, but instead of using the ',' (comma) character to separate entries, it uses '::' (two colons) characters instead. To let Pandas know that it should separate data points at every '::', we have to specify the `sep='::'` parameter when calling the function.Additionally, we also pass the `header=None` parameter due to the fact that our files don't contain any headers.Let's start with the `movies.dat` file and take a look at its structure:<jupyter_code># Loading in the movies dataset.
movies_df = pd.read_csv('./resources/ml-1m/movies.dat', sep='::', header=None, engine='python')
movies_df.head()<jupyter_output><empty_output><jupyter_text>We can do the same for the `ratings.dat` file:<jupyter_code># Loading in the ratings dataset.
ratings_df = pd.read_csv('./resources/ml-1m/ratings.dat', sep="::", header=None, engine='python')
ratings_df.head()<jupyter_output><empty_output><jupyter_text>So our `movies_df` variable contains a dataframe that stores a movie's unique ID number, title and genres, while our `ratings_df` variable stores a unique User ID number, a movie ID that the user has watched, the user's rating and when the user rated that movie.Let's now rename the columns in these dataframes so we can better convey their data more intuitively:<jupyter_code>movies_df.columns = ['MovieID', 'Title', 'Genres']
ratings_df.columns = ['UserID', 'MovieID', 'Rating', 'Timestamp']<jupyter_output><empty_output><jupyter_text>Here's our final `movies_df`:<jupyter_code>movies_df.head()<jupyter_output><empty_output><jupyter_text>And our final `ratings_df`:<jupyter_code>ratings_df.head()<jupyter_output><empty_output><jupyter_text>The Restricted Boltzmann Machine model The Restricted Boltzmann Machine model has two layers of neurons, one of which is what we call a visible input layer and the other is called a hidden layer. The hidden layer is used to learn features from the information fed through the input layer. For our model, the input is going to contain X neurons, where X is the amount of movies in our dataset. Each of these neurons will possess a normalized rating value varying from 0 to 1 - 0 meaning that a user has not watched the movie and the closer the value is to 1, the more the user likes the movie that neuron's representing. These normalized values, of course, will be extracted and normalized from the ratings dataset.After passing in the input, we train the RBM on it and have the hidden layer learn its features. These features are what we use to reconstruct the input, which in our case, will predict the ratings for movies that the user hasn't watched, which is exactly what we can use to recommend movies!We will now begin to format our dataset to follow the model's expected input.  Formatting the Data First let's see how many movies we have and see if the movie ID's correspond with that value:<jupyter_code>len(movies_df)
movies_df.tail()<jupyter_output><empty_output><jupyter_text>As it is possible to notice, we have 3883 movies, while our ID's vary from 1 to 3952. Due to this, we won't be able to index movies through their ID since we would get memory indexing errors. To amend this, we can create a column that shows what spot in our list that particular movie is in:<jupyter_code>movies_df['List Index'] = movies_df.index
movies_df.head()<jupyter_output><empty_output><jupyter_text>With that, let's merge the ratings dataframe into the movies one so we can have the List Index values in both dataframes. Additionally we're also going to drop the Timestamp, Title and Genres columns since we won't be needing it to make recommendations.<jupyter_code># Merging movies_df with ratings_df by MovieID.
merged_df = movies_df.merge(ratings_df, on='MovieID')
# Dropping unecessary columns.
merged_df = merged_df.drop('Timestamp', axis=1).drop('Title', axis=1).drop('Genres', axis=1)
# Displaying the result.
merged_df.head()<jupyter_output><empty_output><jupyter_text>Let's also group up the users by their user IDs and take a look at one of them.<jupyter_code># Group up by UserID.
user_group = merged_df.groupby('UserID')
user_group.first().head()<jupyter_output><empty_output><jupyter_text>Now, we can start formatting the data into input for the RBM. We're going to store the normalized users ratings into a list of lists called `trX`.<jupyter_code># Amount of users used for training.
amount_of_users = 1000
# Creating the training list.
trX = []
# For each user in the group.
for user_id, cur_user in user_group:
    # Create a temp that stores every movie's rating.
    temp = [0]*len(movies_df)
    # For each movie in cur_user's movie list.
    for num, movie in cur_user.iterrows():
        # Divide the rating by 5 and store it.
        temp[movie['List Index']] = movie['Rating'] / 5.0
    # Now add the list of ratings into the training list.
    trX.append(temp)
    # Check to see if we finished adding in the amount of users for training.
    if amount_of_users == 0:
        break
    amount_of_users -= 1<jupyter_output><empty_output><jupyter_text>Setting the Model's Parameters Next, let's start building the RBM with Tensorflow. We'll begin by first determining the number of hidden layers and then creating placeholder variables for storing the visible layer biases, hidden layer biases and weights that connect the hidden layer with the visible one. We will be arbitrarily setting the amount of hidden layers to 20.<jupyter_code>hidden_units = 20
visible_units = len(movies_df)
vb = tf.placeholder("float", [visible_units])  # Number of unique movies.
hb = tf.placeholder("float", [hidden_units])  # Number of features we're going to learn.
W = tf.placeholder("float", [visible_units, hidden_units])<jupyter_output><empty_output><jupyter_text>We then move on to creating the visible and hidden layer units and setting their activation functions. In this case, we will be using the `tf.sigmoid` and `tf.relu` functions as nonlinear activations since it's what is usually used in RBM's.<jupyter_code># Phase 1: Input Processing.
v0 = tf.placeholder("float", [None, visible_units])
_h0 = tf.nn.sigmoid(tf.matmul(v0, W) + hb)
h0 = tf.nn.relu(tf.sign(_h0 - tf.random_uniform(tf.shape(_h0))))
# Phase 2: Reconstruction.
_v1 = tf.nn.sigmoid(tf.matmul(h0, tf.transpose(W)) + vb) 
v1 = tf.nn.relu(tf.sign(_v1 - tf.random_uniform(tf.shape(_v1))))
h1 = tf.nn.sigmoid(tf.matmul(v1, W) + hb)<jupyter_output><empty_output><jupyter_text>Now we set the RBM training parameters and functions.<jupyter_code># Learning rate.
alpha = 1.0
# Create the gradients.
w_pos_grad = tf.matmul(tf.transpose(v0), h0)
w_neg_grad = tf.matmul(tf.transpose(v1), h1)
# Calculate the Contrastive Divergence.
CD = (w_pos_grad - w_neg_grad) / tf.to_float(tf.shape(v0)[0])
# Create methods to update the weights and biases.
update_w = W + alpha * CD
update_vb = vb + alpha * tf.reduce_mean(v0 - v1, 0)
update_hb = hb + alpha * tf.reduce_mean(h0 - h1, 0)<jupyter_output><empty_output><jupyter_text>Set the error function, which in this case will be the Mean Absolute Error Function.<jupyter_code>err = v0 - v1
err_sum = tf.reduce_mean(err * err)<jupyter_output><empty_output><jupyter_text>We also have to initialize our variables. Thankfully, NumPy has a handy `zeros` function for this.<jupyter_code># Current weight.
cur_w = np.zeros([visible_units, hidden_units], np.float32)
# Current visible unit biases.
cur_vb = np.zeros([visible_units], np.float32)
# Current hidden unit biases.
cur_hb = np.zeros([hidden_units], np.float32)
# Previous weight.
prv_w = np.zeros([visible_units, hidden_units], np.float32)
# Previous visible unit biases.
prv_vb = np.zeros([visible_units], np.float32)
# Previous hidden unit biases.
prv_hb = np.zeros([hidden_units], np.float32)
sess = tf.Session()
sess.run(tf.global_variables_initializer())<jupyter_output><empty_output><jupyter_text>Now we train the RBM with 15 epochs with each epoch using 10 batches with size 100. After training, we print out a graph with the error by epoch.<jupyter_code>epochs = 15
batch_size = 100
errors = []
for i in range(epochs):
    for start, end in zip(range(0, len(trX), batch_size), range(batch_size, len(trX), batch_size)):
        batch = trX[start:end]
        cur_w = sess.run(update_w, feed_dict={v0: batch, W: prv_w, vb: prv_vb, hb: prv_hb})
        cur_vb = sess.run(update_vb, feed_dict={v0: batch, W: prv_w, vb: prv_vb, hb: prv_hb})
        cur_hb = sess.run(update_hb, feed_dict={v0: batch, W: prv_w, vb: prv_vb, hb: prv_hb})
        prv_w = cur_w
        prv_vb = cur_vb
        prv_hb = cur_hb
    errors.append(sess.run(err_sum, feed_dict={v0: trX, W: cur_w, vb: cur_vb, hb: cur_hb}))
    print(errors[-1])
plt.plot(errors)
plt.ylabel('Error')
plt.xlabel('Epoch')
plt.show()<jupyter_output>0.12330387
0.082664765
0.06800283
0.060190666
0.050763827
0.047311626
0.045593128
0.044372823
0.043658838
0.04265177
0.04241857
0.041740485
0.041191548
0.04077561
0.040422566<jupyter_text>Recommendation We can now predict movies that an arbitrarily selected user might like. This can be accomplished by feeding in the user's watched movie preferences into the RBM and then reconstructing the input. The values that the RBM gives us will attempt to estimate the user's preferences for movies that he or she hasn't watched based on the preferences of the users that the RBM was trained on.<jupyter_code># Selecting the input user.
input_user = [trX[75]]
# Feeding in the user and reconstructing the input.
hh0 = tf.nn.sigmoid(tf.matmul(v0, W) + hb)
vv1 = tf.nn.sigmoid(tf.matmul(hh0, tf.transpose(W)) + vb)
feed = sess.run(hh0, feed_dict={v0: input_user, W: prv_w, hb: prv_hb})
rec = sess.run(vv1, feed_dict={hh0: feed, W: prv_w, vb: prv_vb})<jupyter_output><empty_output><jupyter_text>Now we can list the 20 most recommended movies for our mock user by sorting it by their scores given by our model.<jupyter_code>scored_movies_df_75 = movies_df
scored_movies_df_75["Recommendation Score"] = rec[0]
scored_movies_df_75.sort_values(["Recommendation Score"], ascending=False).head(20)<jupyter_output><empty_output><jupyter_text>So, how to recommend the movies that the user has not watched yet? Lets first find the __User ID__ of our mock user:<jupyter_code>merged_df.iloc[75]<jupyter_output><empty_output><jupyter_text>Now, we can find all the movies that our mock user has watched before:<jupyter_code>movies_df_75 = merged_df[merged_df['UserID']==215]
movies_df_75.head()<jupyter_output><empty_output><jupyter_text>In the next cell, we merge all the movies that our mock user has watched with the predicted scores based on his historical data:<jupyter_code># Merging movies_df with ratings_df by MovieID.
merged_df_75 = scored_movies_df_75.merge(movies_df_75, on='MovieID', how='outer')
# Dropping unecessary columns.
merged_df_75 = merged_df_75.drop('List Index_y', axis=1).drop('UserID', axis=1)<jupyter_output><empty_output><jupyter_text>Lets sort it and take a look at the firt 20 rows:<jupyter_code>merged_df_75.sort_values(["Recommendation Score"], ascending=False).head(20)<jupyter_output><empty_output>