Nonnegative CP Decomposition using Multiplicative Updates

The function cp_nmu computes a nonnegative CP factorization using multiplicative updates. It does this using Lee & Seung multiplicative updates for nonnegative matrix/tensor factorization with a least squares objective function. The input X can be a tensor, sptensor, ktensor, or ttensor. The output CP model is a ktensor.

CP-NMU is a simple method for nonnegative CP decomposition and is not guaranteed to converge to a stationary point. Use CP-OPT with nonnegativity constraints for a more robust method.

Additionally, we have only implemented here the method for the least squares objective function. Use CP-APR for KL Divergence.

The CP-NMU method (for matrices) is described in:

Contents

Set up a sample problem

We create a nonnegative tensor with a known solution and add a small amount of noise.

rng('default') % For reproducibility
sz = [30 25 20];
R = 3;
A = cell(3,1);
for n = 1:3
    A{n} = rand(sz(n), R);
end
lambda = rand(R,1);
S = ktensor(lambda, A);
X = full(S) + 0.01 * tensor(rand(sz));
M_true = S;

Run CP-NMU with default options

The default is random initialization and 500 iterations.

NOTE The random initialization uses random values in [0.1,1.1] to avoid zeros, which cause problems with the multiplicative updates.

rng('default') % For reproducibility
M = cp_nmu(X, R);
score_val = score(M, M_true);
fprintf('Score (run with default options): %.4f\n', score_val);
Nonnegative CP:
  tol = 0.0001,   maxiters = 500
  init = random
 Iter  1: fit = 9.103725e-01 fitdelta = 9.1e-01
 Iter  2: fit = 9.403600e-01 fitdelta = 3.0e-02
 Iter  3: fit = 9.501213e-01 fitdelta = 9.8e-03
 Iter  4: fit = 9.570217e-01 fitdelta = 6.9e-03
 Iter  5: fit = 9.619077e-01 fitdelta = 4.9e-03
 Iter  6: fit = 9.654248e-01 fitdelta = 3.5e-03
 Iter  7: fit = 9.680202e-01 fitdelta = 2.6e-03
 Iter  8: fit = 9.699901e-01 fitdelta = 2.0e-03
 Iter  9: fit = 9.715279e-01 fitdelta = 1.5e-03
 Iter 10: fit = 9.727606e-01 fitdelta = 1.2e-03
 Iter 11: fit = 9.737731e-01 fitdelta = 1.0e-03
 Iter 12: fit = 9.746231e-01 fitdelta = 8.5e-04
 Iter 13: fit = 9.753504e-01 fitdelta = 7.3e-04
 Iter 14: fit = 9.759834e-01 fitdelta = 6.3e-04
 Iter 15: fit = 9.765422e-01 fitdelta = 5.6e-04
 Iter 16: fit = 9.770416e-01 fitdelta = 5.0e-04
 Iter 17: fit = 9.774924e-01 fitdelta = 4.5e-04
 Iter 18: fit = 9.779027e-01 fitdelta = 4.1e-04
 Iter 19: fit = 9.782789e-01 fitdelta = 3.8e-04
 Iter 20: fit = 9.786255e-01 fitdelta = 3.5e-04
 Iter 21: fit = 9.789464e-01 fitdelta = 3.2e-04
 Iter 22: fit = 9.792445e-01 fitdelta = 3.0e-04
 Iter 23: fit = 9.795223e-01 fitdelta = 2.8e-04
 Iter 24: fit = 9.797818e-01 fitdelta = 2.6e-04
 Iter 25: fit = 9.800247e-01 fitdelta = 2.4e-04
 Iter 26: fit = 9.802525e-01 fitdelta = 2.3e-04
 Iter 27: fit = 9.804663e-01 fitdelta = 2.1e-04
 Iter 28: fit = 9.806673e-01 fitdelta = 2.0e-04
 Iter 29: fit = 9.808565e-01 fitdelta = 1.9e-04
 Iter 30: fit = 9.810347e-01 fitdelta = 1.8e-04
 Iter 31: fit = 9.812027e-01 fitdelta = 1.7e-04
 Iter 32: fit = 9.813613e-01 fitdelta = 1.6e-04
 Iter 33: fit = 9.815111e-01 fitdelta = 1.5e-04
 Iter 34: fit = 9.816527e-01 fitdelta = 1.4e-04
 Iter 35: fit = 9.817867e-01 fitdelta = 1.3e-04
 Iter 36: fit = 9.819135e-01 fitdelta = 1.3e-04
 Iter 37: fit = 9.820337e-01 fitdelta = 1.2e-04
 Iter 38: fit = 9.821477e-01 fitdelta = 1.1e-04
 Iter 39: fit = 9.822559e-01 fitdelta = 1.1e-04
 Iter 40: fit = 9.823586e-01 fitdelta = 1.0e-04
 Iter 41: fit = 9.824563e-01 fitdelta = 9.8e-05
 Final fit = 9.824563e-01 
