Exemple #1
0
        /// <summary>
        /// Ordinal Matrix Factorization.
        /// </summary>
        /// <param name="R_train">The matrix contains training ratings</param>
        /// <param name="R_unknown">The matrix contains ones indicating unknown ratings</param>
        /// <param name="R_scorer">This matrix contains ratings predicted by the scorer on
        /// both the R_train and R_unknown sets</param>
        /// <returns>The predicted ratings on R_unknown</returns>
        #region PredictRatings
        public static string PredictRatings(SparseMatrix R_train, SparseMatrix R_unknown,
                                            SparseMatrix R_scorer, List <double> quantizer, out DataMatrix R_predicted,
                                            out Dictionary <Tuple <int, int>, List <double> > OMFDistributionByUserItem)
        {
            StringBuilder log = new StringBuilder();

            /************************************************************
            *   Parameterization and Initialization
            ************************************************************/
            #region Parameterization and Initialization
            // This matrix stores predictions
            SparseMatrix R_predicted_out = (SparseMatrix)Matrix.Build.Sparse(R_unknown.RowCount, R_unknown.ColumnCount);
            Dictionary <Tuple <int, int>, List <double> > OMFDistributionByUserItem_out =
                new Dictionary <Tuple <int, int>, List <double> >(R_unknown.NonZerosCount);

            // User specified parameters
            double maxEpoch       = Config.OMF.MaxEpoch;
            double learnRate      = Config.OMF.LearnRate;
            double regularization = Config.OMF.Regularization;
            int    intervalCount  = quantizer.Count;
            int    userCount      = R_train.RowCount;

            // Parameters for each user
            Dictionary <int, ParametersOfUser> paramtersByUser = new Dictionary <int, ParametersOfUser>(R_train.RowCount);

            // Compute initial values of t1 and betas
            // that will be used for all users, Eq. 5
            double          t1_initial    = (double)(quantizer[0] + quantizer[1]) / 2;
            Vector <double> betas_initial = Vector.Build.Dense(quantizer.Count - 2);
            for (int i = 1; i <= betas_initial.Count; i++)
            {
                double t_r        = t1_initial;
                double t_r_plus_1 = (quantizer[i] + quantizer[i + 1]) * 0.5f;
                betas_initial[i - 1] = Math.Log(t_r_plus_1 - t_r); // natural base
                t_r = t_r_plus_1;
            }

            // Initialize parameters (t1, betas) for each user
            for (int indexOfUser = 0; indexOfUser < R_train.RowCount; indexOfUser++)
            {
                paramtersByUser[indexOfUser] = new ParametersOfUser(t1_initial, betas_initial);
            }
            #endregion

            /************************************************************
            *   Learn parameters from training data R_train and R_score
            ************************************************************/
            #region Learn parameters from training data R_train and R_score
            // Learn parameters for each user, note that each user has his own model
            Object lockMe = new Object();
            Parallel.ForEach(R_train.EnumerateRowsIndexed(), row =>
            {
                int indexOfUser            = row.Item1;
                SparseVector ratingsOfUser = (SparseVector)row.Item2;

                // Store this user's ratings from R_train and correpsonding ratings from scorer
                List <double> ratingsFromScorer = new List <double>(ratingsOfUser.NonZerosCount);
                List <double> ratingsFromRTrain = new List <double>(ratingsOfUser.NonZerosCount);
                foreach (var element in ratingsOfUser.EnumerateIndexed(Zeros.AllowSkip))
                {
                    int indexOfItem = element.Item1;
                    double rating   = element.Item2;
                    // Ratings need to be added in the same order
                    ratingsFromScorer.Add(R_scorer[indexOfUser, indexOfItem]);
                    ratingsFromRTrain.Add(rating);
                }

                Debug.Assert(ratingsFromScorer.Count == ratingsOfUser.NonZerosCount);
                Debug.Assert(ratingsFromRTrain.Count == ratingsOfUser.NonZerosCount);

                // Parameters for the current user are estimated by
                // maximizing the log likelihood (Eq. 21) using stochastic gradient ascent
                // Eq. 22
                double t1             = paramtersByUser[indexOfUser].t1;
                Vector <double> betas = paramtersByUser[indexOfUser].betas;
                for (int epoch = 0; epoch < maxEpoch; epoch++)
                {
                    for (int i = 0; i < ratingsFromRTrain.Count; i++)
                    {
                        double ratingFromRTrain = ratingsFromRTrain[i];
                        double ratingFromScorer = ratingsFromScorer[i];

                        int r                   = quantizer.IndexOf(ratingFromRTrain);           // r is the interval that the rating falls into
                        double probLE_r         = ComputeProbLE(ratingFromScorer, r, t1, betas); // Eq. 9
                        double probLE_r_minus_1 = ComputeProbLE(ratingFromScorer, r - 1, t1, betas);
                        double probE_r          = probLE_r - probLE_r_minus_1;                   // Eq. 10

                        // Compute derivatives/gradients
                        double derivativeOft1 = learnRate / probE_r * (probLE_r * (1 - probLE_r) * DerivativeOfBeta(r, 0, t1)
                                                                       - probLE_r_minus_1 * (1 - probLE_r_minus_1) * DerivativeOfBeta(r - 1, 0, t1)
                                                                       - Config.OMF.Regularization * t1);

                        Vector <double> derivativesOfbetas = Vector.Build.Dense(betas.Count);
                        for (int k = 0; k < betas.Count; k++)
                        {
                            derivativesOfbetas[k] = learnRate / probE_r * (probLE_r * (1 - probLE_r) *
                                                                           DerivativeOfBeta(r, k + 1, betas[k]) - probLE_r_minus_1 * (1 - probLE_r_minus_1) *
                                                                           DerivativeOfBeta(r - 1, k + 1, betas[k]) - regularization * betas[k]);
                        }

                        // Update parameters
                        t1    += derivativeOft1;
                        betas += derivativesOfbetas;
                    }
                }

                // Store the leanred paramemters
                lock (lockMe)
                {
                    paramtersByUser[indexOfUser].t1    = t1;
                    paramtersByUser[indexOfUser].betas = betas;
                }

                log.AppendLine(Utils.PrintEpoch("user/total", indexOfUser, userCount, "Learned params",
                                                String.Format("t1={0:0.000},betas={1}", t1, string.Concat(
                                                                  betas.Select(i => string.Format("{0:0.00},", i))))));
            });
            #endregion

            /************************************************************
            *   Make predictions using learned parameters
            ************************************************************/
            #region Make predictions using learned parameters
            lockMe = new Object();
            Parallel.ForEach(R_unknown.EnumerateIndexed(Zeros.AllowSkip), element =>
            {
                int indexOfUser = element.Item1;
                int indexOfItem = element.Item2;
                // This is the ordinal distribution of the current user
                // given the internal score by MF
                // e.g. what is the probability of each rating 1-5
                List <double> probabilitiesByInterval = new List <double>(quantizer.Count);
                double scoreFromScorer = R_scorer[indexOfUser, indexOfItem];
                double pre             = ComputeProbLE(scoreFromScorer, 0, paramtersByUser[indexOfUser].t1, paramtersByUser[indexOfUser].betas);
                probabilitiesByInterval.Add(pre);
                for (int i = 1; i < intervalCount; i++)
                {
                    double pro = ComputeProbLE(scoreFromScorer, i, paramtersByUser[indexOfUser].t1, paramtersByUser[indexOfUser].betas);
                    probabilitiesByInterval.Add(pro - pre);
                    pre = pro;
                }

                // Compute smoothed expectation for RMSE metric
                double expectationRating = 0.0;
                for (int i = 0; i < probabilitiesByInterval.Count; i++)
                {
                    expectationRating += (i + 1) * probabilitiesByInterval[i];
                }

                // TODO: Compute most likely value for MAE metric

                lock (lockMe)
                {
                    // Keep OMF Distribution
                    OMFDistributionByUserItem_out[new Tuple <int, int>(indexOfUser, indexOfItem)] = probabilitiesByInterval;
                    // Keep the numerical prediction
                    R_predicted_out[indexOfUser, indexOfItem] = expectationRating;
                }
            });
            #endregion


            /************************************************************
            *   Generate OMF distributions for R_train as well
            ************************************************************/
            #region Generate OMF distributions for R_train as well
            lockMe = new Object();
            Parallel.ForEach(R_train.EnumerateIndexed(Zeros.AllowSkip), element =>
            {
                int indexOfUser = element.Item1;
                int indexOfItem = element.Item2;;
                // This is the ordinal distribution of the current user
                // given the internal score by MF
                // e.g. what is the probability of each rating 1-5
                List <double> probabilitiesByInterval = new List <double>(quantizer.Count);
                double scoreFromScorer = R_scorer[indexOfUser, indexOfItem];
                double pre             = ComputeProbLE(scoreFromScorer, 0, paramtersByUser[indexOfUser].t1, paramtersByUser[indexOfUser].betas);
                probabilitiesByInterval.Add(pre);
                for (int i = 1; i < intervalCount; i++)
                {
                    double pro = ComputeProbLE(scoreFromScorer, i, paramtersByUser[indexOfUser].t1, paramtersByUser[indexOfUser].betas);
                    probabilitiesByInterval.Add(pro - pre);
                    pre = pro;
                }

                lock (lockMe)
                {
                    // Keep OMF Distribution
                    OMFDistributionByUserItem_out[new Tuple <int, int>(indexOfUser, indexOfItem)] = probabilitiesByInterval;
                }
            });
            #endregion

            R_predicted = new DataMatrix(R_predicted_out);
            OMFDistributionByUserItem = OMFDistributionByUserItem_out;

            return(log.ToString());
        }
