iPiano
An implementation of the iPiano algorithms for non-convex and non-smooth optimization.
 All Classes Functions Variables
nmipiano.h
1 
41 #ifndef NMIPIANO_H
42 #define NMIPIANO_H
43 
44 #include <random>
45 #include <fstream>
46 #include <functional>
47 #include <Eigen/Dense>
48 #include <glog/logging.h>
49 
56 class nmiPiano
57 {
58 public:
59 
61  struct Options
62  {
64  Eigen::MatrixXf x_0;
66  unsigned int max_iter;
68  float beta = 0.5f;
70  float eta = 1.05f;
72  float L_0m1 = 1.f;
74  bool BOUND_L_N = false;
76  float epsilon = 0;
77  };
78 
81  struct Iteration
82  {
84  Eigen::MatrixXf x_n;
86  Eigen::MatrixXf x_nm1;
88  Eigen::MatrixXf Delta_x_n;
90  float Delta_n;
92  float f_x_n;
94  Eigen::MatrixXf df_x_n;
96  float g_x_n;
98  float L_n;
100  float alpha_n;
102  int n;
103  };
104 
108  static void silent_callback(const Iteration &iteration);
109 
113  static void default_callback(const Iteration &iteration, int n = 10);
114 
118  static void brief_callback(const Iteration &iteration, int n = 10);
119 
124  static void file_callback(const Iteration &iteration, const std::string &file);
125 
130  static void plot_callback(const Iteration &iteration, const std::string &file);
131 
138  nmiPiano(const std::function<float(const Eigen::MatrixXf&)> f,
139  const std::function<void(const Eigen::MatrixXf&, Eigen::MatrixXf&)> df,
140  std::function<float(const Eigen::MatrixXf&)> g,
141  const std::function<void(const Eigen::MatrixXf&, Eigen::MatrixXf&, float alpha)> prox_g,
142  Options options,
143  const std::function<void(const Iteration&)> callback = [](const Iteration &iteration) -> void { /* Nothing ... */ });
144 
146  ~nmiPiano();
147 
152  void optimize(Eigen::MatrixXf &x_star, float &f_x_star);
153 
154 protected:
155 
159  void initialize(Iteration &iteration);
160 
161  float estimateL(const Iteration &iteration);
162 
168  void iterate(Iteration &iteration, float beta, Eigen::MatrixXf &x_np1);
169 
175  bool checkLipschitz(const Iteration& iteration, const Eigen::MatrixXf &x_np1);
176 
178  std::function<float(const Eigen::MatrixXf&)> f_;
180  std::function<void(const Eigen::MatrixXf&, Eigen::MatrixXf&)> df_;
182  std::function<float(const Eigen::MatrixXf&)> g_;
184  std::function<void(const Eigen::MatrixXf&, Eigen::MatrixXf&, float)> prox_g_;
186  std::function<void(const Iteration&)> callback_;
189 };
190 
192 // nmiPiano::silent_callback
194 
196 {
197 
198 }
199 
201 // nmiPiano::default_callback
203 
204 void nmiPiano::default_callback(const nmiPiano::Iteration &iteration, int n)
205 {
206  if (iteration.n%n == 0)
207  {
208  std::cout << "[" << iteration.n << "] "
209  << iteration.f_x_n + iteration.g_x_n
210  << " (Delta_n = " << iteration.Delta_n << "; L_n = " << iteration.L_n
211  << "; alpha_n = " << iteration.alpha_n << ")"
212  << std::endl;
213  }
214 }
215 
217 // nmiPiano::brief_callback
219 
220 void nmiPiano::brief_callback(const nmiPiano::Iteration &iteration, int n)
221 {
222  if (iteration.n%n == 0)
223  {
224  std::cout << "[" << iteration.n << "] "
225  << iteration.f_x_n + iteration.g_x_n
226  << " (Delta_n = " << iteration.Delta_n << ")"
227  << std::endl;
228  }
229 }
230 
232 // nmiPiano::file_callback
234 
235 void nmiPiano::file_callback(const nmiPiano::Iteration &iteration, const std::string &file)
236 {
237  std::ofstream out;
238  out.open(file, std::ios_base::app);
239  out << "iteration = " << iteration.n << "; f_x_n = " << iteration.f_x_n
240  << "; g_x_n = " << iteration.g_x_n << "; alpha_n = " << iteration.alpha_n
241  << "; L_n = " << iteration.L_n
242  << "; |df_x_n| = " << std::sqrt(iteration.df_x_n.squaredNorm())
243  << "; Delta_n = " << iteration.Delta_n << std::endl;
244  out.close();
245 }
246 
248 // nmiPiano::plot_callback
250 
251 void nmiPiano::plot_callback(const nmiPiano::Iteration &iteration, const std::string &file)
252 {
253  std::ofstream out;
254  out.open(file, std::ios_base::app);
255  out << iteration.n << "," << iteration.f_x_n + iteration.g_x_n
256  << "," << iteration.f_x_n << "," << iteration.g_x_n
257  << "," << iteration.alpha_n << "," << iteration.L_n
258  << "," << iteration.Delta_n << std::endl;
259  out.close();
260 }
261 
263 // nmiPiano::nmiPiano
265 
266 nmiPiano::nmiPiano(const std::function<float(const Eigen::MatrixXf&)> f,
267  const std::function<void(const Eigen::MatrixXf&,Eigen::MatrixXf&)> df,
268  std::function<float(const Eigen::MatrixXf &)> g,
269  const std::function<void(const Eigen::MatrixXf&, Eigen::MatrixXf&, float alpha)> prox_g,
270  nmiPiano::Options options,
271  const std::function<void(const nmiPiano::Iteration&)> callback)
272 {
273  f_ = f;
274  df_ = df;
275  g_ = g;
276  prox_g_ = prox_g;
277  callback_ = callback;
278  options_ = options;
279 }
280 
282 // nmiPiano::~nmiPiano
284 
286 {
287  // ...
288 }
289 
291 // nmiPiano::optimize
293 
294 void nmiPiano::optimize(Eigen::MatrixXf &x_star, float &f_x_star)
295 {
296  nmiPiano::Iteration iteration;
297  initialize(iteration);
298 
299  // Used for backtracking; referring to the next iterate x_np1:
300  Eigen::MatrixXf x_np1;
301 
302  for (unsigned int t = 0; t <= options_.max_iter; t++)
303  {
304  callback_(iteration);
305 
306  // Backtrack for the lipschitz constant L_n:
307  float L_nm1 = estimateL(iteration);
308 
309  // Fall back to last L_n in worst case.
310  if (std::isinf(L_nm1) || std::isnan(L_nm1) || options_.BOUND_L_N)
311  {
312  LOG_IF(INFO, !options_.BOUND_L_N) << "Could not get starting value for local Lipschitz constant, using L_nm1 instead (L_n = " << L_nm1 << ")";
313  L_nm1 = iteration.L_n;
314  }
315 
316  bool condition = false;
317 
318  int l = 0; // Backtracking steps.
319  do
320  {
321  iteration.L_n = std::pow(options_.eta, l)*L_nm1;
322 
323  LOG_IF(FATAL, std::isinf(iteration.L_n) || std::isnan(iteration.L_n))
324  << "Could not find the local Lipschitz constant - L_n = "
325  << iteration.L_n << std::endl;
326  LOG_IF(INFO, l > 0 && l%1000 == 0)
327  << "Having a hard time finding the local Lipschitz constant (L_n = " << iteration.L_n
328  << "; L_nm1 = " << L_nm1 << "; eta = " << options_.eta << "; l = " << l << ")" << std::endl;
329 
330  iteration.alpha_n = 2*(1 - options_.beta)/iteration.L_n;
331  LOG_IF(FATAL, iteration.alpha_n < 0)
332  << "Cannot choose alpha_n - it does not exist: alpha_n = "
333  << iteration.alpha_n << " (L_n = " << iteration.L_n << ")!";
334 
335 
336  // We fixes L_n and alpha_n, so we can try an iteration to check the
337  // Lipschitz condition.
338  iterate(iteration, options_.beta, x_np1);
339  condition = checkLipschitz(iteration, x_np1);
340 
341  l++;
342  }
343  while (!condition); // Now alpha_n and L_n are set correctly.
344 
345  iteration.x_nm1 = iteration.x_n;
346  iteration.x_n = x_np1;
347 
348  // For iteration 0, this is done in initialize():
349  iteration.f_x_n = f_(iteration.x_n);
350  iteration.g_x_n = g_(iteration.x_n);
351  df_(iteration.x_n, iteration.df_x_n);
352 
353  LOG_IF(FATAL, iteration.x_n.rows() != iteration.df_x_n.rows()
354  || iteration.x_n.cols() != iteration.df_x_n.cols()) << "Output dimensions of df invalid.";
355 
356  iteration.n++;
357 
358  // Termination criterion
359  if (iteration.Delta_n < options_.epsilon && options_.epsilon > 0)
360  {
361  break;
362  }
363  }
364 
365  x_star = iteration.x_n;
366  f_x_star = iteration.f_x_n;
367 }
368 
370 // nmiPiano::initialize
372 
374 {
375  iteration.n = 0;
376  iteration.x_n = options_.x_0;
377  iteration.x_nm1 = options_.x_0;
378 
379  iteration.g_x_n = g_(iteration.x_n);
380  iteration.f_x_n = f_(iteration.x_n);
381  df_(iteration.x_n, iteration.df_x_n);
382 
383  LOG_IF(FATAL, iteration.x_n.rows() != iteration.df_x_n.rows()
384  || iteration.x_n.cols() != iteration.df_x_n.cols()) << "Output dimensions of df invalid.";
385 
386  iteration.alpha_n = 0.1f; // Required for estimateL!
387 
388  // Estimate L_n.
389  if (options_.L_0m1 > 0)
390  {
391  iteration.L_n = options_.L_0m1;
392  }
393 
394  // Estimate L and take max.
395  if (iteration.df_x_n.squaredNorm() > 0.001)
396  {
397  iteration.L_n = std::max(iteration.L_n, estimateL(iteration));
398  }
399 
400  iteration.alpha_n = 2*(1 - options_.beta)/iteration.L_n;
401 }
402 
403 float nmiPiano::estimateL(const nmiPiano::Iteration &iteration)
404 {
405  Eigen::MatrixXf x_tilde = iteration.x_n - iteration.alpha_n*iteration.df_x_n;
406 
407  Eigen::MatrixXf df_x_tilde;
408  df_(x_tilde, df_x_tilde);
409 
410  Eigen::MatrixXf delta_df_x = iteration.df_x_n - df_x_tilde;
411  Eigen::MatrixXf delta_x = iteration.x_n - x_tilde;
412 
413  float L_n = std::sqrt(delta_df_x.squaredNorm())/std::sqrt(delta_x.squaredNorm ());
414  if (options_.BOUND_L_N) {
415  L_n = std::max(L_n, options_.L_0m1);
416  }
417 
418  return L_n;
419 }
420 
422 // nmiPiano::iterate
424 
425 void nmiPiano::iterate(nmiPiano::Iteration& iteration, float beta, Eigen::MatrixXf& x_np1)
426 {
427  // TODO: precompute x_n - x_nm1
428  iteration.Delta_x_n = - iteration.alpha_n*iteration.df_x_n + beta*(iteration.x_n - iteration.x_nm1);
429 
430  prox_g_(iteration.x_n + iteration.Delta_x_n, x_np1, iteration.alpha_n);
431 
432  LOG_IF(FATAL, iteration.x_n.rows() != x_np1.rows()
433  || iteration.x_n.cols() != x_np1.cols()) << "Output dimensions of prox_g invalid.";
434 
435  Eigen::MatrixXf Delta_n = iteration.x_n - x_np1;
436  iteration.Delta_n = std::sqrt(Delta_n.squaredNorm());
437 }
438 
440 // nmiPiano::checkLipschitz
442 
443 bool nmiPiano::checkLipschitz(const nmiPiano::Iteration& iteration, const Eigen::MatrixXf& x_np1)
444 {
445  float f_x_np1 = f_(x_np1);
446 
447  Eigen::MatrixXf residual = x_np1 - iteration.x_n;
448  Eigen::MatrixXf product = iteration.df_x_n.array()*residual.array();
449  float threshold = iteration.f_x_n + product.squaredNorm() + iteration.L_n/2.f * residual.squaredNorm();
450 
451  return (f_x_np1 <= threshold);
452 }
453 
454 #endif /* NMIPIANO_H */
455 
float L_n
L_n of current iterate (within iterations, this is also used to estimate L_np1 via backtracking)...
Definition: nmipiano.h:98
std::function< float(const Eigen::MatrixXf &)> g_
Convex, nonsmooth part of objective.
Definition: nmipiano.h:182
float f_x_n
Smooth objective function at current iterate.
Definition: nmipiano.h:92
Eigen::MatrixXf Delta_x_n
Update: x_np1 = x_n + Delta_x_n.
Definition: nmipiano.h:88
~nmiPiano()
Destructor.
Definition: nmipiano.h:285
void optimize(Eigen::MatrixXf &x_star, float &f_x_star)
Optimize the given objective using the nmiPiano algorithm.
Definition: nmipiano.h:294
Implementation of nmiPiano (algorithm 4) as proposed in [1]: [1] P. Ochs, Y. Chen, T. Brox, T. Pock. iPiano: Inertial Proximal Algorithm for Nonconvex Optimization. SIAM J. Imaging Sciences, vol. 7, no. 2, 2014.
Definition: nmipiano.h:56
int n
Current iteration.
Definition: nmipiano.h:102
void initialize(Iteration &iteration)
Initialize the iteration structure (i.e. set iteration 0):
Definition: nmipiano.h:373
float epsilon
Termination criterion; stop if Delta_n smaller than epsilon.
Definition: nmipiano.h:76
Eigen::MatrixXf df_x_n
Derivative of smooth objective at current iterate.
Definition: nmipiano.h:94
static void file_callback(const Iteration &iteration, const std::string &file)
File callback, used to write all information to the specified file.
Definition: nmipiano.h:235
float eta
Fixed eta for backtracking the local lipschitz constant.
Definition: nmipiano.h:70
std::function< void(const Eigen::MatrixXf &, Eigen::MatrixXf &)> df_
Gradient of smooth part of the objective function (i.e. derivative).
Definition: nmipiano.h:180
static void brief_callback(const Iteration &iteration, int n=10)
Brief callback to use with nmiPiano.
Definition: nmipiano.h:220
static void default_callback(const Iteration &iteration, int n=10)
Default callback to use with nmiPiano.
Definition: nmipiano.h:204
static void plot_callback(const Iteration &iteration, const std::string &file)
File callback for plotting, writes CSV output of format: iteration,f,g,alpha_n,L_n,Delta_n.
Definition: nmipiano.h:251
float beta
Fixed beta in [0, 1).
Definition: nmipiano.h:68
void iterate(Iteration &iteration, float beta, Eigen::MatrixXf &x_np1)
Do an iteration, i.e. compute x_np1.
Definition: nmipiano.h:425
static void silent_callback(const Iteration &iteration)
Silent callback to use with nmiPiano.
Definition: nmipiano.h:195
Eigen::MatrixXf x_nm1
Last iterate.
Definition: nmipiano.h:86
std::function< void(const Eigen::MatrixXf &, Eigen::MatrixXf &, float)> prox_g_
Proximal map for g, i.e. (I + alpha_n*g)^(-1).
Definition: nmipiano.h:184
float alpha_n
alpha_n of current iterate (within iterations, this is also used to estimate alpha_np1).
Definition: nmipiano.h:100
bool checkLipschitz(const Iteration &iteration, const Eigen::MatrixXf &x_np1)
Check the lipschitz condition for backtracking.
Definition: nmipiano.h:443
float g_x_n
Definition: nmipiano.h:96
Eigen::MatrixXf x_0
Initial iterate.
Definition: nmipiano.h:64
unsigned int max_iter
Maximum number of iterations.
Definition: nmipiano.h:66
Options options_
Options.
Definition: nmipiano.h:188
std::function< float(const Eigen::MatrixXf &)> f_
The smooth part of the objective function.
Definition: nmipiano.h:178
float Delta_n
Difference between two iterates.
Definition: nmipiano.h:90
std::function< void(const Iteration &)> callback_
Callback, can e.g. be sued to monitor process using the provided information in the Iteration structu...
Definition: nmipiano.h:186
Options of algorithm.
Definition: nmipiano.h:61
Eigen::MatrixXf x_n
Current iterate.
Definition: nmipiano.h:84
float L_0m1
Initialization of loca Lipschitz.
Definition: nmipiano.h:72
bool BOUND_L_N
Whether to bound estimated Lipschitz constant below by the given L_n.
Definition: nmipiano.h:74
nmiPiano(const std::function< float(const Eigen::MatrixXf &)> f, const std::function< void(const Eigen::MatrixXf &, Eigen::MatrixXf &)> df, std::function< float(const Eigen::MatrixXf &)> g, const std::function< void(const Eigen::MatrixXf &, Eigen::MatrixXf &, float alpha)> prox_g, Options options, const std::function< void(const Iteration &)> callback=[](const Iteration &iteration) -> void{})
Constructor.
Definition: nmipiano.h:266
Structure representing an iteration, passed as is to a callback function to be able to monitor proces...
Definition: nmipiano.h:81