@@ -130,6 +130,9 @@ Each of the implemented methods is allowed to have additional cv-modifiers
130130The following optimizers can be used with differentiable functions:
131131
132132 * [ L-BFGS] ( #l-bfgs ) (` ens::L_BFGS ` )
133+ * [ Forward-backward splitting (FBS)] ( #forward-backward-splitting-fbs ) (` ens::FBS ` )
134+ * [ Fast Iterative Shrinkage-Thresholding Algorithm (FISTA)] ( #fast-iterative-shrinkage-thresholding-algorithm-fista ) (` ens::FISTA ` )
135+ * [ Fast Adaptive Shrinkage/Thresholding Algorithm (FASTA)] ( #fast-adaptive-shrinkage-thresholding-algorithm-fasta ) (` ens::FASTA ` )
133136 * [ FrankWolfe] ( #frank-wolfe ) (` ens::FrankWolfe ` )
134137 * [ GradientDescent] ( #gradient-descent ) (` ens::GradientDescent ` )
135138 - Any optimizer for [ arbitrary functions] ( #arbitrary-functions )
@@ -210,6 +213,12 @@ class LinearRegressionEWGFunction
210213 g = -2 * data * v;
211214 return arma::accu(v % v); // equivalent to \| v \| ^2
212215 }
216+
217+ private:
218+ // The data.
219+ const arma::mat& data;
220+ // The responses to each data point.
221+ const arma::rowvec& responses;
213222};
214223
215224int main()
@@ -249,7 +258,7 @@ int main()
249258 const double time1 = clock.toc();
250259
251260 std::cout << "LinearRegressionFunction with Evaluate() and Gradient() took "
252- << time1 << " seconds to converge to the model: " << std::endl;
261+ << time1 << " seconds to converge to the model: " << std::endl;
253262 std::cout << lrf1Params.t();
254263
255264 // Create the second objective function, which uses EvaluateWithGradient().
@@ -273,6 +282,150 @@ int main()
273282
274283</details>
275284
285+ ### Proximal operators and non-differentiable functions
286+
287+ Some optimization problems involve non-differentiable components, and can be
288+ expressed as the optimization below:
289+
290+ $$ \operatorname{argmin}_x h(x) = \operatorname{argmin}_x f(x) + g(x). $$
291+
292+ Here, `f(x)` is a regular differentiable function (as in the previous
293+ subsection), and `g(x)` is a non-differentiable arbitrary function. These
294+ classes of functions can still be optimized using *proximal gradient
295+ optimizers*. The following proximal gradient optimizers are implemented in
296+ ensmallen (these can also optimize differentiable functions only, taking `g(x) =
297+ 0`):
298+
299+ * [Forward-backward splitting (FBS)](#forward-backward-splitting-fbs) (`ens::FBS`)
300+ * [Fast Iterative Shrinkage-Thresholding Algorithm (FISTA)](#fast-iteartive-shrinkage-thresholding-algorithm-fista) (`ens::FISTA`)
301+ * [Fast Adaptive Shrinkage/Thresholding Algorithm (FASTA)](#fast-adaptive-shrinkage-thresholding-algorithm-fasta) (`ens::FASTA`)
302+
303+ ensmallen implements a few `g(x)` options that can be used with proximal
304+ gradient optimizers:
305+
306+ * `L1Penalty(`_`lambda`_`)`: $g(x) = \lambda \| x \|_1$
307+ * `L1Constraint(`_`lambda`_`)`: $g(x)$ is the constraint $\| x \|_1 \le \lambda$
308+
309+ For example, by pairing `L1Penalty` with the `LinearRegressionFunction` from the
310+ previous section, we can implement L1-penalized (sparse) linear regression:
311+
312+ <details>
313+ <summary>Click to collapse/expand example code.
314+ </summary>
315+
316+ ```c++
317+ #include <ensmallen.hpp>
318+
319+ // Define a differentiable objective function by implementing only
320+ // EvaluateWithGradient().
321+ class LinearRegressionEWGFunction
322+ {
323+ public:
324+ // Construct the object with the given data matrix and responses.
325+ LinearRegressionEWGFunction(const arma::mat& dataIn,
326+ const arma::rowvec& responsesIn) :
327+ data(dataIn), responses(responsesIn) { }
328+
329+ // Simultaneously compute both the objective function and gradient for model
330+ // parameters x. Note that this is faster than implementing Evaluate() and
331+ // Gradient() individually because it caches the computation of
332+ // (responses - x.t() * data)!
333+ double EvaluateWithGradient(const arma::mat& x, arma::mat& g)
334+ {
335+ const arma::rowvec v = (responses - x.t() * data);
336+ g = -2 * data * v;
337+ return arma::accu(v % v); // equivalent to \| v \|^2
338+ }
339+
340+ private:
341+ // The data.
342+ const arma::mat& data;
343+ // The responses to each data point.
344+ const arma::rowvec& responses;
345+ };
346+
347+ int main()
348+ {
349+ // First, generate some random data, with 1000 points and 500 dimensions.
350+ // This data has no pattern and as such will make a model that's not very
351+ // useful---but the purpose here is just demonstration. :)
352+ //
353+ // For a more "real world" situation, load a dataset from file using X.load()
354+ // and y.load() (but make sure the matrix is column-major, so that each
355+ // observation/data point corresponds to a *column*, *not* a row.
356+ arma::mat data(500, 1000, arma::fill::randn);
357+ arma::rowvec responses(1000, arma::fill::randn);
358+
359+ // Create a starting point for our optimization as the vector of all zeros.
360+ // The model has 500 parameters, so the shape is 500x1.
361+ arma::mat startingPoint(500, 1, arma::fill::zeros);
362+
363+ // Construct the objective function f(x), and the penalty function g(x) with a
364+ // lambda value of 0.1.
365+ LinearRegressionEWGFunction lrf(data, responses);
366+ ens::L1Penalty g(0.1);
367+
368+ // Create the FBS optimizer with default parameters, and optimize the function
369+ // f(x) + g(x) (i.e. L1-penalized linear regression).
370+ // The ens::FBS class can be replaced with any ensmallen proximal gradient
371+ // optimizer.
372+ ens::FBS fbs(g);
373+ arma::mat lrfParams(startingPoint);
374+ fbs.Optimize(lrf, lrfParams);
375+
376+ // Count the number of nonzeros in the final model.
377+ // To get fewer nonzeros, the penalty value (0.1) could be increased.
378+ std::cout << "Number of nonzeros in optimized parameter vector: "
379+ << arma::accu(lrfParams != 0) << "." << std::endl;
380+ }
381+ ```
382+
383+ </details >
384+
385+ It is possible to implement a custom proximal operator (` g(x) ` ). To do so, a
386+ class with two methods (` Evaluate() ` and ` BackwardStep() ` ) must be defined:
387+
388+ <details open >
389+ <summary >Click to collapse/expand example code.
390+ </summary >
391+
392+ ``` c++
393+ // Compute the value of g(x).
394+ double Evaluate (const arma::mat& x);
395+
396+ // Perform a backward step (proximal step) on ` x ` , given that the forward step
397+ // size was ` stepSize ` .
398+ void BackwardStep(arma::mat& x, const double stepSize);
399+ ```
400+
401+ </details>
402+
403+ A simple implementation is below for the `L1Penalty` class:
404+
405+ <details open>
406+ <summary>Click to collapse/expand example code.
407+ </summary>
408+
409+ ```c++
410+ double L1Penalty::Evaluate(const arma::mat& coordinates) const
411+ {
412+ // Compute the L1 penalty.
413+ return norm(vectorise(coordinates), 1) * lambda;
414+ }
415+
416+ void L1Penalty::ProximalStep(arma::mat& coordinates,
417+ const double stepSize) const
418+ {
419+ // Apply the backwards step coordinate-wise.
420+ // (See Goldstein, Studer, and Baraniuk 2009, eq. (12).)
421+ coordinates.transform([this, stepSize](double val) { return (val > 0.0) ?
422+ (std::max(0.0, val - lambda * stepSize)) :
423+ (std::min(0.0, val + lambda * stepSize)); });
424+ }
425+ ```
426+
427+ </details >
428+
276429### Partially differentiable functions
277430
278431Some differentiable functions have the additional property that the gradient
0 commit comments