Skip to content

Commit 0ddd5d7

Browse files
author
Stephane Gaiffas
committed
TICK-367 Adds ProxOscar and removes useless weights_types attribute in ProxSortedL1
1 parent 6af54a2 commit 0ddd5d7

File tree

15 files changed

+310
-117
lines changed

15 files changed

+310
-117
lines changed

doc/modules/api.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ This module contains all the proximal operators available in tick.
146146
optim.prox.ProxPositive
147147
optim.prox.ProxEquality
148148
optim.prox.ProxSlope
149+
optim.prox.ProxOscar
149150
optim.prox.ProxTV
150151
optim.prox.ProxBinarsity
151152

setup.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -535,6 +535,7 @@ def add_dir_name(dir_name, filenames):
535535
"prox_multi.cpp",
536536
"prox_equality.cpp",
537537
"prox_slope.cpp",
538+
"prox_oscar.cpp",
538539
"prox_binarsity.cpp"],
539540
"h_files": ["prox.h",
540541
"prox_separable.h",
@@ -549,6 +550,7 @@ def add_dir_name(dir_name, filenames):
549550
"prox_multi.h",
550551
"prox_equality.h",
551552
"prox_slope.h",
553+
"prox_oscar.h",
552554
"prox_binarsity.h"],
553555
"swig_files": ["prox_module.i"],
554556
"module_dir": "./tick/optim/prox/",

tick/optim/prox/prox_oscar.py

Lines changed: 26 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,43 @@
11
# License: BSD 3 clause
22

3-
# -*- coding: utf8 -*-
4-
53
from tick.optim.prox.base import Prox
64
import numpy as np
75

8-
from tick.optim.prox.build.prox import ProxSortedL1 as _ProxSortedL1
9-
from tick.optim.prox.build.prox import WeightsType_bh, \
10-
WeightsType_oscar
11-
12-
13-
# TODO: put also the OSCAR weights
14-
# TODO: we should be able to put any weights we want...
6+
from tick.optim.prox.build.prox import ProxOscar as _ProxOscar
157

168

17-
class ProxSortedL1(Prox):
18-
"""Proximal operator of sorted L1 penalization
9+
class ProxOscar(Prox):
10+
"""Proximal operator of the OSCAR penalization.
11+
This penalization combines L1 penalization with a clustering penalization,
12+
that induces exact equality of weights corresponding to correlated features,
13+
so that clusters can be represented by a single coefficient.
14+
This penalization is therefore particularly relevant in high-dimensional
15+
problems with strong features correlation.
1916
2017
Parameters
2118
----------
2219
strength : `float`
2320
Level of penalization
2421
25-
fdr : `float`, default=0.6
26-
Desired False Discovery Rate for detection of non-zeros in
27-
the coefficients
28-
29-
weights_type : "bh" | "oscar", default="bh"
30-
31-
* If "bh", we use Benjamini-Hochberg weights, under a Gaussian
32-
error assumption, and expect a FDR control
33-
* If "oscar", there is no FDR control, and we get the OSCAR
34-
penalization, see notes below for references
22+
ratio : `float`
23+
The Oscar ratio parameter, with ratio >= 0. For ratio = 0 this is L1
24+
regularization, while a large ratio provides only the clustering effect.
3525
3626
range : `tuple` of two `int`, default=`None`
3727
Range on which the prox is applied. If `None` then the prox is
3828
applied on the whole vector
3929
40-
Attributes
41-
----------
42-
weights : `np.array`, shape=(n_coeffs,)
43-
The weights used in the penalization. They are automatically
44-
setted, depending on the ``weights_type`` and ``fdr``
45-
parameters.
30+
positive : `bool`, default=`False`
31+
If True, apply an extra projection onto the set of vectors with
32+
non-negative entries
4633
4734
Notes
4835
-----
49-
Uses the stack-based algorithm for FastProxL1 from
36+
This penalization was introduced in
37+
* Simultaneous regression shrinkage, variable selection and clustering of
38+
predictors with OSCAR, by Bondell H.D. and Reich B.J., Biometrics. 2008
39+
40+
It uses the stack-based algorithm for FastProxL1 from
5041
5142
* SLOPE--Adaptive Variable Selection via Convex Optimization, by
5243
Bogdan, M. and Berg, E. van den and Sabatti, C. and Su, W. and Candes, E. J.
@@ -58,59 +49,28 @@ class ProxSortedL1(Prox):
5849
"writable": True,
5950
"cpp_setter": "set_strength"
6051
},
61-
"fdr": {
52+
"ratio": {
6253
"writable": True,
63-
"cpp_setter": "set_fdr"
64-
},
65-
"_weights_type": {
66-
"writable": False,
67-
"cpp_setter": "set_weights_type"
54+
"cpp_setter": "set_ratio"
6855
},
6956
"positive": {
7057
"writable": True,
7158
"cpp_setter": "set_positive"
72-
},
73-
"weights": {
74-
"writable": False,
7559
}
7660
}
7761

