<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
Step1:  Out-of-matrix prediction using synthetic data
In this notebook we illustrate how to do inference using sideinfo for rows, columns and both.  
Synthetic data generation
Step2:  Train the model
Step3:  Make a PredictSession
Step4:  Out-of-matrix prediction using side-info for the rows
Step5:  Out-of-matrix prediction using side-info for the columns
Step6:  Out-of-matrix prediction using side-info for rows and columns
<ASSISTANT_TASK:>
Python Code:
import logging
logging.basicConfig(level = logging.INFO)
import smurff
import numpy as np
import scipy.sparse as sp
def gen_matrix(shape, num_latent, density = 1.0 ):
    
    Generate a  matrix by multipling two factors.
    Sparsify if asked.
    
    X = np.random.normal(size=(shape[0],num_latent))
    W = np.random.normal(size=(shape[1],num_latent))
    Y = np.dot(X, W.transpose()) + np.random.normal(size=shape)
    if density < 1.0:
        Y = sp.coo_matrix(Y)
        _, Y = smurff.make_train_test(Y, density)
    return Y, X ,W
Explanation: Out-of-matrix prediction using synthetic data
In this notebook we illustrate how to do inference using sideinfo for rows, columns and both.  
Synthetic data generation
End of explanation
Y, X, W = gen_matrix((20, 30), 2, 0.8)
Ytrain, Ytest = smurff.make_train_test(Y, 0.1)
##### train the model
# using X and W as sideinfo
session = smurff.MacauSession(
                       Ytrain     = Ytrain,
                       Ytest      = Ytest,
                       side_info  = [X, W],
                       direct     = True,
                       num_latent = 4,
                       burnin     = 40,
                       nsamples   = 100,
                       save_freq  = 1,
                       save_name  = "syn-out-matrix-pred.hdf5",
                       verbose    = 0,)
predictions = session.run()
Explanation: Train the model
End of explanation
# predictor = smurff.PredictSession.fromRootFile("syn-out-matrix-pred/root.ini")
predictor = session.makePredictSession()
print(predictor)
Explanation: Make a PredictSession
End of explanation
# predict with a single feature-vector and for one column with PredictSession.predict
# return type: smurff.Prediction
p = predictor.predict((X[0],0))
print(p)
# predict with many feature-vectors in a np.ndarray and for all columns with PredictSession.predict
# return type: numpy.ndarray
p = predictor.predict((X,Ellipsis))
print(p)
# predict with many feature-vectors in a np.ndarray and for a single columns with PredictSession.predict
# return type: numpy.ndarray
p = predictor.predict((X[1:5],10))
print(p)
Explanation: Out-of-matrix prediction using side-info for the rows
End of explanation
# predict with a single feature-vector and for one row with PredictSession.predict
p = predictor.predict((0, W[0]))
print(p)
# predict with many feature-vectors in a np.ndarray and for all rows with PredictSession.predict
# return type: numpy.ndarray
p = predictor.predict((None, W))
print(p)
# predict with many feature-vectors in a np.ndarray and for a single row with PredictSession.predict
# return type: numpy.ndarray
p = predictor.predict((10,W[1:5]))
print(p)
Explanation: Out-of-matrix prediction using side-info for the columns
End of explanation
# predict with a single feature-vector for the rows, and for the columns
p = predictor.predict((X[0], W[0]))
print(p)
# predict with many feature-vectors in a np.ndarray  PredictSession.predict
# return type: numpy.ndarray
p = predictor.predict((X, W))
print(p)
# predict with many feature-vectors in a np.ndarray and for a single row with PredictSession.predict
# return type: numpy.ndarray
p = predictor.predict((X[1:4],W[1:5]))
print(p)
Explanation: Out-of-matrix prediction using side-info for rows and columns
End of explanation
<END_TASK>