コード例 #1
0
ファイル: OMF.cs プロジェクト: lawrencewu/RecSys
        /// <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();
        }