Score (run with default options): 0.7675

Run with custom initialization

You can provide a cell array as the initial guess.

rng('default') % For reproducibility
Uinit = cell(3,1);
for n = 1:3
    Uinit{n} = rand(sz(n), R);
end
M1 = cp_nmu(X, R, 'init', Uinit, 'maxiters', 100);
score_val = score(M1, M_true);
fprintf('Score (run with custom init): %.4f\n', score_val);
Nonnegative CP:
  tol = 0.0001,   maxiters = 100
  init = cell
 Iter  1: fit = 9.141275e-01 fitdelta = 9.1e-01
 Iter  2: fit = 9.508846e-01 fitdelta = 3.7e-02
 Iter  3: fit = 9.585137e-01 fitdelta = 7.6e-03
 Iter  4: fit = 9.638050e-01 fitdelta = 5.3e-03
 Iter  5: fit = 9.676426e-01 fitdelta = 3.8e-03
 Iter  6: fit = 9.704992e-01 fitdelta = 2.9e-03
 Iter  7: fit = 9.726733e-01 fitdelta = 2.2e-03
 Iter  8: fit = 9.743614e-01 fitdelta = 1.7e-03
 Iter  9: fit = 9.756968e-01 fitdelta = 1.3e-03
 Iter 10: fit = 9.767722e-01 fitdelta = 1.1e-03
 Iter 11: fit = 9.776529e-01 fitdelta = 8.8e-04
 Iter 12: fit = 9.783857e-01 fitdelta = 7.3e-04
 Iter 13: fit = 9.790043e-01 fitdelta = 6.2e-04
 Iter 14: fit = 9.795338e-01 fitdelta = 5.3e-04
 Iter 15: fit = 9.799925e-01 fitdelta = 4.6e-04
 Iter 16: fit = 9.803942e-01 fitdelta = 4.0e-04
 Iter 17: fit = 9.807494e-01 fitdelta = 3.6e-04
 Iter 18: fit = 9.810662e-01 fitdelta = 3.2e-04
 Iter 19: fit = 9.813507e-01 fitdelta = 2.8e-04
 Iter 20: fit = 9.816079e-01 fitdelta = 2.6e-04
 Iter 21: fit = 9.818417e-01 fitdelta = 2.3e-04
 Iter 22: fit = 9.820553e-01 fitdelta = 2.1e-04
 Iter 23: fit = 9.822513e-01 fitdelta = 2.0e-04
 Iter 24: fit = 9.824318e-01 fitdelta = 1.8e-04
 Iter 25: fit = 9.825986e-01 fitdelta = 1.7e-04
 Iter 26: fit = 9.827532e-01 fitdelta = 1.5e-04
 Iter 27: fit = 9.828970e-01 fitdelta = 1.4e-04
 Iter 28: fit = 9.830309e-01 fitdelta = 1.3e-04
 Iter 29: fit = 9.831559e-01 fitdelta = 1.3e-04
 Iter 30: fit = 9.832730e-01 fitdelta = 1.2e-04
 Iter 31: fit = 9.833828e-01 fitdelta = 1.1e-04
 Iter 32: fit = 9.834859e-01 fitdelta = 1.0e-04
 Iter 33: fit = 9.835830e-01 fitdelta = 9.7e-05
 Final fit = 9.835830e-01 
