@Override
	protected void buildModel() throws Exception {
		for (int iter = 1; iter <= numIters; iter++) {
			errs = 0;
			loss = 0;

			DenseMatrix PS = new DenseMatrix(numUsers, numFactors);
			DenseMatrix QS = new DenseMatrix(numItems, numFactors);
			DenseMatrix ZS = new DenseMatrix(numUsers, numFactors);

			// ratings
			for (MatrixEntry me : trainMatrix) {
				int u = me.row();
				int j = me.column();
				double ruj = me.get();
				if (ruj <= 0)
					continue;

				double pred = predict(u, j, false);
				double euj = g(pred) - normalize(ruj);

				errs += euj * euj;
				loss += euj * euj;

				for (int f = 0; f < numFactors; f++) {
					double puf = P.get(u, f);
					double qjf = Q.get(j, f);

					PS.add(u, f, gd(pred) * euj * qjf + regU * puf);
					QS.add(j, f, gd(pred) * euj * puf + regI * qjf);

					loss += regU * puf * puf + regI * qjf * qjf;
				}
			}

			// friends
			for (MatrixEntry me : socialMatrix) {
				int u = me.row();
				int v = me.column();
				double tuv = me.get(); // tuv ~ cik in the original paper
				if (tuv <= 0)
					continue;

				double pred = DenseMatrix.rowMult(P, u, Z, v);

				int vminus = inDegrees.get(v); // ~ d-(k)
				int uplus = outDegrees.get(u); // ~ d+(i)
				double weight = Math.sqrt(vminus / (uplus + vminus + 0.0));

				double euv = g(pred) - weight * tuv; // weight * tuv ~ cik*
				loss += regC * euv * euv;

				for (int f = 0; f < numFactors; f++) {
					double puf = P.get(u, f);
					double zvf = Z.get(v, f);

					PS.add(u, f, regC * gd(pred) * euv * zvf);
					ZS.add(v, f, regC * gd(pred) * euv * puf + regZ * zvf);

					loss += regZ * zvf * zvf;
				}
			}

			P = P.add(PS.scale(-lRate));
			Q = Q.add(QS.scale(-lRate));
			Z = Z.add(ZS.scale(-lRate));

			errs *= 0.5;
			loss *= 0.5;

			if (isConverged(iter))
				break;
		}
	}