vmmlib  1.7.0
 All Classes Namespaces Functions Pages
t3_hooi.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 orthogonal iteration (HOOI) is also known as Tucker-ALS (Tuck-ALS)
37  * The t3_hooi implements a HOOI for a third-order tensor
38  * references:
39  * - Tucker, 1966: Some mathematical notes on three-mode factor analysis, Psychometrika.
40  * - De Lathauwer, De Moor, Vandewalle, 2000a: A multilinear singular value decomposition, SIAM J. Matrix Anal. Appl.
41  * - De Lathauwer, De Moor, Vandewalle, 2000b: On the Best rank-1 and Rank-(R_1, R_2, ..., R_N) Approximation and Applications of Higher-Order Tensors, 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_HOOI__HPP__
48 #define __VMML__T3_HOOI__HPP__
49 
50 
51 #include <vmmlib/t3_hosvd.hpp>
52 #include <vmmlib/t3_ttm.hpp>
53 #include <vmmlib/matrix_pseudoinverse.hpp>
54 #include <vmmlib/tensor_stats.hpp>
55 
56 namespace vmml {
57 
58  template< size_t R1, size_t R2, size_t R3, size_t I1, size_t I2, size_t I3, typename T = float >
59  class t3_hooi {
60  public:
61 
63 
65 
69 
73 
74  /* higher-order orthogonal iteration (HOOI) is a truncated HOSVD decompositions, i.e., the HOSVD components are of lower-ranks. An optimal rank-reduction is
75  performed with an alternating least-squares (ALS) algorithm, which minimizes the error between the approximated and orignal tensor based on the Frobenius norm
76  see: De Lathauwer et al, 2000b; On the best rank-1 and rank-(RRR) approximation of higher-order tensors.
77  the HOOI can be computed based on (a) n-mode PCA, i.e., an eigenvalue decomposition on the covariance matrix of every mode's matriciziation, and
78  (b) by performing a 2D SVD on the matricization of every mode. Matrix matricization means that a tensor I1xI2xI3 is unfolded/sliced into one matrix
79  with the dimensions I1xI2I3, which corresponds to a matrizitation alonge mode I1.
80  */
81  template< typename T_init>
82  static tensor_stats als(const t3_type& data_, u1_type& u1_, u2_type& u2_, u3_type& u3_, t3_core_type& core_, T_init init, const double& max_f_norm_ = 0.0, const size_t max_iterations = 10, const float tolerance = 1e-04);
83 
84  //core not needed
85  template< typename T_init>
86  static tensor_stats als(const t3_type& data_, u1_type& u1_, u2_type& u2_, u3_type& u3_, T_init init, const double& max_f_norm_ = 0.0, const size_t max_iterations = 10, const float tolerance = 1e-04);
87 
88  /* derive core
89  implemented according to core = data x_1 U1_pinv x_2 U2_pinv x_3 U3_pinv,
90  where x_1 ... x_3 are n-mode products and U1_pinv ... U3_pinv are inverted basis matrices
91  the inversion is done with a matrix pseudoinverse computation
92  */
93  static void derive_core(const t3_type& data_, const u1_type& u1_, const u2_type& u2_, const u3_type& u3_, t3_core_type& core_);
94  //faster: but only if basis matrices are orthogonal
95  static void derive_core_orthogonal_bases(const t3_type& data_, const u1_type& u1_, const u2_type& u2_, const u3_type& u3_, t3_core_type& core_);
96 
97  // init functors
98 
99  struct init_hosvd {
100 
101  inline void operator()(const t3_type& data_, u2_type& u2_, u3_type & u3_) {
104  }
105  };
106 
107  struct init_random {
108 
109  inline void operator()(const t3_type&, u2_type& u2_, u3_type & u3_) {
110  srand(time(NULL));
111  u2_.set_random();
112  u3_.set_random();
113 
114  u2_ /= u2_.frobenius_norm();
115  u3_ /= u3_.frobenius_norm();
116  }
117  };
118 
119  struct init_dct {
120 
121  inline void operator()(const t3_type&, u2_type& u2_, u3_type & u3_) {
122  u2_.set_dct();
123  u3_.set_dct();
124  }
125  };
126 
127  protected:
128 
129 
130  static void optimize_mode1(const t3_type& data_, const u2_type& u2_, const u3_type& u3_,
131  tensor3< I1, R2, R3, T >& projection_,
133  static void optimize_mode2(const t3_type& data_, const u1_type& u1_, const u3_type& u3_,
134  tensor3< R1, I2, R3, T >& projection_,
136  static void optimize_mode3(const t3_type& data_, const u1_type& u1_, const u2_type& u2_,
137  tensor3< R1, R2, I3, T >& projection_,
139 
140 
141  }; //end class t3_hooi
142 
143 #define VMML_TEMPLATE_STRING template< size_t R1, size_t R2, size_t R3, size_t I1, size_t I2, size_t I3, typename T >
144 #define VMML_TEMPLATE_CLASSNAME t3_hooi< R1, R2, R3, I1, I2, I3, T >
145 
146  VMML_TEMPLATE_STRING
147  template< typename T_init>
149  VMML_TEMPLATE_CLASSNAME::als(const t3_type& data_,
150  u1_type& u1_, u2_type& u2_, u3_type& u3_,
151  T_init init, const double& max_f_norm_, const size_t max_iterations, const float tolerance) {
152  t3_core_type core;
153  core.zero();
154  return als(data_, u1_, u2_, u3_, core, init, max_f_norm_, max_iterations, tolerance);
155  }
156 
157  VMML_TEMPLATE_STRING
158  template< typename T_init>
159  tensor_stats
160  VMML_TEMPLATE_CLASSNAME::als(const t3_type& data_,
161  u1_type& u1_, u2_type& u2_, u3_type& u3_,
162  t3_core_type& core_,
163  T_init init,
164  const double& max_f_norm_, const size_t max_iterations_, const float tolerance_) {
165  tensor_stats result;
166 
167  //intialize basis matrices
168  init(data_, u2_, u3_);
169 
170  core_.zero();
171  T max_f_norm = 0.0;
172  T f_norm, fit, fitchange, fitold, normresidual;
173  if (tolerance_ > 0) {
174  max_f_norm = max_f_norm_;
175 
176  if (max_f_norm <= 0.0) {
177  max_f_norm = data_.frobenius_norm();
178  }
179  fit = 0;
180  //removed to save computation
181  /*if ( (max_f_norm != 0) && (max_f_norm > f_norm) )
182  {
183  fit = 1 - (normresidual / max_f_norm);
184  } else {
185  fit = 1;
186  }*/
187  fitchange = 1;
188  fitold = fit;
189  normresidual = 0;
190  }
191 
192  tensor3< I1, R2, R3, T > projection1;
193  tensor3< R1, I2, R3, T > projection2;
194  tensor3< R1, R2, I3, T > projection3;
195 
196  tensor3< I1, R2, I3, T > tmp1;
197  tensor3< R1, I2, I3, T > tmp2;
198 
199 #if TUCKER_LOG
200  std::cout << "HOOI ALS (for tensor3) " << std::endl
201  << "initial fit: " << fit << ", "
202  << "frobenius norm original: " << max_f_norm << std::endl;
203 #endif
204  size_t i = 0;
205  while (i < max_iterations_ && (tolerance_ < 0 || fitchange >= tolerance_)) { //do until converges
206  fitold = fit;
207 
208  //optimize modes
209  optimize_mode1(data_, u2_, u3_, projection1, tmp1);
210  t3_hosvd< R1, R2, R3, I1, R2, R3, T >::apply_mode1(projection1, u1_);
211 
212  optimize_mode2(data_, u1_, u3_, projection2, tmp2);
213  t3_hosvd< R1, R2, R3, R1, I2, R3, T >::apply_mode2(projection2, u2_);
214 
215  optimize_mode3(data_, u1_, u2_, projection3, tmp2);
216  t3_hosvd< R1, R2, R3, R1, R2, I3, T >::apply_mode3(projection3, u3_);
217 
218  t3_ttm::multiply_horizontal_bwd(projection3, transpose(u3_), core_);
219 
220  if (tolerance_ > 0) {
221  f_norm = core_.frobenius_norm();
222  normresidual = sqrt( fabs(max_f_norm * max_f_norm - f_norm * f_norm) );
223  fit = 1 - (normresidual / max_f_norm);
224  fitchange = fabs(fitold - fit);
225 #if TUCKER_LOG
226  std::cout << "iteration '" << i << "', fit: " << fit
227  << ", fitdelta: " << fitchange
228  << ", frobenius norm of core: " << f_norm << std::endl;
229 #endif
230  }
231  ++i;
232  }
233  result.set_n_iterations(i);
234  return result;
235  }
236 
237  VMML_TEMPLATE_STRING
238  void
239  VMML_TEMPLATE_CLASSNAME::optimize_mode1(const t3_type& data_, const u2_type& u2_, const u3_type& u3_,
240  tensor3< I1, R2, R3, T >& projection_,
241  tensor3< I1, R2, I3, T >& tmp_) {
242  u2_t_type* u2_inv = new u2_t_type;
243  u3_t_type* u3_inv = new u3_t_type;
244  u2_.transpose_to(*u2_inv);
245  u3_.transpose_to(*u3_inv);
246 
247 #if 1
248  //backward cyclic matricization/unfolding (after Lathauwer et al., 2000a)
249  t3_ttm::multiply_frontal_bwd(data_, *u2_inv, tmp_);
250  t3_ttm::multiply_horizontal_bwd(tmp_, *u3_inv, projection_);
251 #else
252  //forward cyclic matricization/unfolding (after Kiers, 2000) -> memory optimized
253  t3_ttm::multiply_horizontal_fwd(data_, *u2_inv, tmp_);
254  t3_ttm::multiply_lateral_fwd(tmp_, *u3_inv, projection_);
255 #endif
256 
257  delete u2_inv;
258  delete u3_inv;
259  }
260 
261  VMML_TEMPLATE_STRING
262  void
263  VMML_TEMPLATE_CLASSNAME::optimize_mode2(const t3_type& data_, const u1_type& u1_, const u3_type& u3_,
264  tensor3< R1, I2, R3, T >& projection_,
265  tensor3< R1, I2, I3, T >& tmp_) {
266  u1_t_type* u1_inv = new u1_t_type();
267  u3_t_type* u3_inv = new u3_t_type();
268  u1_.transpose_to(*u1_inv);
269  u3_.transpose_to(*u3_inv);
270 
271 
272 #if 0
273  //backward cyclic matricization (after Lathauwer et al., 2000a)
274  t3_ttm::multiply_lateral_bwd(data_, *u1_inv, tmp_);
275  t3_ttm::multiply_horizontal_bwd(tmp_, *u3_inv, projection_);
276 #else
277  //forward cyclic matricization/unfolding (after Kiers, 2000) -> memory optimized
278  t3_ttm::multiply_frontal_fwd(data_, *u1_inv, tmp_);
279  t3_ttm::multiply_lateral_fwd(tmp_, *u3_inv, projection_);
280 #endif
281 
282  delete u1_inv;
283  delete u3_inv;
284  }
285 
286  VMML_TEMPLATE_STRING
287  void
288  VMML_TEMPLATE_CLASSNAME::optimize_mode3(const t3_type& data_, const u1_type& u1_, const u2_type& u2_,
289  tensor3< R1, R2, I3, T >& projection_,
290  tensor3< R1, I2, I3, T >& tmp_) {
291  u1_t_type* u1_inv = new u1_t_type;
292  u2_t_type* u2_inv = new u2_t_type;
293  u1_.transpose_to(*u1_inv);
294  u2_.transpose_to(*u2_inv);
295 
296 #if 0
297  //backward cyclic matricization (after Lathauwer et al., 2000a)
298  t3_ttm::multiply_lateral_bwd(data_, *u1_inv, tmp_);
299  t3_ttm::multiply_frontal_bwd(tmp_, *u2_inv, projection_);
300 #else
301  //forward cyclic matricization/unfolding (after Kiers, 2000) -> memory optimized
302  t3_ttm::multiply_frontal_fwd(data_, *u1_inv, tmp_);
303  t3_ttm::multiply_horizontal_fwd(tmp_, *u2_inv, projection_);
304 #endif
305 
306  delete u1_inv;
307  delete u2_inv;
308  }
309 
310  VMML_TEMPLATE_STRING
311  void
312  VMML_TEMPLATE_CLASSNAME::derive_core_orthogonal_bases(const t3_type& data_, const u1_type& u1_, const u2_type& u2_, const u3_type& u3_, t3_core_type& core_) {
313  u1_t_type* u1_inv = new u1_t_type;
314  u2_t_type* u2_inv = new u2_t_type;
315  u3_t_type* u3_inv = new u3_t_type;
316 
317  u1_.transpose_to(*u1_inv);
318  u2_.transpose_to(*u2_inv);
319  u3_.transpose_to(*u3_inv);
320 
321  t3_ttm::full_tensor3_matrix_multiplication(data_, *u1_inv, *u2_inv, *u3_inv, core_);
322 
323  delete u1_inv;
324  delete u2_inv;
325  delete u3_inv;
326  }
327 
328  VMML_TEMPLATE_STRING
329  void
330  VMML_TEMPLATE_CLASSNAME::derive_core(const t3_type& data_, const u1_type& u1_, const u2_type& u2_, const u3_type& u3_, t3_core_type& core_) {
331 
332 #if 1
333  //faster approach
334  //compute pseudo inverse for matrices u1-u3
335  u1_type* u1_pinv_t = new u1_type;
336  u2_type* u2_pinv_t = new u2_type;
337  u3_type* u3_pinv_t = new u3_type;
338 
339  compute_pseudoinverse< u1_type > compute_pinv_u1;
340  compute_pinv_u1(u1_, *u1_pinv_t);
341  compute_pseudoinverse< u2_type > compute_pinv_u2;
342  compute_pinv_u2(u2_, *u2_pinv_t);
343  compute_pseudoinverse< u3_type > compute_pinv_u3;
344  compute_pinv_u3(u3_, *u3_pinv_t);
345 
346  u1_t_type* u1_pinv = new u1_t_type;
347  u2_t_type* u2_pinv = new u2_t_type;
348  u3_t_type* u3_pinv = new u3_t_type;
349 
350  u1_pinv_t->transpose_to(*u1_pinv);
351  u2_pinv_t->transpose_to(*u2_pinv);
352  u3_pinv_t->transpose_to(*u3_pinv);
353 
354  t3_ttm::full_tensor3_matrix_multiplication(data_, *u1_pinv, *u2_pinv, *u3_pinv, core_);
355 
356  delete u1_pinv;
357  delete u2_pinv;
358  delete u3_pinv;
359  delete u1_pinv_t;
360  delete u2_pinv_t;
361  delete u3_pinv_t;
362 
363 #else
364  //previous version of compute core
365  for (size_t r3 = 0; r3 < R3; ++r3) {
366  for (size_t r1 = 0; r1 < R1; ++r1) {
367  for (size_t r2 = 0; r2 < R2; ++r2) {
368  float_t sum_i1_i2_i3 = 0.0;
369  for (size_t i3 = 0; i3 < I3; ++i3) {
370  for (size_t i1 = 0; i1 < I1; ++i1) {
371  for (size_t i2 = 0; i2 < I2; ++i2) {
372  sum_i1_i2_i3 += u1_.at(i1, r1) * u2_.at(i2, r2) * u3_.at(i3, r3) * T(data_.at(i1, i2, i3));
373  }
374  }
375  }
376  core_.at(r1, r2, r3) = sum_i1_i2_i3;
377  }
378  }
379  }
380 
381 #endif
382  }
383 
384 
385 #undef VMML_TEMPLATE_STRING
386 #undef VMML_TEMPLATE_CLASSNAME
387 
388 
389 }//end vmml namespace
390 
391 #endif