public MfModel Fit(MfProblem trainingData, MfProblem testing, MfTrainerOptions mfTrainerOptions) { var matrix = SparseMatrix.CreateFromMfProblem(trainingData); var W = InitializeColumn(mfTrainerOptions.ApproximationRank, matrix.Rows); var H = InitializeColumn(mfTrainerOptions.ApproximationRank, matrix.Cols); var watcher = new Stopwatch(); watcher.Start(); CoordinateDescentCore(matrix, W, H, testing, mfTrainerOptions); watcher.Stop(); _logger?.WriteLine($"Time taken is {watcher.ElapsedMilliseconds} ms."); return(new MfModel() { M = matrix.Rows, N = matrix.Cols, K = mfTrainerOptions.ApproximationRank, W = W, H = H }); }
// Cyclic Coordinate Descent for Matrix Factorization private void CoordinateDescentCore(SparseMatrix r, float[][] w, float[][] h, MfProblem testProblem, MfTrainerOptions options) { long k = options.ApproximationRank; long numberOfIterations = options.NumberOfIterations; long innerIterations = options.NumberOfInnerIterations; var numberOfThread = options.NumberOfThreads; var lambda = options.LambdaRegularization; var eps = options.Eps; float wTime = 0, hTime = 0, rTime = 0; var doNmf = options.NonNegativeMatrixFactorization; var verbose = options.Verbose; var parallelOptions = new ParallelOptions() { MaxDegreeOfParallelism = numberOfThread }; // Create transpose view of R var rt = r.Transpose(); var stopwatch = new Stopwatch(); // initial value of the regularization term // H is a zero matrix now. for (long feature = 0; feature < k; ++feature) { for (long column = 0; column < r.Cols; ++column) { h[feature][column] = 0; } } var oldWt = new float[r.Rows]; var oldHt = new float[r.Cols]; var u = new float[r.Rows]; var v = new float[r.Cols]; for (long outerIteration = 1; outerIteration <= numberOfIterations; ++outerIteration) { float fundecMax = 0; long earlyStop = 0; for (long tt = 0; tt < k; ++tt) { long t = tt; if (earlyStop >= 5) { break; } stopwatch.Start(); float[] wt = w[t], ht = h[t]; for (int i = 0; i < r.Rows; i++) { oldWt[i] = u[i] = wt[i]; } for (int i = 0; i < r.Cols; i++) { v[i] = ht[i]; oldHt[i] = (outerIteration == 1) ? 0 : v[i]; } // Create Rhat = R - Wt Ht^T if (outerIteration > 1) { UpdateRating(r, wt, ht, true, parallelOptions); UpdateRating(rt, ht, wt, true, parallelOptions); } stopwatch.Stop(); double innerFundecMax = 0; long maxIterations = innerIterations; // if(oiter > 1) maxit *= 2; for (long iteration = 1; iteration <= maxIterations; ++iteration) { // Update H[t] stopwatch.Restart(); var innerFunDecCur = 0f; var innerFun = new ThreadSafe(); Parallel.For(0, r.Cols, parallelOptions, () => 0f, (c, y, z) => { v[c] = RankOneUpdate(r, c, u, (lambda * (r.ColPtr[c + 1] - r.ColPtr[c])), v[c], doNmf, ref z); ; return(z); }, f => { lock (innerFun) { innerFun.AddToTotal(f); } }); stopwatch.Stop(); hTime += stopwatch.ElapsedMilliseconds; // Update W[t] stopwatch.Restart(); Parallel.For(0, rt.Cols, parallelOptions, () => 0f, (c, y, z) => { u[c] = RankOneUpdate(rt, c, v, (lambda * (rt.ColPtr[c + 1] - rt.ColPtr[c])), u[c], doNmf, ref z); return(z); }, f => { lock (innerFun) { innerFun.AddToTotal(f); } }); innerFunDecCur += innerFun.Total; if ((innerFunDecCur < fundecMax * eps)) { if (iteration == 1) { earlyStop += 1; } break; } innerFundecMax = Math.Max(innerFundecMax, innerFunDecCur); // the fundec of the first inner iter of the first rank of the first outer iteration could be too large!! if (!(outerIteration == 1 && t == 0 && iteration == 1)) { fundecMax = Math.Max(fundecMax, innerFunDecCur); } stopwatch.Stop(); wTime += stopwatch.ElapsedMilliseconds; } // Update R and Rt // start = omp_get_wtime(); stopwatch.Restart(); for (int i = 0; i < r.Rows; i++) { wt[i] = u[i]; } for (int i = 0; i < r.Cols; i++) { ht[i] = v[i]; } UpdateRating(r, u, v, false, parallelOptions); UpdateRating(rt, v, u, false, parallelOptions); stopwatch.Stop(); rTime += stopwatch.ElapsedMilliseconds; } if (testProblem != null && verbose) { _logger?.Write("iter {0, 5} time {1, 5} rmse {2, 5}", outerIteration, hTime + wTime + rTime, testProblem.CalculateRmseOneRow(w, h, k)); } } }