Personalized Loss

In the following, we present a case to show how to incorporate the personalized loss and plug it into different DRO losses based on cvxpy and pytorch.

Exact Solver

\(f\)-divergence DRO

Across \(f\)-divergence DRO, the adaptive loss can be easily modified as follows. Below, we modify the standard loss into the quantile regression: \(\ell((\theta, b);(X, Y)) = 3(Y - \theta^{\top}X - b)^+ + (\theta^{\top}X + b - Y)^+\).

[1]:
import cvxpy as cp
import numpy as np
from dro.src.linear_model.wasserstein_dro import *
from dro.src.linear_model.chi2_dro import *
X = np.array([[1, 1], [2, 1], [3, 1], [4,1]])
y = np.array([1, 1, 0, 0])
model = Chi2DRO(input_dim = 2, model_type = 'quantile')


from types import MethodType

def _loss(self, X, y):
    return 3 * np.maximum(y - X @ self.theta - self.b, 0) + np.maximum(X @ self.theta + self.b - y, 0)

def _cvx_loss(self, X, y, theta, b):
    return 3 * cp.pos(y - X @ theta - b) + 1 * cp.pos(X @ theta + b - y)

model._loss = MethodType(_loss, model)
model._cvx_loss = MethodType(_cvx_loss, model)
for k in range(5):
    model.update({'eps': 0.05 * (k + 1)})
    print(model.fit(X, y))
{'theta': [-0.48891007299787476, 0.0], 'b': array(1.9556403)}
{'theta': [-0.47103180655319055, 0.0], 'b': array(1.88412725)}
{'theta': [-0.4651670885530432, 0.0], 'b': array(1.86066836)}
{'theta': [-0.4620192279713162, 0.0], 'b': array(1.84807691)}
{'theta': [-0.4599744797040242, 0.0], 'b': array(1.83989792)}
/opt/anaconda3/lib/python3.9/site-packages/dro/src/linear_model/base.py:61: UserWarning: Unsupported model_type: quantile. Default supported types are svm, logistic, ols, lad. Please define your personalized loss.
  warnings.warn(f"Unsupported model_type: {model_type}. Default supported types are svm, logistic, ols, lad. Please define your personalized loss.", UserWarning)

Wasserstein DRO

To adjust Wasserstein DRO, besides modifying the _cvx_loss (and _loss) functions, we also need to modify the _penalization function to adjust the regularization component, where the regularization part denotes the additional part besides the empirical objective. More specifically, in the previous quantile regression example,

[3]:
from types import MethodType
def _penalization(self, theta):
    theta_K = theta
    if self.p == 1:
        dual_norm = np.inf
    elif self.p != 'inf':
        dual_norm = 1 / (1 - 1 / self.p)
    else:
        dual_norm = 1
    if self.kappa == 'inf':
        return 3 * cp.norm(self.cost_inv_transform @ theta_K, dual_norm)
    else:
        return cp.maximum(cp.norm(self.cost_inv_transform @ theta_K, dual_norm), 1 / self.kappa)

[7]:
from dro.linear_model.wasserstein_dro import *
X = np.array([[1, 1], [2, 1], [3, 1], [4,1]])
y = np.array([1, 1, 0, 0])

model = WassersteinDRO(input_dim = 2, model_type = 'quantile')
model._loss = MethodType(_loss, model)
model._cvx_loss = MethodType(_cvx_loss, model)
# additional adjustment for the penalization function
model._penalization = MethodType(_penalization, model)

model.update({'eps': 0.05})
print('small eps', model.fit(X, y))

model.update({'eps': 0.1})
print('large eps', model.fit(X, y))
small eps {'theta': [-0.5000000000064543, -8.42946033492792e-09], 'b': array(2.00000001)}
large eps {'theta': [-0.4999999999261157, 1.1546461152356625e-07], 'b': array(1.99999988)}

Neural Approximation