Score (run with custom init): 0.7964

Run with ktensor initialization

Alternatively, you can pass in a ktensor as the initial guess. Here, I'm using the same factors as the custom initialization and the result will be the same.

kt_init = ktensor(Uinit);
M2 = cp_nmu(X, R, 'init', kt_init, 'maxiters', 100);
score_val = score(M2, M_true);
fprintf('Score (run with ktensor init): %.4f\n', score_val);

fprintf('Solutions equal? %d\n', isequal(M1, M2));
Nonnegative CP:
  tol = 0.0001,   maxiters = 100
  init = ktensor
 Iter  1: fit = 9.141275e-01 fitdelta = 9.1e-01
 Iter  2: fit = 9.508846e-01 fitdelta = 3.7e-02
 Iter  3: fit = 9.585137e-01 fitdelta = 7.6e-03
 Iter  4: fit = 9.638050e-01 fitdelta = 5.3e-03
 Iter  5: fit = 9.676426e-01 fitdelta = 3.8e-03
 Iter  6: fit = 9.704992e-01 fitdelta = 2.9e-03
 Iter  7: fit = 9.726733e-01 fitdelta = 2.2e-03
 Iter  8: fit = 9.743614e-01 fitdelta = 1.7e-03
 Iter  9: fit = 9.756968e-01 fitdelta = 1.3e-03
 Iter 10: fit = 9.767722e-01 fitdelta = 1.1e-03
 Iter 11: fit = 9.776529e-01 fitdelta = 8.8e-04
 Iter 12: fit = 9.783857e-01 fitdelta = 7.3e-04
 Iter 13: fit = 9.790043e-01 fitdelta = 6.2e-04
 Iter 14: fit = 9.795338e-01 fitdelta = 5.3e-04
 Iter 15: fit = 9.799925e-01 fitdelta = 4.6e-04
 Iter 16: fit = 9.803942e-01 fitdelta = 4.0e-04
 Iter 17: fit = 9.807494e-01 fitdelta = 3.6e-04
 Iter 18: fit = 9.810662e-01 fitdelta = 3.2e-04
 Iter 19: fit = 9.813507e-01 fitdelta = 2.8e-04
 Iter 20: fit = 9.816079e-01 fitdelta = 2.6e-04
 Iter 21: fit = 9.818417e-01 fitdelta = 2.3e-04
 Iter 22: fit = 9.820553e-01 fitdelta = 2.1e-04
 Iter 23: fit = 9.822513e-01 fitdelta = 2.0e-04
 Iter 24: fit = 9.824318e-01 fitdelta = 1.8e-04
 Iter 25: fit = 9.825986e-01 fitdelta = 1.7e-04
 Iter 26: fit = 9.827532e-01 fitdelta = 1.5e-04
 Iter 27: fit = 9.828970e-01 fitdelta = 1.4e-04
 Iter 28: fit = 9.830309e-01 fitdelta = 1.3e-04
 Iter 29: fit = 9.831559e-01 fitdelta = 1.3e-04
 Iter 30: fit = 9.832730e-01 fitdelta = 1.2e-04
 Iter 31: fit = 9.833828e-01 fitdelta = 1.1e-04
 Iter 32: fit = 9.834859e-01 fitdelta = 1.0e-04
 Iter 33: fit = 9.835830e-01 fitdelta = 9.7e-05
 Final fit = 9.835830e-01 
Score (run with ktensor init): 0.7964
Solutions equal? 1

Trace option

You can enable tracing to get fit and time per iteration.

