vmmlib  1.7.0
 All Classes Namespaces Functions Pages
t3_hopm.hpp
1 /*
2  * Copyright (c) 2006-2014, Visualization and Multimedia Lab,
3  * University of Zurich <http://vmml.ifi.uzh.ch>,
4  * Eyescale Software GmbH,
5  * Blue Brain Project, EPFL
6  *
7  * This file is part of VMMLib <https://github.com/VMML/vmmlib/>
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions are met:
11  *
12  * Redistributions of source code must retain the above copyright notice, this
13  * list of conditions and the following disclaimer. Redistributions in binary
14  * form must reproduce the above copyright notice, this list of conditions and
15  * the following disclaimer in the documentation and/or other materials provided
16  * with the distribution. Neither the name of the Visualization and Multimedia
17  * Lab, University of Zurich nor the names of its contributors may be used to
18  * endorse or promote products derived from this software without specific prior
19  * written permission.
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
24  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30  * POSSIBILITY OF SUCH DAMAGE.
31  */
32 
33 /* @author Susanne Suter
34  * @author Rafa Ballester
35  *
36  * the higher-order power method (HOPM) is also known as CP-ALS (ALS: alternating least squares)
37  * CP stands for Candecomp/Parafac (1970)
38  * references:
39  * - Carroll & Chang, 1970: Analysis of Individual Differences in Multidimensional Scaling via an N-way generalization of ``Eckart--Young'' decompositions, Psychometrika.
40  * - Harshman, 1970: Foundations of the PARAFAC procedure: Models and conditions for an 'explanatory' multi-modal factor analysis, UCLA Working Papers in Phonetics.
41  * - De Lathauwer, De Moor, Vandewalle, 2000: A multilinear singular value decomposition, SIAM J. Matrix Anal. Appl.
42  * - Kolda & Bader, 2009: Tensor Decompositions and Applications, SIAM Review.
43  * - Bader & Kolda, 2006: Algorithm 862: Matlab tensor classes for fast algorithm prototyping. ACM Transactions on Mathematical Software.
44  *
45  */
46 
47 #ifndef __VMML__T3_HOPM__HPP__
48 #define __VMML__T3_HOPM__HPP__
49 
50 #include <vmmlib/t3_hosvd.hpp>
51 #include <vmmlib/matrix_pseudoinverse.hpp>
52 #include <vmmlib/blas_dgemm.hpp>
53 #include <vmmlib/blas_dot.hpp>
54 #include <vmmlib/validator.hpp>
55 #include <vmmlib/t3_ttv.hpp>
56 #include <vmmlib/tensor_stats.hpp>
57 
58 namespace vmml {
59 
60  template< size_t R, size_t I1, size_t I2, size_t I3, typename T = float >
61  class t3_hopm {
62  public:
64 
66 
70 
74 
78 
80 
81  typedef typename lambda_type::iterator lvalue_iterator;
82  typedef typename lambda_type::const_iterator lvalue_const_iterator;
83  typedef std::pair< T, size_t > lambda_pair_type;
84 
85  //higher-order power method (lathauwer et al., 2000b)
86 
87  template< typename T_init >
88  static tensor_stats als(const t3_type& data_, u1_type& u1_, u2_type& u2_, u3_type& u3_, lambda_type& lambdas_, T_init init, const size_t max_iterations_ = 50, const float tolerance = 1e-04);
89  static void reconstruct(t3_type& data_, const u1_type& u1_, const u2_type& u2_, const u3_type& u3_, const lambda_type& lambdas_);
90 
91  //ktensor = kruskal tensor, i.e., lambda, U1, U2, U3
92  static double norm_ktensor(const u1_type& u1_, const u2_type& u2_, const u3_type& u3_, const lambda_type& lambdas_);
93 
94  // init functors
95 
96  struct init_hosvd {
97 
98  inline void operator()(const t3_type& data_, u2_type& u2_, u3_type & u3_) {
101  }
102  };
103 
104  struct init_random {
105 
106  inline void operator()(const t3_type&, u2_type& u2_, u3_type & u3_) {
107  srand(time(NULL));
108  u2_.set_random();
109  u3_.set_random();
110  }
111  };
112 
113  //FIXME: check test on linux
114 #if 1
115 
116  struct init_dct {
117 
118  inline void operator()(const t3_type&, u2_type& u2_, u3_type & u3_) {
119  u2_.set_dct();
120  u3_.set_dct();
121  }
122  };
123 #endif
124 
125 
126  protected:
127 
128  static void optimize_mode1(const t3_type& data_, u1_type& u1, const u2_type& u2_, const u3_type& u3_, lambda_type& lambdas_);
129  static void optimize_mode2(const t3_type& data_, const u1_type& u1_, u2_type& u2_, const u3_type& u3_, lambda_type& lambdas_);
130  static void optimize_mode3(const t3_type& data_, const u1_type& u1_, const u2_type& u2_, u3_type& u3_, lambda_type& lambdas_);
131 
132  template< size_t J, size_t K, size_t L >
133  static void optimize(const matrix< J, K*L, T >& unfolding_,
134  matrix< J, R, T >& uj_,
135  const matrix< K, R, T >& uk_, const matrix< L, R, T >& ul_,
136  vector< R, T>& lambdas_
137  );
138 
139  static void sort_dec(u1_type& u1_, u2_type& u2_, u3_type& u3_, lambda_type& lambdas_);
140 
141  // comparison functor
142 
143  struct lambda_compare {
144 
145  inline bool operator()(const lambda_pair_type& a, const lambda_pair_type & b) {
146  return fabs(a.first) > fabs(b.first);
147  }
148  };
149 
150  };
151 
152 
153 
154 #define VMML_TEMPLATE_STRING template< size_t R, size_t I1, size_t I2, size_t I3, typename T >
155 #define VMML_TEMPLATE_CLASSNAME t3_hopm< R, I1, I2, I3, T >
156 
157  VMML_TEMPLATE_STRING
158  template< typename T_init>
160  VMML_TEMPLATE_CLASSNAME::als(const t3_type& data_,
161  u1_type& u1_, u2_type& u2_, u3_type& u3_,
162  lambda_type& lambdas_,
163  T_init init,
164  const size_t max_iterations_, const float tolerance_) {
165 
166  tensor_stats result;
167  t3_type* approximated_data = new t3_type;
168  t3_type* residual_data = new t3_type;
169  residual_data->zero();
170 
171  T max_f_norm = 0.0;
172  T fit = 0.;
173  T fitchange, norm2, innerprod, fitold, normresidual;
174  if (tolerance_ > 0) {
175  max_f_norm = data_.frobenius_norm();
176  fit = 0;
177  if (max_f_norm == 0)
178  fit = 1;
179  fitchange = 1;
180  norm2 = innerprod = 0;
181  fitold = fit;
182  normresidual = 0;
183  }
184 
185  //intialize u1-u3
186  //inital guess not needed for u1 since it will be computed in the first optimization step
187  init(data_, u2_, u3_);
188 
189  assert(validator::is_valid(u2_) && validator::is_valid(u3_));
190  assert(validator::is_valid(lambdas_));
191 
192 #if CP_LOG
193  std::ostringstream convert;
194  convert << R;
195  std::cout << convert.str() + "-R rank CP ALS: HOPM (for tensor3) " << std::endl;
196 #endif
197 
198  size_t i = 0;
199  while (i < max_iterations_ && (tolerance_ < 0 || fitchange >= tolerance_)) //do until converges
200  {
201  fitold = fit;
202 
203  // timer myTimer;
204  // myTimer.start();
205 
206  optimize_mode1(data_, u1_, u2_, u3_, lambdas_);
207  //#if CP_LOG
208  // std::cerr << myTimer.get_seconds() << " ";
209  // myTimer.start();
210  //#endif
211  optimize_mode2(data_, u1_, u2_, u3_, lambdas_);
212 #if CP_LOG
213  // std::cerr << myTimer.get_seconds() << " ";
214  // myTimer.start();
215 #endif
216  optimize_mode3(data_, u1_, u2_, u3_, lambdas_);
217  //#if CP_LOG
218  // std::cerr << myTimer.get_seconds() << " ";
219  //#endif
220 
221  if (tolerance_ > 0) {
222  norm2 = norm_ktensor(u1_, u2_, u3_, lambdas_);
223 
224  T val2 = 0;
225  for( size_t j = 0; j < lambdas_.size(); ++j)
226  {
227  matrix<I2, I3, T> res1;
228  t3_ttv::multiply_first_mode(data_, u1_.get_column(j), res1);
229  vector<I2, T> res2 = res1 * u3_.get_column(j);
230  val2 += lambdas_.at(j) * (res2.dot(u2_.get_column(j)));
231  }
232  innerprod = 2 * val2;
233  // ||X-P|| = sqrt( ||X||^2 + ||P||^2 - 2 * X.P );
234  normresidual = sqrt(max_f_norm * max_f_norm + norm2 * norm2 - innerprod);
235  fit = 1 - (normresidual / max_f_norm);
236  fitchange = fabs(fitold - fit);
237 #if CP_LOG
238  // std::cerr << myTimer.get_seconds() << " ";
239  std::cout << "iteration '" << i
240  << "', fit: " << fit
241  << ", fitdelta: " << fit - fitold
242  << ", normresidual: " << normresidual;
243  if (fit - fitold < 0) std::cout << " *fit is worsening*";
244  std::cout << std::endl;
245 #endif
246  }
247 #if 0
248  myTimer.start();
249 
250  //Reconstruct tensor and measure norm of approximation
251  //slower version since cp reconstruction is slow
252  reconstruct(*approximated_data, u1_, u2_, u3_, lambdas_);
253  // approx_norm = approximated_data->frobenius_norm();
254  *residual_data = data_ - *approximated_data;
255  normresidual = residual_data->frobenius_norm();
256  std::cerr << myTimer.get_seconds() << " ";
257 #endif
258 
259  ++i;
260  } // end ALS
261  result.set_n_iterations(i);
262 
263  //sort lambdas by decreasing magnitude
264  sort_dec(u1_, u2_, u3_, lambdas_);
265 
266  delete residual_data;
267  delete approximated_data;
268  return result;
269  }
270 
271  VMML_TEMPLATE_STRING
272  void
273  VMML_TEMPLATE_CLASSNAME::optimize_mode1(const t3_type& data_, u1_type& u1_, const u2_type& u2_, const u3_type& u3_, lambda_type& lambdas_) {
274  u1_unfolded_type* unfolding = new u1_unfolded_type; // -> u1
275  //data_.horizontal_unfolding_bwd( *unfolding ); //lathauwer
276  data_.frontal_unfolding_fwd(*unfolding);
277 
278  assert(validator::is_valid(u2_) && validator::is_valid(u3_));
279 
280  optimize(*unfolding, u1_, u2_, u3_, lambdas_);
281 
282  delete unfolding;
283  }
284 
285  VMML_TEMPLATE_STRING
286  void
287  VMML_TEMPLATE_CLASSNAME::optimize_mode2(const t3_type& data_, const u1_type& u1_, u2_type& u2_, const u3_type& u3_, lambda_type& lambdas_) {
288  u2_unfolded_type* unfolding = new u2_unfolded_type; // -> u2
289  data_.frontal_unfolding_bwd(*unfolding); //lathauwer
290  //data_.horizontal_unfolding_fwd( *unfolding );
291 
292  assert(validator::is_valid(u1_) && validator::is_valid(u3_));
293 
294  optimize(*unfolding, u2_, u1_, u3_, lambdas_);
295 
296  delete unfolding;
297  }
298 
299  VMML_TEMPLATE_STRING
300  void
301  VMML_TEMPLATE_CLASSNAME::optimize_mode3(const t3_type& data_, const u1_type& u1_, const u2_type& u2_, u3_type& u3_, lambda_type& lambdas_) {
302  u3_unfolded_type* unfolding = new u3_unfolded_type; //-> u3
303  //data_.horizontal_unfolding_bwd( *unfolding );//lathauwer
304  data_.lateral_unfolding_fwd(*unfolding);
305 
306  assert(validator::is_valid(u1_) && validator::is_valid(u2_));
307 
308  optimize(*unfolding, u3_, u1_, u2_, lambdas_);
309 
310  delete unfolding;
311  }
312 
313  VMML_TEMPLATE_STRING
314  template< size_t J, size_t K, size_t L >
315  void
316  VMML_TEMPLATE_CLASSNAME::optimize(
317  const matrix< J, K*L, T >& unfolding_,
318  matrix< J, R, T >& uj_,
319  const matrix< K, R, T >& uk_, const matrix< L, R, T >& ul_,
320  vector< R, T>& lambdas_
321  ) {
322 
323  typedef matrix< K*L, R, T > krp_matrix_type;
324  krp_matrix_type* krp_prod = new krp_matrix_type;
325  assert(validator::is_valid(uk_) && validator::is_valid(ul_));
326 
327  ul_.khatri_rao_product(uk_, *krp_prod);
328 
329  matrix< J, R, T >* u_new = new matrix< J, R, T >;
330 
331  blas_dgemm< J, K*L, R, T> blas_dgemm1;
332  blas_dgemm1.compute(unfolding_, *krp_prod, *u_new);
333 
334  //square matrix of U_l and U_k
335  m_r2_type* uk_r = new m_r2_type;
336  m_r2_type* ul_r = new m_r2_type;
337 
338  blas_dgemm< R, K, R, T> blas_dgemm2;
339  blas_dgemm2.compute_t(uk_, *uk_r);
340  assert(validator::is_valid(*uk_r));
341 
342  blas_dgemm< R, L, R, T> blas_dgemm3;
343  blas_dgemm3.compute_t(ul_, *ul_r);
344  assert(validator::is_valid(*ul_r));
345 
346  uk_r->multiply_piecewise(*ul_r);
347  assert(validator::is_valid(*uk_r));
348 
349  m_r2_type* pinv_t = new m_r2_type;
350  compute_pseudoinverse< m_r2_type > compute_pinv;
351 
352  compute_pinv(*uk_r, *pinv_t);
353 
354  blas_dgemm< J, R, R, T> blas_dgemm4;
355  blas_dgemm4.compute_bt(*u_new, *pinv_t, uj_);
356  assert(validator::is_valid(uj_));
357 
358  *u_new = uj_;
359  u_new->multiply_piecewise(*u_new); //2 norm
360  u_new->columnwise_sum(lambdas_);
361 
362  assert(validator::is_valid(lambdas_));
363 
364  lambdas_.sqrt_elementwise();
365  lambda_type* tmp = new lambda_type;
366  *tmp = lambdas_;
367  tmp->reciprocal_safe();
368 
369  assert(validator::is_valid(*tmp));
370 
371  m_r2_type* diag_lambdas = new m_r2_type;
372  diag_lambdas->diag(*tmp);
373 
374  matrix< J, R, T >* tmp_uj = new matrix< J, R, T > (uj_);
375  blas_dgemm4.compute(*tmp_uj, *diag_lambdas, uj_);
376 
377  assert(validator::is_valid(uj_));
378 
379  delete krp_prod;
380  delete uk_r;
381  delete ul_r;
382  delete pinv_t;
383  delete u_new;
384  delete diag_lambdas;
385  delete tmp;
386  delete tmp_uj;
387 
388  }
389 
390  VMML_TEMPLATE_STRING
391  void
392  VMML_TEMPLATE_CLASSNAME::reconstruct(t3_type& data_, const u1_type& u1_, const u2_type& u2_, const u3_type& u3_, const lambda_type& lambdas_) {
393  u1_inv_type* u1_t = new u1_inv_type;
394  u2_inv_type* u2_t = new u2_inv_type;
395  u3_inv_type* u3_t = new u3_inv_type;
396  typedef matrix< R, I2 * I3, T > m_temp_type;
397  m_temp_type* temp = new m_temp_type;
398 
399  u1_.transpose_to(*u1_t);
400  u2_.transpose_to(*u2_t);
401  u3_.transpose_to(*u3_t);
402 
403  data_.reconstruct_CP(lambdas_, *u1_t, *u2_t, *u3_t, *temp);
404 
405  delete temp;
406  delete u1_t;
407  delete u2_t;
408  delete u3_t;
409  }
410 
411  VMML_TEMPLATE_STRING
412  double
413  VMML_TEMPLATE_CLASSNAME::norm_ktensor(const u1_type& u1_, const u2_type& u2_, const u3_type& u3_, const lambda_type& lambdas_) {
414  m_r2_type* coeff2_matrix = new m_r2_type;
415  m_r2_type* cov_u1 = new m_r2_type;
416  m_r2_type* cov_u2 = new m_r2_type;
417  m_r2_type* cov_u3 = new m_r2_type;
418 
419  blas_dgemm< R, 1, R, T >* blas_l2 = new blas_dgemm< R, 1, R, T>;
420  blas_l2->compute_vv_outer(lambdas_, lambdas_, *coeff2_matrix);
421  delete blas_l2;
422 
423  blas_dgemm< R, I1, R, T >* blas_u1cov = new blas_dgemm< R, I1, R, T>;
424  blas_u1cov->compute_t(u1_, *cov_u1);
425  delete blas_u1cov;
426 
427  blas_dgemm< R, I2, R, T >* blas_u2cov = new blas_dgemm< R, I2, R, T>;
428  blas_u2cov->compute_t(u2_, *cov_u2);
429  delete blas_u2cov;
430 
431  blas_dgemm< R, I3, R, T >* blas_u3cov = new blas_dgemm< R, I3, R, T>;
432  blas_u3cov->compute_t(u3_, *cov_u3);
433  delete blas_u3cov;
434 
435  coeff2_matrix->multiply_piecewise(*cov_u1);
436  coeff2_matrix->multiply_piecewise(*cov_u2);
437  coeff2_matrix->multiply_piecewise(*cov_u3);
438 
439  double nrm = coeff2_matrix->sum_elements();
440 
441  delete coeff2_matrix;
442  delete cov_u1;
443  delete cov_u2;
444  delete cov_u3;
445 
446  return sqrt(nrm);
447  }
448 
449  VMML_TEMPLATE_STRING
450  void
451  VMML_TEMPLATE_CLASSNAME::sort_dec(u1_type& u1_, u2_type& u2_, u3_type& u3_, lambda_type& lambdas_) {
452  //keep copy of original matrices
453  u1_type *orig_u1 = new u1_type(u1_);
454  u2_type *orig_u2 = new u2_type(u2_);
455  u3_type *orig_u3 = new u3_type(u3_);
456  lambda_type sorted_lvalues;
457 
458  //(1) store permutations of the lambdas (According to larges magnitude
459  std::vector< lambda_pair_type > lambda_permut;
460 
461  lvalue_const_iterator it = lambdas_.begin(), it_end = lambdas_.end();
462  size_t counter = 0;
463  for (; it != it_end; ++it, ++counter) {
464  lambda_permut.push_back(lambda_pair_type(*it, counter));
465  }
466 
467  std::sort(
468  lambda_permut.begin(),
469  lambda_permut.end(),
470  lambda_compare()
471  );
472 
473  //(2) sort the matrix vectors according to lambda permutations and set sorted lambdas
474  typename std::vector< lambda_pair_type >::const_iterator it2 = lambda_permut.begin(), it2_end = lambda_permut.end();
475  lvalue_iterator lvalues_it = lambdas_.begin();
476  for (counter = 0; it2 != it2_end; ++it2, ++counter, ++lvalues_it) {
477  *lvalues_it = it2->first;
478  u1_.set_column(counter, orig_u1->get_column(it2->second));
479  u2_.set_column(counter, orig_u2->get_column(it2->second));
480  u3_.set_column(counter, orig_u3->get_column(it2->second));
481  }
482 
483  delete orig_u1;
484  delete orig_u2;
485  delete orig_u3;
486  }
487 
488 
489 
490 
491 
492 #undef VMML_TEMPLATE_STRING
493 #undef VMML_TEMPLATE_CLASSNAME
494 
495 }//end vmml namespace
496 
497 #endif