List<FeatureInfo> featureInfo = new ArrayList<FeatureInfo>(featureCount);
// Use scratch vectors for each feature for better cache locality
// Per-feature vectors are strided in the output matrices
Vector uvec = Vector.createLength(userCount);
Vector ivec = Vector.createLength(itemCount);
for (int f = 0; f < featureCount; f++) {
logger.debug("Training feature {}", f);
StopWatch timer = new StopWatch();
timer.start();
uvec.fill(initialValue);
ivec.fill(initialValue);
FeatureInfo.Builder fib = new FeatureInfo.Builder(f);
trainFeature(f, estimates, uvec, ivec, fib);
summarizeFeature(uvec, ivec, fib);
featureInfo.add(fib.build());
// Update each rating's cached value to accommodate the feature values.
estimates.update(uvec, ivec);
// And store the data into the matrix
userFeatures.setColumn(f, uvec);
assert Math.abs(userFeatures.getColumnView(f).elementSum() - uvec.elementSum()) < 1.0e-4 : "user column sum matches";
itemFeatures.setColumn(f, ivec);
assert Math.abs(itemFeatures.getColumnView(f).elementSum() - ivec.elementSum()) < 1.0e-4 : "item column sum matches";
timer.stop();
logger.info("Finished feature {} in {}", f, timer);
}