rng('default') % For reproducibility
[M, ~, out] = cp_nmu(X, R, 'trace', true, 'maxiters', 50);
score_val = score(M, M_true);
fprintf('Score (run with default options): %.4f\n', score_val);
figure(1); clf;
plot(out.time_trace, out.fit_trace, '-o');
xlabel('Time (seconds)'); ylabel('Fit');
title('CP-NMU Fit Trace');
grid on;
Nonnegative CP:
  tol = 0.0001,   maxiters = 50
  init = random
  trace = true
 Iter  1: fit = 9.103725e-01 fitdelta = 9.1e-01
 Iter  2: fit = 9.403600e-01 fitdelta = 3.0e-02
 Iter  3: fit = 9.501213e-01 fitdelta = 9.8e-03
 Iter  4: fit = 9.570217e-01 fitdelta = 6.9e-03
 Iter  5: fit = 9.619077e-01 fitdelta = 4.9e-03
 Iter  6: fit = 9.654248e-01 fitdelta = 3.5e-03
 Iter  7: fit = 9.680202e-01 fitdelta = 2.6e-03
 Iter  8: fit = 9.699901e-01 fitdelta = 2.0e-03
 Iter  9: fit = 9.715279e-01 fitdelta = 1.5e-03
 Iter 10: fit = 9.727606e-01 fitdelta = 1.2e-03
 Iter 11: fit = 9.737731e-01 fitdelta = 1.0e-03
 Iter 12: fit = 9.746231e-01 fitdelta = 8.5e-04
 Iter 13: fit = 9.753504e-01 fitdelta = 7.3e-04
 Iter 14: fit = 9.759834e-01 fitdelta = 6.3e-04
 Iter 15: fit = 9.765422e-01 fitdelta = 5.6e-04
 Iter 16: fit = 9.770416e-01 fitdelta = 5.0e-04
 Iter 17: fit = 9.774924e-01 fitdelta = 4.5e-04
 Iter 18: fit = 9.779027e-01 fitdelta = 4.1e-04
 Iter 19: fit = 9.782789e-01 fitdelta = 3.8e-04
 Iter 20: fit = 9.786255e-01 fitdelta = 3.5e-04
 Iter 21: fit = 9.789464e-01 fitdelta = 3.2e-04
 Iter 22: fit = 9.792445e-01 fitdelta = 3.0e-04
 Iter 23: fit = 9.795223e-01 fitdelta = 2.8e-04
 Iter 24: fit = 9.797818e-01 fitdelta = 2.6e-04
 Iter 25: fit = 9.800247e-01 fitdelta = 2.4e-04
 Iter 26: fit = 9.802525e-01 fitdelta = 2.3e-04
 Iter 27: fit = 9.804663e-01 fitdelta = 2.1e-04
 Iter 28: fit = 9.806673e-01 fitdelta = 2.0e-04
 Iter 29: fit = 9.808565e-01 fitdelta = 1.9e-04
 Iter 30: fit = 9.810347e-01 fitdelta = 1.8e-04
 Iter 31: fit = 9.812027e-01 fitdelta = 1.7e-04
 Iter 32: fit = 9.813613e-01 fitdelta = 1.6e-04
 Iter 33: fit = 9.815111e-01 fitdelta = 1.5e-04
 Iter 34: fit = 9.816527e-01 fitdelta = 1.4e-04
 Iter 35: fit = 9.817867e-01 fitdelta = 1.3e-04
 Iter 36: fit = 9.819135e-01 fitdelta = 1.3e-04
 Iter 37: fit = 9.820337e-01 fitdelta = 1.2e-04
 Iter 38: fit = 9.821477e-01 fitdelta = 1.1e-04
 Iter 39: fit = 9.822559e-01 fitdelta = 1.1e-04
 Iter 40: fit = 9.823586e-01 fitdelta = 1.0e-04
 Iter 41: fit = 9.824563e-01 fitdelta = 9.8e-05
 Final fit = 9.824563e-01 
Score (run with default options): 0.7675

Varying options

You can change the order of updates or the tolerance or the maximum number of iterations.

