Linear Bayesian DRO

We discuss the DRO models based on misspecified parametric distribution assumptions. They are often imposed in newsvendor or portfolio loss with normal or exponential family; regression with gaussian assumptions.

Frequentist

Fit a single parametric nominal distribution to the observed data, then solve an f-DRO problem around that fitted distribution. Here we estimate a joint Gaussian distribution for (X, y) using its sample mean and maximum-likelihood covariance. We draw 500 joint samples from this fitted Gaussian and pass them to Chi2DRO.fit() for robust linear regression.

This is a finite Monte Carlo approximation to the parametric f-DRO problem. Increasing the simulated sample count improves the approximation; eps separately controls the chi-square ambiguity radius. The Gaussian parameters are point estimates, with no posterior sampling.

[1]:
import numpy as np
from dro.linear_model.chi2_dro import Chi2DRO

# Observed regression data.
rng = np.random.default_rng(42)
X_observed = rng.normal(size=(100, 2))
y_observed = X_observed @ np.array([1.5, -2.0]) + rng.normal(scale=0.5, size=100)

# Fit one joint Gaussian nominal distribution and approximate it by sampling.
joint_observed = np.column_stack((X_observed, y_observed))
mean_hat = joint_observed.mean(axis=0)
cov_hat = np.cov(joint_observed, rowvar=False, ddof=0)
joint_nominal = rng.multivariate_normal(mean_hat, cov_hat, size=500)

frequentist_model = Chi2DRO(input_dim=2, model_type='ols', solver='CLARABEL')
frequentist_model.update({'eps': 0.1})
frequentist_params = frequentist_model.fit(joint_nominal[:, :-1], joint_nominal[:, -1])

print('Coefficients:', np.round(frequentist_params['theta'], 3))
print(f'Robust objective: {frequentist_model.robust_obj:.3f}')
print(f'Observed-data MSE: {frequentist_model.score(X_observed, y_observed):.3f}')

Coefficients: [ 1.443 -2.019]
Robust objective: 0.367
Observed-data MSE: 0.256

Bayesian

For the Bayesian-DRO model, we provide additional model type called newsvendor loss. The default setup is as follows: \(\ell(\theta;(X, Y)) = 3 (Y - \theta)_+ + (\theta - Y)_+\). In this case, X is the unit vector. That corresponds to exponential gamma conjugate.

That is, when conducting fitting, resampling, we ignore the existence of \(X\).

Both Bayesian examples explicitly set eps=0.1 to illustrate a positive-radius ambiguity set. The newsvendor example uses SCS for its exponential-cone formulation and fit_intercept=False, since its loss depends only on the order quantity.

[2]:
## Newsvendor Example
import numpy as np
from dro.linear_model.chi2_dro import *
from dro.linear_model.bayesian_dro import *

from dro.data.dataloader_regression import regression_basic

feature_dim = 1
sample_num = 1000
np.random.seed(42)
X = np.ones((sample_num, feature_dim))
y = np.random.exponential(2, sample_num)

reg_model = BayesianDRO(
    input_dim=feature_dim, model_type='newsvendor',
    eps=0.1, solver='SCS', fit_intercept=False,
)
reg_model.update({'posterior_param_num': 1, 'distribution_class': 'Exponential', 'distance_type': 'KL'})
reg_model.fit(X, y)
[2]:
{'theta': [3.5978596108504233], 'b': 0}
[3]:
reg_model.distance_type
[3]:
'KL'
[4]:
## Regression Example
from dro.linear_model.chi2_dro import *
from dro.linear_model.bayesian_dro import *

from dro.data.dataloader_regression import regression_basic

feature_dim = 5
X, y = regression_basic(num_samples = 100, d = feature_dim, noise = 1)

reg_model = BayesianDRO(input_dim=feature_dim, model_type='ols', eps=0.1, solver='CLARABEL')

reg_model.update({'posterior_param_num': 5, 'distance_type': 'chi2'})
reg_model.fit(X, y)
sample lool 0
sample lool 1
sample lool 2
sample lool 3
sample lool 4
[4]:
{'theta': [61.057566360599026,
  98.39982638076957,
  62.38125553686774,
  55.18121760481207,
  35.213916290192095],
 'b': array(-0.77980186)}