1
2 """
3 The :mod:`parsimony.functions.penalties` module contains the penalties used to
4 constrain the loss functions. These represent mathematical functions and
5 should thus have properties used by the corresponding algorithms. These
6 properties are defined in :mod:`parsimony.functions.properties`.
7
8 Penalties should be stateless. Penalties may be shared and copied and should
9 therefore not hold anything that cannot be recomputed the next time it is
10 called.
11
12 Created on Mon Apr 22 10:54:29 2013
13
14 Copyright (c) 2013-2014, CEA/DSV/I2BM/Neurospin. All rights reserved.
15
16 @author: Tommy Löfstedt, Vincent Guillemot, Edouard Duchesnay and
17 Fouad Hadj-Selem
18 @email: lofstedt.tommy@gmail.com, edouard.duchesnay@cea.fr
19 @license: BSD 3-clause.
20 """
21 import time
22
23 import numpy as np
24 import scipy.sparse as sparse
25
26 try:
27 from . import properties
28 except ValueError:
29 import parsimony.functions.properties as properties
30 import parsimony.utils.maths as maths
31 import parsimony.utils.consts as consts
32 import parsimony.utils.linalgs as linalgs
33
34 __all__ = ["ZeroFunction", "L1", "L0", "LInf", "L2", "L2Squared",
35 "L1L2Squared",
36 "QuadraticConstraint", "RGCCAConstraint", "RidgeSquaredError",
37 "LinearVariableConstraint",
38 "SufficientDescentCondition"]
39
40
41 -class ZeroFunction(properties.AtomicFunction,
42 properties.Gradient,
43 properties.Penalty,
44 properties.Constraint,
45 properties.ProximalOperator,
46 properties.ProjectionOperator):
47
48 - def __init__(self, l=1.0, c=0.0, penalty_start=0):
49 """
50 Parameters
51 ----------
52 l : Non-negative float. The Lagrange multiplier, or regularisation
53 constant, of the function.
54
55 c : Float. The limit of the constraint. The function is feasible if
56 ||\beta||_1 <= c. The default value is c=0, i.e. the default is
57 a regularisation formulation.
58
59 penalty_start : Non-negative integer. The number of columns, variables
60 etc., to be exempt from penalisation. Equivalently, the first
61 index to be penalised. Default is 0, all columns are included.
62 """
63 self.l = float(l)
64 self.c = float(c)
65 if self.c < 0.0:
66 raise ValueError("A negative constraint parameter does not make " \
67 "sense, since the function is always zero.")
68 self.penalty_start = int(penalty_start)
69
70 self.reset()
71
75
77 """Function value.
78 """
79 return 0.0
80
82 """Gradient of the function.
83
84 From the interface "Gradient".
85 """
86 if self._zero is None:
87 self._zero = np.zeros(x.shape)
88
89 return self._zero
90
91 - def prox(self, x, factor=1.0, **kwargs):
92 """The corresponding proximal operator.
93
94 From the interface "ProximalOperator".
95 """
96 return x
97
99 """The corresponding projection operator.
100
101 From the interface "ProjectionOperator".
102 """
103 return x
104
106 """Feasibility of the constraint.
107
108 From the interface "Constraint".
109 """
110 return self.c >= 0.0
111
112
113 -class L1(properties.AtomicFunction,
114 properties.Penalty,
115 properties.Constraint,
116 properties.ProximalOperator,
117 properties.ProjectionOperator):
118 """The proximal operator of the L1 function with a penalty formulation
119
120 f(\beta) = l * (||\beta||_1 - c),
121
122 where ||\beta||_1 is the L1 loss function. The constrained version has the
123 form
124
125 ||\beta||_1 <= c.
126
127 Parameters
128 ----------
129 l : Non-negative float. The Lagrange multiplier, or regularisation
130 constant, of the function.
131
132 c : Float. The limit of the constraint. The function is feasible if
133 ||\beta||_1 <= c. The default value is c=0, i.e. the default is a
134 regularisation formulation.
135
136 penalty_start : Non-negative integer. The number of columns, variables
137 etc., to be exempt from penalisation. Equivalently, the first index
138 to be penalised. Default is 0, all columns are included.
139 """
140 - def __init__(self, l=1.0, c=0.0, penalty_start=0):
141
142 self.l = float(l)
143 self.c = float(c)
144 self.penalty_start = int(penalty_start)
145
147 """Function value.
148 """
149 if self.penalty_start > 0:
150 beta_ = beta[self.penalty_start:, :]
151 else:
152 beta_ = beta
153
154 return self.l * (maths.norm1(beta_) - self.c)
155
156 - def prox(self, beta, factor=1.0, **kwargs):
157 """The corresponding proximal operator.
158
159 From the interface "ProximalOperator".
160 """
161 l = self.l * factor
162 if self.penalty_start > 0:
163 beta_ = beta[self.penalty_start:, :]
164 else:
165 beta_ = beta
166
167 prox = (np.abs(beta_) > l) * (beta_ - l * np.sign(beta_ - l))
168
169 if self.penalty_start > 0:
170 prox = np.vstack((beta[:self.penalty_start, :], prox))
171
172 return prox
173
174 - def proj(self, beta, **kwargs):
175 """The corresponding projection operator.
176
177 From the interface "ProjectionOperator".
178 """
179 if self.penalty_start > 0:
180 beta_ = beta[self.penalty_start:, :]
181 else:
182 beta_ = beta
183
184 p = beta_.shape[0]
185
186 abs_beta = np.absolute(beta_)
187 norm1 = np.sum(abs_beta)
188
189 if norm1 <= self.c:
190 return beta
191
192 a = np.flipud(np.sort(abs_beta, axis=0)).ravel()
193 suma = np.cumsum(a)
194
195 phi = np.zeros((p + 1,))
196 np.multiply(a, np.arange(-1, -p - 1, -1), phi[:p])
197 phi[:p] += (suma - self.c)
198 phi[p] = suma[p - 1] - self.c
199
200
201 i = np.searchsorted(phi, 0.0)
202 if phi[i] < 0.0:
203
204 return self.__proj_old(beta)
205 i -= 1
206 if phi[i] >= 0.0:
207
208 return self.__proj_old(beta)
209
210 l = a[i] + phi[i] / float(i + 1)
211
212
213 eps = consts.FLOAT_EPSILON
214 l += eps
215
216 return (np.abs(beta_) > l) * (beta_ - l * np.sign(beta_ - l))
217
219 """The corresponding projection operator.
220
221 From the interface "ProjectionOperator".
222 """
223 if self.penalty_start > 0:
224 beta_ = beta[self.penalty_start:, :]
225 else:
226 beta_ = beta
227
228 abs_beta = np.absolute(beta_)
229 norm1 = np.sum(abs_beta)
230
231 if norm1 <= self.c:
232 return beta
233
234 from parsimony.algorithms.explicit import Bisection
235 bisection = Bisection(force_negative=True,
236 parameter_positive=True,
237 parameter_negative=False,
238 parameter_zero=False,
239 eps=1e-8)
240
241 class F(properties.Function):
242 def __init__(self, beta, c):
243 self.beta = beta
244 self.c = c
245
246 def f(self, l):
247 beta = (abs_beta > l) \
248 * (self.beta - l * np.sign(self.beta - l))
249
250 return maths.norm1(beta) - self.c
251
252 func = F(beta_, self.c)
253 l = bisection.run(func, [0.0, np.max(np.abs(beta_))])
254
255 return (abs_beta > l) * (beta_ - l * np.sign(beta_ - l))
256
258 """Feasibility of the constraint.
259
260 From the interface "Constraint".
261
262 Parameters
263 ----------
264 beta : Numpy array. The variable to check for feasibility.
265 """
266 if self.penalty_start > 0:
267 beta_ = beta[self.penalty_start:, :]
268 else:
269 beta_ = beta
270
271 return maths.norm1(beta_) <= self.c
272
273
274 -class L0(properties.AtomicFunction,
275 properties.Penalty,
276 properties.Constraint,
277 properties.ProximalOperator,
278 properties.ProjectionOperator):
279 """The proximal operator of the "pseudo" L0 function
280
281 f(x) = l * (||x||_0 - c),
282
283 where ||x||_0 is the L0 loss function. The constrainted version has the
284 form
285
286 ||x||_0 <= c.
287
288 Warning: Note that this function is not convex, and the regular assumptions
289 when using it in e.g. ISTA or FISTA will not apply. Nevertheless, it will
290 still converge to a local minimum if we can guarantee that we obtain a
291 reduction of the smooth part in each step. See e.g.:
292
293 http://eprints.soton.ac.uk/142499/1/BD_NIHT09.pdf
294 http://people.ee.duke.edu/~lcarin/blumensath.pdf
295
296 Parameters
297 ----------
298 l : Non-negative float. The Lagrange multiplier, or regularisation
299 constant, of the function.
300
301 c : Float. The limit of the constraint. The function is feasible if
302 ||x||_0 <= c. The default value is c=0, i.e. the default is a
303 regularisation formulation.
304
305 penalty_start : Non-negative integer. The number of columns, variables
306 etc., to be exempt from penalisation. Equivalently, the first index
307 to be penalised. Default is 0, all columns are included.
308 """
309 - def __init__(self, l=1.0, c=0.0, penalty_start=0):
310
311 self.l = float(l)
312 self.c = float(c)
313 self.penalty_start = int(penalty_start)
314
316 """Function value.
317
318 From the interface "Function".
319
320 Example
321 -------
322 >>> import numpy as np
323 >>> from parsimony.functions.penalties import L0
324 >>> import parsimony.utils.maths as maths
325 >>>
326 >>> np.random.seed(42)
327 >>> x = np.random.rand(10, 1)
328 >>> l0 = L0(l=0.5)
329 >>> maths.norm0(x)
330 10
331 >>> l0.f(x) - 0.5 * maths.norm0(x)
332 0.0
333 >>> x[0, 0] = 0.0
334 >>> maths.norm0(x)
335 9
336 >>> l0.f(x) - 0.5 * maths.norm0(x)
337 0.0
338 """
339 if self.penalty_start > 0:
340 x_ = x[self.penalty_start:, :]
341 else:
342 x_ = x
343
344 return self.l * (maths.norm0(x_) - self.c)
345
346 - def prox(self, x, factor=1.0, **kwargs):
347 """The corresponding proximal operator.
348
349 From the interface "ProximalOperator".
350
351 Example
352 -------
353 >>> import numpy as np
354 >>> from parsimony.functions.penalties import L0
355 >>> import parsimony.utils.maths as maths
356 >>>
357 >>> np.random.seed(42)
358 >>> x = np.random.rand(10, 1)
359 >>> l0 = L0(l=0.5)
360 >>> maths.norm0(x)
361 10
362 >>> l0.prox(x)
363 array([[ 0. ],
364 [ 0.95071431],
365 [ 0.73199394],
366 [ 0.59865848],
367 [ 0. ],
368 [ 0. ],
369 [ 0. ],
370 [ 0.86617615],
371 [ 0.60111501],
372 [ 0.70807258]])
373 >>> l0.f(l0.prox(x))
374 3.0
375 >>> 0.5 * maths.norm0(l0.prox(x))
376 3.0
377 """
378 if self.penalty_start > 0:
379 x_ = x[self.penalty_start:, :]
380 else:
381 x_ = x
382
383 l = self.l * factor
384 prox = x_ * (np.abs(x_) > l)
385 prox = np.vstack((x[:self.penalty_start, :],
386 prox))
387
388 return prox
389
391 """The corresponding projection operator.
392
393 From the interface "ProjectionOperator".
394
395 Examples
396 --------
397 >>> import numpy as np
398 >>> from parsimony.functions.penalties import L0
399 >>>
400 >>> np.random.seed(42)
401 >>> x = np.random.rand(10, 1) * 2.0 - 1.0
402 >>> l0 = L0(c=5.0)
403 >>> l0.proj(x)
404 array([[ 0. ],
405 [ 0.90142861],
406 [ 0. ],
407 [ 0. ],
408 [-0.68796272],
409 [-0.68801096],
410 [-0.88383278],
411 [ 0.73235229],
412 [ 0. ],
413 [ 0. ]])
414 """
415 if self.penalty_start > 0:
416 x_ = x[self.penalty_start:, :]
417 else:
418 x_ = x
419
420 if maths.norm0(x_) <= self.c:
421 return x
422
423 K = int(np.floor(self.c) + 0.5)
424 ind = np.abs(x_.ravel()).argsort()[:K]
425 y = np.copy(x_)
426 y[ind] = 0.0
427
428 if self.penalty_start > 0:
429
430 y = np.vstack((x[:self.penalty_start, :],
431 y))
432
433 return y
434
436 """Feasibility of the constraint.
437
438 From the interface "Constraint".
439
440 Parameters
441 ----------
442 beta : Numpy array. The variable to check for feasibility.
443
444 Examples
445 --------
446 >>> import numpy as np
447 >>> from parsimony.functions.penalties import L0
448 >>>
449 >>> np.random.seed(42)
450 >>> x = np.random.rand(10, 1) * 2.0 - 1.0
451 >>> l0 = L0(c=5.0)
452 >>> l0.feasible(x)
453 False
454 >>> l0.feasible(l0.proj(x))
455 True
456 """
457 if self.penalty_start > 0:
458 beta_ = beta[self.penalty_start:, :]
459 else:
460 beta_ = beta
461
462 return maths.norm0(beta_) <= self.c
463
464
465 -class LInf(properties.AtomicFunction,
466 properties.Penalty,
467 properties.Constraint,
468 properties.ProximalOperator,
469 properties.ProjectionOperator):
470 """The proximal operator of the L-infinity function
471
472 f(x) = l * (||x||_inf - c),
473
474 where ||x||_inf is the L-infinity loss function. The constrainted version
475 has the form
476
477 ||x||_inf <= c.
478
479 Parameters
480 ----------
481 l : Non-negative float. The Lagrange multiplier, or regularisation
482 constant, of the function.
483
484 c : Float. The limit of the constraint. The function is feasible if
485 ||x||_inf <= c. The default value is c=0, i.e. the default is a
486 regularisation formulation.
487
488 penalty_start : Non-negative integer. The number of columns, variables
489 etc., to be exempt from penalisation. Equivalently, the first index
490 to be penalised. Default is 0, all columns are included.
491 """
492 - def __init__(self, l=1.0, c=0.0, penalty_start=0):
493
494 self.l = float(l)
495 self.c = float(c)
496 self.penalty_start = int(penalty_start)
497
499 """Function value.
500
501 From the interface "Function".
502
503 Parameters
504 ----------
505 x : Numpy array. The point at which to evaluate the function.
506
507 Example
508 -------
509 >>> import numpy as np
510 >>> from parsimony.functions.penalties import LInf
511 >>> import parsimony.utils.maths as maths
512 >>>
513 >>> np.random.seed(42)
514 >>> x = np.random.rand(10, 1)
515 >>> linf = LInf(l=1.1)
516 >>> linf.f(x) - 1.1 * maths.normInf(x)
517 0.0
518 """
519 if self.penalty_start > 0:
520 x_ = x[self.penalty_start:, :]
521 else:
522 x_ = x
523
524 return self.l * (maths.normInf(x_) - self.c)
525
526 - def prox(self, x, factor=1.0, **kwargs):
527 """The corresponding proximal operator.
528
529 From the interface "ProximalOperator".
530
531 Examples
532 --------
533 >>> import numpy as np
534 >>> from parsimony.functions.penalties import LInf
535 >>> import parsimony.utils.maths as maths
536 >>>
537 >>> np.random.seed(42)
538 >>> x = np.random.rand(10, 1)
539 >>> linf = LInf(l=1.45673045, c=0.5)
540 >>> linf_prox = linf.prox(x)
541 >>> linf_prox
542 array([[ 0.37454012],
543 [ 0.5 ],
544 [ 0.5 ],
545 [ 0.5 ],
546 [ 0.15601864],
547 [ 0.15599452],
548 [ 0.05808361],
549 [ 0.5 ],
550 [ 0.5 ],
551 [ 0.5 ]])
552 >>> linf_proj = linf.proj(x)
553 >>> linf_proj
554 array([[ 0.37454012],
555 [ 0.5 ],
556 [ 0.5 ],
557 [ 0.5 ],
558 [ 0.15601864],
559 [ 0.15599452],
560 [ 0.05808361],
561 [ 0.5 ],
562 [ 0.5 ],
563 [ 0.5 ]])
564 >>> np.linalg.norm(linf_prox - linf_proj)
565 7.2392821740411278e-09
566 """
567 if self.penalty_start > 0:
568 x_ = x[self.penalty_start:, :]
569 else:
570 x_ = x
571
572 l = self.l * factor
573 l1 = L1(c=l)
574 y = x_ - l1.proj(x_)
575
576
577
578 if self.penalty_start > 0:
579 y = np.vstack((x[:self.penalty_start, :],
580 y))
581
582 return y
583
585 """The corresponding projection operator.
586
587 From the interface "ProjectionOperator".
588
589 Examples
590 --------
591 >>> import numpy as np
592 >>> from parsimony.functions.penalties import LInf
593 >>>
594 >>> np.random.seed(42)
595 >>> x = np.random.rand(10, 1) * 2.0 - 1.0
596 >>> linf = LInf(c=0.618)
597 >>> linf.proj(x)
598 array([[-0.25091976],
599 [ 0.618 ],
600 [ 0.46398788],
601 [ 0.19731697],
602 [-0.618 ],
603 [-0.618 ],
604 [-0.618 ],
605 [ 0.618 ],
606 [ 0.20223002],
607 [ 0.41614516]])
608 """
609 if self.penalty_start > 0:
610 x_ = x[self.penalty_start:, :]
611 else:
612 x_ = x
613
614 if maths.normInf(x_) <= self.c:
615 return x
616
617 y = np.copy(x_)
618 y[y > self.c] = self.c
619 y[y < -self.c] = -self.c
620
621
622 if self.penalty_start > 0:
623 y = np.vstack((x[:self.penalty_start, :],
624 y))
625
626 return y
627
629 """Feasibility of the constraint.
630
631 From the interface "Constraint".
632
633 Parameters
634 ----------
635 x : Numpy array. The variable to check for feasibility.
636
637 Examples
638 --------
639 >>> import numpy as np
640 >>> from parsimony.functions.penalties import LInf
641 >>>
642 >>> np.random.seed(42)
643 >>> x = np.random.rand(10, 1) * 2.0 - 1.0
644 >>> linf = LInf(c=0.618)
645 >>> linf.feasible(x)
646 False
647 >>> linf.feasible(linf.proj(x))
648 True
649 """
650 if self.penalty_start > 0:
651 x_ = x[self.penalty_start:, :]
652 else:
653 x_ = x
654
655 return maths.normInf(x_) <= self.c
656
657
658 -class L2(properties.AtomicFunction,
659 properties.Penalty,
660 properties.Constraint,
661 properties.ProximalOperator,
662 properties.ProjectionOperator):
663 """The proximal operator of the L2 function with a penalty formulation
664
665 f(\beta) = l * (0.5 * ||\beta||_2 - c),
666
667 where ||\beta||_2 is the L2 loss function. The constrained version has
668 the form
669
670 0.5 * ||\beta||_2 <= c.
671
672 Parameters
673 ----------
674 l : Non-negative float. The Lagrange multiplier, or regularisation
675 constant, of the function.
676
677 c : Float. The limit of the constraint. The function is feasible if
678 0.5 * ||\beta||_2 <= c. The default value is c=0, i.e. the
679 default is a regularised formulation.
680
681 penalty_start : Non-negative integer. The number of columns, variables
682 etc., to be exempt from penalisation. Equivalently, the first index
683 to be penalised. Default is 0, all columns are included.
684 """
685 - def __init__(self, l=1.0, c=0.0, penalty_start=0):
686
687 self.l = float(l)
688 self.c = float(c)
689 self.penalty_start = int(penalty_start)
690
692 """Function value.
693
694 From the interface "Function".
695 """
696 if self.penalty_start > 0:
697 beta_ = beta[self.penalty_start:, :]
698 else:
699 beta_ = beta
700
701 return self.l * (maths.norm(beta_) - self.c)
702
703 - def prox(self, beta, factor=1.0, **kwargs):
704 """The corresponding proximal operator.
705
706 From the interface "ProximalOperator".
707 """
708 l = self.l * factor
709 if self.penalty_start > 0:
710 beta_ = beta[self.penalty_start:, :]
711 else:
712 beta_ = beta
713
714 norm = maths.norm(beta_)
715 if norm >= l:
716 beta_ *= (1.0 - l / norm) * beta_
717 else:
718 beta_ *= 0.0
719
720 if self.penalty_start > 0:
721 prox = np.vstack((beta[:self.penalty_start, :], beta_))
722 else:
723 prox = beta_
724
725 return prox
726
727 - def proj(self, beta, **kwargs):
728 """The corresponding projection operator.
729
730 From the interface "ProjectionOperator".
731
732 Examples
733 --------
734 >>> import numpy as np
735 >>> from parsimony.functions.penalties import L2
736 >>> np.random.seed(42)
737 >>> l2 = L2(c=0.3183098861837907)
738 >>> y1 = l2.proj(np.random.rand(100, 1) * 2.0 - 1.0)
739 >>> round(np.linalg.norm(y1), 15)
740 0.318309886183791
741 >>> y2 = np.random.rand(100, 1) * 2.0 - 1.0
742 >>> l2.feasible(y2)
743 False
744 >>> l2.feasible(l2.proj(y2))
745 True
746 """
747 if self.penalty_start > 0:
748 beta_ = beta[self.penalty_start:, :]
749 else:
750 beta_ = beta
751
752 norm = maths.norm(beta_)
753
754
755 if norm <= self.c:
756 return beta
757
758
759 eps = consts.FLOAT_EPSILON
760 beta_ *= self.c / (norm + eps)
761 proj = beta_
762 if self.penalty_start > 0:
763 proj = np.vstack((beta[:self.penalty_start, :], beta_))
764
765 return proj
766
768 """Feasibility of the constraint.
769
770 From the interface "Constraint".
771
772 Parameters
773 ----------
774 beta : Numpy array. The variable to check for feasibility.
775
776 Examples
777 --------
778 >>> import numpy as np
779 >>> from parsimony.functions.penalties import L2
780 >>> np.random.seed(42)
781 >>> l2 = L2(c=0.3183098861837907)
782 >>> y1 = 0.01 * (np.random.rand(50, 1) * 2.0 - 1.0)
783 >>> l2.feasible(y1)
784 True
785 >>> y2 = 10.0 * (np.random.rand(50, 1) * 2.0 - 1.0)
786 >>> l2.feasible(y2)
787 False
788 >>> y3 = l2.proj(50.0 * np.random.rand(100, 1) * 2.0 - 1.0)
789 >>> l2.feasible(y3)
790 True
791 """
792 if self.penalty_start > 0:
793 beta_ = beta[self.penalty_start:, :]
794 else:
795 beta_ = beta
796
797 return maths.norm(beta_) <= self.c + consts.FLOAT_EPSILON
798
799
800 -class L2Squared(properties.AtomicFunction,
801 properties.Gradient,
802 properties.LipschitzContinuousGradient,
803 properties.Penalty,
804 properties.Constraint,
805 properties.ProximalOperator,
806 properties.ProjectionOperator):
807 """The proximal operator of the squared L2 function with a penalty
808 formulation
809
810 f(\beta) = l * (0.5 * ||\beta||²_2 - c),
811
812 where ||\beta||²_2 is the squared L2 loss function. The constrained
813 version has the form
814
815 0.5 * ||\beta||²_2 <= c.
816
817 Parameters
818 ----------
819 l : Non-negative float. The Lagrange multiplier, or regularisation
820 constant, of the function.
821
822 c : Float. The limit of the constraint. The function is feasible if
823 0.5 * ||\beta||²_2 <= c. The default value is c=0, i.e. the
824 default is a regularised formulation.
825
826 penalty_start : Non-negative integer. The number of columns, variables
827 etc., to be exempt from penalisation. Equivalently, the first index
828 to be penalised. Default is 0, all columns are included.
829 """
830 - def __init__(self, l=1.0, c=0.0, penalty_start=0):
831
832 self.l = float(l)
833 self.c = float(c)
834 self.penalty_start = int(penalty_start)
835
837 """Function value.
838
839 From the interface "Function".
840 """
841 if self.penalty_start > 0:
842 beta_ = beta[self.penalty_start:, :]
843 else:
844 beta_ = beta
845
846 return self.l * (0.5 * np.dot(beta_.T, beta_)[0, 0] - self.c)
847
848 - def grad(self, beta):
849 """Gradient of the function.
850
851 From the interface "Gradient".
852
853 Example
854 -------
855 >>> import numpy as np
856 >>> from parsimony.functions.penalties import L2Squared
857 >>>
858 >>> np.random.seed(42)
859 >>> beta = np.random.rand(100, 1)
860 >>> l2 = L2Squared(l=3.14159, c=2.71828)
861 >>> np.linalg.norm(l2.grad(beta)
862 ... - l2.approx_grad(beta, eps=1e-4)) < 5e-10
863 True
864 >>>
865 >>> l2 = L2Squared(l=3.14159, c=2.71828, penalty_start=5)
866 >>> np.linalg.norm(l2.grad(beta)
867 ... - l2.approx_grad(beta, eps=1e-4)) < 5e-10
868 True
869 """
870 if self.penalty_start > 0:
871 beta_ = beta[self.penalty_start:, :]
872 grad = np.vstack((np.zeros((self.penalty_start, 1)),
873 self.l * beta_))
874 else:
875 beta_ = beta
876 grad = self.l * beta_
877
878
879
880
881 return grad
882
884 """Lipschitz constant of the gradient.
885 """
886 return self.l
887
888 - def prox(self, beta, factor=1.0, **kwargs):
889 """The corresponding proximal operator.
890
891 From the interface "ProximalOperator".
892 """
893 l = self.l * factor
894 if self.penalty_start > 0:
895 beta_ = beta[self.penalty_start:, :]
896 else:
897 beta_ = beta
898
899 if self.penalty_start > 0:
900 prox = np.vstack((beta[:self.penalty_start, :],
901 beta_ * (1.0 / (1.0 + l))))
902 else:
903 prox = beta_ * (1.0 / (1.0 + l))
904
905 return prox
906
907 - def proj(self, beta, **kwargs):
908 """The corresponding projection operator.
909
910 From the interface "ProjectionOperator".
911
912 Examples
913 --------
914 >>> import numpy as np
915 >>> from parsimony.functions.penalties import L2Squared
916 >>> np.random.seed(42)
917 >>> l2 = L2Squared(c=0.3183098861837907)
918 >>> y1 = l2.proj(np.random.rand(100, 1) * 2.0 - 1.0)
919 >>> round(0.5 * np.linalg.norm(y1) ** 2.0, 15)
920 0.318309886183791
921 >>> y2 = np.random.rand(100, 1) * 2.0 - 1.0
922 >>> l2.feasible(y2)
923 False
924 >>> l2.feasible(l2.proj(y2))
925 True
926 """
927 if self.penalty_start > 0:
928 beta_ = beta[self.penalty_start:, :]
929 else:
930 beta_ = beta
931
932 sqnorm = np.dot(beta_.T, beta_)[0, 0]
933
934
935 if 0.5 * sqnorm <= self.c:
936 return beta
937
938
939
940 eps = consts.FLOAT_EPSILON
941 if self.penalty_start > 0:
942 proj = np.vstack((beta[:self.penalty_start, :],
943 beta_ * np.sqrt((2.0 * self.c - eps) / sqnorm)))
944 else:
945 proj = beta_ * np.sqrt((2.0 * self.c - eps) / sqnorm)
946
947 return proj
948
950 """Feasibility of the constraint.
951
952 From the interface "Constraint".
953
954 Parameters
955 ----------
956 beta : Numpy array. The variable to check for feasibility.
957
958 Examples
959 --------
960 >>> import numpy as np
961 >>> from parsimony.functions.penalties import L2Squared
962 >>> np.random.seed(42)
963 >>> l2 = L2Squared(c=0.3183098861837907)
964 >>> y1 = 0.1 * (np.random.rand(50, 1) * 2.0 - 1.0)
965 >>> l2.feasible(y1)
966 True
967 >>> y2 = 10.0 * (np.random.rand(50, 1) * 2.0 - 1.0)
968 >>> l2.feasible(y2)
969 False
970 >>> y3 = l2.proj(50.0 * np.random.rand(100, 1) * 2.0 - 1.0)
971 >>> l2.feasible(y3)
972 True
973 """
974 if self.penalty_start > 0:
975 beta_ = beta[self.penalty_start:, :]
976 else:
977 beta_ = beta
978
979 sqnorm = np.dot(beta_.T, beta_)[0, 0]
980
981 return 0.5 * sqnorm <= self.c + consts.FLOAT_EPSILON
982
983
984 -class L1L2Squared(properties.AtomicFunction,
985 properties.Penalty,
986 properties.ProximalOperator):
987 """The proximal operator of the L1 function with an L2 constraint.
988 The function is
989
990 f(x) = l1 * ||x||_1 + Indicator(||x||²_2 <= l2),
991
992 where ||.||_1 is the L1 norm and ||.||²_2 is the squared L2 norm.
993
994 Parameters
995 ----------
996 l1 : Non-negative float. The Lagrange multiplier, or regularisation
997 constant, of the L1 norm penalty.
998
999 l2 : Non-negative float. The limit of the constraint of of the squared L2
1000 norm penalty.
1001
1002 penalty_start : Non-negative integer. The number of columns, variables
1003 etc., to be exempt from penalisation. Equivalently, the first index
1004 to be penalised. Default is 0, all columns are included.
1005 """
1006 - def __init__(self, l1=1.0, l2=1.0, penalty_start=0):
1007
1008 self.l1 = max(0.0, float(l1))
1009 self.l2 = max(0.0, float(l2))
1010 self.penalty_start = max(0, int(penalty_start))
1011
1012 - def f(self, beta):
1013 """Function value.
1014 """
1015 if self.penalty_start > 0:
1016 beta_ = beta[self.penalty_start:, :]
1017 else:
1018 beta_ = beta
1019
1020 if maths.norm(beta_) ** 2.0 > self.l2:
1021 return consts.FLOAT_INF
1022
1023 return self.l1 * maths.norm1(beta_)
1024
1025 - def prox(self, beta, factor=1.0, **kwargs):
1026 """The corresponding proximal operator.
1027
1028 From the interface "ProximalOperator".
1029 """
1030 if self.penalty_start > 0:
1031 beta_ = beta[self.penalty_start:, :]
1032 else:
1033 beta_ = beta
1034
1035 l1 = self.l1 * factor
1036 prox = (np.abs(beta_) > l1) * (beta_ - l1 * np.sign(beta_ - l1))
1037 prox *= np.sqrt(self.l2 / np.dot(prox.T, prox)[0, 0])
1038
1039 if self.penalty_start > 0:
1040 prox = np.vstack((beta[:self.penalty_start, :], prox))
1041
1042 return prox
1043
1044
1045 -class QuadraticConstraint(properties.AtomicFunction,
1046 properties.Gradient,
1047 properties.Penalty,
1048 properties.Constraint):
1049 """The proximal operator of the quadratic function
1050
1051 f(x) = l * (x'Mx - c),
1052
1053 or
1054
1055 f(x) = l * (x'M'Nx - c),
1056
1057 where M or M'N is a given symmatric positive-definite matrix. The
1058 constrained version has the form
1059
1060 x'Mx <= c,
1061
1062 or
1063
1064 x'M'Nx <= c
1065
1066 if two matrices are given.
1067
1068 Parameters
1069 ----------
1070 l : Non-negative float. The Lagrange multiplier, or regularisation
1071 constant, of the function.
1072
1073 c : Float. The limit of the constraint. The function is feasible if
1074 x'Mx <= c. The default value is c=0, i.e. the default is a
1075 regularisation formulation.
1076
1077 M : Numpy array. The given positive definite matrix. It is assumed that
1078 the first penalty_start columns must be excluded.
1079
1080 N : Numpy array. The second matrix if the factors of the positive-definite
1081 matrix are given. It is assumed that the first penalty_start
1082 columns must be excluded.
1083
1084 penalty_start : Non-negative integer. The number of columns, variables
1085 etc., to be exempt from penalisation. Equivalently, the first index
1086 to be penalised. Default is 0, all columns are included.
1087 """
1088 - def __init__(self, l=1.0, c=0.0, M=None, N=None, penalty_start=0):
1089
1090 self.l = float(l)
1091 self.c = float(c)
1092 if self.penalty_start > 0:
1093 self.M = M[:, self.penalty_start:]
1094 self.N = N[:, self.penalty_start:]
1095 else:
1096 self.M = M
1097 self.N = N
1098 self.penalty_start = penalty_start
1099
1100 - def f(self, beta):
1101 """Function value.
1102 """
1103 if self.penalty_start > 0:
1104 beta_ = beta[self.penalty_start:, :]
1105 else:
1106 beta_ = beta
1107
1108 if self.N is None:
1109 val = self.l * (np.dot(beta_.T, np.dot(self.M, beta_)) - self.c)
1110 else:
1111 val = self.l * (np.dot(beta_.T, np.dot(self.M.T,
1112 np.dot(self.N, beta_))) \
1113 - self.c)
1114
1115 return val
1116
1117 - def grad(self, beta):
1118 """Gradient of the function.
1119
1120 From the interface "Gradient".
1121 """
1122 if self.penalty_start > 0:
1123 beta_ = beta[self.penalty_start:, :]
1124 else:
1125 beta_ = beta
1126
1127 if self.N is None:
1128 grad = (2.0 * self.l) * np.dot(self.M, beta_)
1129 else:
1130 grad = (2.0 * self.l) * np.dot(self.M.T, np.dot(self.N, beta_))
1131
1132 if self.penalty_start > 0:
1133 grad = np.vstack(np.zeros((self.penalty_start, 1)), grad)
1134
1135 return grad
1136
1138 """Feasibility of the constraint.
1139
1140 From the interface "Constraint".
1141 """
1142 if self.penalty_start > 0:
1143 beta_ = beta[self.penalty_start:, :]
1144 else:
1145 beta_ = beta
1146
1147 if self.N is None:
1148 bMb = np.dot(beta_.T, np.dot(self.M, beta_))
1149 else:
1150 bMb = np.dot(beta_.T, np.dot(self.M.T, np.dot(self.N, beta_)))
1151
1152 return bMb <= self.c
1153
1154
1155 -class RGCCAConstraint(QuadraticConstraint,
1156 properties.ProjectionOperator):
1157 """Represents the quadratic function
1158
1159 f(x) = l * (x'(tau * I + ((1 - tau) / n) * X'X)x - c),
1160
1161 where tau is a given regularisation constant. The constrained version has
1162 the form
1163
1164 x'(tau * I + ((1 - tau) / n) * X'X)x <= c.
1165
1166 Parameters
1167 ----------
1168 l : Non-negative float. The Lagrange multiplier, or regularisation
1169 constant, of the function.
1170
1171 c : Float. The limit of the constraint. The function is feasible if
1172 x'(tau * I + ((1 - tau) / n) * X'X)x <= c. The default value is
1173 c=0, i.e. the default is a regularisation formulation.
1174
1175 tau : Non-negative float. The regularisation constant.
1176
1177 X : Numpy array, n-by-p. The associated data matrix. The first
1178 penalty_start columns will be excluded.
1179
1180 unbiased : Boolean. Whether the sample variance should be unbiased or not.
1181 Default is True, i.e. unbiased.
1182
1183 penalty_start : Non-negative integer. The number of columns, variables
1184 etc., to be exempt from penalisation. Equivalently, the first index
1185 to be penalised. Default is 0, all columns are included.
1186 """
1187 - def __init__(self, l=1.0, c=0.0, tau=1.0, X=None, unbiased=True,
1188 penalty_start=0):
1189
1190 self.l = max(0.0, float(l))
1191 self.c = float(c)
1192 self.tau = max(0.0, min(float(tau), 1.0))
1193 if penalty_start > 0:
1194 self.X = X[:, penalty_start:]
1195 else:
1196 self.X = X
1197 self.unbiased = bool(unbiased)
1198 self.penalty_start = max(0, int(penalty_start))
1199
1200 self.reset()
1201
1203
1204 self._U = None
1205 self._S = None
1206 self._V = None
1207
1208 - def f(self, beta):
1209 """Function value.
1210 """
1211 if self.penalty_start > 0:
1212 beta_ = beta[self.penalty_start:, :]
1213 else:
1214 beta_ = beta
1215
1216 xtMx = self._compute_value(beta_)
1217
1218 return self.l * (xtMx - self.c)
1219
1220 - def grad(self, beta):
1221 """Gradient of the function.
1222
1223 From the interface "Gradient".
1224 """
1225 if self.penalty_start > 0:
1226 beta_ = beta[self.penalty_start:, :]
1227 else:
1228 beta_ = beta
1229
1230 if self.unbiased:
1231 n = float(self.X.shape[0] - 1.0)
1232 else:
1233 n = float(self.X.shape[0])
1234
1235 if self.tau < 1.0:
1236 XtXbeta = np.dot(self.X.T, np.dot(self.X, beta_))
1237 grad = (self.tau * 2.0) * beta_ \
1238 + ((1.0 - self.tau) * 2.0 / n) * XtXbeta
1239 else:
1240 grad = (self.tau * 2.0) * beta_
1241
1242 if self.penalty_start > 0:
1243 grad = np.vstack(np.zeros((self.penalty_start, 1)),
1244 grad)
1245
1246
1247
1248
1249 return grad
1250
1252 """Feasibility of the constraint.
1253
1254 From the interface "Constraint".
1255 """
1256 if self.penalty_start > 0:
1257 beta_ = beta[self.penalty_start:, :]
1258 else:
1259 beta_ = beta
1260
1261 xtMx = self._compute_value(beta_)
1262
1263 return xtMx <= self.c
1264
1265 - def proj(self, beta, **kwargs):
1266 """The projection operator corresponding to the function.
1267
1268 From the interface "ProjectionOperator".
1269
1270 Examples
1271 --------
1272 >>> import parsimony.functions.penalties as penalties
1273 >>> import numpy as np
1274 >>> np.random.seed(42)
1275 >>>
1276 >>> X = np.random.randn(10, 10)
1277 >>> x = np.random.randn(10, 1)
1278 >>> L2 = penalties.RGCCAConstraint(c=1.0, tau=1.0, X=X, unbiased=True)
1279 >>> L2.f(x)
1280 5.7906381220390024
1281 >>> y = L2.proj(x)
1282 >>> abs(L2.f(y)) <= 2.0 * consts.FLOAT_EPSILON
1283 True
1284 >>> np.linalg.norm(y)
1285 0.99999999999999989
1286 """
1287 if self.penalty_start > 0:
1288 beta_ = beta[self.penalty_start:, :]
1289 else:
1290 beta_ = beta
1291
1292 xtMx = self._compute_value(beta_)
1293 if xtMx <= self.c + consts.FLOAT_EPSILON:
1294 return beta
1295
1296 n, p = self.X.shape
1297
1298 if self.unbiased:
1299 n_ = float(n - 1.0)
1300 else:
1301 n_ = float(n)
1302
1303 if self.tau == 1.0:
1304
1305 sqnorm = np.dot(beta_.T, beta_)
1306 eps = consts.FLOAT_EPSILON
1307 y = beta_ * np.sqrt((self.c - eps) / sqnorm)
1308
1309 else:
1310
1311 if self._U is None or self._S is None or self._V is None:
1312
1313
1314 self._V, self._S, self._U = np.linalg.svd(self.X.T,
1315 full_matrices=0)
1316 self._V = self._V.T
1317 self._U = self._U.T
1318 self._S = ((1.0 - self.tau) / n_) * (self._S ** 2.0) + self.tau
1319 self._S = self._S.reshape((min(n, p), 1))
1320
1321 atilde = np.dot(self._V, beta_)
1322 atilde2 = atilde ** 2.0
1323 ssdiff = np.dot(beta_.T, beta_)[0, 0] - np.sum(atilde2)
1324 atilde2lambdas = atilde2 * self._S
1325 atilde2lambdas2 = atilde2lambdas * self._S
1326 tau2 = self.tau ** 2.0
1327
1328 from parsimony.algorithms.utils import NewtonRaphson
1329 newton = NewtonRaphson(force_negative=True,
1330 parameter_positive=True,
1331 parameter_negative=False,
1332 parameter_zero=True,
1333 eps=consts.TOLERANCE,
1334 max_iter=30)
1335
1336 class F(properties.Function,
1337 properties.Gradient):
1338
1339 def __init__(self, tau, S, c):
1340 self.tau = tau
1341 self.S = S
1342 self.c = c
1343
1344 self.precomp = None
1345 self.precomp_mu = None
1346
1347 def f(self, mu):
1348 term1 = (self.tau \
1349 / ((1.0 + 2.0 * mu * self.tau) ** 2.0)) * ssdiff
1350
1351 self.precomp = 1.0 + (2.0 * mu) * self.S
1352 self.precomp_mu = mu
1353
1354 term2 = np.sum(atilde2lambdas * (self.precomp ** -2.0))
1355
1356 return term1 + term2 - self.c
1357
1358 def grad(self, mu):
1359 term1 = (-4.0 * tau2 \
1360 / ((1.0 + 2.0 * mu * self.tau) ** 3.0)) * ssdiff
1361
1362 if self.precomp is None or self.precomp_mu != mu:
1363 self.precomp = 1.0 + (2.0 * mu) * self.S
1364
1365 term2 = -4.0 * np.sum(atilde2lambdas2 \
1366 * (self.precomp ** -3.0))
1367
1368 self.precomp = None
1369 self.precomp_mu = None
1370
1371 return term1 + term2
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383 start_mu = 0.0
1384 mu = newton.run(F(self.tau, self._S, self.c), start_mu)
1385
1386
1387 if mu <= consts.FLOAT_EPSILON:
1388 return beta
1389
1390 if p > n:
1391 l2 = ((self._S - self.tau) \
1392 * (1.0 / ((1.0 - self.tau) / n_))).reshape((n,))
1393
1394 a = 1.0 + 2.0 * mu * self.tau
1395 b = 2.0 * mu * (1.0 - self.tau) / n_
1396 y = (beta_ - np.dot(self.X.T, np.dot(self._U,
1397 (np.reciprocal(l2 + (a / b)) \
1398 * np.dot(self._U.T,
1399 np.dot(self.X, beta_)).T).T))) * (1. / a)
1400
1401 else:
1402 l2 = ((self._S - self.tau) \
1403 * (1.0 / ((1.0 - self.tau) / n_))).reshape((p,))
1404
1405 a = 1.0 + 2.0 * mu * self.tau
1406 b = 2.0 * mu * (1.0 - self.tau) / n_
1407 y = np.dot(self._V.T, (np.reciprocal(a + b * l2) * atilde.T).T)
1408
1409 if self.penalty_start > 0:
1410 y = np.vstack((beta[:self.penalty_start, :],
1411 y))
1412
1413 return y
1414
1416 """Helper function to compute the function value.
1417
1418 Note that beta must already be sliced!
1419 """
1420 if self.unbiased:
1421 n = float(self.X.shape[0] - 1.0)
1422 else:
1423 n = float(self.X.shape[0])
1424
1425 Xbeta = np.dot(self.X, beta)
1426 val = self.tau * np.dot(beta.T, beta) \
1427 + ((1.0 - self.tau) / n) * np.dot(Xbeta.T, Xbeta)
1428
1429 return val[0, 0]
1430
1431
1432 -class RidgeSquaredError(properties.CompositeFunction,
1433 properties.Gradient,
1434 properties.StronglyConvex,
1435 properties.Penalty,
1436 properties.ProximalOperator):
1437 """Represents a ridge squared error penalty, i.e. a representation of
1438
1439 f(x) = l.((1 / (2 * n)) * ||Xb - y||²_2 + (k / 2) * ||b||²_2),
1440
1441 where ||.||²_2 is the L2 norm.
1442
1443 Parameters
1444 ----------
1445 l : Non-negative float. The Lagrange multiplier, or regularisation
1446 constant, of the function.
1447
1448 X : Numpy array (n-by-p). The regressor matrix.
1449
1450 y : Numpy array (n-by-1). The regressand vector.
1451
1452 k : Non-negative float. The ridge parameter.
1453
1454 penalty_start : Non-negative integer. The number of columns, variables
1455 etc., to except from penalisation. Equivalently, the first
1456 index to be penalised. Default is 0, all columns are included.
1457
1458 mean : Boolean. Whether to compute the squared loss or the mean
1459 squared loss. Default is True, the mean squared loss.
1460 """
1461 - def __init__(self, X, y, k, l=1.0, penalty_start=0, mean=True):
1462 self.l = max(0.0, float(l))
1463 self.X = X
1464 self.y = y
1465 self.k = max(0.0, float(k))
1466
1467 self.penalty_start = max(0, int(penalty_start))
1468 self.mean = bool(mean)
1469
1470 self.reset()
1471
1473 """Free any cached computations from previous use of this Function.
1474
1475 From the interface "Function".
1476 """
1477 self._Xty = None
1478 self._s2 = None
1479 self._V = None
1480
1482 """Function value.
1483
1484 From the interface "Function".
1485
1486 Parameters
1487 ----------
1488 x : Numpy array. Regression coefficient vector. The point at which to
1489 evaluate the function.
1490 """
1491 if self.penalty_start > 0:
1492 x_ = x[self.penalty_start:, :]
1493
1494 Xx_ = np.dot(self.X, x) \
1495 - np.dot(self.X[:, :self.penalty_start],
1496 x[:self.penalty_start, :])
1497
1498
1499 else:
1500 x_ = x
1501 Xx_ = np.dot(self.X, x_)
1502
1503 if self.mean:
1504 d = 2.0 * float(self.X.shape[0])
1505 else:
1506 d = 2.0
1507
1508 f = (1.0 / d) * np.sum((Xx_ - self.y) ** 2.0) \
1509 + (self.k / 2.0) * np.sum(x_ ** 2.0)
1510
1511 return self.l * f
1512
1513 - def grad(self, x):
1514 """Gradient of the function at beta.
1515
1516 From the interface "Gradient".
1517
1518 Parameters
1519 ----------
1520 x : Numpy array. The point at which to evaluate the gradient.
1521
1522 Examples
1523 --------
1524 >>> import numpy as np
1525 >>> from parsimony.functions.losses import RidgeRegression
1526 >>>
1527 >>> np.random.seed(42)
1528 >>> X = np.random.rand(100, 150)
1529 >>> y = np.random.rand(100, 1)
1530 >>> rr = RidgeRegression(X=X, y=y, k=3.14159265)
1531 >>> beta = np.random.rand(150, 1)
1532 >>> round(np.linalg.norm(rr.grad(beta)
1533 ... - rr.approx_grad(beta, eps=1e-4)), 9)
1534 1.3e-08
1535 """
1536 if self.penalty_start > 0:
1537 x_ = x[self.penalty_start:, :]
1538 X_ = self.X[:, self.penalty_start:]
1539 grad = np.dot(X_.T, np.dot(self.X_, x_) - self.y)
1540 del X_
1541 else:
1542 x_ = x
1543 grad = np.dot((np.dot(self.X, x_) - self.y).T, self.X).T
1544
1545 if self.mean:
1546 grad *= 1.0 / float(self.X.shape[0])
1547
1548 grad += self.k * x_
1549
1550 if self.penalty_start > 0:
1551 grad = np.vstack((np.zeros((self.penalty_start, 1)),
1552 self.l * grad))
1553 else:
1554 grad += self.l
1555
1556 return grad
1557
1559 """Lipschitz constant of the gradient.
1560
1561 From the interface "LipschitzContinuousGradient".
1562 """
1563 if self._lambda_max is None:
1564 s = np.linalg.svd(self.X, full_matrices=False, compute_uv=False)
1565
1566 self._lambda_max = np.max(s) ** 2.0
1567
1568 if len(s) < self.X.shape[1]:
1569 self._lambda_min = 0.0
1570 else:
1571 self._lambda_min = np.min(s) ** 2.0
1572
1573 if self.mean:
1574 self._lambda_max /= float(self.X.shape[0])
1575 self._lambda_min /= float(self.X.shape[0])
1576
1577 return self.l * (self._lambda_max + self.k)
1578
1580 """Returns the strongly convex parameter for the function.
1581
1582 From the interface "StronglyConvex".
1583 """
1584 if self._lambda_min is None:
1585 self._lambda_max = None
1586 self.L()
1587
1588 return self.l * (self._lambda_min + self.k)
1589
1591 """The proximal operator associated to this function.
1592
1593 Parameters
1594 ----------
1595 x : Numpy array (p-by-1). The point at which to apply the proximal
1596 operator.
1597
1598 factor : Positive float. A factor by which the Lagrange multiplier is
1599 scaled. This is usually the step size.
1600
1601 eps : Positive float. This is the stopping criterion for inexact
1602 proximal methods, where the proximal operator is approximated
1603 numerically.
1604
1605 max_iter : Positive integer. This is the maximum number of iterations
1606 for inexact proximal methods, where the proximal operator is
1607 approximated numerically.
1608
1609 index : Non-negative integer. For multivariate functions, this
1610 identifies the variable for which the proximal operator is
1611 associated.
1612
1613 From the interface "ProximalOperator".
1614 """
1615
1616 n, p = self.X.shape
1617 rho = 1.0 / self.l
1618
1619 if self._Xty is None:
1620 self._Xty = np.dot(self.X.T, self.y)
1621
1622 v = rho * x + self._Xty
1623 c = self.k + rho
1624
1625 if n >= p:
1626 if self._s2 is None or self._V is None:
1627
1628
1629
1630
1631
1632
1633 _, self._s2, self._V = np.linalg.svd(self.X)
1634 self._V = self._V.T
1635 self._s2 = self._s2.reshape((p, 1)) ** 2.0
1636
1637
1638
1639 y = np.dot(self._V,
1640 np.reciprocal(c + self._s2) * np.dot(self._V.T, v))
1641
1642
1643 else:
1644 if self._s2 is None or self._V is None:
1645
1646
1647
1648
1649
1650
1651 _, self._s2, self._V = np.linalg.svd(self.X.T)
1652 self._V = self._V.T
1653 self._s2 = self._s2.reshape((n, 1)) ** 2.0
1654
1655
1656
1657
1658 Xv = np.dot(self.X, v)
1659 y = (v - np.dot(self.X.T, np.dot(self._V,
1660 np.reciprocal(c + self._s2) \
1661 * np.dot(self._V.T, Xv)))) \
1662 * (1.0 / c)
1663
1664 return y
1665
1666
1667 -class LinearVariableConstraint(properties.IndicatorFunction,
1668 properties.Constraint,
1669 properties.ProjectionOperator):
1670 """Represents a linear constraint
1671
1672 r = Ax,
1673
1674 where both x and r are variables.
1675
1676 Parameters
1677 ----------
1678 A : Numpy or sparse scipy array. The linear map between x and r.
1679 """
1681
1682 self.A = A
1683
1684 self.penalty_start = max(0, int(penalty_start))
1685
1686 self.solver = solver
1687
1688 self.reset()
1689
1691
1692 self._inv_AtA_I = None
1693
1695 """The function value of this indicator function. The function value is
1696 0 if the constraint is feasible and infinite otherwise.
1697
1698 Parameters
1699 ----------
1700 xr : List or tuple with two elements, numpy arrays. The first element
1701 is x and the second is r.
1702 """
1703 if self.feasible(xr):
1704 return 0.0
1705 else:
1706 return np.inf
1707
1709 """Feasibility of the constraint at points x and r.
1710
1711 From the interface Constraint.
1712
1713 Parameters
1714 ----------
1715 xr : List or tuple with two elements, numpy arrays. The first element
1716 is x and the second is r.
1717 """
1718 if isinstance(xr, linalgs.MultipartArray):
1719 xr = xr.get_parts()
1720
1721 x = xr[0]
1722
1723 if self.penalty_start > 0:
1724 x_ = x[self.penalty_start:, :]
1725 else:
1726 x_ = x
1727
1728 r = xr[1]
1729
1730 Ax = [0.0] * len(self.A)
1731 for i in xrange(len(self.A)):
1732 Ax[i] = self.A[i].dot(x_)
1733 Ax = np.vstack(Ax)
1734
1735 return maths.norm(Ax - r) < consts.TOLERANCE
1736
1737 - def proj(self, xr):
1738 """The projection operator corresponding to the function.
1739
1740 From the interface ProjectionOperator.
1741
1742 Parameters
1743 ----------
1744 xr : List or tuple with two elements, numpy arrays. The first element
1745 is x and the second is r.
1746 """
1747 if isinstance(xr, linalgs.MultipartArray):
1748 xr = xr.get_parts()
1749
1750 x = xr[0]
1751 p = x.shape[0]
1752
1753
1754
1755
1756 p_limit = 1000
1757
1758 if self.penalty_start > 0:
1759 x_ = x[self.penalty_start:, :]
1760 else:
1761 x_ = x
1762
1763 r = xr[1]
1764
1765
1766 A = self.A
1767
1768
1769 Ax = [0.0] * len(A)
1770 for i in xrange(len(A)):
1771 Ax[i] = A[i].dot(x_)
1772 Ax = np.vstack(Ax)
1773 if maths.norm(Ax - r) < consts.TOLERANCE:
1774 return xr
1775
1776
1777 if self._inv_AtA_I is None:
1778
1779 AtA = A[0].T.dot(A[0])
1780 if len(A) >= 2:
1781 AtA = AtA + A[1].T.dot(A[1])
1782 if len(A) >= 3:
1783 AtA = AtA + A[2].T.dot(A[2])
1784 if len(A) >= 4:
1785 AtA = AtA + A[3].T.dot(A[3])
1786 if len(A) > 4:
1787 for i in xrange(4, len(A)):
1788 AtA = AtA + A[i].T.dot(A[i])
1789
1790 AtA_I = AtA + sparse.eye(*AtA.shape, format=AtA.format)
1791 if p >= p_limit:
1792 self._inv_AtA_I = AtA_I.todia()
1793 else:
1794 self._inv_AtA_I = np.linalg.inv(AtA_I.toarray())
1795
1796 Atr = 0.0
1797 start = 0
1798 end = 0
1799 for i in xrange(len(A)):
1800 end += A[i].shape[0]
1801 Atr += A[i].T.dot(r[start:end])
1802 start = end
1803
1804 if p >= p_limit:
1805 z = self.solver.solve(self._inv_AtA_I, Atr + x_)
1806 else:
1807 z = np.dot(self._inv_AtA_I, Atr + x_)
1808
1809 Az = [0.0] * len(A)
1810 for i in xrange(len(A)):
1811 Az[i] = A[i].dot(z)
1812 s = np.vstack(Az)
1813
1814 return linalgs.MultipartArray([z, s], vertical=True)
1815
1816
1819
1820 - def __init__(self, function, p, c=1e-4):
1821 """The sufficient condition
1822
1823 f(x + a * p) <= f(x) + c * a * grad(f(x))'p
1824
1825 for descent. This condition is sometimes called the Armijo condition.
1826
1827 Parameters
1828 ----------
1829 p : Numpy array. The descent direction.
1830
1831 c : Float, 0 < c < 1. A constant for the condition. Should be small.
1832 """
1833 self.function = function
1834 self.p = p
1835 self.c = c
1836
1837 - def f(self, x, a):
1838
1839 return self.function.f(x + a * self.p)
1840
1842 """Feasibility of the constraint at point x with step a.
1843
1844 From the interface "Constraint".
1845 """
1846 x = xa[0]
1847 a = xa[1]
1848
1849 f_x_ap = self.function.f(x + a * self.p)
1850 f_x = self.function.f(x)
1851 grad_p = np.dot(self.function.grad(x).T, self.p)[0, 0]
1852
1853
1854
1855
1856 feasible = f_x_ap <= f_x + self.c * a * grad_p
1857
1858 return feasible
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935 if __name__ == "__main__":
1936 import doctest
1937 doctest.testmod()
1938