78-
def __init__(self, strength: float, fdr: float=0.6,
79-
weights_type: str="bh", range: tuple=None,
62+
def __init__(self, strength: float, ratio: float, range: tuple=None,
8063
positive: bool=False):
8164
Prox.__init__(self, range)
8265
self.strength = strength
83-
self.fdr = fdr
84-
self.weights_type = weights_type
66+
self.ratio = ratio
8567
self.positive = positive
8668
self.weights = None
8769
if range is None:
88-
self._prox = _ProxSortedL1(self.strength, self.fdr,
89-
self._weights_type,
90-
self.positive)
70+
self._prox = _ProxOscar(self.strength, self.positive)
9171
else:
92-
self._prox = _ProxSortedL1(self.strength, self.fdr,
93-
self._weights_type,
94-
self.range[0], self.range[1],
95-
self.positive)
96-
97-
@property
98-
def weights_type(self):
99-
if self._weights_type == WeightsType_bh:
100-
return "bh"
101-
elif self._weights_type == WeightsType_oscar:
102-
return "oscar"
103-
104-
@weights_type.setter
105-
def weights_type(self, val):
106-
if val == "bh":
107-
self._set("_weights_type", WeightsType_bh)
108-
elif val == "oscar":
109-
self._set("_weights_type", WeightsType_oscar)
110-
raise NotImplementedError("``oscar`` weights.")
111-
else:
112-
raise ValueError("``weights_type`` must be either 'bh' "
113-
"or 'oscar'")
72+
self._prox = _ProxOscar(self.strength, self.range[0], self.range[1],
73+
self.positive)
11474

11575
def _call(self, coeffs: np.ndarray, t: float, out: np.ndarray):
11676
self._prox.call(coeffs, t, out)
@@ -130,8 +90,3 @@ def value(self, coeffs: np.ndarray) -> float:
13090
Value of the penalization at ``coeffs``
13191
"""
13292
return self._prox.value(coeffs)
133-
134-
def _as_dict(self):
135-
dd = Prox._as_dict(self)
136-
del dd["weights"]
137-
return dd

tick/optim/prox/prox_slope.py

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,9 @@ class ProxSlope(Prox):
2525
Range on which the prox is applied. If `None` then the prox is
2626
applied on the whole vector
2727
28-
Attributes
29-
----------
30-
weights : `np.array`, shape=(n_coeffs,)
31-
The weights used in the penalization. They are automatically
32-
setted, depending on the ``weights_type`` and ``fdr``
33-
parameters.
28+
positive : `bool`, default=`False`
29+
If True, apply an extra projection onto the set of vectors with
30+
non-negative entries
3431
3532
Notes
3633
-----
@@ -53,9 +50,6 @@ class ProxSlope(Prox):
5350
"positive": {
5451
"writable": True,
5552
"cpp_setter": "set_positive"
56-
},
57-
"weights": {
58-
"writable": False,
5953
}
6054
}
6155

@@ -65,7 +59,6 @@ def __init__(self, strength: float, fdr: float=0.6, range: tuple=None,
6559
self.strength = strength
6660
self.fdr = fdr
6761
self.positive = positive
68-
self.weights = None
6962
if range is None:
7063
self._prox = _ProxSlope(self.strength, self.fdr, self.positive)
7164
else:
@@ -91,8 +84,3 @@ def value(self, coeffs: np.ndarray) -> float:
9184
Value of the penalization at ``coeffs``
9285
"""
9386
return self._prox.value(coeffs)
94-
95-
def _as_dict(self):
96-
dd = Prox._as_dict(self)
97-
del dd["weights"]
98-
return dd

tick/optim/prox/src/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ add_library(tick_prox EXCLUDE_FROM_ALL
88
prox_zero.cpp prox_zero.h
99
prox_sorted_l1.cpp prox_sorted_l1.h
1010
prox_slope.cpp prox_slope.h
11+
prox_oscar.cpp prox_oscar.h
1112
prox_tv.cpp prox_tv.h
1213
prox_l1w.cpp prox_l1w.h
1314
prox_elasticnet.cpp prox_elasticnet.h

tick/optim/prox/src/prox_oscar.cpp

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// License: BSD 3 clause
2+
3+
#include "prox_oscar.h"
4+
5+
ProxOscar::ProxOscar(double strength,
6+
double ratio,
7+
bool positive)
8+
: ProxSortedL1(strength, positive) {
9+
set_ratio(ratio);
10+
}
11+
12+
ProxOscar::ProxOscar(double strength,
13+
double ratio,
14+
ulong start,
15+
ulong end,
16+
bool positive)
17+
: ProxSortedL1(strength, start, end, positive) {
18+
set_ratio(ratio);
19+
}
20+
21+
const std::string ProxOscar::get_class_name() const {
22+
return "ProxOscar";
23+
}
24+
25+
void ProxOscar::compute_weights(void) {
26+
if (!weights_ready) {
27+
ulong size = end - start;
28+
weights = ArrayDouble(size);
29+
for (ulong i = 0; i < size; i++) {
30+
weights[i] = strength * (ratio * (size - i - 1) + 1);
31+
}
32+
weights_ready = true;
33+
}
34+
}
35+
36+
double ProxOscar::get_ratio() const {
37+
return ratio;
38+
}
39+
40+
void ProxOscar::set_ratio(double ratio) {
41+
if (ratio < 0) {
42+
TICK_ERROR("Ratio should be non-negative");
43+
} else {
44+
if (ratio != this->ratio) {
45+
weights_ready = false;
46+
this->ratio = ratio;
47+
}
48+
}
49+
}

tick/optim/prox/src/prox_oscar.h

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#ifndef TICK_OPTIM_PROX_SRC_PROX_OSCAR_H_
2+
#define TICK_OPTIM_PROX_SRC_PROX_OSCAR_H_
3+
4+
// License: BSD 3 clause
5+
6+
#include "prox_sorted_l1.h"
7+
8+
class ProxOscar : public ProxSortedL1 {
9+
protected:
10+
void compute_weights(void) override;
11+
double ratio;
12+
13+
public:
14+
ProxOscar(double strength, double ratio, bool positive);
15+
16+
ProxOscar(double strength,
17+
double ratio,
18+
ulong start,
19+
ulong end,
20+
bool positive);
21+
22+
const std::string get_class_name() const override;
23+
24+
virtual double get_ratio() const;
25+
26+
virtual void set_ratio(double ratio);
27+
};
28+
29+
#endif // TICK_OPTIM_PROX_SRC_PROX_OSCAR_H_

tick/optim/prox/src/prox_slope.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
ProxSlope::ProxSlope(double strength,
66
double false_discovery_rate,
77
bool positive)
8-
: ProxSortedL1(strength, WeightsType::bh, positive) {
8+
: ProxSortedL1(strength, positive) {
99
this->false_discovery_rate = false_discovery_rate;
1010
}
1111

@@ -14,7 +14,7 @@ ProxSlope::ProxSlope(double strength,
1414
ulong start,
1515
ulong end,
1616
bool positive)
17-
: ProxSortedL1(strength, WeightsType::bh, start, end, positive) {
17+
: ProxSortedL1(strength, start, end, positive) {
1818
this->false_discovery_rate = false_discovery_rate;
1919
}
2020

tick/optim/prox/src/prox_slope.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ class ProxSlope : public ProxSortedL1 {
2929
inline void set_false_discovery_rate(double false_discovery_rate) {
3030
if (false_discovery_rate <= 0 || false_discovery_rate >= 1) {
3131
TICK_ERROR("False discovery rate must be in (0, 1) but received "
32-
<< false_discovery_rate)
32+
<< false_discovery_rate)
3333
}
3434
if (false_discovery_rate != this->false_discovery_rate) {
3535
weights_ready = false;

tick/optim/prox/src/prox_sorted_l1.cpp

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,16 @@
33
#include "prox_sorted_l1.h"
44

55
ProxSortedL1::ProxSortedL1(double strength,
6-
WeightsType weights_type,
76
bool positive)
8-
: Prox(strength, positive) {
9-
this->weights_type = weights_type;
7+
: Prox(strength, positive) {
108
weights_ready = false;
119
}
1210

1311
ProxSortedL1::ProxSortedL1(double strength,
14-
WeightsType weights_type,
1512
ulong start,
1613
ulong end,
1714
bool positive)
18-
: Prox(strength, start, end, positive) {
19-
this->weights_type = weights_type;
15+
: Prox(strength, start, end, positive) {
2016
weights_ready = false;
2117
}
2218

0 commit comments

Comments
 (0)