for(int side=LEFTSIDE; side<=RIGHTSIDE; side++) {
/* The latent factors for both sides of the graph are kept in memory,
but in separate matrices. This chooses which one matrix has the value
of the vertex in question, and which has neighbors.
*/
HugeDoubleMatrix thisSideMatrix = (side == LEFTSIDE ? leftSideMatrix : rightSideMatrix);
HugeDoubleMatrix otherSideMatrix = (side == LEFTSIDE ? rightSideMatrix : leftSideMatrix);
/* Check if this vertex is active on the given side (left or right) */
if (side == LEFTSIDE && vertex.numOutEdges() == 0) continue;
if (side == RIGHTSIDE && vertex.numInEdges() == 0) continue;
/* Start computing the new factor */
RealMatrix XtX = new BlockRealMatrix(D, D);
RealVector Xty = new ArrayRealVector(D);
try {
double[] neighborLatent = new double[D];
int ne = (side == LEFTSIDE ? vertex.numOutEdges() : vertex.numInEdges());
// Compute XtX and Xty (NOTE: unweighted)
for(int e=0; e < ne; e++) {
ChiEdge<Float> edge = (side == LEFTSIDE ? vertex.outEdge(e) : vertex.inEdge(e));
float observation = edge.getValue();
if (observation < 1.0) throw new RuntimeException("Had invalid observation: " + observation + " on edge " + idTranslate.backward(vertex.getId()) + "->" +
idTranslate.backward(edge.getVertexId()));
otherSideMatrix.getRow(idTranslate.backward(edge.getVertexId()), neighborLatent);
for(int i=0; i < D; i++) {
Xty.setEntry(i, Xty.getEntry(i) + neighborLatent[i] * observation);
for(int j=i; j < D; j++) {
XtX.setEntry(j,i, XtX.getEntry(j, i) + neighborLatent[i] * neighborLatent[j]);
}
}
}
// Symmetrize
for(int i=0; i < D; i++) {
for(int j=i+1; j< D; j++) XtX.setEntry(i,j, XtX.getEntry(j, i));
}
// Diagonal -- add regularization
for(int i=0; i < D; i++) XtX.setEntry(i, i, XtX.getEntry(i, i) + LAMBDA * vertex.numEdges());
// Solve the least-squares optimization using Cholesky Decomposition
RealVector newLatentFactor = new CholeskyDecompositionImpl(XtX).getSolver().solve(Xty);
// Set the new latent factor for this vector
for(int i=0; i < D; i++) {
thisSideMatrix.setValue(idTranslate.backward(vertex.getId()), i, newLatentFactor.getEntry(i));
}
if (context.isLastIteration() && side == RIGHTSIDE) {
/* On the last iteration - compute the RMSE error. But only for
vertices on the right side of the matrix, i.e vectors
that have only in-edges.
*/
if (vertex.numInEdges() > 0) {
// Sanity check
double squaredError = 0;
for(int e=0; e < vertex.numInEdges(); e++) {
// Compute RMSE
ChiEdge<Float> edge = vertex.inEdge(e);
float observation = edge.getValue();
otherSideMatrix.getRow(idTranslate.backward(edge.getVertexId()), neighborLatent);
double prediction = new ArrayRealVector(neighborLatent).dotProduct(newLatentFactor);
squaredError += (prediction - observation) * (prediction - observation);
}
synchronized (this) {