rng('default') % For reproducibility
[M, U0, out] = cp_nmu(X, R, 'dimorder', [3 2 1], 'tol', 1e-5, 'maxiters', 100);
score_val = score(M, M_true);
fprintf('dimorder/tol score: %.4f\n', score_val);
Nonnegative CP:
  tol = 1e-05,   maxiters = 100
  dimorder = [3  2  1]
  init = random
 Iter  1: fit = 7.498676e-01 fitdelta = 7.5e-01
 Iter  2: fit = 7.715137e-01 fitdelta = 2.2e-02
 Iter  3: fit = 7.835071e-01 fitdelta = 1.2e-02
 Iter  4: fit = 7.927945e-01 fitdelta = 9.3e-03
 Iter  5: fit = 8.010841e-01 fitdelta = 8.3e-03
 Iter  6: fit = 8.094320e-01 fitdelta = 8.3e-03
 Iter  7: fit = 8.185604e-01 fitdelta = 9.1e-03
 Iter  8: fit = 8.289310e-01 fitdelta = 1.0e-02
 Iter  9: fit = 8.406978e-01 fitdelta = 1.2e-02
 Iter 10: fit = 8.536302e-01 fitdelta = 1.3e-02
 Iter 11: fit = 8.671246e-01 fitdelta = 1.3e-02
 Iter 12: fit = 8.803757e-01 fitdelta = 1.3e-02
 Iter 13: fit = 8.926376e-01 fitdelta = 1.2e-02
 Iter 14: fit = 9.034183e-01 fitdelta = 1.1e-02
 Iter 15: fit = 9.125253e-01 fitdelta = 9.1e-03
 Iter 16: fit = 9.200008e-01 fitdelta = 7.5e-03
 Iter 17: fit = 9.260252e-01 fitdelta = 6.0e-03
 Iter 18: fit = 9.308330e-01 fitdelta = 4.8e-03
 Iter 19: fit = 9.346593e-01 fitdelta = 3.8e-03
 Iter 20: fit = 9.377119e-01 fitdelta = 3.1e-03
 Iter 21: fit = 9.401617e-01 fitdelta = 2.4e-03
 Iter 22: fit = 9.421438e-01 fitdelta = 2.0e-03
 Iter 23: fit = 9.437629e-01 fitdelta = 1.6e-03
 Iter 24: fit = 9.450988e-01 fitdelta = 1.3e-03
 Iter 25: fit = 9.462122e-01 fitdelta = 1.1e-03
 Iter 26: fit = 9.471495e-01 fitdelta = 9.4e-04
 Iter 27: fit = 9.479463e-01 fitdelta = 8.0e-04
 Iter 28: fit = 9.486299e-01 fitdelta = 6.8e-04
 Iter 29: fit = 9.492213e-01 fitdelta = 5.9e-04
 Iter 30: fit = 9.497373e-01 fitdelta = 5.2e-04
 Iter 31: fit = 9.501908e-01 fitdelta = 4.5e-04
 Iter 32: fit = 9.505921e-01 fitdelta = 4.0e-04
 Iter 33: fit = 9.509495e-01 fitdelta = 3.6e-04
 Iter 34: fit = 9.512697e-01 fitdelta = 3.2e-04
 Iter 35: fit = 9.515581e-01 fitdelta = 2.9e-04
 Iter 36: fit = 9.518192e-01 fitdelta = 2.6e-04
 Iter 37: fit = 9.520566e-01 fitdelta = 2.4e-04
 Iter 38: fit = 9.522734e-01 fitdelta = 2.2e-04
 Iter 39: fit = 9.524721e-01 fitdelta = 2.0e-04
 Iter 40: fit = 9.526549e-01 fitdelta = 1.8e-04
 Iter 41: fit = 9.528236e-01 fitdelta = 1.7e-04
 Iter 42: fit = 9.529798e-01 fitdelta = 1.6e-04
 Iter 43: fit = 9.531247e-01 fitdelta = 1.4e-04
 Iter 44: fit = 9.532597e-01 fitdelta = 1.3e-04
 Iter 45: fit = 9.533855e-01 fitdelta = 1.3e-04
 Iter 46: fit = 9.535032e-01 fitdelta = 1.2e-04
 Iter 47: fit = 9.536134e-01 fitdelta = 1.1e-04
 Iter 48: fit = 9.537169e-01 fitdelta = 1.0e-04
 Iter 49: fit = 9.538141e-01 fitdelta = 9.7e-05
 Iter 50: fit = 9.539058e-01 fitdelta = 9.2e-05
 Iter 51: fit = 9.539922e-01 fitdelta = 8.6e-05
 Iter 52: fit = 9.540739e-01 fitdelta = 8.2e-05
 Iter 53: fit = 9.541512e-01 fitdelta = 7.7e-05
 Iter 54: fit = 9.542245e-01 fitdelta = 7.3e-05
 Iter 55: fit = 9.542940e-01 fitdelta = 7.0e-05
 Iter 56: fit = 9.543600e-01 fitdelta = 6.6e-05
 Iter 57: fit = 9.544228e-01 fitdelta = 6.3e-05
 Iter 58: fit = 9.544826e-01 fitdelta = 6.0e-05
 Iter 59: fit = 9.545397e-01 fitdelta = 5.7e-05
 Iter 60: fit = 9.545941e-01 fitdelta = 5.4e-05
 Iter 61: fit = 9.546461e-01 fitdelta = 5.2e-05
 Iter 62: fit = 9.546959e-01 fitdelta = 5.0e-05
 Iter 63: fit = 9.547435e-01 fitdelta = 4.8e-05
 Iter 64: fit = 9.547892e-01 fitdelta = 4.6e-05
 Iter 65: fit = 9.548331e-01 fitdelta = 4.4e-05
 Iter 66: fit = 9.548752e-01 fitdelta = 4.2e-05
 Iter 67: fit = 9.549157e-01 fitdelta = 4.1e-05
 Iter 68: fit = 9.549547e-01 fitdelta = 3.9e-05
 Iter 69: fit = 9.549923e-01 fitdelta = 3.8e-05
 Iter 70: fit = 9.550286e-01 fitdelta = 3.6e-05
 Iter 71: fit = 9.550636e-01 fitdelta = 3.5e-05
 Iter 72: fit = 9.550975e-01 fitdelta = 3.4e-05
 Iter 73: fit = 9.551303e-01 fitdelta = 3.3e-05
 Iter 74: fit = 9.551621e-01 fitdelta = 3.2e-05
 Iter 75: fit = 9.551930e-01 fitdelta = 3.1e-05
 Iter 76: fit = 9.552229e-01 fitdelta = 3.0e-05
 Iter 77: fit = 9.552521e-01 fitdelta = 2.9e-05
 Iter 78: fit = 9.552805e-01 fitdelta = 2.8e-05
 Iter 79: fit = 9.553082e-01 fitdelta = 2.8e-05
 Iter 80: fit = 9.553352e-01 fitdelta = 2.7e-05
 Iter 81: fit = 9.553617e-01 fitdelta = 2.6e-05
 Iter 82: fit = 9.553876e-01 fitdelta = 2.6e-05
 Iter 83: fit = 9.554130e-01 fitdelta = 2.5e-05
 Iter 84: fit = 9.554380e-01 fitdelta = 2.5e-05
 Iter 85: fit = 9.554626e-01 fitdelta = 2.5e-05
 Iter 86: fit = 9.554868e-01 fitdelta = 2.4e-05
 Iter 87: fit = 9.555108e-01 fitdelta = 2.4e-05
 Iter 88: fit = 9.555344e-01 fitdelta = 2.4e-05
 Iter 89: fit = 9.555579e-01 fitdelta = 2.3e-05
 Iter 90: fit = 9.555812e-01 fitdelta = 2.3e-05
 Iter 91: fit = 9.556044e-01 fitdelta = 2.3e-05
 Iter 92: fit = 9.556275e-01 fitdelta = 2.3e-05
 Iter 93: fit = 9.556506e-01 fitdelta = 2.3e-05
 Iter 94: fit = 9.556737e-01 fitdelta = 2.3e-05
 Iter 95: fit = 9.556969e-01 fitdelta = 2.3e-05
 Iter 96: fit = 9.557202e-01 fitdelta = 2.3e-05
 Iter 97: fit = 9.557436e-01 fitdelta = 2.3e-05
 Iter 98: fit = 9.557673e-01 fitdelta = 2.4e-05
 Iter 99: fit = 9.557913e-01 fitdelta = 2.4e-05
 Iter 100: fit = 9.558156e-01 fitdelta = 2.4e-05
 Final fit = 9.558156e-01 
dimorder/tol score: 0.5331