/
implicit.cpp
136 lines (110 loc) · 4.06 KB
/
implicit.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
#include <RcppArmadillo.h>
#ifdef _OPENMP
#include <omp.h>
#endif
using namespace Rcpp;
using namespace arma;
// [[Rcpp::depends(RcppArmadillo)]]
// [[Rcpp::plugins(openmp)]]
// Presumably no need to compile with these flags to make this parallel anymore
// Sys.setenv("PKG_CXXFLAGS" = "-fopenmp")
// Sys.setenv("PKG_LIBS" = "-fopenmp")
// TODO: Add stopping condition by tolerance
// TODO: Add early stopping
// TODO: Use sparse arma::matrices wherever possible
// [[Rcpp::export]]
void updateImplicitX(arma::mat & X, const arma::mat & Y, const arma::mat & P, const arma::mat & C,
double lambda, int cores) {
int num_users = C.n_rows;
int num_prods = C.n_cols;
int num_factors = Y.n_cols; // or X.n_cols
Rprintf(".");
arma::mat YTY = Y.t() * Y;
arma::mat fact_eye = eye(num_prods, num_prods);
arma::mat lambda_eye = lambda * eye(num_factors, num_factors);
#pragma omp parallel for num_threads(cores)
for (int u = 0; u < C.n_rows; u++) {
arma::mat Cu = diagmat(C.row(u));
arma::mat YTCuIY = Y.t() * (Cu) * Y;
arma::mat YTCupu = Y.t() * (Cu + fact_eye) * P.row(u).t();
arma::mat WuT = YTY + YTCuIY + lambda_eye;
arma::mat xu = solve(WuT, YTCupu);
// Update gradient -- maybe a slow operation in parallel?
X.row(u) = xu.t();
}
}
// [[Rcpp::export]]
void updateImplicitY(const arma::mat & X, arma::mat & Y, const arma::mat & P, const arma::mat & C,
double lambda, int cores) {
int num_users = C.n_rows;
int num_prods = C.n_cols;
int num_factors = Y.n_cols; // or X.n_cols
Rprintf(".");
arma::mat XTX = X.t() * X;
arma::mat fact_eye = eye(num_users, num_users);
arma::mat lambda_eye = lambda * eye(num_factors, num_factors);
#pragma omp parallel for num_threads(cores)
for (int i = 0; i < C.n_cols; i++) {
arma::mat Ci = diagmat(C.col(i));
arma::mat YTCiIY = X.t() * (Ci) * X;
arma::mat YTCipi = X.t() * (Ci + fact_eye) * P.col(i);
arma::mat yu = solve(XTX + YTCiIY + lambda_eye, YTCipi);
// Update gradient
Y.row(i) = yu.t();
}
}
// [[Rcpp::export]]
double implicitCost(const arma::mat & X, const arma::mat & Y, const arma::mat & P, const arma::mat & C, double lambda,
int cores) {
double delta = 0.0;
#pragma omp parallel for num_threads(cores)
for (int u = 0; u < C.n_rows; u++) {
delta += accu(dot(C.row(u), square(P.row(u) - X.row(u) * Y.t())));
}
return (delta +
lambda * (pow(accu(X),2) + pow(accu(Y),2))) / (C.n_rows * C.n_cols);
}
// [[Rcpp::export]]
List implicit(const arma::mat & init_X, const arma::mat & init_Y, const arma::mat & P, const arma::mat & C,
double lambda, int batches,
double epsilon, int checkInterval, int cores = 1) {
//const double epsilon = 0.1;
//const int checkInterval = 1;
arma::mat X(init_X); arma::mat Y(init_Y);
double prevJ;
Rprintf("Initial cost\t%d\n", implicitCost(X, Y, P, C, lambda, cores));
for (int b = 1; b <= batches; b++) {
Rprintf("batch %d", b);
updateImplicitX(X, Y, P, C, lambda, cores);
updateImplicitY(X, Y, P, C, lambda, cores);
double J = implicitCost(X, Y, P, C, lambda, cores);
Rprintf("\tcost\t%f\n", J);
}
List ret;
// Could also add dimension attributes
ret["X"] = X;
ret["Y"] = Y;
return ret;
}
// [[Rcpp::export]]
arma::mat explain_predict(const arma::mat & X, const arma::mat & Y, const arma::mat & P, const arma::mat & C, double lambda, int u) {
int num_users = C.n_rows;
int num_prods = C.n_cols;
int num_factors = Y.n_cols; // or X.n_cols
Rprintf("In explain_predict()");
arma::mat YTY = Y.t() * Y;
arma::mat fact_eye = eye(num_prods, num_prods);
arma::mat lambda_eye = lambda * eye(num_factors, num_factors);
arma::mat Cu = diagmat(C.row(u));
arma::mat YTCuIY = Y.t() * (Cu) * Y;
arma::mat YTCupu = Y.t() * (Cu + fact_eye) * P.row(u).t();
arma::mat WuT = YTY + YTCuIY + lambda_eye;
//arma::mat xu = solve(WuT, YTCupu);
arma::mat sij = Y * solve(WuT, Y.t());
arma::mat p = sij * Cu;
// Numericarma::matrix parma::mat(wrap(p));
// parma::mat.attr("colnames") = C.attr("colnames");
// colnames(p) = colnames(C);
// rownames(p) = colnames(C);
return p;
}