Marginal DRO¶
- class dro.src.linear_model.marginal_dro.MarginalCVaRDRO(input_dim, model_type='svm', fit_intercept=True, solver='MOSEK', kernel='linear', alpha=1.0, L=10.0, p=2)¶
Bases:
BaseLinearDRO
Marginal-X Conditional Value-at-Risk (Conditional-CVaR) Distributionally Robust Optimization (DRO) model that only allow likelihood ratio changes in X.
This model minimizes a robust loss function for both regression and classification tasks under a CVaR constraint only for the marginal distribution of X.
Reference: [1] <https://pubsonline.informs.org/doi/10.1287/opre.2022.2363> [2] The specific model follows Equation (27) in: https://arxiv.org/pdf/2007.13982.pdf with parameters L and p.
- Parameters:
input_dim (int) – Dimension of input features. Must be ≥ 1.
model_type (str) –
Base model architecture. Supported:
'svm'
: Hinge loss (classification)'logistic'
: Logistic loss (classification)'ols'
: Least squares (regression)'lad'
: Least absolute deviation (regression)
fit_intercept (bool) – Whether to learn intercept term \(b\). Set False for pre-centered data. Defaults to True.
solver (str) – Convex optimization solver. Defaults to ‘MOSEK’.
kernel (str) – the kernel type to be used in the optimization model, default = ‘linear’
alpha (float) – CVaR risk level controlling tail expectation. Must satisfy 0 < α ≤ 1. Defaults to 1.0.
L (float) – Wasserstein radius scaling factor. Larger values increase distributional robustness. Must satisfy L ≥ 0. Defaults to 10.0.
p (int) – Order of Wasserstein distance. Supported values: 1 (Earth Mover’s Distance) or 2 (Quadratic Wasserstein). Defaults to 2.
- Raises:
If input_dim < 1
If alpha ∉ (0, 1]
If L < 0
If p < 1
- Example:
>>> model = MarginalCVaRDRO( ... input_dim=5, ... model_type='lad', ... alpha=0.95, ... L=5.0, ... p=1 ... ) >>> model.L # 5.0 >>> model.p # 1
- update(config)¶
Update Marginal CVaR-DRO model configuration parameters.
Dynamically adjusts the robustness parameters for marginal distribution shifts. Preserves existing solutions until next
fit()
call.- Parameters:
config (Dict[str, Any]) –
Configuration dictionary with optional keys:
control_name
(List[int]):Indices of features to protect against marginal shifts. Constraints:
All indices must satisfy \(0 \leq \text{index} < \text{input_dim}\)
Empty list disables marginal robustness
L
(float):Wasserstein radius scaling factor. Must satisfy \(L > 0\) Larger values increase conservativeness
p
(int):Wasserstein metric order. Must satisfy \(p \geq 1\) Supported values: 1 (EMD), 2 (Quadratic)
alpha
(float):CVaR risk level. Must satisfy \(0 < \alpha \leq 1\) \(\alpha \to 0\) focuses on average loss, \(\alpha = 1\) is worst-case
- Raises:
MarginalCVaRDROError –
If
control_name
contains invalid indicesIf
L
is non-positiveIf
p
< 1If
alpha
∉ (0, 1]If config contains unsupported keys
- Return type:
- Example:
>>> model = MarginalCVaRDRO(input_dim=5, control_name=[1,3]) >>> model.update({ ... "L": 15.0, ... "alpha": 0.95 ... }) >>> model.L # 15.0
- fit(X, y)¶
Solve the Marginal CVaR-DRO problem via convex optimization.
Constructs and solves the following distributionally robust optimization problem:
- Parameters:
X (numpy.ndarray) – Training feature matrix of shape (n_samples, n_features). Must satisfy n_features == self.input_dim.
y (numpy.ndarray) –
Target vector of shape (n_samples,). Format requirements depend on model_type:
Classification (svm/logistic):
Binary labels in {-1, +1}
No missing values allowed
Regression** (ols/lad):
Continuous real values
May contain NaN
- Returns:
Solution dictionary containing:
theta
: Weight vector of shape (n_features,)b
: Intercept term (exists if fit_intercept=True)B
: Marginal robustness dual matrix of shape (n_samples, n_samples)threshold
: CVaR threshold value
- Return type:
Dict[str, Any]
- Raises:
MarginalCVaRDROError –
If X.shape[1] != self.input_dim
If X.shape[0] != y.shape[0]
If optimization fails (problem infeasible/solver error)
If control_name indices exceed feature dimensions
- Example:
>>> model = MarginalCVaRDRO(input_dim=3, control_name=[0,2], L=5.0) >>> X = np.random.randn(100, 3) >>> y = np.sign(np.random.randn(100)) >>> sol = model.fit(X, y) >>> print(sol["theta"].shape) # (3,) >>> print(sol["B"].shape) # (2, 2)