SHOGUN  v2.0.0
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
LocallyLinearEmbedding.cpp
Go to the documentation of this file.
1 /*
2  * This program is free software; you can redistribute it and/or modify
3  * it under the terms of the GNU General Public License as published by
4  * the Free Software Foundation; either version 3 of the License, or
5  * (at your option) any later version.
6  *
7  * Written (W) 2011 Sergey Lisitsyn
8  * Copyright (C) 2011 Berlin Institute of Technology and Max-Planck-Society
9  */
10 
12 #include <shogun/lib/config.h>
13 #ifdef HAVE_LAPACK
18 #include <shogun/lib/CoverTree.h>
19 #include <shogun/base/DynArray.h>
21 #include <shogun/io/SGIO.h>
22 #include <shogun/lib/Time.h>
24 #include <shogun/lib/Signal.h>
25 
26 #ifdef HAVE_PTHREAD
27 #include <pthread.h>
28 #endif
29 
30 using namespace shogun;
31 
32 #ifndef DOXYGEN_SHOULD_SKIP_THIS
33 struct LINRECONSTRUCTION_THREAD_PARAM
34 {
36  int32_t idx_start;
38  int32_t idx_stop;
40  int32_t idx_step;
42  int32_t m_k;
44  const int32_t* neighborhood_matrix;
46  const float64_t* feature_matrix;
48  float64_t* z_matrix;
50  float64_t* covariance_matrix;
52  float64_t* id_vector;
54  float64_t* W_matrix;
56  float64_t m_reconstruction_shift;
58  int32_t dim;
60  int32_t N;
62  int32_t actual_k;
63 #ifdef HAVE_PTHREAD
64 
65  PTHREAD_LOCK_T* W_matrix_lock;
66 #endif
67 };
68 
69 class LLE_COVERTREE_POINT
70 {
71 public:
72 
73  LLE_COVERTREE_POINT(int32_t index, const SGMatrix<float64_t>& dmatrix)
74  {
75  point_index = index;
76  distance_matrix = dmatrix;
77  }
78 
79  inline double distance(const LLE_COVERTREE_POINT& p) const
80  {
81  return distance_matrix[point_index*distance_matrix.num_rows+p.point_index];
82  }
83 
84  inline bool operator==(const LLE_COVERTREE_POINT& p) const
85  {
86  return (p.point_index==point_index);
87  }
88 
89  int32_t point_index;
90  SGMatrix<float64_t> distance_matrix;
91 };
92 
93 #endif /* DOXYGEN_SHOULD_SKIP_THIS */
94 
97 {
98  m_k = 10;
99  m_max_k = 60;
100  m_auto_k = false;
101  m_nullspace_shift = -1e-9;
102  m_reconstruction_shift = 1e-3;
103 #ifdef HAVE_ARPACK
104  m_use_arpack = true;
105 #else
106  m_use_arpack = false;
107 #endif
108  init();
109 }
110 
112 {
113  SG_ADD(&m_auto_k, "auto_k",
114  "whether k should be determined automatically in range", MS_AVAILABLE);
115  SG_ADD(&m_k, "k", "number of neighbors", MS_AVAILABLE);
116  SG_ADD(&m_max_k, "max_k",
117  "maximum number of neighbors used to compute optimal one", MS_AVAILABLE);
118  m_parameters->add(&m_nullspace_shift, "nullspace_shift",
119  "nullspace finding regularization shift");
120  SG_ADD(&m_reconstruction_shift, "reconstruction_shift",
121  "shift used to regularize reconstruction step", MS_NOT_AVAILABLE);
122  SG_ADD(&m_use_arpack, "use_arpack", "whether arpack is being used or not",
124 }
125 
126 
128 {
129 }
130 
132 {
133  ASSERT(k>0);
134  m_k = k;
135 }
136 
138 {
139  return m_k;
140 }
141 
143 {
144  ASSERT(max_k>=m_k);
145  m_max_k = max_k;
146 }
147 
149 {
150  return m_max_k;
151 }
152 
154 {
155  m_auto_k = auto_k;
156 }
157 
159 {
160  return m_auto_k;
161 }
162 
164 {
165  m_nullspace_shift = nullspace_shift;
166 }
167 
169 {
170  return m_nullspace_shift;
171 }
172 
174 {
175  m_reconstruction_shift = reconstruction_shift;
176 }
177 
179 {
180  return m_reconstruction_shift;
181 }
182 
184 {
185  m_use_arpack = use_arpack;
186 }
187 
189 {
190  return m_use_arpack;
191 }
192 
194 {
195  return "LocallyLinearEmbedding";
196 }
197 
199 {
200  ASSERT(features);
201  // check features
202  if (!(features->get_feature_class()==C_DENSE &&
203  features->get_feature_type()==F_DREAL))
204  {
205  SG_ERROR("Given features are not of SimpleRealFeatures type.\n");
206  }
207  // shorthand for simplefeatures
208  CDenseFeatures<float64_t>* simple_features = (CDenseFeatures<float64_t>*) features;
209  SG_REF(features);
210 
211  // get and check number of vectors
212  int32_t N = simple_features->get_num_vectors();
213  if (m_k>=N)
214  SG_ERROR("Number of neighbors (%d) should be less than number of objects (%d).\n",
215  m_k, N);
216 
217  // compute distance matrix
218  SG_DEBUG("Computing distance matrix\n");
220  CTime* time = new CTime();
221  time->start();
222  m_distance->init(simple_features,simple_features);
225  SG_DEBUG("Distance matrix computation took %fs\n",time->cur_time_diff());
226  SG_DEBUG("Calculating neighborhood matrix\n");
227  SGMatrix<int32_t> neighborhood_matrix;
228 
229  time->start();
230  if (m_auto_k)
231  {
232  neighborhood_matrix = get_neighborhood_matrix(distance_matrix,m_max_k);
233  m_k = estimate_k(simple_features,neighborhood_matrix);
234  SG_DEBUG("Estimated k with value of %d\n",m_k);
235  }
236  else
237  neighborhood_matrix = get_neighborhood_matrix(distance_matrix,m_k);
238 
239  SG_DEBUG("Neighbors finding took %fs\n",time->cur_time_diff());
240 
241  // init W (weight) matrix
242  float64_t* W_matrix = SG_CALLOC(float64_t, N*N);
243 
244  // construct weight matrix
245  SG_DEBUG("Constructing weight matrix\n");
246  time->start();
247  SGMatrix<float64_t> weight_matrix = construct_weight_matrix(simple_features,W_matrix,neighborhood_matrix);
248  SG_DEBUG("Weight matrix construction took %.5fs\n", time->cur_time_diff());
249 
250  // find null space of weight matrix
251  SG_DEBUG("Finding nullspace\n");
252  time->start();
253  SGMatrix<float64_t> new_feature_matrix = construct_embedding(weight_matrix,m_target_dim);
254  SG_DEBUG("Eigenproblem solving took %.5fs\n", time->cur_time_diff());
255  delete time;
256 
257  SG_UNREF(features);
258  return (CFeatures*)(new CDenseFeatures<float64_t>(new_feature_matrix));
259 }
260 
262 {
263  int32_t right = m_max_k;
264  int32_t left = m_k;
265  int32_t left_third;
266  int32_t right_third;
267  ASSERT(right>=left);
268  if (right==left) return left;
269  int32_t dim;
270  int32_t N;
271  float64_t* feature_matrix= simple_features->get_feature_matrix(dim,N);
272  float64_t* z_matrix = SG_MALLOC(float64_t,right*dim);
273  float64_t* covariance_matrix = SG_MALLOC(float64_t,right*right);
274  float64_t* resid_vector = SG_MALLOC(float64_t, right);
275  float64_t* id_vector = SG_MALLOC(float64_t, right);
276  while (right-left>2)
277  {
278  left_third = (left*2+right)/3;
279  right_third = (right*2+left)/3;
280  float64_t left_val = compute_reconstruction_error(left_third,dim,N,feature_matrix,z_matrix,
281  covariance_matrix,resid_vector,
282  id_vector,neighborhood_matrix);
283  float64_t right_val = compute_reconstruction_error(right_third,dim,N,feature_matrix,z_matrix,
284  covariance_matrix,resid_vector,
285  id_vector,neighborhood_matrix);
286  if (left_val<right_val)
287  right = right_third;
288  else
289  left = left_third;
290  }
291  SG_FREE(z_matrix);
292  SG_FREE(covariance_matrix);
293  SG_FREE(resid_vector);
294  SG_FREE(id_vector);
295  return right;
296 }
297 
299  float64_t* z_matrix, float64_t* covariance_matrix,
300  float64_t* resid_vector, float64_t* id_vector,
301  SGMatrix<int32_t> neighborhood_matrix)
302 {
303  // todo parse params
304  int32_t i,j;
305  float64_t total_residual_norm = 0.0;
306  for (i=CMath::random(0,20); i<N; i+=N/20)
307  {
308  for (j=0; j<k; j++)
309  {
310  cblas_dcopy(dim,feature_matrix+neighborhood_matrix[i*m_k+j]*dim,1,z_matrix+j*dim,1);
311  cblas_daxpy(dim,-1.0,feature_matrix+i*dim,1,z_matrix+j*dim,1);
312  }
313  cblas_dgemm(CblasColMajor,CblasTrans,CblasNoTrans,
314  k,k,dim,
315  1.0,z_matrix,dim,
316  z_matrix,dim,
317  0.0,covariance_matrix,k);
318  for (j=0; j<k; j++)
319  {
320  resid_vector[j] = 1.0;
321  id_vector[j] = 1.0;
322  }
323  if (k>dim)
324  {
325  float64_t trace = 0.0;
326  for (j=0; j<k; j++)
327  trace += covariance_matrix[j*k+j];
328  for (j=0; j<m_k; j++)
329  covariance_matrix[j*k+j] += m_reconstruction_shift*trace;
330  }
331  clapack_dposv(CblasColMajor,CblasLower,k,1,covariance_matrix,k,id_vector,k);
332  float64_t norming=0.0;
333  for (j=0; j<k; j++)
334  norming += id_vector[j];
335  cblas_dscal(k,-1.0/norming,id_vector,1);
336  cblas_dsymv(CblasColMajor,CblasLower,k,-1.0,covariance_matrix,k,id_vector,1,1.0,resid_vector,1);
337  total_residual_norm += cblas_dnrm2(k,resid_vector,1);
338  }
339  return total_residual_norm/k;
340 }
341 
343  float64_t* W_matrix, SGMatrix<int32_t> neighborhood_matrix)
344 {
345  int32_t N = simple_features->get_num_vectors();
346  int32_t dim = simple_features->get_num_features();
347 #ifdef HAVE_PTHREAD
348  int32_t t;
349  int32_t num_threads = parallel->get_num_threads();
350  ASSERT(num_threads>0);
351  // allocate threads
352  pthread_t* threads = SG_MALLOC(pthread_t, num_threads);
353  LINRECONSTRUCTION_THREAD_PARAM* parameters = SG_MALLOC(LINRECONSTRUCTION_THREAD_PARAM, num_threads);
354  pthread_attr_t attr;
355  pthread_attr_init(&attr);
356  pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
357 #else
358  int32_t num_threads = 1;
359 #endif
360  // init storages to be used
361  float64_t* z_matrix = SG_MALLOC(float64_t, m_k*dim*num_threads);
362  float64_t* covariance_matrix = SG_MALLOC(float64_t, m_k*m_k*num_threads);
363  float64_t* id_vector = SG_MALLOC(float64_t, m_k*num_threads);
364 
365  // get feature matrix
366  SGMatrix<float64_t> feature_matrix = simple_features->get_feature_matrix();
367 
368 #ifdef HAVE_PTHREAD
369  PTHREAD_LOCK_T W_matrix_lock;
370  PTHREAD_LOCK_INIT(&W_matrix_lock);
371  for (t=0; t<num_threads; t++)
372  {
373  parameters[t].idx_start = t;
374  parameters[t].idx_step = num_threads;
375  parameters[t].idx_stop = N;
376  parameters[t].m_k = m_k;
377  parameters[t].neighborhood_matrix = neighborhood_matrix.matrix;
378  parameters[t].z_matrix = z_matrix+(m_k*dim)*t;
379  parameters[t].feature_matrix = feature_matrix.matrix;
380  parameters[t].covariance_matrix = covariance_matrix+(m_k*m_k)*t;
381  parameters[t].id_vector = id_vector+m_k*t;
382  parameters[t].W_matrix = W_matrix;
383  parameters[t].m_reconstruction_shift = m_reconstruction_shift;
384  parameters[t].dim = feature_matrix.num_rows;
385  parameters[t].N = feature_matrix.num_cols;
386  parameters[t].actual_k = neighborhood_matrix.num_rows;
387  parameters[t].W_matrix_lock = &W_matrix_lock;
388  pthread_create(&threads[t], &attr, run_linearreconstruction_thread, (void*)&parameters[t]);
389  }
390  for (t=0; t<num_threads; t++)
391  pthread_join(threads[t], NULL);
392  pthread_attr_destroy(&attr);
393  SG_FREE(parameters);
394  SG_FREE(threads);
395 #else
396  LINRECONSTRUCTION_THREAD_PARAM single_thread_param;
397  single_thread_param.idx_start = 0;
398  single_thread_param.idx_step = 1;
399  single_thread_param.idx_stop = N;
400  single_thread_param.m_k = m_k;
401  single_thread_param.neighborhood_matrix = neighborhood_matrix.matrix;
402  single_thread_param.z_matrix = z_matrix;
403  single_thread_param.feature_matrix = feature_matrix.matrix;
404  single_thread_param.covariance_matrix = covariance_matrix;
405  single_thread_param.id_vector = id_vector;
406  single_thread_param.W_matrix = W_matrix;
407  single_thread_param.m_reconstruction_shift = m_reconstruction_shift;
408  run_linearreconstruction_thread((void*)&single_thread_param);
409 #endif
410 
411  // clean
412  SG_FREE(id_vector);
413  SG_FREE(z_matrix);
414  SG_FREE(covariance_matrix);
415 
416  return SGMatrix<float64_t>(W_matrix,N,N);
417 }
418 
420 {
421  int i,j;
422  ASSERT(matrix.num_cols==matrix.num_rows);
423  int N = matrix.num_cols;
424  // get eigenvectors with ARPACK or LAPACK
425  int eigenproblem_status = 0;
426 
427  float64_t* eigenvalues_vector = NULL;
428  float64_t* eigenvectors = NULL;
429  float64_t* nullspace_features = NULL;
430  if (m_use_arpack)
431  {
432 #ifndef HAVE_ARPACK
433  SG_ERROR("ARPACK is not supported in this configuration.\n");
434 #endif
435  // using ARPACK (faster)
436  eigenvalues_vector = SG_MALLOC(float64_t, dimension+1);
437 #ifdef HAVE_ARPACK
438  arpack_dsxupd(matrix.matrix,NULL,false,N,dimension+1,"LA",true,3,true,false,m_nullspace_shift,0.0,
439  eigenvalues_vector,matrix.matrix,eigenproblem_status);
440  matrix.num_rows = dimension+1;
441 #endif
442  if (eigenproblem_status)
443  SG_ERROR("ARPACK failed with code: %d", eigenproblem_status);
444  nullspace_features = SG_MALLOC(float64_t, N*dimension);
445  for (i=0; i<dimension; i++)
446  {
447  for (j=0; j<N; j++)
448  nullspace_features[j*dimension+i] = matrix[j*(dimension+1)+i+1];
449  }
450  SG_FREE(eigenvalues_vector);
451  }
452  else
453  {
454  // using LAPACK (slower)
455  eigenvalues_vector = SG_MALLOC(float64_t, N);
456  eigenvectors = SG_MALLOC(float64_t,(dimension+1)*N);
457  wrap_dsyevr('V','U',N,matrix.matrix,N,2,dimension+2,eigenvalues_vector,eigenvectors,&eigenproblem_status);
458  if (eigenproblem_status)
459  SG_ERROR("LAPACK failed with code: %d", eigenproblem_status);
460  nullspace_features = SG_MALLOC(float64_t, N*dimension);
461  // LAPACKed eigenvectors
462  for (i=0; i<dimension; i++)
463  {
464  for (j=0; j<N; j++)
465  nullspace_features[j*dimension+i] = eigenvectors[i*N+j];
466  }
467  SG_FREE(eigenvectors);
468  SG_FREE(eigenvalues_vector);
469  }
470  return SGMatrix<float64_t>(nullspace_features,dimension,N);
471 }
472 
474 {
475  LINRECONSTRUCTION_THREAD_PARAM* parameters = (LINRECONSTRUCTION_THREAD_PARAM*)p;
476  int32_t idx_start = parameters->idx_start;
477  int32_t idx_step = parameters->idx_step;
478  int32_t idx_stop = parameters->idx_stop;
479  int32_t m_k = parameters->m_k;
480  const int32_t* neighborhood_matrix = parameters->neighborhood_matrix;
481  float64_t* z_matrix = parameters->z_matrix;
482  const float64_t* feature_matrix = parameters->feature_matrix;
483  float64_t* covariance_matrix = parameters->covariance_matrix;
484  float64_t* id_vector = parameters->id_vector;
485  float64_t* W_matrix = parameters->W_matrix;
486  float64_t m_reconstruction_shift = parameters->m_reconstruction_shift;
487 #ifdef HAVE_PTHREAD
488  PTHREAD_LOCK_T* W_matrix_lock = parameters->W_matrix_lock;
489 #endif
490 
491  int32_t i,j,k;
492  int32_t dim = parameters->dim;
493  int32_t N = parameters->N;
494  int32_t actual_k = parameters->actual_k;
495  float64_t norming,trace;
496 
497  for (i=idx_start; i<idx_stop; i+=idx_step)
498  {
499  // compute local feature matrix containing neighbors of i-th vector
500  // center features by subtracting i-th feature column
501  for (j=0; j<m_k; j++)
502  {
503  cblas_dcopy(dim,feature_matrix+neighborhood_matrix[i*actual_k+j]*dim,1,z_matrix+j*dim,1);
504  cblas_daxpy(dim,-1.0,feature_matrix+i*dim,1,z_matrix+j*dim,1);
505  }
506 
507  // compute local covariance matrix
508  cblas_dgemm(CblasColMajor,CblasTrans,CblasNoTrans,
509  m_k,m_k,dim,
510  1.0,z_matrix,dim,
511  z_matrix,dim,
512  0.0,covariance_matrix,m_k);
513 
514  for (j=0; j<m_k; j++)
515  id_vector[j] = 1.0;
516 
517  // regularize in case of ill-posed system
518  if (m_k>dim)
519  {
520  // compute tr(C)
521  trace = 0.0;
522  for (j=0; j<m_k; j++)
523  trace += covariance_matrix[j*m_k+j];
524 
525  for (j=0; j<m_k; j++)
526  covariance_matrix[j*m_k+j] += m_reconstruction_shift*trace;
527  }
528 
529  // solve system of linear equations: covariance_matrix * X = 1
530  // covariance_matrix is a pos-def matrix
531  clapack_dposv(CblasColMajor,CblasLower,m_k,1,covariance_matrix,m_k,id_vector,m_k);
532 
533  // normalize weights
534  norming=0.0;
535  for (j=0; j<m_k; j++)
536  norming += id_vector[j];
537 
538  cblas_dscal(m_k,1.0/norming,id_vector,1);
539 
540  memset(covariance_matrix,0,sizeof(float64_t)*m_k*m_k);
541  cblas_dger(CblasColMajor,m_k,m_k,1.0,id_vector,1,id_vector,1,covariance_matrix,m_k);
542 
543  // put weights into W matrix
544  W_matrix[N*i+i] += 1.0;
545 #ifdef HAVE_PTHREAD
546  PTHREAD_LOCK(W_matrix_lock);
547 #endif
548  for (j=0; j<m_k; j++)
549  {
550  W_matrix[N*i+neighborhood_matrix[i*actual_k+j]] -= id_vector[j];
551  W_matrix[N*neighborhood_matrix[i*actual_k+j]+i] -= id_vector[j];
552  }
553  for (j=0; j<m_k; j++)
554  {
555  for (k=0; k<m_k; k++)
556  W_matrix[N*neighborhood_matrix[i*actual_k+j]+neighborhood_matrix[i*actual_k+k]]+=
557  covariance_matrix[j*m_k+k];
558  }
559 #ifdef HAVE_PTHREAD
560  PTHREAD_UNLOCK(W_matrix_lock);
561 #endif
562  }
563  return NULL;
564 }
565 
567 {
568  int32_t i;
569  int32_t N = distance_matrix.num_rows;
570 
571  int32_t* neighborhood_matrix = SG_MALLOC(int32_t, N*k);
572 
573  float64_t max_dist = SGVector<float64_t>::max(distance_matrix.matrix,N*N);
574 
576 
577  for (i=0; i<N; i++)
578  coverTree->insert(LLE_COVERTREE_POINT(i,distance_matrix));
579 
580  for (i=0; i<N; i++)
581  {
582  std::vector<LLE_COVERTREE_POINT> neighbors =
583  coverTree->kNearestNeighbors(LLE_COVERTREE_POINT(i,distance_matrix),k+1);
584  for (std::size_t m=1; m<unsigned(k+1); m++)
585  neighborhood_matrix[i*k+m-1] = neighbors[m].point_index;
586  }
587 
588  delete coverTree;
589 
590  return SGMatrix<int32_t>(neighborhood_matrix,k,N);
591 }
592 #endif /* HAVE_LAPACK */

SHOGUN Machine Learning Toolbox - Documentation