Exemple #2
0
        /// <summary>
        /// Ordinal Matrix Factorization.
        /// </summary>
        /// <param name="R_train">The matrix contains training ratings</param>
        /// <param name="R_unknown">The matrix contains ones indicating unknown ratings</param>
        /// <param name="R_scorer">This matrix contains ratings predicted by the scorer on
        /// both the R_train and R_unknown sets</param>
        /// <returns>The predicted ratings on R_unknown</returns>
        #region PredictRatings
        public static string PredictRatings(SparseMatrix R_train, SparseMatrix R_unknown,
 SparseMatrix R_scorer, List<double> quantizer, out DataMatrix R_predicted,
            out Dictionary<Tuple<int, int>, List<double>> OMFDistributionByUserItem)
        {
            StringBuilder log = new StringBuilder();
            /************************************************************
             *   Parameterization and Initialization
            ************************************************************/
            #region Parameterization and Initialization
            // This matrix stores predictions
            SparseMatrix R_predicted_out = (SparseMatrix)Matrix.Build.Sparse(R_unknown.RowCount, R_unknown.ColumnCount);
            Dictionary<Tuple<int, int>, List<double>> OMFDistributionByUserItem_out =
                new Dictionary<Tuple<int, int>, List<double>>(R_unknown.NonZerosCount);

            // User specified parameters
            double maxEpoch = Config.OMF.MaxEpoch;
            double learnRate = Config.OMF.LearnRate;
            double regularization = Config.OMF.Regularization;
            int intervalCount = quantizer.Count;
            int userCount = R_train.RowCount;

            // Parameters for each user
            Dictionary<int, ParametersOfUser> paramtersByUser = new Dictionary<int, ParametersOfUser>(R_train.RowCount);

            // Compute initial values of t1 and betas 
            // that will be used for all users, Eq. 5
            double t1_initial = (double)(quantizer[0] + quantizer[1]) / 2;
            Vector<double> betas_initial = Vector.Build.Dense(quantizer.Count - 2);
            for (int i = 1; i <= betas_initial.Count; i++)
            {
                double t_r = t1_initial;
                double t_r_plus_1 = (quantizer[i] + quantizer[i + 1]) * 0.5f;
                betas_initial[i - 1] = Math.Log(t_r_plus_1 - t_r); // natural base
                t_r = t_r_plus_1;
            }

            // Initialize parameters (t1, betas) for each user
            for (int indexOfUser = 0; indexOfUser < R_train.RowCount; indexOfUser++)
            {
                paramtersByUser[indexOfUser] = new ParametersOfUser(t1_initial, betas_initial);
            }
            #endregion

            /************************************************************
             *   Learn parameters from training data R_train and R_score
            ************************************************************/
            #region Learn parameters from training data R_train and R_score
            // Learn parameters for each user, note that each user has his own model
            Object lockMe = new Object();
            Parallel.ForEach(R_train.EnumerateRowsIndexed(), row =>
            {
                int indexOfUser = row.Item1;
                SparseVector ratingsOfUser = (SparseVector)row.Item2;

                // Store this user's ratings from R_train and correpsonding ratings from scorer
                List<double> ratingsFromScorer = new List<double>(ratingsOfUser.NonZerosCount);
                List<double> ratingsFromRTrain = new List<double>(ratingsOfUser.NonZerosCount);
                foreach (var element in ratingsOfUser.EnumerateIndexed(Zeros.AllowSkip))
                {
                    int indexOfItem = element.Item1;
                    double rating = element.Item2;
                    // Ratings need to be added in the same order
                    ratingsFromScorer.Add(R_scorer[indexOfUser, indexOfItem]);
                    ratingsFromRTrain.Add(rating);
                }

                Debug.Assert(ratingsFromScorer.Count == ratingsOfUser.NonZerosCount);
                Debug.Assert(ratingsFromRTrain.Count == ratingsOfUser.NonZerosCount);

                // Parameters for the current user are estimated by
                // maximizing the log likelihood (Eq. 21) using stochastic gradient ascent
                // Eq. 22
                double t1 = paramtersByUser[indexOfUser].t1;
                Vector<double> betas = paramtersByUser[indexOfUser].betas;
                for (int epoch = 0; epoch < maxEpoch; epoch++)
                {
                    for (int i = 0; i < ratingsFromRTrain.Count; i++)
                    {
                        double ratingFromRTrain = ratingsFromRTrain[i];
                        double ratingFromScorer = ratingsFromScorer[i];

                        int r = quantizer.IndexOf(ratingFromRTrain);    // r is the interval that the rating falls into
                        double probLE_r = ComputeProbLE(ratingFromScorer, r, t1, betas);   // Eq. 9
                        double probLE_r_minus_1 = ComputeProbLE(ratingFromScorer, r - 1, t1, betas);
                        double probE_r = probLE_r - probLE_r_minus_1;    // Eq. 10

                        // Compute derivatives/gradients
                        double derivativeOft1 = learnRate / probE_r * (probLE_r * (1 - probLE_r) * DerivativeOfBeta(r, 0, t1)
                                - probLE_r_minus_1 * (1 - probLE_r_minus_1) * DerivativeOfBeta(r - 1, 0, t1)
                                - Config.OMF.Regularization * t1);

                        Vector<double> derivativesOfbetas = Vector.Build.Dense(betas.Count);
                        for (int k = 0; k < betas.Count; k++)
                        {
                            derivativesOfbetas[k] = learnRate / probE_r * (probLE_r * (1 - probLE_r) *
                                    DerivativeOfBeta(r, k + 1, betas[k]) - probLE_r_minus_1 * (1 - probLE_r_minus_1) *
                                    DerivativeOfBeta(r - 1, k + 1, betas[k]) - regularization * betas[k]);
                        }

                        // Update parameters
                        t1 += derivativeOft1;
                        betas += derivativesOfbetas;
                    }
                }

                // Store the leanred paramemters
                lock (lockMe)
                {
                    paramtersByUser[indexOfUser].t1 = t1;
                    paramtersByUser[indexOfUser].betas = betas;
                }

                log.AppendLine(Utils.PrintEpoch("user/total", indexOfUser, userCount, "Learned params",
                    String.Format("t1={0:0.000},betas={1}", t1, string.Concat(
                    betas.Select(i => string.Format("{0:0.00},", i))))));
            });
            #endregion

            /************************************************************
             *   Make predictions using learned parameters
            ************************************************************/
            #region Make predictions using learned parameters
            lockMe = new Object();
            Parallel.ForEach(R_unknown.EnumerateIndexed(Zeros.AllowSkip), element =>
            {
                int indexOfUser = element.Item1;
                int indexOfItem = element.Item2;
                // This is the ordinal distribution of the current user
                // given the internal score by MF
                // e.g. what is the probability of each rating 1-5
                List<double> probabilitiesByInterval = new List<double>(quantizer.Count);
                double scoreFromScorer = R_scorer[indexOfUser, indexOfItem];
                double pre = ComputeProbLE(scoreFromScorer, 0, paramtersByUser[indexOfUser].t1, paramtersByUser[indexOfUser].betas);
                probabilitiesByInterval.Add( pre);
                for (int i = 1; i < intervalCount; i++)
                {
                    double pro = ComputeProbLE(scoreFromScorer, i, paramtersByUser[indexOfUser].t1, paramtersByUser[indexOfUser].betas);
                    probabilitiesByInterval.Add( pro - pre);
                    pre = pro;
                }

                // Compute smoothed expectation for RMSE metric
                double expectationRating = 0.0;
                for (int i = 0; i < probabilitiesByInterval.Count; i++)
                {
                    expectationRating += (i + 1) * probabilitiesByInterval[i];
                }

                // TODO: Compute most likely value for MAE metric

                lock (lockMe)
                {
                    // Keep OMF Distribution
                    OMFDistributionByUserItem_out[new Tuple<int, int>(indexOfUser, indexOfItem)] = probabilitiesByInterval;
                    // Keep the numerical prediction
                    R_predicted_out[indexOfUser, indexOfItem] = expectationRating;
                }
            });
            #endregion


            /************************************************************
             *   Generate OMF distributions for R_train as well
            ************************************************************/
            #region Generate OMF distributions for R_train as well
            lockMe = new Object();
            Parallel.ForEach(R_train.EnumerateIndexed(Zeros.AllowSkip), element =>
            {
                int indexOfUser = element.Item1;
                int indexOfItem = element.Item2; ;
                // This is the ordinal distribution of the current user
                // given the internal score by MF
                // e.g. what is the probability of each rating 1-5
                List<double> probabilitiesByInterval = new List<double>(quantizer.Count);
                double scoreFromScorer = R_scorer[indexOfUser, indexOfItem];
                double pre = ComputeProbLE(scoreFromScorer, 0, paramtersByUser[indexOfUser].t1, paramtersByUser[indexOfUser].betas);
                probabilitiesByInterval.Add(pre);
                for (int i = 1; i < intervalCount; i++)
                {
                    double pro = ComputeProbLE(scoreFromScorer, i, paramtersByUser[indexOfUser].t1, paramtersByUser[indexOfUser].betas);
                    probabilitiesByInterval.Add(pro - pre);
                    pre = pro;
                }

                lock (lockMe)
                {
                    // Keep OMF Distribution
                    OMFDistributionByUserItem_out[new Tuple<int, int>(indexOfUser, indexOfItem)] = probabilitiesByInterval;
                }
            });
            #endregion

            R_predicted = new DataMatrix(R_predicted_out);
            OMFDistributionByUserItem = OMFDistributionByUserItem_out;

            return log.ToString();
        }